
基于 Slate v2 装饰 / 注解 / Widget 三层叠加架构为示例站点补齐装饰与批注示例覆盖的完整实践【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本计划文档docs/plans/2026-04-15-slate-v2-decoration-example-coverage.md记录了一次以“示例覆盖”example coverage为验收标准的工程任务让 plate 项目所基于的 Slate v2 叠加系统Decorations / Annotations / Widgets在示例站点上获得完整的用户可见示例并顺带暴露、修复了一个重要的 React store 引用稳定性陷阱。本文将以该计划为主体结合仓库内的叠加架构路线图与问题记录展开讲解其背景、审计结论、缺口补齐方案、实现细节与验证方法供后续在 plate 中实现或扩展类似叠加功能的开发者直接参考。一、任务背景三层叠加架构与“示例即架构验收”在深入示例覆盖之前必须先理解这套叠加系统的定位。仓库中的 docs/slate-v2/decoration-roadmap.md 是这条技术线的“执行权威”它明确将叠加系统划分为三条一等公民通道lane通道语义关键特征Decoration装饰瞬态、可重叠、由快照状态或显式外部状态推导搜索高亮、语法高亮、临时标记Annotation注解持久、带 id、以 Bookmark 为公开锚点、随事务重定位评论锚点、远程光标、评审线程Widget控件锚定式 UIportal、标签、按钮、气泡、诊断浮层、评审外壳悬浮工具栏、提及菜单、评论气泡这三条通道可以共享投影projection基础设施但绝不共享所有权语义装饰只负责瞬态切片注解负责持久身份与锚点控件负责锚定 UI。这正是旧版单一decorate回调失败的根源——一个回调不可能同时诚实地主宰语法高亮、搜索命中、评论、远程光标、诊断、评审建议、组合输入装饰与超大文档失效。该路线图还定义了目标 API 锁OverlayKernel、DecorationSourceAdapter、AnnotationStoreAdapter、WidgetAnchor等类型与最小 React 绑定useSlateDerivedDecorations(...)useSlateDecorationSet(...)useSlateAnnotationStore(...)以及“公开示例矩阵”要求每个架构通道都应有一个**规范示例canonical example**作为最终公开叙事例如search-highlighting瞬态显式刷新装饰源、persistent-annotation-anchors持久锚点、hovering-toolbar选区驱动控件、huge-document走廊优先的规模化行为。这些内容共同构成了本次“示例覆盖”任务的验收语境。二、现状审计哪些示例已经覆盖了哪些通道计划的第一个阶段Audit current example coverage对示例站点的现有覆盖做了盘点。结论是“可见”示例与“隐藏/低层”示例已经覆盖了三条通道的大部分关键场景派生装饰derived decorationsearch-highlighting搜索高亮证明显式刷新的装饰源code-highlighting代码高亮证明结构化装饰推导与局部失效选区控件selection widgetshovering-toolbar悬浮工具栏证明选区驱动的控件行为mentions提及菜单证明不滥用文本通道的锚定控件行为超大文档叠加姿态large-document overlay posturehuge-document走廊优先corridor-first的规模化表现隐藏/低层示例highlighted-text最小派生装饰证明重叠友好的文本投影与分片叶子渲染persistent-annotation-anchorsBookmark 支撑的锚点 注解支撑的控件证明持久锚点换句话说装饰、注解、控件三层的主要机制在当时都已有对应的示例与浏览器证明例如 docs/slate-v2/ledgers/example-parity-matrix.md 中维护的示例对等矩阵以及路线图中列出的search-highlighting.test.ts、code-highlighting.test.ts、persistent-annotation-anchors.test.ts等 Playwright 集成测试。三、真正的缺口外部装饰源与产品级评审/评论示例审计的结论是现有覆盖存在两个真实缺口而非为了凑数补几个示例外部持有的装饰源externally-owned decoration sources缺少公开示例路线图的DecorationSourceAdapter明确支持两种诚实的源模式derived从编辑器快照推导装饰结果derive(snapshot): readonly Decoration[]external应用/服务已持有装饰结果只差订阅与刷新/失效getSnapshot()subscribe(listener)。搜索高亮等既有示例属于前者或者至少视觉上是“从输入推导”而“外部索引数据驱动装饰”这一模式此前没有任何用户可见的示例开发者无法照抄这一最贴合真实产品的接入形态。缺少产品级feature-grade的评审/评论示例现有的锚点示例是“调试级”debug-grade的它能证明锚点机制本身却不像一个真实的评审/评论产品。评审评论通常需要持久锚点 注解实体 锚定控件 UI气泡、侧栏 允许多重重叠评论这些组合没有公开示例可供参考。因此审计得出的**最小诚实集minimal honest set**是一个用户可见的external-decoration-sources示例一个用户可见的review-comments示例让叠加示例覆盖在文档/导航中变得显式的更新。这里强调“诚实”是有意为之示例覆盖的目的是让公共 API 叙事可被直接验证而不是用若干演示页充门面。四、实现阶段新增示例与公开面更新计划的进度记录2026-04-15显示实现阶段的关键事实包括示例站点支持自动发现新示例文件新增文件即可被路由拾取但仍需在site/constants/examples.ts中显式登记示例名否则不会出现在示例导航中。这印证了“示例导航需要显式维护”这一工程约束。本次实际落地的内容均在配套的 slate-v2 仓库的示例站点中路径按计划原文记录新增示例文件external-decoration-sources.tsxreview-comments.tsx更新公开示例/文档面site/constants/examples.ts显式登记新示例site/examples/Readme.md示例目录说明docs/general/replacement-candidate.md替换候选文档使叠加示例覆盖显式化这段记录对 plate 的启发是“功能实现完成”不等于“架构叙事完成”。一个新 API 必须同时满足“有规范示例 有导航入口 有文档映射”才算真正对外交付。这也是路线图反复强调的“每个新公共名词都需要精确的源码所有权与示例所有权”。五、实现中暴露的非显眼陷阱store 输入必须保持稳定引用这是本计划最有实战价值的部分。新增的review-comments示例在首次加载时直接触发了Maximum update depth exceededReact 无限循环。表面看是通用 React 循环实际根因非常具体示例在每次渲染时重建了全新的注解负载对象并直接喂给useSlateAnnotationStore(...)。也就是说问题不在机制而在输入的身份identity不稳定。仓库对此的完整记录见 docs/solutions/logic-errors/2026-04-15-annotation-store-inputs-must-keep-stable-data-references.md。5.1 错误写法每次渲染重建负载对象const annotationStore useSlateAnnotationStore( editor, comments.map((comment) ({ id: comment.id, bookmark: comment.bookmark, data: { body: comment.body, label: comment.label, tone: comment.tone, }, })) );data对象在每次渲染都是新引用。而createSlateAnnotationStore(...)判定注解快照是否变化时比较的就是bookmark、解析后的 range 以及data对象的引用身份。引用一变hook 就在每次渲染刷新 store连锁触发已挂载编辑器的重渲染最终形成循环。5.2 正确写法useMemo 保持未变项的 data 引用稳定const annotations useMemo( () comments.map((comment) ({ id: comment.id, bookmark: comment.bookmark, data: comment, // 直接复用原对象保持引用稳定 })), [comments] ); const annotationStore useSlateAnnotationStore(editor, annotations);要点是只有真正变化的数据才产生新引用未变化的评论在多次渲染间保持同一个data引用。5.3 可复用规则当向useSlateAnnotationStore(...)喂数据时对注解数组做useMemo未变化条目保持data引用稳定除非你确实想触发刷新否则不要在渲染内联重建派生负载对象。这条规则同样适用于 widget 与 projection 输入数组如果 store 契约按引用比较就要把输入身份当作 API 的一部分来对待。六、同族陷阱的第二个现场投影 store 与搜索高亮同样的规则还咬到了search-highlighting示例只是表现不同搜索输入更新装饰是正确的但如果编辑器先获得焦点输入第一个字符时光标会跳回编辑器。根因是从 React 搜索状态重建createSlateProjectionStore(...)。输入改变 → 状态改变 → 状态重建投影 store → 编辑器重挂载路径恢复了此前的编辑器焦点。错误写法store 随搜索状态重建const [search, setSearch] useState() const projectionStore useMemo( () createSlateProjectionStore( editor, (snapshot) collectSearchProjections(snapshot.children, search), { dirtiness: [text, external], sourceId: search-highlighting } ), [editor, search] // search 变化导致 store 重建 )正确写法store 稳定外部控制状态放 ref显式刷新const searchRef useRef() const projectionStore useMemo( () createSlateProjectionStore( editor, (snapshot) collectSearchProjections(snapshot.children, searchRef.current), { dirtiness: [text, external], sourceId: search-highlighting } ), [editor] // store 只在编辑器变化时重建 ) const handleSearchChange useCallback( (event: ChangeEventHTMLInputElement) { searchRef.current event.currentTarget.value projectionStore.refresh({ reason: external }) }, [projectionStore] )核心心法保持 store 稳定把外部控制状态放进 ref再用“external”脏因显式触发 store 刷新。这恰好与路线图中“失效必须是显式的绝不隐式依赖‘稳定回调 也许重算’”的设计锁一致——refresh是唯一合法的刷新入口。七、验证与交付类型检查、lint 与浏览器冒烟计划的完成标准Run required verification记录了三条验证手段可作为叠加示例开发的回归清单pnpm typecheck:site示例站点全量类型检查确保新增示例与导航登记的类型闭合pnpm lint:fix代码风格与 lint 修复保证示例代码符合仓库规范、可被后续 Agent 干净复用浏览器冒烟通过scripts/run-slate-browser-local.sh对两个新路由external-decoration-sources、review-comments做真实浏览器冒烟验证。这与路线图的“三重证明”制度包契约测试 → 浏览器/示例证明 → 基准/重渲染证明一脉相承示例代码本身不是最终证明跑在真实浏览器里的示例才是。计划还记录了一个仓库层面的已知问题docs/solutions/patterns/critical-patterns.md在当前仓库中缺失Errors 节提示读者在检索该文件时以实际仓库状态为准。八、更广的架构坐标本次示例覆盖在整体路线图中的位置本次计划虽以“示例覆盖”为题但它处在叠加架构更大执行序列的末端。理解这一点有助于判断示例代码的写法约束规范示例必须一对一映射到主架构通道不得依赖遗留decorateAPI其迁移叙事见路线图 Wave 7每个示例都有防作弊出口anti-cheat exits例如search-highlighting不得用ref走私查询作为主 API 叙事code-highlighting不得用Editor.replace(...)作弊来触发语言切换失效hovering-toolbar不得以原生 DOM 选区矩形作为主要锚定真相几何必须来自 widget placement 语义huge-document必须使用最终叠加失效模型不得悄悄退回整文档叠加重算示例之上还有基准程序约束pnpm bench:react:rerender-breadth:local、pnpm bench:react:huge-document-overlays:local保证叠加改动不会造成无关子树全局重渲染也不会重新打开占位符、富文本、超大文档等既有绿色通道。更深入的概念背景可继续阅读仓库内的 docs/research/concepts/overlay-lane-separation.md、docs/research/concepts/durable-anchor-vs-live-handle.md 与 docs/research/concepts/source-scoped-overlay-invalidation.md。九、结语这次任务留下的可执行经验复盘这份计划可以提炼出三条可直接复用的工程经验示例覆盖是 API 交付的一部分。新增装饰/注解/控件类能力时先盘点既有示例含隐藏/低层示例再补“最小诚实缺口集”并同步更新导航登记与文档映射——功能实现 ≠ 架构叙事完成。Store 输入的身份稳定性是硬性契约。凡是useSlateAnnotationStore/createSlateProjectionStore这类按引用比较的 store输入数组与data必须保持稳定引用外部控制状态放入 ref刷新走显式refresh({ reason: external })。违反它会以“无限循环”或“焦点跳回编辑器”的形式表现为难排查的 React 问题。验证必须真实可执行。类型检查 lint 浏览器冒烟含真实路由是底线若涉及性能还需落到具名基准通道上否则任何“无回归”声明都不成立。计划本身已标记为 completed新增的两个公开示例外部装饰源、评审评论补齐了此前缺失的公共叙事示例导航与文档完成同步并在真实浏览器中通过冒烟验证。对希望在 plate 中接入类似叠加能力的开发者而言这份计划连同其配套的问题记录就是一份从“架构 → 示例 → 陷阱 → 验证”全程闭环的参照模板。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考