# TP 1.0 API Semantics 本文记录 TP 1.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为;目标背景与设计理由见 [retained runtime architecture](retained-runtime-target-architecture.md)。 完整的公共符号、参数形状、返回值和示例见 [API reference](API-REFERENCE.md)。 ## 1. 产品边界 TP 负责最终 Emacs text property policy/contribution、signals/bindings、stable objects、marker-backed mounts、surface diff、transaction、rollback 和 Buffer publication。 TP 不实现 selector、stylesheet、specificity、origin/importance、CSS cascade layers、CSS-wide values、custom properties 或 Box/Flex/Grid。需要 CSS cascade 的调用者先通过独立 ECSS 获得 final declarations,再交给 TP。 ## 2. 普通值、显式 nil 与 computed source 普通 Elisp value 始终是 literal,包括 function object、keymap command、`help-echo` callback 和 list。TP 不会因为一个值可调用就执行它。 需要求值的 declaration 必须显式使用: ```elisp (tp-computed (lambda () ...)) ``` `tp-resolve-value` 只执行这种 tagged source。compute result 只 normalize/project 一次,不会被隐式二次调用。compute error 直接终止 candidate transaction。 TP 严格区分: - absent:property plist 中没有该 key; - present nil:property key 存在,value 为 `nil`。 `tp-member` 和底层 contribution ledger 使用 presence-aware 语义。properties mount 的显式 nil contribution 可以覆盖 baseline,但 unmount 仍只撤销 TP 自己拥有的 contribution。 ## 3. Property policy 与 direct declarations `tp-define-property-policy` 为一个 canonical native property id 注册: - normalizer; - validator; - equality; - merge; - projector。 注册是原子的:无效 options 或 function 会报 `tp-invalid-property-policy`,旧 definition 保持不变。 `tp-register-text-property` 为原生 Emacs property 建立默认 policy,并返回 policy record。`tp-text-property-id` 把原生 property 映射到 canonical `text/PROPERTY` id;`tp-text-declarations` 把普通 property plist 转换为 canonical declarations。 `tp-merge-declarations` 按输入顺序合并 direct declaration groups,并防御性复制 caller-owned value。它只做 TP contribution composition,不实现 CSS winner selection。 `tp-define-style`、`tp-style-declarations` 和 `tp-undefine-style` 管理 named direct declarations。registry getter 返回防御性副本。 ## 4. Declaration recipes `define-tp` 定义一个返回 native property plist 的 recipe;`define-tps` 定义一组有序 recipe elements。`tp-define-layer`、`define-tp-group` 与 `tp-define-group` 是同一静态 declaration workflow 的命名入口。 Recipe 可以是静态或参数化的,可以组合其他 recipes。展开结果经过同一 direct property policy/projector。Recipe application 不建立 live identity,不写 `tp-name`、`tp-layers` 或 `tp-meta`。 旧 `$variable` syntax 会报 `tp-invalid-layer-definition`。响应式值必须使用 `tp-computed` 加 signal/binding,不能建立第二套 watcher runtime。 Recipe/group definition 与 redefinition 是原子的:definition body、generated named elements 或 compiled style 任一步失败时,不留下半个新 definition,已有 definition 保持可用。 ## 5. 一次性 façade ### 5.1 `tp-propertize` ```elisp (tp-propertize STRING DECLARATIONS) ``` 返回新的 propertized string,不修改输入 STRING,不创建 object、binding、anchor、mount 或 surface。 ### 5.2 `tp-apply` ```elisp (tp-apply BUFFER START END DECLARATIONS) ``` 只修改 BUFFER 的 `[START, END)` 文本属性,不替换文字,成功返回 `(START . END)`。无效 buffer 不会退回 current buffer;无效 range 直接报错。 ### 5.3 Direct operations `tp-set`、`tp-reset`、`tp-add`、`tp-remove`、`tp-clear`、`tp-get`、`tp-at` 与 `tp-member` 保留既有 string/buffer 调用形状,但统一经过 direct property resolution。 - whole-string `tp-set`/`tp-reset`/`tp-add` 返回新 string; - 带 range 的 string 操作按各函数 docstring 的 mutation contract 执行; - buffer 坐标使用 Emacs 原生 1-based position; - string 坐标使用 0-based index; - direct buffer write 遵循 Emacs read-only、undo 和 modified semantics。 Search/match/regexp/navigation/query API 继续委托 Emacs 原生 text-property interval 语义,不创建 retained runtime。 ## 6. Signals 与 bindings `tp-signal-create` 返回 global 或 buffer-scoped signal。`tp-signal-read` 在当前 binding computation 中登记依赖;`tp-signal-peek` 只读值而不登记依赖;`tp-signal-set` 在 transaction 中设置 candidate value。 相等写入按 signal equality 返回 no-op,不 invalidates subscribers。buffer-scoped signal 随 buffer kill 自动 dispose;global signal 使用 `tp-signal-dispose` 显式释放。 `tp-bind` 的 identity 是 owner 加 caller-namespaced key。相同 owner/key 幂等复用 binding。binding 保存 last successful value、dynamic dependencies、dirty/revision state 和 lifecycle policy。 `tp-binding-read` 读取 memoized binding value并登记 binding-to-binding dependency。每次 recompute 成功后,本次实际读取集合替换旧 dependencies;条件分支因此自动断开旧 source。cycle 报 `tp-binding-cycle` 并包含 dependency path。 删除 owner 会释放 bindings、subscriptions 和下游 edges。`tp-reactive-counters` 只读报告 invalidated、recomputed、skipped、subscription-added 和 subscription-removed,用于结构性性能验收,不暴露内部 hash tables。 ## 7. Surface plan 与 object identity `tp-surface-plan-create` 的公共字段是: | Field | Contract | | --- | --- | | `key` | sibling-local stable key;同一 parent 下不可重复 | | `kind` | opaque comparable discriminator | | `text` | optional plain/propertized string leaf | | `props` | final direct Emacs properties | | `children` | ordered child plans | | `tags` | opaque indexed metadata,TP 不解释其业务含义 | | `capability` | `content` 或 `properties` | Plan 不允许 marker、buffer position、patch op、producer closure 或 binding closure。Constructor 防御性复制 caller 提供的 key、kind、text、props、children list 与 tags,使 caller 后续 mutation 不改变 committed plan。`tp-surface-result-create` 只携带 plan 与 opaque client state;TP 在 prepare transaction 内从该 plan 生成唯一的 authoritative rendered output,不接受第二份 caller-provided text 或 traversal records。Result 与 producer normalization 会在各自边界建立防御性 plan snapshot。Producer 如果为当前 candidate freshly allocate 了完整 plan tree,并且之后不再 mutation 或 expose,可使用 `tp-surface-plan-create-owned` 与 `tp-surface-result-create-owned` 将 plan 所有权转给 TP,跳过重复 snapshot;owned result 必须绑定当前 active prepare context,并且只允许被 prepare normalization 消费一次;普通 caller-owned plan 必须使用复制入口。 producer 在 prepare 阶段接收 context,并在产生 plan 前调用: ```elisp (tp-object-ensure CONTEXT PARENT KEY KIND) ``` Object identity 只在所属 surface 中有效。显式 key 按 parent/key/kind reconcile;unkeyed object 按 position/kind reconcile。duplicate sibling key、stale parent、orphan object 或 cross-surface handle 在 prepare 中失败。 Candidate object 只有成功 publication 后才变为 live。失败 candidate handle 必须不可解析。`tp-object-resolve` 按 surface/key path 查询 live handle,不创建 identity。 无可见字符但需要保留的 logical object 使用 `tp-object-retain`。一个 logical object 可以通过 `tp-object-attach-fragment` 关联多个离散 plan fragments;attachment 保存在 prepare side state,不进入 plan。 当 producer 已经证明一个现有 logical subtree 在本次 candidate 中保持不变,可以使用 `tp-object-retain-subtree` 复用当前 live root 下已知的 object path。该 API 会把 subtree objects 标记为 touched/retained,并保留其 live bindings;它不创建缺失对象,也不替 producer 验证 candidate plan、输出范围或布局不变性。调用方仍必须提交完整 candidate plan,并在不满足证明时回退到普通 `tp-object-ensure` 路径。若完整 candidate 仍会重新提交这些对象的 output attachment,但已证明它们的 identity、lifecycle 和 output 不变,可使用 `tp-object-reuse-subtree`:对象保留在 candidate 中但不进入 touched/reconcile 集合。 Content range ownership 有两个入口:`tp-object-attach-content-ranges` 对每个 `:tags` 建立防御性快照;只有当 producer 为当前 candidate 新建了 tags、之后不会再修改或暴露它们时,才可使用 `tp-object-attach-content-ranges-owned` 转移这些值的所有权并跳过复制。转移入口不改变失败 candidate 的 rollback 语义;未成功 publication 的值会随 candidate 一起丢弃。普通 caller-owned tags 必须使用复制入口。 `tp-surface-report` 始终返回完整的防御性 report snapshot。只需要 transaction、operation、scope 或 object reconciliation 计数等提交标量的 integration caller 应使用 `tp-surface-report-summary`,它不暴露 nested report payload,也不为读取这些标量复制整份 report。 ## 8. Surface lifecycle ### 8.1 Materialize ```elisp (tp-surface-materialize-string PLAN-OR-PRODUCER) ``` 以 ephemeral prepare context 生成 propertized string,不建立 live surface。函数返回前释放 candidate objects、bindings、subscriptions 和 anchors。 ### 8.2 Mount/update ```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) ``` Mount options 支持 `:capability`、`:start`、`:end`、`:inhibit-read-only`、`:client-state` 和 `:observers`。 `content` surface 拥有其 span 的 text 和 properties,可以插入、删除、替换或移动输出。`properties` surface 只能贡献声明的 properties,不能替换 host text。 `tp-surface-update-scoped` 仍接收完整 candidate。TP 从 object-to-mount index 得到授权范围,验证 candidate 没有改变范围外输出,再在同一 transaction 发布。默认 mismatch 报 `tp-scope-mismatch`;`(:on-mismatch root)` 显式允许 full-root fallback。默认返回 defensive report snapshot;调用方如果只通过 `tp-surface-report-summary` 读取标量提交指标,可传 `(:return-report nil)` 避免构造完整 report snapshot。 相等 candidate 不产生 publication,surface revision 和 buffer modified state 保持不变。 ### 8.3 Unmount `tp-surface-unmount` 释放 surface、objects、bindings、mounts、markers、subscriptions、indexes 和 opaque client state,并返回 generic report。content surface 删除自己拥有的 span;properties surface 只撤销仍由 TP 拥有的 contributions。 kill-buffer cleanup 以 buffer 死亡为权威结果,释放 runtime state,不尝试复活 buffer。 ## 9. Range anchors 与 property conflicts `tp-range-anchor-create` 接受 buffer、start/end、marker insertion policy 与 `stale`/`shorten`/`remove` boundary policy,返回 opaque handle。raw marker 和 position 不进入 surface plan。 producer 使用 `tp-object-attach-range` 把 object 绑定到 anchor。一个 properties surface 的重叠 mounts 通过 property policy 合成 contributions。 TP 为每个 interval 保存: - host baseline presence/value; - ordered TP contributions; - last published presence/value。 如果当前 host value 与 TP last published value 不同,update 报 `tp-property-conflict`,不会覆盖外部值。`tp-range-rebase` 显式把当前 host state 接受为新 baseline。Unmount 只在当前值仍等于 last published value 时恢复 baseline,否则保留 host value并在 report 中列出 conflict。 ## 10. `tp-watch` ```elisp (tp-watch BUFFER START END COMPUTE) ``` `COMPUTE` 返回 native direct declarations。`tp-watch` 组合 range anchor、properties surface、stable object 和 binding,返回 underlying surface handle。COMPUTE 中读取的 signals/bindings进入正常 dependency graph;更新与 unmount 使用同一 conflict和rollback合同。 ## 11. Transactions `tp-with-transaction` 的顺序是: 1. 保存 candidate signal writes 并去重 dirty bindings; 2. 为所有实际受影响 surfaces 建立 prepare contexts; 3. 运行 binding graph 与 producers; 4. 校验 object、plan、capability、range、conflict 与 lifecycle; 5. 为所有 surfaces 准备 text/property operations 与 inverse journals; 6. 按稳定 surface id publish; 7. 原子切换 signals、bindings、plans、mount/index、client state 和 revisions; 8. 全部成功后运行 observers。 嵌套 transaction 加入最外层。一个 global signal 可以原子触达多个 buffers;任一 surface 失败时,已发布 surfaces 和 source/binding state 全部回滚。 `tp-transaction-participate` 允许 client side state 在 surfaces 发布后、source commit 前加入同一 rollback boundary。participant key 在一个 outer transaction 中必须唯一。它不是 observer;失败会回滚 transaction。Observer failure 只记录,不回滚已提交结果。 ## 12. Diagnostics 与 reports `tp-surface-report` 返回最近一次成功 publication 的防御性 report;equal/no-op update 不替换 report。字段包括 transaction/surface/revision、candidate source writes、binding counters、object reconcile counts、text/property operation counts、touched characters、scope/full-root 和 failure-related slots。 `tp-surface-inspect` 返回 surface id、buffer、capability、revision、object/mount count、opaque client state 与 report。`tp-surface-at-point` 从 side index 返回 mounted objects;它不扫描显示文本寻找 identity。`tp-object-mounts` 返回 defensive numeric range/tag snapshots,不暴露 live markers。 ## 13. Error ownership Surface publication 还会使用 `tp-invalid-content-range`、 `tp-producer-buffer-mutation`、`tp-publication-mismatch` 和 `tp-owned-result-error`;它们分别归属于 content range 校验、prepare buffer guard、publication journal 和 owned producer result 合同。 主要错误类型: - property/declaration:`tp-property-error`、`tp-invalid-property-policy`、`tp-invalid-declaration`、`tp-invalid-layer-definition`、`tp-unresolved-layer`; - reactive:`tp-reactive-error`、`tp-invalid-signal-scope`、`tp-disposed-signal`、`tp-disposed-binding`、`tp-binding-cycle`; - retained surface:`tp-surface-error`、`tp-invalid-surface-plan`、`tp-duplicate-object-key`、`tp-invalid-prepare-context`、`tp-stale-object`、`tp-cross-surface-object`、`tp-orphan-object`、`tp-capability-error`、`tp-stale-mount`、`tp-property-conflict`、`tp-dead-surface`、`tp-invalid-range-anchor`、`tp-invalid-content-range`、`tp-producer-buffer-mutation`、`tp-publication-mismatch`、`tp-owned-result-error`、`tp-scope-mismatch`。 内部 computation 不吞错或返回貌似合理的 fallback。只有用户入口和 batch test runner等外层边界负责把错误转换为展示信息。 ## 14. 1.0 删除项 TP 1.0 删除了不能诚实映射到统一 retained runtime 的 0.3 managed behavior: - `tp-render.el` 和 `tp-stack.el`; - stack push/pop/move/hide/show/merge/flatten workflow; - `tp-text` 双向内容替换; - `$variable` declaration syntax; - layer-to-buffer registry、buffer-list/identity scan refresh; - managed attach/detach/diagnostics/transaction; - 以 `tp-name`、`tp-layers`、`tp-meta` 作为字符上权威 runtime database 的机制。 TP 不提供 hidden compatibility engine,也不根据文本是否含旧 metadata 自动切换执行语义。静态 recipe、`tp-watch` 和 retained surface 分别承担复用声明、已有文本响应式属性与 retained content 的清晰职责。