tp/postmortem/2026-07-28-api-semantics-phase-1.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

3.9 KiB
Raw Blame History

API 语义收敛阶段 1属性所有权必须先于便利重载

背景

仓库审计发现的错误并非彼此独立的条件遗漏。tp-text 属性扩散、层重定义残留、显式 nil 丢失、搜索 nil 分裂、隐藏层覆盖外部编辑,都来自同一个更深的问题:

调用者、内嵌字符串、普通 Emacs 属性、层定义和 managed stack 对同一属性键的所有权没有统一表达。

继续为每个入口增加特殊分支会扩大语义分裂,因此本阶段先冻结 docs/API-SEMANTICS.md,再在拥有状态的层修复。

决策

1. presence 是数据,不是真值

属性缺失与 present-nil 必须使用 plist-member 一类 presence-aware 判断区分。搜索中 VALUE 省略不能再借用 nil 表达,因而增加唯一哨兵 tp-any-value。选择唯一对象而不是某个普通符号,是为了避免与合法属性值冲突。

2. tp-text 的属性所有者是 interval

内嵌字符串的位置 0 不能代表整串。初次应用与响应式更新都按每个真实 property interval 计算,外部 props 在每段上覆盖内嵌 props显式 nil 也参与覆盖。

逐段算出待写属性后仍必须执行调用者选择的操作语义:tp-set 覆盖指定键,tp-reset 替换完整属性集合,tp-add 合并 face-family 和嵌套 plist。实现中一度让“属性已逐段应用”的返回标志绕过了这一步导致文本内容相同时 reset 留下旧键、add 覆盖而不合并。最终把 operation 传到唯一的逐段写入点,并为字符串/缓冲区和 tp-text nil 初始化路径加入回归断言。

3. 层重定义必须拿到 old 与 new

只传 layer name 会让渲染器无法判断哪些键应删除。定义入口在替换注册表前保存 old props渲染器随后执行

  1. 仅删除仍等于旧值的旧层键;
  2. 保留无关键和已经被外部改写的值;
  3. 写入新定义;
  4. 同步 direct render 与 buried/hidden stack entry。

这比完整 set-text-properties 更安全,因为后者会错误取得整个区间的所有权。

4. 隐藏层冲突默认失败

隐藏层存在时,直接属性只是 managed stack 的渲染缓存。外部直接写入无法可靠归属到某一层。三个候选方案中:

  • 静默覆盖会丢用户数据;
  • 自动收编为匿名层会凭空改变 stack 结构;
  • 明确失败能保留双方状态并暴露所有权冲突。

因此选择 tp-layer-conflict,并保证在 managed write 前检查。

5. NOERROR 只处理可预期的解析失败

增加 tp-unresolved-layerNOERROR 仅捕获这一类型。层 body、compute、transform 和内部不变量错误不属于“未找到”,必须传播。

6. 业务计算与 observer 分开

transform/compute 决定渲染结果失败后继续会产生貌似有效的陈旧输出因此错误传播。watcher 是副作用观察者,单个失败不应阻断 managed update错误以结构化 plist 记录在 tp-reactive-observer-errors,同时保留用户可见消息。

有意延期

参数化 mounted layer entry 没有保存调用实参,重定义时无法重新求值。为避免猜测参数或引入兼容包装,本阶段明确不自动刷新参数化实例。后续应让 managed entry 保存参数和定义版本,再设计迁移。

字符串/缓冲区搜索返回结构、复制/原地修改策略和 stack mutator 的 run-count 返回值也仍有历史差异;它们属于 canonical façade 阶段,不能混入正确性补丁。

验证标准

本阶段以以下证据作为停止条件:

  • 每个确认问题先有失败回归测试;
  • 聚焦测试覆盖字符串/缓冲区、present-nil、静态/响应式重定义和 hidden conflict
  • warning-as-error 字节编译通过;
  • 全量 ERT、固定种子乱序 ERT 与 README doctest 通过;
  • 中英文 README、docstring、架构文档和 changelog 与规范同步。