Files
mnote/design/old/10-review/done/02-frontend-editor-tree-review.md
T
Agent Board b798f628ee chore: land tree view-state, vault, Pi module split, and repo hygiene
Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
2026-07-21 05:13:05 +08:00

126 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# [recycle] 前端编辑器 / 树体验实现偏差审查
> 执行状态:已归档到 `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_island`tree 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 / 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,防止 active source 重新引入 `iframe_srcdoc`。 | `wolai-frontend/src/components/sidebar/tree-shell-host.tsx` 默认 `rust_family` 且有 workspace 时只使用 `TreeShellRustDomShellHost``wolai-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:225``loadPageAggregate` 只返回 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:205``action` 分发 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 选择层。
- `AppLayoutShell``Sidebar` 都依赖 `usePreferredSidebarSnapshot`
- 当前算法没有基于 Rust stream cursor / projection version 做统一仲裁,而是通过 sync key 与 `treeStreamStatus``query``tree_stream` 之间选择。
4. 页面设置中仍有显式未完成项。
- `protectEditing``showBlockRefCount` 的代码状态已经诚实标为 `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-1``showHeadingNumbers / embedDefaultBlockId` 的描述已经落后于当前代码,应在后续文档整理时更新。
3. 页面设置 planned 项保持降级展示,直到真正进入 island runtime 或被产品侧移除。
## 修改的文件
- `/mnt/Data1T/mnote/design/10-review/done/02-frontend-editor-tree-review.md`