etaf/docs/proposals/module-boundaries.zh.md

476 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ETAF 模块职责与目标架构(设计提案,未实现)
> 状态:设计提案。本文不是当前公共 API 或已交付实现的规范;当前可用契约仍以
> [`architecture.zh.md`](../architecture.zh.md)、[`user-guide.zh.md`](../user-guide.zh.md)
> 和实际测试为准。
本文把 ETAF、Ebox、ECSS、TP、`etaf-ui`、`etaf-sqlite`、性能工具和两个
Playground 的职责收敛成一份待评审的边界提案。它描述的是从零设计时应保持的
模型,不是把当前实现中的每个文件名当成架构真相。
文中标有“目标”的部分是后续演进方向;除非代码、测试和基准同时完成,不能
把目标 API 描述成已经交付的功能。提案没有自动授权实现,也不改变当前 Host
注册表、View 语法或包依赖。
## 1. 最小稳定模型
ETAF 需要区分四种表示而不是把它们都叫作“组件”或“box”
```text
Component 语义与所有权
Text / Box View 结构
Ebox 几何与渲染计划
TP / Emacs 最终提交
```
### 1.1 公共概念
用户只需要学习三个主要概念:
| 概念 | 稳定职责 | 不负责的事情 |
| --- | --- | --- |
| `Text` | 文本内容、字体、inline text properties、换行入口 | 子树布局、业务状态、生命周期 |
| `Box` | 尺寸、背景、边框、padding、子节点和布局 | Component identity、Data、Action |
| `Component` | 可复用语义、props、slot、状态、Context、Action、生命周期 | 像素测量和布局算法 |
`Host` 是 Renderer 内部用于称呼可直接降低到后端的原语,不是用户需要额外
学习的第三套视觉分类。`Ebox Node` 则是更底层的几何/渲染对象;它可以由一个
`Text`、一个 `Box`,或一棵 Component 子树产生。
### 1.2 Text 与 Box 的关系
CSS 中所有最终可见元素都会产生 box但 CSS 生成的 box、ETAF 的 View 原语和
Ebox 的后端节点不是同一棵树。`inline` 和 `block` 描述盒子参与父布局的方式;
`flex``grid` 描述盒子内部如何排列子节点。
因此 ETAF 不应该把这些概念做成 Component而应在 Box 上保留两个正交属性:
```text
:outer = inline | block
:layout = normal | row | column | flex | grid
```
`:outer` 表示这个 Box 如何参与父级的普通布局;`:layout` 表示这个 Box 如何
排列自己的子节点。`normal` 是 ETAF 用户层对普通内容布局的名称Renderer
可以把它降低为 Ebox 内部的普通内容路径。
目标公共模型是:
```text
View = Text(content, typography)
| Box(layout, surface, children)
| ComponentCall(props, slots)
```
目标中 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` 是两个属性的正交组合:
```elisp
(box :outer 'inline :layout 'flex ...)
```
Component call 自身没有固定的 `:outer``:layout`;它的根 Text/Box 输出决定
它如何参与父级布局。透明 Component 可以输出多个兄弟节点,因此不能假定每个
Component 都有一个外部盒子。
### 1.3 属性分组
Box 的属性按 owner 分组,不能做成一个无约束的大 plist
| 分组 | 属性 | 作用 |
| --- | --- | --- |
| 外部/内部结构 | `: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 和查询 |
`:outer``:layout` 是结构/几何属性,变化会触发布局 owner它们不是 TP
paint slot。ECSS 可以参与解析它们,但最终合法性和布局仍由 Ebox 契约验证。
### 1.4 子节点与后端 content
目标 ETAF 语法只保留一个内容模型:子节点。
```elisp
(box "this is text")
```
等价于:
```elisp
(box (text "this is text"))
```
需要文本属性时显式写 `text`
```elisp
(box
:padding '(1 2)
(text :face 'bold "Title"))
```
ETAF 公共语法不增加 `(box :content "...")`。`:content` 是 Ebox 后端构造器
(例如 `ebox-create`的内部字段Renderer 可以把 `text` 降低为后端 content
但不能把后端字段反向暴露成第二套 ETAF 子树模型。这样可以自然混合字符串、
Text、Box、Component 和 slot也不会产生 content 与 children 的优先级歧义。
ETAF 公共模型也不定义 `spacer`。没有子节点的 `Box` 就是空 BoxEbox 可以在
内部使用空内容节点或私有 helper 表示它,但这不是用户需要学习的独立视觉类型。
## 2. 模块职责
下面的边界是“谁拥有完整规则”的判断标准。调用别的模块的公开契约不等于拥有
那个模块的职责。
### 2.1 ETAF Core
#### `etaf-view`
拥有:
- `Text`、`Box`、Component call、Fragment 的结构语法和规范化 View 值;
- 属性优先解析和 `expr :value` 求值边界;
- props、`key` 和 slot 的静态形状验证;
- Component 注册表与 View 名称解析。
不拥有:
- 字宽、像素、布局、滚动和 buffer 写入;
- Component 生命周期和 Runtime 调度;
- 业务数据、数据库、UI 控件实现。
#### `etaf-component`
拥有:
- `etaf-define-component`
- `:view`、`:setup`、`:styles` 定义边界;
- props 和静态 Component style 的声明验证。
不拥有:
- Ebox 私有节点;
- Data 请求、数据库连接或全局 UI 控件目录。
#### `etaf-runtime`
拥有:
- Component identity、`:key`、retained instance
- ref、computed、effect、watch 和 Scope cleanup
- Context、Theme、Action、Behavior、Data、Resource 的生命周期;
- 依赖失效、候选 generation、提交、回滚和调度
- 将变化定位到 Component、Range、Host 属性或 Paint contribution。
不拥有:
- 字体测量、flex/grid 算法、像素布局;
- Ebox 私有状态;
- SQLite、HTTP 或某个示例的业务规则。
#### `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 的提交适配
```
这两个边界不一定现在就变成两个发行包,但不能让布局算法和 Emacs buffer 副作用
混在同一个职责中。
### 2.3 ECSS
拥有:
- selector 解析和匹配;
- style declaration、cascade、继承和 computed style
- 把静态规则转换为可复用、可验证的样式中间产物。
不拥有:
- Component state、slot、Action
- 字宽、布局或 Ebox Node
- Emacs text property 的提交和回滚。
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
```
约束:
- Ebox 不向上依赖 ETAF
- TP 不解析 View也不调用 Component
- ECSS 不写 buffer
- `etaf-ui` 不调用 Ebox 私有函数;
- `etaf-sqlite` 不知道任何 UI
- Playground 不向 core 反向注入示例协议;
- 性能工具通过公开边界观察各包,不让被测包依赖某个示例。
## 4. Rust 与 Elisp 的切分
目标不是把所有代码机械地翻译成 Rust而是把确定性计算和环境副作用分开。
适合 Rust 纯计算核心的内容:
- 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 注册表仍包含:
```text
text · fragment · container · row · column · stack · flex · grid · spacer
```
当前 Renderer 直接把这些名称映射到 Ebox 构造器Ebox Playground 也直接使用
自己的布局 API。这只是现状记录不是目标 API也不构成必须保留的兼容表面。
`spacer``flow` 都不属于目标公共词汇;目标 Ebox 可以用普通 Box/Text 路径
替代当前的 `flow` 标记,不为它保留独立布局分支。
目标演进方向是:
```text
Text + Box(layout)
如果确实需要短写法row/column/flex/grid 只能作为新语法糖
```
这些简写不能通过 `etaf-define-component` 实现,否则每个布局节点都会被迫获得
Component 的 identity、scope、slot 和调度边界。它们应直接降低为 Ebox 布局节点。
不保留 `container`、`stack`、`spacer` 或旧 Host 注册名的兼容别名。
目标模型尚未交付前,不得修改现有 Host 语法后再把未验证的 `box` 描述成已完成。
## 7. 有边界的演进顺序
如果开始实现目标模型,只做下面这个最小顺序:
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`
任何一步不能证明结果等价或性能收益,就删除该实验,不继续堆下一层抽象。
这是一次干净重设计,不要求旧 `.el`、`.etaf` 文件或旧 Host 名称继续运行。迁移
由明确的源码更新完成,不通过长期兼容层隐藏模型变化。
## 8. 明确不做的事情
- 不把 `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 等不同所有权
和生命周期概念。