tp/postmortem/2026-07-28-complete-text-property-facade.md
Kinneyzhang 972b6d4e4c Complete text-property facade and managed lifecycle
Add canonical query semantics, managed metadata and transactions, overlay-aware lookup, reproducible benchmarks, and synchronized API documentation.
2026-07-28 22:42:55 +08:00

4.9 KiB
Raw Blame History

完整文本属性 facade把“替代”定义为语义覆盖而不是重写 Emacs

背景

仓库最初能够便捷地写入、搜索和组合文本属性,但“可以替代所有文本属性操作”仍缺少可验证边界:直接值与有效值混在一起,显式 nil 难以观察,参数化 managed layer 丢失实参overlay-aware 字符属性没有统一入口,主题变化和多步写入也没有生命周期证据。

本轮没有复制 Emacs 的 interval、overlay、undo、yank 或 stickiness 引擎。目标改为更严格也更可维护的定义:

tp 为高频文本属性工作流提供统一 facade原生语义由 GNU Emacs 执行并以等价测试锁定overlay 生命周期等不应被包装的能力明确委托。

决策

1. 一个 lookup record五种明确模式

新增 tp-lookup-resulttp-lookup,而没有继续增加 tp-direct-attp-char-at 等平行入口。模式区分:

  • :text-direct:只看直接 text plist
  • :text-effective:等价于 get-text-property
  • :text-source:报告 direct/category/alias/default/absent
  • :char:等价于 overlay-aware get-char-property
  • :char-source:同时报告来源和获胜 overlay。

record 的 present-p 是必要字段,因为 nil 既可能是合法直接值,也可能表示缺失。overlay 只在 overlay 真正提供获胜值时设置。

2. 原生编辑语义优先委托

property change、any/not-all 只做签名统一,直接调用对应 Emacs primitive。copy、substring、insert、insert-and-inherit、kill/yank、stickiness、narrowing 和 indirect buffer 不增加包装层;测试证明 facade 不会破坏这些行为,文档给出委托边界。

with-silent-modifications 原生会绑定 inhibit-read-only。因此 :silent + :respect 无法诚实实现:伪装支持会让策略名与实际行为冲突。本轮只支持 ordinary/respect、ordinary/inhibit、silent/inhibit并对矛盾组合立即报错。

3. managed metadata 属于存储,不属于渲染属性

managed entry 使用单一保留键 tp-meta 保存 schema、entry id、原始 spec、实参、arglist、定义版本和 entry 版本。没有把这些字段拆成大量普通 text-property key避免污染用户属性命名空间。

tp-meta 必须保留在 authoritative stack storage 中,但必须从直接渲染属性和公开 stack query 中剥离。冲突比较也只比较渲染投影,否则 metadata 自身会制造假冲突。

参数化 layer 重定义现在可用保存的 args 重新求值。旧 entry 若没有 args不猜测、不静默套用错误参数诊断将其标为 legacy limitation。

完整存储模式下的直接属性明确对应第一个可见 entry。definition/reactive refresh 具备 old/new 所有权上下文,因此会先把原生直接编辑协调进该 entry再刷新定义以保留外部值普通 stack decode 没有这层上下文,仍对缓存不一致发出 tp-layer-conflict。所有层都隐藏时出现直接属性同样属于冲突。

4. attach、diagnostics、transaction 是显式生命周期

插入已经带属性的字符串不会经过普通 buffer 写入注册路径,因此提供显式 attach 扫描。detach 可以移除 managed identity/storage并由调用者选择是否保留当前渲染结果。

诊断 API 必须是只读的:不能移动 point、修改 modified state、undo、文本属性或 registry清除已死亡 buffer 除外)。

多步 managed 写入通过 opt-in transaction 包装。事务保存受影响范围的精确文本与属性状态buffer 事务使用 live markers 跟踪范围内的插入和删除,任一步失败都恢复快照。默认重新抛出结构化 tp-layer-transaction-error,只有显式 NOERROR 才把失败转换为结构化返回值。

5. 主题检测与刷新分层

palette 模块只负责检测 enable-theme / disable-theme、递增 generation 并发出 hookmanaged renderer 负责刷新。v1 允许保守扫描全部 managed ranges因为错误地漏刷比多刷一次更危险。诊断记录 hook 来源、generation、刷新模式、范围和错误为后续 dependency-targeted 优化保留证据。

有意排除

  • 不提供 overlay 创建、移动、删除、evaporation 或 priority mutation API
  • 不重写 category、alias、default、undo、yank 或 stickiness 引擎;
  • 不引入 compositional layer 语义;
  • 不改变现有公共 mutator 的历史返回值;
  • 不提高 Emacs 28.1 或 Dash 2.19.1 baseline。

验证标准

完成声明必须同时具备:

  • direct/effective/source/char lookup 的原生等价测试;
  • managed metadata、参数化刷新、attach/detach、只读诊断、事务回滚和主题 lifecycle 测试;
  • 全量 ERT、固定 seed 乱序 ERT、doctest 和 warning-as-error 编译;
  • 大文本、碎片 interval、深 stack、reactive fan-out 与主题刷新的可复现实测;
  • 中英文 README、API semantics、architecture、audit checklist 和 changelog 同步。