
Slate v2 装饰与注释 API 架构外部注释通道、持久锚点与 id 定向刷新【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文围绕 Plate 仓库内 Slate v2 的decoration-annotation-api规划文档docs/plans/2026-04-30-slate-v2-decoration-annotation-api-ralplan.md展开完整还原其核心结论评论/注释元数据默认不写入 Slate 文档值而是存放在独立的注释通道中通过持久锚点anchor映射到文档区间由slate-react镜像到投影存储供渲染消费。读完本文你将掌握 Slate v2 注释系统从 API 形态bookmark→anchor、数据拆分data/projection、外部刷新refresh({ ids })到双编辑器只读评论协作验证方案的完整架构以及它与 ProseMirror、Lexical、Tiptap 的横向对比结论。一、核心结论注释默认不写入 Slate 文档值规划文档给出的首要裁决是Slate 文档值不应成为评论comment数据的默认属主。在 Google Docs 式的工作流中一个用户编辑文档、另一个用户只发表评论此时若让两个用户都写入同一个 Slate value会产生错误的心智模型——它会污染权限comment-only 用户被迫获得文档写权限、undo/history、冲突策略、审计事件与协作路由。因此更强的目标架构是Slate 文档值只拥有文档内容注释/评论元数据存放在应用、服务或协作存储中锚点anchor是持久值可解析为 Slate 区间Rangeslate-react将解析后的注释镜像进投影存储projection store供文本绘制、侧边栏与挂件消费由协作适配器而非裸 Slate把远程锚点映射到 CRDT 或服务位置。用规划文档的原话概括这是吸收了 ProseMirror 的教训但不照搬其 API同时借鉴 Lexical 的评论存储拆分、却不强迫评论用户变更文档树的方案。该结论的配套研究决策已沉淀在 docs/research/decisions/slate-v2-collaborative-annotation-channels.md并被索引收录。二、意图、范围与边界意图定义 Slate v2 装饰decoration/注释annotation/评论comment在性能与协作两个维度上的最优架构回答评论专用用户是否应把评论写进 Slate 值这一核心问题在确定公开 API 之前将当前 Slate v2 与 Legacy Slate、Lexical、ProseMirror、Tiptap 做横向对比。期望产出是一份可直接执行的注释/评论特性文档规划 公开锁定public lock前的一次小型 API 修正。本次 pass 不做实现编辑。范围内In scope装饰来源decoration sources注释存储annotation stores持久注释锚点durable annotation anchors基于 Yjs 或等价适配器的评论专用协作源作用域失效source-scoped invalidation与运行时 id 投影性能架构关闭close前所需的文档/示例/测试。非目标Non-goals不做裸 Slate 产品级评论系统核心层不做服务端认证策略不要求兼容当前版本的 Plate 或 slate-yjs 适配器不要求每条评论都必须成为文档内容不复活单一decorate(entry) Range[]作为旗舰模型。决策边界裸 Slate 保持无观点unopinionatedslate-react可拥有面向渲染的投影存储产品框架可以选择文档内嵌锚点但裸 Slate 不应强制要求RangeRef停留在底层运行时机制层面它不是公开的评论故事。三、当前实现状态盘点Current State Read规划文档记录了当时2026-04-30Slate v2 的实时实现状态指出其已具备正确的基础设施SlateAnnotation已带id但当时强制要求bookmark: Bookmark字段注释存储会把 bookmark 解析为快照snapshots与投影projections并具备受影响的注释 id 可计算时的候选重建candidate rebuild逻辑纯选区变化与无关文本变更会被跳过避免无谓重算存储通过Editor.subscribeSource订阅而非宽泛的editor.subscribe把唤醒范围收窄到具体来源投影存储已支持源脏标记source dirtiness、源 id、运行时 id 订阅与显式刷新原因refresh reasonsSlate组件把注释存储投影与装饰来源组合在一起Bookmark是隐藏的、可随操作重基线op-rebased的区间锚点提供resolve()与unref()review-comments与persistent-annotation-anchors两个示例已经用 bookmark 注释存储数据演示了本地锚点场景。当前差距Gaps公开注释输入被命名并类型化为bookmark只能描述本地编辑器锚点这一种情况外部注释变更无法通过公开的refresh()API 定向到候选 id——当时的refresh是() void投影构建会把整个注释data对象展开进内联投影数据这对于评论正文/侧边栏数据变更频率远高于内联绘制元数据的评论系统来说过于宽泛示例只演示了本地 bookmark没有演示只读/评论专用用户写入独立协作通道的场景。这些差距正是本次 API 修正的动机来源。四、外部对标四种编辑器的注释方案规划文档对比了四个外部参照系作为选择公开 API 前的证据输入Legacy SlateEditable只暴露一个decorate?: (entry: NodeEntry) Range[]回调——它是瞬态渲染的逃生舱而不是持久的注释架构旧RangeRef通过操作追踪区间但必须手动释放旧版性能指南强调decorate函数引用稳定性stable function identity的重要性——函数身份变化会导致大面积重算。ProseMirrorDecorationSet是持久化的映射叠加数据persistent mapped overlay data而非渲染回调forChild(...)只提取子节点局部的装饰天然支持源作用域传播SelectionBookmark可随变更映射并在之后解析——这正是映射锚点概念的源头。LexicalMarkNode把内联 id 存进文档树CommentStore单独持有评论/线程元数据并可写入 Yjs 的comments数组——即内联 id 与评论元数据分离属主Playground 会先创建独立的 comments provider之后才用MarkNode包裹内联选区。Tiptap节点区间视觉效果基于 ProseMirror decorations 构建产品评论可以在编辑器之外通过 REST/webhooks 操纵Tiptap Cloud 把评论嵌入协作文档并额外包裹线程认证thread authentication与评论专用策略——评论成为产品/协作层而非编辑器核心。规划文档由此得出结论文档内嵌 mark id 外部元数据是可选的适配器策略有离线持久性优势但仍会变更内容而ProseMirror 式映射外部锚点 外部评论存储是默认首选因为它最适合评论专用用户与源作用域投影性能。五、决策简报原则、驱动因素与选项对比原则内容值不是垃圾场Content value is not a dumping ground权限应跟随数据属主锚点与评论正文是不同类型的数据裸 Slate 暴露的是基板substrate不是产品工作流性能收益必须在源source、投影projection、订阅者subscriber三个层面分别度量。顶层驱动因素评论专用读者必须能在没有文档写权限的情况下添加评论文档编辑与评论编辑必须可以并发进行大型文档不能在每次变更时重算所有注释应用需要持久锚点而不只是活的路径/区间句柄API 必须贴近 Slate 且足够小让 Agent 也能正确使用。选项对比表选项裁决理由把完整评论存进 Slate 值默认拒绝对评论专用权限、undo/history、审计、协作路由而言属主错误Lexical 式文档内联 id 外部元数据保留为可选适配器策略应用想要文档内嵌锚点时有良好持久性但仍会变更内容ProseMirror 式映射外部锚点 外部评论存储选为默认最适合评论专用用户与源作用域投影性能Tiptap 式产品评论扩展/云策略推迟给产品/适配器有参考价值但裸 Slate 不应拥有产品评论服务选定的目标SlateAnnotation应接受通用持久锚点anchor而非仅接受bookmarkBookmark保留为首个内置锚点实现协作适配器可用 Yjs 相对位置或服务持有的锚点实现同一锚点形状注释元数据保持外部化SlateAnnotationStore把解析后的区间与数据镜像为渲染快照外部注释变更应支持 id 定向刷新侧边栏/应用数据不应盲目拷贝进内联投影数据。六、API 目标形态bookmark → anchor当前形态修正前export interface SlateAnnotationT unknown { bookmark: Bookmark; data?: T; id: string; }目标形态公开锁定前export interface SlateAnnotationAnchor { resolve(): Range | null; unref?(): Range | null; } export interface SlateAnnotation TData unknown, TProjection extends Recordstring, unknown Recordstring, unknown, { anchor: SlateAnnotationAnchor; data?: TData; id: string; projection?: TProjection; }采纳答案在公开锁定前把bookmark硬切为anchor。当时包仍处于 pre-1.0/beta 阶段slate-react 以0.124.0发布把错误的名词继续带下去比一次 minor 版本破坏更昂贵默认不提供兼容别名只有当发布评审发现存在真实的外部采用、且其成本超过长期 API 代价时才补充别名Bookmark已满足目标形状无需改动投影条目拷贝projection而非整个data载荷data仍可通过useSlateAnnotation与useSlateAnnotations获取。外部刷新目标refresh({ ids })type SlateAnnotationRefreshOptions { ids?: readonly string[]; reason?: annotation | external | refresh; }; interface SlateAnnotationStoreT unknown { refresh(options?: SlateAnnotationRefreshOptions): void; }ids语义省略ids全量刷新full refresh空数组无操作no-op非空数组只对列出的注释做重解析与重投影。这正是以下外部事件的性能钩子评论正文变更comment body changed线程被解决thread resolved远程评论新增remote comment added锚点从外部协作存储变更anchor changed from an external collaboration store。投影载荷目标data 与 projection 分离const annotations comments.map((comment) ({ anchor: comment.anchor, data: comment, id: comment.id, projection: { resolved: comment.resolved, tone: comment.tone, }, }));这样正文编辑可以只更新侧边栏而不重绘内联文本而状态status或语气tone变更仍会重绘相关的运行时桶runtime buckets。七、协作目标评论专用用户写入外部通道修正前错误心智模型// Comment-only user must mutate the editor value to persist the comment anchor. editor.update((tx) { tx.addMark(commentId, threadId); });修正后// Comment-only user writes to the annotation channel. const anchor yjsAnnotationAdapter.anchorFromSlateRange(editor, selection); commentsMap.set(threadId, { anchor, body, status: open, }); annotationStore.refresh({ ids: [threadId], reason: annotation });写者通道writer lane与适配器通道adapter lane// writer lane: 写者变更文档通道 editor.update((tx) { tx.text.insert(hello, { at }); }); // adapter lane: 适配器监听文档变更并触发注释刷新 yjsAnnotationAdapter.observeDocumentChanges(() { annotationStore.refresh({ reason: annotation }); });架构语义非常清晰写者变更文档通道评论者变更注释通道适配器基于当前 Slate 快照解析锚点用于渲染。完整双编辑器示例目标规划要求新增collaborative-comments.tsx作为左右并排的双编辑器示例左窗格写者编辑器可编辑文档通道无需特殊评论权限右窗格评审者编辑器渲染同一文档快照但为只读评论控件可用共享状态一个文档通道 一个外部注释/评论通道流程写者在左侧编辑器输入评审者在右侧只读编辑器选中文本并创建评论评审者更新/解决该评论但不改变Slate 文档值写者继续编辑两个窗格的高亮/侧边栏始终锚定在移动后的文本上必证条件评审者通道不得为文档写入调用editor.update也不得变更Editor.children只允许注释通道写入与annotationStore.refresh({ ids, reason: annotation })明确禁止用单编辑器评论模式开关替代——开关只能证明 UI 门控双窗格示例才能证明协作属主。八、性能计划源作用域失效与运行时桶局部性必需运行时属性注释存储的输入数组对未变更行保持稳定引用身份stable identity文档提交使用源总线source-bus路由而非宽泛的编辑器唤醒文本/结构变更通过受影响的运行时 id 做候选过滤外部注释变更在 id 已知时只刷新受影响的 id运行时订阅者只在相关运行时桶变化时被唤醒全量刷新保留为未知外部变更的安全兜底。必证行Proof rows行必证内容源扇入Source fan-in用 monkey-patch 让宽泛的editor.subscribe抛错注释存储仍能重基线候选 idCandidate ids在无关块内输入解析出零个注释锚点外部 id 刷新更新一条评论正文只唤醒该注释 id不唤醒无关运行时桶投影载荷拆分更新data.body唤醒注释订阅者但当projection未变时不唤醒内联投影订阅者双窗格评论专用示例写者在左编辑器编辑、评审者从右只读编辑器评论文档值只由写者通道变更远程重基线Remote rebase重放远程文本操作证明本地/协作锚点解析到移动后的文本空锚点Null anchor已删除锚点解析为null不绘制任何内容且不泄漏订阅者压力Stress1000 条注释在某锚点附近输入保持局部全文档刷新只是可度量的兜底这一层正是 docs/research/concepts/source-scoped-overlay-invalidation.md 定义的源作用域叠加失效概念叠加存储能在重建所有投影切片之前先判断一次文档变更是否影响某个装饰/注释/挂件来源。它是全存储刷新与按运行时 id 订阅投递之间的中间层所需配料包括操作派生的脏路径、受触碰的运行时 id、源脏声明、稳定源身份、按源与运行时 id 键控的旧投影快照以及未知/宽泛来源时的全量刷新兜底。规划还引用了一条既有方案笔记源路由、重算选区与运行时桶投递必须分别验证上游扇入与运行时桶局部性必须分开证明。九、新文档与示例规划架构关闭后需新增最终态文档docs/libraries/slate-react/annotations.md最低内容要求装饰Decorationvs 注释Annotationvs 挂件Widget的区分本地 bookmark 用法外部评论存储评论专用协作故事完整的双编辑器并排示例可编辑写者窗格 只读评论窗格Yjs 风格适配器草图独立的文档通道与注释通道何时可以接受文档内嵌 mark id性能规则稳定数据、id 定向刷新、运行时桶。示例变更更新review-comments.tsx使用anchor新增collaborative-comments.tsx并注册路由。若无适配器 mock无法诚实地证明文档/评论通道分离则不发布更弱的开关演示作为替代。十、TDD 与验证计划采用纵向切片vertical slices而不是一次性的大型假红套件测试本地Bookmark仍能通过anchor工作测试旧bookmark形状被拒绝或经一次显式兼容决策后有意别名化测试外部注释存储refresh({ ids })测试data与projection分离projection元数据稳定时正文更新唤醒侧边栏订阅者但不重绘内联运行时桶测试只读/评论专用流程选区到外部锚点不调用editor.update、不变更Editor.children测试远程文档操作重解析外部锚点测试 React 订阅者一次正文更新只唤醒一个注释订阅者浏览器示例测试写者在左编辑器编辑评论专用用户在右只读编辑器评论压力基准1000 条注释下的本地编辑、远程评论更新与全量兜底。十一、质疑账本Objection Ledger质疑回答裁决为什么不直接把评论存进 Slate 值评论专用用户不应仅为讨论文本就获得文档写权限还会污染 undo/history 并让审计事件撒谎rejectLexical 把 mark id 存在树里。确实如此但它把评论元数据分开存储。Slate 可以把文档内嵌 id 作为适配器选择而不必强制要求保持默认外部外部锚点会漂移。正确。漂移策略、引文/上下文恢复、null 解析与测试必须由适配器负责。裸 Slate 只承诺适配器给出锚点后负责解析与投影revise with proofbookmark改名为anchor像在折腾。现在折腾比发布一个只描述本地场景的名词更便宜。Bookmark仍作为内置锚点保留keepid 定向刷新把存储搞复杂了。这正是产品级评论与玩具示例的分水岭。存储已有面向编辑器变更的候选 id 机制外部变更需要同一条通道keep为什么拆分data和projection评论正文/侧边栏的变更不应重绘内联文本。渲染载荷与应用元数据有不同的热路径keep十二、高风险评审与 Steelman 论证触发与爆炸半径触发条件公开 API、协作数据模型、渲染失效、文档/示例。爆炸半径blast radiuspackages/slate-react/src/annotation-store.tspackages/slate-react/src/hooks/use-slate-annotation-store.tsxpackages/slate-react/src/hooks/use-slate-annotations.tsx注释示例与文档未来的 Yjs/协作适配器三场景预检Pre-mortemAPI 以bookmark发布 → 远程锚点需要第二种形状文档变得混乱外部评论更新永远走全量刷新 → 大型评审文档性能崩溃文档暗示评论在值之外但示例仍变更内容 → 读者复制错误模式。补救锁定前改名为anchor或提供迁移别名发布协作示例前加入 id 定向刷新拆分data与渲染面向的projection文档先讲外部元数据、再讲文档内嵌 id 这一可选策略。Steelman 关键行决策最强公平反对为何选定方案胜出硬切bookmark→anchor这是对已能本地工作的东西的折腾。bookmark命名的是实现而非契约Yjs/服务支撑的协作锚点并不需要是 SlateBookmark评论默认外部注释通道Lexical 与许多应用把 mark id 序列化进文档外部锚点会漂移。评论专用用户不应需要文档写权限外部默认保持权限与审计诚实同时允许文档内嵌 id 作为适配器选择拆分data/projection两个载荷比一个 API 更啰嗦。侧边栏/评论正文的变更与内联高亮绘制是不同的热路径拆分防止应用元数据意外变成渲染失效源新增refresh({ ids })带 id 的刷新 API 像让调用者管理内部。产品级评审文档需要 id 定向外部更新id 未知时全量刷新仍是安全兜底接受的修订与放弃的选择接受的修订文档内嵌锚点 id 保留为显式的适配器/产品选项而非被拒绝模式文档必须展示外部锚点的漂移/null 锚点策略测试必须证明投影载荷分离而不只是类型形状。放弃的选择bookmark的默认兼容别名把完整评论元数据拷贝进文本投影切片裸 Slate 产品评论服务。无未解决的 steelman 行。扩展验证计划高风险 pass通道必证内容单元Bookmark满足SlateAnnotationAnchor旧bookmark形状被拒绝或显式别名化已删除锚点解析为nullReact 集成projection不变时注释正文更新唤醒注释订阅者而非内联投影订阅者协作编辑器只读且文档值不变时mock 的 Yjs/服务锚点可以更新浏览器并排示例证明写者在一个编辑器编辑评审者在只读编辑器创建/解决/删除评论迁移/采用文档展示本地 bookmark、外部通道、文档内嵌 id 三种策略及清晰的属主规则性能1000 条注释某锚点附近本地编辑与外部正文更新都保持运行时桶局部全量刷新只是可度量兜底安全/权限文档声明权限强制属于应用/服务/协作层而非裸 Slate回滚或补救若通用锚点过于模糊发布前把契约收窄为resolve(editor)或适配器对象若projection在文档中易混淆发布前改名为renderData但保留拆分若 id 定向刷新被误用保留全量刷新兜底仅在真实误用出现后添加开发警告。结论保留该计划。高风险点真实存在但旧的值属主评论模型对用户的协作场景更糟。十三、迁移主干Migration BackbonePlatePlate 可以在 Plate 自有的插件/存储中保持有观点的评论 UX 与讨论状态既有的文档 mark id 评论系统可以把这些 id 映射为anchor对象适配器策略Plate 不应强迫裸 Slate 在文档值中存储线程正文、权限或已解决状态。slate-yjs文档通道与注释通道应为独立的 Yjs 结构Yjs 注释锚点可以通过把相对位置解析为当前 Slate 区间来实现SlateAnnotationAnchor裸 Slate 不需要内置当前 slate-yjs 适配器它需要的是能让适配器正确的基板形状。Legacy Slatedecorate(entry) Range[]保留为瞬态渲染逃生舱而非持久评论架构RangeRef保留为底层本地运行时机制Bookmark与通用锚点才是公开的持久性故事。十四、评审矩阵与实施阶段适用评审矩阵摘录透镜适用性发现vercel-react-best-practicesapplied渲染面向数据必须留在外部存储与useSyncExternalStore订阅中而非上下文抖动拆分data与projectionperformance-oracleapplied热路径风险不止锚点解析本身而是外部评论抖动引发的宽泛刷新与重绘需要 id 定向刷新、投影拆分、1000 注释压力行tddapplied测试必须证明公开行为评论专用创建、外部刷新、远程重基线、渲染局部性build-web-apps:shadcnskipped本规划 pass 不涉及新 UI 组件设计react-useeffectapplied存储生命周期与外部更新应使用稳定 ref 与外部存储订阅而非重置式渲染副作用实施阶段API 硬切引入SlateAnnotationAnchor把bookmark改名为anchor导出新类型更新测试与示例存储性能新增projection、refresh({ ids })与 id 定向的注释/投影重建语义文档/示例新增docs/libraries/slate-react/annotations.md更新 review-comments 示例若双窗格示例能诚实证明通道分离则新增collaborative-comments.tsx协作证明新增 mock Yjs/服务锚点测试——编辑器内容只读时写入注释状态压力与关闭在宣告实现完成前运行聚焦的单元/React 测试与注释压力基准。快速驱动门禁Fast Driver Gatesbun test packages/slate/test/bookmark-contract.ts packages/slate-react/test/annotation-store-contract.tsx bun test packages/slate-react/test/annotation-store-contract.tsx --bail 1 bun run bench:react:rerender-breadth:local此外发布质量声明前需通过并排评论专用示例的浏览器证明。十五、最终用户评审交接提纲与计划收尾交接提纲陈述核心决策评论默认放在 Slate 值之外展示bookmark - anchor的 API 前后对比展示文档通道 vs 注释通道的数据属主前后对比展示data/projection拆分列出实现关闭前必须通过的证明行。研究决策配套决策页 docs/research/decisions/slate-v2-collaborative-annotation-channels.md 已被添加并索引。该决策接受外部注释通道作为裸 Slate 评论专用协作的默认同时保留文档内嵌 id 作为适配器或产品选择。评分与 pass 状态规划文档自评总分0.93阈值0.92六个维度均不低于0.85React 19.2 运行时性能0.94、Slate 贴近的无观点 DX0.93、Plate/slate-yjs 迁移主干0.91、防回归测试策略0.92、研究证据完整性0.94、shadcn 式组合性/极简主义0.91。所有 pass 均已标记为 complete包括current-state-read、intent-boundary、steelman、high-risk-deliberate、closure-score以及三个实现 passAPI 存储切片、文档/示例/协作证明、浏览器与压力关闭——浏览器证明在collaborative-comments示例上通过重渲染广度基准与bun check均通过。下一 pass 为none即实现已完成无后续动作。十六、延伸阅读想继续深入这套架构可以从仓库内以下文档入手docs/plans/2026-04-30-slate-v2-decoration-annotation-api-ralplan.md本文所依据的完整规划原文含全部证据行与证明表docs/research/decisions/slate-v2-collaborative-annotation-channels.md外部注释通道决策页docs/research/systems/slate-v2-overlay-architecture.mdDecoration / Annotation / Widget 三通道系统架构docs/research/concepts/source-scoped-overlay-invalidation.md源作用域失效概念与必需配料docs/slate-v2/decorations-annotations-cluster.md装饰/注释问题族语义坍缩、区间拓扑、失效性能、选区/IME 时序、注释压力的背景研究docs/plans/2026-04-14-slate-v2-decorations-annotations-cluster-research.md 与 docs/plans/2026-04-28-slate-v2-decoration-annotation-rewrite-review-plan.md该主题的前序研究与重写评审计划。需要说明的是规划文档中引用的.tmp/slate-v2/目录属于规划执行时使用的临时工作区不在当前仓库内文中关于源码行号与实现细节的描述均以规划文档的记录为准仓库内可直接核验的是上述研究、决策与概念文档。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考