Files
mnote/design/10-review/done/02-frontend-editor-tree-review.md
T
lix-2026 5f97800489 chore: align local-first control plane and editor fixes
- wire SQLite control-plane access/session paths into Rust web local-folder routes

- preserve local Markdown attachment semantics across upload, reload, and secondary-pane resource tabs

- refresh design governance docs, Reasonix task templates, and bug records

- retire root .mcp.json local MCP config
2026-05-23 23:38:42 +08:00

11 KiB
Raw Blame History

前端编辑器 / 树体验实现偏差审查

执行状态:已归档到 design/10-review/done/。本文保留为历史审查快照,当前活跃口径以 ./06-execution-checklist-and-acceptance.md 为准。

范围

本报告只审查 wolai-frontend 当前文档页、leptos-tiptap island host、Page Aggregate 消费链、Sidebar / tree shell / tree stream 与设计文档的一致性。

重点对照设计:

  • ARCHITECTURE.md
  • design/01-05-current-priority-overview.md
  • design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md
  • design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md
  • design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md
  • design/04-tree-domain/done/4-18-tree-final-dom-shell-cutover-hard-gate-v1.md
  • design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md
  • design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md
  • design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md
  • design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md
  • design/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_islandtree command 前端主调用已进入 /api/tree/commands + tree.* 元数据口径,Sidebar 的默认 tree shell 已切到 rust_wasm_dom_shell_host。2026-05-16 后,历史 iframe_srcdoc host 与显式 legacy flag 已从 active source 移除并进入 recycle/

但不能把当前状态描述为“前端编辑器 / 树体验已经完全收口到 Rust 单一真源”。主要未完成点集中在三处:页面本地 aggregate reducer 仍承担临时真相与补偿选择,pageSubtree 在本地正文变更后会被置空而不是形成同一份可持续 projectionSidebar 仍通过 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 / conflictDetectionKeywolai-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 返回 nullwolai-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 同时创建 useSidebarDatauseSidebarTreeStream:22usePreferredSidebarSnapshot 选择;wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.ts:29query / 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:60protectEditing 标记为 planned:84showBlockRefCount 标记为 plannedwolai-frontend/src/components/editor/page-options-sidebar.tsx:416 对 downgrade 项显示 待接线:71showBlockRefCount 成为不可交互占位。
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:121pickLeptosTiptapRuntimePageOptions 会把 showHeadingNumbers / embedDefaultBlockId 传给 islandwolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx:717 运行时更新这些选项。
F-06 已收口 Tree final DOM shell 默认路径已符合 4-18legacy iframe host 与环境变量回退入口已删除;后续只需保持负向 smoke,防止 active source 重新引入 iframe_srcdoc wolai-frontend/src/components/sidebar/tree-shell-host.tsx 默认 rust_family 且有 workspace 时只使用 TreeShellRustDomShellHostwolai-frontend/src/components/sidebar/tree-shell-host.test.tsx 覆盖默认不能进入 iframe_srcdoc

证据

已对齐部分

  1. 文档页读取主链已进入 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:225loadPageAggregate 只返回 Rust snapshot,不再在运行时 fallback 到 TS builder。
  2. 默认编辑器 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。
  3. 页面写命令已经开始按 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
  4. 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:205action 分发 tree command:278 创建命令使用 tree.node.create
  5. 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。

仍需收口部分

  1. 客户端 PageAggregateClientState 仍是混合态。

    • 它把 server snapshot、persisted title、draft title、本地 content、server subtree snapshot、revision/conflict key 放在同一个前端 reducer 中。
    • 这符合当前过渡态,但不等于 Rust page aggregate 已成为页面域唯一运行时真相。
  2. pageSubtree 与正文编辑没有同源更新。

    • 本地正文变更只会触发 apply_local_content_snapshot,不会同步生成新的 pageSubtree
    • selector 在内容对象不同于 server snapshot 时返回 null,因此 AI / read view / TOC 只能等待后续持久化与重新拉取。
  3. Sidebar live cache 仍有 freshness 选择层。

    • AppLayoutShellSidebar 都依赖 usePreferredSidebarSnapshot
    • 当前算法没有基于 Rust stream cursor / projection version 做统一仲裁,而是通过 sync key 与 treeStreamStatusquerytree_stream 之间选择。
  4. 页面设置中仍有显式未完成项。

    • protectEditingshowBlockRefCount 的代码状态已经诚实标为 planned / downgrade
    • 这避免了误导用户,但也说明 Page Aggregate -> island runtime 的设置语义还没有完全完成。

建议优先级

P0

暂无需要立即阻断主链的 P0。默认 Page Aggregate 读链、默认 island host、默认 DOM tree shell 均未发现与最新主线相反的实现。

P1

  1. 收口 pageSubtree 与本地正文编辑的关系。

    • 至少明确它是“server projection only”还是“本地编辑也应生成临时 projection”。
    • 如果 AI 面板需要稳定结构上下文,不应在用户正常编辑后直接拿到 null
  2. 将 Sidebar 的三源 preferred snapshot 推进为统一 live cache。

    • 建议以 Rust stream cursor / projection version / query snapshot version 为仲裁字段,减少仅靠 sync key 的 freshness 判断。

P2

  1. 继续移除或强门禁 legacy tree iframe host。

    • 当前显式 env flag 符合设计,但应保留负向 smoke,避免后续默认路径误回退。
  2. 同步修正文档滞后。

    • 5-5-1showHeadingNumbers / embedDefaultBlockId 的描述已经落后于当前代码,应在后续文档整理时更新。
  3. 页面设置 planned 项保持降级展示,直到真正进入 island runtime 或被产品侧移除。

修改的文件

  • /mnt/Data1T/mnote/design/10-review/done/02-frontend-editor-tree-review.md