Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
41 KiB
[recycle] 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 artifact;Rust 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.rsrust/crates/mnote-web/src/routes/command_support.rsrust/crates/mnote-web/src/transport/convex.rs- 必要时补充
wolai-frontend/convex/bridgeLogs.ts查询侧验证
执行项:
- 将 Rust
/api/documents/title的命令执行切到 artifact 记录路径,或统一复用正式 page/tree command route。 - 将 Rust
/api/documents/options的命令执行切到 artifact 记录路径,或统一复用正式 page/tree command route。 - 明确 artifact 写失败策略:主 mutation 成功但 artifact 失败时至少要有可观测错误,并触发保守 resync 或明确降级。
- 补回归测试:标题更新后,
bridgeLogs:listWorkspaceOverview能看到对应 command/domain event。
验收标准:
cargo test -p mnote-web documents_title -- --nocapture或等价标题 route 测试通过。cargo test -p mnote-web documents_options -- --nocapture或等价页面设置 route 测试通过。- 手动或脚本验证:更新标题后
/api/tree/events输出 delta 或 resync,不需要刷新整页才能看到 sidebar/breadcrumb 变化。 - 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_updateddomain event 与resync_requiredstream delta。rust/crates/mnote-web/src/transport/convex.rs:发送给 legacydocuments: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 个测试),Nextdocuments/titleroute 与 page write adapter 继续生成page.head.updateTitlecanonical 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 empty;Sidebar/File Tree dev fallback 仅在 allow_dev_fixtures 开启时可用,并带隐藏 debug/dev 标识。
目标:失败、超时、Convex 不可用时,不再返回看起来像真实业务数据的 fixture / fallback。
建议修改范围:
rust/crates/mnote-web/src/routes/search.rsrust/crates/mnote-web/src/routes/web_shell.rsrust/crates/mnote-web/src/routes/snapshot_support.rs
执行项:
- 优先移除或强门禁 Search 的
fallback_search_dataset。 - 对 workspace shell projection 加载失败的最小合成数据做决策:返回明确 degraded/empty/error,不能伪装成真实 workspace projection。
- 对
allow_dev_fixtures相关 sidebar/filetree fallback 增加明确 debug/dev 标识,并确认 3000 默认启动链不会误开。 - 增加负向 smoke:Convex/query 不可用时,Search、Sidebar、File Tree 不返回固定假业务数据。
验收标准:
- 关闭或破坏 Convex query 后,Search 不返回内置固定结果。
- 关闭或破坏 projection 加载后,workspace/sidebar/filetree 响应带明确错误、空状态或 degraded 标记。
- 3000 默认入口不会出现 fixture workspace、fixture document、fixture filetree row。
- 相关负向测试或 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 dataset;Sidebar/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.rsrust/crates/adapter-onlyoffice/wolai-frontend/src/app/api/onlyoffice/callback/route.ts作为 legacy 写回参考scripts/task174-rust-onlyoffice-attachment-open-smoke.js
执行项:
- 决定 Rust route 直接写回,还是显式代理到 legacy Next 写回链。
- 如果直接写回,接入 media asset / storage 写回与 callback 状态处理。(本轮选择显式代理 legacy Next 写回链,直接写回不适用。)
- forcesave 不再返回纯
mnote-web-rust-noop成功语义;若仍未支持,必须返回明确 unsupported/degraded。 - 补真实浏览器 smoke:打开 OnlyOffice 附件、触发保存、重新打开后内容或版本可验证。
验收标准:
/api/onlyoffice/callback在需要写回的 status 下会产生真实持久化写入或明确失败。/api/onlyoffice/forcesave不再静默 no-op 成功。node scripts/task174-rust-onlyoffice-attachment-open-smoke.js或等价 smoke 通过。- 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.rsrust/crates/bridge-runtime/src/lib.rsrust/crates/mnote-web/src/routes/web_shell.rswolai-frontend/src/lib/documents/page-aggregate-loader.tsdesign/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.mddesign/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md
执行项:
- 明确
PageAggregateSource::KernelProjection与CompatMetaContentJoin的边界。 - 如果 route 仍由
getMeta + getContent构建,返回 source 或 response header 应反映真实来源。 - 文档更新为“Rust route 主读链已成立,底层仍是 Rust runtime adapter 消费 meta/content substrate”。
- 保留或删除
PageAggregateSource::Fixture前,先确认测试和本地 fixture 依赖。(本轮保留,仅用于测试或显式 dev fixture 语义。)
验收标准:
/api/page-aggregate/:id返回的 source/provenance 与实际构建路径一致。- 文档中不再出现“Page Aggregate 已完全 kernel-native projection 闭环”的过满表述。
- 前端 loader 仍只消费
mnote.page_aggregate.v1,不恢复 TS runtime builder 主链。 - 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/done/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.tswolai-frontend/convex/schema.tsrust/crates/mnote-web/src/routes/sse.rsrust/crates/mnote-web/src/routes/stream_support.rswolai-frontend/src/lib/tree-stream/
执行项:
- 为
command_logs增加按workspace_id + created_at/id的查询路径。 - 为
domain_events增加按workspace_id + created_at/id或 command cursor 的查询路径。 listWorkspaceOverview不再每轮 collect 全量 workspace 日志后内存分页。- 明确 polling-backed SSE 是过渡实现还是长期实现;若长期使用,补连接数、日志量、延迟边界测试。(本轮明确为过渡实现,live 轮询仍查最新窗口并由 Rust 做 cursor 比较。)
验收标准:
- 大量历史日志存在时,overview 查询仍按 cursor / limit 返回,不全量 collect。
/api/tree/events的 snapshot/delta/resync 协议不破坏现有前端消费。pnpm --dir wolai-frontend test -- tree-stream或等价 tree-stream 测试通过。- 至少一条 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.tsxwolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.tswolai-frontend/src/components/sidebar/sidebar.tsxwolai-frontend/src/lib/tree-stream/
执行项:
- 定义 live snapshot 的 version/cursor 仲裁字段。
usePreferredSidebarSnapshot从 sync key 比较推进到 cursor/version 比较。- Sidebar、Breadcrumb、文档页头统一消费同一 preferred/live snapshot。
- 保留 initial snapshot 只作为首屏启动输入,不能长期压过更新后的 stream/query。
- Rust 3000 主文档壳监听
tree:local-command,在tree.node.create/ delete 成功后即时更新 Page Tree 与 File Tree DOM。 - Rust 3000 主文档壳监听同页 mindmap 资产写入:slash 插入触发
/api/mindmap/<doc>/<mindmap>POST 成功后,File Tree 即时出现mindmap-<id>.jsonasset row。
验收标准:
- 新建、重命名、移动、归档页面后,Sidebar、Breadcrumb、文档页头无互相打架或回退闪烁。
usePreferredSidebarSnapshot有单测覆盖 query、initial、tree_stream 三者的新旧仲裁。- 真实浏览器 smoke 覆盖至少一个 tree command 后 Sidebar 与文档页头一致。
- 真实浏览器 smoke 必须覆盖
3000 /documents/<id>主文档壳中页面新建后 Page Tree / File Tree 立即出现,且navigationEvents.length=0。 - 真实浏览器 smoke 必须覆盖同一主文档壳中页面删除或 purge 后 Page Tree / File Tree 立即移除,且不依赖浏览器刷新。
- 真实浏览器 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_stream;stream 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_documenttitle 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_documentlive event 后页面树 / File Tree 行data-parent-id更新,以及remove_documentlive event 后两棵树局部移除归档行;文档页头和标题输入保持根页面标题稳定。 - 已通过:
node scripts/task179-tree-create-delete-no-reload-smoke.js,覆盖 Rust/treeconvex_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.tswolai-frontend/src/components/editor/document-content.tsxwolai-frontend/src/lib/documents/page-subtree.tsrust/crates/core-protocol/src/page_aggregate.rs
执行项:
- 决定
pageSubtree是 server-only projection,还是允许本地临时 projection。 - 标记本地临时 projection 与 server projection 的来源差异,避免把本地临时结构误认为 kernel projection。
- AI 面板读取最新 page aggregate snapshot 时,能拿到本地编辑后的结构上下文。
- 真实编辑器 smoke 验证本地正文编辑后阅读态结构 / AI context 不丢失。
验收标准:
- 本地正文或标题草稿变化后,不再继续复用旧 server
pageSubtree。 - 本地正文或标题草稿变化后,可生成临时
pageSubtree供阅读态 / AI context 使用。 - AI context 明确带
pageSubtreeSource,可区分server/local/none。 - 浏览器实测:编辑正文标题或 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,脚本现本机归档于 gitignoredrecycle/scripts/retired-ai-agent-run-smokes/;当前页面 AI 长期验收以scripts/task-hermes-page-ai-*.js和scripts/task-page-block-ai-tools-smoke.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保留既有anywarning。
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.rsrust/crates/mnote-web/src/app.rsrust/crates/mnote-web/src/routes/gateway.rsrust/crates/mnote-web/src/routes/documents.rswolai-frontend/src/lib/documents/page-aggregate-builder.tswolai-frontend/src/app/api/documents/page/route.tswolai-frontend/src/app/api/mnote-web/stream/route.ts
执行项:
- 处理
page.aggregate.get -> documents:getPageAggregate:实现、移除或标注为 future/stale。 - legacy Next proxy 保留时,命名与注释统一为 explicit migration/debug boundary。
- TS
page-aggregate-builder保留为测试/adapter/reference,不得重新接回 runtime 主链。 - 410 compat route 保持退场语义,并补负向测试防止重新启用。
验收标准:
- 全仓没有生产路径调用不存在的
documents:getPageAggregate。 - 默认 3000 主链不走 legacy Next proxy。
/api/documents/page与/api/mnote-web/stream继续保持明确退场响应。- 文档和代码注释不会把 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 command,UI 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.tswolai-frontend/src/lib/mindmap/simple-mind-map-bridge.tswolai-frontend/src/app/api/mindmap-ai/expand-node/route.tsrust/crates/core-protocol/src/tool.rsrust/crates/bridge-runtime/src/lib.rs
执行项:
- 处理 Mindmap
EXPORT:加入安全命令并补 smoke,或在 UI state 中禁用并标注延期。 - 处理 Mindmap AI expand route:按 Hermes plugin / Rust bridge 重接,或显式退役该 route。
- 保持 mindmap 内容保存继续走
mindmap.command.apply,不得回退到 Page Aggregate body 保存链。
验收标准:
- UI 不展示无法执行的 Mindmap export 能力,或 export 能真实执行并有测试覆盖。
/api/mindmap-ai/expand-node不再表现为“产品入口存在但稳定 501”而无说明。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:applyCommandfacade。 - 已通过:
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.mddesign/10-review/done/05-tree.mddesign/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.mddesign/90-reference/
执行项:
- 在
05-tree.md中补充最终采用的 object editor / object tab 方案摘要,并说明核心执行已由4-24/5-12完成。 - 修正
5-5-1中showHeadingNumbers/embedDefaultBlockId已接入 runtime payload 的滞后描述。 - 清理
design/90-reference问答式残留,保留为中性参考笔记。 - 在引用规范中再次强调
90-reference不参与 process/done 状态判断,不是上位架构来源。
验收标准:
design/10-review/README.md能指向本执行清单。05-tree.md不再让后续 worker 误以为 Resource Tree / ObjectIdentity / mindmap 止血仍未执行。design/90-reference的内容不再保留“需要我给你...”这类对话尾巴。- 主线文档对 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/done/5-6-page-aggregate-alignment-checklist-v1.md、design/06-mindmap/reference/6-mindmap-kernel-phase6-projection-editor-v1.md、design/06-mindmap/done/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 边界收口。
全部完成的最低验收:
- P0 两项全部完成,且有自动化测试或真实 smoke 证据。
05-tree.md已执行事项通过task169或等价 smoke 防回归。/api/page-aggregate/:idprovenance 与真实来源一致。/api/tree/events不依赖全量日志扫描作为长期查询模型。- 3000 默认主链不返回假业务数据、不走 legacy proxy、不静默 no-op 保存。
- 设计文档中的
process/、done/状态与真实代码和验收证据一致。
12. 关联缺陷验收状态
本执行清单本身已完成;与本轮用户反馈直接相关的两个 bug 文档状态如下:
bugs/04-tree-domain/done/4-25-tree-command-create-delete-reload-latency-v1.md:Rust/treedebug 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/。