docs: converge target module boundaries

This commit is contained in:
Kinneyzhang 2026-08-26 11:56:34 +08:00
parent c3c92f6741
commit 46c5a69a7a
2 changed files with 456 additions and 522 deletions

View File

@ -1,220 +1,281 @@
# ETAF Module Responsibilities and Target Architecture (Unimplemented Proposal)
> Status: design proposal. This is not the current public API or a delivered
> implementation. The current contract remains [`architecture.en.md`](../architecture.en.md),
> [`user-guide.en.md`](../user-guide.en.md), and the passing tests.
> Status: design proposal. This is not the delivered API. Current behavior remains
> defined by [`architecture.en.md`](../architecture.en.md),
> [`user-guide.en.md`](../user-guide.en.md), and passing tests.
This proposal defines clean-slate ownership boundaries for ETAF, Ebox, ECSS,
TP, `etaf-ui`, `etaf-sqlite`, the performance recorder, and both Playgrounds. It
describes the target architecture; current compatibility names are not treated as
the architecture itself.
This document defines only the target model, module owners, dependency direction, and
completion criteria. Delivery sequencing belongs in a separate implementation plan;
historical names and compatibility do not shape the target architecture.
The target parts below are not delivered API until code, tests, and real benchmarks
prove them. This proposal does not change the current Host registry, View grammar,
or package dependencies.
## 1. Minimal stable model
ETAF has four representations:
## 1. Final model
```text
Component semantics and ownership
Text / Box View structure
View: Text / Box / Fragment
Ebox geometry and render plan
Ebox: measurement, layout, geometry patches
TP / Emacs final submission
TP + Emacs adapter: paint and final submission
```
The public concepts are:
The main public concepts are `Text`, `Box`, and `Component`. `Fragment` is advanced
structure, `Host` is an internal Renderer term, and an Ebox Node is a backend object.
| Concept | Owns | Does not own |
| --- | --- | --- |
| `Text` | text content, typography, inline properties, wrapping input | subtree layout, business state, lifecycle |
| `Box` | size, surface, children, and layout | Component identity, Data, Actions |
| `Component` | reusable semantics, props, slots, state, Context, Actions, lifecycle | pixel measurement and layout algorithms |
The complete normalized View shape is:
`Host` is an internal Renderer term for a primitive lowered to a backend. It is not a
third public visual taxonomy. An `Ebox Node` is a lower-level geometry/render object.
```text
View = Text
| Box
| Fragment
| ComponentCall
| RawEbox
```
ETAF should keep two orthogonal Box properties instead of turning these concepts into
Components:
`expr` and `slot` are computation/projection mechanisms, not visual nodes. `RawEbox`
is an opaque low-level escape and does not inherit normal View semantics.
## 2. Text, Box, and Fragment
### 2.1 Text
Text is a leaf that owns strings and inline Text runs, typography/paint, text
measurement and wrapping input, and optional identity/semantic/event properties.
Text defaults to `:outer 'inline`. It cannot own Box/Component children or establish
row, column, flex, or grid layout. Use an outer Box for padding, border, dimensions, or
child layout.
### 2.2 Box axes
```text
:outer = inline | block
:layout = normal | row | column | flex | grid
```
`:outer` describes how the Box participates in its parent's ordinary layout;
`:layout` describes how the Box arranges its own children. `normal` is the clearer
user-facing name for ordinary content layout; the Renderer may lower it to Ebox's
internal ordinary-content path.
The target View model is:
```text
View = Text(content, typography)
| Box(layout, surface, children)
| ComponentCall(props, slots)
```
The Box inner-layout modes exposed by the target ETAF API are a closed set:
```text
normal | row | column | flex | grid
```
A plain Box defaults to `:outer 'block :layout 'normal`; it does not silently become a
column. Use `:layout 'column` for vertical stacking, or choose `row`, `flex`, or `grid`
explicitly. Text defaults to inline content in normal layout and owns text measurement,
wrapping, and inline runs.
The mapping is:
| CSS meaning | Target ETAF expression |
| --- | --- |
| ordinary block content | `(box :outer 'block :layout 'normal ...)` |
| ordinary inline content | `(box :outer 'inline :layout 'normal ...)` |
| vertical stacking | `(box :layout 'column ...)` |
| horizontal layout | `(box :layout 'row ...)` |
| flex container | `(box :layout 'flex ...)` |
| grid container | `(box :layout 'grid ...)` |
`inline-flex` is the direct orthogonal combination:
`:outer` controls participation in a parent normal layout. `:layout` controls child
layout. Defaults are `Box: block/normal` and `Text: inline`.
```elisp
(box :outer 'inline :layout 'flex ...)
```
A Component call has no fixed `:outer` or `:layout`; its root Text/Box output decides
how it participates in the parent. A transparent Component may return siblings and
therefore has no single outer Box.
This is inline-flex: an orthogonal combination, not another node type. ETAF exposes
`:outer` and `:layout`, not `:inner` or Ebox's raw display-pair representation.
`(box "text")` is shorthand for `(box (text "text"))`. ETAF must not expose
`(box :content "text")`; `:content` remains an Ebox backend field.
### 2.3 Bounded normal layout
ETAF also does not define a public `spacer` type. A Box with no children is an empty
Box; Ebox may use an empty-node representation or private helper, but users do not
need another visual category.
Normal layout supports only:
Box properties remain grouped by owner rather than becoming one unconstrained plist:
- consecutive inline Text/Box nodes in one wrapping line flow;
- block Boxes starting a new independent block;
- stable child order and block boundaries ending adjacent inline lines.
| Group | Properties | Purpose |
It does not implement full browser CSS: no floats, tables, absolute/fixed positioning,
run-in, list-item, or arbitrary anonymous-box rules. Ebox may map `normal` to an
internal flow algorithm; `flow` is not ETAF vocabulary.
For row, column, flex, or grid parents, the parent algorithm owns item placement and a
child's `:outer` does not change ordering. `:outer` is consumed only by a normal parent.
### 2.4 Other layout modes
| Mode | Responsibility |
| --- | --- |
| `row` | simple horizontal order without Flex distribution |
| `column` | simple vertical order without Flex distribution |
| `flex` | Flex sizing, direction, wrapping, alignment, and gaps |
| `grid` | two-dimensional tracks, placement, spans, and gaps |
They are Box modes, not Components or alternate Runtime node names. The only canonical
surface is `(box :layout 'MODE ...)`; core provides no layout-name aliases.
### 2.5 Fragment
Fragment is a nonvisual structural Range. It may contain zero, one, or many sibling
Views, creates no Box or layout context, and may carry only a stable `:key` plus
children. Runtime may retain and replace its Range independently.
A Component call has no fixed outer/layout properties. Its root Text/Box determines
participation. A transparent Component returning a Fragment may have multiple roots.
### 2.6 Strings, content, and empty Boxes
Strings under a Box normalize to Text, so `(box "text")` equals
`(box (text "text"))`. ETAF does not expose `(box :content ...)`; `:content` remains an
Ebox backend field. ETAF also has no spacer type: a childless Box is an empty Box.
## 3. Property contract
Properties form closed, owner-specific schemas. Unknown or inapplicable combinations
fail explicitly.
| Owner | Category | Representative properties |
| --- | --- | --- |
| structure | `:outer`, `:layout`, `:key` | participation, child layout, identity |
| geometry/surface | `:width`, `:height`, min/max, `:margin`, `:padding`, `:border`, `:box-sizing`, `:overflow` | size and surface |
| text | `:face`, `:color`, `:bgcolor`, `:wrap-mode`, `:text-align` | typography and text paint |
| flex item | `:order`, `:flex-grow`, `:flex-shrink`, `:flex-basis`, `:align-self` | valid under a Flex parent |
| grid item | `:grid-row`, `:grid-column`, spans | valid under a Grid parent |
| semantics/interaction | `:class`, `:id`, `:role`, `:ref`, `:aria-*`, `:on-*`, `:use` | Runtime events, Behaviors, and queries |
| Text | outer participation | `:outer` |
| Text | text/paint | `:face`, `:color`, `:bgcolor`, `:wrap-mode`, `:text-align` |
| Box | outer/inner layout | `:outer`, `:layout` |
| Box | geometry/surface | width/height/min/max, margin, padding, border, box sizing, overflow |
| Flex container/item | layout/participation | direction, wrap, alignment, gap, grow/shrink/basis/order |
| Grid container/item | layout/participation | tracks, auto flow, placement, spans, alignment, gap |
| Text/Box | identity/semantics | `:key`, `:ref`, class/id/role/ARIA/events/Behaviors |
`:outer` and `:layout` are geometry/structure properties, not TP paint slots. ECSS may
resolve them, but Ebox validates the final combination and owns the layout result.
Key rules:
Ebox may keep an outer/inner display representation internally; the public split is
the explicit `:outer` and `:layout` contract.
- `:key` is identity metadata, not paint;
- outer/layout/layout-specific properties have structure/geometry impact;
- color/background/typography have paint impact;
- ECSS preserves that impact classification;
- TP consumes resolved paint contributions only;
- structural changes transactionally replace the required layout context.
## 2. Module responsibilities
## 4. Module owners
### ETAF Core
`etaf-view` owns the View grammar, Text/Box/Component-call normalization, property
and slot shape validation, expression boundaries, and Component registration. It does
not measure pixels, lay out nodes, write buffers, or implement business data.
`etaf-view` owns normalized View shapes, string-to-Text normalization, closed property
schemas, slots, keys, and expression boundaries. It does not measure, lay out, or write
buffers.
`etaf-component` owns `etaf-define-component`, `:view`, `:setup`, `:styles`, and
definition validation. It does not own Runtime scheduling or Ebox nodes.
`etaf-component` owns Component props and `:view`/`:setup`/`:styles` definitions. It
does not schedule Runtime or call Ebox.
`etaf-runtime` owns retained identity, reactive refs/computed/effects/watchers, Scope
cleanup, Context/Theme, Actions, Behaviors, Data, Resources, lifecycle, invalidation,
transactions, commit, and rollback. It does not own pixel layout or Ebox private state.
`etaf-runtime` owns Component/Fragment/Range identity, reactive dependencies, Context,
Theme, Actions, Behaviors, Data, Resources, lifecycle, candidate generations, commit
authority, rollback, and disposal. It does not compute pixels.
`etaf-renderer` is the only ETAF-to-Ebox lowering boundary. It translates Text, Box,
and Component output to public Ebox constructors and carries resolved style/paint
contributions downstream. It does not own Component lifecycle or data requests.
`etaf-renderer` is the only ETAF-to-Ebox lowering adapter. It consumes normalized View
and computed properties without accessing Ebox private state.
Theme token meaning and inheritance belong to ETAF Context, not TP.
### Ebox
Ebox is the layout and rendering engine, not the ETAF Component Runtime. It owns text
measurement, wrapping, row/column/flex/grid geometry,
surface/scroll geometry,
stable layout snapshots, and render/paint plans. It does not know Components, slots,
Context, Actions, Behaviors, Data, or Resources.
Ebox owns Text measurement/wrapping/line layout, normal/row/column/flex/grid geometry,
the box model, overflow/scroll/viewport behavior, stable Ebox node identity, geometry
snapshots, and structural patch plans. It does not know Component semantics or paint
contribution priority.
Logically it has two boundaries:
Logical boundaries:
```text
ebox-core pure measurement, layout, snapshots, and render plans
ebox-emacs font/window capabilities and Emacs buffer submission
ebox-core pure measurement, layout, snapshots, structural patches
ebox-emacs font/window capabilities, buffer positions, structural submission
```
They need not be separate distribution packages immediately, but geometry and Emacs
side effects must not become one responsibility.
These need not become separate distribution packages immediately, but must remain
independently testable.
### ECSS and TP
### ECSS
ECSS owns selectors, declarations, cascade, inheritance, and computed styles. It does
not own Runtime state, layout, or buffer writes.
ECSS owns selectors, cascade, inheritance, and computed properties. Its output retains
structure/geometry/paint impact. ECSS does not run layout or write buffers.
TP owns layered Emacs text-property/paint slots, priority merging, atomic application,
journaling, and rollback. It does not parse selectors, measure layout, or execute
Components.
### TP and commit boundary
### Optional/application packages
`etaf-ui` owns reusable Button, Checkbox, Label, Panel, Number input, DataGrid, and
Pagination Components. It does not implement another Runtime or layout engine.
`etaf-sqlite` owns SQLite connections, schema/query/mutation validation, paging, and
Data Source disposal. It does not own Data Controller state or UI.
The performance recorder owns application-neutral operation/stage records, summaries,
environment context, and export. It observes public boundaries and never embeds a
Playground name or changes the measured render path.
`ebox-playground` only validates public Ebox layout APIs. `etaf-playground` validates
ETAF composition and may optionally load `etaf-ui` and `etaf-sqlite`; neither Playground
owns framework protocols. A static `.etaf` manifest belongs to the Playground unless a
future compiler lowers it to the same Text/Box/Component contract.
## 3. Dependency direction
TP owns paint-contribution priority, paint-operation merging, Emacs text-property
journaling, application, and rollback.
```text
etaf-ui ───────▶ ETAF Core ───────▶ Ebox public layout port
etaf-sqlite ──▶ ETAF Data contract
etaf-playground ─▶ ETAF + optional UI/SQLite
ebox-playground ─▶ Ebox public API only
performance ───▶ public probes only
ETAF Runtime creates a candidate and owns commit authority
Ebox stages structural/geometry patches
TP stages paint patches
Emacs adapter atomically applies both
ETAF Runtime promotes the generation; failure rolls participants back
```
Ebox never depends upward on ETAF. TP never parses View trees. ECSS never writes
buffers. UI never calls private Ebox functions. SQLite never knows UI. Playgrounds
never inject example protocols into core.
Ebox does not own TP priority, TP does not own layout, and neither participant may
promote a Runtime generation.
## 4. Rust and Elisp split
### Upper packages
Rust is for deterministic, environment-independent computation: normalized Render IR,
keyed diff/patch, ECSS cascade, text measurement/wrapping, ordinary Box/flex/grid layout,
geometry snapshots, and paint-contribution merging.
| Package | Owns | Does not own |
| --- | --- | --- |
| `etaf-ui` | ordinary reusable Components | another Runtime or layout engine |
| `etaf-sqlite` | SQLite Data Source and its transactions/disposal | Data Controller or UI |
| `etaf-performance` | application-neutral operations/stages/statistics/reports | example semantics or scheduling authority |
| `ebox-playground` | public Ebox examples/verification | ETAF |
| `etaf-playground` | ETAF/UI/SQLite composition examples and tooling | core protocols |
Elisp/Emacs owns user Component execution, refs, Context, Actions, Data, Resources,
lifecycle callbacks, Emacs events/windows/fonts, and final patch submission. Elisp must
not reimplement Rust layout/cascade/diff; Rust must not read Emacs buffers or understand
Component slots.
`etaf-performance` is a separate optional package. No measured package depends on it;
it observes only public boundaries. A Playground `.etaf` manifest belongs to the
Playground. Any future general compiler must lower to the same View/Component contract.
## 5. Invariants and evolution
## 5. Dependency direction
One transaction materializes stable identity, dependencies, layout snapshots, and paint
contributions once. Text paint changes do not rerun unrelated Components or layout.
Box geometry changes affect only the required layout owner. Component state/slot/list
changes affect only the owner of that structure. Failed Ebox/TP candidates preserve the
last committed result.
```text
etaf-ui ─────────────▶ ETAF Core ─────────────▶ Ebox public port
etaf-sqlite ─────────▶ ETAF Data contract
etaf-playground ─────▶ ETAF + optional UI/SQLite
ebox-playground ─────▶ Ebox only
etaf-performance ────▶ public observation boundaries only
The clean public direction is `Text + Box(layout) + Component`. If concise syntax is
needed, `row`/`column`/`flex`/`grid` may be introduced as new compiler sugar, but they
are not compatibility aliases and must not become Runtime Components. `fragment` is
transparent structure. `spacer` and `flow` are not public ETAF types; the target Ebox
may replace the current `(block flow)` marker with ordinary Box/Text paths instead of
retaining a separate flow branch. The redesign does not preserve the old Host registry
names or add a compatibility layer. This target is not implemented merely by documenting it.
ETAF Renderer ───────▶ ECSS computed properties
ETAF Runtime ───────▶ Ebox/TP transaction participants
```
Reverse dependencies are forbidden: Ebox never depends on ETAF, TP never parses View,
ECSS never writes buffers, UI never calls Ebox private APIs, SQLite never knows UI, and
Playgrounds never inject protocols into core.
## 6. Rust and Elisp
Boundaries precede implementation language. Deterministic kernels may move to Rust only
after contracts and equivalence tests are stable:
```text
Rust candidates:
normalized View validation and structural diff over opaque stable IDs
ECSS cascade
Text-measurement input processing and Ebox layout
pure geometry/paint patch computation
Elisp / Emacs:
user Components, refs, Context, Actions, Data, Resources, lifecycle
database and external I/O
Emacs events, font/window capabilities, and final submission
```
Component identity, dependency ownership, TP priority, and generation authority do not
move merely because a kernel is written in Rust. Elisp must not repeat Rust diff,
cascade, layout, or text scans.
## 7. Correctness and performance invariants
- View, computed properties, geometry snapshots, and paint contributions materialize
once per transaction;
- Text paint changes do not execute unrelated Components or layout;
- outer/layout/geometry changes affect only the required layout owner;
- Fragment/slot/list changes replace only their Range;
- Ebox/TP/Emacs participant failure preserves the last committed generation;
- resize uses current window facts immediately, without hidden debouncing semantics;
- instrumentation does not change the measured path.
Final gates cover structural equivalence, legal/illegal outer-layout combinations,
normal inline/block behavior, row/column/flex/grid GUI behavior, Component roots and
Fragments, style/Theme/TP priority and rollback, continuous Research Shelf/Flex
reference resize, and p95/max at or below 50ms in fixed real scenarios.
## 8. Current gap and non-goals
Current code still registers `text`, `fragment`, `container`, `row`, `column`, `stack`,
`flex`, `grid`, and `spacer`. Target `box`, `:outer`, and `:layout 'normal` are not
implemented.
This is a clean redesign: old Host names and old `.etaf` sources need not continue to
run, and no long-lived compatibility layer is added. Formal architecture/user docs must
not present target syntax as delivered before implementation is complete.
Non-goals:
- full browser CSS;
- spacer, public flow, or `(box :content ...)`;
- layout modes as Components or alternate Runtime nodes;
- old Host aliases or optional layout shorthand in core;
- core protocols owned by Playground/UI/database packages;
- App precompilation without measured compile cost and runtime benefit.

View File

@ -1,122 +1,138 @@
# ETAF 模块职责与目标架构(设计提案,未实现)
> 状态:设计提案。本文不是当前公共 API 或已交付实现的规范;当前可用契约仍以
> 状态:设计提案。本文不描述当前已交付 API。当前行为仍以
> [`architecture.zh.md`](../architecture.zh.md)、[`user-guide.zh.md`](../user-guide.zh.md)
> 和实际测试为准。
> 和通过的测试为准。
本文把 ETAF、Ebox、ECSS、TP、`etaf-ui`、`etaf-sqlite`、性能工具和两个
Playground 的职责收敛成一份待评审的边界提案。它描述的是从零设计时应保持的
模型,不是把当前实现中的每个文件名当成架构真相。
本文只定义目标模型、模块 owner、依赖方向和完成条件。实现顺序属于单独的
实施计划;历史名称和兼容策略不参与目标架构设计。
文中标有“目标”的部分是后续演进方向;除非代码、测试和基准同时完成,不能
把目标 API 描述成已经交付的功能。提案没有自动授权实现,也不改变当前 Host
注册表、View 语法或包依赖。
## 1. 最终模型
## 1. 最小稳定模型
ETAF 需要区分四种表示而不是把它们都叫作“组件”或“box”
ETAF 的表示链只有四层:
```text
Component 语义与所有权
Text / Box View 结构
ViewText / Box / Fragment
Ebox 几何与渲染计划
Ebox:测量、布局、几何 patch
TP / Emacs 最终提交
TP + Emacs adapterpaint 与最终提交
```
### 1.1 公共概念
用户需要理解的主要概念只有 `Text`、`Box` 和 `Component`。`Fragment` 是高级
结构语法;`Host` 是 Renderer 内部术语;`Ebox Node` 是后端对象,都不构成第二套
组件模型。
用户只需要学习三个主要概念:
规范化 View 的完整形状是
| 概念 | 稳定职责 | 不负责的事情 |
| --- | --- | --- |
| `Text` | 文本内容、字体、inline text properties、换行入口 | 子树布局、业务状态、生命周期 |
| `Box` | 尺寸、背景、边框、padding、子节点和布局 | Component identity、Data、Action |
| `Component` | 可复用语义、props、slot、状态、Context、Action、生命周期 | 像素测量和布局算法 |
```text
View = Text
| Box
| Fragment
| ComponentCall
| RawEbox
```
`Host` 是 Renderer 内部用于称呼可直接降低到后端的原语,不是用户需要额外
学习的第三套视觉分类。`Ebox Node` 则是更底层的几何/渲染对象;它可以由一个
`Text`、一个 `Box`,或一棵 Component 子树产生。
`expr``slot` 是计算/投影机制,不是视觉节点。`RawEbox` 是明确的低层出口,
不获得正常 View 的样式、事件或增量语义。
### 1.2 Text 与 Box 的关系
## 2. Text、Box 与 Fragment
CSS 中所有最终可见元素都会产生 box但 CSS 生成的 box、ETAF 的 View 原语和
Ebox 的后端节点不是同一棵树。`inline` 和 `block` 描述盒子参与父布局的方式;
`flex``grid` 描述盒子内部如何排列子节点。
### 2.1 Text
因此 ETAF 不应该把这些概念做成 Component而应在 Box 上保留两个正交属性:
`Text` 是文本叶子,负责:
- 字符串和 inline Text runs
- 字体、前景/背景、下划线等文本 paint
- 文字测量、换行和文本对齐的输入;
- 可选 identity、语义和事件属性。
`Text` 默认 `:outer 'inline`。它不能拥有 Box、Component 或任意布局子节点,也
不能建立 `row`、`column`、`flex` 或 `grid` 上下文。需要 padding、border、尺寸
或子布局时,在外层使用 `Box`
### 2.2 Box 的两个正交轴
`Box` 同时拥有一个外部参与方式和一个内部布局方式:
```text
:outer = inline | block
:layout = normal | row | column | flex | grid
```
`:outer` 表示这个 Box 如何参与父级的普通布局;`:layout` 表示这个 Box 如何
排列自己的子节点。`normal` 是 ETAF 用户层对普通内容布局的名称Renderer
可以把它降低为 Ebox 内部的普通内容路径。
- `:outer`:这个 Box 如何参与父级的 `normal` 布局;
- `:layout`:这个 Box 如何排列自己的子节点。
目标公共模型是:
默认值是:
```text
View = Text(content, typography)
| Box(layout, surface, children)
| ComponentCall(props, slots)
Box :outer block :layout normal
Text :outer inline
```
目标中 ETAF 对用户暴露的 Box 内部布局模式是封闭集合:
```text
normal | row | column | flex | grid
```
普通 `Box` 默认是 `:outer 'block :layout 'normal`,不会隐式变成 `column`
需要纵向堆叠时明确写 `:layout 'column`需要横向、Flex 或 Grid 时明确选择
对应模式。`Text` 默认作为 inline content 参与 normal 布局,但自身负责文字
测量、换行和 inline runs。
语义映射大致为:
| CSS 语义 | ETAF 目标表达 |
| --- | --- |
| inline text/run | `text` |
| 普通 block 内容 | `(box :outer 'block :layout 'normal ...)` |
| 普通 inline 内容 | `(box :outer 'inline :layout 'normal ...)` |
| 纵向排列 | `(box :layout 'column ...)` |
| horizontal layout | `(box :layout 'row ...)` |
| flex container | `(box :layout 'flex ...)` |
| grid container | `(box :layout 'grid ...)` |
`inline-flex` 是两个属性的正交组合:
`inline-flex` 是两个轴的组合,不是新的节点类型:
```elisp
(box :outer 'inline :layout 'flex ...)
```
Component call 自身没有固定的 `:outer``:layout`;它的根 Text/Box 输出决定
它如何参与父级布局。透明 Component 可以输出多个兄弟节点,因此不能假定每个
Component 都有一个外部盒子。
公开 API 使用 `:outer``:layout`。不公开 `:inner`,也不要求用户写 Ebox 的
`(:display (block flow))` 一类后端表示。
### 1.3 属性分组
### 2.3 normal 的明确范围
Box 的属性按 owner 分组,不能做成一个无约束的大 plist
`normal` 是一个真实但刻意收敛的布局上下文:
| 分组 | 属性 | 作用 |
| --- | --- | --- |
| 外部/内部结构 | `:outer`、`:layout`、`:key` | 参与方式、子布局、稳定 identity |
| 几何和表面 | `:width`、`:height`、min/max、`:margin`、`:padding`、`:border`、`:box-sizing`、`:overflow` | 尺寸、表面和可见区域 |
| 文本 | `:face`、`:color`、`:bgcolor`、`:wrap-mode`、`:text-align` | Text 或普通内容的排版/绘制 |
| Flex 子项 | `:order`、`:flex-grow`、`:flex-shrink`、`:flex-basis`、`:align-self` | 只在 Flex 父级中有效 |
| Grid 子项 | `:grid-row`、`:grid-column`、span | 只在 Grid 父级中有效 |
| 语义和交互 | `:class`、`:id`、`:role`、`:ref`、`:aria-*`、`:on-*`、`:use` | Runtime 事件、Behavior 和查询 |
- 连续的 inline `Text` 或 inline `Box` 进入同一行流并按可用宽度换行;
- block `Box` 从新行开始并形成独立块;
- block 前后的 inline 行会在块边界结束;
- 子节点顺序保持稳定。
`:outer``:layout` 是结构/几何属性,变化会触发布局 owner它们不是 TP
paint slot。ECSS 可以参与解析它们,但最终合法性和布局仍由 Ebox 契约验证。
目标不实现完整浏览器 CSS没有 float、table、absolute/fixed positioning、
run-in、list-item 或任意匿名盒规则。Ebox 可以把 `normal` 映射为内部 flow
算法,但 `flow` 不是 ETAF 公共词汇。
### 1.4 子节点与后端 content
当父级 `:layout``row`、`column`、`flex` 或 `grid` 时,父算法直接拥有子项
位置;子项的 `:outer` 不改变排列顺序。`:outer` 只决定节点进入 `normal` 父级时
是 inline 还是 block。
目标 ETAF 语法只保留一个内容模型:子节点。
### 2.4 其他布局模式
| 模式 | 职责 |
| --- | --- |
| `row` | 简单水平顺序布局,不执行 Flex 空间分配 |
| `column` | 简单垂直顺序布局,不执行 Flex 空间分配 |
| `flex` | Flex sizing、direction、wrap、alignment 和 gap |
| `grid` | 二维轨道、放置、跨度和 gap |
这些都是 `Box` 的 layout mode不是 Component也不注册 `row`、`column`、
`flex``grid` 的第二套 Runtime 节点名称。目标 API 只有规范写法:
```elisp
(box :layout 'row ...)
(box :layout 'column ...)
(box :layout 'flex ...)
(box :layout 'grid ...)
```
### 2.5 Fragment
`Fragment` 是无视觉 wrapper 的结构 Range
- 可以承载零个、一个或多个兄弟 View
- 不产生 Box、尺寸、背景或布局上下文
- Runtime 可以为其保留稳定 Range identity 并局部替换;
- 只接受子节点和可选稳定 `:key`,不接受视觉/事件属性。
Component call 本身没有 `:outer``:layout`。它的根 Text/Box 决定如何参与父级
布局;返回 Fragment 的透明 Component 可以产生多个兄弟节点,因此不存在单一的
“Component 外部盒子”。
### 2.6 字符串、content 与空 Box
Box 子节点中的字符串规范化为 Text
```elisp
(box "this is text")
@ -128,348 +144,205 @@ paint slot。ECSS 可以参与解析它们,但最终合法性和布局仍由 E
(box (text "this is text"))
```
需要文本属性时显式写 `text`
ETAF 不提供 `(box :content "...")`。`:content` 只属于 Ebox 后端构造器;暴露它
会制造 content 与 children 两套结构模型。
```elisp
(box
:padding '(1 2)
(text :face 'bold "Title"))
```
ETAF 也不提供 `spacer` 类型。没有子节点的 Box 就是空 Box。
ETAF 公共语法不增加 `(box :content "...")`。`:content` 是 Ebox 后端构造器
(例如 `ebox-create`的内部字段Renderer 可以把 `text` 降低为后端 content
但不能把后端字段反向暴露成第二套 ETAF 子树模型。这样可以自然混合字符串、
Text、Box、Component 和 slot也不会产生 content 与 children 的优先级歧义。
## 3. 属性契约
ETAF 公共模型也不定义 `spacer`。没有子节点的 `Box` 就是空 BoxEbox 可以在
内部使用空内容节点或私有 helper 表示它,但这不是用户需要学习的独立视觉类型。
属性是闭集,并按 owner 验证;未知属性和不适用组合必须报错。
## 2. 模块职责
| Owner | 属性类别 | 代表属性 |
| --- | --- | --- |
| Text | 外部参与 | `:outer` |
| Text | 文本/paint | `:face`、`:color`、`:bgcolor`、`:wrap-mode`、`:text-align` |
| Box | 外部/内部布局 | `:outer`、`:layout` |
| Box | 几何/表面 | `:width`、`:height`、`:min-*`、`:max-*`、`:margin`、`:padding`、`:border`、`:box-sizing`、`:overflow` |
| Flex container | 布局 | `:flex-direction`、`:flex-wrap`、`:justify-content`、`:align-items`、`:align-content`、`:gap` |
| Flex item | 父级参与 | `:order`、`:flex-grow`、`:flex-shrink`、`:flex-basis`、`:align-self` |
| Grid container | 布局 | track templates、auto flow、gap、item/content alignment |
| Grid item | 父级参与 | `:grid-row`、`:grid-column`、row/column span |
| Text/Box | identity/语义 | `:key`、`:ref`、`:class`、`:id`、`:role`、`:aria-*`、`:on-*`、`:use` |
下面的边界是“谁拥有完整规则”的判断标准。调用别的模块的公开契约不等于拥有
那个模块的职责。
规则:
### 2.1 ETAF Core
- `:key` 是 identity metadata不是视觉属性
- `:outer`、`:layout` 和 layout-specific 属性属于 structure/geometry impact
- color、background、typography 等属于 paint impact
- ECSS 可以解析上述属性,但必须保留 impact 分类;
- TP 只消费已经解析的 paint contribution不判断布局合法性
- 结构属性变化必须事务性重建对应布局上下文,不能走 paint-only 快速路径。
#### `etaf-view`
## 4. 模块 owner
拥有:
### 4.1 ETAF Core
- `Text`、`Box`、Component call、Fragment 的结构语法和规范化 View 值;
- 属性优先解析和 `expr :value` 求值边界;
- props、`key` 和 slot 的静态形状验证;
- Component 注册表与 View 名称解析。
`etaf-view`
不拥有:
- 定义并规范化 Text、Box、Fragment、ComponentCall 和 RawEbox
- 将字符串规范化为 Text
- 验证属性闭集、slot、`:key` 和表达式边界;
- 不测量、不布局、不写 buffer。
- 字宽、像素、布局、滚动和 buffer 写入;
- Component 生命周期和 Runtime 调度;
- 业务数据、数据库、UI 控件实现。
`etaf-component`
#### `etaf-component`
- 定义 Component props、`:view`、`:setup`、`:styles`
- 不调度 Runtime不调用 Ebox。
拥有
`etaf-runtime`
- `etaf-define-component`
- `:view`、`:setup`、`:styles` 定义边界;
- props 和静态 Component style 的声明验证。
- 拥有 Component/Fragment/Range identity、响应式依赖、Context、Theme、Action、
Behavior、Data、Resource 和 lifecycle
- 拥有 candidate generation、事务、提交授权、回滚和释放
- 决定变化影响哪个 Component、Range、结构属性或 paint contribution
- 不计算像素和布局。
不拥有:
`etaf-renderer`
- Ebox 私有节点;
- Data 请求、数据库连接或全局 UI 控件目录。
- 是唯一 ETAF → Ebox 的 lowering adapter
- 把规范化 View 和 computed properties 转换为 Ebox 输入;
- 不执行 Component lifecycle不访问 Ebox 私有状态。
#### `etaf-runtime`
Theme 的语义 token 和继承属于 ETAF Context。TP 不拥有“暗色主题”等业务意义。
拥有:
### 4.2 Ebox
- Component identity、`:key`、retained instance
- ref、computed、effect、watch 和 Scope cleanup
- Context、Theme、Action、Behavior、Data、Resource 的生命周期;
- 依赖失效、候选 generation、提交、回滚和调度
- 将变化定位到 Component、Range、Host 属性或 Paint contribution。
Ebox 拥有:
不拥有:
- Text 测量、换行和行布局;
- `normal`、`row`、`column`、`flex`、`grid` 几何算法;
- width/height、box model、overflow、scroll、viewport
- 稳定 Ebox node identity、geometry snapshot 和结构 patch plan。
- 字体测量、flex/grid 算法、像素布局;
- Ebox 私有状态;
- SQLite、HTTP 或某个示例的业务规则。
Ebox 不理解 Component、slot、Context、Action、Behavior、Data 或 Resource也不
决定 paint contribution 的优先级。
#### `etaf-renderer`
拥有:
- 唯一的 ETAF View → Ebox 公共 lowering 边界;
- Text、Box、Component 输出到 Ebox 公共构造器的转换;
- 把已解析的 style/theme/property contribution 传给下游;
- 明确的 `raw-ebox` 低层出口。
不拥有:
- Component setup/lifecycle 的所有权;
- Ebox 的测量和布局实现;
- Data 查询或应用 Action。
### 2.2 Ebox
Ebox 是布局和渲染引擎,不是 ETAF Component Runtime。
拥有:
- 文本测量、字宽、换行和内容高度;
- Text 内容测量和换行,以及 row、column、flex、grid 的几何算法;
- padding、border、surface、overflow、scroll 和 viewport 几何;
- 稳定的布局快照、节点 identity、增量几何更新
- 从布局结果到后端可提交 Render/Paint plan 的转换。
不拥有:
- Component、props、slot、Context、Action、Behavior、Data、Resource
- 应用语义或 UI 控件分类;
- ETAF 的响应式依赖图。
`row`、`column`、`flex`、`grid` 即使在 ETAF 目标 API 中统一为
`box :layout ...`Ebox 内部仍然可以保留不同的布局上下文和算法。统一的是
上层表达,不是把不同几何问题硬塞进一个无差别函数。
逻辑上可以继续拆为两个边界:
逻辑边界:
```text
ebox-core 纯测量、布局、快照和 Render plan
ebox-emacs 字体/窗口能力、Ebox Node 到 Emacs buffer 的提交适配
ebox-core 纯测量、布局、快照和结构 patch
ebox-emacs 字体/窗口能力、buffer 位置和结构提交
```
这两个边界不一定现在就变成两个发行包,但不能让布局算法和 Emacs buffer 副作用
混在同一个职责中。
这不要求立即拆成两个发行包,但纯布局和 Emacs 副作用必须能独立测试。
### 2.3 ECSS
### 4.3 ECSS
拥有:
ECSS 拥有 selector、cascade、继承和 computed properties。输出必须携带属性的
structure/geometry/paint impactECSS 不执行布局,也不写 buffer。
- selector 解析和匹配;
- style declaration、cascade、继承和 computed style
- 把静态规则转换为可复用、可验证的样式中间产物。
### 4.4 TP 与提交边界
拥有:
TP 拥有:
- Component state、slot、Action
- 字宽、布局或 Ebox Node
- Emacs text property 的提交和回滚
- Theme、Component style、state、inline 等 paint contribution 的优先级;
- paint operation 合并;
- Emacs text property journal、apply 和 rollback。
ECSS 决定“某个结构节点应该得到哪些样式值”,不决定“这些像素如何排布”。
### 2.4 TP
拥有:
- Emacs text property / paint slot 的分层;
- Theme、Component、state、inline 等 Paint contribution 的优先级合并;
- 原子应用、journal、rollback 和已提交层的复用。
不拥有:
- CSS selector/cascade
- 布局测量、祖先证明或文字换行;
- Component 和 Data。
TP 应接收精确的 Paint operations不应该为了换一个背景色重新推导整棵 View
或重新执行所有 Component。
### 2.5 `etaf-ui`
拥有官方可复用 Component例如
- Button
- Checkbox
- Label
- Panel
- Number input
- DataGrid
- Pagination。
这些控件使用 ETAF 的 props、slot、事件、Behavior、Theme 和 Data 契约。它们
不是第二套 Widget/Control/Component 模型,也不应该实现自己的 Runtime 或布局
引擎。
### 2.6 `etaf-sqlite`
拥有:
- SQLite 连接、schema、查询、分页和 mutation
- 参数和标识符校验;
- 具体 Data Source 的事务和 dispose 边界。
不拥有:
- ETAF Data Controller 状态机;
- View、Component、DataGrid 或 playground UI。
其他数据库、REST、文件和 ORM 集成也应实现同一个具体 Data Source capability
但使用各自明确的包名,不引入一个笼统的 `etaf-adapters` 层。
### 2.7 性能工具
性能记录器必须是应用无关的逻辑模块(当前可以先作为 ETAF 中的独立子模块,
未来再决定是否单独发行)。它拥有:
- operation、stage、父子关系和 generation 记录;
- p50/p95/max、GC、环境信息和跨包阶段关联
- 公共 Event、Action、mount、flush、Data、Resource、viewport 边界的探针;
- 报告、复制和导出。
它不拥有:
- Research Shelf 或任何示例名称;
- 应用状态或布局规则;
- 为了采样而改变正常渲染路径。
Playground 只能展示这个工具,不能定义它的核心协议。
### 2.8 两个 Playground
`ebox-playground`
- 只验证 Ebox 的布局、测量、重排和性能;
- 只依赖 Ebox 公共 API
- 不加载 ETAF、Component、Data Controller 或 UI 目录。
`etaf-playground`
- 验证 ETAF 的 Component、Runtime、Data、UI 和交互组合;
- 可以选择加载 `etaf-ui``etaf-sqlite`
- 提供 `.etaf` 示例加载、快捷键和状态展示;
- 不拥有 ETAF 的核心语法、Runtime 或性能记录协议。
`.etaf` 静态文件如果只是 Playground manifest就属于 Playground它不是 ETAF
Core 的 Runtime 入口。未来若增加通用 `.etaf` compilercompiler 必须把结果
降低到同一套 Text/Box/Component 契约,不能创建第二套 Runtime。
## 3. 依赖方向
目标依赖图如下:
一次更新的权限顺序固定为:
```text
┌──────────────┐
│ etaf-performance │
│ optional observer│
└──────┬───────┘
│ public probes only
┌────────────┐ ┌────────────────────┐ ┌──────────────┐
│ etaf-ui │──────▶│ ETAF Core │──────▶│ Ebox public │
└────────────┘ │ View/Component/RT │ │ layout port │
└─────────┬──────────┘ └──────┬───────┘
│ │
┌─────────▼─────────┐ ┌──────▼───────┐
│ ECSS / TP adapters│ │ Ebox backend │
└───────────────────┘ │ / Emacs │
└──────────────┘
etaf-sqlite ─────▶ ETAF Data Source contract
etaf-playground ─▶ ETAF + optional etaf-ui + optional etaf-sqlite
ebox-playground ─▶ Ebox public API only
ETAF Runtime 创建 candidate 并拥有提交授权
Ebox stage 结构/几何 patch
TP stage paint patch
Emacs adapter 原子应用两个 patch
ETAF Runtime 提升 generation失败则回滚参与者
```
约束:
Ebox 不拥有 TP paint 优先级TP 不拥有节点布局;两者都不能自行提升 Runtime
generation。
- Ebox 不向上依赖 ETAF
- TP 不解析 View也不调用 Component
- ECSS 不写 buffer
- `etaf-ui` 不调用 Ebox 私有函数;
- `etaf-sqlite` 不知道任何 UI
- Playground 不向 core 反向注入示例协议;
- 性能工具通过公开边界观察各包,不让被测包依赖某个示例。
### 4.5 上层包
## 4. Rust 与 Elisp 的切分
| 包 | 拥有 | 不拥有 |
| --- | --- | --- |
| `etaf-ui` | Button、Checkbox、Label、Panel、DataGrid 等普通 Component | 第二套 Widget Runtime、布局引擎 |
| `etaf-sqlite` | SQLite Data Source、查询、mutation、事务和 dispose | Data Controller、View、UI |
| `etaf-performance` | 应用无关 operation/stage、环境、统计和报告 | 示例语义、布局规则、调度权限 |
| `ebox-playground` | Ebox 公共布局 API 示例与验证 | ETAF |
| `etaf-playground` | ETAF/UI/SQLite 组合示例、加载命令和状态展示 | Core 语法、Runtime、性能协议 |
目标不是把所有代码机械地翻译成 Rust而是把确定性计算和环境副作用分开。
`etaf-performance` 是独立可选包。ETAF、Ebox、TP 和 SQLite 不依赖它;它只通过
公开边界观察和关联阶段。
适合 Rust 纯计算核心的内容:
Playground 的 `.etaf` manifest 属于 Playground。未来通用 compiler 若存在,必须
降低到同一套 View/Component 契约,不得创建第二套 Runtime。
- Text/Box Render IR 的验证和规范化;
- keyed diff、identity matching 和 patch batch
- ECSS selector/cascade/computed-style 计算;
- 字符宽度、换行、普通 Box/flex/grid 测量和布局;
- geometry snapshot、layout impact 和 paint contribution merge。
保留在 Elisp/Emacs 适配层的内容:
- 执行用户定义的 Component 函数;
- ref、Context、Action、Data、Resource 和 lifecycle callback
- Emacs 事件、窗口、字体能力和 buffer 提交;
- 调用 Rust 核心并提交返回的 patch/paint plan。
Elisp 不应再次实现 Rust 已经计算过的布局、cascade、diff 或文字扫描Rust 也不
应读取 Emacs buffer 或理解 Component slot 语义。
## 5. 性能与正确性不变量
模块增加不能把一次局部变化升级成多次全量遍历。必须保持:
- 一次事务中的稳定 identity、依赖、布局快照和 Paint contribution 只 materialize
一次;
- Text paint 改变不重新执行无关 Component不重新做布局
- Box 几何改变只触发受影响的布局 owner
- Component 状态/slot/列表拓扑改变只执行拥有该结构的 Component
- Ebox 失败或 TP 提交失败时保留上一代已提交结果;
- 快速路径必须是普通正确路径的严格子集,并且能验证失败后精确回退;
- 性能工具本身不能改变被测应用的调度和布局路径。
真实 GUI 场景仍然是最终证据。固定场景在功能、文本属性、identity、生命周期和
回滚语义不缩水的前提下p95 和 max 都必须满足项目的 50ms 目标;单独的微基准
不能替代跨包验证。
## 6. 当前实现与目标模型的关系
当前 ETAF Core 的 Host 注册表仍包含:
## 5. 依赖方向
```text
text · fragment · container · row · column · stack · flex · grid · spacer
etaf-ui ─────────────▶ ETAF Core ─────────────▶ Ebox public port
etaf-sqlite ─────────▶ ETAF Data contract
etaf-playground ─────▶ ETAF + optional UI/SQLite
ebox-playground ─────▶ Ebox only
etaf-performance ────▶ public observation boundaries only
ETAF Renderer ───────▶ ECSS computed properties
ETAF Runtime ───────▶ Ebox/TP transaction participants
```
当前 Renderer 直接把这些名称映射到 Ebox 构造器Ebox Playground 也直接使用
自己的布局 API。这只是现状记录不是目标 API也不构成必须保留的兼容表面。
`spacer``flow` 都不属于目标公共词汇;目标 Ebox 可以用普通 Box/Text 路径
替代当前的 `flow` 标记,不为它保留独立布局分支。
禁止反向依赖Ebox 不依赖 ETAFTP 不解析 ViewECSS 不写 bufferUI 不调用
Ebox 私有函数SQLite 不知道 UIPlayground 不向 Core 注入协议。
目标演进方向是:
## 6. Rust 与 Elisp
模块边界先于实现语言。只有接口和等价性测试冻结后,确定性计算才迁移到 Rust
```text
Text + Box(layout)
如果确实需要短写法row/column/flex/grid 只能作为新语法糖
Rust 候选:
规范化 View 的验证与结构 diff只处理 opaque stable IDs
ECSS cascade
Text 测量输入处理与 Ebox 布局
geometry/paint patch 的纯数据计算
Elisp / Emacs
执行用户 Component、ref、Context、Action、Data、Resource、lifecycle
数据库和外部 I/O
Emacs 事件、字体/窗口能力和最终提交
```
这些简写不能通过 `etaf-define-component` 实现,否则每个布局节点都会被迫获得
Component 的 identity、scope、slot 和调度边界。它们应直接降低为 Ebox 布局节点。
不保留 `container`、`stack`、`spacer` 或旧 Host 注册名的兼容别名。
Component identity、依赖所有权、TP 优先级和 generation authority 不因 Rust 化而
转移。Elisp 不重复 Rust 已经完成的 diff、cascade、布局或文字扫描。
目标模型尚未交付前,不得修改现有 Host 语法后再把未验证的 `box` 描述成已完成。
## 7. 正确性与性能不变量
## 7. 有边界的演进顺序
- 同一事务中的 View、computed properties、geometry snapshot 和 paint contribution
各 materialize 一次;
- Text paint 改变不执行无关 Component也不运行布局
- `:outer`、`:layout` 或几何改变只运行受影响的布局 owner
- Fragment/slot/list 变化只替换对应 Range
- Ebox/TP/Emacs 任一 participant 失败时保留上一代已提交状态;
- resize 使用当前窗口事实立即重排,不以隐藏 debounce 改变交互语义;
- 性能记录器不改变被测路径。
如果开始实现目标模型,只做下面这个最小顺序:
最终门禁必须覆盖:
1. 增加 `Box` 的规范化 View 形状和字符串子节点规则;
2. 只验证 `layout='column``box` 到 Ebox lowering
3. 决定是否提供 `row`、`column`、`flex`、`grid` 的新语法糖,并直接降低到 Box
4. 分别验证 `flex``grid` 的模式属性、布局结果和增量路径;
5. 保留旧名称兼容,并用 Research Shelf 与 Flex reference 做 GUI 回归;
6. 只有全部公共测试和真实性能门禁通过,才更新用户指南为 `text + box`
- Text/Box/Fragment 结构等价;
- `outer × layout` 合法组合和失败组合;
- normal inline/block、row、column、flex、grid 的 GUI 行为;
- Component root 与透明 Fragment 的布局参与;
- style/Theme/TP 优先级和事务回滚;
- Research Shelf 与 Flex reference 的连续 resize
- 固定真实场景 p95 和 max 均不超过 50ms。
任何一步不能证明结果等价或性能收益,就删除该实验,不继续堆下一层抽象。
## 8. 当前实现差距与非目标
这是一次干净重设计,不要求旧 `.el`、`.etaf` 文件或旧 Host 名称继续运行。迁移
由明确的源码更新完成,不通过长期兼容层隐藏模型变化
当前代码仍注册 `text`、`fragment`、`container`、`row`、`column`、`stack`、
`flex`、`grid` 和 `spacer`;目标 `box`、`:outer`、`:layout 'normal` 尚未实现
## 8. 明确不做的事情
这是干净重设计,不要求旧 Host 名称或旧 `.etaf` 文件继续运行,也不增加长期
兼容层。实现完成前,正式架构和用户指南不得把目标语法描述成当前 API。
- 不把 `button`、`panel`、`data-grid` 等业务控件塞进 Ebox
- 不把 `row`、`column`、`flex`、`grid` 做成 Runtime Component
- 不把 `spacer``flow` 暴露成 ETAF 公共视觉类型;
- 不在 ETAF 公共层引入 `box :content` 第二套内容模型;
- 不让 TP 重新扫描布局或重新调和所有颜色;
- 不让 Playground 拥有通用框架协议;
- 不为了“预编译”而新增没有长编译时间和真实运行时收益的中间产物;
- 不为了减少名词而合并 Component、Text、Box、Ebox Node、Paint slot 等不同所有权
和生命周期概念。
非目标:
- 不实现完整浏览器 CSS
- 不提供 `spacer`、公共 `flow``(box :content ...)`
- 不把布局模式做成 Component 或第二套 Runtime 节点;
- 不在 Core 中保留旧 Host alias 或可选短写法;
- 不让 Playground、UI 或数据库包拥有 Core 协议;
- 不在没有真实编译成本和运行时收益前增加 App 预编译产物。