# TP Retained/Reactive Text Runtime 目标架构 英文版见 [TP Retained/Reactive Text Runtime Target Architecture](retained-runtime-target-architecture-en.md)。 公共入口索引见 [API-REFERENCE.md](API-REFERENCE.md);语义合同见 [API-SEMANTICS.md](API-SEMANTICS.md)。 状态:TP 1.0 已实现的架构合同。本文记录已经落地并由测试保护的目标边界;当前模块和公共行为事实分别以 [ARCHITECTURE.md](ARCHITECTURE.md) 与 [API-SEMANTICS.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-computed` value source 会执行并收集依赖。 - signal source、bindings、plans、markers/indexes 和 opaque client state 在 transaction 中使用同一 revision,失败时共同回滚。 ## 3. 独立数据流 ```text 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 调用: ```elisp (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 简单的一次性属性 ```elisp (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 响应式已有文本 ```elisp (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 ```elisp (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 properties:nested face、keymap、help-echo function、explicit nil; 2. reactive status:`tp-watch` 与 retained content producer、conditional/binding dependency、batch; 3. retained dashboard:keyed add/remove/reorder、button、signal-driven theme declarations、rollback; 4. existing-text diagnostics:range anchor、host edit、overlap、external property conflict/rebase/unmount; 5. two-buffer producer:identity/state 隔离与 global-signal multi-surface atomicity; 6. 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。 当前没有 legacy import API;调用者必须显式提供 plan 或 producer。TP 不会扫描历史 propertized text 来重建 surface identity。 当前实现已经删除 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。