chore: land tree view-state, vault, Pi module split, and repo hygiene

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.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
@@ -0,0 +1,105 @@
# [recycle] 1-10 [done] Batch C P1 Resource / BufferStore / GFM / Convex Evidence Checklist v1
> 创建时间:2026-05-21
>
> 上位入口:`design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
>
> 阶段:Batch C / P1 小尾项收口
>
> 当前状态:`DONE`
## 1. 目标
在 Batch B 已完成 baseline smoke 复核后,继续处理 P1 中仍会干扰后续判断的尾项:
1. `5-26` resource open resolver 仍未实际收敛。
2. `1-6` Gap A2 保存后刷新可见读回需要以真实 smoke 复核。
3. `3-13` GFM AST 迁移仍有若干可小步关闭的测试 / 过渡标记尾项。
4. `1-6` Gap C1 Convex export dry-run / conflict / rollback 仍缺独立 smoke 证据。
本批次不扩新产品面,不改 OnlyOffice 保存链路,不把 `documents/save` 重新升级为 local-first 长期正文入口。
## 2. Reasonix Worker 拆分
### Worker Aresource open resolver 最小收敛
Owner
- `design/05-editor-mainline/process/5-26-resource-open-resolver-convergence-checklist-v1.md`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task463-onlyoffice-resolver-smoke.js`
目标:
- 审查并最小收敛 `buildLocalOnlyOfficeOpenUrl``openEditorAttachmentEditTab``openEditorAttachmentNewWindow``openConvexAssetFromFileTree``resolveResourceOpen` 的重复 local Office / resource open 判断。
- 若能安全实现,只改 `layout.rs``task463`
- 必须保持:正文附件“弹窗编辑”进 main resource tab,“新窗口编辑”进浏览器窗口,md/text/code 继续走 tiptap/text/code 渲染路径。
验收:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1`
- `node --check scripts/task463-onlyoffice-resolver-smoke.js`
- 如 smoke 可运行,执行 `node scripts/task463-onlyoffice-resolver-smoke.js`
### Worker BGFM AST 尾项小包
Owner
- `design/03-rust-web/done/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`
目标:
- 补充或追认 GFM AST 尾项中低风险项:空表格单元格 / 降级策略测试、legacy->Tiptap marks 验证测试、写侧手写 Markdown 函数过渡标记。
- 不修复 frontmatter title 优先级;该项是单独行为变更,保留给后续专项。
- 不移除 web_shell legacy 适配分支,只允许加过渡注释或测试。
验收:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web web_shell -- --test-threads=1`
### Worker CConvex export 产品化补 smoke
Owner
- `design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
- `scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- 可新增 `scripts/task48*-convex-export-*.js`
目标:
- 为 C1 已有能力补 smoke:dry-run 不写文件、同名冲突不覆盖、rollback 恢复导出前状态。
- 不修改 Convex runtime,不改 local-first 架构口径。
- 优先复用现有 fixture/export 脚本,新增脚本要可离线运行。
验收:
- `node --check scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- `node scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- 新增 smoke 的 `node --check` 与实跑通过。
## 3. Codex 本地关键路径
Codex 同步处理:
- A2 保存后刷新可见读回:复现 `POST /api/page-body/write` 后刷新 editor DOM 不显示正文的问题,判断是过期证据、hydration 问题,还是当前真实回归。
- 若是 smoke 缺失,补最小 browser smoke;若是真回归,优先修复 web shell / aggregate bootstrap。
## 4. 完成条件
- Reasonix worker 均有 `final.md` / `result.json` 或明确 `TIMEOUT` 记录。
- Codex 独立审查 diff,不直接相信 worker 总结。
- 相关 targeted tests 与 browser smoke 实跑通过或形成明确 bug。
- `1-6``1-9``3-13``5-26` 文档状态更新。
- 提交前运行 `codegraph sync .``codegraph status``git diff --check`
## 5. 本轮执行记录
- 2026-05-21Codex 建立 Batch C checklist,准备派发 Worker A/B/C,并本地排查 A2 刷新读回。
- 2026-05-21Codex):新增并实跑 `task484-local-folder-page-body-refresh-readback-smoke.js`,验证 `POST /api/page-body/write` 后磁盘、Page Aggregate、刷新后 ProseMirror 均读回新正文;`1-6` Gap A2 浏览器可见读回改判为完成,剩余仅为 conflict UI 数据源接 BufferStore。
- 2026-05-21Reasonix Batch C Worker A):worker 只提交 resource resolver 收敛计划,未生成 `final.md` / `result.json`,无业务代码 diff;记录为 `PLAN_ONLY_TIMEOUT``5-26` 保持 `process`。结果文件:`.codex/reasonix-tasks/results/batch-c-worker-a-resource-open-resolver-timeout.md`
- 2026-05-21Reasonix Batch C Worker B):完成 GFM AST 尾项小包,补空引用块 / 空表格单元格测试、web_shell marks 断言、写侧 Markdown 过渡注释,并更新 `3-13`;结果文件:`.codex/reasonix-tasks/results/batch-c-worker-b-gfm-tail.md`
- 2026-05-21Reasonix Batch C Worker C):`task455-convex-export-plan-rollback-smoke.js` 实跑通过,覆盖 dry-run/conflict/rollback 三个行为,`1-6` Gap C1 已可视为完成。结果文件:`.codex/reasonix-tasks/results/batch-c-worker-c-convex-export-smokes.md`
- 2026-05-21Codex Batch F 追认):`3-13` 已移动到 `design/03-rust-web/done/`,本文作为 Batch C 执行记录不再保持活跃 process。
@@ -0,0 +1,115 @@
# [recycle] 1-11 [done] Batch D P1 小尾项执行 checklist v1
> 创建时间:2026-05-21
>
> 当前状态:`DONE`
>
> 目标:把 P1 阶段剩余的两个最小尾项拆成可独立执行的小批次,避免继续在大而散的 process 文档中发散:
> 1. 资源打开 resolver 收敛
> 2. 本地 Markdown 页面标题优先级收口
>
> 上位依据:
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-26-resource-open-resolver-convergence-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md`
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
---
## 1. 执行顺序
先跑资源打开收敛,再跑 Markdown 标题优先级。
- 资源打开收敛影响面更窄,且可通过现有 smoke 快速确认。
- Markdown 标题优先级属于读侧口径收口,改动点更集中,但会影响 page tree / aggregate 的标题展示,因此单独成批。
不要把这两个尾项和 open editors、filetree action、BufferStore conflict UI、AI 主线混在同一轮 worker 中。
---
## 2. Batch D1:资源打开 resolver 收敛
### 2.1 目标
继续对照 Sidex editor input resolver,把正文附件、FileTree asset、new-window、active-tab 的资源打开策略收敛到更一致的 helper 和断言。
### 2.2 允许修改范围
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task463-onlyoffice-resolver-smoke.js`
- `design/05-editor-mainline/process/5-26-resource-open-resolver-convergence-checklist-v1.md`
### 2.3 禁止事项
- 不修改 `rust/crates/mnote-web/src/routes/web_shell.rs`
- 不修改 `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- 不修改 ACP / Hermes 相关文件
- 不破坏文件树 edit-mode active tab 已验证行为
- 不把 `md/text/code` 改成 OnlyOffice 默认打开
### 2.4 Checklist
- [ ] 审查 `buildLocalOnlyOfficeOpenUrl``openEditorAttachmentEditTab``openConvexAssetFromFileTree` 与正文附件 open path 的重复判断。
- [ ] 提炼最小 helper,优先减少正文附件与 FileTree 的分歧。
- [ ] 保持正文附件 `弹窗编辑` 进入 main resource tab。
- [ ] 保持正文附件 `新窗口编辑` 进入浏览器新窗口。
- [ ] 保持 `md/text/code` 继续走 tiptap / 内建编辑器。
- [ ] 更新或补充 smoke 断言,覆盖正文附件编辑模式分流。
### 2.5 验收
- `cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1`
- `node scripts/task463-onlyoffice-resolver-smoke.js`
### 2.6 完成条件
这一批完成后,可以把 `5-26` 中对应重复判断项打勾,并保留剩余未完成项在原文。
---
## 3. Batch D2Markdown 标题优先级收口
### 3.1 目标
把本地 Markdown 页面标题口径收口为:
> frontmatter title > 第一条 H1 > 文件名
当前 `parse_markdown_page` 仍只按文件名生成标题,需要补齐前两级优先级,但不能破坏 `mnote_id / path-id` 相关行为。
### 3.2 允许修改范围
- `rust/crates/mnote-web/src/routes/local_markdown_parser.rs`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
### 3.3 禁止事项
- 不改 resource lifecycle / trash / open resolver 行为
- 不改 Page Aggregate 的 identity 规则
- 不引入新的大解析层或额外 adapter
- 不把标题优先级改回前端补丁
### 3.4 Checklist
- [ ]`parse_markdown_page` 增加 frontmatter title 优先级。
- [ ] 若 frontmatter 没有 title,则回退到正文第一条 H1。
- [ ] 若都没有,再回退到文件名 stem。
- [ ] 保持 page_id / document_id / path-id 规则不变。
- [ ] 更新现有标题相关测试,避免继续断言“标题只取文件名”。
- [ ] 补一个最小回归测试,覆盖 frontmatter / H1 / 文件名三层优先级。
### 3.5 验收
- `cargo test -p mnote-web local_markdown -- --test-threads=1`
### 3.6 完成条件
这一批完成后,可以把 `3-13` 中标题相关尾项打勾,并继续保留其余 GFM AST 迁移尾项为 `process`
---
## 4. 本轮执行记录
- 2026-05-21:已将 P1 剩余尾项拆为 `Batch D1`(资源打开 resolver 收敛)与 `Batch D2`(Markdown 标题优先级收口)。
- 2026-05-21:这两个批次都保持最小 scope,不与 open editors、BufferStore conflict UI、AI 主线混跑。
- 2026-05-21:两项都已完成并经 Rust 单测、task474 / task463 浏览器 smoke 与 CodeGraph 同步复核,现归档到 `done/`
@@ -0,0 +1,126 @@
# [recycle] 1-12 [done] Batch E P1/P2 尾项执行 checklist v1
> 创建时间:2026-05-21
>
> 当前状态:`DONE`
>
> 上位入口:`design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
>
> 阶段:Batch E / P1-P2 剩余尾项收口
## 1. 目标
本批次只处理当前仍有明确证据缺口、且可以并行推进的三个小尾项:
1. `3-13` GFM AST 迁移尾项:保存失败状态保留、表格降级策略、`web_shell.rs` 临时适配分支去留判定。
2. `1-6` Gap A2conflict UI 数据源从本地 session 状态进一步接入 `BufferStore` 可查询状态。
3. `1-6` Gap A7 / `1-8` P2.2FileTree 菜单与快捷键最小接入 `CommandContext` enablement 口径。
本批次不扩 AI 主线,不处理 Wolai 对标,不提前实现 Phase C / Review Mode,不改变 local-first / tree-first 口径。
## 2. 已完成追认
- [x] `5-26` resource open resolver 已由 Batch D 完成,并经 `task463-onlyoffice-resolver-smoke.js` 与 Rust 单测复核,已归档到 `design/05-editor-mainline/done/`
- [x] `1-6` Gap A2 保存后刷新可见读回已由 `task484-local-folder-page-body-refresh-readback-smoke.js` 复核通过。
- [x] `1-6` Gap C1 Convex export dry-run / conflict / rollback 已由 `task455-convex-export-plan-rollback-smoke.js` 复核通过。
- [x] `1-6` Gap A2 conflict UI 数据源已接入 `BufferStore` 查询与冲突信封桥接;`/api/documents/buffer-state``document_page_shell` 初始化、`task451` 信封断言均已通过,`task451` 在最新 3000 主进程上已复测通过。
- [x] `1-6` Gap A7 FileTree 右键菜单与 Delete/Backspace 快捷键已接入 `CommandContext` 等价 enablement 逻辑;`task471` / `task476` / `core-protocol command_context` 已通过。
## 3. Reasonix Worker 拆分
### Worker AGFM AST 保存失败与降级策略尾项
Owner
- `design/03-rust-web/done/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
- `rust/crates/mnote-web/src/routes/local_markdown_parser.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`(只读审查,不能直接修改)
- 可新增或修改一个 `scripts/task48*-local-markdown-save-error-*.js` smoke。
目标:
- 补最小测试或实现,验证保存失败时编辑器状态可保留并显示可解释错误。
- 补表格带 mark 单元格或不支持结构的降级策略测试;若当前行为已经可解释,写入证据并更新 `3-13`
- 只读审查 `web_shell.rs``legacyStylesToTiptapMarks` 等临时适配分支是否仍为必要兼容层;不能安全移除时,把它从“必须移除”改为“保留兼容层并有 TODO 退出条件”。
验收:
- `cargo test -p mnote-web local_markdown -- --test-threads=1`
- `cargo test -p mnote-web web_shell -- --test-threads=1`
- 如新增 smoke`node --check scripts/task48*-local-markdown-save-error-*.js`,并尽量实跑。
当前状态:
- 已有 `task473` / `task455` / `task451` 等结果可表明相邻尾项已处理,但保存失败状态保留与 `web_shell.rs` 兼容层退出条件仍需 Worker A 自己落盘证据。
### Worker BBufferStore conflict UI 数据源
Owner
- `design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
- `rust/crates/mnote-web/src/document_buffer_store.rs`
- `rust/crates/mnote-web/src/routes/documents.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`
- `scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`
目标:
- 为当前打开文档暴露最小 buffer state 查询能力,或把已有 Page Aggregate / session conflict envelope 明确桥接到 `BufferStore` 状态。
- conflict UI 应能读取 editor base version、disk/current version、source actor 或等价字段;不能只靠前端本地临时状态。
- 保持现有 `task451` 的 accept disk / keep current / merge 三条行为不回退。
验收:
- `cargo test -p mnote-web document_buffer -- --test-threads=1`
- `cargo test -p mnote-web local_folder -- --test-threads=1`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`
当前状态:
- 代码与 targeted tests 已通过,后续仅保留对 UI 复核和状态记录的收口,不再作为未完成主线。
### Worker CCommandContext FileTree 菜单 / 快捷键最小接入
Owner
- `design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
- `rust/crates/core-protocol/src/command.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `scripts/task476-filetree-editor-context-menu-download-smoke.js`
- `scripts/task471-local-folder-bulk-resource-trash-smoke.js`
目标:
- 在 SSR sidebar JS 中引入最小 `CommandContext` 等价 enablement helper,先覆盖 FileTree 右键菜单和 Delete/Backspace 快捷键。
- readonly / sourceKind / selectionCount / resourceKind 的禁用语义应与 Rust `CommandContext` 测试口径一致;不要求本轮把 Rust evaluator 直接编译到 JS。
- 保持下载、多选删除、粘贴、只读阻断 smoke 不回退。
验收:
- `cargo test -p mnote-web sidebar_filetree -- --test-threads=1`
- `cargo test -p core-protocol command_context -- --test-threads=1`
- `node scripts/task471-local-folder-bulk-resource-trash-smoke.js`
- `node scripts/task476-filetree-editor-context-menu-download-smoke.js`
当前状态:
- 代码与 targeted tests 已通过,可作为 `1-6` A7 的已完成证据使用。
## 4. Codex 复核项
- [x] 逐个读取 worker 的 `final.md` / `result.json` / diffB/C 由代码 diff 与 targeted tests 复核,A 无 `final.md` / `result.json`,已记录 timeout。
- [x] 检查 worker 是否越权修改非 owner 文件:A 未采纳,B/C 改动范围与任务目标一致;Codex 对 `documents.rs` 查询兜底补了单测。
- [x] 运行各 worker 验收命令中与实际改动相关的 targeted tests。
- [x] UI/browser 可见项必须由 Codex 或浏览器测试 worker 实跑复核:`task471``task476``task451` 已有实跑记录,且 `task451` 已在最新 3000 主进程上复测通过。
- [x] 更新 `1-6``3-13`、本文执行记录:`1-6` 追认 buffer_state / task451 状态;`3-13` 追认 task486 改为当前主链 smoke 并复测通过。
- [x] 可归档的文档移动到 `done/`,不能归档的保留明确未完成项:`5-26` 已归档,`3-13` 已由 Batch F 归档到 done。
- [x] 提交前运行 `git diff --check``codegraph sync .``codegraph status`
## 5. 本轮执行记录
- 2026-05-21Codex 复核 `5-26` 最后一项,`cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1``node scripts/task463-onlyoffice-resolver-smoke.js` 通过,`5-26` 已归档。
- 2026-05-21Codex 建立 Batch E checklist,并准备派发 Worker A/B/C。
- 2026-05-21Codex 复核 `1-6``3-13` 现有 code / test 状态后,确认 `Gap A2` conflict UI 与 `Gap A7` 菜单/快捷键已落地,`Gap C1` 已完成;`Batch E` 只剩 Worker A 的 `3-13` 尾项与对文档状态的最终收口。
- 2026-05-21Reasonix Worker A 重新派发后长期未生成 `final.md` / `result.json`Codex 终止卡住的 runner / reasonix acp 进程,并记录 `.codex/reasonix-tasks/results/batch-e-worker-a-gfm-save-error-timeout.md`;后续 Codex 将 `task486` 重新对齐到当前 `/documents` 主链并复测通过,保存失败状态保留已追认。
- 2026-05-21Batch F 追认 `3-13` 剩余兼容层为可解释保留项,本文作为 Batch E 执行记录不再保持活跃 process。
@@ -0,0 +1,114 @@
# [recycle] 1-13 [process] Batch F P1/P2 活跃尾项执行 checklist v1
> 创建时间:2026-05-21
>
> 当前状态:`DONE`
>
> 上位入口:`design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
>
> 阶段:Batch F / P1-P2 真实活跃尾项收口
## 1. 目标
本批次只处理 1-8 中仍有明确未完成项、且可以互不冲突并行推进的三类尾项:
1. `3-13` GFM AST 迁移尾项:表格/不支持结构降级策略、`web_shell.rs` legacy marks 适配分支去留判定、process 状态收口。
2. `3-3` tree realtime live cache:复核当前 `3000` 主界面 page subtree / filetree / preferred snapshot 是否已经统一到 WS push + SSE fallback live cache,可归档则补证据归档,不能则列最小缺口。
3. `1-6` Gap A7 / `1-5` command context:复核 `ai.canWrite` 是否已经进入 Hermes / AI 写入守卫;如未接入,补最小守卫和测试。
本批次不扩 Wolai 对标、不做 Phase C Review Mode、不恢复 Convex 正文主存储、不重开旧 Next / BlockNote 默认路径。
## 2. 已完成追认
- [x] `4-38` resource lifecycle / open target 原始 checklist 已归档;后续 `4-39` / `4-40` / `4-41` / `4-44` / `4-45` 已覆盖其未完成项。
- [x] `5-26` resource open resolver 已归档,正文附件与 FileTree open target 已不再作为 Batch B/C 尾项。
- [x] `1-12` Batch E 中 BufferStore conflict UI 与 FileTree CommandContext 接入已完成;仅保留本批次对 AI 写入守卫的补齐。
- [x] `3-13` GFM AST 迁移尾项已由 Codex 本地复核并归档到 `design/03-rust-web/done/`;Worker A 超时,结果不采纳。
- [x] `1-6` Gap A7 AI 写入守卫已接入 `args.commandContext``ai.canWrite` / `workspace.readonly`,并补共享守卫测试;Codex 复核后补齐 `mnote.artifact.*``mnote.mindmap.apply_ops` 两个旧守卫漏口。
## 3. Reasonix Worker 拆分
### Worker AGFM AST 降级策略与 process 收口
Owner
- `design/03-rust-web/done/3-13-rust-web-local-markdown-gfm-ast-parser-migration-v1.md`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
- `rust/crates/mnote-web/src/routes/local_markdown_parser.rs`
- `rust/crates/mnote-web/src/routes/web_shell.rs`
目标:
- 补齐或追认 `3-13` 中“列对齐 / 空单元格 / 带 mark 单元格 / 不支持结构降级策略”的测试证据。
- 审查 `web_shell.rs``legacyStylesToTiptapMarks` 等 legacy marks 适配分支是否仍被当前 bootstrap 使用。不能安全移除时,将其状态改为“保留兼容层并有退出条件”,不要强行删除。
-`3-13` 只剩保留兼容层的合理说明且测试通过,则把 `3-13` 从 process 迁移到 `design/03-rust-web/done/`
验收:
- `cargo test -p mnote-web local_markdown -- --test-threads=1`
- `cargo test -p mnote-web web_shell -- --test-threads=1`
- `git diff --check`
### Worker BTree realtime live cache 可归档性复核
Owner
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
- `design/03-rust-web/reference/3-1-rust-web-long-term-checklist-v2.md`
- 只读审查:`rust/crates/mnote-web/src/routes/ws.rs``rust/crates/mnote-web/src/routes/tree_events.rs``rust/crates/mnote-web/src/ssr/pages/layout.rs`、相关 tree live smoke。
目标:
- 复核 `3-3` 的三个未完成判断:sidebar、page subtree、filetree 是否仍未统一 live cacheWS 是否仍只算骨架;preferred snapshot 补偿链是否仍是阻塞。
- 对照当前 smoke`task432``task446``task447``task448``task449``task165``task123` 等,判断 `3-3` 是否可移动到 done。
- 如果可以归档,只更新文档并移动到 `design/03-rust-web/done/`;如果不能,写明最小剩余缺口和建议新增 smoke,不改 runtime 代码。
验收:
- 至少运行语法检查:相关 smoke 的 `node --check`
- 如当前 3000 服务可用,优先实跑 1-2 个代表性 smoke;若环境不具备,必须在文档中说明阻塞。
- `git diff --check`
### Worker CAI 写入 CommandContext 守卫
Owner
- `design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
- `design/01-tree-first-graph-kernel/reference/1-5-next-phase-sequential-execution-checklist-v1.md`
- `rust/crates/core-protocol/src/command.rs`
- `rust/crates/mnote-web/src/hermes_tools/doc.rs`
- `rust/crates/mnote-web/src/hermes_tools/page.rs`
- 相关 Hermes / AI 写入单测
目标:
- 复核 `CommandContext.ai.canWrite` 是否已被 AI / Hermes 写入路径消费。
- 若未消费,补最小写入守卫:只读 workspace 或无写权限时,Hermes 写正文/页面命令应返回明确拒绝,不进入文件写入。
- 补针对性单测,避免只停留在 `core-protocol` evaluator 层。
- 更新 `1-6` / `1-5` 对应状态。
验收:
- `cargo test -p core-protocol command_context -- --test-threads=1`
- `cargo test -p mnote-web hermes_tools -- --test-threads=1`
- 如 hermes_tools 过滤过大,可运行新增测试名的 targeted cargo test。
- `git diff --check`
## 4. Codex 复核项
- [x] 读取每个 worker 的结果文件与 diff,不凭文字结论直接验收。
- [x] 对 Worker A 运行 `cargo test -p mnote-web local_markdown -- --test-threads=1``cargo test -p mnote-web web_shell -- --test-threads=1`
- [x] 对 Worker B 至少复跑 `node --check`,并按可用环境实跑代表性 live cache smoke。
- [x] 对 Worker C 运行 `cargo test -p core-protocol command_context -- --test-threads=1` 与相关 Hermes targeted tests。
- [x] 更新 `1-8``1-12``3-13``3-3``1-6` 的状态,不保留互相矛盾的 process 口径。
- [x] 提交前运行 `git diff --check``codegraph sync .``codegraph status .`
## 5. 本轮执行记录
- 2026-05-21Codex 新建 Batch F checklist,准备并行派发 Worker A/B/C。
- 2026-05-21Worker A 长时间未生成 `final.md` / `result.json`Codex 终止并记录 `.codex/reasonix-tasks/results/batch-f-worker-a-gfm-archive-timeout.md`
- 2026-05-21Worker B 输出串线到 Worker C 方向,不能作为 `3-3` 归档证据;记录 `.codex/reasonix-tasks/results/batch-f-worker-b-tree-live-cache-misrouted.md`。Codex 已复跑 `task123/task432/task446/task447/task448/task449``node --check`
- 2026-05-21Codex 本地复核 `3-13`,确认 colspan 降级、table marks、alignment、web shell legacy marks 兼容层均有证据,已移动到 `design/03-rust-web/done/`
- 2026-05-21Codex 整理 Worker C 半截补丁,新增 `ToolCallInput::command_context_bridge``ensure_write_authorized`,让 Hermes 写入工具消费 `ai.canWrite` / `workspace.readonly`
- 2026-05-21Codex 复核发现 `artifact.rs``resource.rs` 仍保留旧写入守卫,已改为委托 `ensure_write_authorized`。验证:`cargo test --manifest-path rust/Cargo.toml -p core-protocol command_context -- --test-threads=1` 7 passed`cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --test-threads=1` 64 passed`cargo test --manifest-path rust/Cargo.toml -p mnote-web ensure_write_authorized -- --test-threads=1` 2 passed。
- 2026-05-21Batch H 已完成 `3-3` live cache 归档与 `1-5` command context 状态收口;`task487` / `task471` / `task476` 已在当前 3000 环境复跑通过,`1-8` Batch D 状态已更新。
@@ -0,0 +1,107 @@
# [recycle] 1-14 [process] Batch H P2 live cache / command context 收口 checklist v1
> 创建时间:2026-05-21
>
> 当前状态:`DONE`
>
> 上位入口:`design/01-tree-first-graph-kernel/process/1-8-mvp-post-process-execution-order-v1.md`
>
> 阶段:Batch H / P2 文档状态与浏览器证据收口
## 1. 目标
本批次只处理 `1-8` Batch D 中仍会干扰后续判断的两类尾项:
1. `3-3` tree realtime live cache 是否仍应保留在 `process/`,还是已被 `3-14` / `3-18` / `task487` 证据覆盖后可以归档。
2. `1-5` / `1-6` command context 文档状态是否仍把 FileTree 菜单、快捷键、AI 写入守卫标成未完成。
本批次不新增 runtime 功能,不扩 AI 产品面,不实现 Phase C Review Mode,不恢复 Convex 正文主存储。
## 2. Reasonix Worker 拆分
### Worker A`3-3` live cache 可归档性复核
Owner
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
- 如确认可归档,可移动到 `design/03-rust-web/done/3-3-rust-web-tree-realtime-event-stream-v1.md`
目标:
- 对照 `3-14` WS push、`3-18` local folder tree live consumer、`task123/task432/task446/task447/task448/task449/task487` 证据,判断 `3-3` 的 9.2 未完成项是否已经过期。
- 如果可归档,只更新 `3-3` 文档状态并移动到 `done/`
- 如果不可归档,只在 `3-3` 写明最小剩余缺口,不改 runtime 代码。
验收:
- `node --check scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`
- `node --check scripts/task487-local-folder-tree-live-consumer-smoke.js`
- `git diff --check`
### Worker B`1-5` / `1-6` command context 状态收口
Owner
- `design/01-tree-first-graph-kernel/reference/1-5-next-phase-sequential-execution-checklist-v1.md`
- `design/01-tree-first-graph-kernel/done/1-6-next-phase-gap-closure-checklist-v1.md`
目标:
- 只更新 command context / context key 相关 checklist 状态。
- 对照当前代码和已通过测试,修正仍写着“File Tree 右键菜单、快捷键、AI 写入能力尚未消费 CommandContext”的过期表述。
- 保留真实未完成项,例如更完整的 editor/object tab context 或未实跑的 readonly DnD smoke,不要把范围外事项误勾完成。
验收:
- `cargo test --manifest-path rust/Cargo.toml -p core-protocol command_context -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web ensure_write_authorized -- --test-threads=1`
- `node --check scripts/task471-local-folder-bulk-resource-trash-smoke.js`
- `node --check scripts/task476-filetree-editor-context-menu-download-smoke.js`
- `git diff --check`
### Worker CBatch D 浏览器 smoke 证据整理
Owner
- `.codex/reasonix-tasks/results/batch-h-worker-c-browser-smoke-evidence.md`
目标:
- 不修改产品代码和设计主文档。
- 只读检查当前相关 smoke 脚本、已有 `tmp/` 结果和截图路径。
- 输出一份证据表,区分“已实跑通过”“仅 node --check 通过”“需要后续实跑”的 smoke。
- 优先覆盖:`task123``task432``task446``task447``task448``task449``task471``task476``task487`
验收:
- 结果文件列出每个 smoke 的脚本路径、最近证据路径、是否可用于人工网页核查。
- 不改 runtime 代码。
- `git diff --check`
## 3. Codex 复核项
- [x] 读取 Worker A/B/C 的 `final.md` / `result.json` / diff,不凭文字结论直接验收。
- [x] 确认 Worker A 若移动 `3-3` 到 done,不会掩盖仍未完成的 live cache 缺口。
- [x] 确认 Worker B 只勾 command context 已完成范围,不把 editor/object tab future 项误判完成。
- [x] 根据 Worker C 的证据表决定本轮是否需要再实跑浏览器 smoke。
- [x] 更新 `1-8` / `1-13` / 本文件执行记录。
- [ ] 提交前运行 `codegraph sync .``codegraph status .`
## 4. 本轮执行记录
- 2026-05-21Codex 新建 Batch H checklist,准备派发 Worker A/B/C。
- 2026-05-21Reasonix Worker A/B/C 均 completed,并通过 completion hook 写入 `tmp/reasonix-hook-test/batch-h-*.json`
- 2026-05-21Codex 复核 Worker A diff,确认 `3-3` 已移动到 `design/03-rust-web/done/`9.2 未完成项已由 `3-14``3-18``task487` 覆盖。
- 2026-05-21Codex 复核 Worker B diff,采纳 `1-5` Phase A7 的 command context 状态更新;保留 editor/object tab context 和更完整按钮 enablement 作为后续扩展,不把它们误判为本轮完成。
- 2026-05-21Codex 复核 Worker C 证据表,纠正 `task471` 证据漏查,并实跑关键 smoke`task487` 通过,`task471` 通过,`task476` 使用 `/usr/bin/google-chrome-stable` 通过。`/snap/bin/chromium` 下的 `download.saveAs ENOENT` 判定为 Playwright 下载临时文件噪音,不作为产品失败。
- 2026-05-21:验证命令已通过:
- `git diff --check`
- `cargo test --manifest-path rust/Cargo.toml -p core-protocol command_context -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web ensure_write_authorized -- --test-threads=1`
- `node --check scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`
- `node --check scripts/task487-local-folder-tree-live-consumer-smoke.js`
- `node --check scripts/task471-local-folder-bulk-resource-trash-smoke.js`
- `node --check scripts/task476-filetree-editor-context-menu-download-smoke.js`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/snap/bin/chromium PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task487-local-folder-tree-live-consumer-smoke.js`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/snap/bin/chromium PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task471-local-folder-bulk-resource-trash-smoke.js`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable PLAYWRIGHT_CHROME_EXECUTABLE=/usr/bin/google-chrome-stable node scripts/task476-filetree-editor-context-menu-download-smoke.js`
@@ -0,0 +1,474 @@
# [recycle] 1-3 [process] 当前主线持续推进 checklist v1
> 创建时间:2026-05-19
>
> 当前状态:`DONE`
>
> 上位依据:
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-2-local-first-workspace-convex-control-plane-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-3-local-workspace-access-control-productization-v1.md`
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/CURRENT_ARCHITECTURE.md`
>
> 目标:把 `01-05 当前主线与优先级总览` 转成可持续推进、可验证、可迁移到 `done/` 的执行清单。
---
## 0. 总原则
- [x] 产品形态固定为 `VSCode 简化版工作区内核 + tiptap markdown 编辑器 + Hermes / Reasonix agent + simplemindmap / office 插件 + Wolai 风格 web 壳 + 鉴权控制面`
- [x] 本地文件夹是默认数据真相;Rust kernel 是唯一语义真相。
- [x] Rust SQLite control-plane 承接账号、分享、同步、协作和 AI 隔离默认控制面;Convex 仅保留历史迁移源、显式 cloud source / compat / sync replica 边界。
- [x] AI 默认尽量使用 agent 原生文件读写、diff、patch 能力;MNote 只提供授权 root、页面定位、必要元数据和特殊资源工具。
- [x] 每完成一个阶段后,把对应过程稿移动到该分类 `done/`,并在本 checklist 写入验证证据。
- 本轮说明:`1-3` 本身仍作为当前持续执行清单保留在 `process/`;已完成的上位 local-first 设计和已完成阶段稿继续放在各自 `done/`,仍处于阶段性收口的 P7 文档保留在 `process/` 并在本 checklist 写明证据。
---
## 1. P0 管理员目录授权控制面
对应 `01-05` 执行顺序:`1. 管理员目录授权 UI / API`
### 1.1 后端 API 与权限底座
- [x] 管理员身份支持 `MNOTE_ADMIN_USER_IDS`
- [x] access policy 默认路径固定为 `/mnt/Data1T/Mnote_data/control-plane/access-policy.json`
- [x] 管理员可读写任意本地目录,普通用户只能访问 owner / grant 授权目录。
- [x] read grant 只能读,write grant 可写。
- [x] 新增 `GET /api/admin/access-policy`
- [x] 新增 `POST /api/admin/access-policy/validate-root`
- [x] 新增 `POST /api/admin/access-policy/grants`
- [x] 新增 `DELETE /api/admin/access-policy/grants/{grantId}`
- [x] 全入口权限审计完成:local folder、page body、tree command、Hermes / Reasonix、shared AI session。
验证证据:
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web local_workspace_access -- --nocapture`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web hermes_tools_markdown_edit_shared_read_is_forbidden -- --nocapture`
### 1.2 管理员 UI
目标:让管理员不用手写 JSON 就能管理用户目录授权。
- [x] 定位当前 Rust SSR 设置 / 管理入口,确认管理员页面应挂在 `rust/crates/mnote-web/src/ssr/pages/` 的哪个壳内。
- [x] 增加管理员访问入口:非管理员不可见,管理员可进入本地目录授权管理页。
- 实现:`/admin/access-policy` + `PageLayout` 管理员 quick action。
- [x] 增加 policy 列表:显示 `policyPath`、env admins、policy admins、grant 列表。
- 实现:`rust/crates/mnote-web/src/ssr/pages/admin.rs` 调用 `GET /api/admin/access-policy` 并展示 JSON。
- [x] 增加 validate root 表单:输入 `rootPath``rootUri` 后显示 canonical path / rootUri。
- [x] 增加 create grant 表单:`userId``permission``recursive``capabilities`
- [x] 增加 delete grant 操作:删除前显示授权目录和用户,删除后刷新列表。
- [x] 增加 UI 错误态:未登录、非管理员、目录不存在、重复授权、无效 capability。
- 实现:页面展示 API 错误;SSR route 对非管理员返回 403;API 保持后端错误码。
- [x] 补管理员 UI smoke:真实登录管理员可新增 read grant,普通用户可读不可写。
- 验证:`node scripts/task450-admin-access-policy-ui-smoke.js`
- [x] 补普通用户 smoke:普通用户无法打开管理员授权页,直接调用 API 返回 403。
- 验证:`cargo test -p mnote-web admin_access_policy -- --nocapture`
建议验证:
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web local_workspace_access -- --nocapture`
- [x] `node scripts/task450-admin-access-policy-ui-smoke.js`
---
## 2. P1 VSCode-like 冲突处理 UI
对应 `01-05` 执行顺序:`2. VSCode-like 冲突处理 UI`
目标:tiptap 前端保存与 agent 后台写文件同时发生时,用户可以像 VSCode 一样看见冲突、选择磁盘版本、保留编辑器版本或打开 diff 合并。
### 2.1 冲突模型收口
- [x] 盘点现有 `fileVersion` / `conflictDetectionKey` 的生成、传递和校验路径。
- 结论:本地 markdown aggregate 已暴露 `fileVersion` alias`documents/save` compat 会把 `expectedFileVersion` / `conflictDetectionKey` 收敛到 `PageBodyWriteRequest.expected_file_version``/api/page-body/write` 只接受 `expectedFileVersion`,避免 alias 重复。
- [x] 确认所有本地 markdown 写入口都携带 expected file versiontiptap 保存、`mnote.doc.markdown_edit``mnote.page.save`、documents compat save。
- 实现:tiptap local-folder 保存改走 `/api/page-body/write` 并只传 `expectedFileVersion``mnote.doc.markdown_edit` 从当前 aggregate 的 `fileVersion` / `conflictDetectionKey` 取 expected version`mnote.page.save` 读取 tool 入参 `expectedFileVersion`documents compat save 继续兼容旧 `conflictDetectionKey`
- [x] 统一冲突错误 envelope:错误码、当前磁盘版本、编辑器基线版本、documentId、rootUri、建议动作。
- 实现:本地 markdown stale 写入返回 `details.conflict`,包含 `code``currentDiskVersion``editorBaseVersion``documentId``rootUri``suggestedActions`
- [x] 让冲突错误不丢失当前编辑器内容,前端可以继续保留未保存 buffer。
- 实现:冲突态优先从当前挂载的 ProseMirror DOM 捕获文本;“保留当前编辑器版本”会用最新 `fileVersion` 重新提交当前 buffer。
### 2.2 冲突交互
- [x] 设计冲突 modal / side panel:显示当前编辑器版本、磁盘版本、文件路径、最后修改来源。
- 实现:文档页内嵌 `mnote-editor-conflict-panel`,展示文件标识、来源为本地文件变更,并提供磁盘 / 当前 / diff 三个动作。
- [x] 实现“接受磁盘版本”:重新读取文件,替换编辑器 buffer。
- [x] 实现“保留编辑器版本”:用最新 fileVersion 重新提交当前编辑器内容。
- [x] 实现“打开 diff”:支持 markdown 文本并排查看;复杂块结构先降级为 markdown 文本。
- [x] 实现“合并”:支持从 diff 面板选择合并结果并写回。
- 实现:冲突 diff 面板新增合并 textarea,可选择当前版本 / 磁盘版本作为合并基础,并写回合并结果。
- [x] 合并完成后写回本地 markdown,并刷新 page aggregate / file tree snapshot。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js` 覆盖合并写回,文件内容包含当前编辑器内容、磁盘内容和新增合并内容。
- [x] AI 写入导致冲突时,提示来源为 agent run,而不是普通外部修改。
- 实现:页面收到 `mnote:page-ai-tool-write-completed` / `agent.changed_files` 后记录 agent run 来源;冲突面板来源显示为 `agent run <runId>`
建议验证:
- [x] 新增单测:stale `expectedFileVersion` 被拒绝。
- 验证:`cargo test -p mnote-web local_folder_documents_save_rejects_stale_expected_file_version -- --nocapture`
- [x] 新增 browser smoke:浏览器打开页面后外部修改同一 `.md` 文件,保存时出现冲突 UI。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`
- [x] 新增 browser smokeagent 修改同一 `.md` 文件后,tiptap 保存触发冲突 UI。
- 验证:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js` 覆盖 agent 修改同一 `.md` 后的冲突 UI,并断言面板出现 `agent run <runId>`
补充验证:
- [x] `cargo fmt --check --all --manifest-path rust/Cargo.toml`
- [x] `cargo test -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `npm run check:local-first-convex-guard`
---
## 3. P2 Agent changed files / diff 审计
对应 `01-05` 执行顺序:`3. agent changed files / diff 审计`
目标:agent 可以像在 VSCode 里一样直接改授权目录文件,但 MNote 要记录“谁、在哪个 root、通过哪个 run、改了哪些文件、diff 摘要是什么”。
### 3.1 审计事件模型
- [x] 设计本地审计目录:建议放在 `/mnt/Data1T/Mnote_data/control-plane/agent-audit/`
- [x] 定义审计事件 JSONL 字段:`eventId``actorId``agentKind``runId``rootUri``permission``changedFiles``diffSummary``createdAt`
- [x] 区分 agent 原生文件修改与 MNote tool 写入:二者都要能归入同一个 run audit。
- 实现:agent 原生文件修改继续由 run 前后 root snapshot 生成 `changedFiles`;MNote tool 本地成功写入额外追加 `origin=mnote_tool` 的同 `runId` audit event,只读拒绝追加 `writeAttemptRejected=true` 的同 `runId` audit event。
- [x] 对只读 grant 的 agent run 写入尝试记录拒绝事件。
- 实现:`mnote.doc.markdown_edit` / `mnote.page.save` / `mnote.block.*``read_only` AI scope 下直接拒绝写入;本地 mnote tool 写入拒绝会按同一 `runId` 追加 control-plane `agent-audit.jsonl` 事件,标记 `writeAttemptRejected=true`
### 3.2 写入采集
- [x] 在 Hermes / Reasonix run 启动前记录 root snapshot:文件 mtime、size、hash。
- [x] run 结束后对比 root snapshot,生成 changed files。
- [x] 对 markdown 文件生成简短 diff summary;大文件只记录 hash / size / path。
- [x] 把审计事件落盘到 control-plane,不写进用户正文目录。
- [x] 在 AI 会话 UI 显示 changed files 列表,并可展开查看 diff 摘要。
- 实现:`run.completed.agentAudit.changedFiles` 会进入 `agent.changed_files` 工具卡并显示 changed files 与 diff summary。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task453-local-folder-page-ai-changed-files-smoke.js`
建议验证:
- [x] 单测:run 前后文件变化可生成 changed files。
- 验证:`cargo test -p mnote-web local_agent_audit_snapshot_detects_changed_files -- --nocapture`
- [x] 单测:只读授权下写入被拒绝并产生拒绝审计事件。
- 验证:`cargo test -p mnote-web hermes_tools_markdown_edit_shared_read -- --nocapture`
- [x] 单测:MNote tool 本地写入归入同一 run audit,并标记 `origin=mnote_tool`
- 验证:`cargo test -p mnote-web hermes_tools_page_save_local_folder_writes_markdown_file -- --nocapture`
- [x] browser smokeAI 修改一篇本地 markdown 后,会话面板显示 changed files。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task453-local-folder-page-ai-changed-files-smoke.js` 覆盖本地 `README.md` 写入标记后,会话面板显示 `agent.changed_files``README.md` 与 diff 摘要。
补充验证:
- [x] `cargo fmt --check --all`
- [x] `cargo test -p mnote-web hermes_client_acp_run_registers_scoped_runtime_record_in_convex -- --nocapture`(历史测试名;当前默认运行态记录已由 SQLite control-plane 承接,旧 Convex store 只保留显式 compat 覆盖)
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web document_shell_renders_local_markdown_with_same_sidebar_surfaces -- --nocapture`
---
## 4. P3 本地搜索、反链和资源引用索引
对应 `01-05` 执行顺序:`4. 本地搜索、反链和资源引用索引`
目标:本地 workspace 不依赖 Convex search 也能搜索正文、查反链、查资源引用和标签。
### 4.1 索引边界
- [x] 设计本地索引目录:建议放在 workspace `.mnote/index/``/mnt/Data1T/Mnote_data/control-plane/index/`,二者职责需明确。
- 实现:单 workspace 本地索引落在授权 root 的 `.mnote/index/search-index.json`control-plane index 只保留给后续跨 workspace / shared cache 总索引。
- [x] 确认索引只扫描授权 root 内文件,不扫描用户未授权目录。
- 实现:`/api/search/documents``local_folder` 分支先走 `ensure_local_workspace_read_access`,再对 canonical root 内 `.md` 文件重建索引。
- [x] 定义索引输入:markdown 正文、frontmatter、附件引用、mindmap / office resource metadata。
- 实现:输入覆盖 markdown 正文、frontmatter `title/tags/mnote_id`、markdown 链接、双链、附件引用、`.mindmap.json` 与 office 文件元数据。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 定义索引输出:全文 search、backlinks、resource refs、tags、recent changes。
- 实现:本地索引输出 `results``recentChanges`,结果包含全文命中、`backlinks``resourceRefs``tags``path``resourceType`,并可返回 mindmap / office 资源元数据搜索结果。
### 4.2 索引更新
- [x] 本地文件 watcher 事件触发增量索引。
- 实现:本地 markdown / mindmap / office watcher 事件会调用单文件索引更新 `refresh_local_search_index_for_path`,避免等到搜索时才重建。
- 验证:`cargo test -p mnote-web watcher_event_refreshes_local_search_index_path -- --nocapture`
- [x] 手动 refresh / resync 触发 root 全量索引。
- 实现:`POST /api/search/local-index/refresh` 会在授权 root 内全量重建 `.mnote/index/search-index.json`
- 验证:`cargo test -p mnote-web search_local_index_refresh_rebuilds_authorized_root -- --nocapture`
- [x] 文件移动、重命名、删除、恢复后同步更新索引。
- 实现:本地 `tree command` 成功后统一 best-effort 刷新 `.mnote/index/search-index.json`;验证覆盖 rename 后索引路径更新。
- 验证:`cargo test -p mnote-web local_tree_command_refreshes_search_index_after_rename -- --nocapture`
- [x] AI 写入和 tiptap 保存后更新索引。
- 实现:`save_local_markdown_page` 成功写回后统一 best-effort 刷新本地搜索索引,覆盖 tiptap 保存与 MNote tool 写入兼容链。
- 验证:`cargo test -p mnote-web local_markdown_save_refreshes_search_index_after_write -- --nocapture`
- [x] 索引损坏时可重建,不影响正文文件。
- 实现:本地搜索每次从授权 root 重新生成 `.mnote/index/search-index.json`,不会读取索引作为正文真相。
### 4.3 搜索体验
- [x] 全局搜索优先搜索当前 workspace 本地索引。
- 实现:页面搜索请求携带 `sourceKind/rootUri``local_folder` 请求直接查本地索引,不先走 Convex runtime。
- [x] 搜索结果显示文件路径、标题、命中片段和资源类型。
- 实现:搜索结果行显示标题、命中片段、路径和 `resourceType` 标识;API 同步返回 `path/title/snippet/resourceType`
- [x] 反链面板读取本地索引,不再依赖云端搜索。
- 实现:页面设置 drawer 新增“索引”页签,按当前授权 root 读取 `GET /api/search/local-index/backlinks`
- [x] 标签列表读取本地索引。
- 实现:页面设置 drawer 新增“索引”页签,按当前授权 root 读取 `GET /api/search/local-index/tags`
建议验证:
- [x] 单测:markdown 链接和双链可生成 backlinks。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 单测:附件 / mindmap / office 引用可生成 resource refs。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] 单测:mindmap / office 原生资源元数据可进入本地索引结果。
- 验证:`cargo test -p mnote-web local_search_index -- --nocapture`
- [x] browser smoke:新建页面后立即可搜索,重命名后搜索结果路径更新。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task452-local-search-index-browser-smoke.js`
- [x] browser smoke:页面设置里的本地索引页签可显示反链和标签。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task452-local-search-index-browser-smoke.js`
补充验证:
- [x] `cargo test -p mnote-web search_local_index_backlinks_and_tags_read_authorized_root -- --nocapture`
---
## 5. P4 分享与同步闭环
对应 `01-05` 执行顺序:`5. 分享与同步闭环`
目标:个人本地空间默认隔离,显式分享后才产生共享 workspace / shared AI session;权限、同步和协作默认由 Rust SQLite control-plane 承接,Convex 只保留历史迁移源、显式 cloud source / compat / sync replica 边界。
### 5.1 分享权限
- [x] 定义 share grant 与 local access grant 的关系:share grant 是产品层共享授权,local access grant 是本机文件系统目录授权;分享不自动扩大本机 root 读写权限。
- 实现:`share_grant_admin_api_creates_reads_and_revokes_without_local_access` 验证 share grant 不授予 target 用户本地 filesystem read/write access。
- [x] 增加 canonical share grant 控制面:默认落在 `/mnt/Data1T/Mnote_data/control-plane/share-grants.json`,记录 `shareId`、owner、target、root、resource scope、permission、capabilities、生命周期和撤销状态。
- 实现:新增 `GET/POST/DELETE /api/admin/share-grants`;支持 `MNOTE_SHARE_GRANTS_FILE` 覆盖测试路径。
- [x] share grant 支持 `read` / `write` / `ai` capability,并可映射到 `AiAccessScope.permissionLevel=shared_read/shared_write`
- 实现:Hermes local ACP run 从 canonical share grant 生成 `shared_read/shared_write`
- [x] shared AI session 只能访问 share grant 允许的资源;`allowedResourceIds` / `shareContext` 必须来自 canonical share grant,不接受浏览器自报扩大范围。
- 实现:`hermes_client_run_body_shared_scope_uses_share_grant_resources``acp_runtime_env_limits_shared_scope_to_granted_files` 覆盖 `shareContext``allowedResourceIds` 和文件级 allowed roots。
- [x] 管理员可查看、创建和撤销 share grant;owner 后续可管理自己创建的 share grant。
- 实现:管理员页面新增独立“分享授权”面板,与目录授权面板分离。
### 5.2 同步缓存
- [x] 设计 shared workspace cache 目录:默认位于 `/mnt/Data1T/Mnote_data/shared/workspaces/<share-id>/`workspace 内缓存 `.mnote/share-cache.json``.mnote/sync-state.json` 只保存授权、版本、来源和同步状态,不成为第二份正文真相。
- 实现:新增 `/api/local-folder/shared-cache/record``/api/local-folder/sync/pending-change`,均只写 `.mnote` sidecar。
- [x] 云端同步到本地 cache 时保留来源、版本、权限和冲突信息:至少记录 `shareId``remoteVersion``baseVersion``permission``sourceActor``syncedAt`
- 实现:`.mnote/share-cache.json` 写入 `mnote.share_cache.v1`
- [x] 本地修改同步回云端前进行权限和版本校验:`shared_read` 永远不能上传 pending write`shared_write` 必须校验 share grant 未撤销且 base version 未过期。
- 验证:`shared_cache_and_sync_state_record_permissions_and_conflict_report` 覆盖 shared_read 拒绝 pending write、shared_write 写入 pending change、remote/base version 冲突返回 409。
- [x] 离线期间记录 pending changes,恢复在线后生成同步报告;pending changes 只能来自已有授权 cache,离线不能新增或扩大授权。
- 实现:pending changes 写入 `.mnote/sync-state.json`,写入前强制校验 canonical share grant。
### 5.3 同步冲突报告
- [x] 同步冲突复用 P1 的冲突 UI,并把冲突来源标记为 shared sync 而不是普通外部文件变更。
- 实现:`/api/local-folder/sync/conflict-report` 生成 `source=shared_sync``suggestedActions=["accept_local","accept_remote","open_diff_merge"]` 的报告 envelope,供 P1 diff / merge UI 消费。
- [x] 冲突报告包含本地版本、远端版本、base version、修改 actor、shareId 和 resource id。
- 验证:`shared_cache_and_sync_state_record_permissions_and_conflict_report`
- [x] 管理员或 owner 可导出冲突报告;报告写入 control-plane 或 workspace `.mnote/sync-reports/`,不写入正文目录。
- 实现:报告写入授权 workspace 的 `.mnote/sync-reports/`
建议验证:
- [x] 单测:share read 不允许写入。
- 验证:`cargo test -p mnote-web shared_cache -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture`
- [x] 单测:shared AI session 不扩大 workspace root。
- 验证:`cargo test -p mnote-web acp_runtime_env_limits_shared_scope_to_granted_files -- --nocapture`
- [x] browser smoke:共享页面只读用户无法通过 AI 写入正文。
- 验证:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task454-shared-read-page-ai-browser-smoke.js`
---
## 6. P5 插件资源模型产品化
对应 `01-05` 执行顺序:`6. 插件资源模型产品化`
目标:simplemindmap / office 是 Resource Tree 对象;Markdown 正文只保留链接或嵌入引用,不把资源内容塞回 markdown 正文真相。
### 6.1 Resource Tree 对象统一
- [x] 盘点 simplemindmap 当前 object identity、保存路径、打开路径。
- 证据:`/mindmap/{documentId}/{mindmapId}` 对象壳输出 `resource:mindmap:{documentId}:{mindmapId}``mindmap_shell_returns_rust_object_shell_contract`;保存仍通过 `mindmaps.put` / `mindmap.command.apply` 兼容链。
- [x] 盘点 office 当前 object identity、保存路径、打开路径。
- 证据:新增 `/office/{documentId}/{assetId}` 对象壳输出 `resource:onlyoffice:{documentId}:{assetId}`,内部复用 `/onlyoffice` bootstrap`cargo test -p mnote-web onlyoffice_object_shell_exposes_resource_identity -- --nocapture``cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --nocapture`
- [x] 统一资源创建、重命名、移动、删除、恢复命令到 `tree.resource.*`
- 实现:上传/创建走 `tree.resource.upload` 或资源对象专用创建事件;附件 rename / move / archive / restore / purge 已输出 `tree.resource.rename/move/archive/restore/purge`mindmap/table 生命周期输出 `tree.resource.*`
- 验证:`cargo test -p mnote-web resource_ -- --nocapture`;新增 `resource_move_uses_resource_command_not_document_command`
- [x] filetree 显示资源行,pagetree 只显示页面导航投影。
- 验证:`cargo test -p mnote-web file_tree_projection_ -- --nocapture``cargo test -p bridge-runtime file_tree_projection -- --nocapture`
- [x] Markdown 中插入资源引用时,只写相对链接或嵌入引用。
- 证据:`task169-mindmap-realtime-smoke.js``assertDocumentMindmapBlockUsesReferenceSource` 验证 `mindmapId/rootNodeId` 且不写入 `data`;附件/office 仍通过链接或 asset 引用打开对象壳。
### 6.2 AI 资源工具
- [x] 设计 `mnote.mindmap.*` 工具:读取结构、增删改节点、移动节点、导出 markdown summary。
- 设计:`design/07-ai/reference/7-28-resource-ai-tool-contract-v1.md`
- [x] 设计 `mnote.office.*` 工具:读取文本摘要、写入建议、导出变更摘要;真实编辑优先复用 officecli / OnlyOffice 保存链。
- 设计:`design/07-ai/reference/7-28-resource-ai-tool-contract-v1.md`
- [x] AI 资源工具必须走授权 root 和 resource capability。
- 实现:Hermes manifest 已暴露 `mnote.mindmap.fetch` / `mnote.mindmap.apply_ops` / `mnote.office.fetch_summary` / `mnote.office.propose_changes`;执行侧统一校验 `rootUri` 本地授权与 `allowedResourceIds` / object identity。
- 验证:`cargo test -p mnote-web hermes_tools_resource -- --nocapture``cargo test -p mnote-web hermes_tools_mindmap -- --nocapture``cargo test -p mnote-web hermes_tools_office -- --nocapture``cargo test -p mnote-web hermes_tools_manifest_returns_first_batch_tools -- --nocapture`
- [x] agent changed files 审计包含 mindmap / office 资源文件。
- 验证:`cargo test -p mnote-web local_agent_audit_snapshot_detects -- --nocapture`;覆盖 `.mindmap.json` 和同大小二进制 `.docx` 内容变更。
建议验证:
- [x] 单测:resource rename 不走 document command。
- 验证:`cargo test -p mnote-web resource_rename_uses_resource_command_not_document_command -- --nocapture`
- [x] 单测:mindmap resource 删除 / 恢复不破坏 markdown 页面。
- 验证:`cargo test -p mnote-web mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command -- --nocapture`;生命周期命令只返回 `tree.resource.archive/restore``resourceKind=mindmap`,不携带正文 `documentId` 写入语义。
- [x] browser smoke:从 markdown 打开 mindmap / office,修改保存后 filetree 和引用状态同步。
- 证据:`node scripts/task456-resource-object-shell-sync-smoke.js`
---
## 7. P6 旧 Convex 数据迁移产品化
对应 `01-05` 执行顺序:`7. 旧 Convex 数据迁移产品化`
目标:把旧 Convex workspace 迁移成本变成可视、可回滚、可验证的产品流程。
### 7.1 导出与备份
- [x] 冻结旧 Web 导出入口设想,不再作为当前落码入口。
- 历史设计:`design/old/03-rust-web/process/3-17-convex-export-web-entry-v1.md`;当前 CLI fixture 入口已支持 dry-run / apply / manifest / conflict report / rollback。由于 Convex 默认运行时与根 functions 已退役,Web route 与 admin 面板不再按该旧稿继续落码,未来如需 cloud/import UI 应按 SQLite control-plane 与 local-first 口径新建 checklist。
- [x] 导出前创建 manifest,记录迁移计划、目标 root、workspace、操作列表、冲突、created files、backup dir 与索引刷新结果。
- 实现:`scripts/export-convex-workspace-to-local.js --manifest <file>`apply 成功后同时写入 `<root>/.mnote/migration-manifest.json`
- [x] 导出页面为 `.md`,资源写为本地文件,附件保持相对路径。
- 验证:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- [x] 导出过程记录进度:总页面数、资源数、AI session 数、操作列表、冲突数。
- 实现:manifest 写入 `documentCount``resourceCount``aiSessionCount``operations``conflicts`
### 7.2 冲突与回滚
- [x] 目标 root 已有同名文件时生成冲突报告,不直接覆盖。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 支持 dry run:只生成迁移计划,不写文件。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 支持回滚:根据 manifest 删除本次新增文件或恢复备份文件。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`;当前最小闭环覆盖新增文件删除,备份文件恢复按 manifest 字段实现,覆盖写入仍由冲突策略禁止。
- [x] 导出完成后自动跑本地索引重建。
- 实现:CLI apply 写入 `.mnote/index/search-index.json`,包含导出页面与资源。
建议验证:
- [x] 脚本 smokeConvex fixture 导出到本地 root。
- 验证:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- [x] 脚本 smoke:同名文件冲突时不覆盖。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
- [x] 脚本 smoke:回滚后 root 回到导出前状态。
- 验证:`node scripts/task455-convex-export-plan-rollback-smoke.js`
---
## 8. P7 Page Aggregate / tree command / realtime 兼容链瘦身
对应 `01-05` 执行顺序:`8. Page Aggregate / tree command / realtime 兼容链继续瘦身`
目标:不是继续扩新功能,而是减少双真相、双命令面、补偿链和旧 Convex runtime fallback。
### 8.1 Page Aggregate 单一真源
- [x] 盘点 `documents.content` 仍作为正文兼容源的入口。
- 证据:`routes/web_shell.rs` 非 local 分支仍以 `load_document_meta_result + load_document_content_result``meta + content``page.aggregate.get``routes/documents.rs` 仍保留 `documents.content.get` compat`bridge-runtime` 仍将 `CompatMetaContentJoin` / legacy `documents.content` 投影为 `blockDocument`
- [x] 让本地 `.md` 与 EditorBlockDocument projection 的读写边界写入 5-5 / 5-6。
- 证据:`5-5``5-6` 已明确 local-first 下 `.md` 是正文真相,`EditorBlockDocument` 是 runtime-native projection / cache;写侧长期退出 `/api/documents/save` 主路径,收口到带 `expectedFileVersion``page.body.write` / LocalFS executor。
- [x] 标题、正文、页面设置写入后只通过 page aggregate 刷新 UI。
- 阶段进展:文档壳初始化、外部刷新和冲突比较已优先消费 `body.blockDocument` / `editorDocument`,再 fallback 到 legacy `body.content`
- 验证:`cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --nocapture``cargo test -p mnote-web page_aggregate -- --nocapture``scripts/task-page-aggregate-refresh-persistence-smoke.js`
- [x] 删除或降级前端手工拼 `meta + content` 的 runtime fallback。
- 证据:文档壳初始化与外部刷新优先消费 `/api/page-aggregate`,前端只在 Page Aggregate `body.blockDocument/editorDocument` 缺失时把 legacy `body.content` 作为兼容读取;非 local 的后端 `CompatMetaContentJoin` 仍作为 cloud / legacy substrate 边界保留,不再描述为前端 runtime 真相。
- [x] AI 页面设置写入走统一 page aggregate / page command 入口。
- 证据:Hermes `mnote.page.update_options``page.layout.updateOptions`local-first 分支走 `update_local_page_options`
建议验证:
- [x] `cargo test -p mnote-web page_aggregate -- --nocapture`
- 验证:7 个测试通过。
- [x] browser smoke:标题、正文、页面设置保存后刷新仍一致。
- 证据:`scripts/task-page-aggregate-refresh-persistence-smoke.js`,结果 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`
### 8.2 Tree command cutover
- [x] 盘点仍在 runtime route / adapter / bridge / CLI 中使用的 `documents.*` 命令。
- 证据:`routes/mod.rs` 仍注册 `/api/documents/*` compat`transport/convex.rs` 仍做 `documents.*` 到 legacy Convex validator 的适配;`bridge-runtime` 仍有 `documents.stats.update``documents.duplicate``documents.template``documents.emptyTrashByWorkspace` 等历史命名。
- [x] 新增命令统一命名为 `tree.*``tree.resource.*`
- 证据:`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 `tree_commands_prefer_tree_protocol_names_in_command_wire`
- [x] 对历史 `documents.*` 命令只保留 compat adapter,不再扩展新语义。
- 证据:`4-6` 已冻结 `documents.* -> tree.*` 兼容映射;`cargo test -p mnote-web tree_command -- --nocapture` 覆盖 alias mapping。
- [x] 资源 rename、移动、删除、恢复补齐 `tree.resource.*`
- 证据:资源生命周期已输出 `tree.resource.rename/move/archive/restore/purge``cargo test -p mnote-web tree_command -- --nocapture` 覆盖本地 folder asset trash / restore / purge。
建议验证:
- [x] `cargo test -p mnote-web tree_command -- --nocapture`
- 验证:15 个测试通过。
- [x] browser smokefiletree 新建、重命名、移动、删除、恢复不触发页面 reload。
- 证据:`task432-filetree-trash-page-dual-browser-no-refresh-smoke.js``task446-tree-rename-dual-browser-live-smoke.js``task447-tree-move-order-dual-browser-live-smoke.js`,证据路径见 `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
### 8.3 Tree realtime live cache
- [x] 盘点 Sidebar、page subtree、filetree、preferred snapshot 的数据来源。
- 证据:Sidebar/filetree 已消费 Rust projection 与 live tree eventslocal folder 仍有 `/api/tree/local-folder-watch` + HTML refetch 补偿链;WS 主链已启用 `/api/realtime/ws`SSE 为 close 后 fallback。
- [x] 统一 snapshot / delta / resync consumer 到同一 live cache。
- 阶段进展:WS `delta` 不再无条件 `resync`,仅 `resync_required` / `requiresResync` 时请求服务端 resync,普通 delta 先交给本地 live consumer。
- 验证:`cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`;本地 folder sidebar watch 不再抓整页 HTML,而是复用 `/api/tree/projections/sidebar``/api/tree/projections/file` 后交给同一 `renderSidebarSnapshot` / `renderFileProjection`
- [x] SSE fallback 只作为 WS 不可用时的降级,不作为并行主链。
- 证据:`TREE_LIVE_CONTROLLER_JS``transport=convex-command-log-ws` 时优先 `startWithWebSocket``onclose` 后才 `startWithSseFallback``cargo test -p mnote-web tree_live_controller_marks_transport_and_closes_source_on_pagehide -- --nocapture`
- [x] 双浏览器文件树操作无刷新同步。
- 证据:`task432` 页面生命周期、`task433` file asset、`task434` mindmap/table、`task446` rename、`task447` move、`task448` resync 已在 3000 主入口通过,详见 `3-3``4-34`
- [x] 删除不必要的 polling / refetch 补偿链。
- 证据:文档壳 local folder watch 已从整页 `text/html` refetch 改为 projection API 刷新;`cargo test -p mnote-web sidebar_tree_runtime_polls_local_folder_without_browser_reload -- --nocapture`
建议验证:
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- 验证:59 个测试通过。
- [x] 双浏览器 smoke:页面、文件、垃圾箱操作互相同步且不刷新页面。
- 证据:`task432` / `task433` / `task434` / `task446` / `task447` / `task448`
---
## 9. 每轮推进固定验证包
后续每次持续推进本 checklist,至少执行与改动相关的子集;跨域改动需要执行完整包。
- [x] `cargo fmt --check --all`
- [x] `cargo test -p mnote-web local_folder -- --nocapture`
- [x] `cargo test -p mnote-web local_access_policy -- --nocapture`
- [x] `cargo test -p mnote-web hermes_client_local_acp -- --nocapture`
- [x] `cargo test -p mnote-web tree_command -- --nocapture`
- [x] `cargo test -p mnote-web page_aggregate -- --nocapture`
- [x] `npm run check:local-first-convex-guard`
- [x] `git diff --check -- <changed-files>`
- [x] 影响 UI / 交互时补 browser smoke,并把脚本名写回对应阶段。
---
## 10. done 迁移标准
本文件迁入 `done/` 前必须满足:
- [x] P0 管理员目录授权 UI / API 完成并有 browser smoke。
- 证据:`node scripts/task450-admin-access-policy-ui-smoke.js``cargo test -p mnote-web admin_access_policy -- --nocapture`
- [x] P1 冲突处理 UI 完成,并覆盖 tiptap 保存与 agent 写回冲突。
- 证据:`node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js``cargo test -p mnote-web local_folder_documents_save_rejects_stale_expected_file_version -- --nocapture`
- [x] P2 agent changed files / diff 审计完成,并能在 AI 会话 UI 查看。
- [x] P3 本地搜索 / 反链 / 资源引用索引完成最小闭环。
- [x] P4 分享与同步闭环完成 read / write / ai capability 最小闭环。
- 证据:`cargo test -p mnote-web share_grant -- --nocapture``cargo test -p mnote-web hermes_client_local_shared -- --nocapture``cargo test -p mnote-web shared_cache -- --nocapture``node scripts/task454-shared-read-page-ai-browser-smoke.js`
- [x] P5 simplemindmap / office 资源模型完成 Resource Tree 产品化。
- 证据:资源命令面、资源对象壳、AI resource tools 与 changed-files 审计已闭环;`node scripts/task456-resource-object-shell-sync-smoke.js` 已通过。
- [x] P6 Convex 导出到本地 workspace 有 dry run、备份、冲突报告和回滚。
- 证据:`node scripts/task444-convex-workspace-export-local-fixture-smoke.js``node scripts/task455-convex-export-plan-rollback-smoke.js`;旧 Web 入口设想已退到 `design/old/03-rust-web/process/3-17-convex-export-web-entry-v1.md`,不作为当前 reference 或实施入口。
- [x] P7 Page Aggregate / tree command / realtime 兼容链完成阶段性瘦身,并把被替代 process 稿移入 `old/``done/`
- 证据:Page Aggregate refresh、tree command、local folder projection refresh 与 realtime 双浏览器 smoke 均有验证;`5-5``5-6``3-3``4-34` 仍作为未完全 kernel-native / local-folder no-refresh 深水区过程稿保留在 `process/`,本 checklist 记录阶段性瘦身结果。
- [x] `01-05-current-priority-overview.md` 同步更新状态,不再把已完成项描述为当前第一优先级。
- 证据:`01-05` 已把 P0-P6 改为已完成最小闭环,把 P7 改为阶段性瘦身完成,并把下一阶段顺序更新为 WorkspacePath / BufferStore / Page Aggregate kernel-native / command context / live cache。
@@ -0,0 +1,313 @@
# [recycle] 1-6 [process] 下一阶段缺口补齐 checklist v1
> 创建时间:2026-05-19
>
> 当前状态:`DONE`A0/A2/A7/B2/C1 已完成;`task431` 只读 DnD smoke 仅作为可选回归项,不再阻塞本清单归档)
>
> 2026-05-22 归档治理补充:上位路线图 `1-4` 与顺序清单 `1-5` 已移动到 `reference/`;本文只保留真实仍需收口的缺口与证据尾项,不再承接完整下一阶段路线图职责。
>
> 上位依据:
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/reference/1-5-next-phase-sequential-execution-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/reference/1-4-next-phase-execution-roadmap-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference/5-14-zed-lapce-vscode-reference-adoption-matrix-v1.md`
>
> 目的:基于 Reasonix 执行汇总和本轮核对结果,列出仍不能标完成的缺口,形成可继续顺序执行的补充清单。
---
## 0. 当前核对结论
本轮已确认:
- [x] `ObjectWorkspacePath` 已在 `core-protocol` 定义并导出。
- [x] local folder projection 已输出 `resourceMeta.workspacePath`
- [x] 前端 `fileObjectIdentity` 已优先读取 `resourceMeta.workspacePath.objectIdentity`
- [x] `DocumentBuffer` / `DocBufferDirtyState` 已在 `core-protocol` 定义并有状态机测试。
- [x] `mnote.mindmap.*` / `mnote.office.*` Hermes resource tools 已出现,且最小测试通过。
本轮 Reasonix 执行后新增:
- [x] **Gap A0(主路径完成)**: local-first guard 已通过 ✅(`npm run check:local-first-convex-guard``{ok: true, guard: "local-first-convex"}`);`task451` / `task452` / `task453` 已复跑,旧 `task431` 仅保留为可选回归项。
- [x] **Gap A2(基础设施+运行时接入)**: `BufferStore` 已在 `rust/crates/mnote-web/src/document_buffer_store.rs` 实现,并加入 `AppState.buffer_store`。运行时接入完成:`write_local_markdown_page_body``mark_saved`、watcher → `mark_external_modified`、Hermes tools → 共享 helper。11 个单测全部通过(含 3 个新集成测试)。
- [x] **Gap A2(运行时接入—完成)**: `write_local_markdown_page_body` 已接入 `BufferStore.mark_saved()``documents.rs` `page_body_write``save` 路由已传递 `state.buffer_store`Hermes `doc_apply_block_ops``page_command` 已传递 `state.buffer_store`watcher 已在 `spawn_local_folder_watcher` 内调用 `mark_external_modified`。3 个新集成测试覆盖运行时入口。
- [x] **Gap A2(浏览器可见闭环)**: Codex 复核新增 `task484-local-folder-page-body-refresh-readback-smoke.js`,验证 `POST /api/page-body/write` 后磁盘 `.md`、Page Aggregate、刷新后的 ProseMirror 均读回新正文;旧的“刷新后 editor DOM 未显示正文”证据已判定为过期。
- [x] **Gap A7(基础设施)**: `CommandContext` + `when` evaluator 已在 `core-protocol/src/command.rs` 实现。7 个单测全部通过(含 3 个必补测试)。
- [x] **Gap A7UI/命令接入)**: File Tree 右键菜单、Delete/Backspace 快捷键、AI 写入能力已消费 `CommandContext` / `workspace.readonly` / `ai.canWrite`
- [x] **Gap B2(部分)**: `resource_rename_uses_resource_command_not_document_command` 测试通过;`local_agent_audit_snapshot_detects_resource_files` 通过;mindmap 生命周期测试通过。
- [x] **Gap C1(部分)**: `scripts/task444-convex-workspace-export-local-fixture-smoke.js` 通过;`export-convex-workspace-to-local.js` 既有 `--dry-run``--manifest``--conflict-report``--rollback` 能力已确认,但本轮未新增对应 smoke。
- [x] **core-protocol 全量测试**: 41 单元 + 6 editor bridge + 2 resource tree = 49 通过 ✅
- [x] **mnote-web 相关测试**: BufferStore 8 通过 + resource_trash 6 通过 + audit 2 通过 = 16 通过 ✅
---
## 1. Gap A0:补齐基线验证证据 — 🔄 部分完成
### 1.1 必跑命令
- [x] 运行 local-first guard。
- 命令:`npm run check:local-first-convex-guard`
- 结果:`{"ok": true, "checkedLines": 1, "guard": "local-first-convex"}`
- 验收:通过 ✅
- [x] 冲突 UI smoke — `task451-local-markdown-conflict-resolution-ui-smoke.js`
- 编号已存在脚本目录,需在完整后端环境下运行(依赖 mnote-web + Convex
- 验收:已复跑 browser smoke;单测级 `document_buffer_state_transitions` 不能替代 UI smoke。
- **执行记录(2026-05-21, Batch B Worker C**:✅ **PASS** — 4/4 子步骤全部通过(accept-disk, keep-current, merge-save, agent-conflict-source)。结果文件:`tmp/task451-local-markdown-conflict-resolution-ui-smoke/result.json`
- [x] 本地搜索索引 smoke — `task452-local-search-index-browser-smoke.js`
- 编号已存在脚本目录
- 验收:已复跑 browser smoke。
- **执行记录(2026-05-21, Batch B Worker C**:❌ **FAIL**`/api/search/documents` 返回 200 结果正确,但 page-settings 本地索引 panelbacklinks/tags30s 超时未渲染。归因:**UI 渲染回归**。结果文件:`tmp/task452-local-search-index-browser-smoke/result.json`
- **Codex 复核(2026-05-21**:✅ **PASS** — 诊断显示 API 和 UI 均已渲染,失败来自 smoke 仍期待 frontmatter 标题 `Search Smoke`;当前索引面板按路径口径展示 `README` / `README.md` 和链接片段。已更新断言并实跑通过。
- [x] agent changed files smoke — `task453-local-folder-page-ai-changed-files-smoke.js`
- 编号已存在脚本目录
- 验收:已复跑 browser smoke`local_agent_audit_snapshot_detects_changed_files``detects_resource_files` 只能覆盖 audit 单元路径。
- **执行记录(2026-05-21, Batch B Worker C**:✅ **PASS** — capturedKinds: session/run/events 全部捕获。结果文件:`tmp/task453-local-folder-page-ai-changed-files-smoke/result.json`
- **Codex 复核(2026-05-22**:✅ **PASS** — 已扩展为 local-first AI 前台同步 smokeclean path 验证 Hermes `run.completed.agentAudit.changedFiles` 后工具卡、磁盘 `.md`、Page Aggregate、ProseMirror 均出现 marker 且不进冲突态;dirty path 验证同一路径触发 `external-change-conflict`、冲突面板显示 agent run、冲突信封包含 `externalActor / dirtyState / bufferFileVersion`,diff 同时显示本地未保存 token 与 AI 写盘 token。
### 1.2 收尾
- [x] guard 输出已记录(见上)
- [x] browser smokes 已完成主路径复跑;`task443` / `task445` 已迁移到 local-first fixture 并作为归档证据。
- [x] 失败归属:本轮已无 A0 smoke 失败;`task452` 为旧断言假失败
---
## 2. Gap A2:把 DocumentBuffer 接入运行时 BufferStore — ✅ 完成
### 2.1 当前代码锚点(更新后)
- `rust/crates/mnote-web/src/document_buffer_store.rs`**新增**`BufferStore` + `BufferKey` 实现
- `rust/crates/mnote-web/src/app.rs``buffer_store: BufferStore` 已加入 `AppState`
- `rust/crates/mnote-web/src/lib.rs``pub mod document_buffer_store` 已注册
- `rust/crates/core-protocol/src/kernel.rs``DocumentBuffer` / `DocBufferDirtyState` / `ObjectWorkspacePath`(已有未改)
### 2.2 顺序任务完成情况
- [x] 新增运行时 `BufferStore` owner`rust/crates/mnote-web/src/document_buffer_store.rs`,通过 `AppState.buffer_store` 持有
- [x] 定义 buffer key:以 `ObjectWorkspacePath``workspace_id + source_kind + root_uri + relative_path + document_id` 作为稳定 `BufferKey`
- [x] 接入 tiptap 保存链:`write_local_markdown_page_body` / `documents.rs` `save` 路由已调用 `state.buffer_store.mark_saved()`
- [x] 接入外部文件变更 watcher`spawn_local_folder_watcher` 已通过 `buffer_store.mark_external_modified()` 更新对应 buffer
- [x] 接入 Hermes/Reasonix 写入后状态更新:`hermes_tools/doc.rs``hermes_tools/page.rs` 已通过共享 helper `write_local_markdown_page_body` 自动消费 BufferStore
- [x] conflict UI 数据源改为 buffer state:冲突信封已包含 `externalActor / dirtyState / bufferFileVersion``task451` 已覆盖
- [x] 浏览器可见读回:`task484-local-folder-page-body-refresh-readback-smoke.js` 已验证保存后刷新 ProseMirror 可见正文不丢失
### 2.3 必补测试
- [x] `document_buffer_reuses_workspace_path_key` — 通过 ✅
- [x] `document_buffer_marks_external_modified_for_clean_buffer` — 通过 ✅
- [x] `document_buffer_marks_stale_for_dirty_buffer` — 通过 ✅
- [x] `document_buffer_mark_saved_after_page_body_write` — 通过 ✅
- [x] 额外测试:`document_buffer_different_paths_different_keys`
- [x] 额外测试:`document_buffer_init_buffer_sets_file_version`
- [x] 额外测试:`document_buffer_mark_dirty_and_saved_round_trip`
- [x] 额外测试:`document_buffer_reuses_across_multiple_get_or_create_calls`
### 2.4 当前完成标准
- [x] `DocumentBuffer` 已有 mnote-web runtime `BufferStore` owner
- [x] `BufferKey` 可稳定区分不同文件
- [x] 11 个单测覆盖了关键 path + state transitions + save round-trip + 运行时入口
- [x] **已完成**tiptap/保存路由/Hermes 写入链已通过 `write_local_markdown_page_body` 统一消费 `BufferStore`
- [x] **已完成**watcher 已通过 `spawn_local_folder_watcher` 调用 `mark_external_modified`
- [x] **已完成**conflict UI 数据源已改为 buffer state 查询;`save_local_markdown_page_with_buffer_store` 在冲突发生时从 BufferStore 查询 `externalActor``dirtyState``bufferFileVersion` 并加入冲突信封;`/api/documents/buffer-state` 端点提供显式 buffer 状态查询;`document_page_shell` 在打开 local_folder 文档时调用 `init_buffer` 初始化 file_version。task451 smoke 已扩展为在每次冲突后验证信封包含这些 BufferStore 字段。
- [x] **已完成**:浏览器刷新后编辑器可见正文读回;验证 `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task484-local-folder-page-body-refresh-readback-smoke.js`
### 2.5 下一步
- 保存后刷新读回已由 `task484` 复核通过;后续若复现新问题,应以该 smoke 为回归入口。
- conflict UI 数据源已从纯前端猜测改为 BufferStore 信封 + `/api/documents/buffer-state` 端点。后续若需要实时更新(SSE 推送 buffer 变更事件)可在此基础上扩展。
---
## 3. Gap A7:落地 command context / context key — ✅ 主路径完成
### 3.1 当前代码锚点(更新后)
- `rust/crates/core-protocol/src/command.rs`**已扩展**:新增 `CommandContext``WhenExprNode``parse_when_expr``evaluate_when``check_when``is_command_enabled`
- `rust/crates/core-protocol/src/lib.rs`**已更新**:导出所有 command context 类型和函数
### 3.2 顺序任务完成情况
- [x] 定义最小 `CommandContext` 结构:包含 `workspace.sourceKind``workspace.readonly``tree.focusKind``tree.selectionCount``tree.selectionResourceKind``editor.dirty``editor.hasSelection``ai.canWrite` 共 8 个 key
- [x] 实现最小 `when` evaluator:支持 `key``!key``key == value``key != value``&&``||`、括号分组(完整的递归下降解析器 + AST evaluator
- [x] **File Tree 右键菜单接入 context**Batch C Worker C):在 SSR `layout.rs` 的 SIDEBAR_TREE_JS 中新增 `buildSidebarFileTreeContext()``evaluateSidebarFileTreeWhen()` 两个 JS 等价物。FileTree/Asset/Page 右键菜单的 `delete-trash``rename``new-file``new-folder``paste-into` 均已携带 `when: '!workspace.readonly'`。菜单构建时评估 when 表达式,命中时设置 `disabled: true` 并调整 title。workspace readonly 通过 `<html data-mnote-workspace-readonly>` 属性控制,默认 false。
- [x] **Delete/Backspace 快捷键接入 context**Batch C Worker C):keydown handler 中 Delete/Backspace 分支新增 `buildSidebarFileTreeContext('filetree')` + `evaluateSidebarFileTreeWhen(delCtx, '!workspace.readonly && tree.selectionCount')` 守卫。只读或无选中时阻止动作。
- [x] 把 AI 写入能力接入 context`ai.canWrite` key 已定义,并已在 hermes_tools 写入守卫中消费 `args.commandContext` / `args.command_context``ai.canWrite``workspace.readonly`
### 3.3 必补测试
- [x] `command_context_when_clause` — 通过 ✅(覆盖 key、!key、==、!=、&&、||、括号分组)
- [x] `command_context_disables_write_commands_for_readonly_workspace` — 通过 ✅
- [x] `command_context_filters_resource_commands_by_kind` — 通过 ✅
- [x] `command_context_default_is_allow` — 通过 ✅
- [x] `command_context_number_comparison` — 通过 ✅
- [x] `command_context_parse_error_returns_default` — 通过 ✅
- [x] `command_context_ai_can_write_gates_ai_actions` — 通过 ✅
### 3.4 当前完成标准
- [x] 最小 `CommandContext` 定义 + `when` evaluator 可工作
- [x] readonly / grant / resource kind 的禁用态可测试
- [x] **FileTree 右键菜单已接入**Batch C Worker C):delete-trash、rename、new-file、new-folder、paste-into 携带 `when: '!workspace.readonly'`
- [x] **Delete/Backspace 快捷键已接入**Batch C Worker C):keydown handler 中有 `!workspace.readonly && tree.selectionCount` 守卫
- [x] 下载、多选删除、粘贴、只读阻断的主路径 smoke 已由 `task471` / `task476` 在 Batch H 复跑覆盖;`task431` 作为更旧 DnD readonly smoke 仍保留为可选回归项。
- [x] **已完成**AI 写入能力 (`ai.canWrite`) 已接入 hermes_tools 写入守卫;`mnote.block.*``mnote.doc.markdown_edit``mnote.page.*``mnote.artifact.*``mnote.mindmap.apply_ops` 写入链共享 `ensure_write_authorized`,只读 scope、`workspace.readonly=true``ai.canWrite=false` 均会拒绝写入。
### 3.5 下一步
- `layout.rs` 右键菜单:已引入 `evaluateSidebarFileTreeWhen`Batch C Worker C 完成)
- Delete/Backspace:已接入(Batch C Worker C 完成)
- hermes_tools 写入守卫:已消费 `ai.canWrite` / `workspace.readonly` command context;后续若前端新增更多 AI 写入口,必须把同一份 `commandContext` 透传到 tool args。
- 归档前可选补跑 `task431-vscode-explorer-dnd-readonly-conflict-smoke.js`,但它不再代表 A7 主路径缺失。
### 3.6 Batch F 复核记录
- 2026-05-21Codex 将 Hermes 写入守卫收口为 `ensure_write_authorized``ToolCallInput``args.commandContext` / `args.command_context` 读取 `ai.canWrite``workspace.readonly`
- 2026-05-21`mnote.block.*` / `mnote.doc.markdown_edit` 继续经 `block::ensure_write_contract`,该函数已委托共享守卫;`mnote.page.*``mnote.artifact.*``mnote.mindmap.apply_ops` 也委托共享守卫。
- 2026-05-21:新增测试覆盖 `ai.canWrite=false``workspace.readonly=true` 的拒绝路径。
---
## 4. Gap B2Resource tools 到 Resource Tree 产品化 — 🔄 部分完成
### 4.1 当前代码锚点(已有未改)
- `rust/crates/mnote-web/src/routes/resource_trash.rs` — 媒体 / mindmap / table 生命周期使用 `tree.resource.*` 命令
- `rust/crates/mnote-web/src/routes/hermes_tools.rs`
- `rust/crates/mnote-web/src/hermes_tools/resource.rs`
- `rust/crates/mnote-web/src/hermes_tools/manifest.rs`
### 4.2 顺序任务完成情况
- [x] 明确 resource tool 是辅助入口,不是资源真相 — 文档中已有此约定
- [x] mindmap resource 生命周期走 `tree.resource.*` — 测试验证 ✅
- [x] office resource 生命周期走 `tree.resource.*` — ✅(仅链路存在,需更多测试覆盖)
- [ ] Resource Tree projection 显示资源对象 — 部分完成(filetree 已显示资源行)
- [x] agent audit 包含 resource files — `local_agent_audit_snapshot_detects_resource_files` 通过 ✅
### 4.3 必补测试
- [x] `tree_resource_rename_does_not_emit_document_command` — 被现有测试覆盖:
- `resource_rename_uses_resource_command_not_document_command` ✅(验证 canonicalCommand 为 `tree.resource.rename` 而非 `tree.node.rename`
- [x] `mindmap_resource_trash_restore_keeps_markdown_body` — 已覆盖:
- `mindmap_delete_restore_keeps_markdown_reference_out_of_lifecycle_command` ✅(验证 mindmap 生命周期不携带 documentId 从而不影响 markdown 页面)
- `local_folder_mindmap_delete_moves_resource_file_to_trash` ✅(2026-05-22 补断言:archive / restore 后 `Page.md` 正文引用保持原文)
- [x] `local_agent_audit_snapshot_detects_resource_files` — 通过 ✅
- [x] browser smokes`task443``task445` 需 Playwright 后端环境
- **执行记录(2026-05-21, Batch B Worker C**:❌ **FAIL** — 两个 smoke 均因 `waitForURL` 断言 `/mindmap/` 路由超时,实际导航走的是 `/documents/...?resourceTab=...`。归因:**脚本过时**mindmap 路由改为 resourceTabsmoke 未同步)。结果文件:`tmp/task443-filetree-mindmap-click-active-row-smoke/result.json``tmp/task445-filetree-mindmap-switch-no-flicker-smoke/result.json`
- **Codex 复核(2026-05-21**:✅ **PASS** — 已更新为当前 `resourceTab=resource:mindmap:{docId}:{mindmapId}` 断言,两个 smoke 实跑通过。`task445` 仍捕获一次跨文档 mindmap API `400 Bad Request` console error,但 smoke failures 为空,单独保留为后续噪声/边界排查。
- **Codex 复核(2026-05-22**:✅ **代码级修复**`task445` 的 400 不是纯噪声,根因是跨文档后 asset row 打开时沿用了当前 URL 的 document,而不是资源 owner document。已补 `sidebar_filetree_asset_open_uses_owner_document_id` 回归测试,并让 filetree asset row 输出/消费 `data-owner-document-id``task445` 同步增加 owner URL 断言。浏览器复跑当前被旧 Convex fixture 阻塞:`workspaces:ensureDefaultWorkspace` function 已退役,返回 502。
- **Codex 复核(2026-05-22 续)**:✅ **PASS**`task443` / `task445` 已迁移为 local-first fixture(临时 root + `.mnote/workspace.json` + 本地 `.md` / `.mindmap.json`),不再依赖退役 Convex function。复跑命令:
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task443-filetree-mindmap-click-active-row-smoke.js`
- `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task445-filetree-mindmap-switch-no-flicker-smoke.js`
- 两者结果均为 PASS,结果文件分别为 `tmp/task443-filetree-mindmap-click-active-row-smoke/result.json``tmp/task445-filetree-mindmap-switch-no-flicker-smoke/result.json`
### 4.4 完成标准
- [x] Resource Tree 是资源归属真相的大部分覆盖
- [ ] Markdown 正文只保留资源引用 — 已有原则,未额外验证
- [x] AI resource tools 与 tree.resource 生命周期一致 — 验证通过
---
## 5. Gap C1Convex 迁移产品化补证据 — ✅ 完成
### 5.1 当前代码锚点(已有未改)
- `rust/crates/storage-convex-bridge/src/mapping.rs`
- `rust/crates/mnote-web/src/transport/convex.rs`
- `rust/crates/mnote-web/src/routes/local_folder_source.rs`
- `scripts/task444-convex-workspace-export-local-fixture-smoke.js`
- `scripts/export-convex-workspace-to-local.js`
### 5.2 顺序任务完成情况
- [x] 定义 dry run response — `export-convex-workspace-to-local.js` 支持 `--dry-run` 标识 ✅
- [x] 定义 migration manifest — 支持 `--manifest` 参数 ✅
- [x] 实现同名冲突不覆盖 — 导出时不覆盖已有文件,输出冲突报告 ✅
- [x] 实现 rollback — 支持 `--rollback` 参数 ✅
- [x] 导出完成后重建 local index — task444 smoke 验证通过 ✅
- [x] dry-run/conflict/rollback 独立 smoke — `task455-convex-export-plan-rollback-smoke.js` 三个行为全部通过 ✅
### 5.3 必补测试
- [x] `task444-convex-workspace-export-local-fixture-smoke.js` — 通过 ✅
- [x] 新增 smokedry run 不写文件 — `task455` 验证 `--dry-run` 后 pages 目录不存在 ✅
- [x] 新增 smoke:同名冲突不覆盖 — `task455` 验证冲突后 exit(2) + 页面前内容保持 ✅
- [x] 新增 smokerollback 恢复导出前状态 — `task455` 验证 rollback 后 pages/assets 均删除 ✅
---
## 6. 最终收尾
### 6.1 验证命令结果
- [x] `cargo test -p core-protocol` — 41+6+2=49 passed ✅
- [x] `cargo test -p mnote-web document_buffer` — 8 passed ✅
- [x] `cargo test -p mnote-web resource_trash` — 6 passed ✅
- [x] `cargo test -p mnote-web local_agent_audit_snapshot` — 2 passed ✅
- [x] `npm run check:local-first-convex-guard``{"ok": true}`
- [x] `node scripts/task444-convex-workspace-export-local-fixture-smoke.js``{"ok": true}`
- [x] `cargo fmt --check --all --manifest-path rust/Cargo.toml` — 通过 ✅
- [x] `git diff --check -- <changed-files>` — 通过 ✅
### 6.2 Browser smokes(需完整后端环境)
- [ ] `task431-vscode-explorer-dnd-readonly-conflict-smoke.js`
- [x] `task451-local-markdown-conflict-resolution-ui-smoke.js`
- [x] `task452-local-search-index-browser-smoke.js`
- [x] `task453-local-folder-page-ai-changed-files-smoke.js`
- [x] `task443-filetree-mindmap-click-active-row-smoke.js`
- [x] `task445-filetree-mindmap-switch-no-flicker-smoke.js`
### 6.2.1 本次执行结果(Batch B Worker C @ 2026-05-21
- `task451-local-markdown-conflict-resolution-ui-smoke.js`
- **PASS** ✅ — 4 个子步骤全部通过:accept-disk、keep-current、merge-save、agent-conflict-source
- 命令:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js`
- 结果:`tmp/task451-local-markdown-conflict-resolution-ui-smoke/result.json`
- `task452-local-search-index-browser-smoke.js`
- **PASS** ✅ — Codex 复核后更新旧断言;本地索引面板已渲染 backlinks/tags`README.md`、链接片段和 `#alpha` 可见
- 命令:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task452-local-search-index-browser-smoke.js`
- 结果:`tmp/task452-local-search-index-browser-smoke/result.json`
- 归因:**脚本断言过时** — 原断言期待 frontmatter 标题,当前索引面板按路径标题展示
- `task453-local-folder-page-ai-changed-files-smoke.js`
- **PASS** ✅ — clean AI 写入后 Page Aggregate / ProseMirror 前台同步,dirty AI 写入触发 conflict UI 并显示 BufferStore 信封
- 命令:同上
- 结果:`tmp/task453-local-folder-page-ai-changed-files-smoke/result.json`
- `task443-filetree-mindmap-click-active-row-smoke.js`
- **PASS** ✅ — local-first fixture 下,mindmap 资源行点击后 URL 使用 `/documents/...&resourceTab=resource:mindmap:...`,且对应 asset row 选中
- 命令:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task443-filetree-mindmap-click-active-row-smoke.js`
- 结果:`tmp/task443-filetree-mindmap-click-active-row-smoke/result.json`
- 归因:**脚本过时后已更新并去 Convex fixture 化** — mindmap 已改为 resourceTab 路由,fixture 改为 local_folder
- `task445-filetree-mindmap-switch-no-flicker-smoke.js`
- **PASS** ✅ — local-first fixture 下,页面和 mindmap 来回切换时 file tree root 稳定,选中态正确;跨文档资源重新打开时 URL pathname 使用资源 owner document
- 命令:`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/google-chrome-stable node scripts/task445-filetree-mindmap-switch-no-flicker-smoke.js`
- 结果:`tmp/task445-filetree-mindmap-switch-no-flicker-smoke/result.json`
- 归因:**脚本过时后已更新并去 Convex fixture 化** — 400 根因已收口到 ResourceTab owner document URL 同步
### 6.2.2 本次执行汇总
| Smoke | 结果 | 归因 |
|-------|------|------|
| task451 | ✅ PASS | — |
| task452 | ✅ PASS | 脚本断言过时,已按当前路径标题和链接片段语义更新 |
| task453 | ✅ PASS | 已扩展为 AI clean/dirty 前台同步与 conflict UI 证据 |
| task443 | ✅ PASS | 已改为 resourceTab 路由断言,并迁移到 local-first fixture |
| task445 | ✅ PASS | 已改为 resourceTab 路由断言,并迁移到 local-first fixtureResourceTab owner document URL 已修复 |
---
### 6.3 总完成率
| Gap | 状态 | 关键产出 | 剩余 |
|-----|------|----------|------|
| A0 | ✅ 主路径完成 | guard、task451、task452、task453 通过 | task431 可选复跑 |
| A2 | ✅ 完成 | `BufferStore` 实现 + 11 teststask451 覆盖 BufferStore 冲突信封;task484 验证保存后磁盘、PageAggregate、刷新后 ProseMirror 全部读回 | — |
| A7 | ✅ 主路径完成 | `CommandContext` + when evaluator + 7 testsFileTree 菜单、Delete/Backspace、AI 写入守卫接入 | task431 可选复跑 |
| B2 | ✅ 主路径完成 | 资源生命周期 + agent audit 测试通过;mindmap 正文引用保持、ResourceTab owner 修复与 task443/task445 local-first browser 证据已补齐 | Resource Tree projection 产品形态深化另列后续项 |
| C1 | ✅ 完成 | export/rollback/dry-run 脚本 + task444/task455 smoke 全部通过 | — |
归档结论:本清单原本承担的 gap closure 已完成。后续活跃执行转入 `1-8` 的顺序表与 `7-14` 的 AI Markdown 编辑路径收敛,不再把本文件作为 active process。