
Slate v2 根运行时选择器守卫硬切换用静态权威测试锁定 React 组件与运行时模块的职责边界【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本文以 plate 仓库中 Slate v2 根运行时选择器守卫硬切换计划 为核心骨架展开完整讲解该次架构收口的三项工作——命名根选择器源、EditableDOMRoot根运行时所有权、静态守卫——并逐阶段复盘从激活到闭包的完整证据链。读完本文你将理解 Slate v2 如何在不动公开 DX 的前提下用静态清单 浏览器回归双保险把热渲染路径上的选择器策略与根策略从 React 组件中彻底剥离并能直接复用其权威审计测试的写法保护自己的编辑器架构。一、背景与目标React 只负责接线运行时才拥有策略本次硬切换计划的出发点是关闭评审中遗留的 React/runtime 架构发现项finding且不触碰公开 DX。计划覆盖范围被严格限定为三件事命名根选择器源Named Root Selector SourcesEditableDOMRoot根运行时所有权Root Runtime Ownership防止根策略与宽泛选择器回潮的静态守卫Static Guards。公开的renderVoid/ void-shell DX 工作明确不在范围内它排在本次内部运行时切割之后作为独立的公开 API 车道推进。1.1 事实核对评审结论哪里过时了计划开篇做了一个重要的事实更正评审指出的内联泛型选择器仍直接写在editable-text-blocks.tsx在当前 checkout 中已经不成立——EditableTextBlocks已经消费了来自root-selector-sources.ts的useLargeDocumentRootSources(...)与usePlaceholderValue(...)。真正剩余的问题比评审描述的更严格root-selector-sources.ts仍然持有泛型useSlateSelector(...)调用这在该模块就是命名选择器边界的前提下才是可接受的静态守卫必须保证热渲染组件不会内联重建泛型选择器EditableDOMRoot仍然直接编排了过多的运行时逻辑。1.2 North Star一页纸的架构愿景计划的北极星标准非常清晰React 负责接线refs、listeners、props、rendered children。运行时模块拥有编辑策略、选区导入/导出、修复repair、Android、组合输入composition、追踪tracing、强制渲染force render以及根选择器事实root selector facts。任何热渲染组件都不应内联包含宽泛的选择器谓词或快照遍历。这一原则是后面所有守卫的评判依据。1.3 明确的 Non-Goals为防止车道蔓延计划列出了一组硬性非目标不添加renderVoid不把 Plate 风格节点规范引入 Slate不改变 Slate 文档形状document shape或操作语义不把慢速生成压力测试移入默认bun check不把宽泛的根失效root invalidation藏在更友好名字后面而不加守卫除非本计划暴露直接回归否则不重写 2026-04-27 已完成的事件运行时门面event runtime facade。二、目标形态Target Shape三类边界如何收敛2.1 命名根选择器源泛型选择器只允许一个所有者EditableTextBlocks应当只消费命名事实named facts目标是这样的调用形态const rootSources useEditableRootSources({ largeDocumentConfig, placeholder, promotedIslandIndex, });或等价的拆分 hooks 形态const topLevelRuntimeIds useRootRuntimeIds() const selectedTopLevelIndex useSelectedTopLevelIndex(enabled) const placeholderValue usePlaceholderValue(placeholder) const islandPlan useLargeDocumentRootSources(...)计划为此划定了允许与禁止的泛型选择器所有者清单角色位置允许的唯一泛型选择器所有者packages/slate-react/src/editable/root-selector-sources.ts禁止持有泛型选择器components/editable-text-blocks.tsx、components/editable.tsx禁止持有泛型选择器一旦存在 node/text/source 选择器mounted node/text/leaf 渲染组件同样禁止2.2 根运行时门面EditableDOMRoot只做一件事EditableDOMRoot应该实例化一个根运行时门面const rootRuntime useEditableRootRuntime({ editor, inputController, readOnly, rootRef: ref, scrollSelectionIntoView, shellBackedSelection, state, });并从门面接收运行时拥有的能力const { androidInputManagerRef, callbackRef, eventRuntime, isComposing, repairRuntime, selectionRuntime, traceRuntime, } rootRuntime;EditableDOMRoot可以挂载返回的 refs/listeners 并渲染但不应决定策略policy。配套的开发者经验文档 2026-04-27-slate-react-runtime-owner-cuts-need-static-inventories-and-browser-proof.md 给出了该门面的完整形态示例const { callbackRef, eventRuntime, isComposing, receivedUserInput, rootRef, shellBackedSelection, } useEditableRootRuntime({ autoFocus, decorate, editor, inputController, readOnly, rootPropsRef, scrollSelectionIntoView, state, })事件处理器同样收敛为单一门面useEditableEventRuntime(...)其返回值按语义命名handleKeyDown、handlePaste、handleBeforeInput、handleCompositionEnd等根组件不得逐个导入事件族 worker。2.3 静态守卫边界哪些调用被禁止回到EditableDOMRoot计划要求新增权威测试authority tests在以下禁止根代码出现时直接失败EditableDOMRoot中直接调用useSlateSelector(...)EditableTextBlocks中直接调用useSlateSelector(...)EditableTextBlocks中直接调用Editor.getSnapshot(...)EditableDOMRoot中直接构造 selectionchange 处理器EditableDOMRoot中直接构造 Android 引擎EditableDOMRoot中直接搭建选区协调器selection reconcilerEditableDOMRoot中直接搭建修复运行时repair runtimeEditableDOMRoot中直接搭建内核追踪运行时kernel trace runtimeEditableDOMRoot中直接调用根提交唤醒root commit wakeupEditableDOMRoot中直接挂载全局拖拽生命周期global drag lifecycle。EditableDOMRoot唯一被容忍的导入应当只有根门面和纯 React 展示辅助。三、四阶段执行从实时清单到闭包证明Phase 0实时清单与守卫基线目的在搬代码之前先让车道诚实。动作包括通读当前EditableDOMRoot、EditableTextBlocks与root-selector-sources.ts新增/更新描述当前根所有权数量的清单测试把现状分为三桶——已经正确的命名源所有权、被容忍的根接线、必须移入运行时模块的策略。验收标准守卫必须解释为什么root-selector-sources.ts可以使用泛型useSlateSelector(...)守卫必须在EditableTextBlocks新增内联泛型选择器或快照读取时失败守卫必须在EditableDOMRoot新增内联泛型选择器时失败。涉及文件按计划位于执行工作副本路径下.tmp/slate-v2/packages/slate-react/test/kernel-authority-audit-contract.ts、src/components/editable.tsx、src/components/editable-text-blocks.tsx、src/editable/root-selector-sources.ts。驱动门禁bun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsx bun --filter slate-react typecheckPhase 1完成命名根选择器源目的在不过度构建的前提下完成第一项。动作要点选择器主体留在root-selector-sources.ts内仅当能让契约更清晰时才重命名/拆分 hooksuseRootRuntimeIds、useSelectedTopLevelIndex、usePlaceholderValue、useLargeDocumentRootSources、useEditableRootCommitWakeup仅当能减少根组件 churn 与测试搭建时才考虑小型门面useEditableRootSources(...)把 equality 与shouldUpdate谓词移到每个选择器源旁边若现有覆盖无法证明仅选区操作与仅文本操作不会触发宽泛根重渲染则补充选择器谓词测试。验收标准EditableTextBlocks中无useSlateSelector(...)EditableTextBlocks中无Editor.getSnapshot(...)根选择器事实被命名并有测试文档化仅选区操作不使顶层运行时 id 失效仅文本操作不使顶层运行时 id 失效占位符更新忽略仅选区操作。浏览器门禁若根渲染发生变化PLAYWRIGHT_RETRIES0 PLAYWRIGHT_WORKERS1 bun playwright playwright/integration/examples/large-document-runtime.test.ts playwright/integration/examples/search-highlighting.test.ts --projectchromiumPhase 2引入useEditableRootRuntime(...)目的关闭剩余的 Finding 1 所有者而不只是把文件变小。动作新增editable/runtime-root-engine.ts或等价模块把根运行时编排整体移出EditableDOMRoot包括Android 输入管理器生命周期、selectionchange 处理器与调度器构造、选区导入控制器构造、仅选区 DOM 导出订阅、修复运行时构造、内核追踪运行时构造、根提交唤醒、全局 selectionchange 监听器挂载、全局拖拽生命周期监听器挂载、根 callback ref 组合。事件族装配继续保留在useEditableEventRuntime(...)内。useEditableRootRuntime(...)可以组合现有运行时模块但不能成为垃圾堆——它应当读起来像对命名运行时能力的编排。验收标准EditableDOMRoot调用useEditableRootRuntime(...)不再直接构造 selectionchange 处理器、Android 管理器、修复/追踪运行时不再直接订阅选择器运行时只负责挂载返回的 refs/listeners 并渲染既有事件运行时门面仍是暴露给根组件的唯一事件处理器所有者。此阶段驱动门禁在基础守卫之外追加了选区/内核契约套件bun --filter slate-react test:vitest test/selection-controller-contract.test.ts test/selection-runtime-contract.test.ts test/editing-kernel-contract.test.ts test/editing-epoch-kernel-contract.test.ts test/target-runtime-contract.test.ts浏览器门禁覆盖 hovering-toolbar、richtext、search-highlighting--grep hovering toolbar|paste|undo|search以及 mentions、tables、images、large-document-runtime 两组 Chromium 行。Phase 3根策略硬守卫目的让架构耐用。动作扩展kernel-authority-audit-contract.ts的根运行时清单守卫EditableDOMRoot的禁止调用/导入守卫EditableTextBlocks的选择器所有权守卫根选择器源所有权使该类别中唯一的泛型选择器调用位于root-selector-sources.ts新增带显式允许名单allowlist的热渲染组件中无宽泛根选择器测试。此阶段之后EditableDOMRoot中禁止出现useSlateSelector( Editor.getSnapshot( useEditableRootCommitWakeup( useRuntimeAndroidEngine( createRuntimeSelectionChangeHandler( createRuntimeSelectionChangeScheduler( createRuntimeSelectionImportController( useEditableSelectionReconciler( subscribeSelectionOnlyDOMExport( useRuntimeRepairEngine( useRuntimeKernelTraceEngine( attachEditableSelectionChangeListener( attachEditableGlobalDragLifecycleListeners(允许出现useEditableRootRuntime( useEditableEventRuntime( useEditableRootRef( // 仅当根 ref 组合保持纯 React 时若根 ref 组合需要选区/事件策略也应一并移入useEditableRootRuntime(...)之后。验收标准静态守卫在直接根策略回潮时失败守卫保持根选择器所有权收窄权威测试为每个剩余允许桥接点标注所有者与理由。Phase 4闭包证明目的证明这次重构不是化妆式的。动作跑聚焦单元门禁跑用户报告族对应的聚焦浏览器行——hovering toolbar 鼠标选区、搜索输入焦点保持、mentions 内联 void 导航、表格右箭头单元格边界、图片块级 void 导航、大文档运行时代码移动后跑 lint fix车道其他部分完成后再跑bun check:full。最终门禁完整命令序列计划原文bun --filter slate-react test:vitest test/kernel-authority-audit-contract.test.ts test/surface-contract.test.tsx bun --filter slate-react typecheck bun --filter slate-react test:vitest test/selection-controller-contract.test.ts test/selection-runtime-contract.test.ts test/editing-kernel-contract.test.ts test/editing-epoch-kernel-contract.test.ts test/target-runtime-contract.test.ts bunx turbo build --filter./packages/slate-browser --filter./packages/slate-dom --filter./packages/slate-react --force PLAYWRIGHT_RETRIES0 PLAYWRIGHT_WORKERS1 bun playwright playwright/integration/examples/hovering-toolbar.test.ts playwright/integration/examples/richtext.test.ts playwright/integration/examples/search-highlighting.test.ts --projectchromium --grep hovering toolbar|paste|undo|search PLAYWRIGHT_RETRIES0 PLAYWRIGHT_WORKERS1 bun playwright playwright/integration/examples/mentions.test.ts playwright/integration/examples/tables.test.ts playwright/integration/examples/images.test.ts playwright/integration/examples/large-document-runtime.test.ts --projectchromium bun lint:fix bun check:full完成标准Completion Criteria汇总EditableTextBlocks只消费命名根选择器源泛型根选择器被隔离在root-selector-sources.tsEditableDOMRoot只通过一个根运行时门面获取根策略EditableDOMRoot只通过一个事件运行时门面获取事件处理器热策略若回到EditableDOMRoot静态守卫立即失败聚焦浏览器行全部通过bun check:full通过。四、源码佐证静态清单是架构锁浏览器证明是回归锁本次硬切换的方法论在仓库配套文档 2026-04-27-slate-react-runtime-owner-cuts-need-static-inventories-and-browser-proof.md 中有完整的源码级沉淀核心可复现写法如下。4.1 用expectAuthorityInventory锁定调用点归属对每个运行时能力用正则清单把谁在调用、调用几次、所有者是谁、为什么全部写进权威测试// test/kernel-authority-audit-contract.ts expectAuthorityInventory(/\bbeginEditableEventFrame\(/g, { packages/slate-react/src/editable/runtime-kernel-trace.ts: { count: 3, next: central-owner, owner: Runtime kernel trace engine, rationale: Non-selectionchange event frames are owned by the runtime kernel trace engine., }, })同样的写法还用于根边界expectSourceOwnershipInventory( editableRootRuntimeFiles, /useEditableRootRuntime\(/g, { packages/slate-react/src/components/editable.tsx: { count: 1, owner: Editable root component, next: keep-as-root-facade-call, rationale: EditableDOMRoot may instantiate the root runtime facade, but root policy lives behind that facade., }, } )这套清单 所有者 理由的测试形态让架构漂移在 CI 阶段就可见任何人把useSlateSelector、syncEditorSelectionFromDOM、beginEditableEventFrame、recordEditableKernelTrace或forceRender()加错位置包守卫会在浏览器症状变成打地鼠队列之前直接失败。4.2 事件族与根策略的命名边界同一文档给出了命名边界清单与本计划的 Target Shape 完全一致运行时模块按所有权命名runtime-selection-engine、runtime-repair-engine、runtime-kernel-trace、runtime-composition-engine、runtime-android-engine、runtime-event-engine、runtime-root-engine、root-selector-sourcesEditableDOMRoot只允许一个门面导入多个 worker 导入意味着 React 又在重新装配事件运行时EditableDOMRoot禁止直接调用根策略useRuntimeAndroidEngine、selectionchange 调度器/导入控制器搭建、修复/追踪运行时搭建、根提交唤醒、全局拖拽/选区生命周期挂载浏览器句柄与 target-runtime 发布必须走命名运行时桥接 hooks而不是EditableDOMRoot内的内联 effects。4.3 为什么静态清单 浏览器回归缺一不可配套文档总结得很直白静态清单让架构漂移可见但静态所有权无法证明时序与 DOM 权威类 bug因此浏览器证明负责另一半——它捕捉的是 timing 与 DOM authority 问题。两者组合才能让一次运行时所有者切割不变成化妆式的文件拆分。闭包证据必须是三层聚焦包契约所有权与选择器事实→ 用户报告族的定向浏览器行 → 生成压力 bun check:full发布级闭包。这条方法论的前置车道是 2026-04-27-slate-v2-editable-event-runtime-hard-cut-plan.md事件运行时硬切换本次根运行时切割正是其上方/旁边的延续后续大文档 DOM 呈现2026-05-03-slate-v2-dom-present-large-doc-phase-6-plan.md与移动端 IME 输入运行时2026-05-07-slate-v2-mobile-ime-input-runtime-ralplan.md也在同一架构路线上。五、风险登记与缓解策略计划为四种风险各配了缓解手段这套风险-缓解结构可以直接复用到类似的运行时重构中风险缓解策略useEditableRootRuntime(...)变成新的上帝钩子god hook保持 worker 模块命名且独立门面只组合能力不内联选区/修复算法权威守卫按 worker 文件统计策略调用数根选择器只改名、未改进除非守卫证明热组件不再拥有泛型选择器否则不接受纯包装迁移shouldUpdate谓词紧跟选择器所有权selectionchange 相关的浏览器时序变化一次只提取一个所有者闭包前跑 hovering toolbar、search、mentions、tables、images、large-document 行未过bun check:full不得标记完成与已完成的 2026-04-27 车道冲突视 2026-04-27 事件运行时车道为已关闭本计划只处理其上方/旁边的根运行时编排与选择器守卫加固六、执行台账2026-04-28 一天内的完整证据链计划附带执行台账记录了从激活到闭包的四个 checkpoint是硬切换如何落地的直接事实依据。Checkpoint 1激活与 Phase 0通过complete-plan激活计划active goal state置为status: pending在kernel-authority-audit-contract.ts增加根选择器源所有权守卫为 Phase 2 燃烧清单记录了当前EditableDOMRoot的根运行时编排清单将EditableTextBlocks中既有的Editor.getSnapshot(...)读取归类为mounted-node 子运行时 id 解析而非根选择器债——这是本车道明确拒绝的战术之一不强制本车道把该读取清零。证据权威/表面守卫通过2 文件、17 测试slate-reacttypecheck 通过。Checkpoint 2Phase 1保留拆分根 hooks未加门面因为EditableTextBlocks已清晰消费命名根事实新增 provider hook 测试证明usePlaceholderValue(...)忽略仅选区提交证据provider hooks 契约通过1 文件、10 测试权威/表面守卫仍 2 文件、17 测试。拒绝战术暂不添加useEditableRootSources(...)——拆分 hooks 已命名门面在 Phase 2 证明其价值前只是化妆。Checkpoint 3Phase 2 Phase 3新增runtime-root-engine.ts把根运行时编排全部收进useEditableRootRuntime(...)事件处理器装配保留在useEditableEventRuntime(...)EditableDOMRoot缩减为根门面接线、事件门面接线、refs、listeners、props、渲染权威守卫升级根策略导入/调用若回到EditableDOMRoot即失败根选择器源继续封锁在root-selector-sources.ts顺带修正了发布逃生舱清单escape-hatch inventory中一条过期的slate-react/src核心字段引用证据权威/表面守卫 2 文件 17 测试typecheck 通过选区/编辑内核契约 4 文件 35 测试bun lint:fix通过逃生舱清单契约 3 测试通过。Checkpoint 4Phase 4 完成闭包跑完用户报告族的聚焦浏览器行最终全量门禁bun check:full通过关键数字记录于计划执行台账lint 通过package/site/root typecheck 通过默认测试1050 pass / 95 skip / 0 failslate-reactvitest18 文件 / 105 测试发布纪律83 测试slate-browserproof20 测试scoped 移动端 proof 通过持久 profile 浸泡 5 轮通过集成浏览器扫描628 passed / 4 skipped。结论完成目标达成——EditableDOMRoot不再持有根策略实体热根渲染选择器所有权由命名源模块 守卫双重封锁。七、总结与后续本次根运行时选择器守卫硬切换证明了三个架构结论所有权必须显式化泛型选择器只允许一个命名源模块持有热渲染组件只消费命名事实否则重构只是把问题换了个位置边界必须有守卫静态权威测试以调用清单 所有者 理由的形式锁定根策略边界让回潮在 CI 阶段即失败闭包必须有浏览器证明时序与 DOM 权威类回归只能靠 hovering toolbar、search、mentions、tables、images、large-document 等用户报告族的定向浏览器行 bun check:full收尾。后续的公开 DX 工作runtime-owned void shells、面向作者的 render API 清理将作为独立车道启动本计划的执行台账与配套方案文档2026-04-27-slate-react-runtime-owner-cuts-need-static-inventories-and-browser-proof.md是理解 Slate v2 架构演进路线的第一手资料仓库当前的 Slate 核心源码位于 packages/slate含 interfaces、dom-editor 内部工具 与 transforms 等可供继续深挖底层实现。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考