Implement the P0 View grammar, expr bridge, stateless view Components, and Ebox mount path in a new independent package. Include bilingual architecture and implementation documents plus contract tests.
161 lines
6.4 KiB
Markdown
161 lines
6.4 KiB
Markdown
# ETAF 架构
|
||
|
||
本文定义目标架构,是公共概念和职责边界的契约,不是变更日志。
|
||
|
||
## 1. 唯一的降级路径
|
||
|
||
```text
|
||
Application
|
||
→ Runtime / State / Action / Data
|
||
→ Component(props, local scope)
|
||
→ View
|
||
→ ETAF Renderer
|
||
→ Ebox Node
|
||
→ measure → layout → paint → commit
|
||
→ Emacs buffer
|
||
```
|
||
|
||
ETAF 描述应用结构和行为;Ebox 是底层引擎,负责把可测量的 Node 树转换为文本 surface,并原子地发布这些 surface。每个新的 ETAF 功能都必须在这条路径上拥有唯一职责归属。
|
||
|
||
## 2. 用户需要理解的四个概念
|
||
|
||
### View
|
||
|
||
View 是结果的结构描述。View 可以是 Host 调用、Component 调用、字符串文本叶子、`nil`,或者这些值组成的序列。
|
||
|
||
### Component
|
||
|
||
Component 是复用边界。它接收 props,可选地拥有局部 Scope,并产生 View。Component 不是另一种 Ebox Node,也不应因为“可复用”就增加新的 renderer 分支。
|
||
|
||
### Node
|
||
|
||
Node 是 Ebox 的可测量、可渲染模型。Ebox 负责几何、盒模型、布局、surface 属性、滚动、运行时身份和 buffer 发布。ETAF 不把 Component 语义、slot、Action 或事件塞进 Ebox 属性。
|
||
|
||
### Runtime
|
||
|
||
Runtime 负责挂载、调度、状态失效、事件边界、提交协调、回滚和销毁。Runtime 是应用边界,不是另一种 View 节点。
|
||
|
||
## 3. 统一的 View 语法
|
||
|
||
所有 Host 和 Component 调用都使用同一种形状:
|
||
|
||
```elisp
|
||
(NAME ATTRIBUTE* CHILD*)
|
||
```
|
||
|
||
属性是 `:KEY VALUE`;子节点是 View、字符串、`nil`,或通过 `expr` 产生的序列。属性区必须在子节点区之前完整结束:
|
||
|
||
```elisp
|
||
(text :face 'bold :color "#F4F6FB" "Hello")
|
||
```
|
||
|
||
下面的写法非法,因为属性区和子节点区交错:
|
||
|
||
```elisp
|
||
(text "Hello" :face 'bold)
|
||
```
|
||
|
||
核心 Host 和公共 Component 都遵循同一规则;`text` 没有另一套“位置参数作为内容”的特殊约定。
|
||
|
||
## 4. Elisp 求值边界
|
||
|
||
编译器读取结构位置中的 View,不要求对 View 使用 quote:
|
||
|
||
```elisp
|
||
(etaf-view (text "Hello"))
|
||
```
|
||
|
||
属性值是普通 Elisp 表达式位置:
|
||
|
||
```elisp
|
||
(etaf-view
|
||
(text :face (if dark 'light 'dark) "Theme"))
|
||
```
|
||
|
||
子节点区只有一个明确的计算桥接:
|
||
|
||
```elisp
|
||
(expr :value (if checked "☑" "☐"))
|
||
```
|
||
|
||
`expr` 只接受 `:value`。`if`、`when`、`cond`、`let`、`mapcar` 及其他 Elisp 形式仍然是该 value 内的普通 Elisp;ETAF 不增加单独的 `if`、循环或计算节点类别。
|
||
|
||
表达式要返回 View 时,显式构造它:
|
||
|
||
```elisp
|
||
(expr
|
||
:value
|
||
(when open
|
||
(etaf-view (text :face 'bold "Details"))))
|
||
```
|
||
|
||
`'(text "Details")` 是数据,不是 View。因此 quote 既不是全局禁止,也不是第二套 View 编译器。
|
||
|
||
## 5. Component
|
||
|
||
公共定义模型只有入门形式和高级形式:
|
||
|
||
```elisp
|
||
(etaf-define-component NAME (&key PROPS)
|
||
:view VIEW)
|
||
|
||
(etaf-define-component NAME (&key PROPS)
|
||
:setup SETUP)
|
||
```
|
||
|
||
`:view` 是结构语法,不需要 quote;`:setup` 是普通 Elisp,用于建立 Component Scope 并返回零参数 render 函数;普通 Elisp 产生 View 时使用 `etaf-view`。`:view` 与 `:setup` 互斥。组件静态样式使用独立的定义元数据入口 `:styles (styles RULE...)`;styles 不是第三种渲染模型。
|
||
|
||
Props 和 children 的职责不同。Props 是 Component 声明的命名输入;末尾 children 和具名 slot 组成同一个 slot collection,而不是隐藏的第二套 Component 参数表。该 collection 在 Component 边界统一归一化并投影。
|
||
|
||
在 View 语法中,公共 Component 可以省略 `etaf-` 前缀;普通 Elisp API,如 `etaf-value`、`etaf-ref`、`etaf-mount`,保留前缀。别名解析只发生在结构位置;发生冲突时使用 `list-view` 这样的语义别名,不覆盖 Elisp 的 `list`。
|
||
|
||
## 6. 核心 Host 与官方 Component
|
||
|
||
ETAF 核心只包含最小的、无样式 Host:
|
||
|
||
```text
|
||
text · fragment · container · row · column · stack · flex · spacer
|
||
```
|
||
|
||
这些名字表达结构布局和文本 surface。Button、Checkbox、Input、Dialog、DataGrid 等产品级控件都是独立 `etaf-ui` 包提供的普通 Component。核心的可按压能力通过 `:role`、`:on-press` 等语义属性表达,不建立与 `etaf-ui` Button 竞争的核心 Button Host。
|
||
|
||
字符串是最小文本 View,会降级为带 content 的 Ebox box;`text` 是用于样式和语义属性的显式文本 Host,不是另一种 Ebox Node 家族。
|
||
|
||
## 7. 非视觉能力
|
||
|
||
这些机制扩展同一个 Component/Host 边界,但不建立平行的视觉节点类别:
|
||
|
||
| 机制 | 职责 | 入口 |
|
||
| --- | --- | --- |
|
||
| `on-xx` | 一个局部事件回调 | Host/Component 属性 |
|
||
| Action | 有名字、可测试的业务变更 | Action 定义和 dispatch |
|
||
| Effect | 外部同步、订阅和清理 | Scope 中的 Effect |
|
||
| Behavior | 可复用的非视觉能力集合 | `:use` 属性 |
|
||
| watch | 响应式失效原语 | Reactive API 或 Behavior |
|
||
| Context | 继承的 Scope 值 | Provide / Inject |
|
||
| Data | 请求、状态、取消和错误归属 | Core data API |
|
||
|
||
`expr` 负责计算子节点值,不拥有身份、生命周期、订阅、buffer 写入或 Effect。Behavior 可以使用事件、ref、Effect 和 watch,但自身不会变成 View 节点。
|
||
|
||
## 8. 包职责
|
||
|
||
```text
|
||
ebox
|
||
└── 可选的 ebox-playground
|
||
|
||
etaf → ebox
|
||
├── reactive / state / action / effect / data / ECSS
|
||
└── 纯 .etaf compiler
|
||
|
||
etaf-ui → etaf
|
||
etaf-playground → etaf(展示官方 Component 时再依赖 etaf-ui)
|
||
```
|
||
|
||
Ebox 不理解 Component、slot、Action、Context 或 data。ETAF 只通过 renderer 边界和 Ebox 公共 API 使用 Ebox。`etaf-data` 是 core 能力,不是用户必须额外安装的平级包。数据库和外部集成使用明确的数据源包,例如 `etaf-sqlite`;不需要抽象的公共 `etaf-adapters` 层。
|
||
|
||
两个 Playground 都是独立的可选工具:`ebox-playground` 只使用 Ebox;`etaf-playground` 使用 ETAF 公共 API,绝不调用 Ebox Playground 或 Ebox 私有函数。
|
||
|
||
## 9. 扩展规则
|
||
|
||
应用层功能优先使用 Component、Behavior、Action、Effect、Context 值或 data source。只有当功能确实改变测量、布局、surface 绘制、滚动或发布,并且已经定义好 signature、dirty 分类、渲染、回滚和测试责任时,才增加 Ebox 属性或 Node。这样既保持用户模型小而统一,又通过普通表达式位置和 Runtime API 保留 Elisp 的图灵完备能力。
|