tp/docs/retained-runtime-target-architecture.md
Kinneyzhang 9d4047daf2 docs(tp): separate property composition from CSS cascade
Define TP as the independent retained text-property runtime and reserve complete stylesheet winner semantics for ECSS.

Verified: make test EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs LOAD_EXTRA="-L ../twidget2/dependencies" (729 passed); git diff --check
2026-08-06 18:03:48 +08:00

15 KiB
Raw Blame History

TP Retained/Reactive Text Runtime 目标架构

英文版见 TP Retained/Reactive Text Runtime Target Architecture

状态TP 1.0 已批准目标尚未实现。TP 0.3.x 当前事实仍以 ARCHITECTURE.mdAPI-SEMANTICS.md 为准。

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 committerproducer 和 consumer 的 prepare 阶段只能产生候选值。
  • 一个 live object 只有一个 TP identityconsumer 可以保存 opaque handle但不能建立需要长期同步的平行 identity。
  • signal update 从 source 直达 binding再从 object 直达 marker-backed mount正常热路径不扫描 buffer-list 或按 tp-name/tp-layers 搜索文本。
  • tp-nametp-layerstp-meta 不是 live runtime 数据库definition、provenance、identity、dependencies 和 mount metadata 都在 side state。
  • capability 只有 contentproperties 两种;它们共享同一计算和提交语义,只是写权限不同。
  • 普通函数值是 literal只有显式 tp-computed value 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 policypresence、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-readtp-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 fragmentattachment 不进入 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 semanticscommit 后修改原值不能改变已发布 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 anchorproducer 在 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 被 killkill-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-settp-resettp-addtp-remove、lookup/search APIs 继续复用这些底层语义。

10.2 响应式已有文本

(tp-watch buffer start end compute)

tp-watch 创建 properties range anchor、surface object 和 bindingCOMPUTE 返回 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 时必须长期通过:

  1. static string/buffer propertiesnested face、keymap、help-echo function、explicit nil
  2. reactive statustp-watch 与 retained content producer、conditional/binding dependency、batch
  3. retained dashboardkeyed add/remove/reorder、button、signal-driven theme declarations、rollback
  4. existing-text diagnosticsrange anchor、host edit、overlap、external property conflict/rebase/unmount
  5. two-buffer produceridentity/state 隔离与 global-signal multi-surface atomicity
  6. performancesparse update 与实际 dependency closure 成正比equal value 零 buffer opunmount 后无 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 并建立 surfacenormal 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. 实施前必须冻结的合同

实现开始前的 executable contract tests 必须确定prepare-context/object 时序binding identity/lifecycleliteral/computed valuerange anchor 和 property conflictsingle/multi-surface rollback三层便利 APIerror taxonomyreport shapeexplicit nil/absenceread-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。