docs: define layered property schemas and projections

This commit is contained in:
Kinneyzhang 2026-08-26 18:21:43 +08:00
parent 30ac302d47
commit 2973dd3021
2 changed files with 724 additions and 14 deletions

View File

@ -10,10 +10,56 @@ completion criteria. Implementation order belongs in a separate delivery plan;
historical names and compatibility policy do not shape the target architecture. historical names and compatibility policy do not shape the target architecture.
User mental cost is a hard constraint of this proposal: one capability has one canonical User mental cost is a hard constraint of this proposal: one capability has one canonical
name and one recommended spelling. Internal algorithms, backend nodes, wrappers, and identity, canonical name, and recommended spelling. A small set of aliases may exist
compatibility aliases must not become peer public concepts. Ordinary applications need only as parse-time input sugar and must not become peer public concepts. Internal
to learn only Text, Box, and Component; Fragment, layout details, and lower-level algorithms, backend nodes, wrappers, and compatibility aliases must not become peer
packages enter through progressive disclosure. public concepts. Ordinary applications need to learn only Text, Box, and Component;
Fragment, layout details, and lower-level packages enter through progressive disclosure.
## 0. Design Decision Summary
Ordinary ETAF users learn only Text, Box, Component, and these structural forms:
```elisp
"plain text"
(text PROPS "styled text")
(box PROPS CHILD...)
(row PROPS CHILD...)
(column PROPS CHILD...)
(flex PROPS CHILD...)
(grid PROPS CHILD...)
```
`row/column/flex/grid` are only layout forms of Box. User properties fall into metadata,
semantics/events, visual style, and parent participation. They use semantically aligned
standard CSS names, a small set of explicit ETAF/Ebox-specific properties, and a small
set of parse-time aliases. Users do not interact with the Ebox, TP, or Emacs backend.
Standalone Ebox users access the same visual forms as inert data syntax through
`ebox-build`, but have no Component, Fragment, expr, slot, or lifecycle.
Framework integrators use only typed Text/Box constructors, LayoutConfig, and the render
and commit boundaries. They do not reconstruct DSL, rerun cascade, or generate Emacs
text properties.
Internal execution has only four layers:
```text
Author API
→ Canonical Model
→ Render Plan (Ebox geometry + TP paint + ETAF semantics)
→ Emacs Adapter commit
```
Core ownership remains unchanged: ETAF Runtime owns identity/lifecycle/reactivity; ECSS
owns selectors/cascade/computed style; Ebox owns measurement/geometry; TP owns paint
priority/transactions; only the final adapter owns Emacs text properties such as
`face`, `display`, and `keymap`.
Ordinary user documentation does not mention NormalizedView, PropertyDefinition,
ProjectionPlan, node IDs, RangeAnchor, or TP slots. The framework integration reference
describes typed constructors; only internal architecture sections describe facts, plans,
patches, and journals.
## 1. Final Model ## 1. Final Model
@ -393,9 +439,10 @@ Rules:
- Flex/Grid participation is stored on the child Box, but a change dirties only the - Flex/Grid participation is stored on the child Box, but a change dirties only the
corresponding parent layout owner for geometry; corresponding parent layout owner for geometry;
- ECSS cascade output must retain the impact set above; - ECSS cascade output must retain the impact set above;
- the ETAF/Ebox candidate normalization pipeline parses each property fact once, then - ETAF/Ebox candidate normalization parses the author plist once and produces canonical
passes it by owner/impact to Runtime metadata, ECSS, Ebox geometry, and the appropriate declarations/metadata facts; ECSS computes the computed facts once; projection then
TP adapter; owners do not reparse the original plist; passes them by owner/impact to Runtime, Ebox, and TP, and no owner reparses the original
plist;
- a multi-impact fact such as border or font may project to both geometry and paint - a multi-impact fact such as border or font may project to both geometry and paint
fields, but its computed value materializes only once and paint-only state is not fields, but its computed value materializes only once and paint-only state is not
duplicated in the Ebox node; duplicated in the Ebox node;
@ -423,6 +470,294 @@ failure preserves the previous generation. Unlike browsers, which commonly treat
inapplicable CSS property as having no effect, this framework does not silently ignore inapplicable CSS property as having no effect, this framework does not silently ignore
invalid combinations. invalid combinations.
### 3.1 Canonical Property Composition of Author Forms
The following compositions are the normative public schemas. Shorthands expand
immediately upon entering the canonical registry and do not create a second set of owner
or impact rules.
```text
SharedSourceMetadata = :key :class :id
ETAFOnlyMetadata = :ref :role :aria-* :on-* :use
ETAFSourceMetadata = SharedSourceMetadata + ETAFOnlyMetadata
EboxSourceMetadata = SharedSourceMetadata
TextStyle =
:font-family :font-size :font-weight :font-style
:color :background-color
:text-decoration-line :text-decoration-color :text-decoration-style
:wrap-mode (word | grapheme | none)
BoxFrame =
:outer
:width/:min-width/:max-width
:height/:min-height/:max-height
:box-sizing
margin/padding shorthands and logical/physical longhands
border shorthands and width/style/color longhands
:overflow :visibility
inherited standard text style source such as font/color
non-inherited surface source such as :background-color
NormalConfig = :text-align
RowConfig = :item-gap :cross-align
ColumnConfig = :item-gap :cross-align
FlexConfig =
:flex-direction :flex-wrap :flex-flow
:justify-content :align-items :align-content
:gap :row-gap :column-gap
GridConfig =
:grid-template-columns :grid-template-rows
:grid-auto-columns :grid-auto-rows :grid-auto-flow
:justify-items :align-items :justify-content :align-content
:gap :row-gap :column-gap
FlexParticipation =
:order :flex :flex-grow :flex-shrink :flex-basis :align-self
GridParticipation =
:order :grid-column :grid-row :align-self :justify-self
```
The six visual forms are produced only through composition:
```text
text = SurfaceSourceMetadata + TextStyle + one String
box = SurfaceSourceMetadata + BoxFrame + NormalConfig + ParentParticipation + children
row = SurfaceSourceMetadata + BoxFrame + RowConfig + ParentParticipation + children
column = SurfaceSourceMetadata + BoxFrame + ColumnConfig + ParentParticipation + children
flex = SurfaceSourceMetadata + BoxFrame + FlexConfig + ParentParticipation + children
grid = SurfaceSourceMetadata + BoxFrame + GridConfig + ParentParticipation + children
```
`SurfaceSourceMetadata` resolves to ETAFSourceMetadata on an ETAF surface and to
EboxSourceMetadata on a standalone Ebox surface. `:item-gap` and `:cross-align` are Ebox
Row/Column extensions, not CSS `gap`/`align-items`: the former inserts fixed spacing
along the main axis; the latter accepts `start|center|end|stretch` and controls the cross
axis. Flex/Grid `:gap` retains standard CSS shorthand semantics and expands to both
row-gap and column-gap.
The final direct parent selects `ParentParticipation` as Default,
FlexParticipation, or GridParticipation. A candidate cannot carry mutually exclusive
Flex-only and Grid-only fields simultaneously. Text accepts neither BoxFrame,
LayoutConfig, nor Participation; wrap it in an explicit Box when those capabilities are
required. The Ebox DSL does not accept ETAFOnlyMetadata.
### 3.2 Naming Layers and Backend Projection
Names in the author layer belong to four namespaces: CSS, ETAF, Ebox, and the optional
Emacs adapter. A standard CSS name is adopted only when its applicable objects, value
grammar, defaults/inheritance, impact, and observable behavior match CSS or form an
explicit subset. A capability that is conceptually similar but semantically different
retains a precise ETAF/Ebox name; familiarity does not justify an incorrect standard
name.
The target public API classifies historical and backend names as follows:
```text
:bgcolor → exact alias of :background-color
:font-height → removed from core; optional Emacs adapter source only
:font-slant → exact alias of :font-style with identical value grammar
:border-top-p → retained Ebox boolean shorthand for top width/style
:grid-column-span → Ebox positive-integer shorthand for :grid-column (span N)
:grid-row-span → Ebox positive-integer shorthand for :grid-row (span N)
:face → not a core canonical property
:display → private Ebox/Emacs lowering field
:content → private legacy runtime storage field
```
`:wrap-mode` is not equivalent to CSS `white-space`, `overflow-wrap`, `word-break`, or
`line-break`. It therefore remains the sole public name in the Ebox namespace, with its
value restricted to `word`, `grapheme`, or `none`. If complete, semantically equivalent
CSS properties are implemented in the future, one explicit migration must replace it;
the two naming systems must not be retained indefinitely.
Standard CSS shorthands have exactly one expansion rule, including `:margin`,
`:padding`, `:border`, `:gap`, `:flex`, `:flex-flow`, `:grid-column`, and `:grid-row`.
The canonical registry uses namespaced IDs such as `ebox/font-size` and
`ebox/background-color`; authors write only keywords and never see internal IDs.
Every definition records its naming domain and relationship to standards explicitly:
```text
PropertyDefinition {
authorName
canonicalId
aliases
namespace: css | etaf | ebox | emacs-adapter
standardReference
semanticSubset
acceptedContexts
valueGrammar
initial
inherits
impactSet
owner
projections
}
```
An alias must have exactly the same contexts, value grammar, owner, impact, and
projections as its canonical property, and it cannot transform the value. A shorthand
performs one-to-many expansion; an adapter source resolves a composite backend value.
An alias resolves to canonicalId immediately at the parse entry point, so computed
style, caches, diff, Ebox, TP, and Rust never see it. PropertyDefinition registration
rejects canonical/alias collisions. Writing both the canonical name and an alias—or
repeating an alias—in the same declaration is an error rather than an implicit
last-wins rule. The source spelling may be retained only in diagnostic provenance.
Property references list aliases under the canonical property, for example:
```text
background-color
Canonical: :background-color
Aliases: :bgcolor
```
They do not list `bgcolor` as a second capability. In the first phase, aliases are
registered by the owning package's schema; process-global user aliases are not exposed.
An upper-level package that needs an alias must scope it to its own schema and must not
pollute the global Ebox visual-property domain.
`:border-top-p` is not an alias but a retained Ebox boolean shorthand: `t` expands to
`:border-top-width 1` + `:border-top-style solid`, while `nil` expands to width 0 + style
none. Color comes independently from `:border-top-color`, `:border-color`, or the default
foreground. Use standard `:border-top` or its longhands when full control is required.
Mixing `:border-top-p` with its width/style outputs in the same declaration is an error;
different cascade sources still override one another normally through ECSS.
`:grid-column-span` and `:grid-row-span` are likewise not aliases. They accept only a
positive integer and respectively expand to the corresponding Grid shorthand's `span N`
value; mixing either with `:grid-column` or `:grid-row` in the same declaration is an
error. `:font-slant` is an exact alias only when its value grammar is identical to
`:font-style`. The Emacs face-height semantics of `:font-height` must not masquerade as
CSS `font-size`.
The Emacs text property names `face`, `display`, `keymap`, `mouse-face`, `help-echo`,
`invisible`, `line-prefix`, `wrap-prefix`, and sticky properties belong only to the final
adapter. Ebox core, ETAF Runtime, ECSS, and Rust neither produce nor consume these names
directly. Reuse of a named Emacs face, when needed, is available only through an
optional adapter's explicit `:emacs-face` source input. That input immediately resolves
to standard font/paint facts and computes their impact; the raw face symbol does not
enter the canonical IR.
A typical projection is:
```text
:font-weight bold
→ ebox/font-weight, impact {geometry, paint}
→ Ebox Text measurement weight
+ TP face-weight contribution
→ Emacs adapter emits face :weight
:width 120
→ ebox/width, impact {geometry}
→ Ebox GeometryPatch
→ Emacs adapter may emit display space width
```
### 3.3 Unified Property Facts and Projection Layer
The unified layer is not a direct `author keyword → Emacs text property` dictionary.
One property may affect both measurement and paint; several properties may merge into
one Emacs `face`; and a `display` produced by layout may have no direct author property.
The internal stages must therefore distinguish:
```text
CanonicalDeclaration {
propertyId
specifiedValue
sourceSpelling
sourceProvenance
}
SelectorSubjectFact {
classList
id
semanticSourceHandle
}
RuntimeMetadataFact {
propertyId
value
sourceHandle
}
ComputedPropertyFact {
propertyId
computedValue
winningProvenance
impactSet
sourceHandle
}
ProjectionPlan {
metadataFacts
measurementFacts
geometryFacts
paintFacts
}
```
Normalization parses author input once and simultaneously produces immutable
CanonicalDeclaration, SelectorSubjectFact, and RuntimeMetadataFact artifacts.
`:class/:id` do not enter CSS declarations; SelectorSubjectFact constructs the ECSS subject
directly before cascade. Selector and semantic projection share the same parsed source
value for `:id` rather than rereading the plist independently. ECSS cascade/inheritance
receives SelectorSubjectFact + declarations and produces immutable
ComputedPropertyFact artifacts. `:key`, `:ref`, `:on-*`, and similar properties do not
enter ECSS and appear only in RuntimeMetadataFact. “Materialize once” means that each
stage produces its own immutable artifact once, not that one object mutates across
stages.
A pure property projector consumes computed-style facts and metadata facts and generates
ProjectionPlan according to the PropertyDefinition owner/impact/projections fields.
Every consumer references the same fact rather than reparsing or copying its computed
value. The TP paint projector is the only module that converts paintFacts into
ContributionPlan. The ETAF semantic projector converts metadataFacts into
Runtime/SemanticPlan.
A registry is not one process-global universal table. Each owning package supplies an
immutable SchemaRegistry conforming to the same schema protocol; a surface composes a
SurfaceSchemaSet:
```text
ETAF SurfaceSchemaSet = ETAF registry + Ebox registry
Ebox SurfaceSchemaSet = Ebox registry
ETAF + Emacs adapter = ETAF registry + Ebox registry + adapter registry
```
Each canonicalId has exactly one owning definition. Canonical/alias collisions are
checked when composing the SchemaSet. This avoids both reverse dependencies and
duplicate sources of truth.
Logical modules remain small and complete:
- package property schema: a package-scoped PropertyDefinition registry;
- SurfaceSchemaSet: conflict-free composition of package registries;
- property normalizer: author names, shorthands, and value grammar;
- property projector: computed/metadata facts → backend-neutral ProjectionPlan;
- Ebox layout projector: measurement/geometry facts → LayoutPlan;
- TP paint projector: paint facts → ContributionPlan;
- ETAF semantic projector: metadata facts → Runtime/SemanticPlan;
- Emacs adapter: GeometryPatch + PaintPatch + SemanticPlan → text-property operations.
Consequently, an Ebox canonical node stores no paint-only field; TP stores no geometry;
the Emacs adapter does not reinterpret CSS; and Rust receives only canonical facts/plans,
not `face`, `display`, or an Elisp plist. Debugging and performance tools must expose the
complete chain, for example:
```text
:font-weight
→ ebox/font-weight
→ {geometry, paint}
→ measurement/font-weight + paint/font-weight
→ face :weight
```
## 4. Module Owners ## 4. Module Owners
### 4.1 ETAF Core ### 4.1 ETAF Core
@ -563,6 +898,27 @@ The Playground `.etaf` manifest belongs to the Playground. If a general compiler
in the future, it must lower to the same View/Component contract and must not create a in the future, it must lower to the same View/Component contract and must not create a
second Runtime. second Runtime.
### 4.6 Public Surfaces Layered by Consumer
- Ordinary ETAF users use only `etaf-view`, Component, Text/Box forms, ETAF metadata/
events, canonical CSS-aligned properties, explicitly listed ETAF/Ebox extensions such
as `:outer` and `:wrap-mode`, and parse-time aliases; they do not call Ebox, TP, or the
Emacs adapter.
- Standalone Ebox users use inert data forms with the same visual structural vocabulary
through `ebox-build`; they do not own Component, slot, expr, or lifecycle.
- Framework integrators use `ebox-text-create`, `ebox-box-create`, typed LayoutConfig,
RangeAnchor, and the TP contribution contract; they do not reconstruct DSL, pass
shorthands, or run a second cascade.
- The optional Emacs adapter is an explicit opt-in, independent schema namespace. Only
a surface that loads the adapter accepts `:emacs-face`; otherwise normalization
reports the unknown namespace/property precisely.
- `ebox--*`, `etaf--*`, `tp--*`, `:ebox-type`, `:node-id`, `:region-id`, Emacs text
properties, and runtime storage fields are all private.
The user guide describes only the first layer; the Ebox DSL guide describes the second;
the integration reference describes the third; only architecture documentation
describes canonical IR, identity owners, impact projection, and the commit protocol.
## 5. Dependency Direction ## 5. Dependency Direction
```text ```text
@ -624,6 +980,21 @@ scans already completed by Rust.
The final gates must cover: The final gates must cover:
- structural equivalence of Text/Box/Fragment; - structural equivalence of Text/Box/Fragment;
- rejection of canonical/alias collisions when package registries compose a
SurfaceSchemaSet;
- exact aliases disappear after normalization, while each shorthand expands exactly
once and conflicts with its owned outputs in the same declaration;
- the optional adapter namespace is accepted only by explicitly opted-in surfaces and
reports a precise error when the adapter is not loaded;
- CanonicalDeclaration, SelectorSubjectFact, RuntimeMetadataFact, and
ComputedPropertyFact remain immutable across stages, and the selector subject is
produced exactly once before cascade;
- ProjectionPlan contains only paintFacts, and only the TP projector may produce
ContributionPlan;
- Row/Column accept only the `:item-gap/:cross-align` extensions rather than borrowing
the different semantics of CSS `:gap/:align-items`;
- a multi-impact computed value such as geometry + paint materializes once and is
referenced by both paths;
- strings normalize only to Text, and both author DSLs reject `box :content`; - strings normalize only to Text, and both author DSLs reject `box :content`;
- Text is always inline and has exactly one string payload; nested, empty, or multiple - Text is always inline and has exactly one string payload; nested, empty, or multiple
payloads are errors; payloads are errors;
@ -677,6 +1048,7 @@ Non-goals:
- making Flex/Grid item an author node, visual wrapper, or independent identity; - making Flex/Grid item an author node, visual wrapper, or independent identity;
- allowing applications to inject raw Ebox Nodes into View; - allowing applications to inject raw Ebox Nodes into View;
- making layout modes Components or a second set of Runtime nodes; - making layout modes Components or a second set of Runtime nodes;
- retaining old Host aliases or optional shorthand spellings in Core; - retaining old Host/layout-form aliases in Core; property aliases follow only the
unified mechanism in Section 3.2;
- allowing Playground, UI, or database packages to own Core protocols; - allowing Playground, UI, or database packages to own Core protocols;
- adding App precompilation artifacts before real compile cost and runtime benefit exist. - adding App precompilation artifacts before real compile cost and runtime benefit exist.

View File

@ -7,10 +7,53 @@
本文只定义目标模型、模块 owner、依赖方向和完成条件。实现顺序属于单独的 本文只定义目标模型、模块 owner、依赖方向和完成条件。实现顺序属于单独的
实施计划;历史名称和兼容策略不参与目标架构设计。 实施计划;历史名称和兼容策略不参与目标架构设计。
用户心智成本是本提案的硬约束:同一能力只有一个规范名称和一种推荐写法;内部 用户心智成本是本提案的硬约束:同一能力只有一个 canonical identity、规范名称和
算法、后端节点、wrapper 和兼容别名不能作为并列公共概念。普通应用只需学习 推荐写法;少量 alias 只能是 parse-time 输入糖,不能成为并列公共概念。内部算法、
后端节点、wrapper 和兼容别名不能作为并列公共概念。普通应用只需学习
Text、Box、ComponentFragment、布局细节和底层包通过渐进披露进入。 Text、Box、ComponentFragment、布局细节和底层包通过渐进披露进入。
## 0. 设计决策摘要
普通 ETAF 用户只学习 Text、Box、Component以及下面的结构 form
```elisp
"plain text"
(text PROPS "styled text")
(box PROPS CHILD...)
(row PROPS CHILD...)
(column PROPS CHILD...)
(flex PROPS CHILD...)
(grid PROPS CHILD...)
```
`row/column/flex/grid` 都只是 Box 的布局形式。用户属性分为 metadata、语义/事件、
视觉样式和 parent participation采用语义一致的标准 CSS 名称、少量明确的
ETAF/Ebox 个性属性和少量 parse-time alias。用户不接触 Ebox/TP/Emacs backend。
独立 Ebox 用户通过 `ebox-build` 使用相同视觉 form 的 inert data syntax但没有
Component、Fragment、expr、slot 或 lifecycle。
框架集成者只使用 typed Text/Box constructors、LayoutConfig、render 和 commit
边界;不重新拼 DSL不重新执行 cascade不生成 Emacs text properties。
内部执行只有四层:
```text
Author API
→ Canonical Model
→ Render Plan (Ebox geometry + TP paint + ETAF semantics)
→ Emacs Adapter commit
```
核心 owner 不变ETAF Runtime 拥有 identity/lifecycle/reactivityECSS 拥有
selector/cascade/computed styleEbox 拥有 measurement/geometryTP 拥有 paint
priority/transaction最终 adapter 才拥有 `face`、`display`、`keymap` 等 Emacs
text properties。
普通用户文档不出现 NormalizedView、PropertyDefinition、ProjectionPlan、node ID、
RangeAnchor 或 TP slot。框架 integration reference 才描述 typed constructors
只有内部架构章节描述 facts、plans、patches 和 journals。
## 1. 最终模型 ## 1. 最终模型
ETAF/Ebox 的表示链按职责分层: ETAF/Ebox 的表示链按职责分层:
@ -358,9 +401,9 @@ ETAF 也不提供 `spacer` 类型。没有子节点的 Box 就是空 Box。
- `:outer`、Layout variant 属于 structure + geometry - `:outer`、Layout variant 属于 structure + geometry
- Flex/Grid participation 存储在 child Box但变化时只使对应父布局 owner geometry dirty - Flex/Grid participation 存储在 child Box但变化时只使对应父布局 owner geometry dirty
- ECSS cascade 输出必须保留上述 impact set - ECSS cascade 输出必须保留上述 impact set
- ETAF/Ebox candidate normalization pipeline 只解析一次 property fact再按 owner/impact - ETAF/Ebox candidate normalization 只解析一次 author plist 并产生 canonical
交给 Runtime metadata、ECSS、Ebox geometry 和对应 TP adapter各 owner 不重新解析 declarations/metadata factsECSS 只计算一次 computed factsprojection 再按
原 plist owner/impact 交给 Runtime、Ebox 和 TP各 owner 不重新解析原 plist
- border/font 等 multi-impact fact 可以同时投影 geometry 与 paint 字段,但 computed - border/font 等 multi-impact fact 可以同时投影 geometry 与 paint 字段,但 computed
value 只 materialize 一次,不在 Ebox node 中复制 paint-only 状态; value 只 materialize 一次,不在 Ebox node 中复制 paint-only 状态;
- TP 只消费已经解析的 paint contribution不判断布局合法性 - TP 只消费已经解析的 paint contribution不判断布局合法性
@ -381,6 +424,274 @@ ETAF 也不提供 `spacer` 类型。没有子节点的 Box 就是空 Box。
Layout 和 child participation 时只验证最终 candidate任何失败保留上一代。与 Layout 和 child participation 时只验证最终 candidate任何失败保留上一代。与
浏览器对不适用 CSS 属性常采取 no-effect 不同,本框架不静默忽略无效组合。 浏览器对不适用 CSS 属性常采取 no-effect 不同,本框架不静默忽略无效组合。
### 3.1 作者 form 的规范属性组合
下面的组合是规范性公共 schemashorthand 在进入 canonical registry 时立即展开,
不产生第二套 owner 或 impact 规则。
```text
SharedSourceMetadata = :key :class :id
ETAFOnlyMetadata = :ref :role :aria-* :on-* :use
ETAFSourceMetadata = SharedSourceMetadata + ETAFOnlyMetadata
EboxSourceMetadata = SharedSourceMetadata
TextStyle =
:font-family :font-size :font-weight :font-style
:color :background-color
:text-decoration-line :text-decoration-color :text-decoration-style
:wrap-mode (word | grapheme | none)
BoxFrame =
:outer
:width/:min-width/:max-width
:height/:min-height/:max-height
:box-sizing
margin/padding shorthands and logical/physical longhands
border shorthands and width/style/color longhands
:overflow :visibility
inherited standard text style source such as font/color
non-inherited surface source such as :background-color
NormalConfig = :text-align
RowConfig = :item-gap :cross-align
ColumnConfig = :item-gap :cross-align
FlexConfig =
:flex-direction :flex-wrap :flex-flow
:justify-content :align-items :align-content
:gap :row-gap :column-gap
GridConfig =
:grid-template-columns :grid-template-rows
:grid-auto-columns :grid-auto-rows :grid-auto-flow
:justify-items :align-items :justify-content :align-content
:gap :row-gap :column-gap
FlexParticipation =
:order :flex :flex-grow :flex-shrink :flex-basis :align-self
GridParticipation =
:order :grid-column :grid-row :align-self :justify-self
```
六个视觉 form 只通过组合产生:
```text
text = SurfaceSourceMetadata + TextStyle + one String
box = SurfaceSourceMetadata + BoxFrame + NormalConfig + ParentParticipation + children
row = SurfaceSourceMetadata + BoxFrame + RowConfig + ParentParticipation + children
column = SurfaceSourceMetadata + BoxFrame + ColumnConfig + ParentParticipation + children
flex = SurfaceSourceMetadata + BoxFrame + FlexConfig + ParentParticipation + children
grid = SurfaceSourceMetadata + BoxFrame + GridConfig + ParentParticipation + children
```
`SurfaceSourceMetadata` 在 ETAF surface 取 ETAFSourceMetadata在独立 Ebox surface
取 EboxSourceMetadata。`:item-gap` 与 `:cross-align` 是 Ebox Row/Column extension
不是 CSS `gap`/`align-items`;前者沿主轴插入固定间距,后者值为
`start|center|end|stretch` 并控制 cross axis。Flex/Grid 的 `:gap` 保持标准 CSS
shorthand 语义,同时展开 row-gap 和 column-gap。
`ParentParticipation` 由最终直接父级选择为 Default、FlexParticipation 或
GridParticipation同一个 candidate 不能同时携带互斥的 Flex-only 与 Grid-only
字段。Text 不接受 BoxFrame、LayoutConfig 或 Participation需要这些能力时显式包
Box。Ebox DSL 不接受 ETAFOnlyMetadata。
### 3.2 命名层与后端投影
作者层名称分为 CSS、ETAF、Ebox 和 optional Emacs-adapter 四个 namespace。只有
适用对象、值 grammar、默认/继承、impact 和可观察行为与 CSS 一致或是明确子集时,
才采用标准 CSS 名称;概念相近但语义不同的能力保留精确的 ETAF/Ebox 名称,不能
为了熟悉感使用错误的标准名称。
目标公共 API 对历史/后端名称分类处理:
```text
:bgcolor → exact alias of :background-color
:font-height → removed from core; optional Emacs adapter source only
:font-slant → exact alias of :font-style with identical value grammar
:border-top-p → retained Ebox boolean shorthand for top width/style
:grid-column-span → Ebox positive-integer shorthand for :grid-column (span N)
:grid-row-span → Ebox positive-integer shorthand for :grid-row (span N)
:face → not a core canonical property
:display → private Ebox/Emacs lowering field
:content → private legacy runtime storage field
```
`:wrap-mode` 不与 CSS `white-space`、`overflow-wrap`、`word-break` 或 `line-break`
等价,因此保留为 Ebox namespace 的唯一公共名称,值限定为 `word`、`grapheme`、
`none`。如果未来实现完整且语义一致的 CSS 属性,应以一次明确迁移替换它,而不是
长期同时保留两套名称。
标准 CSS shorthand 只有一套展开规则,例如 `:margin`、`:padding`、`:border`、
`:gap`、`:flex`、`:flex-flow`、`:grid-column` 和 `:grid-row`。canonical registry 使用
namespaced ID例如 `ebox/font-size`、`ebox/background-color`);作者只写 keyword
不会看到内部 ID。
每个定义显式记录命名领域与标准关系:
```text
PropertyDefinition {
authorName
canonicalId
aliases
namespace: css | etaf | ebox | emacs-adapter
standardReference
semanticSubset
acceptedContexts
valueGrammar
initial
inherits
impactSet
owner
projections
}
```
alias 必须与 canonical property 的 context、value grammar、owner、impact 和 projection
完全相同,且不能转换 value需要一对多展开的是 shorthand需要解析后端复合值的
是 adapter source。alias 在 parse 入口立即解析为 canonicalIdcomputed style、
cache、diff、Ebox、TP 和 Rust 都不再看到它。PropertyDefinition 注册时拒绝
canonical/alias collision同一声明同时写 canonical 与 alias或重复 alias直接
报错,不采用隐式 last-wins。source spelling 只可保存在 diagnostics provenance。
属性参考把 alias 列在 canonical property 下,例如:
```text
background-color
Canonical: :background-color
Aliases: :bgcolor
```
不把 `bgcolor` 单独列成第二个功能。第一阶段 alias 由 owning package 的 schema
注册,不开放 process-global 用户 alias上层包若需要 alias必须限制在自己的
schema scope不能污染全局 Ebox visual property domain。
`:border-top-p` 不是 alias而是保留的 Ebox boolean shorthand`t` 展开为
`:border-top-width 1` + `:border-top-style solid``nil` 展开为 width 0 + style none
颜色由 `:border-top-color`/`:border-color`/默认前景提供。需要完整控制时使用标准
`:border-top` 或 longhands。同一声明混写 `:border-top-p` 与其 width/style 输出必须
报错;不同 cascade source 仍按 ECSS 正常覆盖。
`:grid-column-span`/`:grid-row-span` 同样不是 alias它们只接受正整数并分别展开
为对应 Grid shorthand 的 `span N` value`:grid-column`/`:grid-row` 在同一声明
混写时报错。`:font-slant` 只有在值 grammar 与 `:font-style` 完全相同时才作为 exact
alias`:font-height` 的 Emacs face-height 语义不冒充 CSS `font-size`
Emacs text property 名称 `face`、`display`、`keymap`、`mouse-face`、`help-echo`、
`invisible`、`line-prefix`、`wrap-prefix` 和 sticky properties 只属于最终 adapter。
Ebox core、ETAF Runtime、ECSS 和 Rust 都不直接生成或消费这些名字。若需要复用
named Emacs face只能由可选 adapter 提供显式 `:emacs-face` source input立即
解析为标准 font/paint facts 并计算 impact原始 face symbol 不进入 canonical IR。
一次典型投影是:
```text
:font-weight bold
→ ebox/font-weight, impact {geometry, paint}
→ Ebox Text measurement weight
+ TP face-weight contribution
→ Emacs adapter emits face :weight
:width 120
→ ebox/width, impact {geometry}
→ Ebox GeometryPatch
→ Emacs adapter may emit display space width
```
### 3.3 统一属性事实与投影层
统一层不是 `author keyword → Emacs text property` 的直接字典。一项属性可能同时
影响测量与 paint多项属性也可能合并为一个 Emacs `face`;布局结果产生的
`display` 又可能没有直接作者属性。内部阶段必须区分:
```text
CanonicalDeclaration {
propertyId
specifiedValue
sourceSpelling
sourceProvenance
}
SelectorSubjectFact {
classes
id
sourceHandle
sourceProvenance
}
RuntimeMetadataFact {
propertyId
value
sourceHandle
}
ComputedPropertyFact {
propertyId
computedValue
winningProvenance
impactSet
sourceHandle
}
ProjectionPlan {
metadataFacts
measurementFacts
geometryFacts
paintFacts
}
```
normalization 对 author input 只解析一次,并同时产生 immutable
CanonicalDeclaration、SelectorSubjectFact 和 RuntimeMetadataFact。`:class/:id` 不
进入 CSS declarationsSelectorSubjectFact 在 cascade 前直接构造 ECSS subject。
`:id` 的同一个 parsed source value 由 selector 与 semantic projection 共享,不各自
重读 plist。ECSS cascade/inheritance 接收 SelectorSubjectFact + declarations产生
immutable ComputedPropertyFact。`:key`、`:ref`、`:on-*` 等不进入 ECSS只进入
RuntimeMetadataFact。这里的“一次 materialize”指每个阶段只生成一次自己的
immutable artifact不是让同一对象跨阶段变异。
纯 property projector 在 cascade 后消费 computed style facts 与 metadata facts
PropertyDefinition 的 owner/impact/projections 生成 ProjectionPlan各 consumer
引用同一 fact不重新解析或复制 computed value。TP paint projector 是唯一把
paintFacts 转为 ContributionPlan 的模块ETAF semantic projector 把 metadataFacts
转为 Runtime/SemanticPlan。
Registry 不是 process-global 万能表。每个 owning package 提供 immutable
SchemaRegistry并遵守同一个 schema protocol一个 surface 组合成 SurfaceSchemaSet
```text
ETAF SurfaceSchemaSet = ETAF registry + Ebox registry
Ebox SurfaceSchemaSet = Ebox registry
ETAF + Emacs adapter = ETAF registry + Ebox registry + adapter registry
```
每个 canonicalId 只有一个 owning definitioncanonical/alias collision 在
SchemaSet composition 时检查。这样既没有反向依赖,也没有重复事实源。
逻辑模块保持小而完整:
- package property schemapackage-scoped PropertyDefinition registry
- SurfaceSchemaSet无冲突组合各 package registry
- property normalizerauthor name、shorthand、value grammar
- property projectorcomputed/metadata facts → backend-neutral ProjectionPlan
- Ebox layout projectormeasurement/geometry facts → LayoutPlan
- TP paint projectorpaint facts → ContributionPlan
- ETAF semantic projectormetadata facts → Runtime/SemanticPlan
- Emacs adapterGeometryPatch + PaintPatch + SemanticPlan → text-property operations。
因此 Ebox canonical node 不保存 paint-only 字段TP 不保存 geometryEmacs adapter
不重新解释 CSSRust 只接收 canonical facts/plans 而不接收 `face`、`display` 或
Elisp plist。调试与性能工具必须能显示完整链路例如
```text
:font-weight
→ ebox/font-weight
→ {geometry, paint}
→ measurement/font-weight + paint/font-weight
→ face :weight
```
## 4. 模块 owner ## 4. 模块 owner
### 4.1 ETAF Core ### 4.1 ETAF Core
@ -508,6 +819,24 @@ Ebox surface 与 TP/Emacs 提交对齐的唯一机制。
Playground 的 `.etaf` manifest 属于 Playground。未来通用 compiler 若存在,必须 Playground 的 `.etaf` manifest 属于 Playground。未来通用 compiler 若存在,必须
降低到同一套 View/Component 契约,不得创建第二套 Runtime。 降低到同一套 View/Component 契约,不得创建第二套 Runtime。
### 4.6 按使用者分层的公共面
- 普通 ETAF 用户只使用 `etaf-view`、Component、Text/Box form、ETAF metadata/
events、canonical CSS-aligned properties以及明确列出的 ETAF/Ebox extensions
(如 `:outer`、`:wrap-mode`)和 parse-time aliases不调用 Ebox/TP/Emacs adapter。
- 独立 Ebox 用户通过 `ebox-build` 使用同一视觉结构词汇的 inert data form不拥有
Component、slot、expr 或 lifecycle。
- 框架集成者使用 `ebox-text-create`、`ebox-box-create`、typed LayoutConfig、
RangeAnchor 和 TP contribution contract不重新拼 DSL、不传 shorthand、不运行
第二次 cascade。
- optional Emacs adapter 是显式 opt-in 的独立 schema namespace只有加载 adapter
的 surface 才接受 `:emacs-face`,否则 normalization 精确报未知 namespace/property。
- `ebox--*`、`etaf--*`、`tp--*`、`:ebox-type`、`:node-id`、`:region-id`、Emacs
text properties 和 runtime storage fields 全部私有。
用户指南只描述第一层Ebox DSL 指南描述第二层integration reference 描述第三层;
架构文档才描述 canonical IR、identity owner、impact projection 和 commit protocol。
## 5. 依赖方向 ## 5. 依赖方向
```text ```text
@ -563,6 +892,15 @@ cascade、布局或文字扫描。
最终门禁必须覆盖: 最终门禁必须覆盖:
- Text/Box/Fragment 结构等价; - Text/Box/Fragment 结构等价;
- package registries 组合 SurfaceSchemaSet 时拒绝 canonical/alias collision
- exact alias 在 normalization 后消失shorthand 唯一展开且与 owned outputs 混写报错;
- optional adapter namespace 只有显式 opt-in surface 接受,未加载时精确报错;
- CanonicalDeclaration、SelectorSubjectFact、RuntimeMetadataFact 和
ComputedPropertyFact 分阶段 immutableselector subject 在 cascade 前只生成一次;
- ProjectionPlan 只含 paintFacts只有 TP projector 能产生 ContributionPlan
- Row/Column 只接受 `:item-gap/:cross-align` extension不借用 CSS
`:gap/:align-items` 的不同语义;
- geometry+paint 等 multi-impact computed value 只 materialize 一次并被两路引用;
- 字符串只规范化为 Text两个作者 DSL 都拒绝 `box :content` - 字符串只规范化为 Text两个作者 DSL 都拒绝 `box :content`
- Text 固定 inline 且恰好一个字符串 payload嵌套/空/多 payload 均报错; - Text 固定 inline 且恰好一个字符串 payload嵌套/空/多 payload 均报错;
- ETAF/Ebox 五个 Box form 的 canonical shape、defaults 和错误跨接口一致; - ETAF/Ebox 五个 Box form 的 canonical shape、defaults 和错误跨接口一致;
@ -603,6 +941,6 @@ typed Layout 尚未实现,目标公共 View 也不保留 `raw-ebox`。
- 不把 Flex/Grid item 做成作者节点、视觉 wrapper 或独立 identity - 不把 Flex/Grid item 做成作者节点、视觉 wrapper 或独立 identity
- 不允许应用把原始 Ebox Node 注入 View - 不允许应用把原始 Ebox Node 注入 View
- 不把布局模式做成 Component 或第二套 Runtime 节点; - 不把布局模式做成 Component 或第二套 Runtime 节点;
- 不在 Core 中保留旧 Host alias 或可选短写法 - 不在 Core 中保留旧 Host/layout-form aliasproperty alias 只服从 3.2 的统一机制
- 不让 Playground、UI 或数据库包拥有 Core 协议; - 不让 Playground、UI 或数据库包拥有 Core 协议;
- 不在没有真实编译成本和运行时收益前增加 App 预编译产物。 - 不在没有真实编译成本和运行时收益前增加 App 预编译产物。