Replace the legacy managed layer renderer with one independent retained/reactive text runtime. TP now owns exact dependencies, stable objects, marker-backed mounts, property contribution composition, atomic publication, rollback, and direct text-property facades without ECSS or Ebox dependencies.\n\nBREAKING CHANGE: remove tp-render, tp-stack, scan-driven managed layers, inline runtime metadata, TP-owned CSS cascade APIs, and dollar-variable declarations.\n\nVerified: 290/290 ERT, shuffled 290/290 (seed 20260806), 8/8 doctests, WERROR compile-all, checkdoc, package-lint, diff-check, and isolated TP-only load.
15 KiB
TP Retained/Reactive Text Runtime 目标架构
英文版见 TP Retained/Reactive Text Runtime Target Architecture。
状态:TP 1.0 已实现的架构合同。本文记录已经落地并由测试保护的目标边界;当前模块和公共行为事实分别以 ARCHITECTURE.md 与 API-SEMANTICS.md 为准。文件名保留 target-architecture 以维持既有链接稳定。
1. 产品定位
TP 1.0 是一个可独立安装、测试、发布和使用的 Emacs retained/reactive text runtime:它把声明式属性、响应式数据和稳定文本对象投影到 string 与 buffer,并拥有最终文本属性 diff、marker/index、事务和 buffer publication。
TP 不依赖 Ebox,也不包含 Box、Flex、Grid、padding、layout owner 或 Ebox region 等空间布局概念。Ebox、ETAF、dashboard、diagnostics、交互列表和普通 buffer 标注都可以通过同一 TP public API 使用它。
TP 也不是 CSS 引擎。完整 stylesheet、selector、specificity、origin/importance、CSS cascade layer、CSS-wide value 和 custom property 由独立 ECSS 包负责;ECSS 可以把最终 computed declarations 交给 TP 发布,但 TP 不 require、检测或命名 ECSS。
一句话边界是:
TP 负责对象、依赖、位置、变化和提交;上层 consumer 负责这些对象的业务含义以及期望显示结果。
2. 设计不变量
- TP source、tests、examples 和 package metadata 不 require、检测或命名 Ebox。
- TP 不拥有 CSS stylesheet、selector matching、specificity 或 cascade winner;这些能力属于独立 ECSS 包。
- live runtime 只有一套 property、binding、surface、mount、diff 和 transaction core,不增加 embedded mode。
- TP 是唯一最终 buffer committer;producer 和 consumer 的 prepare 阶段只能产生候选值。
- 一个 live object 只有一个 TP identity;consumer 可以保存 opaque handle,但不能建立需要长期同步的平行 identity。
- signal update 从 source 直达 binding,再从 object 直达 marker-backed mount;正常热路径不扫描
buffer-list或按tp-name/tp-layers搜索文本。 tp-name、tp-layers、tp-meta不是 live runtime 数据库;definition、provenance、identity、dependencies 和 mount metadata 都在 side state。- capability 只有
content与properties两种;它们共享同一计算和提交语义,只是写权限不同。 - 普通函数值是 literal;只有显式
tp-computedvalue source 会执行并收集依赖。 - signal source、bindings、plans、markers/indexes 和 opaque client state 在 transaction 中使用同一 revision,失败时共同回滚。
3. 独立数据流
application state / theme / host edit
│
▼
signals + bindings
│ exact dependency graph
▼
TP prepare context
│ candidate objects/bindings/anchors
▼
ordinary or client producer
│ object → direct property contributions
│ → generic keyed surface plan
▼
reconcile + prepare diff
│ prepared text/property operations
▼
atomic multi-surface publication
│
▼
strings / Emacs buffers
prepare 只产生 candidate state。producer 返回普通 surface plan 和可选 opaque client state。TP 不解释 client state,但把它与 plan、dependencies、mounts 和 revision 一起原子晋升。
4. 属性 contribution、合成与 value source
4.1 Property policy 与 contribution composition
TP 为它最终写入的 Emacs text property 维护通用 property policy:presence、normalizer、validator、equality、merge 和 projector。policy 只回答多个 TP contribution 如何合成、值是否真的变化以及怎样形成最终 Emacs property;它不定义 selector、stylesheet 或 CSS winner。
每个 properties mount 只贡献它声明的 property。TP 按稳定的 mount/contribution 顺序逐属性合成 host baseline 与所有 TP contribution,严格区分显式 nil 和 absent,并保存足够的 side state 以便撤销单个 contribution、检测外部冲突和执行事务回滚。这个过程是文本属性 ownership/composition,不是 CSS cascade。
ECSS 是独立的纯样式计算包:输入 property schemas、subject tree/adapter 与 stylesheet,输出 computed declarations 和 provenance,不读写 buffer、不持有 marker,也不依赖 TP 或 Ebox。Ebox 或普通文本 UI 若需要完整 CSS 能力,先调用 ECSS,再把最终文本/属性计划交给 TP;不需要 CSS 的调用者直接使用 TP。
4.2 Literal 与 computed
普通 Elisp value 永远是 literal,包括 function object。help-echo function、keymap command 和 callback 不会被 TP 隐式调用。
需要求值的声明必须使用 tp-computed 包装 compute function。compute 在所属 object binding 的 prepare context 中运行;tp-signal-read 和 tp-binding-read 自动登记依赖。返回值随后按 property policy normalize/validate,并按 literal 处理,不隐式调用第二次。错误终止整个 candidate transaction;相等结果不产生 surface change。
上层 DSL 可以接受可求值 Elisp,但必须在 constructor/compiler 边界明确区分 quoted list、literal function 和 computed source,并把响应式计算编译到 tp-computed,不能建立第二套 watcher。
5. Stable object 与 prepare context
每个 object 的 identity 只在一个 surface 中有效,由 parent identity、sibling key 和 opaque kind 保留。显式 key 在同一 parent 下唯一;unkeyed object 只能按 position+kind 保留;handle 不能跨 surface 使用。
TP 在 mount/update prepare 开始时创建短生命周期 prepare context。producer 调用:
(tp-object-ensure context parent key kind)
匹配的 live object 返回原 handle;新增 object 只存在于 candidate state。duplicate key、stale parent、orphan object 或 cross-surface handle 在 prepare 中失败。只有成功 publication 才把 candidate identity 变成 live identity;失败 handle 必须不可解析。
tp-surface-materialize-string 给 producer 提供相同语义的 ephemeral context。object/binding 只在这次纯计算中存在,string 返回后全部释放,不进入 live registry。
tp-object-resolve 只读地按 surface 与 key path 解析 live handle,用于公开选择/交互入口,不创建对象。
6. Binding 与真正的响应式依赖
definition 是可复用配方,binding 是安装在一个 object 上的具体计算。tp-bind 以 object handle 加 caller-namespaced binding key 幂等安装,至少保存 compute、last successful value、dependencies、dirty state、revision 和 lifecycle policy。
tp-binding-read 读取另一个 binding 的 memoized value并登记 binding→binding dependency。因此 signal、computed property、measurement 或普通 derived value 可以组成真实依赖图,而不是在变量变化后扫描 buffer。
重新计算前撤销旧依赖,成功后以本次实际读取集合替换。conditional branch 因而会自动断开不再使用的 source。transaction dirty queue 去重,同一 binding 每批最多重算一次;循环报告完整路径。
每次 prepare 记录 touched object/binding。没有可见字符但仍属于 candidate 的 logical object 必须显式 retain;一个 logical object 可以通过 prepare-only attachment 对应多个不连续 plan fragment,attachment 不进入 plan。删除 object 时同步清理 bindings、subscriptions、anchors 和 mounts;仍存在但本次未声明的 binding 默认删除,只有显式 lifecycle policy 才能保留。普通 binding 可返回任意 client value,只有 designated producer binding 返回 plan/subplan。
7. Surface plan
surface plan 是不可变语义的纯数据,节点字段为:
| Field | Contract |
|---|---|
key |
sibling-local stable key |
kind |
opaque comparable discriminator |
text |
optional plain/propertized string leaf |
props |
final direct Emacs text properties |
children |
ordered child plans |
tags |
opaque side metadata indexed but not interpreted by TP |
capability |
content or properties |
plan 不携带 marker、buffer position、patch op、producer closure、binding closure 或 client continuation。合法的 keymap/help-echo/callback function 可以作为最终 property value 保留,reconciler 永不调用它们。
public constructor 对 caller-owned plist/string 做 defensive copy 或提供 immutable semantics;commit 后修改原值不能改变已发布 surface。
8. Mount、range anchor 与属性所有权
8.1 Content capability
content mount 拥有其 span 的文本和属性,可以插入、删除、移动、替换文字以及修改属性。它适合 dashboard、menu、special UI buffer 和高级 renderer output。
8.2 Properties capability
properties mount 只能贡献声明的属性,不能修改 host text。tp-range-anchor-create 从 buffer、start/end 和 marker insertion policy 创建 opaque marker-backed anchor;producer 在 prepare 中调用 tp-object-attach-range 把 object 绑定到 anchor。plan 本身仍没有位置。
TP side state 为每个 anchor/property interval 保存 host baseline、各 TP contribution 和最后一次 published value。同一 surface 的重叠 contribution 通过 property policy 逐属性合成。
外部代码把同一 property 改成不同于 TP 最后发布值时,下一次 prepare 默认报 tp-property-conflict。TP 不覆盖外部值,也不恢复旧 snapshot;调用者显式 tp-range-rebase 或 unmount。unmount 只撤销 TP contribution:当前值仍等于 TP 最后发布值时恢复 baseline,否则保留外部值并在 report 中记录 conflict。
host text 编辑由 live markers 跟随。跨越或删除 anchor 边界时按显式 shorten/remove/stale policy 处理;TP 不扫描附近文本猜测新位置。
9. Reconcile 与 transaction
TP 按 parent/key reconcile object identity,并对 old/new plan 计算 child insert/remove/reorder、text diff 和 property-run diff。normal update 从 object→mount index 直接取得一个或多个范围;公开查询只返回数值 range/tag snapshot,不暴露 live marker。
transaction 顺序是:冻结 candidate signal writes;建立 prepare contexts;准备所有实际受影响 surfaces;验证 capability/conflict/lifecycle;生成 per-surface operations 与 inverse journals;按稳定 surface id publish;最后原子切换 source values、bindings、plans、indexes、client state 和 revisions;全部成功后才运行 observers。
一个 global signal 可以触达多个 surfaces。TP 必须先全部 prepare,再逐 surface publish;任一 compute、buffer write、marker/index step 或 transaction participant 失败时,回滚已经发布的 surfaces,并恢复 signal values、binding values/dependencies、dirty queues 和 revisions。observer failure 发生在成功 commit 后,只记录而不回滚。
若 publish 中 buffer 被 kill,kill-buffer teardown 是不可逆权威结果;其他 surface 与 source state 回滚,TP 不复活 killed buffer。
10. Public API 层级
10.1 简单的一次性属性
(tp-propertize string declarations)
(tp-apply buffer start end declarations)
二者使用相同的 direct-declaration validation/projection 和 mutation primitives,不建立 identity、binding 或 live mount。现有 tp-set、tp-reset、tp-add、tp-remove、lookup/search APIs 继续复用这些底层语义。
10.2 响应式已有文本
(tp-watch buffer start end compute)
tp-watch 创建 properties range anchor、surface object 和 binding,COMPUTE 返回 declarations,结果是可 update/unmount 的 opaque handle。普通用户不需要手工创建 object、binding 或 surface。
10.3 完整 retained UI
(tp-surface-mount buffer plan-or-producer options)
(tp-surface-update surface plan-or-producer)
(tp-surface-update-scoped surface objects plan-or-producer options)
(tp-surface-unmount surface)
tp-surface-update-scoped 是一次事务内基于 retained object identity 的授权。TP 通过 mount index 解析对象,支持一个对象拥有多个离散 mount,验证完整 candidate 不会修改授权范围之外的输出,然后在同一回滚边界内发布 text、direct properties、mount、index、client state 和 revision。scope 不写入文本,也不会形成第二套响应式 runtime。默认 mismatch 直接报错;调用者可显式选择 full-root fallback。
完整 API 还包括 tp-computed、signals、prepare/object、bindings、range anchors、transactions、materialize-string、at-point/inspect/report。TP 不增加含义重叠的 tp-mount/tp-update aliases,也不公开 raw marker/patch/change-set、domain dirty kind 或 :impact APIs。
11. 独立验收场景
TP 1.0 在没有 Ebox load-path 时必须长期通过:
- static string/buffer properties:nested face、keymap、help-echo function、explicit nil;
- reactive status:
tp-watch与 retained content producer、conditional/binding dependency、batch; - retained dashboard:keyed add/remove/reorder、button、signal-driven theme declarations、rollback;
- existing-text diagnostics:range anchor、host edit、overlap、external property conflict/rebase/unmount;
- two-buffer producer:identity/state 隔离与 global-signal multi-surface atomicity;
- performance:sparse update 与实际 dependency closure 成正比,equal value 零 buffer op,unmount 后无 marker/subscription/weak-reference 泄漏。
12. 兼容、版本与删除
TP 1.0 是主版本切换。能直接映射到统一 core 的静态 public API 保留;依赖 inline tp-layers/tp-meta database 和扫描式 renderer 才能工作的 managed behavior 必须明确删除或替换,不保留 hidden compatibility engine。
允许显式 one-shot legacy import 扫描历史 propertized text 并建立 surface;normal signal/property/update 热路径禁止自动调用它。
当前实现已经删除 layer→buffer registry、scan-driven refresh hooks、旧 inline managed codec、重复 transaction 和旧 batch renderer。TP 1.0 package、tests、examples 和本文档均可在没有 Ebox repository 的环境中独立工作。
13. 已冻结并实现的合同
TP 1.0 的 executable contract tests 已确定并持续保护:prepare-context/object 时序;binding identity/lifecycle;literal/computed value;range anchor 和 property conflict;single/multi-surface rollback;三层便利 API;error taxonomy;report shape;explicit nil/absence;read-only、undo、narrowing、indirect-buffer 和 kill-buffer 行为。
任何实现若要求 Ebox/ECSS-specific branch、CSS selector/cascade winner、post-commit identity scan、plan 中的 raw position/closure、第二套 renderer 或无法安全撤销 properties contribution,应停止接入并重新评审 ownership model,而不是增加 adapter mode。