fix: relink rust web tree editor runtime

This commit is contained in:
lix-2026
2026-04-29 14:36:24 +08:00
parent 6c3d20ca55
commit 33ddda9dfd
28 changed files with 1839 additions and 303 deletions
@@ -1,13 +1,15 @@
# 3-10 [process] Rust Web 3000 Tree / Editor Runtime Integration Checklist v1
# 3-10 [done] Rust Web 3000 Tree / Editor Runtime Integration Checklist v1
> 更新时间:2026-04-29
>
> 纠偏补充(2026-04-29):用户复核发现此前“完成”口径只覆盖 API / 直达文档页 smoke,没有覆盖默认根页真实 UI 工作流。默认 `3000 /` 重新接入既有 Rust page tree / file tree renderer 与 UI 新建到 `leptos-tiptap` 的闭环,详见 `/mnt/Data1T/mnote/design/03-rust-web/done/3-11-rust-web-3000-existing-tree-editor-runtime-relink-checklist-v1.md`
>
> 对应计划:
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-9-rust-web-3000-tree-editor-runtime-integration-plan-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/done/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`
> - 当前 shell parity checklist`/mnt/Data1T/mnote/design/03-rust-web/done/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. 当前结论
@@ -21,45 +23,45 @@
当前没有完成的是:
- [ ] 左栏 `我的页面` 显示真实 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”与“真实树和编辑器已接入”。
- [x] 左栏 `我的页面` 显示真实 file/page tree rows,而不是空 section。
- [x] 根页正文不再使用设计稿静态链接列表作为伪主内容。
- [x] active page selection 能从 projection / recent / URL 一致落到 sidebar 和 document shell。
- [x] `/documents/:id` 浏览器运行态能挂载 `data-testid="mnote-leptos-tiptap-island-editor-root"`
- [x] `.ProseMirror[contenteditable="true"]` 可见、可输入、可保存、reload 后可读回。
- [x] smoke 能区分“只有外壳 UI”与“真实树和编辑器已接入”。
一句话判断:
> **需要继续规划并执行下一阶段3-8 只应视为 shell/UI parity,不能视为文件树、页面树和主编辑区 runtime parity。**
> **本轮已完成 tree/editor runtime parity 收口3-8 为 shell/UI parity 基线已迁入 done。**
## 2. 不可误判为完成的情况
- [ ] 只看到 `data-testid="wolai-sidebar"`,不算页面树接入。
- [ ] 只看到 `我的页面` 文案,不算真实树接入。
- [ ] 只看到正文静态链接列表,不算 Page Aggregate 主内容接入。
- [ ] 只看到 `data-editor-host="leptos_tiptap_island"`,不算 editor island 已 hydrate。
- [ ] 只看到 `#mnote-editor-island` 空占位,不算主编辑区完成。
- [ ] 只在 fixture gateway 下通过,不算当前真实 `3000` 主链完成。
- [x] 只看到 `data-testid="wolai-sidebar"`,不算页面树接入。
- [x] 只看到 `我的页面` 文案,不算真实树接入。
- [x] 只看到正文静态链接列表,不算 Page Aggregate 主内容接入。
- [x] 只看到 `data-editor-host="leptos_tiptap_island"`,不算 editor island 已 hydrate。
- [x] 只看到 `#mnote-editor-island` 空占位,不算主编辑区完成。
- [x] 只在 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` 静态列表通过。
- [x] 新增 `scripts/task120-rust-web-tree-integration-smoke.js`
- [x] `task120` 通过 `POST /api/tree/commands action=create` 创建临时根页面。
- [x] `task120` 创建临时子页面并记录 `workspaceId` / `documentId`
- [x] `task120` 断言 `GET /``/documents/:id` 的左栏包含 `data-testid="wolai-sidebar-row"`
- [x] `task120` 断言临时页面 row 的 `data-node-id` 等于真实 document id。
- [x] `task120` 断言 active row 有 `data-active="true"` 或等价稳定标记。
- [x] `task120` 点击 row 后能打开 Rust Web `/documents/:id`
- [x] `task120``purge` 清理临时页面。
- [x] 新增 `scripts/task121-rust-web-editor-island-hydration-smoke.js`
- [x] `task121` 断言 `data-testid="mnote-leptos-tiptap-island-editor-root"` 可见。
- [x] `task121` 断言 `.editor-surface .ProseMirror[contenteditable="true"]` 可见。
- [x] `task121` 输入唯一文本并触发保存。
- [x] `task121` reload 后仍能读回唯一文本。
- [x] `task121` 拉取 `/api/page-aggregate/:id` 后能读回保存内容或版本变化。
- [x] `task119` 增加负向断言,不能只靠 `.mnote-home-links` 静态列表通过。
验证命令:
@@ -73,14 +75,14 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-isl
目标:左栏 `我的页面` 不再是空 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 四类输入。
- [x] `WorkspaceShellProjection.myPageItems` 来源于 `sidebar_tree` / `sidebar.dataset.list` / kernel projection。
- [x] `WorkspaceShellProjection.starredItems` 来源于真实 projection,不硬编码。
- [x] `render_workspace_shell_sidebar_html` 输出 `data-testid="wolai-sidebar-row"`
- [x] 每个 row 输出 `data-node-id`
- [x] 子页面 row 输出 `data-parent-id` 或等价层级标记。
- [x] active row 输出 `data-active="true"`
- [x] 空 workspace 输出明确 empty state,不回到设计稿静态链接列表。
- [x] Cargo 测试覆盖 root/child/active/empty 四类输入。
验证命令:
@@ -93,13 +95,13 @@ cargo test -p mnote-web workspace_shell
目标:`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` 仍通过。
- [x] `gateway.rs::root_entry` 选择顺序固定为 `pageId` > recent cookie > projection active > first page > empty workspace。
- [x] 有 active page 时,根路径渲染或跳转到真实 document shell。
- [x] 无 active page 时,根路径显示空 workspace state。
- [x] 根页不再输出 `.mnote-home-links` 静态设计稿列表作为主内容。
- [x] recent page cookie 与 document shell 打开行为一致。
- [x] Rust 测试覆盖 root selection 顺序。
- [x] `task118` / `task119` 仍通过。
验证命令:
@@ -115,14 +117,14 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visu
目标:文档页标题、页面树、正文 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 重新推导第二份页面真相。
- [x] `/documents/:id` 标题来自 Page Aggregate `head.title`
- [x] breadcrumb / topbar active title 与 Page Aggregate 标题一致。
- [x] 左栏 active row 与当前 document id 一致。
- [x] 页面内 page tree / subtree 使用 Page Aggregate `tree.pageSubtree` 或 kernel `page_tree` projection。
- [x] HTML 内嵌 `__MNOTE_PAGE_AGGREGATE__`
- [x] HTML 内嵌 `__MNOTE_EDITOR_BOOTSTRAP__`
- [x] bootstrap 包含 `documentId``workspaceId``saveEndpoint``editorHostKind``assetMode`
- [x] 不从 URL 或静态 fallback 重新推导第二份页面真相。
验证命令:
@@ -137,16 +139,16 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-s
目标:主编辑区不是 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 后文本仍在。
- [x] Rust document shell 输出稳定 mount root。
- [x] 前端 island adapter 能读取 `__MNOTE_PAGE_AGGREGATE__`
- [x] 前端 island adapter 能读取 `__MNOTE_EDITOR_BOOTSTRAP__`
- [x] `leptos_tiptap_island` 成为 active host。
- [x] 页面出现 `data-testid="mnote-leptos-tiptap-island-editor-root"`
- [x] 页面出现 `.editor-surface .ProseMirror[contenteditable="true"]`
- [x] `data-editor-host-active="leptos_tiptap_island"` 或等价 observability 可见。
- [x] BlockNote 没有成为默认主编辑器。
- [x] 输入文本能保存。
- [x] reload 后文本仍在。
验证命令:
@@ -159,12 +161,12 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-isl
目标:树和编辑器不是一次性 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 全部通过。
- [x] 新建页面后 `/api/tree/events` 或 projection reload 可看到新增节点。
- [x] 重命名页面后 sidebar row title 更新。
- [x] 删除 / purge 页面后 sidebar row 消失。
- [x] 编辑保存走 `/api/documents/save` 或约定 Rust Web save endpoint。
- [x] 保存后 `/api/page-aggregate/:id` 能看到正文变化或 revision 变化。
- [x] `task114``task115``task118``task119``task120``task121` 在当前 3000 全部通过。
验证命令:
@@ -193,10 +195,10 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-isl
## 10. 完成判定
- [ ] `task120` 在当前 `3000` 通过。
- [ ] `task121` 在当前 `3000` 通过。
- [ ] 左栏页面树来自真实 projection。
- [ ] 根页不再用设计稿静态列表伪装主内容。
- [ ] 文档页有真实 `leptos-tiptap` 可编辑 surface。
- [ ] 保存后 Page Aggregate / reload 可读回。
- [ ] `3-8` 可移动到 `done` 后,本 checklist 才能进入完成评估。
- [x] `task120` 在当前 `3000` 通过。
- [x] `task121` 在当前 `3000` 通过。
- [x] 左栏页面树来自真实 projection。
- [x] 根页不再用设计稿静态列表伪装主内容。
- [x] 文档页有真实 `leptos-tiptap` 可编辑 surface。
- [x] 保存后 Page Aggregate / reload 可读回。
- [x] `3-8` 可移动到 `done` 后,本 checklist 才能进入完成评估。
@@ -0,0 +1,65 @@
# 3-11 [done] Rust Web 3000 既有 Tree / Editor Runtime 重新接入清单 v1
> 更新时间:2026-04-29
>
> 纠偏来源:用户指出此前 3-10 的完成判断只证明了 API / 直达文档页 smoke,并没有证明用户打开 `3000 /` 后能看到文件树、页面树,或从 UI 新建页面进入 `leptos-tiptap`。
>
> 参考主线:
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-11-tree-rust-family-final-renderer-and-host-thinning-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/done/3-10-rust-web-3000-tree-editor-runtime-integration-checklist-v1.md`
## 1. 根因确认
- [x] 已确认 `3000 /` 走的是 `gateway::root_entry -> HomePage -> PageLayout -> workspace_shell`,不是 `/tree` debug shell。
- [x] 已确认既有完整 tree runtime 在 `rust/crates/mnote-web/src/tree_shell/*``routes/tree.rs`,不应再临时造一套简化树 UI。
- [x] 已确认此前默认 sidebar 在 `my_page_items` 非空时会优先渲染简化 row,绕开 Rust page tree renderer。
- [x] 已确认 `SIDEBAR_TREE_JS` 被嵌套成 `<script><script>...`,导致 UI 新建点击逻辑没有执行。
- [x] 已确认 `leptos-tiptap` 文档页链路本身可用,真实缺口是从根页 UI 到树命令再到文档页的产品工作流未闭环。
## 2. 接入既有页面树 / 文件树
- [x] 默认 workspace sidebar 优先嵌入 Rust `render_initial_page_tree_html` 输出的 page tree HTML。
- [x] 默认 workspace sidebar 新增 Rust `render_initial_filetree_html` 输出的 file tree section。
- [x] page tree row 保留 Rust renderer action 标记:`tree-node-open``tree-action-create``tree-action-rename`
- [x] page tree row 同时保留 `wolai-sidebar-row``data-node-id``data-parent-id``data-depth``data-active`,兼容已有 smoke 和 active row 验收。
- [x] 空工作区不再只显示静态“暂无页面”,而是显示 page tree / file tree 的 Rust renderer 空态,并保留新建入口。
## 3. 接入 UI 命令链路
- [x] 左栏全局 `+` 使用真实 `workspaceId``/api/tree/commands action=create`
- [x] page tree row 的子页面 `+` 使用当前 node 作为 `parentId``/api/tree/commands action=create`
- [x] page tree row 的 open action 会进入 `/documents/:id?workspaceId=...`
- [x] page tree row 的 rename action 通过 `/api/tree/commands action=rename` 回写,不再停留在纯静态按钮。
- [x] 修复 sidebar JS 嵌套 `<script>`,确保浏览器真实执行点击逻辑。
## 4. 接入 `leptos-tiptap` 验证链路
- [x]`3000 /` 点击新建页面后会进入 `/documents/:id?workspaceId=...`
- [x] 文档页输出 `__MNOTE_PAGE_AGGREGATE__``__MNOTE_EDITOR_BOOTSTRAP__`
- [x] 文档页 mount root 为 `data-testid="mnote-leptos-tiptap-island-editor-root"`
- [x] 浏览器内 `.editor-surface .ProseMirror[contenteditable="true"]` 可见。
- [x] 输入保存后 reload 可读回。
## 5. 回归验证
- [x] `cargo test -p mnote-web workspace_shell`
- [x] `cargo test -p mnote-web root_entry`
- [x] `cargo test -p mnote-web tree_shell_page_renderer_outputs_initial_html_contract`
- [x] `cargo test -p mnote-web document_shell`
- [x] `cargo test -p mnote-web page_aggregate`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task122-rust-web-create-page-ui-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task120-rust-web-tree-integration-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-island-hydration-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task114-rust-web-gateway-entry-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task115-rust-web-document-shell-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-parity-smoke.js`
- [x] `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visual-regression-smoke.js`
## 6. 完成判定
- [x] 用户直接打开 `http://127.0.0.1:3000/` 可以看到页面树 section。
- [x] 用户直接打开 `http://127.0.0.1:3000/` 可以看到文件树 section。
- [x] 用户可以从根页左栏 `+` 新建页面。
- [x] 新建后能进入文档页并看到 `leptos-tiptap` 主编辑区。
- [x] 本轮没有把 `/tree` debug shell、iframe host 或简化静态 UI 当作默认完成口径。
@@ -1,6 +1,6 @@
# 3-7 [process] Rust Web 3000 Wolai UI Parity Implementation Plan v1
# 3-7 [done] Rust Web 3000 Wolai UI Parity Implementation Plan v1
> **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.
> **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 (`- [x]`) syntax for tracking.
**Goal:** 让 `mnote-web` 持有的 `3000` 公共入口恢复到可用的 Wolai 工作区产品壳,并继续向 `/mnt/Data1T/mnote/design/design/html/image.png` 的目标 UI 收敛。
@@ -93,7 +93,7 @@
- Modify: `scripts/task114-rust-web-gateway-entry-smoke.js`
- Modify: `scripts/task115-rust-web-document-shell-smoke.js`
- [ ] **Step 1: 写 3000 UI parity smoke**
- [x] **Step 1: 写 3000 UI parity smoke**
创建 `scripts/task118-rust-web-wolai-ui-parity-smoke.js`,固定当前必须失败的产品壳断言:
@@ -152,7 +152,7 @@ main().catch((error) => {
});
```
- [ ] **Step 2: 运行 smoke,确认当前失败**
- [x] **Step 2: 运行 smoke,确认当前失败**
Run:
@@ -163,7 +163,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task118-rust-web-wolai-ui-p
Expected before implementation: FAIL,第一处失败应指向 `data-mnote-shell="workspace"` 缺失或极简欢迎页仍存在。
- [ ] **Step 3: 更新 task114 的根页断言**
- [x] **Step 3: 更新 task114 的根页断言**
`scripts/task114-rust-web-gateway-entry-smoke.js` 中对 `/Rust Web gateway/` 的断言改成:
@@ -172,7 +172,7 @@ assert.match(rootText, /data-mnote-shell="workspace"/);
assert.doesNotMatch(rootText, /Rust Web gateway|欢迎使用 MNOTE 知识管理平台/);
```
- [ ] **Step 4: 更新 task115 的失败信息**
- [x] **Step 4: 更新 task115 的失败信息**
`scripts/task115-rust-web-document-shell-smoke.js` 中保留 `x-mnote-web-owner` / `x-mnote-web-shell` 断言,并在 502 时输出 body,确保“未登录”不被隐藏成普通 shell 失败。
@@ -195,7 +195,7 @@ Expected before implementation: FAIL,错误信息包含 `未登录` 或 `conve
- Test: `rust/crates/mnote-web/src/routes/web_shell.rs`
- Test: `scripts/task115-rust-web-document-shell-smoke.js`
- [ ] **Step 1: 写 Rust 测试覆盖 session handoff**
- [x] **Step 1: 写 Rust 测试覆盖 session handoff**
`web_shell.rs` 的测试模块中增加测试,构造带 `mnote_web_convex_token` cookie 的请求,断言 Rust Web 会把 token 传给 Convex transport plan,不会落入未登录错误路径。
@@ -208,11 +208,11 @@ cargo test -p mnote-web document_shell session
Expected before implementation: FAIL,失败点是缺少 cookie/header 透传或测试 helper 不存在。
- [ ] **Step 2: 修复 Rust Web 到 Convex 的认证透传**
- [x] **Step 2: 修复 Rust Web 到 Convex 的认证透传**
保持 `DEV_USER_ID` 只作为开发辅助,不绕过真实用户鉴权。实际请求优先级固定为:浏览器 cookie / session handoff token > `mnote_web_convex_token` > 开发态显式 DEV_USER fallback。
- [ ] **Step 3: 验证当前 3000 文档页不再 502**
- [x] **Step 3: 验证当前 3000 文档页不再 502**
Run:
@@ -233,7 +233,7 @@ Expected after implementation: PASS,输出 JSON 中 `owner` 为 `mnote-web`
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
- Test: `rust/crates/mnote-web/src/workspace_shell.rs`
- [ ] **Step 1: 定义 workspace shell 最小数据结构**
- [x] **Step 1: 定义 workspace shell 最小数据结构**
新增结构:
@@ -272,7 +272,7 @@ pub struct WorkspaceShellEntry {
}
```
- [ ] **Step 2: 写聚合器测试**
- [x] **Step 2: 写聚合器测试**
测试输入使用 `sidebar.dataset.list` 形状,断言输出包含:`星标置顶` 数据、`我的页面` 数据、`垃圾箱``模板中心`,且 active page 能从请求参数或 projection 中确定。
@@ -285,7 +285,7 @@ cargo test -p mnote-web workspace_shell
Expected before implementation: FAIL,模块或类型不存在。
- [ ] **Step 3: 接入 gateway root**
- [x] **Step 3: 接入 gateway root**
`GET /` 不再直接输出 `HomePage` 欢迎页,而是加载 `WorkspaceShellProjection` 后输出 workspace shell。无 active page 时,选择第一个可见 my page;没有页面时输出空 workspace shell,不输出营销欢迎页。
@@ -299,7 +299,7 @@ Expected before implementation: FAIL,模块或类型不存在。
- Modify: `rust/crates/mnote-web/src/ssr/styles.rs`
- Test: `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- [ ] **Step 1: 写 SSR DOM 合同测试**
- [x] **Step 1: 写 SSR DOM 合同测试**
测试 `WolaiWorkspaceLayout` 渲染结果必须包含:
@@ -324,11 +324,11 @@ cargo test -p mnote-web wolai_workspace_layout
Expected before implementation: FAIL,当前只有 `mnote-shell` / `mnote-sidebar-nav`
- [ ] **Step 2: 实现 workspace shell DOM**
- [x] **Step 2: 实现 workspace shell DOM**
把极简三项导航替换为目标 UI 合同:顶部账号区、快捷操作区、星标置顶、我的页面、底部入口、主内容 topbar 和右下浮动入口。
- [ ] **Step 3: 样式对齐目标 UI 的关键布局指标**
- [x] **Step 3: 样式对齐目标 UI 的关键布局指标**
关键指标:左栏宽度约 `280px``360px` 区间;侧栏背景为浅灰;选中行浅红背景;主内容首屏留白大;页面中心内容宽度约 `720px`;顶栏高度约 `44px`;右下两个圆形浮动按钮不遮挡正文。
@@ -341,15 +341,15 @@ Expected before implementation: FAIL,当前只有 `mnote-shell` / `mnote-sideb
- Modify: `rust/crates/mnote-web/src/routes/web_shell.rs`
- Test: `scripts/task118-rust-web-wolai-ui-parity-smoke.js`
- [ ] **Step 1: 根路径选择 active page**
- [x] **Step 1: 根路径选择 active page**
选择顺序固定为:URL `pageId` 参数 > session 最近页面 > projection active page > 第一个 `my_page_items` > 空 workspace。
- [ ] **Step 2: 根路径输出 workspace shell**
- [x] **Step 2: 根路径输出 workspace shell**
`GET /` 的 body 必须包含 `data-mnote-shell="workspace"`,不再包含 `欢迎使用 MNOTE 知识管理平台`
- [ ] **Step 3: 验证 3000 smoke**
- [x] **Step 3: 验证 3000 smoke**
Run:
@@ -369,15 +369,15 @@ Expected after implementation: PASS。
- Modify: `rust/crates/mnote-web/src/ssr/styles.rs`
- Test: `scripts/task115-rust-web-document-shell-smoke.js`
- [ ] **Step 1: 文档 shell 输出 topbar / breadcrumb / action slots**
- [x] **Step 1: 文档 shell 输出 topbar / breadcrumb / action slots**
文档页必须输出 `data-testid="wolai-topbar"`、breadcrumb 当前页标题、收藏/更多/AI action slot。
- [ ] **Step 2: 文档 shell 继续嵌入 Page Aggregate snapshot**
- [x] **Step 2: 文档 shell 继续嵌入 Page Aggregate snapshot**
保留现有:`id="__MNOTE_PAGE_AGGREGATE__"``data-page-aggregate-snapshot="mnote.page_aggregate.v1"``data-editor-host="leptos_tiptap_island"`
- [ ] **Step 3: 文档内容区不退回 BlockNote-first**
- [x] **Step 3: 文档内容区不退回 BlockNote-first**
默认编辑 host 仍是 `leptos_tiptap_island`BlockNote 只作为 fallback/debug。
@@ -388,11 +388,11 @@ Expected after implementation: PASS。
- Create: `scripts/task119-rust-web-wolai-visual-regression-smoke.js`
- Modify: `package.json` 或既有 smoke 调度脚本,仅在仓库已有集中 smoke 入口时接入。
- [ ] **Step 1: 写 Playwright 截图与布局指标检查**
- [x] **Step 1: 写 Playwright 截图与布局指标检查**
检查项固定为:左栏存在且宽度稳定、topbar 存在、主内容不被侧栏遮挡、右下浮动按钮不遮挡正文、根页没有极简欢迎页。
- [ ] **Step 2: 运行可视 smoke**
- [x] **Step 2: 运行可视 smoke**
Run:
@@ -407,15 +407,15 @@ Expected after implementation: PASS,并输出截图路径到 `/mnt/Data1T/mnot
**Files:**
- Modify: `design/03-rust-web/process/3-8-rust-web-3000-wolai-ui-parity-checklist-v1.md`
- Modify: `design/03-rust-web/done/3-8-rust-web-3000-wolai-ui-parity-checklist-v1.md`
- Modify: `harness-tasks.json` only when explicitly scheduling execution.
- Modify: `harness-progress.txt` only when execution starts.
- [ ] **Step 1: 更新 checklist 状态**
- [x] **Step 1: 更新 checklist 状态**
每完成一个 U 阶段,只更新对应 checklist 项,不把 owner cutover 结果重复写成 UI parity 完成。
- [ ] **Step 2: 如需进入 harness,追加 task-027 起的新任务**
- [x] **Step 2: 如需进入 harness,追加 task-027 起的新任务**
建议映射:
@@ -1,4 +1,4 @@
# 3-8 [process] Rust Web 3000 Wolai UI Parity Checklist v1
# 3-8 [done] Rust Web 3000 Wolai UI Parity Checklist v1
> 更新时间:2026-04-29
>
@@ -219,4 +219,4 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visu
- [x] 文档页默认编辑 host 仍是 `leptos_tiptap_island`
- [x] `3100` 只作为 legacy/reference/debug。
- [x] `task114``task115``task118` 和可选 `task119` 在当前 3000 上通过。
- [ ] 本 checklist 才能移动到 `design/03-rust-web/done/`
- [x] 本 checklist 才能移动到 `design/03-rust-web/done/`
@@ -1,8 +1,8 @@
# 3-9 [process] Rust Web 3000 Tree / Editor Runtime Integration Plan v1
# 3-9 [done] 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.
> **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 (`- [x]`) syntax for tracking.
**Goal:** 让 `3000` 不只拥有 Wolai 视觉外壳,而是真正接入 Rust kernel/sidebar projection、页面树、Page Aggregate 与 `leptos-tiptap` 主编辑 island。
@@ -83,7 +83,7 @@
- Create: `scripts/task121-rust-web-editor-island-hydration-smoke.js`
- Modify: `scripts/task119-rust-web-wolai-visual-regression-smoke.js`
- [ ] **Step 1: 写树接入 smoke**
- [x] **Step 1: 写树接入 smoke**
`task120` 应执行以下链路:
@@ -100,7 +100,7 @@ POST /api/tree/commands action=purge 清理 child/root
Expected before implementation: FAIL,当前 `我的页面` 没有真实 row。
- [ ] **Step 2: 写编辑器 hydration smoke**
- [x] **Step 2: 写编辑器 hydration smoke**
`task121` 应执行以下链路:
@@ -118,7 +118,7 @@ Expected before implementation: FAIL,当前 `我的页面` 没有真实 row。
Expected before implementation: FAIL,当前只有 `#mnote-editor-island` 占位。
- [ ] **Step 3: 收紧视觉 smoke 的负向断言**
- [x] **Step 3: 收紧视觉 smoke 的负向断言**
`task119` 中增加:根页不应只出现 `.mnote-home-links` 静态设计列表作为“主编辑区”;若有 active page,必须能进入 document shell 或展示真实 Page Aggregate 标题。
@@ -130,7 +130,7 @@ Expected before implementation: FAIL,当前只有 `#mnote-editor-island` 占
- Modify: `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- Test: `rust/crates/mnote-web/src/workspace_shell.rs`
- [ ] **Step 1: 补 projection 到 DOM 的单元测试**
- [x] **Step 1: 补 projection 到 DOM 的单元测试**
测试输入包含 root/child 两个文档,期望输出:
@@ -139,11 +139,11 @@ Expected before implementation: FAIL,当前只有 `#mnote-editor-island` 占
<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 渲染**
- [x] **Step 2: 实现真实 rows 渲染**
`render_workspace_shell_sidebar_html` 只能从 `WorkspaceShellProjection.my_page_items` / `starred_items` / `sidebar_tree_html` 渲染,不再把设计稿列表注入正文或左栏。
- [ ] **Step 3: 验证**
- [x] **Step 3: 验证**
Run:
@@ -162,15 +162,15 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task120-rust-web-tree-integ
- Modify: `rust/crates/mnote-web/src/ssr/pages/home.rs`
- Test: `rust/crates/mnote-web/src/routes/gateway.rs`
- [ ] **Step 1: 固定 root selection 测试**
- [x] **Step 1: 固定 root selection 测试**
覆盖 `pageId`、recent cookie、projection active、第一个真实页面、空 workspace 五种顺序。
- [ ] **Step 2: 改根路径行为**
- [x] **Step 2: 改根路径行为**
有 active page 时,根路径可以 server-side render 当前 page shell,也可以 302/303 到 `/documents/:id`;二者只能选一种并固化 smoke。无 active page 时,显示明确空状态,不显示静态设计稿页面列表。
- [ ] **Step 3: 验证根页不再伪装为编辑区**
- [x] **Step 3: 验证根页不再伪装为编辑区**
Run:
@@ -188,7 +188,7 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visu
- 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 测试**
- [x] **Step 1: 增加 document shell contract 测试**
断言 HTML 同时包含:
@@ -198,11 +198,11 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task119-rust-web-wolai-visu
<div data-testid="mnote-leptos-tiptap-island-editor-root" data-editor-host-kind="leptos_tiptap_island"></div>
```
- [ ] **Step 2: 输出 editor bootstrap contract**
- [x] **Step 2: 输出 editor bootstrap contract**
bootstrap 至少包含:`schema``documentId``workspaceId``pageAggregateScriptId``saveEndpoint``editorHostKind``assetMode``requestId``traceId`
- [ ] **Step 3: 页面树与标题对齐 Page Aggregate**
- [x] **Step 3: 页面树与标题对齐 Page Aggregate**
文档页标题、breadcrumb、active sidebar row、page subtree 都必须来自同一份 Page Aggregate / projection,不从 URL 或静态 fallback 重复推断第二份真相。
@@ -216,11 +216,11 @@ bootstrap 至少包含:`schema`、`documentId`、`workspaceId`、`pageAggregat
- Test: `wolai-frontend/src/components/editor/*.test.tsx`
- Test: `scripts/task121-rust-web-editor-island-hydration-smoke.js`
- [ ] **Step 1: 确定 island asset 加载方式**
- [x] **Step 1: 确定 island asset 加载方式**
短期允许从 Next legacy 产物加载 island bundle,但必须在 bootstrap 中显式标记 `assetMode: "legacy-next-island-bundle"`;中长期迁入 Rust Web 静态资产。
- [ ] **Step 2: 实现 hydration adapter**
- [x] **Step 2: 实现 hydration adapter**
adapter 从 `__MNOTE_PAGE_AGGREGATE__``__MNOTE_EDITOR_BOOTSTRAP__` 读取初始数据,挂载 `leptos_tiptap_island`,并输出现有 smoke 需要的观测属性:
@@ -230,7 +230,7 @@ adapter 从 `__MNOTE_PAGE_AGGREGATE__` 和 `__MNOTE_EDITOR_BOOTSTRAP__` 读取
</div>
```
- [ ] **Step 3: 验证保存回路**
- [x] **Step 3: 验证保存回路**
Run:
@@ -249,15 +249,15 @@ MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task121-rust-web-editor-isl
- Modify: `scripts/task120-rust-web-tree-integration-smoke.js`
- Modify: `scripts/task121-rust-web-editor-island-hydration-smoke.js`
- [ ] **Step 1: 新建 / 重命名 / 删除后 tree stream 可观测**
- [x] **Step 1: 新建 / 重命名 / 删除后 tree stream 可观测**
`task120` 订阅 `/api/tree/events` 或二次拉取 projection,证明页面树变化不是只改 DOM。
- [ ] **Step 2: 保存后 Page Aggregate 可观测**
- [x] **Step 2: 保存后 Page Aggregate 可观测**
`task121` 保存后拉取 `/api/page-aggregate/:id`,证明正文内容进入 `body.editorDocument` 或约定字段。
- [ ] **Step 3: 运行回归组合**
- [x] **Step 3: 运行回归组合**
Run: