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

70 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 完整文本属性 facade把“替代”定义为语义覆盖而不是重写 Emacs
## 背景
仓库最初能够便捷地写入、搜索和组合文本属性,但“可以替代所有文本属性操作”仍缺少可验证边界:直接值与有效值混在一起,显式 nil 难以观察,参数化 managed layer 丢失实参overlay-aware 字符属性没有统一入口,主题变化和多步写入也没有生命周期证据。
本轮没有复制 Emacs 的 interval、overlay、undo、yank 或 stickiness 引擎。目标改为更严格也更可维护的定义:
> `tp` 为高频文本属性工作流提供统一 facade原生语义由 GNU Emacs 执行并以等价测试锁定overlay 生命周期等不应被包装的能力明确委托。
## 决策
### 1. 一个 lookup record五种明确模式
新增 `tp-lookup-result``tp-lookup`,而没有继续增加 `tp-direct-at`、`tp-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 同步。