Slate Browser 的下一个系统级动作:以 openExample 就绪契约为核心,攻克零宽字符与 IME 渲染/输入策略证明赛道
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本文以仓库内 docs/plans/2026-04-04-slate-browser-next-system-move.md 为主干,融合 docs/slate-browser/ 下的配套研究文档,完整还原这次"系统级动作"的决策过程、API 分期、落地清单与验证命令,并给出可供直接引用的源码级依据。
导读
在 Slate v2 /slate-browser的第一个公开包 tranche 落地之后,"下一步做什么"成为决定浏览器证明(browser proof)体系走向的关键问题。本文基于该计划文档,完整梳理其结论:最高杠杆的系统级动作不是跨浏览器、也不是性能,而是先把openExample(...)打磨成真正的"就绪契约"(readiness contract),再用它搭建渲染器/输入策略(renderer/input-policy)证明赛道,专门攻克零宽字符、空状态与 IME 敏感行为。读完本文,你将掌握:slate-browser三阶段 API tranche 的全部落地形态、选择书签(selection bookmark)接缝的设计动机、本地验证命令清单,以及该决策如何服务于"slate-v2管文档真相、slate-browser管浏览器证明、未来plate-v2管投影与产品化"的整体系统目标。
一、文档定位:一份"条件性后续动作指南"
该计划文档在开头就明确了自己的属性:
这是条件性的后续动作指南,而不是 Slate v2 项目当前的默认队列。
换句话说,它不是 roadmap 本身(roadmap 真相见 docs/slate-v2/ 中链接的 master-roadmap 文档体系),而是回答一个更窄的问题:
如果
slate-browser工作在第一波公开包 tranche 之后重新开启,最高杠杆的定向动作是什么?
文档同时给出了完整的执行背景(Context)与六个阶段(Phases):
- 完成:将原内部文档树迁入
docs/,且不丢失已有docs/*内容; - 完成:更新内部文档对
docs的引用; - 完成:阅读相关文档与包/测试文件;
- 完成:综合出
slate-browser/slate-v2的最强下一步; - 完成:为该动作直接修改文档;
- 完成:同一轮内验证文档迁移与文档同步。
从后续docs/analysis/editor-global-systems-objective.md、docs/slate-browser/next-api-candidates.md、docs/slate-browser/next-api-candidates-matrix.md、docs/slate-browser/four-way-api-deep-dive.md 等产出物可以看出,这些阶段全部按期闭环。
二、关键发现(Findings):合并而非重命名
文档用一组"Findings"记录了调研结论,其中最重要的几条是:
- 现有
docs/已包含活跃内容(analysis/、plans/、solutions/、performance/、table/),原内部文档树还额外带有plans/、slate-browser/、slate-v2、slate-issues与额外的solutions/子树——这是一次合并(merge),不是重命名(rename); - 当前
slate-browser公共 tranche 已落地在.tmp/slate-v2; - 最高杠杆的下一步不是跨浏览器,也不是性能优先;
- 最好的下一步是:更强的
openExample(...)就绪契约,然后是针对零宽字符与 IME 敏感行为的渲染器/输入策略证明赛道。
这些结论并非拍脑袋,而是经过了文档重读、包/测试文件审阅、以及一轮"跨仓库系统扫描"(覆盖 Lexical、edix、rich-textarea、Premirror、Pretext、TanStack DB、urql、VS Code、LSP)之后收敛出来的。
三、更大的图景:三层系统分工
该计划把"下一步动作"放在了一个显式的系统目标之下(详见 docs/analysis/editor-global-systems-objective.md):
slate-v2:负责文档真相(document truth)——文档语义、操作、事务、不可变提交快照;slate-browser:负责浏览器证明(browser proof)——分层测试车道、示例挂载真相、把选区与剪贴板当作一等公民、Chromium 优先的 IME 证明;- 未来
plate-v2:负责投影(projections)、执行管道(pipelines)、托管服务、布局系统与产品化。
系统目标文档还给出了完整的九层"责任分层"栈:文档引擎平面(slate-v2)、运行时与渲染平面(slate-react-v2)、浏览器证明平面(slate-browser)、投影与派生数据平面(未来plate-v2)、执行管道平面(未来plate-v2)、托管特性与协议平面(未来plate-v2)、布局与测量平面(未来plate-v2)、轻量表面平面(未来plate-v2)、产品化平面(未来plate-v2)。
计划文档中记录的最强跨域参照系(cross-domain imports)也与之对应:
| 参照对象 | 借鉴方向 |
|---|---|
| TanStack DB | 投影存储(projection stores) |
| urql | 执行管道(execution pipelines) |
| VS Code + LSP | 托管语义服务(hosted semantic services) |
| Premirror + Pretext | 布局与测量系统(layout and measurement) |
| rich-textarea / edix | 轻量表面(lightweight surfaces) |
文档特别强调一条纪律:"从每个参照仓库偷对的理念,但让每一层待在它的车道上"——这正是判断"下一步该做什么、不该做什么"的总纲。
四、下一批 API 候选的排序与三段式分期
计划文档给出了slate-browser下一批 API 候选的显式排序:
ready契约editor.selection.select(...)editor.get.blockTexts()/assert.blockTexts(...)editor.snapshot()- 容错选区断言(tolerant selection assertions)
- HTML 归一化选项(HTML normalization options)
- 真实剪贴板读取助手(real clipboard read helpers)
- 更晚的路径导向定位器(path-oriented locators)
配套的 docs/slate-browser/next-api-candidates-matrix.md 给出了每个候选的详细评审(状态、候选形态、证据来源、消除的痛点、风险与规则),并据此划出阶段切分:
- Phase 1:
ready、selection.select(...)、blockTexts、snapshot() - Phase 2:容错选区断言、HTML 归一化选项、可能的
get.selectedText() - Phase 3:真实剪贴板读取、替代表面作用域(alternate-surface scoping)、路径导向定位器
4.1 当前测试痛点数据:API 存在的理由
矩阵文档统计了当前 Playwright 示例套件中的"重复噪音":
- 裸
page.goto(...):23 处 - 裸
getByRole('textbox'):41 处 - 裸
selectText():7 处 - 裸 DOM 选区手术(
document.createRange/window.getSelection()/addRange(...)):5 处 - 裸
boundingBox()断言:2 处
这套数据正是"下一个 API 应该消除高频噪音设置,而不是再添一个可爱助手名"的量化依据。
4.2 四个"现在就发"的 API 候选形态
①ready契约(最强,取自 Lexical 的initialize(...)纪律,但不复制其整体臃肿):
const editor = await openExample(page, "custom-placeholder", { ready: { editor: "visible", placeholder: "visible", text: /Type something/, selection: "settled", selector: "#document-outline", }, });可选表单(harness 内):
await editor.ready({ selection: { anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, }, });它消除的痛点:临时page.goto(...)、对占位符/文本/额外选择器的一次性等待、隐藏的选区稳定(selection-settle)时序。强规则:优先一个ready对象,而不是更多顶层waitForX布尔值;风险是变成"厨房水槽",规则是"保持狭窄、只做就绪"。
②editor.selection.select(...)(最强 Slate 风格候选):
await editor.selection.select({ anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 5 }, });以及便捷形态与伴随方法:
await editor.selection.collapse({ path: [0, 0], offset: 0 }); await editor.selection.selectBlock([1]);它解决的悖论是:当前 harness 能断言语义选区,却不能创建语义选区——测试被迫回退到裸 DOM Range 手术。强规则:选区设置语义优先,鼠标手势留给交互测试而非基础设置。
③editor.get.blockTexts()/editor.assert.blockTexts(...)(最佳语义 getter,取自 edix):
expect(await editor.get.blockTexts()).toEqual(["alpha", "beta"]); await editor.assert.blockTexts(["alpha", "beta"]);伴随窄 getter 与选中文本:
expect(await editor.get.textAt([1])).toBe("beta"); expect(await editor.get.selectedText()).toBe("wise quote");它优于扁平的get.text()(对许多 Slate 测试而言过于有损),也比裸 HTML 更贴近 Slate 语义、比 DOM 琐碎更稳定。强规则:停在块级文本语义,不要在这里添加伪造的 DOM 到 Slate JSON 反序列化。
④editor.snapshot()(最高价值的调试 API,灵感来自use-editable的getState()):
const snapshot = await editor.snapshot(); expect(snapshot).toEqual({ text: "Hello", blockTexts: ["Hello"], selection: { anchor: { path: [0, 0], offset: 5 }, focus: { path: [0, 0], offset: 5 }, }, domSelection: { anchorNodeText: "Hello", anchorOffset: 5, focusNodeText: "Hello", focusOffset: 5, }, });矩阵文档给出的预期载荷还包括selectedText、placeholderShape。强规则:快照只聚合既有真相,不发明新的隐藏状态——这也是未来 agent-native 产物捕获的燃料。
4.3 第二波候选
容错选区断言(取自 Lexical;offset 可以是精确值或区间[min, max]):
await editor.assert.selection({ anchor: { path: [0, 0], offset: [0, 1] }, focus: { path: [0, 0], offset: [0, 1] }, });DOM 侧对应:
await editor.assert.domSelection({ anchorOffset: [0, 1], focusOffset: [0, 1], });HTML 归一化选项(挂在htmlEquals上,而不是新增一堆近似重复方法):
await editor.assert.htmlEquals(expectedHtml, { ignoreClasses: true, ignoreInlineStyles: true, ignoreDir: true, });4.4 第三波候选
真实剪贴板读取(edix 已证明navigator.clipboard.read()在浏览器测试中有用):
await editor.clipboard.copy(); expect(await editor.clipboard.readText()).toContain("Hello"); expect(await editor.clipboard.readHtml()).toContain("<p");替代表面作用域(iframe / shadow DOM,当前套件已有 iframe.test.ts 与 shadow-dom.test.ts 所述压力)与路径导向定位器:
const editor = await openExample(page, "iframe", { surface: "iframe" }); await editor.path([1]).click({ clickCount: 3 }); await editor.textNode([0, 0]).click();4.5 明确拒绝的 API
两份候选文档都列出了"不要加"清单:
openFixture(...):没有真实 fixture 车道,示例挂载真相仍是正确接缝;editor.driver():通用驱动逃生舱会摧毁 Slate 风格 API 的意义;- 伪造的合成公开粘贴助手:真实浏览器剪贴板写入 + 真实粘贴手势已经存在;
- 一个巨型
EditorDriver抽象:当前后端是 Playwright-first,现在假装不是就是"抽象角色扮演"。
五、四方深度对比:谁还在贡献 API 思想
计划文档记录了"4-way deep dive"(针对 Lexical、ProseMirror、Tiptap、edix 四家的聚焦对照,完整版见 docs/slate-browser/four-way-api-deep-dive.md)。其结论(Bottom Line First):
- Lexical仍是助手 API 的最强来源(就绪契约、容错选区断言、HTML 归一化选项、剪贴板序列化纪律、人类可读期望选区构建器);
- ProseMirror是最强的接缝/不变量来源——不提供最漂亮的 API,但提供最好的不变量和一个严肃的后期 API:选区书签(selection bookmarks);
- edix仍贡献少量高价值语义 getter(
getText、getSelection、getSelectedRect、getSelectedText); - Tiptap主要是 DX 与产品化验证者,不是助手 API 矿藏。
这次深潜"materially changed one thing":ProseMirror 暴露了一个真实的后期候选——editor.selection.bookmark()/capture()——但前提是它必须有真实的 Slate 侧书签/range-ref 接缝支撑,而不是 Playwright 伪造品。
六、当前状态盘点:三个 Phase 已全部落地
计划文档的 Findings 明确记录了"已落地在代码里"的清单(对应实现位于.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts,配套 red/green 覆盖在.tmp/slate-v2/playwright/integration/examples/slate-browser-helpers.test.ts;这些路径是计划文档记录的当时状态):
Phase 1 已落地:
ready契约editor.selection.select(...)editor.selection.collapse(...)editor.get.blockTexts()editor.assert.blockTexts(...)editor.snapshot()
Phase 2 已落地:
- 容错选区断言
- 容错 DOM 选区断言
editor.get.selectedText()- 归一化的
editor.assert.htmlEquals(..., options?)
Phase 3 已落地:
- 真实剪贴板读取:
editor.clipboard.readText()、editor.clipboard.readHtml() - 替代表面作用域:
surface.frame、surface.scope - 路径导向定位器:
editor.locator.block(...)、editor.locator.text(...)
选择书签接缝也已落地:
editor.selection.capture(...)editor.selection.bookmark(...)editor.selection.resolve(...)editor.selection.restore(...)editor.selection.unref(...)
文档特别强调:这个接缝由真实编辑器RangeRef语义支撑(暴露在根表面上),而不是一个伪造的仅限 Playwright 的快照别名——为此还在旧Editable根与slate-dom-v2挂载根上暴露了刻意设计的浏览器测试句柄,让slate-browser能创建真正的RangeRef支撑选区书签。
七、决策标准与五个方向研判
文档 docs/slate-browser/next-system-move.md 给出了下一步动作的决策标准,下一步应:
- 直接帮助证明剩余的
slate-v2浏览器面向接缝; - 提升测试诚实度,而不是抽象数量;
- 让未来的跨浏览器与性能车道更轻松;
- 在第二个后端真实存在之前,避免伪造"车道中立"的 API 设计。
基于此,文档对五个候选方向逐一研判:
方向 1:更强的openExample(...)就绪契约——最强动作。理由:每个剩余的浏览器面向证明都以挂载示例为起点;当前openExample(...)有用但仍单薄;就绪状态目前被描述为"几个等待",而不是一个真正的契约。它应该:保持openExample(...)作为唯一路由入口、让就绪显式且有意为之、区分页面导航 / 编辑器根可见性 / 示例特定挂载状态 / 选区敏感就绪。它不应该变成:Lexical 体量的厨房水槽初始化器、通用 runner 抽象、新的公开openFixture(...)。强立场:偷 Lexical 的设置纪律,不复制它的整个initialize(...)大块。
方向 2:跨浏览器车道——重要,但不是第一。现在做它输的原因:文档已拒绝伪造的跨浏览器 IME 抽象剧场;Chromium 仍是 IME 与剪贴板重负载示例证明的诚实第一车道;没有更锋利的就绪契约,跨浏览器车道只会给你"更宽的 flake"。最佳姿态:就绪契约落地后加test:slate-browser:cross,先从非 IME 证明开始(占位符可见性、语义选区归一化、普通剪贴板行为),不要从 Safari/WebKit IME 英雄主义开始。
方向 3:性能/精度车道——应该做,但要等下一个正确性接缝钉死之后。Premirror 与 Pretext 是车道命名与基准诚实度的正确影响源,但正确性目标停止漂移后,性能车道才值钱得多。最佳后期形态:test:slate-browser:perf,可能还有test:slate-browser:accuracy。强立场:渲染器/输入策略证明赛道存在之前,不上性能车道。
方向 4:弱化 Playwright 特定公开边界——今天做是错的。理由:当前只有一个真实公开后端slate-browser/playwright;现在构建通用EditorDriver是抽象角色扮演;包已经有正确的切分(core/browser放车道中立名词,playwright放后端特定 harness)。规则:保持 API 名词编辑器形状,在第二个后端真实之前保持后端表面显式。
方向 5:发布姿态——此刻最不重要。包形状已经真实;包承诺仍应保守;发布策略不会解锁下一个slate-v2接缝。最佳姿态:在公开 API 之上再落地一个真实证明车道之前,保持包 experimental/private。
八、具体动作批次与目标接缝
计划的最终推荐是两个连续动作:
- 把
openExample(...)变成真正的就绪契约; - 用该契约搭建第一个渲染器/输入策略证明赛道。
该赛道应瞄准当前未解决的接缝(这也是本次"系统级动作"最终要钉死的四大目标):
- 空块占位符策略(empty block placeholder policy)
- 零宽字符渲染策略(zero-width rendering policy)
- 哨兵文本周围的选区归一化(selection normalization around sentinel text)
- 空/零宽敏感起点上的 IME 提交行为(IME commit behavior on empty and zero-width-sensitive starts)
具体 tranche(Do this next):
- 收紧
OpenExampleOptions,让就绪语义替代临时等待; - 为零宽 / 空状态 / IME 邻近行为添加就绪敏感的浏览器回归;
- 只有在那之后,才向外分支到
test:slate-browser:cross、test:slate-browser:perf,可能还有test:slate-browser:accuracy。
8.1 反规则清单
文档明确列出"不要先做这些":
- 重新引入
openFixture(...) - 添加伪造的公开合成粘贴助手
- 发明车道中立 driver 抽象
- 从跨浏览器 IME 剧场开始
- 把发布语义变成主要架构决策
九、分层测试框架与命令体系
该动作植根于 docs/slate-browser/overview.md 确立的分层测试框架世界观:"一个 runner 统治一切是陷阱;速度、保真度与覆盖想要不同的工具":
- Layer 0 核心快速测试:纯模型/变换/选区/投影语义;
- Layer 1 DOM 契约测试:浏览器支撑的契约测试(未来方向是 Vitest browser + Playwright provider);
- Layer 2 示例集成测试:Playwright,断言真实示例表面上的真实编辑器行为;
- Lane A IME/组合:Chromium Playwright + CDP(jsdom 组合测试不够);
- Lane B Agent 原生:
dev-browser/agent-browser,后期扩展接缝; - Lane C 性能:显式基准脚本。
slate-browser在该体系中的角色是:master roadmap 的专家测试/证明车道,不拥有 roadmap 真相或队列顺序,但喂养已产出工件义务(proof ledger)。
计划文档记录的可信本地命令包括(均为当时仓库根命令):
yarn workspace slate-browser testyarn test:slate-browser:e2e:localyarn test:slate-browser:bookmarks:localPLAYWRIGHT_BASE_URL=http://localhost:3200 yarn test:slate-browser:e2ePLAYWRIGHT_BASE_URL=http://localhost:3200 yarn test:slate-browser:imePLAYWRIGHT_BASE_URL=http://localhost:3200 yarn test:slate-browser:clipboardPLAYWRIGHT_BASE_URL=http://localhost:3200 yarn test:slate-browser:anchorsyarn lint:typescriptROLLUP_PACKAGES=slate-browser,slate-react,slate-dom-v2 yarn build:rollup
其中两条本地置信命令(yarn test:slate-browser:e2e:local、yarn test:slate-browser:bookmarks:local)是新接入的:计划文档记录,书签接缝验证时因共享的:3200服务器对应用运行时改动"不是可信目标",因此用全新本地站点服务器http://localhost:3210验证,并修复了本地 Playwright runner 路径(新增scripts/run-slate-browser-local.sh并在.tmp/slate-v2/package.json接入 fresh-server 本地命令)。
十、执行进度:从文档迁移到书签验证的一日闭环
计划文档的 Progress 日志(2026-04-04)完整记录了这次动作从调研到验证的闭环:
- 加载
task、learnings-researcher、goal workflow、major-task; - 对照当前
docs/与原内部文档树,映射迁移前的重叠; - 将原内部文档树迁入
docs/并重写过期内部路径引用; - 重读 Slate v2 /
slate-browser文档、学习文档、包文件与示例测试; - 新增 docs/slate-browser/next-system-move.md 并从
slate-browser/slate-v2概览文档链接它; - 验证旧内部文档树消失、无陈旧路径引用残留;
- 运行跨仓库系统扫描(Lexical、edix、rich-textarea、Premirror、Pretext、TanStack DB、urql、VS Code、LSP);
- 新增 docs/analysis/editor-global-systems-objective.md 并链接;
- 新增 docs/slate-browser/next-api-candidates.md 与 docs/slate-browser/next-api-candidates-matrix.md;
- 完成 Lexical / ProseMirror / Tiptap / edix 四方对照,新增 docs/slate-browser/four-way-api-deep-dive.md;
- 依次实现 Phase 1、Phase 2、Phase 3 的
slate-browserPlaywright tranche,并逐波补充 red/green 覆盖; - 暴露浏览器测试句柄、落地书签接缝,用
:3210fresh-server 验证并以yarn test:slate-browser:e2e:local跑完全部 helper 套件。
十一、结论
这份"下一系统级动作"文档的核心判断可以用三句话收束:
- 公开包 tranche 已经完成——
slate-browser已从一次性 spike 变成带公开拆分的真实 workspace 包(slate-browser、slate-browser/core、slate-browser/browser、slate-browser/playwright); - 下一步不是"更多表面积",而是两件事:在
openExample(...)上建立更锋利的就绪契约,再用该契约搭建渲染器/输入策略证明赛道,钉死空块占位符、零宽渲染、哨兵文本选区归一化与 IME 提交行为这四大接缝; - 这是通往诚实跨浏览器、性能与发布决策的最干净路径——只有当正确性车道停止漂移,
test:slate-browser:cross与test:slate-browser:perf才有意义;而只有由真实RangeRef语义支撑的选择书签接缝,才能让选区在历史、注释锚点与 transform 存续测试中成为一等公民。
对任何想要继续深入研究slate-browser体系的人,建议按此顺序阅读:先读 docs/slate-browser/overview.md 建立分层框架观,再读 docs/slate-browser/next-api-candidates.md 与 docs/slate-browser/next-api-candidates-matrix.md 理解 API 排序依据,然后读 docs/slate-browser/four-way-api-deep-dive.md 对照四方结论,最后回到 docs/slate-browser/next-system-move.md 与本文所依据的计划文档,即可完整还原这次"系统级动作"的决策全貌。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考