tp/docs/API-SEMANTICS.md
Kinneyzhang 0d35358e05 refactor(tp)!: implement retained reactive runtime
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.
2026-08-07 00:39:50 +08:00

13 KiB
Raw Blame History

TP 1.0 API Semantics

本文记录 TP 1.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为目标背景与设计理由见 retained runtime architecture

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 必须显式使用:

(tp-computed (lambda () ...))

tp-resolve-value 只执行这种 tagged source。compute result 只 normalize/project 一次不会被隐式二次调用。compute error 直接终止 candidate transaction。

TP 严格区分:

  • absentproperty plist 中没有该 key
  • present nilproperty 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 idtp-text-declarations 把普通 property plist 转换为 canonical declarations。

tp-merge-declarations 按输入顺序合并 direct declaration groups并防御性复制 caller-owned value。它只做 TP contribution composition不实现 CSS winner selection。

tp-define-styletp-style-declarationstp-undefine-style 管理 named direct declarations。registry getter 返回防御性副本。

4. Declaration recipes

define-tp 定义一个返回 native property plist 的 recipedefine-tps 定义一组有序 recipe elements。tp-define-layerdefine-tp-grouptp-define-group 是同一静态 declaration workflow 的命名入口。

Recipe 可以是静态或参数化的,可以组合其他 recipes。展开结果经过同一 direct property policy/projector。Recipe application 不建立 live identity不写 tp-nametp-layerstp-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

(tp-propertize STRING DECLARATIONS)

返回新的 propertized string不修改输入 STRING不创建 object、binding、anchor、mount 或 surface。

5.2 tp-apply

(tp-apply BUFFER START END DECLARATIONS)

只修改 BUFFER 的 [START, END) 文本属性,不替换文字,成功返回 (START . END)。无效 buffer 不会退回 current buffer无效 range 直接报错。

5.3 Direct operations

tp-settp-resettp-addtp-removetp-cleartp-gettp-attp-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 自动 disposeglobal 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 metadataTP 不解释其业务含义
capability contentproperties

Plan 不允许 marker、buffer position、patch op、producer closure 或 binding closure。Constructor 防御性复制 string、props、children 与 tags使 caller 后续 mutation 不改变 committed plan。

producer 在 prepare 阶段接收 context并在产生 plan 前调用:

(tp-object-ensure CONTEXT PARENT KEY KIND)

Object identity 只在所属 surface 中有效。显式 key 按 parent/key/kind reconcileunkeyed 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 fragmentsattachment 保存在 prepare side state不进入 plan。

8. Surface lifecycle

8.1 Materialize

(tp-surface-materialize-string PLAN-OR-PRODUCER)

以 ephemeral prepare context 生成 propertized string不建立 live surface。函数返回前释放 candidate objects、bindings、subscriptions 和 anchors。

8.2 Mount/update

(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。

相等 candidate 不产生 publicationsurface revision 和 buffer modified state 保持不变。

8.3 Unmount

tp-surface-unmount 释放 surface、objects、bindings、mounts、markers、subscriptions、indexes 和 opaque client state并返回 generic report。content surface 删除自己拥有的 spanproperties 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

(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 的防御性 reportequal/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

主要错误类型:

  • property/declarationtp-property-errortp-invalid-property-policytp-invalid-declarationtp-invalid-layer-definitiontp-unresolved-layer
  • reactivetp-reactive-errortp-invalid-signal-scopetp-disposed-signaltp-disposed-bindingtp-binding-cycle
  • retained surfacetp-invalid-surface-plantp-duplicate-object-keytp-invalid-prepare-contexttp-stale-objecttp-cross-surface-objecttp-orphan-objecttp-capability-errortp-stale-mounttp-property-conflicttp-dead-surfacetp-invalid-range-anchortp-scope-mismatch

内部 computation 不吞错或返回貌似合理的 fallback。只有用户入口和 batch test runner等外层边界负责把错误转换为展示信息。

14. 1.0 删除项

TP 1.0 删除了不能诚实映射到统一 retained runtime 的 0.3 managed behavior

  • tp-render.eltp-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-nametp-layerstp-meta 作为字符上权威 runtime database 的机制。

TP 不提供 hidden compatibility engine也不根据文本是否含旧 metadata 自动切换执行语义。静态 recipe、tp-watch 和 retained surface 分别承担复用声明、已有文本响应式属性与 retained content 的清晰职责。