11 KiB
前端编辑器 / 树体验实现偏差审查
范围
本报告只审查 wolai-frontend 当前文档页、leptos-tiptap island host、Page Aggregate 消费链、Sidebar / tree shell / tree stream 与设计文档的一致性。
重点对照设计:
ARCHITECTURE.mddesign/01-05-current-priority-overview.mddesign/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.mddesign/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.mddesign/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.mddesign/04-tree-domain/done/4-18-tree-final-dom-shell-cutover-hard-gate-v1.mddesign/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.mddesign/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.mddesign/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.mddesign/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.mddesign/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md
重点读取代码:
wolai-frontend/src/app/(app)/documents/wolai-frontend/src/components/editor/wolai-frontend/src/lib/documents/wolai-frontend/src/components/sidebar/wolai-frontend/src/lib/tree-stream/wolai-frontend/src/lib/documents/tree-command-client.ts
结论
当前实现总体方向与最新设计主线一致:文档页 SSR 入口已消费 Rust mnote.page_aggregate.v1,默认主编辑器已折返到页面内 leptos_tiptap_island,tree command 前端主调用已进入 /api/tree/commands + tree.* 元数据口径,Sidebar 的默认 tree shell 已切到 rust_wasm_dom_shell_host,并且 iframe_srcdoc 只在显式 legacy flag 下存在。
但不能把当前状态描述为“前端编辑器 / 树体验已经完全收口到 Rust 单一真源”。主要未完成点集中在三处:页面本地 aggregate reducer 仍承担临时真相与补偿选择,pageSubtree 在本地正文变更后会被置空而不是形成同一份可持续 projection,Sidebar 仍通过 initial / query / tree stream 三源 freshness 选择维持一致性。此外,部分页面设置仍明确是 planned / downgrade,属于设计清单中尚未完成的范围。
关键发现表格
| 编号 | 分类 | 严重度 | 发现 | 证据 |
|---|---|---|---|---|
| F-01 | 未完成 | 中 | Page Aggregate 读链已 Rust-first,但客户端仍有本地 aggregate reducer 持有标题、正文、设置、子树快照等临时真相;设计中的“页面域单一真源”尚未闭环。 | wolai-frontend/src/components/editor/page-aggregate-client-state.ts:7 定义本地 PageAggregateClientState,包含 serverPageTitle / persistedPageTitle / draftPageTitle / options / content / serverPageSubtreeSnapshot / contentRevision / conflictDetectionKey;wolai-frontend/src/components/editor/document-content.tsx:144 使用 reducer 作为文档页核心状态。 |
| F-02 | 实现偏差 | 中 | pageSubtree 不是跟随本地正文和标题持续更新的 page projection;一旦本地正文对象与服务器快照不同,就直接返回 null,AI 面板和阅读结构面会失去这份 projection。 |
wolai-frontend/src/components/editor/page-aggregate-client-state.ts:163 通过 content === serverContentSnapshot 判断;wolai-frontend/src/components/editor/page-aggregate-client-state.ts:166 只有未改动时返回 serverPageSubtreeSnapshot,否则 :169 返回 null;wolai-frontend/src/components/editor/document-content.tsx:867 将该选择结果作为 AI snapshot 的 pageSubtree 来源,:1162 也传给阅读视图。 |
| F-03 | 未完成 | 中 | Sidebar / Breadcrumb / 文档页头已共享 preferred snapshot,但 live cache 仍不是唯一来源;当前仍在 initial、query refetch、tree stream 之间做 freshness 选择。 | wolai-frontend/src/components/app-layout-shell.tsx:20 同时创建 useSidebarData 与 useSidebarTreeStream;:22 用 usePreferredSidebarSnapshot 选择;wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.ts:29 在 query / tree_stream / initial 间选择;wolai-frontend/src/components/sidebar/sidebar.tsx:201 Sidebar 内部也消费同样选择结果。 |
| F-04 | 未完成 | 低 | 页面设置运行时语义已有代码级分类,但仍有正式 UI 中可见的 planned / downgrade 项;这与 5-6 清单中“页面设置未整体收口”的口径一致。 |
wolai-frontend/src/lib/documents/page-option-semantics.ts:60 将 protectEditing 标记为 planned,:84 将 showBlockRefCount 标记为 planned;wolai-frontend/src/components/editor/page-options-sidebar.tsx:416 对 downgrade 项显示 待接线,:71 让 showBlockRefCount 成为不可交互占位。 |
| F-05 | 方向变化/文档滞后 | 低 | 5-5-1 中仍写着 showHeadingNumbers / embedDefaultBlockId 只完成字段贯通,但当前代码已把它们纳入 island runtime payload;这里更像文档滞后,而不是实现偏差。 |
design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md 第 2.3 节仍说明两项“不可描述成正式支持”;wolai-frontend/src/lib/documents/page-option-semantics.ts:48 与 :88 均标记为 wired;:121 的 pickLeptosTiptapRuntimePageOptions 会把 showHeadingNumbers / embedDefaultBlockId 传给 island,wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx:717 运行时更新这些选项。 |
| F-06 | 风险 | 低 | Tree final DOM shell 默认路径已符合 4-18,但 legacy iframe host 仍可被环境变量显式打开;后续需要保持负向 smoke,防止默认路径回退。 |
wolai-frontend/src/components/sidebar/tree-shell-host.tsx:131 默认 rust_family 且有 workspace 时使用 DOM host,:133 仅在 NEXT_PUBLIC_TREE_SHELL_LEGACY_IFRAME_HOST=1 时启用 legacy iframe;wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx:182 覆盖默认不能进入 iframe_srcdoc,:199 覆盖显式 legacy flag。 |
证据
已对齐部分
-
文档页读取主链已进入 Rust Page Aggregate。
wolai-frontend/src/app/(app)/documents/[id]/page.tsx:51调用loadPageAggregateFromNextHeaders。wolai-frontend/src/lib/documents/page-aggregate-loader.ts:170请求/api/page-aggregate/:id。wolai-frontend/src/lib/documents/page-aggregate-loader.ts:130校验 schema 必须是mnote.page_aggregate.v1。wolai-frontend/src/lib/documents/page-aggregate-loader.ts:225的loadPageAggregate只返回 Rust snapshot,不再在运行时 fallback 到 TS builder。
-
默认编辑器 host 已是页面内
leptos_tiptap_island。wolai-frontend/src/components/editor/editor-host-config.ts:1只保留EditorHostKind = "leptos_tiptap_island"。wolai-frontend/src/components/editor/editor-host.tsx:7动态加载leptos-tiptap-island-editor-host。wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx:619直接调用 runtime module 的mount(container, ...),不是 iframe。
-
页面写命令已经开始按 page command family 收口。
wolai-frontend/src/lib/documents/page-command-contract.ts:15固定page.head.updateTitle / page.layout.updateOptions / page.body.save。wolai-frontend/src/lib/documents/page-command-client.ts:72标题写入发送commandName: page.head.updateTitle。wolai-frontend/src/lib/documents/page-command-client.ts:89页面设置写入发送commandName: page.layout.updateOptions。wolai-frontend/src/app/api/documents/save/route.ts:35服务端 envelope 使用PAGE_COMMAND_NAMES.saveBody。
-
tree command 前端调用主面已以
tree.*为结果元数据口径。wolai-frontend/src/lib/documents/tree-command-client.ts:26定义tree.node.create / tree.node.rename / tree.subtree.move / tree.node.archive等 preferred command。wolai-frontend/src/lib/documents/tree-command-client.ts:185统一 POST 到/api/tree/commands。wolai-frontend/src/app/api/tree/commands/route.ts:205由action分发 tree command,:278创建命令使用tree.node.create。
-
tree final DOM shell 默认路径已符合硬门禁。
wolai-frontend/src/components/sidebar/tree-shell-host.tsx:139默认 implementation 为rust_wasm_dom_shell_host。wolai-frontend/src/components/sidebar/tree-shell-surface.tsx:113旧 React page tree renderer 已显示为 removed fallback,不再作为正常 renderer。wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx:189测试断言默认 implementation 是rust_wasm_dom_shell_host,:194断言没有 iframe host。
仍需收口部分
-
客户端
PageAggregateClientState仍是混合态。- 它把 server snapshot、persisted title、draft title、本地 content、server subtree snapshot、revision/conflict key 放在同一个前端 reducer 中。
- 这符合当前过渡态,但不等于 Rust page aggregate 已成为页面域唯一运行时真相。
-
pageSubtree与正文编辑没有同源更新。- 本地正文变更只会触发
apply_local_content_snapshot,不会同步生成新的pageSubtree。 - selector 在内容对象不同于 server snapshot 时返回
null,因此AI / read view / TOC只能等待后续持久化与重新拉取。
- 本地正文变更只会触发
-
Sidebar live cache 仍有 freshness 选择层。
AppLayoutShell和Sidebar都依赖usePreferredSidebarSnapshot。- 当前算法没有基于 Rust stream cursor / projection version 做统一仲裁,而是通过 sync key 与
treeStreamStatus在query与tree_stream之间选择。
-
页面设置中仍有显式未完成项。
protectEditing与showBlockRefCount的代码状态已经诚实标为planned / downgrade。- 这避免了误导用户,但也说明
Page Aggregate -> island runtime的设置语义还没有完全完成。
建议优先级
P0
暂无需要立即阻断主链的 P0。默认 Page Aggregate 读链、默认 island host、默认 DOM tree shell 均未发现与最新主线相反的实现。
P1
-
收口
pageSubtree与本地正文编辑的关系。- 至少明确它是“server projection only”还是“本地编辑也应生成临时 projection”。
- 如果 AI 面板需要稳定结构上下文,不应在用户正常编辑后直接拿到
null。
-
将 Sidebar 的三源 preferred snapshot 推进为统一 live cache。
- 建议以 Rust stream cursor / projection version / query snapshot version 为仲裁字段,减少仅靠 sync key 的 freshness 判断。
P2
-
继续移除或强门禁 legacy tree iframe host。
- 当前显式 env flag 符合设计,但应保留负向 smoke,避免后续默认路径误回退。
-
同步修正文档滞后。
5-5-1对showHeadingNumbers / embedDefaultBlockId的描述已经落后于当前代码,应在后续文档整理时更新。
-
页面设置 planned 项保持降级展示,直到真正进入 island runtime 或被产品侧移除。
修改的文件
/mnt/Data1T/mnote/design/10-review/02-frontend-editor-tree-review.md