Plate 编辑器降级契约(Degradation Contract):为虚拟化与 Shell 模式划定原生行为底线
Plate 编辑器降级契约(Degradation Contract):为虚拟化与 Shell 模式划定原生行为底线
导读
本文围绕 Plate 仓库 .agents/skills/performance/rules/degradation-contract.md 中的核心规则展开,说明在编辑器引入虚拟化渲染、Shell 岛屿(shell islands)、模型驱动选择(model-backed selection)或分阶段挂载(staged mounting)等激进性能手段时,如何为每一种降级模式登记完整的行为契约,明确"哪些原生浏览器行为被改变、哪些保持原生、哪些显式不支持"。读完本文,你将掌握在 Plate/Slate v2 大文档性能工作中为降级模式撰写契约的完整字段清单、拒绝清单,以及如何与同目录下的 cohort-segmentation、repeated-unit-budget、staged-readiness、editor-native-behavior-proof 等规则配合,形成可评审、可验证的性能路线。
为什么需要降级契约:性能优化不能以"静默破坏"为代价
在富文本编辑器领域,"更快"从来不是唯一目标。浏览器为编辑器提供了大量原生能力——Ctrl+F 查找、原生选区、屏幕阅读器遍历、剪贴板复制粘贴、IME 输入法组合、移动端触摸选字、撤销/历史栈、协作远程更新——这些能力依赖真实、完整、新鲜的 DOM 表面。一旦为了性能引入虚拟化、Shell 岛屿或分阶段挂载,DOM 就不再完整或不再即时,上述原生行为就可能悄悄失效。
这正是 degradation-contract.md 要解决的问题。该规则文件位于 .agents/skills/performance/rules/degradation-contract.md,属于仓库中 performance 技能(.agents/skills/performance/SKILL.md)负责维护的独特性能评审维度。技能文档明确:当"性能需要虚拟化、Shell 岛屿、模型驱动选择、分阶段挂载或任何非原生模式"时,必须加载本规则(degradation-contract 一行的 use-when 条件)。
与它配套的规则还包括:
| 规则文件 | 使用时机 |
|---|---|
| cohort-segmentation.md | 计划笼统说"大文档""大列表"而不区分规模/复杂度分群 |
| repeated-unit-budget.md | block、row、decoration、island 等重复单元以规模放大 |
| staged-readiness.md | 计划包含启动、水合、全文档替换或分阶段 DOM-present 挂载 |
| editor-native-behavior-proof.md | 更快的模式可能改变浏览器原生行为 |
核心规则:先优化原生 DOM-present 路径,再谈降级
规则文件的 ## Rule 节给出了降级契约的第一原则,原文可概括为两句话:
- 先优化原生 DOM-present 路径——在考虑任何激进模式之前,先把所有 DOM 完整呈现(DOM-present)的默认路径做到极致;
- 只为具名分群降级,并说明哪些原生行为发生改变——降级不是全局默认,而是针对明确命名的 cohort(分群),且每个降级行为必须伴随行为变更声明。
这条原则在仓库的实际执行中体现得非常具体。在 2026-05-03-slate-v2-dom-present-large-doc-phase-6-plan.md 的 Performance Pass 记录中,该项目对 large-doc(大文档)路线的态度是:
- degradation contract:DOM-present 分阶段挂载是"暂时缺失 DOM + 完成承诺(completion promise)",Shell 是显式激进的模式,虚拟化保持实验性(experimental);
- 文档同时强调 "No default mode"——没有任何激进模式默认开启,只有满足前置条件(materialize caret target、model-backed copy、IME target mounting、mobile selection、browser find strategy、screen-reader strategy、persistent caret soak 等)才考虑产品级模式。
也就是说,降级契约的第一条规则在实际路线图中被翻译为:原生 DOM-present 是安全默认基线,Shell/遮挡(occlusion)升级必须显式声明,虚拟化只有在用户显式打开研究通道时才被允许。
契约正文:每个降级模式必须登记的十个字段
规则文件的 ## Contract 节定义了降级契约的最小信息集。对每一个降级模式,都必须记录以下十个字段。下面逐项结合 Plate/Slate 编辑器的实际场景展开说明。
1. Cohort threshold(分群阈值)
该模式从哪个规模/复杂度阈值开始生效?降级不允许"一刀切",必须绑定到具名分群。这与 cohort-segmentation.md 的基线分群表直接对应:
| Cohort | 示例 | 默认立场 |
|---|---|---|
| normal | 0–500 blocks,低装饰量 | 优化重复单元(repeated unit) |
| medium | 500–2000 blocks | DOM-present,严格执行预算 |
| large | 2000–10000 blocks | DOM-present 分组,分阶段工作,保护原生行为 |
| stress | 10000–50000 blocks | 显式降级候选 |
| pathological | 自定义渲染器、comments、annotations、嵌套隐藏范围 | 按复杂度打标签,不藏在 block 数里 |
例如,一个虚拟化模式如果只应用于 stress cohort(10000+ blocks),就必须在契约里写明这一阈值,并说明 normal/large cohort 不采用该模式。在 2026-05-03-slate-v2-dom-present-large-doc-phase-6-plan.md 中,实际记录的分群为 1000, 5000, 10000, 25000+ blocks,且 Shell 与 DOM-present 默认模式分开计数——这正是"每个性能声明必须点名其覆盖的 cohort"的落地。
2. Browser find behavior(浏览器查找行为)
Ctrl+F/Cmd+F 是用户最依赖的原生能力之一,而浏览器 find 只能命中真实存在于 DOM 中的文本。虚拟化意味着视口外的文本根本没有 DOM 节点,浏览器 find 会"搜不到";Shell 模式若用代理 DOM 代替真实内容,find 可能命中错误文本。契约必须写明:
- 该模式下浏览器 find 是否可用?
- 是 native(DOM 完整)还是需要 materialize-first(先物化目标范围再查找)?
- 是否需要显式 opt-in 的"搜索模式"来物化全部 DOM?
3. Screen-reader behavior(屏幕阅读器行为)
屏幕阅读器同样依赖 DOM 的语义结构与文本顺序。分阶段挂载造成"远处 DOM 缺失"时,阅读器遍历到一半就断了;Shell 岛屿若在无障碍树(accessibility tree)中暴露了占位节点,阅读器会读出错误的导航信息。契约需要声明:阅读器遍历是 native、materialize-first,还是显式不支持。
4. Native selection behavior(原生选区行为)
原生选区依赖 DOM Range 与真实文本节点。当选中范围跨越"尚未物化的隐藏区域"时,Selection API 无法表示该范围。契约必须回答:跨隐藏边界的选区如何映射?是采用模型(model)映射选区、先物化目标范围再选择(materialize-first),还是该场景显式不支持?
5. Copy/paste behavior(复制/粘贴行为)
这是规则文件 ## Reject 节特别点名的一项:禁止没有可见契约的模型驱动复制/粘贴。当选中内容部分在 DOM 外时,document.execCommand('copy')/Clipboard API 只能拿到 DOM 中存在的部分。模型驱动复制意味着从编辑器数据模型重建剪贴板内容——这可行,但必须:
- 在契约中显式说明复制内容由模型重建;
- 明确粘贴时序列化格式(如 HTML、纯文本、自定义 JSON)的映射规则;
- 保持复制/粘贴结果的可见性与一致性,不能出现"复制了 A 粘贴出 B"的静默错误。
6. IME/composition behavior(IME/组合输入行为)
中文、日文、韩文等输入法的 composition 过程极端依赖 DOM 文本节点的连续性与实时性。分阶段挂载若在组合输入进行中物化/卸载 DOM,会导致拼音组合中断或候选词丢失。契约必须说明:IME 目标区域的挂载策略(IME target mounting)、组合期间的 DOM 稳定性保证,以及该模式是否要求先物化再输入。
7. Mobile behavior(移动端行为)
移动端触摸选字、长按菜单、虚拟键盘弹出等行为与 DOM 选区/焦点状态强耦合。契约需声明:移动触摸选择(mobile touch selection)在该模式下是 native、materialize-first 还是显式不支持。
8. Undo/history behavior(撤销/历史行为)
撤销栈依赖操作与 DOM 变更的因果一致性。如果物化过程本身产生了大量"隐藏的 DOM 变更",撤销时这些变更会污染历史。契约必须说明:物化/卸载操作是否进入历史栈、撤销是否能正确跨过隐藏区域、历史记录是否保持用户可理解的操作粒度。
9. Collaboration behavior(协作行为)
在 Plate 的 Yjs 协作场景中,远程更新需要应用到 DOM 表面。分阶段挂载期间,隐藏区域的远程更新是排队、丢弃还是立即物化?协作行(collaboration/remote update)在 editor-native-behavior-proof.md 的 Proof Rows 中与其它原生行为并列,必须逐一作答,不能默认"协作没问题"。
10. Escape hatch or explicit opt-in(逃生通道或显式选择)
每个降级模式都必须给用户一条退路:要么提供显式 opt-in(用户主动开启,例如"实验性虚拟化"开关),要么提供逃生通道(模式出问题时一键回退到原生 DOM-present 路径)。契约需写明:开关在哪、默认值是什么、回退后哪些能力恢复。
拒绝清单:三种一票否决的表述
规则文件的 ## Reject 节列出三种必须被拒绝的情况,这是性能评审的"红灯":
- 在重复单元预算耗尽之前就把虚拟化设为默认——即"repeated-unit budget"(repeated-unit-budget.md)没有先执行、先证明,就直接上虚拟化。预算表的每个维度(每个单元 DOM 节点数、React 组件数、事件处理器、effects、subscriptions、selectors、每次交互分配量、样式/布局成本、React scheduler/effect 成本)都该先被压到最低,正如预算规则所说:"每单元移除两个 DOM 节点,在 10k 单元时就是 2 万个节点的减少"。只有单元级优化到极限仍然不够时,降级才进入讨论范围。
- 把 Shell 模式描述成"同一个编辑器,只是更快"(same editor, just faster)——Shell 改变了 DOM 表面与交互语义,它就是不同的行为模式。任何"只是更快"的表述都是在掩盖未声明的行为变更,必须拒绝并要求补全契约。
- 没有可见契约的模型驱动复制/粘贴——模型重建剪贴板内容本身可以接受,但必须伴随上面第 5 项要求的显式契约,否则拒绝。
与相邻规则的配合:从"降级"到"可验证"
降级契约不是孤立文件,它处在 performance 技能规则网的中心。评审一个激进模式时,四条规则需要联合使用:
staged-readiness:给挂载过程两个可测量的终点
staged-readiness.md 要求把"可交互"与"原生表面完整"分开度量:
interactiveReady:活跃/走廊(active/corridor)内容已新鲜且可编辑;nativeSurfaceComplete:所有预期的 DOM 都已新鲜,可供浏览器 find、原生选择、复制与阅读器遍历。
其硬规则(Hard Rule)是:预热期间缺失远处 DOM 可以接受,但把陈旧旧 DOM 冒充当前内容展示则不可接受。评审时需要用 Gate 清单度量:visible commit timing(可见提交时机)、background completion timing(后台完成时机)、max-latency budget(最大延迟预算)、stale DOM count(陈旧 DOM 数量)、pending group count(待处理分组数)、far interaction 的 materialization cost(远处交互的物化成本)。
editor-native-behavior-proof:给契约一个逐行打勾的证明
editor-native-behavior-proof.md 提供 11 行 Proof Rows:browser find、screen-reader traversal、native selection、copy、paste、select-all、IME/composition、mobile touch selection、undo/history、collaboration/remote update、follow-up typing after repair/materialization。对每种模式,逐行标注状态:
- native(原生)
- model-backed(模型驱动)
- materialize-first(先物化)
- intentionally unsupported(有意不支持)
- explicit opt-in only(仅显式选择)
其核心禁令与降级契约一脉相承:不要把原生行为回归藏在时间收益里(Do not hide native behavior regressions inside timing wins)。
仓库中的实战形态:一个已应用的降级契约示例
在 Plate 的 Slate v2 大文档路线中,降级契约被正式应用于 2026-05-03-slate-v2-dom-present-large-doc-phase-6-plan.md。该计划第 242–269 行的 Performance Pass 记录了一个完整的契约形态:
- repeated unit:顶层 root group 与 editable 后代;
- cohorts:1000 / 5000 / 10000 / 25000+ blocks,Shell 与 DOM-present 默认分开计数;
- budgets:普通打字不得查询文档扫描注册表;pending root groups 应合并注册而非为每个隐藏 block 注册一个边界;每个 root group 的事件/effect 工作量在注册之外趋近于零;
- metrics:startup、typing、select+type、full replace visible commit、
interactiveReady、nativeSurfaceComplete、DOM nodes、editable descendants、root groups、shell count、heap; - degradation contract:DOM-present 分阶段挂载 = 暂时缺失 DOM + completion promise;Shell = 显式激进模式;virtualization = 保持实验性;
- dashboard/RUM gap:未来生产级证明需要 document-size、mode、interaction name、group counts、DOM nodes、heap、browser、mobile、IME、release 等标签。
与此同时,2026-05-03-slate-v2-experimental-virtualized-rendering-boundary.md 展示了"虚拟化=实验性"标签的落实:公共 Editable 渲染策略配置保留 type: 'virtualized',但文档、JSDoc、示例侧边栏均标注 Experimental. Not production-ready.,且主 Editable 文档与性能文档改为链接到独立实验文档,而不是内联教学。这正好呼应降级契约中"显式 opt-in only"的要求——虚拟化模式可以被代码支持,但绝不能被描述为生产就绪的默认能力。
快速撰写模板:一份可直接套用的降级契约
综合上述规则,为一个新的降级模式(例如某个 cohort 的虚拟化或 Shell)撰写契约时,可直接套用以下模板:
## Degradation Contract: <模式名>
- 适用 cohort:<阈值,如 stress 10000–50000 blocks,且必须带复杂度标签>
- 触发条件:<repeated-unit 预算已耗尽并留有证据>
- browser find:<native | materialize-first | unsupported>
- screen-reader:<native | materialize-first | unsupported>
- native selection:<native | model-backed | materialize-first | unsupported>
- copy/paste:<native | model-backed(必须描述序列化映射)| unsupported>
- IME/composition:<native | materialize-first(IME target mounting 策略)| unsupported>
- mobile:<native | materialize-first | unsupported>
- undo/history:<变更是否入栈、跨隐藏区撤销策略>
- collaboration:<远程更新在隐藏区的排队/物化策略>
- escape hatch / opt-in:<开关路径、默认值、回退行为>
- 证明文件:<editor-native-behavior-proof 11 行逐项状态 + staged-readiness 的 interactiveReady/nativeSurfaceComplete 度量 + 浏览器 trace/RUM 标签>
结语
降级契约的本质,是把"为了性能牺牲原生行为"这件事从隐性决策变成显式、可评审、可回退的工程记录。在 Plate 这类以浏览器原生编辑能力为生命线的富文本编辑器中,任何"更快"的声明都必须先回答"更快之外,我还改变了什么"。遵循本文所述的十条契约字段、三条拒绝红线,以及与 cohort-segmentation、repeated-unit-budget、staged-readiness、editor-native-behavior-proof 的组合用法,可以让虚拟化、Shell 岛屿、模型驱动选择与分阶段挂载这些激进手段始终处于受控范围——原生 DOM-present 路径永远是默认基线,降级只为具名分群发生,且每一项原生行为变更都有据可查、有路可退。
更多推荐
所有评论(0)