Files
mnote/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md
T

14 KiB
Raw Blame History

3-9 [process] Rust Web 3000 Tree / Editor Runtime Integration Plan v1

更新时间:2026-04-29

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal:3000 不只拥有 Wolai 视觉外壳,而是真正接入 Rust kernel/sidebar projection、页面树、Page Aggregate 与 leptos-tiptap 主编辑 island。

Architecture: mnote-web 继续持有 3000 gateway 和 server-first shell;树、页面结构、排序、选中态来自 Rust kernel projection / Page Aggregate,不在 UI 层重新造一份静态真相。编辑区由 Rust shell 输出稳定 bootstrap contract,前端只作为 leptos-tiptap island runtime 与必要 bundle source,不能把 Next App Router 恢复为主壳,也不能把 BlockNote 写回默认主编辑器。

Tech Stack: Rust workspace、mnote-web、Axum、Leptos SSR、Page Aggregate、Rust tree projection、Convex substrate、leptos-tiptap / ProseMirror island、Node/Playwright smoke、Cargo tests、Vitest。


1. 当前事实基线

本计划基于 2026-04-29 对 3000、截图 /mnt/Data1T/mnote/tmp/image copy 32.png 和当前 DOM 的检查:

  • 3000 当前已经由 mnote-web 持有,根页返回 x-mnote-web-owner: mnote-web
  • 根页已经有 Wolai 风格外壳:左栏、顶栏、右下浮动按钮、data-mnote-shell="workspace"
  • 左栏 我的页面 section 当前没有真实页面树行;页面列表出现在正文中,是 /mnt/Data1T/mnote/design/design/html 参考 UI 的静态内容形态。
  • 根页正文仍是 mnote-home-links 静态链接列表,不能证明 active page、Page Aggregate 或页面树选中态已经接入。
  • /documents/:id 当前只证明 SSR shell 包含 data-editor-host="leptos_tiptap_island"#mnote-editor-island 占位;还没有证明浏览器里出现 data-testid="mnote-leptos-tiptap-island-editor-root".ProseMirror[contenteditable="true"] 或保存回路。
  • scripts/task115-rust-web-document-shell-smoke.js 目前只检查 HTML marker 和 Page Aggregate JSON,不检查 editor island hydration。
  • scripts/task119-rust-web-wolai-visual-regression-smoke.js 目前只检查视觉布局,不检查真实树数据或编辑器运行态。

结论:3-7/3-8 完成的是 Rust-owned Wolai shell parity;下一阶段必须单独收口 tree/editor runtime parity

2. 边界原则

  • 3000 继续是唯一公开入口;3100 只作为 legacy/reference/debug。
  • 不把 Next App Router 恢复为主路由;如临时使用 Next 产物,只能作为显式 island bundle source。
  • 不在 Rust SSR 中硬编码“个人 / 正版软件备份 / 杂记”等设计稿页面列表作为真实数据。
  • Sidebar / 页面树 / active selection 必须来自 sidebar_treepage_tree 或 Page Aggregate 的稳定 projection。
  • 文档正文必须由 Page Aggregate 和 leptos-tiptap island 共同驱动;仅有空 <section id="mnote-editor-island"> 不算完成。
  • 保存、重命名、新建、删除、页面切换的 smoke 要使用当前 3000,不能只依赖 fixture-only 通过。

3. 文件结构

Rust Web shell 与 projection

  • rust/crates/mnote-web/src/workspace_shell.rs
    • 收紧 WorkspaceShellProjection:真实 myPageItemsstarredItems、active row、空状态和 fallback 原因都可观测。
  • rust/crates/mnote-web/src/routes/gateway.rs
    • 根路径选择 active pagepageId 查询参数 > recent cookie > projection active > 第一个真实页面 > 空 workspace。
    • 根路径不再展示静态设计稿页面列表作为伪内容。
  • rust/crates/mnote-web/src/routes/web_shell.rs
    • 统一加载 workspace sidebar、page tree、Page Aggregate 和 editor bootstrap contract。
  • rust/crates/mnote-web/src/ssr/pages/layout.rs
    • 侧栏只渲染 projection rows;补 data-testid="wolai-sidebar-row"data-node-iddata-active 等稳定验收点。
  • rust/crates/mnote-web/src/ssr/pages/home.rs
    • workspace home 只在没有 active page 时渲染空状态或真实页面入口,不再承担主编辑区。
  • rust/crates/mnote-web/src/ssr/pages/document.rs
    • 文档页输出 editor bootstrap、Page Aggregate snapshot、真实编辑器 mount root 和 fallback/error observability。

Editor island runtime

  • wolai-frontend/src/components/editor/editor-host.tsx
    • 继续保持 leptos_tiptap_island 为默认 host。
  • wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx
    • 作为 Rust shell 的 island runtime 入口复用现有初始化、保存与观测属性。
  • wolai-frontend/src/components/editor/document-content.tsx
    • 如需复用,只抽出最小 bootstrap/hydration adapter,不把完整 Next 文档页壳带回主路径。
  • wolai-frontend/src/lib/documents/page-aggregate-loader.ts / page-aggregate-builder.ts
    • 确认 Rust shell 内嵌 Page Aggregate 与前端类型一致。

Smoke / 验收

  • scripts/task120-rust-web-tree-integration-smoke.js
    • 新增真实树接入 smoke:在 3000 创建临时页面和子页面,断言左栏 / 页面树出现真实 rows、active selection、点击打开、清理成功。
  • scripts/task121-rust-web-editor-island-hydration-smoke.js
    • 新增编辑器接入 smoke:打开当前 3000 文档页,断言 leptos-tiptap root、.ProseMirror[contenteditable="true"]、Page Aggregate bootstrap、输入保存和 reload 后内容一致。
  • scripts/task119-rust-web-wolai-visual-regression-smoke.js
    • 保留视觉布局检查,增加负向断言:不能只靠 .mnote-home-links 静态设计列表通过。

4. 实施任务

Task E-0:冻结真实树和编辑器失败用例

Files:

  • Create: scripts/task120-rust-web-tree-integration-smoke.js

  • Create: scripts/task121-rust-web-editor-island-hydration-smoke.js

  • Modify: scripts/task119-rust-web-wolai-visual-regression-smoke.js

  • Step 1: 写树接入 smoke

task120 应执行以下链路:

POST /api/tree/commands action=create title=task120-root
POST /api/tree/commands action=create parentId=<root> title=task120-child
GET /
断言 [data-testid="wolai-sidebar-row"][data-node-id=<root>] 存在
断言 [data-testid="wolai-sidebar-row"][data-node-id=<child>] 存在或可展开后存在
点击 root row,断言 URL 进入 /documents/<root>
断言 active row 的 data-active="true"
POST /api/tree/commands action=purge 清理 child/root

Expected before implementation: FAIL,当前 我的页面 没有真实 row。

  • Step 2: 写编辑器 hydration smoke

task121 应执行以下链路:

创建临时文档
打开 /documents/<id>?workspaceId=<workspace>
等待 [data-testid="mnote-leptos-tiptap-island-editor-root"]
等待 .editor-surface .ProseMirror[contenteditable="true"]
输入唯一文本
触发保存或等待自动保存
重新加载同一页面
断言 ProseMirror 或 Page Aggregate body 含唯一文本
清理临时文档

Expected before implementation: FAIL,当前只有 #mnote-editor-island 占位。

  • Step 3: 收紧视觉 smoke 的负向断言

task119 中增加:根页不应只出现 .mnote-home-links 静态设计列表作为“主编辑区”;若有 active page,必须能进入 document shell 或展示真实 Page Aggregate 标题。

Task E-1:让 workspace sidebar 渲染真实 projection rows

Files:

  • Modify: rust/crates/mnote-web/src/workspace_shell.rs

  • Modify: rust/crates/mnote-web/src/ssr/pages/layout.rs

  • Test: rust/crates/mnote-web/src/workspace_shell.rs

  • Step 1: 补 projection 到 DOM 的单元测试

测试输入包含 root/child 两个文档,期望输出:

<a data-testid="wolai-sidebar-row" data-node-id="doc_root" data-active="true" href="/documents/doc_root?workspaceId=ws_demo">Root</a>
<a data-testid="wolai-sidebar-row" data-node-id="doc_child" data-parent-id="doc_root" href="/documents/doc_child?workspaceId=ws_demo">Child</a>
  • Step 2: 实现真实 rows 渲染

render_workspace_shell_sidebar_html 只能从 WorkspaceShellProjection.my_page_items / starred_items / sidebar_tree_html 渲染,不再把设计稿列表注入正文或左栏。

  • Step 3: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web workspace_shell
cd /mnt/Data1T/mnote
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task120-rust-web-tree-integration-smoke.js

Task E-2:把根路径从静态首页切到 active page / empty workspace

Files:

  • Modify: rust/crates/mnote-web/src/routes/gateway.rs

  • Modify: rust/crates/mnote-web/src/ssr/pages/home.rs

  • Test: rust/crates/mnote-web/src/routes/gateway.rs

  • Step 1: 固定 root selection 测试

覆盖 pageId、recent cookie、projection active、第一个真实页面、空 workspace 五种顺序。

  • Step 2: 改根路径行为

有 active page 时,根路径可以 server-side render 当前 page shell,也可以 302/303 到 /documents/:id;二者只能选一种并固化 smoke。无 active page 时,显示明确空状态,不显示静态设计稿页面列表。

  • Step 3: 验证根页不再伪装为编辑区

Run:

cd /mnt/Data1T/mnote
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-parity-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visual-regression-smoke.js

Task E-3:把 document shell 接上真实 Page Aggregate 与 page tree

Files:

  • Modify: rust/crates/mnote-web/src/routes/web_shell.rs

  • Modify: rust/crates/mnote-web/src/ssr/pages/document.rs

  • Test: rust/crates/mnote-web/src/routes/web_shell.rs

  • Step 1: 增加 document shell contract 测试

断言 HTML 同时包含:

<script id="__MNOTE_PAGE_AGGREGATE__" type="application/json">...</script>
<script id="__MNOTE_EDITOR_BOOTSTRAP__" type="application/json">...</script>
<div data-testid="mnote-leptos-tiptap-island-editor-root" data-editor-host-kind="leptos_tiptap_island"></div>
  • Step 2: 输出 editor bootstrap contract

bootstrap 至少包含:schemadocumentIdworkspaceIdpageAggregateScriptIdsaveEndpointeditorHostKindassetModerequestIdtraceId

  • Step 3: 页面树与标题对齐 Page Aggregate

文档页标题、breadcrumb、active sidebar row、page subtree 都必须来自同一份 Page Aggregate / projection,不从 URL 或静态 fallback 重复推断第二份真相。

Task E-4:挂载 leptos-tiptap island 并证明可编辑

Files:

  • Modify: rust/crates/mnote-web/src/ssr/pages/document.rs

  • Modify: wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx

  • Modify: wolai-frontend/src/components/editor/editor-host.tsx

  • Test: wolai-frontend/src/components/editor/*.test.tsx

  • Test: scripts/task121-rust-web-editor-island-hydration-smoke.js

  • Step 1: 确定 island asset 加载方式

短期允许从 Next legacy 产物加载 island bundle,但必须在 bootstrap 中显式标记 assetMode: "legacy-next-island-bundle";中长期迁入 Rust Web 静态资产。

  • Step 2: 实现 hydration adapter

adapter 从 __MNOTE_PAGE_AGGREGATE____MNOTE_EDITOR_BOOTSTRAP__ 读取初始数据,挂载 leptos_tiptap_island,并输出现有 smoke 需要的观测属性:

<div data-testid="mnote-leptos-tiptap-island-editor-root" data-editor-host-kind="leptos_tiptap_island">
  <div class="editor-surface"><div class="ProseMirror" contenteditable="true"></div></div>
</div>
  • Step 3: 验证保存回路

Run:

cd /mnt/Data1T/mnote
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-island-hydration-smoke.js

Task E-5:树事件和编辑保存联动回归

Files:

  • Modify: rust/crates/mnote-web/src/routes/sse.rs

  • Modify: rust/crates/mnote-web/src/routes/tree.rs

  • Modify: rust/crates/mnote-web/src/routes/documents.rs

  • Modify: scripts/task120-rust-web-tree-integration-smoke.js

  • Modify: scripts/task121-rust-web-editor-island-hydration-smoke.js

  • Step 1: 新建 / 重命名 / 删除后 tree stream 可观测

task120 订阅 /api/tree/events 或二次拉取 projection,证明页面树变化不是只改 DOM。

  • Step 2: 保存后 Page Aggregate 可观测

task121 保存后拉取 /api/page-aggregate/:id,证明正文内容进入 body.editorDocument 或约定字段。

  • Step 3: 运行回归组合

Run:

cd /mnt/Data1T/mnote
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task114-rust-web-gateway-entry-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-parity-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visual-regression-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task120-rust-web-tree-integration-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-island-hydration-smoke.js

5. 完成判定

  • 3000 根页不再用静态设计稿页面列表伪装真实页面树。
  • 左栏 我的页面 能显示当前 Convex/Rust projection 中真实页面,并能反映 active selection。
  • 创建临时页面后,当前 3000 左栏 / page tree 能看到该页面;清理后消失。
  • /documents/:id 在浏览器里能看到 leptos-tiptap island 根节点和可编辑 ProseMirror surface。
  • 输入文本保存后,reload 和 /api/page-aggregate/:id 都能读回。
  • task120task121 通过,并与 task114/115/118/119 一起形成 3000 主链验收。

6. Harness 映射建议

task-027task-033 已被 3-8 建议预留给 shell UI parity。若进入本计划执行,建议从 task-034 继续追加,不修改旧 completed 任务:

新任务 Phase 标题 优先级
task-034 E-0 冻结真实树和编辑器 hydration 失败用例 P0
task-035 E-1 让 workspace sidebar 渲染真实 projection rows P0
task-036 E-2 把根路径从静态首页切到 active page / empty workspace P0
task-037 E-3 把 document shell 接上真实 Page Aggregate 与 page tree P0
task-038 E-4 挂载 leptos-tiptap island 并证明可编辑 P0
task-039 E-5 树事件和编辑保存联动回归 P1

本计划建立阶段不直接修改 harness-tasks.json