From 6c3d20ca556eeb39ccb32f7df021cd0101b14579 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Wed, 29 Apr 2026 12:28:36 +0800 Subject: [PATCH] docs: plan rust web tree editor integration --- ...editor-runtime-integration-checklist-v1.md | 202 ++++++++++++ ...tree-editor-runtime-integration-plan-v1.md | 296 ++++++++++++++++++ 2 files changed, 498 insertions(+) create mode 100644 design/03-rust-web/process/3-10-rust-web-3000-tree-editor-runtime-integration-checklist-v1.md create mode 100644 design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md diff --git a/design/03-rust-web/process/3-10-rust-web-3000-tree-editor-runtime-integration-checklist-v1.md b/design/03-rust-web/process/3-10-rust-web-3000-tree-editor-runtime-integration-checklist-v1.md new file mode 100644 index 00000000..07b4100f --- /dev/null +++ b/design/03-rust-web/process/3-10-rust-web-3000-tree-editor-runtime-integration-checklist-v1.md @@ -0,0 +1,202 @@ +# 3-10 [process] Rust Web 3000 Tree / Editor Runtime Integration Checklist v1 + +> 更新时间:2026-04-29 +> +> 对应计划: +> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md` +> +> 参考输入: +> - 当前 3000 截图:`/mnt/Data1T/mnote/tmp/image copy 32.png` +> - 当前 shell parity checklist:`/mnt/Data1T/mnote/design/03-rust-web/process/3-8-rust-web-3000-wolai-ui-parity-checklist-v1.md` +> - Page Aggregate 对齐主线:`/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` + +## 1. 当前结论 + +当前已经完成的是: + +- [x] `3000` 默认由 `mnote-web` 监听。 +- [x] `3000` 根页已经有 Wolai 风格 workspace shell。 +- [x] 根页包含左栏、顶栏、右下浮动按钮和 `data-mnote-shell="workspace"`。 +- [x] `/documents/:id` 已有 Rust Web document shell、Page Aggregate marker 和 `data-editor-host="leptos_tiptap_island"` marker。 + +当前没有完成的是: + +- [ ] 左栏 `我的页面` 显示真实 file/page tree rows,而不是空 section。 +- [ ] 根页正文不再使用设计稿静态链接列表作为伪主内容。 +- [ ] active page selection 能从 projection / recent / URL 一致落到 sidebar 和 document shell。 +- [ ] `/documents/:id` 浏览器运行态能挂载 `data-testid="mnote-leptos-tiptap-island-editor-root"`。 +- [ ] `.ProseMirror[contenteditable="true"]` 可见、可输入、可保存、reload 后可读回。 +- [ ] smoke 能区分“只有外壳 UI”与“真实树和编辑器已接入”。 + +一句话判断: + +> **需要继续规划并执行下一阶段;3-8 只应视为 shell/UI parity,不能视为文件树、页面树和主编辑区 runtime parity。** + +## 2. 不可误判为完成的情况 + +- [ ] 只看到 `data-testid="wolai-sidebar"`,不算页面树接入。 +- [ ] 只看到 `我的页面` 文案,不算真实树接入。 +- [ ] 只看到正文静态链接列表,不算 Page Aggregate 主内容接入。 +- [ ] 只看到 `data-editor-host="leptos_tiptap_island"`,不算 editor island 已 hydrate。 +- [ ] 只看到 `#mnote-editor-island` 空占位,不算主编辑区完成。 +- [ ] 只在 fixture gateway 下通过,不算当前真实 `3000` 主链完成。 + +## 3. Phase E-0:冻结真实树和编辑器失败用例 + +目标:让当前截图暴露的问题可以自动失败。 + +- [ ] 新增 `scripts/task120-rust-web-tree-integration-smoke.js`。 +- [ ] `task120` 通过 `POST /api/tree/commands action=create` 创建临时根页面。 +- [ ] `task120` 创建临时子页面并记录 `workspaceId` / `documentId`。 +- [ ] `task120` 断言 `GET /` 或 `/documents/:id` 的左栏包含 `data-testid="wolai-sidebar-row"`。 +- [ ] `task120` 断言临时页面 row 的 `data-node-id` 等于真实 document id。 +- [ ] `task120` 断言 active row 有 `data-active="true"` 或等价稳定标记。 +- [ ] `task120` 点击 row 后能打开 Rust Web `/documents/:id`。 +- [ ] `task120` 用 `purge` 清理临时页面。 +- [ ] 新增 `scripts/task121-rust-web-editor-island-hydration-smoke.js`。 +- [ ] `task121` 断言 `data-testid="mnote-leptos-tiptap-island-editor-root"` 可见。 +- [ ] `task121` 断言 `.editor-surface .ProseMirror[contenteditable="true"]` 可见。 +- [ ] `task121` 输入唯一文本并触发保存。 +- [ ] `task121` reload 后仍能读回唯一文本。 +- [ ] `task121` 拉取 `/api/page-aggregate/:id` 后能读回保存内容或版本变化。 +- [ ] `task119` 增加负向断言,不能只靠 `.mnote-home-links` 静态列表通过。 + +验证命令: + +```bash +cd /mnt/Data1T/mnote +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 +``` + +## 4. Phase E-1:workspace sidebar 接入真实 projection rows + +目标:左栏 `我的页面` 不再是空 section,而是 projection 驱动的真实页面树。 + +- [ ] `WorkspaceShellProjection.myPageItems` 来源于 `sidebar_tree` / `sidebar.dataset.list` / kernel projection。 +- [ ] `WorkspaceShellProjection.starredItems` 来源于真实 projection,不硬编码。 +- [ ] `render_workspace_shell_sidebar_html` 输出 `data-testid="wolai-sidebar-row"`。 +- [ ] 每个 row 输出 `data-node-id`。 +- [ ] 子页面 row 输出 `data-parent-id` 或等价层级标记。 +- [ ] active row 输出 `data-active="true"`。 +- [ ] 空 workspace 输出明确 empty state,不回到设计稿静态链接列表。 +- [ ] Cargo 测试覆盖 root/child/active/empty 四类输入。 + +验证命令: + +```bash +cd /mnt/Data1T/mnote/rust +cargo test -p mnote-web workspace_shell +``` + +## 5. Phase E-2:根路径接入 active page / empty workspace + +目标:`GET /` 选择真实 active page 或显示真实空 workspace,不再把设计稿静态首页当主编辑区。 + +- [ ] `gateway.rs::root_entry` 选择顺序固定为 `pageId` > recent cookie > projection active > first page > empty workspace。 +- [ ] 有 active page 时,根路径渲染或跳转到真实 document shell。 +- [ ] 无 active page 时,根路径显示空 workspace state。 +- [ ] 根页不再输出 `.mnote-home-links` 静态设计稿列表作为主内容。 +- [ ] recent page cookie 与 document shell 打开行为一致。 +- [ ] Rust 测试覆盖 root selection 顺序。 +- [ ] `task118` / `task119` 仍通过。 + +验证命令: + +```bash +cd /mnt/Data1T/mnote/rust +cargo test -p mnote-web root_entry +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 +``` + +## 6. Phase E-3:document shell 接上真实 Page Aggregate 与 page tree + +目标:文档页标题、页面树、正文 bootstrap 来自同一份 Page Aggregate / projection。 + +- [ ] `/documents/:id` 标题来自 Page Aggregate `head.title`。 +- [ ] breadcrumb / topbar active title 与 Page Aggregate 标题一致。 +- [ ] 左栏 active row 与当前 document id 一致。 +- [ ] 页面内 page tree / subtree 使用 Page Aggregate `tree.pageSubtree` 或 kernel `page_tree` projection。 +- [ ] HTML 内嵌 `__MNOTE_PAGE_AGGREGATE__`。 +- [ ] HTML 内嵌 `__MNOTE_EDITOR_BOOTSTRAP__`。 +- [ ] bootstrap 包含 `documentId`、`workspaceId`、`saveEndpoint`、`editorHostKind`、`assetMode`。 +- [ ] 不从 URL 或静态 fallback 重新推导第二份页面真相。 + +验证命令: + +```bash +cd /mnt/Data1T/mnote/rust +cargo test -p mnote-web document_shell page_aggregate +cd /mnt/Data1T/mnote +MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js +``` + +## 7. Phase E-4:挂载 `leptos-tiptap` island 并证明可编辑 + +目标:主编辑区不是 marker,而是浏览器内真实可编辑 runtime。 + +- [ ] Rust document shell 输出稳定 mount root。 +- [ ] 前端 island adapter 能读取 `__MNOTE_PAGE_AGGREGATE__`。 +- [ ] 前端 island adapter 能读取 `__MNOTE_EDITOR_BOOTSTRAP__`。 +- [ ] `leptos_tiptap_island` 成为 active host。 +- [ ] 页面出现 `data-testid="mnote-leptos-tiptap-island-editor-root"`。 +- [ ] 页面出现 `.editor-surface .ProseMirror[contenteditable="true"]`。 +- [ ] `data-editor-host-active="leptos_tiptap_island"` 或等价 observability 可见。 +- [ ] BlockNote 没有成为默认主编辑器。 +- [ ] 输入文本能保存。 +- [ ] reload 后文本仍在。 + +验证命令: + +```bash +cd /mnt/Data1T/mnote +MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-island-hydration-smoke.js +``` + +## 8. Phase E-5:树事件与编辑保存联动回归 + +目标:树和编辑器不是一次性 SSR,而是接入当前 Rust Web transport / realtime 主链。 + +- [ ] 新建页面后 `/api/tree/events` 或 projection reload 可看到新增节点。 +- [ ] 重命名页面后 sidebar row title 更新。 +- [ ] 删除 / purge 页面后 sidebar row 消失。 +- [ ] 编辑保存走 `/api/documents/save` 或约定 Rust Web save endpoint。 +- [ ] 保存后 `/api/page-aggregate/:id` 能看到正文变化或 revision 变化。 +- [ ] `task114`、`task115`、`task118`、`task119`、`task120`、`task121` 在当前 3000 全部通过。 + +验证命令: + +```bash +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 +``` + +## 9. Harness 映射建议 + +本 checklist 建立阶段不直接修改 `harness-tasks.json`。若进入执行,建议追加: + +| 新任务 | 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 | + +## 10. 完成判定 + +- [ ] `task120` 在当前 `3000` 通过。 +- [ ] `task121` 在当前 `3000` 通过。 +- [ ] 左栏页面树来自真实 projection。 +- [ ] 根页不再用设计稿静态列表伪装主内容。 +- [ ] 文档页有真实 `leptos-tiptap` 可编辑 surface。 +- [ ] 保存后 Page Aggregate / reload 可读回。 +- [ ] `3-8` 可移动到 `done` 后,本 checklist 才能进入完成评估。 diff --git a/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md b/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md new file mode 100644 index 00000000..33ac801e --- /dev/null +++ b/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md @@ -0,0 +1,296 @@ +# 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_tree`、`page_tree` 或 Page Aggregate 的稳定 projection。 +- 文档正文必须由 Page Aggregate 和 `leptos-tiptap` island 共同驱动;仅有空 `
` 不算完成。 +- 保存、重命名、新建、删除、页面切换的 smoke 要使用当前 `3000`,不能只依赖 fixture-only 通过。 + +## 3. 文件结构 + +### Rust Web shell 与 projection + +- `rust/crates/mnote-web/src/workspace_shell.rs` + - 收紧 `WorkspaceShellProjection`:真实 `myPageItems`、`starredItems`、active row、空状态和 fallback 原因都可观测。 +- `rust/crates/mnote-web/src/routes/gateway.rs` + - 根路径选择 active page:`pageId` 查询参数 > 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-id`、`data-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` 应执行以下链路: + +```text +POST /api/tree/commands action=create title=task120-root +POST /api/tree/commands action=create parentId= title=task120-child +GET / +断言 [data-testid="wolai-sidebar-row"][data-node-id=] 存在 +断言 [data-testid="wolai-sidebar-row"][data-node-id=] 存在或可展开后存在 +点击 root row,断言 URL 进入 /documents/ +断言 active row 的 data-active="true" +POST /api/tree/commands action=purge 清理 child/root +``` + +Expected before implementation: FAIL,当前 `我的页面` 没有真实 row。 + +- [ ] **Step 2: 写编辑器 hydration smoke** + +`task121` 应执行以下链路: + +```text +创建临时文档 +打开 /documents/?workspaceId= +等待 [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 两个文档,期望输出: + +```html +Root +Child +``` + +- [ ] **Step 2: 实现真实 rows 渲染** + +`render_workspace_shell_sidebar_html` 只能从 `WorkspaceShellProjection.my_page_items` / `starred_items` / `sidebar_tree_html` 渲染,不再把设计稿列表注入正文或左栏。 + +- [ ] **Step 3: 验证** + +Run: + +```bash +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: + +```bash +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 同时包含: + +```html + + +
+``` + +- [ ] **Step 2: 输出 editor bootstrap contract** + +bootstrap 至少包含:`schema`、`documentId`、`workspaceId`、`pageAggregateScriptId`、`saveEndpoint`、`editorHostKind`、`assetMode`、`requestId`、`traceId`。 + +- [ ] **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 需要的观测属性: + +```html +
+
+
+``` + +- [ ] **Step 3: 验证保存回路** + +Run: + +```bash +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: + +```bash +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` 都能读回。 +- `task120` 和 `task121` 通过,并与 `task114/115/118/119` 一起形成 3000 主链验收。 + +## 6. Harness 映射建议 + +`task-027` 到 `task-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`。