docs(design): 归档 10-review 到 done

将 10-review 的 01-06 文档统一迁入 done/,根目录 README 收缩为索引。

补充各历史审查稿的归档说明,并把 06 的活跃引用改到 done/ 下,避免后续重复把已完成审查当成当前待办。
This commit is contained in:
lix-2026
2026-05-14 16:30:13 +08:00
parent 9816035491
commit d1d19f755c
7 changed files with 38 additions and 54 deletions
@@ -0,0 +1,121 @@
# Rust Kernel / Web 实现偏差审查
> 执行状态:已归档到 `design/10-review/done/`。本文保留为历史审查快照,当前活跃口径以 `./06-execution-checklist-and-acceptance.md` 为准。
## 范围
本次只审查 Rust kernel / protocol / bridge-runtime / mnote-web / storage-convex-bridge 与当前设计主线的一致性。
重点对照设计:
- `ARCHITECTURE.md`
- `design/01-05-current-priority-overview.md`
- `design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
- `design/01-tree-first-graph-kernel/process/1-1-tree-first-graph-kernel-checklist-v2.md`
- `design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
- `design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
- `design/03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md`
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
- `design/03-rust-web/process/3-15-runtime-fallback-retirement-checklist-v1.md`
- `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
重点代码:
- `rust/crates/core-protocol/`
- `rust/crates/core-domain/`
- `rust/crates/bridge-runtime/`
- `rust/crates/mnote-web/`
- `rust/crates/storage-convex-bridge/`
## 结论
Rust kernel / protocol / bridge-runtime / mnote-web 的主干方向与当前设计基本一致:`core-protocol` 已有统一 kernel node / edge / projection / page aggregate 协议;`bridge-runtime` 已能执行 kernel query / command / projection`mnote-web` 已持有 3000 主入口、文档页 shell、`/api/page-aggregate/:id`、tree command、`/api/tree/events`、Search shell 等关键接缝;`storage-convex-bridge` 明确把 `tree.*` / `page.*` 等语义命令映射到底层 Convex mutation。
主要偏差不是“Rust 主线没有落地”,而是当前仍存在几类收口缺口:
1. 部分 Rust Web 主路径仍会静默合成 fallback / fixture 数据,和 `Runtime Fallback 退场` 的激进口径不完全一致。
2. Page Aggregate 对外标记为 `KernelProjection`,但 Rust Web 仍先取 `documents.meta/content` 再在 runtime 内拼成聚合;这是合理过渡实现,但“单一 kernel 真源”口径容易被写得过满。
3. Tree realtime 已有正式 SSE 主链,但实现仍偏轮询 bridge overviewWS 仍是 snapshot/resync 骨架,尚未成为完整实时主链。
4. Legacy / compat 能力默认大多关闭,但配置位、proxy 辅助函数和兼容源枚举仍在,设计文档中“全部退场”的勾选状态可能偏乐观。
## 关键发现表格
| 编号 | 分类 | 严重度 | 发现 | 证据 |
| --- | --- | --- | --- | --- |
| RK-01 | 未完成 | 中 | Kernel projection 仍主要从 `sidebar.dataset.list` 这一份 Convex 数据集构建,广义 node pool / reference edge / summary/index 真相层尚未完全独立。 | `rust/crates/mnote-web/src/routes/snapshot_support.rs:27` 定义 `sidebar.dataset.list``:93` 先加载 sidebar dataset 再执行 `kernel.project_view``rust/crates/bridge-runtime/src/lib.rs:8010` 后续由 sidebar 数据构建 subtree`:8111` 再转为 projection。 |
| RK-02 | 实现偏差 / 风险 | 高 | Rust Web 主路径仍存在静默 fallbackworkspace shell 在 projection 加载失败时无条件合成最小 workspace/documentssidebar/filetree 在 `allow_dev_fixtures` 时合成开发数据;Search 在 Convex query 失败时返回内置搜索数据。这与 `3-15-runtime-fallback-retirement` 的“默认不再 fallback”口径冲突。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2379``:2401` 无条件 fallback 最小 dataset`:2432``:2461` 合成 sidebar dev dataset`:2493``:2519` 合成 filetree dev dataset`rust/crates/mnote-web/src/routes/search.rs:233``:247` 搜索失败后走 `fallback_search_dataset`。 |
| RK-03 | 方向变化 / 文档滞后 | 中 | Page Aggregate 已由 Rust Web 暴露正式 route,但实现仍是先读 `documents.meta/content`,再把 joined data 交给 `page.aggregate.get` 生成 projection。代码对外 source 标成 `KernelProjection`,但底层仍是 meta/content join 的迁移形态;需要文档明确这是 Rust runtime adapter,而非完整 kernel storage 真源。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2282``:2315` 先读 meta/content 再执行 `page.aggregate.get``rust/crates/bridge-runtime/src/lib.rs:8246``:8253` 将 source 传为 `PageAggregateSource::KernelProjection``:5303``:5324` 从 meta/content 中抽取 title/content/revision。 |
| RK-04 | 未完成 | 中 | Tree realtime 正式 route 已存在,但当前 SSE 仍通过 polling `bridge.workspace.overview` 生成 snapshot/delta/resyncWS 只发送初始 snapshot,并只支持客户端请求 resync,不是完整主实时链路。 | `rust/crates/mnote-web/src/routes/sse.rs:51``:91` 循环 sleep/poll overview 后生成 delta`rust/crates/mnote-web/src/routes/ws.rs:32` 发送 snapshot`:40``:80` 只处理 resync 或 unsupported ack。 |
| RK-05 | 风险 / 文档滞后 | 低 | Legacy Next compat 默认关闭,但代码仍保留 env-gated proxy 能力、Next proxy 辅助函数和 compat 配置位。若文档继续写“legacy compat 已删除”,会与代码事实不一致;若保留,应写成显式调试/迁移边界。 | `rust/crates/mnote-web/src/app.rs:42``:54` 仍读取 `MNOTE_WEB_LEGACY_NEXT_BASE_URL` / `MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT``rust/crates/mnote-web/src/routes/gateway.rs:86``:87` auth API 可转 legacy proxy`:402``:499` legacy proxy 实现仍在;`rust/crates/mnote-web/src/routes/documents.rs:153` 当前 `should_proxy_via_next` 返回 false,但 Next proxy helper 仍保留。 |
| RK-06 | 实现一致 | 低 | Tree command cutover Stage 2 的长期命名面已在 Rust route / bridge / storage mapping 中落地,`documents.*` 仍作为 alias/底层 Convex mutation 名称存在,和 Stage 2A/2B 过渡口径基本一致。 | `rust/crates/storage-convex-bridge/src/mapping.rs:37``:43` 映射 `tree.*``:52``:68` 保留 `documents.*` / `page.*` alias`rust/crates/mnote-web/src/tree_shell/dispatcher.rs:16``:23` 使用 `tree.*``rust/crates/mnote-web/src/routes/tree.rs:7634` 附近测试确认 documents alias 映射仍存在。 |
| RK-07 | 实现一致 | 低 | Debug shell 默认关闭,符合 `3104`/debug 壳默认退场口径。 | `rust/crates/mnote-web/src/routes/mod.rs:120``:124` 仅在 `enable_debug_shell_routes` 时挂 `/tree``/document-debug`;同文件测试 `:142``:184` 验证默认 404。 |
## 证据
### 1. Kernel / projection 已落地,但底层仍主要基于 sidebar dataset
- `rust/crates/core-protocol/src/kernel.rs:7``:42` 定义 `KernelNodeType``KernelEdgeType``KernelProjectionKind`
- `rust/crates/mnote-web/src/routes/kernel.rs:62``:85` 暴露 projection route,并经 `load_projection_snapshot` 获取投影。
- `rust/crates/mnote-web/src/routes/snapshot_support.rs:27``:47` 固定 `sidebar.dataset.list` 为数据获取入口。
- `rust/crates/bridge-runtime/src/lib.rs:8119``:8137` 在没有 root 时由当前数据构建 subtree,再生成 projection`file_tree` 另走 `build_file_tree_projection_result`
判断:这符合 `1-1` 中“真实主线已落地,但更广义 node pool/reference edge 仍后续”的口径,不应被写成“尚未开始”;也不应被写成“完整 kernel 真源已闭环”。
### 2. Runtime fallback 仍在主路径附近
- `rust/crates/mnote-web/src/routes/web_shell.rs:2379``:2401`workspace shell projection 加载失败时合成 `active_workspace_id``workspaces``documents`
- `rust/crates/mnote-web/src/routes/web_shell.rs:2432``:2461``allow_dev_fixtures` 时合成 sidebar dev dataset。
- `rust/crates/mnote-web/src/routes/web_shell.rs:2493``:2519``allow_dev_fixtures` 时合成 filetree dev dataset。
- `rust/crates/mnote-web/src/routes/search.rs:233``:247`:搜索 Convex 查询失败后执行 `fallback_search_dataset(workspace_id)`
判断:这与 `design/03-rust-web/process/3-15-runtime-fallback-retirement-checklist-v1.md` 的“默认运行时不再 fallback / 不静默降级”存在偏差。尤其 Search fallback 会产生看似真实的固定结果,风险高于空状态降级。
### 3. Page Aggregate 是 Rust route,但仍是迁移期 adapter
- `rust/crates/mnote-web/src/routes/mod.rs:53``:56` 挂载 `/api/page-aggregate/{document_id}`
- `rust/crates/mnote-web/src/routes/web_shell.rs:2264``:2319` 构建 aggregate。
- `rust/crates/bridge-runtime/src/lib.rs:5291``:5442` 从 meta/content 形状构建 `PageAggregateProjection`
- `rust/crates/core-protocol/src/page_aggregate.rs:4``:10` 协议层仍保留 `KernelProjection``CompatMetaContentJoin``Fixture` 三种 source。
- `rust/crates/mnote-web/src/page_aggregate/builder.rs:66` 默认 source 仍是 `CompatMetaContentJoin`,但当前检索未发现该 builder 进入主要 route 主路径。
判断:当前代码已摆脱 TS builder runtime 主链,但还不是“页面域全部直接来自独立 kernel storage 真源”。文档应保留“Rust-first Page Aggregate 过渡态”的精确口径。
### 4. Tree realtime 主链已成立,但不是完整实时闭环
- `rust/crates/mnote-web/src/routes/mod.rs:113` 挂载 `/api/tree/events``:114` 挂载 `/api/stream/events``:115` 挂载 `/api/realtime/ws`
- `rust/crates/mnote-web/src/routes/sse.rs:127``:146``/api/tree/events``x-mnote-web-owner: mnote-web``x-mnote-tree-stream-owner: rust-web`
- `rust/crates/mnote-web/src/routes/sse.rs:51``:91` 通过 sleep/poll bridge overview 发现变化。
- `rust/crates/mnote-web/src/routes/ws.rs:32``:80` WebSocket 当前只发 snapshot,并响应 resync 请求。
判断:这与 `3-3` 中“已完成 route / snapshot / delta / resync 基础,WS 尚未成为主链,live cache 未完全统一”一致;若 `3-15` 写成 tree realtime 补偿链已完全删除,则偏乐观。
### 5. Compat / legacy 未成为默认主链,但未完全删除
- `rust/crates/mnote-web/src/app.rs:42``:54` 仍读取 legacy Next 相关环境变量,默认 `enable_legacy_next_compat` 为 false。
- `rust/crates/mnote-web/src/routes/gateway.rs:86``:87` 在 compat 开启且配置 legacy base URL 时,`/api/auth` 可走 legacy proxy。
- `rust/crates/mnote-web/src/routes/documents.rs:153` 当前 `should_proxy_via_next` 返回 false,说明文档 API 默认不走 Next proxy。
判断:代码实际状态更像“默认关闭、残留显式迁移/调试能力”,不是“所有 legacy proxy 代码已删除”。
## 建议优先级
### P0
- 移除或显式隔离 Search 的 `fallback_search_dataset`。失败时应返回明确错误或空 projection,并带可观测错误头;不要返回固定假结果。
-`load_workspace_shell_projection` 的无条件合成 dataset 做决策:若用于首屏容错,应在响应或 contract 中显式标记 degraded;若严格执行 fallback 退场,应改为失败或空树,不再伪装成真实 workspace projection。
### P1
-`allow_dev_fixtures` 相关 fallback 全部加上更明确的 debug/dev 标识,并确认 `desktop:hot` / 3000 默认启动链不会误开。
- 补一条 Rust Web smokeConvex/query 不可用时,搜索、Sidebar、filetree 不应返回假业务数据。
- Page Aggregate 文档补一句:当前 Rust route 已是主读链,但底层仍通过 Rust runtime adapter 消费 meta/content substrate;完整页面域单一真源仍在推进。
### P2
- Tree realtime 后续应把 SSE polling overview 与真正 domain event stream 的边界写清楚,并继续推进 page subtree / filetree / preferred snapshot 同一 live cache。
- Legacy Next proxy 若仍需保留,建议统一命名为 explicit migration/debug boundary;若不再需要,后续单独删除 proxy helper、配置位和测试样例,避免和 `3-15` 的退场状态长期冲突。
- `PageAggregateSource::CompatMetaContentJoin` / `Fixture` 是否继续保留在 `core-protocol` 需要架构决策:保留则标记为迁移态 source;删除则需先确认没有测试、local folder、fixture 依赖。
## 修改的文件路径
- `/mnt/Data1T/mnote/design/10-review/done/01-rust-kernel-web-review.md`
@@ -0,0 +1,125 @@
# 前端编辑器 / 树体验实现偏差审查
> 执行状态:已归档到 `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/process/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/process/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`,并且 `iframe_srcdoc` 只在显式 legacy flag 下存在。
但不能把当前状态描述为“前端编辑器 / 树体验已经完全收口到 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,防止默认路径回退。 | `wolai-frontend/src/components/sidebar/tree-shell-host.tsx:131` 默认 `rust_family` 且有 workspace 时使用 DOM host`:133` 仅在 `NEXT_PUBLIC_TREE_SHELL_LEGACY_IFRAME_HOST=1` 时启用 legacy iframe`wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx:182` 覆盖默认不能进入 `iframe_srcdoc``:199` 覆盖显式 legacy flag。 |
## 证据
### 已对齐部分
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`
@@ -0,0 +1,217 @@
# Convex / Realtime / Storage 实现偏差审查
> 执行状态:已归档到 `design/10-review/done/`。本文保留为历史审查快照,当前活跃口径以 `./06-execution-checklist-and-acceptance.md` 为准。
## 范围
本报告审查 Convex / storage bridge / realtime / Page Aggregate 数据底座与当前设计主线的一致性,重点对照以下方向:
- Convex 保留为自托管 storage / realtime substrate,不作为树语义 owner。
- Rust kernel / bridge-runtime / mnote-web 持有 tree-first graph、projection、command、Page Aggregate 的语义主导权。
- `/api/page-aggregate/:id` 作为文档页 Rust-first 读取主链。
- `/api/tree/events` 作为 Rust Web tree realtime snapshot / delta / resync 主链。
- 前端只消费稳定 projection、Page Aggregate 与 tree stream,不重新持有第二套对象真相。
重点读取范围:
- `ARCHITECTURE.md`
- `design/01-05-current-priority-overview.md`
- `design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
- `infra/convex/README.md`
- `wolai-frontend/convex/`
- `rust/crates/storage-convex-bridge/`
- `rust/crates/mnote-web/src/transport/convex.rs`
- `rust/crates/mnote-web/src/routes/documents.rs`
- `rust/crates/mnote-web/src/routes/tree.rs`
- `rust/crates/mnote-web/src/routes/sse.rs`
- `rust/crates/mnote-web/src/routes/stream_support.rs`
- `wolai-frontend/src/lib/documents/`
- `wolai-frontend/src/lib/tree-stream/`
## 结论
整体方向与设计主线基本一致:Convex 没有被拆掉,仍承担本地自托管存储、文件和实时底座;Rust Web 已注册 `/api/page-aggregate/:document_id``/api/tree/events`;前端 tree stream 已直接使用 `EventSource("/api/tree/events")`Next 的 `/api/documents/page``/api/mnote-web/stream` 已明确作为 410 compat 边界退场。
但当前实现仍是明显过渡态,主要问题集中在三处:
1. Page Aggregate 读链虽然是 Rust endpoint,但实际数据仍由 `documents:getMeta` + `documents:getContent` 分别取回后在 bridge-runtime 中拼 projection`source` 标识存在“看起来比真实实现更 canonical”的风险。
2. Rust Web 的 `/api/documents/title``/api/documents/options` 与 Next route 的 page write adapter 在 bridge artifact 记录上不一致;tree stream 依赖 bridgeLogs 时,Rust page write 路径可能漏掉 command/domain event。
3. `/api/tree/events` 是正式 SSE 入口,但当前 change detection 依赖周期性查询 `bridgeLogs:listWorkspaceOverview`,且 Convex query 内部会按 workspace collect 全量 command/domain rows 后内存过滤排序,实时性和规模风险较高。
## 关键发现表格
| 编号 | 分类 | 严重度 | 发现 | 证据 |
| --- | --- | --- | --- | --- |
| F1 | 实现偏差 | 高 | Rust Page Aggregate endpoint 对外是 `mnote.page_aggregate.v1`,但构建过程仍先分别读取 meta/content,再在 bridge-runtime 中拼 projection`source=KernelProjection` 容易掩盖底层仍是兼容 join。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2282``:2291``:2301``rust/crates/bridge-runtime/src/lib.rs:8246``:8252``:5291``:5303``:5386` |
| F2 | 风险 | 高 | Rust Web 的 title/options 写路由只执行 Convex mutation,不记录 bridge command/domain artifacts;而 tree stream 以 bridgeLogs 为事件来源,主路径 page 写入可能不进入实时流。 | `rust/crates/mnote-web/src/routes/documents.rs:714``:802``rust/crates/mnote-web/src/routes/command_support.rs:65`;对比 `rust/crates/mnote-web/src/routes/tree.rs:6529``wolai-frontend/src/lib/documents/page-write-command-adapter.ts:63` |
| F3 | 风险 | 高 | tree realtime 当前是 SSE transport,但后端通过 `sleep(poll_ms)` 周期性轮询 Convex bridgeLogs,而不是直接利用 Convex subscription/push;默认 2s,实时性和负载都需继续验证。 | `rust/crates/mnote-web/src/routes/sse.rs:25``:26``:51``:58``:60``rust/crates/mnote-web/src/routes/stream_support.rs:610` |
| F4 | 风险 | 中高 | `bridgeLogs:listWorkspaceOverview` 会按 workspace collect 全量 `command_logs``domain_events` 后在内存中过滤、排序、分页;SSE 轮询叠加后,历史日志增长会放大 Convex 读压力。 | `wolai-frontend/convex/bridgeLogs.ts:283``:287``:292``:301``:305` |
| F5 | 方向变化/文档滞后 | 中 | `storage-convex-bridge``page.aggregate.get` 映射到 `documents:getPageAggregate`,但 Convex `documents.ts` 只发现 `getMeta` / `getContent`,未发现 `getPageAggregate` export;当前真实 Page Aggregate 路线已绕到 Rust adapter。 | `rust/crates/storage-convex-bridge/src/mapping.rs:101``wolai-frontend/convex/documents.ts:684``:803`;全仓 `rg getPageAggregate` 未发现 Convex 实现 |
| F6 | 已完成/方向一致 | 中 | Next compat 读链和旧 stream alias 已显式退场,前端 tree stream 直接构造 `/api/tree/events`,符合当前主线口径。 | `wolai-frontend/src/app/api/documents/page/route.ts:17``wolai-frontend/src/app/api/mnote-web/stream/route.ts:9``wolai-frontend/src/lib/tree-stream/protocol.ts:66``:72` |
| F7 | 未完成 | 中 | Page Aggregate TS builder 已不在 runtime 主链中被引用,但文件仍保留;当前定位应继续写成 fallback / adapter / 测试材料,不能作为运行时主路径描述。 | `wolai-frontend/src/lib/documents/page-aggregate-builder.ts:51`;全仓非测试引用仅剩定义,runtime loader 只调用 Rust snapshot`wolai-frontend/src/lib/documents/page-aggregate-loader.ts:225` |
## 证据
### 1. Page Aggregate 读链已收口到 Rust endpoint,但仍由 meta/content join 生成
`mnote-web` 注册了正式 endpoint
- `rust/crates/mnote-web/src/routes/mod.rs:53``:56` 注册 `/api/page-aggregate/{document_id}`
- `rust/crates/mnote-web/src/routes/web_shell.rs:2227``:2252` 返回 `schema: "mnote.page_aggregate.v1"``result: aggregate`
但实际构建流程仍是:
- `rust/crates/mnote-web/src/routes/web_shell.rs:2282``:2290` 读取 `load_document_meta_result`
- `rust/crates/mnote-web/src/routes/web_shell.rs:2291``:2299` 读取 `load_document_content_result`
- `rust/crates/mnote-web/src/routes/web_shell.rs:2301``:2315``meta + content` 作为数据传给 `page.aggregate.get`
- `rust/crates/bridge-runtime/src/lib.rs:5291``:5312``build_page_aggregate_projection_result` 明确从 `data.meta``data.content` 中拆字段。
这说明“Rust-first 读取主链”成立,但“Page Aggregate 已经由 kernel 原生投影独立产出”仍未成立。该点与 `5-5/5-6` 的过渡态判断一致,但需要在后续文档和汇报中保持精确。
需进一步验证:`PageAggregateSource::KernelProjection` 是否应在这种 `meta/content join` 场景下继续使用,还是应保留 `CompatMetaContentJoin` 以避免 provenance 误导。
### 2. Page write side effects 在 Rust route 与 Next adapter 之间不一致
Next route 侧:
- `wolai-frontend/src/app/api/documents/title/route.ts:58` 调用 `executePageWriteBridgeCommand`
- `wolai-frontend/src/app/api/documents/options/route.ts:47` 调用 `executePageWriteBridgeCommand`
- `wolai-frontend/src/app/api/documents/save/route.ts:43` 调用 `executePageWriteBridgeCommand`
- `wolai-frontend/src/lib/documents/page-write-command-adapter.ts:63``:69` 成功后调用 `recordRustBridgeCommandArtifacts`
Rust route 侧:
- `rust/crates/mnote-web/src/routes/documents.rs:579` 的正文保存使用 `execute_runtime_command_via_convex_with_artifacts`
- `rust/crates/mnote-web/src/routes/documents.rs:714` 的标题更新使用 `execute_runtime_command_via_convex`
- `rust/crates/mnote-web/src/routes/documents.rs:802` 的页面设置更新使用 `execute_runtime_command_via_convex`
- `rust/crates/mnote-web/src/routes/command_support.rs:65``:73``execute_runtime_command_via_convex` 只执行 Convex command plan,不持久化 artifacts。
- `rust/crates/mnote-web/src/routes/command_support.rs:75``:93` 的 artifact 版本才会调用 `execute_convex_command_plan_with_artifacts`
tree command route 是一致的:
- `rust/crates/mnote-web/src/routes/tree.rs:6529` 使用 `execute_runtime_command_via_convex_with_artifacts`
风险是:如果当前 3000 主入口走 Rust Web `/api/documents/title``/api/documents/options`,这些 page 写入不会进入 `command_logs/domain_events`,而 `/api/tree/events` 正是通过 bridgeLogs 检测变化。标题类写入尤其可能影响 sidebar/breadcrumb/page tree 的实时一致性。
需进一步验证:当前文档页标题编辑在 3000 主壳中到底走 `/api/documents/title` 还是 `/api/tree/commands`;如果走前者,应补 Rust route artifact 记录或统一到 tree/page command route。
### 3. Tree realtime 是正式 SSE 入口,但实现仍是 polling-backed stream
正式入口成立:
- `rust/crates/mnote-web/src/routes/mod.rs:130` 注册 `/api/tree/events`
- `rust/crates/mnote-web/src/routes/sse.rs:127``:146` 给 tree events 加 `x-mnote-tree-stream-owner: rust-web`
- `wolai-frontend/src/lib/tree-stream/protocol.ts:66``:77` 构造 `/api/tree/events` URL。
- `wolai-frontend/src/lib/tree-stream/use-sidebar-tree-stream.ts:136``:143``EventSource` 监听 `snapshot/delta/resync`
但服务端 change detection 是轮询:
- `rust/crates/mnote-web/src/routes/sse.rs:25``:26` 读取 `max_polls``poll_ms`,默认 `2000ms`,最低 `250ms`
- `rust/crates/mnote-web/src/routes/sse.rs:51``:58` 循环中 `sleep(Duration::from_millis(poll_ms))`
- `rust/crates/mnote-web/src/routes/sse.rs:60``:67` 每轮调用 `load_stream_overview``resolve_stream_change`
- `rust/crates/mnote-web/src/routes/stream_support.rs:610``:625``load_stream_overview` 通过 `bridge.workspace.overview` 查询 Convex。
这与“Rust Web 提供正式实时 transport”一致,但还不是“基于 Convex realtime subscription 的 push stream”。短期可接受为过渡实现,长期如果继续承载主链,需要明确性能和延迟边界。
### 4. bridgeLogs overview 查询存在规模风险
`wolai-frontend/convex/bridgeLogs.ts``listWorkspaceOverview`
- `:283``:286` 按 workspace 查询并 `collect()` 所有 `command_logs`
- `:287``:290` 按 workspace 查询并 `collect()` 所有 `domain_events`
- `:292``:301` 在内存中过滤、排序、切片 command logs。
- `:303``:312` 再用 commandId 集合过滤 domain events。
该实现作为调试/小数据过渡可以工作,但被 `/api/tree/events` 默认每 2 秒调用时,日志增长后会造成:
- Convex query 读放大。
- SSE 连接数量增加时成倍放大。
- cursor 语义依赖内存排序,历史数据规模变大后容易出现延迟或超时。
建议后续至少增加按 `workspace_id + created_at/id` 的索引分页,避免每次 stream poll 全量扫描。
### 5. storage-convex-bridge 中存在疑似过时的 `page.aggregate.get` 映射
`rust/crates/storage-convex-bridge/src/mapping.rs:101`
```text
"page.aggregate.get" => "documents:getPageAggregate"
```
但当前 `wolai-frontend/convex/documents.ts` 中只发现:
- `getMeta``wolai-frontend/convex/documents.ts:684`
- `getContent``wolai-frontend/convex/documents.ts:803`
全仓搜索未发现 Convex `documents:getPageAggregate` 实现。当前真实 Page Aggregate 读取是 Rust Web 先读 meta/content,再由 bridge-runtime 生成 projection。因此该映射要么是未来目标,要么已经滞后;如果有代码路径直接通过 storage-convex-bridge 执行 `page.aggregate.get`,会存在运行时函数不存在风险。
需进一步验证:`storage-convex-bridge` 是否仍有生产路径直接执行 `page.aggregate.get -> documents:getPageAggregate`;如果没有,应把映射标注为 future/stale,或改为当前真实读链。
### 6. Convex substrate 定位总体一致
正向证据:
- `infra/convex/README.md` 明确自托管 Convex backend/dashboard、HTTP Actions 和文件存储。
- `wolai-frontend/convex/schema.ts:35``:91``documents` 表继续承接页面结构、正文、页面设置、统计字段。
- `wolai-frontend/convex/schema.ts:121``:162``media_assets``_storage` 字段保留文件底座。
- `rust/crates/storage-convex-bridge/README.md:5``:13` 明确 bridge 只做协议到 Convex 读写请求映射,不复制主事实层、不维护第二数据库。
这与“Convex 不拆,Rust 收口语义”的设计一致。
## 建议优先级
### P0:修正 Rust page write route 的 artifact 一致性
目标:让当前 3000 主入口的 `/api/documents/title``/api/documents/options` 至少在 side effect 上与 Next `page-write-command-adapter` 保持一致。
建议:
-`rust/crates/mnote-web/src/routes/documents.rs` 中 title/options 的 `execute_runtime_command_via_convex` 改为 artifact 版本,或统一复用 tree/page command route。
- 对标题更新补最小回归:更新标题后 `bridgeLogs:listWorkspaceOverview` 能看到对应 command/domain event`/api/tree/events` 能输出 delta 或 resync。
- 对 artifact 写失败的策略重新定级:当前 `rust/crates/mnote-web/src/transport/convex.rs:759``:770` 是主 mutation 成功、artifact 失败仍返回成功;对 tree-relevant command 至少应打可观测错误并触发保守 resync。
### P1:收口 Page Aggregate provenance
目标:避免把 `meta/content join` 误标为已完成的 kernel-native projection。
建议:
- 明确 `PageAggregateSource::KernelProjection``CompatMetaContentJoin` 的使用边界。
- 如果 `build_page_aggregate_snapshot` 仍通过 `load_document_meta_result + load_document_content_result` 构建,应在返回 source 或 header 中反映真实来源。
- 如果目标是 kernel-native projection,则补正式 kernel/query 数据路径,避免 route 层长期手工拼装。
### P1:优化 tree stream 的 Convex 查询模型
目标:让 `/api/tree/events` 可以长期承载主链,而不是随着日志增长退化。
建议:
-`command_logs``domain_events` 增加按 `workspace_id + created_at/id` 的查询索引和 cursor 查询。
- `listWorkspaceOverview` 不再 `collect()` 全量 workspace 日志后内存分页。
- 明确 polling-backed SSE 是过渡实现,还是长期 realtime transport;若长期使用,应补连接数、日志量、延迟上限的 smoke/bench。
### P2:清理或标注 stale mapping
目标:降低后续 worker 误用 `page.aggregate.get -> documents:getPageAggregate` 的风险。
建议:
-`documents:getPageAggregate` 不计划实现,移除或注释 `storage-convex-bridge` 中的映射。
- 若计划实现,补 Convex query 与最小测试,并让 mnote-web Page Aggregate route 直接消费它或说明为什么不消费。
### P2:保留 Next compat 退场边界,但避免双实现继续发散
目标:Next route 继续作为 legacy/adapter 参考时,不与 Rust Web 主入口形成不同 side effects。
建议:
-`/api/documents/title/options/save` 明确 ownerRust Web 主路径与 Next legacy 路径只能有一份 canonical side-effect 规则。
-`wolai-frontend/src/lib/documents/page-aggregate-builder.ts` 保留测试/adapter 标签,避免被重新接回 runtime 主链。
## 修改的文件路径
- `design/10-review/done/03-convex-realtime-storage-review.md`
@@ -0,0 +1,68 @@
# 次级域与设计治理实现偏差审查
> 执行状态:已归档到 `design/10-review/done/`。本文保留为历史审查快照,当前活跃口径以 `./06-execution-checklist-and-acceptance.md` 为准。
## 范围
本次只审查 Mindmap、AI、OnlyOffice、Wolai-aline、SiYuan reference,以及 `design/process/done` 治理口径与当前实现方向的一致性。重点读取了 `ARCHITECTURE.md``design/README.md``design/06-mindmap/process/*``design/07-ai/process/*``design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md``design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md``design/90-reference/*`,并对照了 Mindmap、AI route/lib、OnlyOffice route/adapter 与相关 Convex/Rust runtime 代码。
## 结论
总体方向与当前主线基本一致:Mindmap 已明确降为 `tree-first graph kernel` 的视图/编辑挂件;AI 长期方向已从 `mnote-cli` 收口改为 Hermes 页面内客户端 + mnote Hermes skill/pluginOnlyOffice 仍是独立页面型编辑器边界,SiYuan 参考稿没有发现被提升为上位架构来源的证据。
主要风险集中在三处:OnlyOffice 在 `mnote-web` 主入口里的 callback/forcesave 仍是 no-op,而 legacy Next route 已有真实写回链;Mindmap 的导出 action 在 schema/action map 中暴露,但 bridge 安全命令集不支持;Mindmap AI 补完 route 当前硬返回 501,和 Rust tool 已登记的能力面不闭合。
## 关键发现表格
| ID | 分类 | 级别 | 发现 | 简短证据 | 建议 |
| --- | --- | --- | --- | --- | --- |
| F-01 | 实现偏差 / 风险 | P1 | OnlyOffice `mnote-web` 主入口已挂载 callback/forcesave,但当前实现不写回,只返回成功或 noop,可能让 3000 主链下的 OnlyOffice 保存丢失。 | `rust/crates/mnote-web/src/routes/mod.rs:78-84` 挂载 `/api/onlyoffice/callback``/api/onlyoffice/forcesave``rust/crates/mnote-web/src/routes/onlyoffice.rs:717-759` callback 只记录日志并返回 `{error:0}`forcesave 返回 `mnote-web-rust-noop`;而 `wolai-frontend/src/app/api/onlyoffice/callback/route.ts:93-192` 有真实 Convex 写回链。 | 优先把 Rust route 接到 `onlyoffice_prepare_callback` / media asset writeback,或显式把该路由代理到 legacy Next,避免主入口 shadow 掉真实写回。 |
| F-02 | 未完成 | P2 | Mindmap `export` 动作暴露给 UI action map,但 simple-mind-map 安全执行器不允许 `EXPORT`,默认路径可能显示能力却执行失败。 | `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts:65``export` 映射为 runtimeCommand `EXPORT``wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts:119-129``SIMPLE_MIND_MAP_SAFE_COMMANDS` 不包含 `EXPORT`。 | 要么把 `EXPORT` 加入安全命令并补 smoke,要么在 UI state 中继续禁用导出并标注为延期。 |
| F-03 | 未完成 / 方向变化 | P2 | Mindmap AI 补完 route 当前直接 501,后续大段旧 Supabase/在线 AI 实现被注释;但 Rust tool registry 已登记 `mindmap_expand_node`,形成“工具存在、产品入口不可用”的断层。 | `wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts:90-97` 校验后直接返回 `501`;同文件后续注释块仍保留旧 Supabase/AI 逻辑;`rust/crates/core-protocol/src/tool.rs:231-240``rust/crates/bridge-runtime/src/lib.rs:3803-3812` 已有 `mindmap_expand_node`。 | 若该能力仍在 Phase 6/7 范围内,应按 CLI/Rust bridge 路线重接;若延期,应把 route 标成 retired/debug,避免前端或测试误以为可用。 |
| F-04 | 方向变化 / 文档滞后 | P2 | AI 主 Web route 曾收口到 `mnote-cli` host,但新主线已改为 Hermes 页面内客户端;`wolai-backend` 仍暴露 `openai_agents_python` 文档 agent route 与旧工具面,旧 route / sidecar / host 的退场关系需重新明确。 | `wolai-frontend/src/app/api/ai-agent/run/route.ts:36-57` 曾明确拒绝 codex/hermes/claudecode 并进入 `startMnoteCliAgentHostRun``wolai-backend/app/routers/ai_agent.py:34-57` 仍暴露 `/ai-agent/health``/ai-agent/document/run`health 返回 `bridge: openai_agents_python``wolai-backend/app/services/ai_document_agent.py:1174-1470` 仍指令 agent 使用 `doc_insert_blocks``doc_replace_range``slash_run`。 | 以 `design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md` 为上位依据,明确页面 AI 改走 Hermes client proxymnote 能力通过 Hermes skill/plugin 暴露;`openai-agents-python``mnote-cli host` 只保留为历史/兼容/内部适配路径并补退场计划。 |
2026-05-14 复核:F-04 的长期方向已由 `design/07-ai/done/7-4-page-ai-hermes-panel-execution-checklist-v1.md` 继续落地到 mnote-web Hermes client proxy 与 mnote Hermes tool routes;旧 `/api/ai-agent/run` 已在 mnote-web 中收口为 `legacy_ai_agent_run_retired` guard,旧 `openai-agents-python` / `mnote-cli host` 只按历史/兼容/对照链处理,不再代表页面 AI 长期主入口。
| F-05 | 文档治理风险 | P3 | `design/90-reference` 符合“参考资料”目录定位,但内容仍带有问答式残留,容易被后续 worker 误用为正式设计结论。 | `design/README.md` 明确 `90-reference/` 不参与 process/done 状态判断;`design/90-reference/90-1-filetree.md` 末尾保留“需要我给你...”类对话尾巴;`design/90-reference/90-2-yemianshu.md` 同样保留示例请求口吻。 | 低优先级清理为中性参考笔记,并在引用时强制以 `ARCHITECTURE.md` 和主线设计为上位依据。 |
## 证据
### Mindmap
- 设计口径清晰:`design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` 明确 Phase 6 主线是 `Rust kernel truth -> mindmap.simple_mind_map_scene.v1 -> leptos-mindmap editor island -> simple-mind-map runtime -> command bridge`,并禁止把 runtime data 当唯一事实源。
- 实现已对齐主线壳:`rust/crates/mnote-web/src/routes/mindmap_shell.rs:105-123` 输出 `mnote.mindmap_shell.v1``mindmap.simple_mind_map_scene.get``mindmap.command.apply``rust/crates/mnote-web/src/ssr/pages/mindmap.rs:50-56` 提供 standalone island 挂载点。
- 旧 React 块已自我标注为 legacy/compat/reference`wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx:3-5` 明确 3000 文档页默认主链是 `leptos-tiptap NodeView + leptos-mindmap adapter`
- 仍有 compat 数据层:`wolai-frontend/convex/schema.ts:164-184``mindmaps.data: v.any()` 仍承载导图数据;这与当前过渡态兼容,但不应被描述为长期 canonical truth。
### AI
- 旧 CLI-first 入口曾基本对齐:`wolai-frontend/src/app/api/ai-agent/run/route.ts:36-57` 将默认执行入口限定到 `mnote-cli host`,拒绝旧 provider;但该口径已被 2026-05-13 的 Hermes 面板主线覆盖。
- 结构化 artifact 设计应继续保留对象模型,但触发方改为 Hermes tool call -> mnote plugin -> Rust runtime / kernel,见 `design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md``design/07-ai/done/7-2-phase7-structured-artifact-write-chain-v1.md`
- 旧后端 agent 仍存在:`wolai-backend/app/routers/ai_agent.py:47-57` 仍提供 streaming run;这只能作为历史/兼容/对照链,不能被误认为长期默认主编排。
### OnlyOffice
- 正确边界已有:`rust/crates/adapter-onlyoffice/src/lib.rs:124-126` 明确 adapter 只定义资产定位、会话、签名、代理、callback/forcesave 边界,不把 OnlyOffice 变成主事实层。
- `mnote-web` 页面是独立编辑页:`rust/crates/mnote-web/src/routes/onlyoffice.rs:325-630` 直接渲染 `/onlyoffice`,通过 DocsAPI 创建编辑器,未嵌入正文主编辑画布。
- 关键风险是写回链 owner 分裂:Rust 主入口 no-op 与 legacy Next 真写回并存,见 F-01。
### Wolai-aline
- 流程文档严格要求“Wolai 基线 -> RED smoke -> 小范围实现 -> 本地验证 -> subagent 复测 -> 主线程截图复核”:`design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
- 本次不是具体 Wolai 对标实现任务,未执行浏览器对标;未发现该流程被错误提升为产品架构来源。
### SiYuan Reference
- `design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md` 的边界与当前主线一致:思源只作为产品能力与交互参考,不替代 `tree-first graph kernel`、Rust kernel、Page Aggregate、tree command 与 tree realtime。
- 当前只读检索未发现将 SiYuan `.sy`、SQL API 或前端 runtime 直接提升为 mnote 长期事实源的实现证据;若后续新增属性视图/数据库视图,应继续先做对象域评估。
## 建议优先级
1. P1:补齐或明确代理 OnlyOffice Rust callback/forcesave 写回链,避免主入口下保存成功但内容未持久化。
2. P2:收口 Mindmap action 可用性,先修 `export` 映射与安全命令不一致,再继续扩展 UI 能力。
3. P2:处理 Mindmap AI route:要么按 Hermes plugin / Rust bridge 重接 `mindmap_expand_node`,要么显式退役该 Next route。
4. P2:继续保持 `wolai-backend` AI agent、旧 `/api/ai-agent/run``mnote-cli host` 的历史/兼容定位和访问边界;页面 AI 长期主链已经由 `7-4` 收口到 Hermes 面板 + mnote plugin,后续只在各自 legacy domain 里拆迁移。
5. P3:清理 `design/90-reference` 的问答残留,并在引用规范里再次强调它不是 process/done 设计稿。
## 本次修改文件
- `/mnt/Data1T/mnote/design/10-review/done/04-secondary-domains-and-design-governance-review.md`
+154
View File
@@ -0,0 +1,154 @@
> 执行状态:本文已归档到 `design/10-review/done/`,是 10-review 阶段的树域判断历史记录,不再作为待办清单直接执行。Resource Tree / File Tree / Page Tree 真源合同已由 `/mnt/Data1T/mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md` 承接;主编辑区 Object Tab、mindmap object editor、`index.md` 隔离已由 `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md` 承接。后续只需按 `06-execution-checklist-and-acceptance.md` 保留 smoke 防回归。
结论
我基本同意你的方向,但要把“文件树为根源”说得更精确:不应让“文件树 UI 组件”成为真源,而应让 Rust kernel 中的 workspace resource tree / file tree projection 背后
的资源层级 成为页面、附件、mindmap、OnlyOffice 等对象的组织根源。页面树则是从这棵资源/对象树派生出的导航投影,类似快捷方式、收藏视图或文档视图。
对当前 bug 来说,最核心的规则应固定为:
1. index.md 只代表页面正文,即 Page Aggregate body。
2. mindmap 不是主编辑区正文替身,而是页面内某个 block 关联的 object / asset editor。
3. 文件树点击 mindmap 应打开 mindmap object editor,不能被 index.md 吞掉,也不能用临时 mindmap-only bootstrap 覆盖真实页面正文。
4. 页面树不应再持有独立结构真相,只显示“文档导航关系”;文件树显示“资源组织关系”。
主编辑区与插件
主编辑区应该是一个工作区 tab host,而不是只等于文档正文编辑器。index.md 是其中一种 tabmindmap、OnlyOffice、附件预览、代码附件都是其他 object tab/model。
Siyuan 这里值得借鉴:doc 和 asset 是不同 model/tabasset 打开不伪装成 doc 正文;插件、widgets、assets、.sy 文档也在存储层分开。mnote 也应采用类似原则:插件/附
件/mindmap 可以嵌入页面,但它们的持久化真源不能变成页面正文本身,只能通过 block-asset relation 被 Page Aggregate 引用。
文件树与页面树
我建议改成三层表述:
- Resource Tree:长期 canonical 对象组织树,Rust kernel 持有语义,Convex 只是底层存储。
- File TreeResource Tree 的主组织投影,展示页面文件夹、index.md、附件、mindmap、OnlyOffice、代码附件等。
- Page Tree:面向阅读/导航的快捷投影,只显示页面关系,不拥有排序、父子、附件归属的最终真相。
这样你的“文件树为根源”可以落地,但不会和现有 tree-first graph kernel 冲突。更准确地说:文件树背后的 resource hierarchy 是根源,页面树是它的页面导航视图。
建议路线
短期先修当前 bug:保留 /mindmap/{doc}/{asset} 作为 object editor,但彻底隔离草稿 key、保存 command 和 Page Aggregate bodysmoke 必须真实网页验证“点击 mindmap、
编辑长中文、保存、切回 index.md、再打开 mindmap 内容仍在”。
中期做主编辑区 asset tabindex.md | mindmap.json | xxx.docx,每个 tab 有自己的 object identity 和保存命令。
长期补 resource projection / block-asset relation:文件树 row、页面内 mindmap block、mindmap projection 必须来自同一关系索引,而不是前端临时按路由拼。
推荐路线
我建议采用“分阶段收口 Resource Tree”的方案,不做一次性大重构。
可选方案有三种:
- 只修 mindmap bug:最快,但会继续留下页面树/文件树真源分裂。
- 分阶段建立 Resource Tree -> File Tree/Page Tree projection:最稳,能解决当前 bug,也能承接附件、OnlyOffice、插件对象。
- 全量重写 tree/page/resource 模型:理论干净,但当前工作区和主链都很活跃,风险太高。
我推荐第二种。
阶段 0:冻结合同
历史建议曾是:先把 design/10-review/05-tree.md 升级为正式设计合同,后续移动或补一份到:
- design/04-tree-domain/process/4-24-resource-tree-filetree-pagetree-source-contract-v1.md
- 或 design/05-editor-mainline/process/5-11-main-editor-object-tab-resource-tree-alignment-v1.md
合同里固定四句话:
- Resource Tree 是组织真源。
- File Tree 是资源组织主投影。
- Page Tree 是页面导航投影/快捷视图。
- index.md、mindmap、OnlyOffice、附件是不同 object model,不能互相伪装。
阶段 1:先关闭当前 mindmap bug
目标:文件树 mindmap 仍可编辑保存,但不污染 index.md。
重点文件:
- rust/crates/mnote-web/src/ssr/pages/layout.rs
- rust/crates/mnote-web/src/routes/mindmap_shell.rs
- rust/spikes/leptos-tiptap-spike/src/lib.rs
- scripts/task169-mindmap-realtime-smoke.js
验收必须是真实浏览器链路:
1. 从文件树点击 mindmap asset。
2. 进入明确的 mindmap object editor。
3. 输入长中文并保存。
4. 切回同页 index.md,正文仍是 Page Aggregate body。
5. 再打开 mindmap,刚才内容仍在。
6. 确认没有用真实 documentId 写 standalone 草稿 key。
阶段 2:补 Resource Projection 协议
在 Rust 协议层明确资源节点和 block-asset 关系。
建议新增或扩展:
- ResourceNode
- ResourceKind: page | index | mindmap | attachment | onlyoffice | code
- ObjectIdentity: { objectKind, documentId, blockId?, assetId? }
- BlockAssetRelation: { documentId, blockId, assetId, assetKind }
落点优先看:
- rust/crates/core-protocol/
- rust/crates/bridge-runtime/
- rust/crates/mnote-web/src/tree_shell/
这个阶段不要求立刻迁移所有数据,只要协议和 projection 能表达清楚。
阶段 3:让 File Tree 从 Resource Tree 派生
当前 file tree 已有 resourceMeta、assetKind、file_tree projection 基础。下一步是收口为:
- 页面节点下面固定有 index.md
- mindmap/附件/OnlyOffice 都是同一 Resource Tree 下的 child resource
- 文件树 row 不再临时从多份前端数据拼出第二真相
同时保留 tree.asset.open,但它必须只表达 object open intent,不直接决定“把谁当正文”。
阶段 4:Page Tree 降级为导航投影
Page Tree 不再拥有资源归属、附件归属、mindmap 归属。它只显示页面导航关系:
- 页面标题
- 页面层级
- 快捷入口/收藏/最近打开这类导航语义
如果页面树需要显示某个资源状态,也只读 Resource Projection,不自己维护。
阶段 5:主编辑区变成 Object Tab Host
这是中期关键体验:
- index.md tab:加载 Page Aggregate body。
- mindmap.json tab:加载 mindmap projection/command。
- xxx.docx tab:打开 OnlyOffice object editor。
- attachment tab:预览/代码编辑/外部打开。
这样 mindmap 不会被 index.md 吞掉,也不会伪装成正文。
阶段 6:命令面统一
后续命令应逐步收口到:
- tree.asset.attach
- tree.asset.detach
- tree.resource.rename
- tree.resource.move
- page.body.save
- mindmap.command.apply
关键规则:改资源关系走 tree/resource command,改页面正文走 page command,改导图内容走 mindmap command。
落地入口
以上判断已落实到两个主线 done checklist
- /mnt/Data1T/mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md
用于冻结 Resource Tree / File Tree / Page Tree 的真源合同和 projection / command 边界。
- /mnt/Data1T/mnote/design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md
用于落实主编辑区 Object Tab、mindmap object editor、草稿隔离和真实浏览器 smoke 验收。
执行顺序固定为:
1. 先完成 4-24:协议、projection、command 边界必须能表达 `index.md`、mindmap、OnlyOffice、附件、代码附件的不同 object identity,并确认 Page Tree 只是页面导航投影。
2. 再完成 5-12:主编辑区只消费 4-24 输出的 object identity / resourceMeta / open intent,先关闭 mindmap 污染 `index.md` 的 P0 bug,再推进 Object Tab Host。
3. 验收以真实浏览器 smoke 为准,尤其是 `scripts/task169-mindmap-realtime-smoke.js` 覆盖“打开 mindmap、长中文编辑、保存、切页、回 `index.md`、再开 mindmap”的链路。
@@ -0,0 +1,446 @@
# 10-review 顺序执行清单与验收标准
> 执行状态:已完成并归档到 `design/10-review/done/`。本文是本轮 10-review 的最终验收依据与防回归口径,根目录 `design/10-review/README.md` 仅保留索引。
> 更新时间:2026-05-14
>
> 上游依据:
> - `/mnt/Data1T/mnote/design/10-review/README.md`
> - `/mnt/Data1T/mnote/design/10-review/done/01-rust-kernel-web-review.md`
> - `/mnt/Data1T/mnote/design/10-review/done/02-frontend-editor-tree-review.md`
> - `/mnt/Data1T/mnote/design/10-review/done/03-convex-realtime-storage-review.md`
> - `/mnt/Data1T/mnote/design/10-review/done/04-secondary-domains-and-design-governance-review.md`
> - `/mnt/Data1T/mnote/design/10-review/done/05-tree.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md`
## 0. 执行前提
`05-tree.md` 中关于 Resource Tree / File Tree / Page Tree、ObjectIdentity、mindmap 与 `index.md` 隔离的核心事项已经由 `4-24``5-12` 承接到 `done/`。后续执行不要重复重做该主线,只需要保留防回归验证。
本清单按依赖顺序执行。原则是先修会影响主链一致性和数据可信度的 P0,再处理 realtime / projection / compat 收口,最后做次级域和文档治理。
## 1. P0:修正 Rust page write artifact 一致性
状态:已完成第 1 项代码侧与 Rust 3000 验证。Rust `/api/documents/title``/api/documents/options` 已切到 artifact 记录路径;`page.layout.updateOptions` 已补 tree domain event / conservative resync artifactRust 3000 下已直接验证标题更新后 `bridge.workspace.overview` 可读到 `page.head.updateTitle` command log 与 `tree.node.renamed` domain event`/api/tree/events` 也能输出包含新标题的 stream 事件。完整 File Tree 标题同步仍归入第 6 项继续处理。
目标:让 3000 主入口下的页面标题、页面设置、正文保存拥有一致 side effect,确保 tree stream 能看到相关变更。
建议修改范围:
- `rust/crates/mnote-web/src/routes/documents.rs`
- `rust/crates/mnote-web/src/routes/command_support.rs`
- `rust/crates/mnote-web/src/transport/convex.rs`
- 必要时补充 `wolai-frontend/convex/bridgeLogs.ts` 查询侧验证
执行项:
- [x] 将 Rust `/api/documents/title` 的命令执行切到 artifact 记录路径,或统一复用正式 page/tree command route。
- [x] 将 Rust `/api/documents/options` 的命令执行切到 artifact 记录路径,或统一复用正式 page/tree command route。
- [x] 明确 artifact 写失败策略:主 mutation 成功但 artifact 失败时至少要有可观测错误,并触发保守 resync 或明确降级。
- [x] 补回归测试:标题更新后,`bridgeLogs:listWorkspaceOverview` 能看到对应 command/domain event。
验收标准:
- [x] `cargo test -p mnote-web documents_title -- --nocapture` 或等价标题 route 测试通过。
- [x] `cargo test -p mnote-web documents_options -- --nocapture` 或等价页面设置 route 测试通过。
- [x] 手动或脚本验证:更新标题后 `/api/tree/events` 输出 delta 或 resync,不需要刷新整页才能看到 sidebar/breadcrumb 变化。
- [x] Rust route 与 Next legacy adapter 对同一 page write 的 canonical command / artifacts 语义一致。
证据:
- `rust/crates/mnote-web/src/routes/documents.rs``title` / `options` 使用 `execute_runtime_command_via_convex_with_artifacts`,响应 `meta.artifacts` / `meta.artifactError`
- `rust/crates/bridge-runtime/src/lib.rs``page.layout.updateOptions` 产生 `page.layout.options_updated` domain event 与 `resync_required` stream delta。
- `rust/crates/mnote-web/src/transport/convex.rs`:发送给 legacy `documents:updateOptions` 时剥离 artifact-only 字段。
- 已通过:Rust 3000 目标脚本创建临时页面后调用 `/api/documents/title`,再查 `/api/bridge/workspace?targetPageId=<documentId>&aggregateType=page&aggregateId=<documentId>`,确认返回 `command_name=page.head.updateTitle``event_type=tree.node.renamed`
- 已通过:Rust 3000 目标脚本创建临时页面后调用 `/api/documents/title`,再查 `/api/tree/events?workspaceId=<workspaceId>&maxPolls=1&pollMs=250`,确认 stream 文本包含新标题且包含 `snapshot` / `delta` / `resync` 事件。
- 已通过:`node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`
- 部分通过:`node scripts/task110-page-title-single-truth-smoke.js` 在 Rust 3000 下已通过页头、breadcrumb、sidebar、page tree 标题一致性检查;当前失败点为 File Tree 标题行,继续归入第 6 项处理。
- 已通过:`pnpm test -- src/app/api/documents/route-adapters.test.ts src/lib/documents/page-write-command-adapter.test.ts`Vitest 实际执行 112 个测试文件、457 个测试),Next `documents/title` route 与 page write adapter 继续生成 `page.head.updateTitle` canonical command 并进入 Rust artifact writer。
- 已通过:`cargo test -p mnote-web documents_ -- --nocapture`
- 已通过:`cargo test -p bridge-runtime page_head_update_title_command_plan_uses_canonical_page_command -- --nocapture`
- 已通过:`cargo test -p bridge-runtime page_layout_update_options_command_plan_uses_canonical_page_command -- --nocapture`
- 已通过:`cargo test -p bridge-runtime document_options_command_plan_maps_to_documents_update_options -- --nocapture`
- 已通过:`cargo test -p mnote-web convex_command_args_strips_page_options_artifacts_for_legacy_mutation -- --nocapture`
## 2. P0:移除或显式隔离假 fallback 数据
状态:已完成代码侧收口与负向单测。Search 默认不再返回内置固定业务结果;workspace shell projection 默认进入 degraded emptySidebar/File Tree dev fallback 仅在 `allow_dev_fixtures` 开启时可用,并带隐藏 debug/dev 标识。
目标:失败、超时、Convex 不可用时,不再返回看起来像真实业务数据的 fixture / fallback。
建议修改范围:
- `rust/crates/mnote-web/src/routes/search.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `rust/crates/mnote-web/src/routes/snapshot_support.rs`
执行项:
- [x] 优先移除或强门禁 Search 的 `fallback_search_dataset`
- [x] 对 workspace shell projection 加载失败的最小合成数据做决策:返回明确 degraded/empty/error,不能伪装成真实 workspace projection。
- [x]`allow_dev_fixtures` 相关 sidebar/filetree fallback 增加明确 debug/dev 标识,并确认 3000 默认启动链不会误开。
- [x] 增加负向 smokeConvex/query 不可用时,Search、Sidebar、File Tree 不返回固定假业务数据。
验收标准:
- [x] 关闭或破坏 Convex query 后,Search 不返回内置固定结果。
- [x] 关闭或破坏 projection 加载后,workspace/sidebar/filetree 响应带明确错误、空状态或 degraded 标记。
- [x] 3000 默认入口不会出现 fixture workspace、fixture document、fixture filetree row。
- [x] 相关负向测试或 smoke 在失败场景下能稳定断言“不返回假数据”。
证据:
- `rust/crates/mnote-web/src/routes/search.rs`Convex/search query 失败时,默认返回空结果与 `meta.degraded=true`;仅 `allow_dev_fixtures` 开启时使用 `fallback_search_dataset`
- `rust/crates/mnote-web/src/routes/web_shell.rs`workspace shell projection 失败时,默认返回 degraded empty datasetSidebar/File Tree dev fallback HTML 增加 `data-mnote-dev-fixture` 标识。
- `rust/crates/mnote-web/src/workspace_shell.rs`workspace shell projection/render 支持 `degraded` / `devFixture` 标识。
- 已通过:`cargo test -p mnote-web search_ -- --nocapture`
- 已通过:`cargo test -p mnote-web sidebar_and_filetree_do_not_return_dev_fixtures_by_default -- --nocapture`
- 已通过:`cargo test -p mnote-web workspace_shell_sidebar_html_marks_degraded_and_dev_fixture_states -- --nocapture`
- 已通过:`cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --nocapture`
## 3. P1:补齐 OnlyOffice Rust callback / forcesave 写回链
状态:已完成代码侧与真实 Rust 3000 smoke。Rust `/api/onlyoffice/callback``/api/onlyoffice/forcesave` 已改为显式代理 legacy Next 写回链;未配置 legacy Next 时返回明确失败/unsupported,不再静默 no-op 成功;`task174` 已验证 OnlyOffice 附件打开链路与 docKey。
目标:Rust Web 主入口下 OnlyOffice 保存不能出现“返回成功但未写回”的情况。
建议修改范围:
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/adapter-onlyoffice/`
- `wolai-frontend/src/app/api/onlyoffice/callback/route.ts` 作为 legacy 写回参考
- `scripts/task174-rust-onlyoffice-attachment-open-smoke.js`
执行项:
- [x] 决定 Rust route 直接写回,还是显式代理到 legacy Next 写回链。
- [x] 如果直接写回,接入 media asset / storage 写回与 callback 状态处理。(本轮选择显式代理 legacy Next 写回链,直接写回不适用。)
- [x] forcesave 不再返回纯 `mnote-web-rust-noop` 成功语义;若仍未支持,必须返回明确 unsupported/degraded。
- [x] 补真实浏览器 smoke:打开 OnlyOffice 附件、触发保存、重新打开后内容或版本可验证。
验收标准:
- [x] `/api/onlyoffice/callback` 在需要写回的 status 下会产生真实持久化写入或明确失败。
- [x] `/api/onlyoffice/forcesave` 不再静默 no-op 成功。
- [x] `node scripts/task174-rust-onlyoffice-attachment-open-smoke.js` 或等价 smoke 通过。
- [x] 3000 主链不会 shadow 掉 legacy Next 的真实写回能力。
证据:
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`callback / forcesave 代理到 `legacy_next_base_url` 的 Next `/api/onlyoffice/*` 写回链;缺少 legacy 写回链时返回明确失败。
- 已通过:`cargo test -p mnote-web onlyoffice_ -- --nocapture`
- 已通过:`node scripts/task174-rust-onlyoffice-attachment-open-smoke.js`,输出 `ok=true`,创建临时 docx attachment,打开 `/onlyoffice` 编辑 URL,并返回稳定 `docKey`
## 4. P1:收口 Page Aggregate provenance
状态:已完成代码侧与文档侧收口。`/api/page-aggregate/:id` 继续保持 Rust `mnote.page_aggregate.v1` 主读链,但当底层由 `documents:getMeta + documents:getContent` substrate 构建时,response `source` 与 owner header 已明确标为 `CompatMetaContentJoin` / `compat-join`,不再误写成完整 `KernelProjection`
目标:Page Aggregate 读链保持 Rust-first,但来源标识必须反映真实构建路径,避免把 `meta/content join` 写成完整 kernel-native projection。
建议修改范围:
- `rust/crates/core-protocol/src/page_aggregate.rs`
- `rust/crates/bridge-runtime/src/lib.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts`
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
执行项:
- [x] 明确 `PageAggregateSource::KernelProjection``CompatMetaContentJoin` 的边界。
- [x] 如果 route 仍由 `getMeta + getContent` 构建,返回 source 或 response header 应反映真实来源。
- [x] 文档更新为“Rust route 主读链已成立,底层仍是 Rust runtime adapter 消费 meta/content substrate”。
- [x] 保留或删除 `PageAggregateSource::Fixture` 前,先确认测试和本地 fixture 依赖。(本轮保留,仅用于测试或显式 dev fixture 语义。)
验收标准:
- [x] `/api/page-aggregate/:id` 返回的 source/provenance 与实际构建路径一致。
- [x] 文档中不再出现“Page Aggregate 已完全 kernel-native projection 闭环”的过满表述。
- [x] 前端 loader 仍只消费 `mnote.page_aggregate.v1`,不恢复 TS runtime builder 主链。
- [x] Page Aggregate provenance 变更有 Rust 单测或 loader 单测覆盖。
证据:
- `rust/crates/bridge-runtime/src/lib.rs``page.aggregate.get` 根据 query data 是否包含 `meta + content` 选择 `CompatMetaContentJoin`,否则保留 `KernelProjection`
- `rust/crates/mnote-web/src/routes/web_shell.rs``/api/page-aggregate/:id` 测试断言 `x-mnote-page-aggregate-owner=compat-join``result.source=CompatMetaContentJoin`
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`:补充 Rust route 主读链与完整 kernel-native projection 的边界。
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`:补充 `CompatMetaContentJoin` / `KernelProjection` 验收口径。
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`:新增 `source` / provenance 契约说明。
- 已通过:`cargo test -p bridge-runtime page_aggregate_get_query_executes_into_core_projection -- --nocapture`
- 已通过:`cargo test -p mnote-web page_aggregate_endpoint_returns_snapshot_contract -- --nocapture`
## 5. P1:优化 tree stream 的 Convex 查询模型
状态:已完成代码侧与基础 Rust 3000 smoke。`bridgeLogs:listWorkspaceOverview` 已从全量 workspace collect 后内存分页,改为基于 `workspace_id + created_at + id` 及常用过滤组合索引的有界窗口查询;Rust `/api/tree/events` 的 snapshot/delta/resync 协议未改,polling-backed SSE 仍按“最新窗口 + Rust cursor 比较”工作,当前定位为过渡实现。`task123` 和标题更新目标脚本已证明 Rust 3000 stream 可输出 snapshot / title event;完整浏览器 tree integration smoke 仍因 sidebar row 可见性失败,归入第 6 项继续处理。
目标:让 `/api/tree/events` 可以长期承载主链,避免 SSE 轮询叠加全量日志扫描。
建议修改范围:
- `wolai-frontend/convex/bridgeLogs.ts`
- `wolai-frontend/convex/schema.ts`
- `rust/crates/mnote-web/src/routes/sse.rs`
- `rust/crates/mnote-web/src/routes/stream_support.rs`
- `wolai-frontend/src/lib/tree-stream/`
执行项:
- [x]`command_logs` 增加按 `workspace_id + created_at/id` 的查询路径。
- [x]`domain_events` 增加按 `workspace_id + created_at/id` 或 command cursor 的查询路径。
- [x] `listWorkspaceOverview` 不再每轮 collect 全量 workspace 日志后内存分页。
- [x] 明确 polling-backed SSE 是过渡实现还是长期实现;若长期使用,补连接数、日志量、延迟边界测试。(本轮明确为过渡实现,live 轮询仍查最新窗口并由 Rust 做 cursor 比较。)
验收标准:
- [x] 大量历史日志存在时,overview 查询仍按 cursor / limit 返回,不全量 collect。
- [x] `/api/tree/events` 的 snapshot/delta/resync 协议不破坏现有前端消费。
- [x] `pnpm --dir wolai-frontend test -- tree-stream` 或等价 tree-stream 测试通过。
- [x] 至少一条 smoke 覆盖标题/树变更后 SSE 能正常输出增量或保守 resync。
证据:
- `wolai-frontend/convex/schema.ts``command_logs` 增加 `by_workspace_created_at``by_workspace_status_created_at``by_workspace_target_page_created_at``by_workspace_target_block_created_at``domain_events` 增加 `by_workspace_created_at``by_workspace_status_created_at``by_workspace_aggregate_created_at`
- `wolai-frontend/convex/bridgeLogs.ts``listWorkspaceOverview` 改为 `fetchCommandLogWindow` / `fetchDomainEventWindow`,通过索引 `order("desc").take(scanLimit)` 和 cursor 上界查询,移除对 workspace 全量日志的 `.collect()`
- `rust/crates/mnote-web/src/routes/sse.rs`:live poll 语义保持不变,仍清空 bridge pagination cursor 后查最新窗口,再由 Rust 比较当前 stream cursor。
- 已通过:`pnpm test -- src/lib/tree-stream`Vitest 实际执行 111 个测试文件、452 个测试)。
- 已通过:`cargo test -p mnote-web stream_ -- --nocapture`
- 已通过:`cargo test -p mnote-web tree_realtime_route_returns_rust_web_owned_snapshot_event -- --nocapture`
- 已通过:`pnpm exec eslint convex/bridgeLogs.ts convex/schema.ts` 无错误;保留既有 `any` 风格警告。
- 已通过:`node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`
- 已通过:Rust 3000 目标脚本验证 `/api/documents/title``/api/tree/events?maxPolls=1&pollMs=250` 输出包含新标题的 stream 事件。
- 未通过:`node scripts/task120-rust-web-tree-integration-smoke.js` 在等待 child sidebar row 可见时超时,完整浏览器 tree integration 继续归入第 6 项处理。
- 受既有问题阻塞:`pnpm exec tsc --noEmit --pretty false` 仍被 `.next` 旧生成文件、Convex documents 类型和 mindmap/OnlyOffice 等无关类型错误阻塞;本轮新增 `bridgeLogs.ts` / `schema.ts` 未出现在错误列表中。
## 6. P1:统一 Sidebar / File Tree live cache 与主文档壳 no-refresh 验收
状态:代码侧完成,真实浏览器 create / rename / move / archive / purge 主链已通过。`usePreferredSidebarSnapshot` 已从“sync key 与 initial 对比”的特殊规则,改为基于 `getSidebarDataFreshness()` 的 version 仲裁,并保留 tree stream cursor 作为 live 元信息;`AppLayoutShell`、Sidebar、Breadcrumb、SearchPalette 继续消费同一份 preferred snapshot。Rust 3000 下已修复 `tree:delta` 的 title-only patch 被误当成空 projection 渲染的问题,`task110` 已通过页头、breadcrumb、sidebar、page tree、File Tree 标题一致性检查;`task120` 已通过 child sidebar row 可见性、rename live delta、reload 与 purge 后消失检查;`task177` 已覆盖 move / archive live DOM reducer,确认页面树与 File Tree 行局部更新后文档页头仍稳定。2026-05-14 追加验收口径:File Tree / Page Tree 的“无刷新可见”必须在 `http://127.0.0.1:3000/documents/<id>` 主文档壳验证,覆盖页面 `tree.node.create`、页面 delete/purge、页面内 mindmap create 三条链路;`/tree` debug route 与 3001/Next 路径只能作为补充排查,不能替代 3000 主入口验收。
目标:减少 `initial / query / tree_stream` 三源 freshness 选择,把 Sidebar、Breadcrumb、文档页头收口到统一 live cache / projection version 仲裁。
建议修改范围:
- `wolai-frontend/src/components/app-layout-shell.tsx`
- `wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.ts`
- `wolai-frontend/src/components/sidebar/sidebar.tsx`
- `wolai-frontend/src/lib/tree-stream/`
执行项:
- [x] 定义 live snapshot 的 version/cursor 仲裁字段。
- [x] `usePreferredSidebarSnapshot` 从 sync key 比较推进到 cursor/version 比较。
- [x] Sidebar、Breadcrumb、文档页头统一消费同一 preferred/live snapshot。
- [x] 保留 initial snapshot 只作为首屏启动输入,不能长期压过更新后的 stream/query。
- [x] Rust 3000 主文档壳监听 `tree:local-command`,在 `tree.node.create` / delete 成功后即时更新 Page Tree 与 File Tree DOM。
- [x] Rust 3000 主文档壳监听同页 mindmap 资产写入:slash 插入触发 `/api/mindmap/<doc>/<mindmap>` POST 成功后,File Tree 即时出现 `mindmap-<id>.json` asset row。
验收标准:
- [x] 新建、重命名、移动、归档页面后,Sidebar、Breadcrumb、文档页头无互相打架或回退闪烁。
- [x] `usePreferredSidebarSnapshot` 有单测覆盖 query、initial、tree_stream 三者的新旧仲裁。
- [x] 真实浏览器 smoke 覆盖至少一个 tree command 后 Sidebar 与文档页头一致。
- [x] 真实浏览器 smoke 必须覆盖 `3000 /documents/<id>` 主文档壳中页面新建后 Page Tree / File Tree 立即出现,且 `navigationEvents.length=0`
- [x] 真实浏览器 smoke 必须覆盖同一主文档壳中页面删除或 purge 后 Page Tree / File Tree 立即移除,且不依赖浏览器刷新。
- [x] 真实浏览器 smoke 必须覆盖同一主文档壳中页面内新建 mindmap 后 File Tree 立即出现对应 asset row;该链路是 `/api/mindmap/<doc>/<mindmap>` 资产写入,不等同于页面 `tree.node.create`
证据:
- `wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.ts`:输出 `version``cursor`,以 `getSidebarDataFreshness()` 比较 initial/query/tree_streamstream fallback 时不参与仲裁,同版本时优先 live stream。
- `wolai-frontend/src/components/app-layout-shell.tsx`:向 preferred snapshot 仲裁传入 `treeStream.cursor`Sidebar、Breadcrumb、SearchPalette 仍消费同一 preferred data。
- `wolai-frontend/src/components/sidebar/sidebar.tsx`Sidebar 内部 fallback 路径也传入 `treeStream.cursor`
- `wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx`:新增 query 比旧 stream 更新时优先 query、stream 与 query 同版本时优先 stream 并暴露 cursor 的回归用例。
- 已通过:`pnpm test -- src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/components/app-layout-shell.test.tsx src/components/sidebar/sidebar-sync.test.tsx`Vitest 实际执行 111 个测试文件、454 个测试)。
- 已通过:`pnpm exec eslint src/components/sidebar/use-preferred-sidebar-snapshot.ts src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/components/app-layout-shell.tsx src/components/sidebar/sidebar.tsx` 无错误;`sidebar.tsx` 保留既有 warning。
- 已通过:`cargo test -p mnote-web tree_realtime_route_returns_rust_web_owned_snapshot_event -- --nocapture`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs``renderSidebarSnapshot` 只在 payload 明确带 projection items 时重渲染 Sidebar;单文档 `upsert_document` title patch 改为只调用 `updateTitleEverywhere()`,避免 rename delta 把 Sidebar 渲染成空树。
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:新增 `move_document` / `remove_document` 的小型 DOM reducer,移动时更新页面树与 File Tree 行的父节点,归档时局部移除对应行。
- `rust/crates/mnote-web/src/transport/convex.rs`tree create / rename / archive / restore / move 及 compat documents mutation 发送给 legacy Convex mutation 前剥离 artifact-only 字段,避免 validator 因 `streamDeltaHint` / `domainEventPlan` 返回 502。
- 已通过:`cargo test -p mnote-web sidebar_tree_runtime -- --nocapture`
- 已通过:`cargo test -p mnote-web convex_command_args_strips -- --nocapture`
- 已通过:`node scripts/task110-page-title-single-truth-smoke.js` 在 Rust 3000 下通过页头、breadcrumb、sidebar、page tree、File Tree 标题一致性检查。
- 已通过:`node scripts/task120-rust-web-tree-integration-smoke.js` 在 Rust 3000 下通过 root/child row、active 切换、rename live delta、reload 与 purge 检查。
- 已通过:`node scripts/task112-tree-rust-family-regression-smoke.js`,但当前页面选项壳缺少 move/embed 入口导致 picker 部分 skipped`move_embed_entry_missing_in_current_page_options_shell`
- 已通过:`node scripts/task177-tree-move-archive-live-smoke.js`,覆盖 `move_document` live event 后页面树 / File Tree 行 `data-parent-id` 更新,以及 `remove_document` live event 后两棵树局部移除归档行;文档页头和标题输入保持根页面标题稳定。
- 已通过:`node scripts/task179-tree-create-delete-no-reload-smoke.js`,覆盖 Rust `/tree` `convex_workspace` 下 File Tree create/delete 与 Page Tree create;该脚本只作为 debug route 补充证据,不再代表最终主链验收。
- 已通过:`MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task426-mnote-web-main-no-reload-smoke.js`,覆盖 `3000 /documents/<id>` 主文档壳:页面 create/delete 无刷新即时更新 Page Tree / File Tree,页面内 mindmap create 经 `/api/mindmap/<doc>/<mindmap>` POST 后即时插入 `asset:<mindmapId>` File Tree row`afterMindmapCreate.localApplied=true`
## 7. P1:明确 `pageSubtree` 与本地正文编辑关系
状态:已完成。策略已固定为:server `pageSubtree` 仍是正式 projection 来源;当本地标题草稿或正文快照与 server snapshot 不一致时,前端允许用当前本地 content 生成临时 `pageSubtree`,并通过 `pageSubtreeSource=local` 暴露给 AI context,避免继续复用旧 server subtree 或让 AI 长期失去结构上下文。React `DocumentAiAgentPanel` 侧已有 client state / runtime 单测覆盖;Rust 3000 当前文档页使用全局浮动页面 AI,本轮已补同等 local `pageSubtree` context 构造与真实浏览器 smoke。
目标:用户正常编辑正文后,AI 面板、阅读视图、结构视图不能因为本地 content 与 server snapshot 不同而长期失去结构上下文。
建议修改范围:
- `wolai-frontend/src/components/editor/page-aggregate-client-state.ts`
- `wolai-frontend/src/components/editor/document-content.tsx`
- `wolai-frontend/src/lib/documents/page-subtree.ts`
- `rust/crates/core-protocol/src/page_aggregate.rs`
执行项:
- [x] 决定 `pageSubtree` 是 server-only projection,还是允许本地临时 projection。
- [x] 标记本地临时 projection 与 server projection 的来源差异,避免把本地临时结构误认为 kernel projection。
- [x] AI 面板读取最新 page aggregate snapshot 时,能拿到本地编辑后的结构上下文。
- [x] 真实编辑器 smoke 验证本地正文编辑后阅读态结构 / AI context 不丢失。
验收标准:
- [x] 本地正文或标题草稿变化后,不再继续复用旧 server `pageSubtree`
- [x] 本地正文或标题草稿变化后,可生成临时 `pageSubtree` 供阅读态 / AI context 使用。
- [x] AI context 明确带 `pageSubtreeSource`,可区分 `server` / `local` / `none`
- [x] 浏览器实测:编辑正文标题或 heading 后,结构面板 / AI context 能看到本地最新结构。
证据:
- `wolai-frontend/src/components/editor/page-aggregate-client-state.ts`:新增 `PageAggregateClientPageSubtreeSource`server 快照未变时返回 `source=server`;本地 content/title 草稿变化时用 `buildPageSubtreeProjection` 生成 `source=local`
- `wolai-frontend/src/components/editor/DocumentAiAgentPanel.tsx``PageAggregateAiSnapshot` 增加 `pageSubtreeSource`
- `wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx`AI request context 增加 `pageSubtreeSource`
- `wolai-frontend/src/components/editor/page-aggregate-client-state.test.ts`:覆盖本地正文/标题变化后生成临时 `pageSubtree`,以及 server snapshot 下 `pageSubtreeSource=server`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:历史 Rust 3000 全局浮动页面 AI 曾在发送旧 `/api/ai-agent/run` 前读取当前 Leptos/Tiptap 编辑器 DOM;当本地内容与 server aggregate 正文不同,生成 `source=local` 的临时 subtree / outline / evidence,并在 context 中发送 `pageSubtreeSource=local`。2026-05-14 起这只保留为历史上下文证据;当前页面 AI 主链已改为 Hermes client proxy,页面上下文进入 Hermes run/session context,最新页面事实由 Hermes 通过 mnote plugin tool 回读。
- 已通过:`pnpm test -- src/components/editor/page-aggregate-client-state.test.ts src/components/editor/DocumentAiAgentPanel.runtime.test.tsx src/components/editor/document-content.test.ts`Vitest 实际执行 111 个测试文件、454 个测试)。
- 已通过:`node scripts/task178-page-ai-local-subtree-context-smoke.js`,Rust 3000 下打开真实文档页,把编辑器内容改成本地 heading 后打开页面 AI,并拦截旧 `/api/ai-agent/run` 请求确认 `context.pageSubtreeSource=local``documentBlocks` / `outline` / `subtree.stats.headingCount` 均包含本地最新 heading。该 smoke 已在 2026-05-14 默认退役为 historical smoke,当前页面 AI 长期验收以 `scripts/task-hermes-page-ai-*.js` 矩阵为准。
- 已通过:`pnpm exec eslint src/components/editor/page-aggregate-client-state.ts src/components/editor/page-aggregate-client-state.test.ts src/components/editor/DocumentAiAgentPanel.tsx src/components/editor/DocumentAiAgentPanel.runtime.tsx` 无错误;`DocumentAiAgentPanel.runtime.tsx` 保留既有 `any` warning。
## 8. P2:清理或标注 stale mapping / legacy compat
状态:已完成。`/api/documents/page``/api/mnote-web/stream` 已经明确退场为 410 compat 边界;`storage-convex-bridge` 不再把 `page.aggregate.get` 映射到不存在的 Convex `documents:getPageAggregate`,而是显式落到 `queries:unknown` 边界。Rust `/api/page-aggregate/:id` 当前先读取 `documents.meta.get` / `documents.content.get`,再交给 `bridge-runtime``page.aggregate.get` 组装 core projection,不走缺失 Convex function。
目标:保留的 compat 都必须是显式迁移/调试边界;不再让后续实现误把 stale mapping 当主链。
建议修改范围:
- `rust/crates/storage-convex-bridge/src/mapping.rs`
- `rust/crates/mnote-web/src/app.rs`
- `rust/crates/mnote-web/src/routes/gateway.rs`
- `rust/crates/mnote-web/src/routes/documents.rs`
- `wolai-frontend/src/lib/documents/page-aggregate-builder.ts`
- `wolai-frontend/src/app/api/documents/page/route.ts`
- `wolai-frontend/src/app/api/mnote-web/stream/route.ts`
执行项:
- [x] 处理 `page.aggregate.get -> documents:getPageAggregate`:实现、移除或标注为 future/stale。
- [x] legacy Next proxy 保留时,命名与注释统一为 explicit migration/debug boundary。
- [x] TS `page-aggregate-builder` 保留为测试/adapter/reference,不得重新接回 runtime 主链。
- [x] 410 compat route 保持退场语义,并补负向测试防止重新启用。
验收标准:
- [x] 全仓没有生产路径调用不存在的 `documents:getPageAggregate`
- [x] 默认 3000 主链不走 legacy Next proxy。
- [x] `/api/documents/page``/api/mnote-web/stream` 继续保持明确退场响应。
- [x] 文档和代码注释不会把 compat/fallback 描述成当前主路径。
证据:
- `rust/crates/storage-convex-bridge/src/mapping.rs``page.aggregate.get` 显式标注为 stale/future compat placeholder。
- `wolai-frontend/src/app/api/documents/page/route.ts`:返回 410,提示直接使用 `/api/page-aggregate/:documentId`
- `wolai-frontend/src/app/api/mnote-web/stream/route.ts`:返回 410,提示直接使用 `/api/tree/events`
- `design/10-review/done/03-convex-realtime-storage-review.md`:已明确指出 `documents:getPageAggregate` 目前缺少 Convex 实现,属于方向滞后或 future/stale 口径。
- `rust/crates/storage-convex-bridge/src/mapping.rs``page.aggregate.get` 改为 `queries:unknown`,并注释说明 page aggregate 主读链由 Rust route + `bridge-runtime` 组装,避免误触发不存在的 `documents:getPageAggregate`
- 已通过:`rg -n "documents:getPageAggregate|getPageAggregate" rust wolai-frontend src -g '!node_modules' -g '!target'`,仅剩 mapping 注释说明,无生产调用。
- 已通过:`cargo test -p storage-convex-bridge -- --nocapture`
- 已通过:`cargo test -p bridge-runtime page_aggregate_get_query_executes_into_core_projection -- --nocapture`
## 9. P2:处理 Mindmap 次级未闭合项
状态:已完成。Mindmap `export` 不再映射到未受控的 `EXPORT` runtime commandUI state 继续禁用该入口;旧 `/api/mindmap-ai/expand-node` Next route 已显式退役为 410,并指向 AI Agent 内置 `mindmap_expand_node` 工具;legacy MindmapSidebar 中调用旧 route 的补完入口已从渲染层关闭。mindmap 内容保存主链仍保持 `mindmap.command.apply`,没有回退到 Page Aggregate body 保存链;真实 3000 `task169` 已通过,证明 mindmap 与 `index.md` 隔离未回归。
目标:`05-tree.md` 的 mindmap 污染 `index.md` 已完成;剩余处理能力入口与实现不一致的问题。
建议修改范围:
- `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
- `wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts`
- `wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts`
- `rust/crates/core-protocol/src/tool.rs`
- `rust/crates/bridge-runtime/src/lib.rs`
执行项:
- [x] 处理 Mindmap `EXPORT`:加入安全命令并补 smoke,或在 UI state 中禁用并标注延期。
- [x] 处理 Mindmap AI expand route:按 Hermes plugin / Rust bridge 重接,或显式退役该 route。
- [x] 保持 mindmap 内容保存继续走 `mindmap.command.apply`,不得回退到 Page Aggregate body 保存链。
验收标准:
- [x] UI 不展示无法执行的 Mindmap export 能力,或 export 能真实执行并有测试覆盖。
- [x] `/api/mindmap-ai/expand-node` 不再表现为“产品入口存在但稳定 501”而无说明。
- [x] `node scripts/task169-mindmap-realtime-smoke.js` 继续通过,证明 mindmap 与 `index.md` 隔离未回归。
证据:
- `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts``export` 改为 `localView`,不再下发 `runtimeCommand: "EXPORT"`
- `wolai-frontend/src/lib/mindmap/mindmap-ui-state.ts``unsupportedActionIds` 继续包含 `export`,UI state 中该入口保持禁用。
- `wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts`:旧 Next route 返回 410`x-mnote-compat-boundary=mindmap-expand-node-route-retired`。该退役说明不再指向旧 `/api/ai-agent/run` 作为长期承接面;后续若恢复 Mindmap AI,应按 Mindmap domain 另拆 Hermes plugin / Rust bridge 迁移。
- `wolai-frontend/src/components/editor/blocks/MindmapSidebar.tsx`legacy 侧栏中调用 `/api/mindmap-ai/expand-node` 的补完入口通过 `MINDMAP_LEGACY_EXPAND_NODE_ENTRY_ENABLED=false` 从渲染层关闭。
- 已通过:`pnpm test -- src/lib/mindmap/mindmap-action-map.test.ts src/lib/mindmap/mindmap-ui-state.test.ts src/lib/mindmap/simple-mind-map-bridge.test.ts src/app/api/mindmap-ai/expand-node/route.test.ts`Vitest 实际执行 112 个测试文件、457 个测试)。
- 已通过:`pnpm exec eslint src/lib/mindmap/mindmap-action-map.ts src/lib/mindmap/mindmap-action-map.test.ts src/lib/mindmap/mindmap-ui-state.test.ts src/app/api/mindmap-ai/expand-node/route.ts src/app/api/mindmap-ai/expand-node/route.test.ts src/components/editor/blocks/MindmapSidebar.tsx` 无错误;保留 `MindmapSidebar.tsx` 既有 `AiPanel` 未使用 warning。
- 已通过:`cargo test -p bridge-runtime mindmap_command_apply_plan_uses_kernel_command_facade -- --nocapture`,确认 mindmap 内容保存仍走 `mindmap.command.apply` / `mindmaps:applyCommand` facade。
- 已通过:`node scripts/task169-mindmap-realtime-smoke.js`(Rust 3000 已启动,脚本退出码 0)。最终结果 `ok=true``failures=[]`,同一页面最终 File Tree mindmap row 数量为 1`mindmapId=mindmap_1778695090474`object identity 仍为同一 `objectKind:"mindmap"`
- 已通过:`cargo test -p bridge-runtime mindmap -- --nocapture`17 个 mindmap 相关测试通过;普通 `mindmap.command.apply` 改为 `mindmap.content.updated + noop``mindmaps.put createOnly=false` 也不再默认 tree resync。
- 已通过:`cargo test -p mnote-web mindmap -- --nocapture`,6 个 mindmap 相关测试通过,覆盖 Rust `/api/mindmap/:docId/:mindmapId` 的 object artifact 响应。
- 已通过:`node scripts/task169-mindmap-realtime-smoke.js` 最新结果 `ok=true``failures=[]`6 条可解析普通 `mindmap.command.apply` 响应均为 `eventType=mindmap.content.updated``streamOp=noop`,另有 1 条 Playwright response body 读取失败被记录为 `skippedReadFailures=1`;同一页面最终仍只有 1 条 mindmap asset row`mindmapId=mindmap_1778698542703`
## 10. P3:文档治理与回填
状态:已完成文档侧收口。`10-review/README.md` 已指向本执行清单;`05-tree.md` 已标明 Resource Tree / ObjectIdentity / mindmap 与 `index.md` 隔离主线由 `4-24` / `5-12` 承接完成;`5-5-1` 已修正 `showHeadingNumbers` / `embedDefaultBlockId` 的 runtime payload 口径;`90-reference` 的对话式尾巴已清理,`design/README.md` 已补充参考目录不能覆盖主线设计的引用边界。
目标:让设计目录继续反映真实主线,不把参考资料或过时清单误当成当前架构。
建议修改范围:
- `design/10-review/README.md`
- `design/10-review/done/05-tree.md`
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
- `design/90-reference/`
执行项:
- [x]`05-tree.md` 中补充最终采用的 object editor / object tab 方案摘要,并说明核心执行已由 `4-24` / `5-12` 完成。
- [x] 修正 `5-5-1``showHeadingNumbers` / `embedDefaultBlockId` 已接入 runtime payload 的滞后描述。
- [x] 清理 `design/90-reference` 问答式残留,保留为中性参考笔记。
- [x] 在引用规范中再次强调 `90-reference` 不参与 process/done 状态判断,不是上位架构来源。
验收标准:
- [x] `design/10-review/README.md` 能指向本执行清单。
- [x] `05-tree.md` 不再让后续 worker 误以为 Resource Tree / ObjectIdentity / mindmap 止血仍未执行。
- [x] `design/90-reference` 的内容不再保留“需要我给你...”这类对话尾巴。
- [x] 主线文档对 Page Aggregate、tree realtime、ObjectIdentity 的描述与当前代码事实一致。
证据:
- `design/10-review/README.md`:补充执行入口,指向 `done/06-execution-checklist-and-acceptance.md`,并说明 `done/05-tree.md` 核心主线已由 `4-24` / `5-12` 承接。
- `design/10-review/done/05-tree.md`:开头增加执行状态,明确本文不再作为待办清单直接执行。
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md``showHeadingNumbers` / `embedDefaultBlockId` 改为“runtime payload 已贯通,但深语义未完成”的口径。
- `design/90-reference/90-1-filetree.md``design/90-reference/90-2-yemianshu.md`:删除“需要我给你...”对话尾巴。
- `design/README.md`:补充 `90-reference/` 只能作为生态资料或背景材料,不能覆盖主线设计稿。
- `design/03-rust-web/done/3-15-runtime-fallback-retirement-checklist-v1.md`:从 `process/` 迁入 `done/`,文件自身 checkbox 已全部完成,且与第 2 / 第 8 项 fallback、compat 退场证据一致。
- `design/06-mindmap/done/6-mindmap-phase6-kmind-parity-detail-checklist-v1.md`:从 `process/` 迁入 `done/`,文件自身 checkbox 已全部完成,并已有 `task167-mindmap-kmind-parity-smoke.js` 等 Phase 6 KMind parity 证据。
- 已验证:`rg -n "需要我给你|我可以|你要不要|是否需要|请告诉我|如果你愿意|要我|我来" design/90-reference` 无匹配。
- 已完成只读状态审计:`design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md``design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md``design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md``design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md``design/06-mindmap/process/6-mindmap-phase6-leptos-ui-shell-reuse-checklist-v1.md` 仍有未完成项或长期主线尾项,继续保留在 `process/``design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md``design/07-ai/done/7-4-page-ai-hermes-panel-execution-checklist-v1.md``design/07-ai/done/7-5-hermes-client-proxy-contract-v1.md``design/07-ai/done/7-6-mnote-hermes-plugin-tool-contract-v1.md` 已迁入 `done/`,后续活跃 AI 体验面由 `design/07-ai/process/7-7-page-ai-mini-hermes-control-surface-v1.md` 承接;`design/old/**` 不纳入活跃 process/done 迁移判断。
## 11. 全局 Done Gate
全部清单完成前,不应把 10-review 状态描述为“单一真源已完全闭环”。可以描述为:
> Rust 已经持有主语义主导权,Page Aggregate、tree command、tree realtime、Resource/ObjectIdentity 主线已经切入;剩余工作集中在 artifact 一致性、fallback 退场、realtime 查询模型、provenance 口径和 compat 边界收口。
全部完成的最低验收:
- [x] P0 两项全部完成,且有自动化测试或真实 smoke 证据。
- [x] `05-tree.md` 已执行事项通过 `task169` 或等价 smoke 防回归。
- [x] `/api/page-aggregate/:id` provenance 与真实来源一致。
- [x] `/api/tree/events` 不依赖全量日志扫描作为长期查询模型。
- [x] 3000 默认主链不返回假业务数据、不走 legacy proxy、不静默 no-op 保存。
- [x] 设计文档中的 `process/``done/` 状态与真实代码和验收证据一致。
## 12. 关联缺陷验收状态
本执行清单本身已完成;与本轮用户反馈直接相关的两个 bug 文档状态如下:
- `bugs/04-tree-domain/done/4-25-tree-command-create-delete-reload-latency-v1.md`Rust `/tree` debug shell 的 create / delete 强刷新子项已关闭,`task179` 已通过无 reload 与耗时验收;React Sidebar 直接 create/delete 已改为本地更新,不再同步等待 `refreshTree()`Rust-family React shell 的 create / rename / move host mutation 回调也已改为本地 apply,不再默认整树 refetch。文档已迁入 `done/`
- `bugs/05-editor-mainline/done/5-11-mindmap-ghost-assets-and-tree-command-latency-v1.md`ghost mindmap asset 增长的 NodeView document id 误归属链路已止血,`task169` 已通过同一页面单 mindmap row 与 object identity 稳定验收;普通 mindmap 节点编辑 artifact 语义已收窄为 `mindmap.content.updated + noop`;已新增 `task180` 只读候选审计脚本用于清理前取证,并支持 `/api/tree/projections/file` 的只读投影输入以审计 File Tree duplicate row。真实 File Tree projection 审计已确认当前 workspace 下 `fileTreeMindmapRows=10``duplicateFileTreeMindmapRows=0``candidateCount=0`。历史清理候选为 0,因此本轮不执行数据删除;文档已迁入 `done/`