chore: 保存当前架构收口与 bug 修复快照

归档本轮 P0/P1 bug 修复、设计审查迁移、AI selection scope 收口与 stream contract 调整,并保留当前 05 主线迁移起点。
This commit is contained in:
lix-2026
2026-05-18 17:01:35 +08:00
parent 61ee4a38a2
commit a2cb1338c8
953 changed files with 14383 additions and 211845 deletions
@@ -1,531 +0,0 @@
# 08 [process] Kernel 架构收口与下一阶段优先级 Review / Checklist v1
> 更新时间:2026-05-16
>
> 执行状态:`process`
>
> 关联文档:
> - `/mnt/Data1T/mnote/AGENTS.md`
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
---
## 1. Review 结论
当前内核架构的骨架已经基本成立,但还不能说“已完善”。
已经成立的部分:
- `tree-first graph kernel` 是长期语义事实源。
- `mnote-web` 已是 `3000` 主 Web 执行面。
- 文档页默认主编辑器已切到页面内 `leptos-tiptap` island。
- Page Aggregate 已进入 Rust-first 读取链,并已输出 `blockDocument / blockProjectionVersion / projectionSource`
- 页面/块 AI tools 已开始走 `mnote.doc.*` / `mnote.block.*`、Page Aggregate block projection 和 Rust `EditorCommand`
仍未完善的部分:
- Page Aggregate 仍是过渡态,block projection 主要从 `documents.content` / local markdown content 投影,不是 EditorBlockDocument 原生落库完成态。
- 标题、正文、页面设置、page tree、AI 写入口尚未完全闭环到同一组 projection / command family。
- `tree.*` 已是 preferred command name,但 `documents.*` 兼容命令面仍未完全降级。
- `/api/tree/events` 已是 tree realtime 主链,但 Sidebar、page subtree、filetree、preferred snapshot 还没有完全统一到一套 live cache。
- AI 块工具已有最小闭环,但当前编辑路径以块操作(blockId)为中心——AI 被迫理解 UI 层概念,且本地 `.md` 文件无 AI 写入路径。`7-14` 提出应将 AI 编辑主路径修正为 markdown 文本级(search/replace),`mnote.block.*` 降级为结构性辅助,在线 Convex 文档与本地文件共用同一条 markdown 写入路径。
补充 BlockNote / Tiptap AI 参考后的判断:
- BlockNote AI 和 Tiptap AI Toolkit 可以参考的是 AI runtime shape 与 tool contract,不是 mnote 的事实源。
- AI 方向下一步只应补基础底座:`PageAIContextBuilder``scope=selection``page_xml/text`、tool manifest annotations、`PageAIReviewSession`、accept/reject/retry/abort 状态机。
- 2026-05-16 补充(受 `7-14` 方向修正):AI 编辑主路径应从块级操作调整为 markdown 文本级 search/replace,新增 `mnote.doc.markdown_edit` 作为主要 AI 写入工具;当前 `local_rule` planner`direct_block_edit_operations`)是过渡实现,应退役。在线 Convex 文档和本地 `.md` 文件共用同一条写入路径。
- 在非 AI 主架构和 AI 基础合同打牢之前,不应扩展新的 AI agent 工作流、复杂 AI UI 或跨页面智能功能。
因此,下一阶段不应优先“大改架构”或“大量加新功能”,而应优先做:
> **架构收口 + 验收矩阵 + 定向找 bug。**
更具体地说:
> **先打牢除 AI 以外的 Page Aggregate / tree command / tree realtime 主架构,再把 AI 的基础读写、上下文、冲突与审阅底座打牢;之后才进入 AI 功能扩展。**
---
## 2. 下一步总优先级
总原则:
- 非 AI 主架构优先级高于 AI 功能扩展。
- AI 当前只推进基础设施,不推进新功能面。
- AI 基础设施必须服从 Rust kernel / Page Aggregate / EditorCommand / Hermes audit,不引入 BlockNote runtime 或 Tiptap/ProseMirror editor truth。
### P0Page Aggregate 单一真源收口
目标:
- 标题、正文、页面设置、page tree、AI 读写入口继续收敛到同一组 Page Aggregate projection / command family。
- 前端不在页面壳、island 外侧、Sidebar preferred snapshot 外再拼第二份页面真相。
- 明确当前 block projection 过渡态和长期 EditorBlockDocument 原生落库目标之间的边界。
判断标准:
- 页面读取只走 Rust `/api/page-aggregate/:id` 正式读链。
- 页面标题、正文、页面设置写入后,Page Aggregate、页头、Sidebar、Breadcrumb、File Tree、AI fetch 回读一致。
- 失败时明确 degraded / conflict / stale,不返回看似成功的旧快照。
### P0Tree Command Cutover
目标:
- `tree.*` 成为正式命令面。
- `documents.*` 只保留为兼容层,不再继续扩写长期业务语义。
- 页面新建、重命名、移动、归档、恢复、删除、资源生命周期都能通过正式 tree/page command 解释。
### P0Tree Realtime Live Cache 统一
目标:
- `/api/tree/events` 的 snapshot / delta / resync 成为 Sidebar、Page Tree、File Tree、page subtree 的共同 live cache 来源。
- 减少 query/refetch/freshness 补偿链和旧快照回闪。
### P0:AI 基础工具与审阅底座
目标:
- 不继续扩新 AI 功能,先把当前 `mnote.doc.*` / `mnote.block.*` 变成可靠、可测、可审阅、可回滚的基础工具链。
- 参考 BlockNote / Tiptap 的架构形态,但保持 mnote Rust-owned tool runtime。
必须补齐:
- `PageAIContextBuilder``scope=selection`
- `format=page_xml/text/json` 的稳定输出边界。
- tool manifest annotations`readonly/destructive/requiresApproval/selectionEffect/runtimeOwner/writeOwner`
- `PageAIReviewSession`preview -> accept/reject/retry/abort。
- 多块插入的边界与 inserted block ids。
- 复杂块移动阻断矩阵。
- stale revision / stale blockRevisionRef / idempotency 重放。
明确不做:
- 不新增跨页面 AI agent 工作流。
- 不新增复杂 AI 自动化功能面。
- 不把 `@blocknote/xl-ai` 或 BlockNote `AIExtension` 作为 runtime dependency。
- 不让 AI 写入绕过 `dryRun/idempotencyKey/revision/conflictDetectionKey/revisionRef`
- **不把 `direct_block_edit_operations` / `local_rule` planner 当作长期 AI 编辑路径(当前为过渡实现,将被 `markdown_edit` 替代)。**
- **不新增 `mnote.block.*` 工具;现有保留为结构性辅助。**
**2026-05-16 新增(受 7-14 方向修正):**
- **Phase A(当前唯一活跃实施)**:`mnote.doc.markdown_edit` + `mnote.doc.fetch` 增强(search/replace + `format: "markdown"` + local source + Hermes 注册,7-14 v2 Phase A)。
- **Phase B(下一阶段)**:退役 `direct_block_edit_operations``page_ai_workflow.rs` 改走 `markdown_edit`7-14 v2 Phase B)。
- 在线 Convex 文档和本地 `.md` 文件共用同一条 markdown AI 写入路径(`resolve_source` → Convex | LocalFS)。
### P1:定向 Bug Hunt
目标:
- 不做泛泛“找 bug”,只围绕已知架构风险做定向排查。
优先找:
- Page Aggregate 回流不一致。
- AI 块写入 conflict / stale revision。
- File Tree `{title}.md` 与 page title sync。
- tree stream resync / 双浏览器一致性。
- debug / compat / fallback 是否混入主链。
---
## 3. 可执行 Checklist
### 3.1 Page Aggregate 单一真源
- [x] 盘点 `wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 是否仍有 runtime fallback 或 TS builder 读取分支。
- [x] 盘点 `wolai-frontend/src/lib/documents/page-aggregate-builder*` 的引用,确认只剩 test helper / historical adapter。
- [x] 检查 `/api/documents/page` 仍返回明确 `410`,不参与 runtime 主链。
- [x] 跑文档页打开 smoke,记录 `/api/page-aggregate/:id` 是首要读链。
- [x] 新建页面后检查 Page Aggregate `identity/head/body/tree/stats` 字段完整。
- [x] 修改标题后检查 Page Aggregate、页头、Breadcrumb、Sidebar、File Tree `{title}.md` 同步。
- [x] 修改正文后检查 Page Aggregate `body.revision/conflictDetectionKey/blockDocument` 同步。
- [x] 修改页面设置后检查 Page Aggregate 与 island runtime page options 同步。
- [x] 刷新页面后检查标题、正文、页面设置不回退到旧快照。
- [x] 破坏或暂停 Convex query,检查响应是 degraded/error,不返回伪 fixture。
- [x]`design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` 勾选已验证项,并补证据路径。
2026-05-16 静态审计证据:
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 只通过 `loadPageAggregateFromRustSnapshot` 请求 Rust `/api/page-aggregate/:documentId``loadPageAggregate` 不再回退 TS builder`buildServerBridgeRequest("/documents/page")` 只用于构造转发 header/request,不调用 Next `/api/documents/page` compat route。
- `rg -n "buildPageAggregateFromDocumentPayloads|page-aggregate-builder" wolai-frontend/src rust scripts design --glob '!design/05-editor-mainline/reference-code/**' --glob '!node_modules/**'` 显示 runtime 非测试引用仅剩 builder 定义;代码引用只有 `wolai-frontend/src/lib/documents/page-aggregate-builder.test.ts`
- `wolai-frontend/src/app/api/documents/page/route.ts` 明确返回 `410`,错误文案指向 `/api/page-aggregate/:documentId``route.test.ts` 覆盖该行为。
- 已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate``cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate``cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts`
- 全仓 `git diff --check` 当前被既有删除文件 `rust/spikes/leptos-tiptap-spike/trunk-8123.err` 阻断:`fatal: unable to generate checkdiff for ...`;本轮未清理该无关脏改动,已改用限定文件 diff check 复核。
2026-05-16 真实 3000 smoke 证据:
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task110-page-title-single-truth-smoke.js` 通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json`。该脚本创建两页、修改标题,并验证页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致。
- 文档页打开与 Page Aggregate snapshot 验证通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json`,截图:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.png`。文档响应 `x-mnote-web-owner=mnote-web``x-mnote-web-shell=document`HTML 包含 `data-page-aggregate-snapshot="mnote.page_aggregate.v1"``data-page-tree-source="page_aggregate.tree.pageSubtree"`,同一临时页 `/api/page-aggregate/:id` 回读 `schema=mnote.page_aggregate.v1`
- 新建页面 Page Aggregate 字段完整性验证通过,证据:`tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json``identity/head/body/tree/stats` 全部为 true`body.blockProjectionVersion=1``projectionSource=documents.content`
- 说明:曾尝试在浏览器 network 中直接捕获 `/api/page-aggregate/:id`,证据 `page-open-network.stderr.log`;当前 Rust SSR 主入口会直接把 Page Aggregate snapshot 写入 HTML,浏览器侧不必出现该 API 请求,因此该尝试不作为失败验收项。
2026-05-16 正文写入后 body 同步证据:
- 新增并运行 `scripts/task-page-aggregate-body-sync-smoke.js`,通过真实 3000 文档页输入正文,等待 `/api/documents/save` 成功后轮询 `/api/page-aggregate/:id`
- 证据:`tmp/page-aggregate-body-sync-smoke/mp87mgz7.json`,截图:`tmp/page-aggregate-body-sync-smoke/mp87mgz7.png`stdout`tmp/page-aggregate-body-sync-smoke/latest.stdout.json`
- 验证结果:`body.revision``0` 更新到 `1``body.conflictDetectionKey``tree_1778927703753_1:0` 更新到 `tree_1778927703753_1:1``body.blockProjectionVersion=1``projectionSource=documents.content``blockDocument.blocks[0]` 回读到文本 `Page Aggregate body sync mp87mgz7``revisionRef=pageRev:1:block:block_1:hash:fnv1a64:208e0e63c853eef5`
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command``cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document`
2026-05-16 页面设置写入后 pageOptions 同步证据:
- 新增并运行 `scripts/task-page-aggregate-options-sync-smoke.js`,通过真实 3000 文档页打开页面设置,依次修改 `wideLayout=true``smallText=true``layoutDensity=compact`,等待 `/api/documents/options` 返回 `page.layout.updateOptions` 后轮询 `/api/page-aggregate/:id`
- 证据:`tmp/page-aggregate-options-sync-smoke/mp87y6j3.json`,截图:`tmp/page-aggregate-options-sync-smoke/mp87y6j3.png`stdout`tmp/page-aggregate-options-sync-smoke/latest.stdout.json`
- 验证结果:Page Aggregate `layout.pageOptions` 依次回读到 `wideLayout=true``smallText=true``layoutDensity=compact`;运行时 DOM 同步为 `data-page-wide-layout="true"``data-page-small-text="true"``data-layout-density="compact"`island `editorRoot``.editor-surface` 同步,字号从 `16px``15px`,段落间距从 `8px``4px`
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options``cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts`
2026-05-16 刷新后标题 / 正文 / 页面设置不回退证据:
- 新增并运行 `scripts/task-page-aggregate-refresh-persistence-smoke.js`,同一临时页内依次通过 UI 写入标题、正文和页面设置,等待 Page Aggregate 回读最新 `head/body/layout` 后刷新页面,再断言页头、正文 DOM、页面设置控件、runtime DOM 与 `/api/page-aggregate/:id` 均保持最新值。
- 证据:`tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`,截图:`tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png`stdout`tmp/page-aggregate-refresh-persistence-smoke/latest.stdout.json`
- 验证结果:刷新前后 `head.title=Page Aggregate refresh mp88fr6k``body.revision=1``body.conflictDetectionKey=tree_1778929070007_1:1``blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k``layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact`;刷新后页头标题、`.ProseMirror` 正文、设置控件、`documentElement``.document-shell`、island root 和 `.editor-surface` 均保持最新值。
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs``initializePageUiSurfaces` 延后到 `DOMContentLoaded` 后执行,避免 layout 脚本早于嵌入 Page Aggregate JSON / island DOM 完成时把 runtime 属性按默认 pageOptions 应用。
- 缺陷记录:`bugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.md`
- 配套测试已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate``cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options`
2026-05-16 Convex query 失败时不返回伪 fixture 证据:
-`rust/crates/mnote-web/src/routes/web_shell.rs` 补充负向 route 测试,构造隔离配置 `convex_url=http://127.0.0.1:9``allow_dev_fixtures=false``query_fixtures_json=None`,模拟 Convex query 不可达。
- `/api/page-aggregate/doc_1?workspaceId=ws_demo` 返回 `503 SERVICE_UNAVAILABLE``x-error-code=convex_unavailable``x-error-phase=query_send``x-upstream-service=convex`body 为 `ok=false/code=convex_unavailable`,且没有 `schema` / `result`
- `/documents/doc_1?workspaceId=ws_demo` 返回同类错误 JSONHTML body 不包含 `mnote.page_aggregate.v1``data-mnote-dev-fixture``data-page-aggregate-snapshot`
- 验证命令已通过:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture -- --nocapture``cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture -- --nocapture``cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`
验证命令建议:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate
node scripts/task110-page-title-single-truth-smoke.js
node scripts/task-page-aggregate-body-sync-smoke.js
node scripts/task-page-aggregate-options-sync-smoke.js
node scripts/task-page-aggregate-refresh-persistence-smoke.js
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_errors_without_convex_or_fixture
cargo test --manifest-path rust/Cargo.toml -p mnote-web document_shell_errors_without_convex_or_fixture
```
### 3.2 Page Block AI Tooling
- [ ]`7-10` 为工具执行清单、`7-11` 为 AI runtime 基础设计,逐 phase 检查已勾选项是否都有代码、测试、smoke 证据。
- [x]`mnote.doc.fetch scope=selection` 补真实选区上下文输入和返回结构。
- [x]`mnote.doc.fetch format=page_xml` 补最小 PageXML 输出。
- [x]`mnote.block.fetch format=text/page_xml` 补格式分支。
- [x] 给 tool manifest 补 `annotations`,区分 readonly / destructive / requiresApproval / selectionEffect。
- [x] 定义 `mnote.page_ai_context.v1`,明确 context 只来自 Page Aggregate projection。
- [ ] 定义 `mnote.page_ai_review_session.v1`,明确 accept / reject / retry / abort 语义。
- [ ]`mnote.block.insert_after` 补多块插入限制和返回 inserted block ids。
- [ ]`mnote.block.move_after` 补标题带子块阻断 smoke。
- [ ]`mnote.block.move_after` 补列表项阻断 smoke。
- [ ]`mnote.block.move_after` 补表格 / mindmap / resource 阻断 smoke。
- [x]`mnote.block.replace` 补 stale revision 失败用例。
- [x]`mnote.block.replace` 补 stale blockRevisionRef 失败用例。
- [x] 给写工具补重复 idempotencyKey 的端到端用例。
- [x] 检查 `mnote.page.save` 在 manifest / UI 中继续标为页面级兜底,不显示为精确块编辑主入口。
- [ ] 检查任何新增 AI surface 是否只是基础 review/context/tooling 验收,不是新功能扩展。
- [ ] 更新 `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md` 的每个 phase 证据。
- [ ] 更新 `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` 的 Hermes 工具路由与审阅面 checklist。
**新增(2026-05-16,受 7-14 方向修正):**
**Phase A(当前唯一活跃实施):**
- [ ] `mnote.doc.markdown_edit` 核心实现:resolve_source → search_replace → write_target
- [ ] `mnote.doc.markdown_edit` 搜索替换语义:精确匹配 → fuzzy fallback → operationsFailed7-14 §5
- [ ] `mnote.doc.markdown_edit` Convex 写入 adapter(复用 `page.body.save` 链路)
- [ ] `mnote.doc.markdown_edit` 本地文件写入 adapter`fs.writeFile` + GFM AST markdown 序列化)
- [ ] `mnote.doc.fetch format: "markdown"` 增强(Page Aggregate 产出 PageMarkdown
- [ ] `mnote.doc.fetch` local source 支持(识别本地文件路径并返回 `.md` 原文)
- [ ] Hermes tool manifest 注册 `mnote.doc.markdown_edit`
- [ ] Hermes `mnote` plugin 更新:`mnote_doc_fetch` schema 增加 `format: "markdown"` 和本地文件路径支持
- [ ] 乐观锁:`revision` 冲突检测
- [ ] 单元测试 + smoke(在线 + 本地两种 source
**Phase B(下一阶段):**
- [ ] `direct_block_edit_operations` 退役(`page_ai_workflow.rs` `local_rule` 分支)
- [ ] `page_ai_workflow.rs` 修正:system prompt 改为产 search/replace 对,底层走 `mnote.doc.markdown_edit`
- [ ] 补全 operation schema`content` 字段)
- [ ] 浏览器 smoke:自然语言编辑可用(不使用「」格式)
**Phase C(设计冻结,不实施):**
- [ ] 流式 apply + suggest/review 设计已冻结,不阻塞 Phase A/B 实施
2026-05-16 Page Block AI context / format focused smoke 证据:
- 新增并运行 `scripts/task-page-block-ai-context-format-smoke.js`,通过真实 3000 + 测试账号创建临时页,用 `mnote.page.save` 初始化 `p_1/p_2/p_3`,再验证 Hermes mnote tools 的 context / format / annotations / selection guard。
- 证据:`tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`,截图:`tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png`
- 验证结果:
- `GET /api/hermes/tools/mnote/manifest``mnote.doc.fetch``mnote.block.fetch``mnote.block.replace``mnote.page.save` 均包含 `readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect``mnote.page.save` 明确 `destructive=true``approvalMode=yolo`,继续定位为页面级粗粒度兜底。
- `mnote.doc.fetch scope=selection selectedBlockIds=["p_2"] format=page_xml` 返回 `schema=mnote.page_ai_context.v1``allowedTargetBlockIds=["p_2"]``revision/conflictDetectionKey``revisionRef``content` 只包含 `p_2``第二段 mp8ddr4n`,不包含未选中的 `p_1/p_3`
- `mnote.doc.fetch scope=selection format=text` 返回 `[p_2] 第二段 mp8ddr4n`,不包含未选中块。
- `mnote.block.fetch blockId=p_2 format=page_xml/text` 返回目标块、`revisionRef` 和同父级 before/after 上下文 `p_1/p_3`
- `mnote.doc.apply_block_ops dryRun=true allowedTargetBlockIds=["p_2"]` 尝试 replace `p_1` 被拒绝,HTTP `400`,错误码 `mnote_block_target_out_of_scope`
- 边界:本轮只验证 `mnote.doc.apply_block_ops` 的 selection scope guard;单个 `mnote.block.replace/insert_after/move_after` 尚未校验 `allowedTargetBlockIds`,不能据此勾选完整 selection 写保护矩阵。
- 配套验证已通过:`node --check scripts/task-page-block-ai-context-format-smoke.js``cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture``MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js`
2026-05-16 Page Block AI stale / idempotency focused smoke 证据:
- 新增并运行 `scripts/task-page-block-ai-conflict-idempotency-smoke.js`,通过真实 3000 + 测试账号创建临时页,用 `mnote.page.save` 初始化 `p_1/p_2`,再围绕 `mnote.block.replace` 验证冲突和幂等安全边界。
- 证据:`tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq.json`,截图:`tmp/page-block-ai-conflict-idempotency-smoke/mp8dyqiq-page.png`
- 验证结果:
- 首次 `mnote.block.replace` 携带最新 `revision/conflictDetectionKey/blockRevisionRef/idempotencyKey` 成功写入 `p_2`,返回 `commandName=page.body.save``commandId=page_body_save_req_1778938353824_11`
- 使用同一 `idempotencyKey=idem_conflict_replace_mp8dyqiq` 再次调用 `mnote.block.replace`,即使请求 content 改成不同文本,也 replay 同一 `commandId`Page Aggregate 回读 `p_2` 仍为首次写入文本,`revisionAfterFirst=2``revisionAfterReplay=2`,确认不重复写入。
- 使用旧 `revision/conflictDetectionKey` 调用 `mnote.block.replace` 返回 HTTP `400`、错误码 `mnote_tool_conflict`,正文保持首次写入结果。
- 使用最新 `revision/conflictDetectionKey` 但旧 `blockRevisionRef` 调用 `mnote.block.replace` 返回 HTTP `400`、错误码 `mnote_tool_conflict`,正文保持首次写入结果。
- 边界:本轮只覆盖 `mnote.block.replace` 直接 tool executor 的 stale revision / stale blockRevisionRef / idempotency replay;不代表 `mnote.block.insert_after` 幂等矩阵、所有写工具幂等矩阵、review session accept stale 或 accept/reject/retry/abort 已完成。
- 配套验证已通过:`node --check scripts/task-page-block-ai-conflict-idempotency-smoke.js``cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture``MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-conflict-idempotency-smoke.js`
验证命令建议:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime editor_document
node scripts/task-page-block-ai-tools-smoke.js
```
### 3.3 Tree Command Cutover
- [x] `rg -n "documents\\.create|documents\\.title\\.update|documents\\.move|documents\\.archive|documents\\.restore" rust wolai-frontend` 盘点旧命令引用。
- [x] 将仍在 runtime 主链的旧命令按 owner 分类:必须迁移、兼容保留、测试 fixture。
- [x] 确认页面新建默认输出 `tree.node.create` 或正式 tree command。
- [x] 确认页面重命名默认输出 `tree.node.rename` 或正式 page/tree command。
- [x] 确认移动页面默认输出 `tree.subtree.move`
- [x] 确认归档 / 恢复 / 永久删除的 command family 与 resource lifecycle 设计一致。
- [x] 对 compat alias 返回增加 owner / deprecated 标识,避免被当作主链。
- [x] 更新 `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md` 或新增后续 process checklist。
2026-05-16 Tree Command Cutover 静态盘点与 alias 标识证据:
- 只读盘点命令:`rg -n "documents\\.(create|title\\.update|move|archive|restore|delete|purge|copy_tree)" rust wolai-frontend scripts --glob '!node_modules/**'`
- 前端主链:`wolai-frontend/src/lib/documents/tree-command-client.ts` 的新建、重命名、移动、归档、恢复、永久删除和复制均 POST `/api/tree/commands`,返回 meta 使用 `TREE_COMMAND_PROTOCOL.*.preferredCommandName``TREE_COMMAND_PROTOCOL` 中的 `documents.*` 只保留为 `compatCommandName`
- Next tree command route`wolai-frontend/src/app/api/tree/commands/route.ts` 在 create / move / rename / archive / restore 分支分别构造 `tree.node.create``tree.subtree.move``tree.node.rename``tree.node.archive``tree.node.restore`
- Rust Web tree route`rust/crates/mnote-web/src/routes/tree.rs``create_command_wire` 输出 `tree.node.create``tree.node.rename``tree.subtree.move``tree.node.archive``tree.node.restore``tree.node.purge``tree.subtree.copy``tree_command` route 只接收 action,不接收旧 `documents.*` command name 作为主链输入。
- 兼容保留:`bridge-runtime` 仍接受 `documents.create/title.update/move/delete/restore/purge/copy_tree`,但本轮已在对应 execution plan 的 `args_json.commandProtocol` 增加 `family=tree``owner=rust-runtime-kernel``preferredCommandName``compatCommandName``deprecatedAlias`;旧 `documents.*` alias 会标记 `deprecatedAlias=true`
- Transport 边界:`rust/crates/mnote-web/src/transport/convex.rs` 发送给 Convex legacy mutation 前会剥离 `commandProtocol``streamDeltaHint``domainEventHint``domainEventPlan(s)`,避免 legacy validator 把审计字段当写入参数。
- 仍保留为后续兼容收口点:`wolai-frontend/src/app/api/documents/create-child/route.ts` / `page-command-adapter.ts` 仍构造 `documents.create`,当前分类为 compat-only`mnote-cli` 仍有历史 `documents.*` CLI 构造,不属于 3000 主交互链。
2026-05-16 Tree Command Cutover 验证命令:
```bash
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_ -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
```
验证命令建议:
```bash
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime tree_command
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_command
node scripts/task122-rust-web-create-page-ui-smoke.js
```
### 3.4 Tree Realtime Live Cache
- [x] 2026-05-17 WS push 迁移(`57ec8322`):`/api/realtime/ws` 成为主 transportSSE 降级为 fallback。mutation 后通过 `stream_delta_tx` broadcast channel 推送 deltaWS 客户端零 Convex 查询。
- [x] 盘点 Sidebar、Page Tree、File Tree、page subtree 仍依赖 query/refetch/freshness 补偿的位置。
- [x] 确认 `/api/realtime/ws`(主)和 `/api/tree/events` SSE fallback 的 snapshot / delta / resync payload 覆盖 page/file/resource row。
- [x] 新建页面后,双浏览器 A/B 检查另一端无需刷新出现页面。
- [x] 重命名页面后,双浏览器 A/B 检查 Sidebar / Breadcrumb / File Tree 一致更新。
- [x] 移动页面后,双浏览器 A/B 检查 tree order 不回闪。
- [x] 删除 / 恢复后检查 trash 与主树事件一致。
- [x] 断开 SSE 后恢复,检查 resync 能把 UI 拉回正确状态。
- [x] 更新 `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` 的已验证项。
2026-05-16 Tree Realtime Live Cache 静态盘点与 payload 覆盖证据:
- 只读审查确认 React `AppLayoutShell` 同时接入 `useSidebarData``useSidebarTreeStream``usePreferredSidebarSnapshot` 负责 freshness 仲裁;Page Tree / File Tree 通过 preferred snapshot 消费 `kernelSidebarTree``kernelFileTreeProjection`
- 仍未统一的补偿链:`useSidebarData` 还保留 Convex query、HTTP `/api/sidebar` fallback 与手动 `refetch`mutation 后仍有 `refreshTree` / `sidebarQuery.refetch`Rust SSR shell 与 React hook 目前各自可建立 EventSourcepage subtree 仍从 Page Aggregate client state 派生,不应误标为已统一 live cache。
- Rust stream 盘点确认 workspace snapshot 已同时加载 `KernelProjectionKind::SidebarTree``KernelProjectionKind::FileTree`SSE payload 的 `data.dataset.kernel_sidebar_projection` / `data.dataset.kernel_file_tree_projection` 覆盖 page/file rowsubtree snapshot 仍只覆盖 `page_tree`,不含 file tree projection。
- 本轮新增 `stream_change_preserves_remove_asset_delta_fields`,确认 `tree.resource.delete``remove_asset` delta 字段保真;`structural_delta_requires_projection_snapshot` 已覆盖 `remove_asset` 需要 projection snapshot,避免资源删除类事件只靠局部 patch。
- 本轮强化 `scripts/task123-rust-web-tree-live-stream-consumer-smoke.js`:解析 SSE `snapshot` data,断言 `kind=snapshot``stream=workspace``projection=sidebar_tree``x-mnote-tree-stream-owner=rust-web`,并确认 workspace snapshot 中有 `kernel_sidebar_projection``kernel_file_tree_projection` 和临时页 `doc:<documentId>` file tree row。
2026-05-16 Tree Realtime Live Cache 验证命令:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web stream_change_preserves_remove_asset_delta_fields -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web structural_delta_requires_projection_snapshot -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::stream_support -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events -- --nocapture
cd wolai-frontend && pnpm test src/components/sidebar/use-preferred-sidebar-snapshot.test.tsx src/lib/tree-stream/use-sidebar-tree-stream.test.tsx
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
```
3000 smoke 证据:
- `tmp/tree-live-cache-smoke/20260516-task123/task123.stdout.json`,结果 `owner=rust-web``stream=/api/tree/events``snapshotProjection=sidebar_tree``fileTreeRows=59`
2026-05-16 双浏览器 no-refresh 验证与 purge 修复证据:
- 复用 `scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js` 做真实 3000 双浏览器验证:A 端通过真实 auth + `/api/tree/commands` 创建页面、归档、恢复、彻底删除与清空垃圾箱;B 端同时打开 File Tree 与 Trash,断言目标行无刷新出现 / 消失,且 `navigationEvents` 在初始打开后为空。
- 修复前该 smoke 在 `purge-visible-on-b` 前失败:`documents:purge` 收到 artifact-only 字段 `commandProtocol`Convex legacy validator 返回 `ArgumentValidationError: Object contains extra field commandProtocol`
- 修复点:`rust/crates/mnote-web/src/transport/convex.rs``tree.node.purge` / `documents.purge` 加入 `strip_tree_artifact_fields` 剥离范围,避免 compat mutation 接收 tree command audit 字段。
- 缺陷记录:`bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.md`
- 修复后 `task432` 通过,证据 `tmp/tree-live-cache-smoke/20260516-task432/result.json``ok=true``create-visible-on-b` 通过 `tree:resync` 出现在 B 端 File Tree`archive-visible-on-b` 通过 `tree:delta remove_document` 同步 File Tree 与 Trash`restore-visible-on-b` 通过 `tree:delta upsert_document` 同步恢复;`purge-visible-on-b``empty-trash-visible-on-b` 通过 `tree:resync` 拉回正确状态。
2026-05-16 purge 修复验证命令:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_purge_artifacts_for_legacy_mutation -- --nocapture
cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_strips_tree_archive_artifacts_for_legacy_mutation -- --nocapture
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
```
2026-05-16 双浏览器 rename live cache 修复与验证记录:
- 新增 regression smoke`scripts/task446-tree-rename-dual-browser-live-smoke.js`。脚本使用 A/B 独立 browser contextA 端通过正式 `/api/tree/commands` 创建并执行 `{ action: "rename", workspaceId, documentId, title }`,B 端分别保持目标文档页与 File Tree 页面不刷新,记录 `tree:snapshot/tree:delta/tree:resync``/api/tree/events` 请求、DOM 状态、导航事件与截图路径。
- RED 证据:修复前真实 3000 smoke 失败,B 端已收到 `tree:delta``op=upsert_document``liveApplied=delta``liveError=""`Sidebar / Page Tree 与 File Tree `{renamedTitle}.md` 已更新,但当前打开目标文档页的 `titleInputValue`、Breadcrumb 与 `document.title` 仍停留旧标题,导致 `waitForBDocumentRename` 超时。
- 根因:Rust SSR 文档 shell 的 `updateTitleEverywhere` 已更新 Page Tree / File Tree 行标题,但 title-only `upsert_document` 未同步当前文档页 chrome(页头标题输入框、Breadcrumb、`document.title`)。
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs``updateTitleEverywhere(documentId, title)` 现在同步当前文档 chrome,并保持 scoped tree row 更新;字符串合同单测新增当前页 title input 与 Breadcrumb selector 覆盖。
- 构建阻断补齐:当前工作区已有 `AppState.editor_actor` / `AppConfig.enable_editor_actor` 脏改动但缺少真实 `editor_actor.rs` 文件,导致 mnote-web 无法编译启动;本轮把 misplaced 的 `EditorRuntimeActor` 实现补到 `rust/crates/mnote-web/src/editor_actor.rs`,并补齐测试配置中的 `enable_editor_actor` 字段,以恢复 3000 验证入口。
- GREEN 证据:重新启动最新 `desktop:hot` 后运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task446-tree-rename-dual-browser-live-smoke.js` 通过。证据 `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task446-rename/b-document-after-rename.png``tmp/tree-live-cache-smoke/20260516-task446-rename/b-filetree-after-rename.png`
- 验证结果:B 文档页 `documentTitle/titleInputValue/breadcrumbTitle/sidebarTitle` 均为新裸标题;B File Tree `fileTreeTitle={renamedTitle}.md`;两端 `liveApplied=delta``liveError=""`rename 后 `navigationEvents=[]`,确认 B 端无刷新 / 无导航。
- 缺陷记录:`bugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md`
2026-05-16 双浏览器 move order live cache 修复与验证记录:
- 新增 regression smoke`scripts/task447-tree-move-order-dual-browser-live-smoke.js`。脚本使用 A/B 独立 browser contextA 端创建同父级 `A/B/C` 后执行正式 `/api/tree/commands``{ action: "move", documentId: C, parentId: root, sortOrder: 1 }`,B 端分别保持目标文档页与 File Tree 页面不刷新,记录 direct child order、tree events、导航事件与截图路径。
- RED 证据:合法 `sortOrder=1` 下修复前真实 3000 smoke 失败。B 端收到 `tree:delta move_document`payload 含 `sortOrder=1``liveApplied=delta``liveError=""`,但 Page Tree / File Tree DOM 顺序仍为 `A/B/C`,导致 `waitForExpectedOrder` 超时。
- 根因:Rust SSR 文档 shell 的 `applyMoveDocumentDelta(data)` 未读取 `sortOrder``moveDocumentRowForMode(mode, documentId, parentId)` 固定 `appendChild` 到目标父节点末尾;`tree:local-command` optimistic move 分支也未传 `body.sortOrder`
- 修复点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 增加 `sortOrderFromDelta``insertTreeNodeAtSortOrder`,让 live delta 与 local command move 按目标父节点直系 sibling index 插入;`rust/crates/mnote-web/src/routes/tree.rs` 补充 route/artifact 单测断言 `sortOrder` 保留在 result 与 `streamDelta`
- GREEN 证据:重启最新 `desktop:hot` 后运行 `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task447-tree-move-order-dual-browser-live-smoke.js` 通过。证据 `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task447-move-order/b-document-after-move.png``tmp/tree-live-cache-smoke/20260516-task447-move-order/b-filetree-after-move.png`
- 验证结果:B 文档页与 B File Tree 页面中的 Page Tree / File Tree direct child order 均从 `A/B/C` 变为 `A/C/B`;两端 `liveApplied=delta``liveError=""`move 后 `navigationEvents=[]`,确认无刷新 / 无导航。
- 缺陷记录:`bugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.md`
2026-05-16 SSE resync / reconnect recovery 验证记录:
- 新增 `scripts/task448-tree-resync-recovery-dual-browser-smoke.js`,通过 B 端 EventSource URL 注入 `pollMs=5000`,A 端在同一 poll 间隔内连续创建两个子页,强制 `/api/tree/events` 进入非单条 delta 的 `event: resync` 分支。
- `task448` 验证结果:B 文档页与 B File Tree 页面均收到 `tree:resync``liveApplied=resync``liveError=""`Page Tree / File Tree direct child order 从仅有初始子页恢复为包含新增 `b/c` 两个子页,`navigationEvents=[]`。证据 `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task448-resync/b-document-after-resync.png``tmp/tree-live-cache-smoke/20260516-task448-resync/b-filetree-after-resync.png`
- 新增 `scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js`,通过 Playwright `context.setOffline(true)` 模拟 B 端 SSE 断线,A 端离线期间连续创建两个子页,B 端恢复在线后等待 EventSource 恢复事件把 UI 拉回最新。
- `task449` 验证结果:B 文档页与 B File Tree 页面恢复后 `liveStatus=connected``liveApplied=resync``liveError=""`,新增 `b/c` 子页出现在 Page Tree 与 File Tree`navigationEvents=[]`。证据 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`,截图 `tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-document-after-reconnect.png``tmp/tree-live-cache-smoke/20260516-task449-reconnect/b-filetree-after-reconnect.png`
- 说明:当前 Rust SSR controller 的真实恢复合同是“断线期间错过多条变化后,恢复时通过 snapshot/resync 类完整投影拉回 UI”;本轮实测恢复事件为 `tree:resync`,不是浏览器刷新或导航。
验证命令建议:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web tree_events
node --check scripts/task447-tree-move-order-dual-browser-live-smoke.js
node --check scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node --check scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
node scripts/task123-rust-web-tree-live-stream-consumer-smoke.js
node scripts/task432-filetree-trash-page-dual-browser-no-refresh-smoke.js
node scripts/task446-tree-rename-dual-browser-live-smoke.js
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
```
### 3.5 定向 Bug Hunt
- [ ] 建立 bugs 分类:Page Aggregate 问题归 `bugs/05-editor-mainline/process/`
- [ ] Tree command / realtime / File Tree 问题归 `bugs/04-tree-domain/process/`
- [ ] AI tools 问题归 `bugs/07-ai/process/`
- [ ] 每个 bug 必须包含复现步骤、期望、实际、证据截图或 JSON、owner 判断。
- [ ] 先补最小 failing smoke,再修实现。
- [ ] 修复后移动到对应 `done/`,并记录验证命令。
重点 bug 方向:
- [ ] Page Aggregate 与页头标题不一致。
- [ ] Page Aggregate 与 File Tree `{title}.md` 不一致。
- [ ] AI 写入成功但 Page Aggregate 回读旧内容。
- [ ] stale revision 未阻断写入。
- [ ] tree stream 断线恢复后 UI 停在旧快照。
- [ ] 旧 compat / debug route 在 3000 首屏被误用。
### 3.6 2026-05-16 暂停交接进展摘要
本轮按用户要求暂停继续实现,只记录当前进展与恢复点;未继续推进 `3.2 Page Block AI Tooling` 的实现或 smoke。
已完成到可继续接力的阶段:
- `3.1 Page Aggregate 单一真源` 已完成本 checklist 中的当前验证项,并补齐真实 3000 smoke、Rust route 测试、前端单测和失败降级证据。关键证据包括 `tmp/page-aggregate-single-truth-smoke/20260516-182244/``tmp/page-aggregate-body-sync-smoke/mp87mgz7.json``tmp/page-aggregate-options-sync-smoke/mp87y6j3.json``tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json`
- `3.3 Tree Command Cutover` 已完成当前收口:3000 主链使用 `tree.*` preferred command`documents.*` 保留为 compat alias,并在 Rust execution plan / artifact 中标记 owner、preferredCommandName、compatCommandName、deprecatedAlias。
- `3.4 Tree Realtime Live Cache` 已完成本轮重点验证与修复:snapshot/delta/resync payload 覆盖、双浏览器新建 / 归档 / 恢复 / purge、rename 当前文档 chrome 同步、move sortOrder live 排序、SSE resync 与 reconnect recovery。
- 本轮新增或更新的 Tree Realtime 关键证据:
- `tmp/tree-live-cache-smoke/20260516-task432/result.json`
- `tmp/tree-live-cache-smoke/20260516-task446-rename/result.json`
- `tmp/tree-live-cache-smoke/20260516-task447-move-order/result.json`
- `tmp/tree-live-cache-smoke/20260516-task448-resync/result.json`
- `tmp/tree-live-cache-smoke/20260516-task449-reconnect/result.json`
- 本轮已归档的缺陷:
- `bugs/04-tree-domain/done/4-41-tree-node-purge-command-protocol-leaks-to-convex-v1.md`
- `bugs/04-tree-domain/done/4-42-tree-move-live-delta-sort-order-ignored-v1.md`
- `bugs/05-editor-mainline/done/5-12-page-options-refresh-runtime-attrs-v1.md`
- `bugs/05-editor-mainline/done/5-13-tree-rename-live-document-chrome-stale-v1.md`
当前暂停点:
- 已进入 `3.2 Page Block AI Tooling` 的只读审计阶段,但尚未勾选 3.2 的任何新 checkbox。
- 已确认现有代码中 `mnote.doc.fetch` 已支持 `scope=selection``format=page_xml/text/markdown/json``schema=mnote.page_ai_context.v1``allowedTargetBlockIds`、truncation/warnings/continuation 等基础字段;`mnote.block.fetch` 已支持 `format=page_xml/text`manifest 已包含 `readonly/destructive/idempotent/requiresApproval/approvalMode/runtimeOwner/writeOwner/selectionEffect` 等 annotations。
- 已确认 `scripts/task-page-block-ai-tools-smoke.js` 覆盖基础块工具闭环,但还没有专门覆盖 `scope=selection``page_xml/text`、manifest annotations、选区外写入阻断、review session / conflict / idempotency 的完整验收。
- 用户目标中提到的 `design/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md` 当前在 `design/07-ai/process/` 下不存在;实际可读历史参考位于 `design/old/07-ai/process/7-11-blocknote-tiptap-ai-reference-and-mnote-ai-tool-runtime-v1.md`,当前执行口径应继续以 `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md` 为准。
恢复时的下一步建议:
- 继续从 `3.2 Page Block AI Tooling` 开始,不要跳到定向 bug hunt。
- 优先新增一个聚焦 smoke,例如 `scripts/task-page-block-ai-context-format-smoke.js`,只验证基础底座,不扩新 AI 功能:
- manifest annotations 中 `mnote.doc.fetch` / `mnote.block.fetch` / `mnote.block.replace` / `mnote.page.save` 的 readonly、destructive、requiresApproval、selectionEffect、owner 字段。
- `mnote.doc.fetch scope=selection selectedBlockIds=[...] format=page_xml` 只返回选区块,返回 `schema=mnote.page_ai_context.v1``allowedTargetBlockIds``revisionRef`
- `mnote.doc.fetch scope=selection format=text` 不包含未选中块。
- `mnote.block.fetch format=page_xml/text` 返回稳定块内容和 `revisionRef`
- 选区冻结后写工具尝试修改 `allowedTargetBlockIds` 外块时返回 `mnote_block_target_out_of_scope`
- 配套验证建议从这些命令开始:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools -- --nocapture
node --check scripts/task-page-block-ai-context-format-smoke.js
MNOTE_UI_BASE_URL=http://127.0.0.1:3000 MNOTE_AUTH_BASE_URL=http://127.0.0.1:3000 node scripts/task-page-block-ai-context-format-smoke.js
```
交接注意事项:
- 当前工作区有大量既有未提交改动和文件迁移,继续执行时必须只碰当前小项相关文件,不要回滚、删除或清理无关脏文件。
- `git diff --check` 全仓可能仍会被既有删除文件阻断;验证本轮改动时可先用限定路径或 `git diff --no-index --check /dev/null <new-file>`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs` 中已有 Tree Realtime rename / move order 相关改动,同时可能混有先前 editor actor 相关改动;继续改动前必须重新读 diff,避免覆盖用户或前序 agent 的更改。
- `3000` 相关 smoke 必须继续保留 JSON / 截图证据路径,并在本 checklist 与对应 `design/07-ai``bugs/*` 文档同步记录。
---
## 4. 当前不优先做
- [ ] 不优先增加新的编辑器 UI 大功能。
- [ ] 不优先新增 AI Agent 工作流功能。
- [ ] 不优先扩展 AI 功能面;AI 只做基础上下文、工具合同、审阅会话、冲突与回滚。
- [ ] 不优先大规模替换 Convex。
- [ ] 不优先重写 `leptos-tiptap` 输入层。
- [ ] 不把 `mnote.page.save` 包装成精确块编辑长期方案。
- [ ] 不在 compat route 继续扩写长期业务语义。
- [ ] 不把 BlockNote / Tiptap AI runtime 作为 mnote runtime 依赖。
- [ ] 不把 `direct_block_edit_operations` / `local_rule` planner 当作长期 AI 编辑路径(当前为过渡实现,将被 `mnote.doc.markdown_edit` 替代)。
- [ ] 不新增 `mnote.block.*` 工具;`mnote.block.*` 保留为结构性辅助,不做块操作向新工具的扩张。
---
## 5. 完成定义
本 checklist 不能迁入 `done/`,直到:
- [ ] Page Aggregate 的标题 / 正文 / 页面设置 / page tree / AI fetch 回读形成同一组可验证真相。
- [ ] `7-10` 的 Page Block AI Tooling 剩余矩阵完成或明确拆出后续 process 文档。
- [ ] `7-11` 的 AI 基础 runtime 口径完成:context、format、manifest annotations、review session、状态机边界都有代码或明确后续 checklist。
- [ ] 7-14 v2 Phase A + B 全部完成(`mnote.doc.markdown_edit` + `mnote.doc.fetch` 增强 + `page_ai_workflow.rs` 收口);Phase C 设计已冻结。设计稿修正已由 7-14 v2 自行完成。
- [ ] `tree.*` command 面对主要页面生命周期动作成为唯一 preferred runtime 主链。
- [ ] tree realtime live cache 覆盖 Sidebar、Page Tree、File Tree、page subtree 的关键写后更新。
- [ ] 至少一轮定向 bug hunt 完成,所有 P0/P1 blocker 已归档到 `bugs/*/done/` 或明确保留为后续 process。
---
## 6. 给后续 /goal 的持续执行 Prompt
```text
/goal objective: 持续执行 /mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md,按 P0 -> P1 顺序推进 MNOTE 架构收口、AI 基础底座和定向 bug hunt。每轮开始先读取 /home/lix/.codex/memories/PROFILE.md 与 ACTIVE.md,再读取 AGENTS.md、ARCHITECTURE.md、design/01-05-current-priority-overview.md、本 checklist、design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md、design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md、design/07-ai/process/7-14-online-local-ai-markdown-editing-convergence-v1.md。必须保护用户已有未提交改动,不回滚、不覆盖、不删除无关文件。优先使用多个 subagent 并行做只读审查和浏览器验证,主线程只整合证据和做小范围实现。执行顺序固定为:1) Page Aggregate 单一真源;2) tree command cutover3) tree realtime live cache4) AI 基础工具与审阅底座,按 `7-14` v2 方向优先做 Phase A`mnote.doc.markdown_edit` + `mnote.doc.fetch` 增强),Phase B 退役 `local_rule` planner + `page_ai_workflow.rs` 收口;Phase C(流式 apply + suggest/review)仅设计冻结不实施。同时补 context/selection/page_xml/tool annotations,不扩新 AI 功能;5) 定向 bug hunt。每完成一个小项都要更新本 checklist 和对应 design/bugs 文档,补真实验证命令或证据路径。不要优先扩新功能,不要大改架构,不要把 compat/debug/fallback 当主链,不要把 BlockNote/Tiptap AI runtime 作为 mnote runtime 依赖。验证至少包含 git diff --check、相关 cargo test / smoke;如涉及 3000 页面,使用 mnote-tester 或浏览器自动化并保留截图/JSON 证据。
```
@@ -1,255 +0,0 @@
# 09 页面 AI 快速块编辑 Runtime Review
> 状态:`process`
>
> 日期:2026-05-16
>
> 关联主线:
> - `design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
> - `design/07-ai/process/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
> - `design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
> - `design/05-editor-mainline/done/5-13-page-block-identity-and-command-contract-v1.md`
---
## 1. 本轮结论
页面 AI 块写入的慢点不在 Rust 块工具本身,也不在 Convex 持久化本身。
本轮真实浏览器 smoke 显示:
- 旧路径:页面 AI 先调用 `/api/page-ai/block-edit-workflow`,快路径模型阶段耗时约 `7695ms`,随后因模型输出的块定位未命中 projection,`doc_apply_block_ops` 返回 `mnote_block_not_found`,前端继续 fallback 到 `/api/hermes/client/runs`,最终由 Hermes agent 调 `mnote.doc.apply_block_ops` 写入,总可见耗时约 `13246ms`
- 新路径:同一类简单中文块操作先由 mnote 本地 planner 解析为 `replace/insert_after/delete` operations,再直接调用 Rust `doc_apply_block_ops`。浏览器 smoke 回读可见耗时 `788ms`;后端日志显示 `operation_source=local_rule``model_ms=0``apply_ms=42``total_ms=42`,且未进入 `/api/hermes/client/runs`
因此,当前要继续做的是 Hermes 工具路由、上下文冻结、dry-run/review 与 Rust 写入校验基建,而不是继续让 Hermes agent 反复执行同一个端到端动作,也不是在 mnote 内新建第二套 AI runtime。
---
## 2. 慢的直接原因
### 2.1 通用 agent 编排链路太长
旧页面 AI 编辑链路实际包含:
```text
浏览器输入
-> Hermes session/profile/tools 初始化
-> 模型理解页面任务
-> 模型选择工具
-> 工具调用
-> 回读或继续推理
-> 最终回复
```
这适合复杂任务,但不适合“把 A 替换为 B / 在 A 后插入 C / 删除 D”这类明确块操作。
### 2.2 模型直接产 operations 不够可靠
本轮快路径第一版已经绕开 Hermes agent,但仍让 DeepSeek 直接根据 `page_xml/page_text` 生成 operations。失败点是:
```text
model_ms ~= 7695
operations = 3
apply -> mnote_block_not_found
fallback -> Hermes agent
```
说明模型输出了看似正确的操作数,但 block 定位信息没有命中当前 Page Aggregate projection。这个问题不能靠增加 smoke 次数解决,必须让 mnote runtime 在模型前后都掌握定位与校验。
### 2.3 失败后 fallback 放大了耗时
第一版前端逻辑在快路径失败时继续进入 `/api/hermes/client/runs`。这导致一次用户请求可能经历:
```text
快路径模型失败成本 + Hermes agent 成功成本
```
本轮已改为:只有后端明确返回 `page_ai_workflow_not_block_edit` 才允许 fallback;其他快路径错误直接在本轮 run 中失败展示,避免重复写入和重复等待。
---
## 3. 已完成的修正
### 3.1 页面 AI 快速块编辑 route
已新增并接入:
- `POST /api/page-ai/block-edit-workflow`
- 文件:`rust/crates/mnote-web/src/routes/page_ai_workflow.rs`
该 route 负责:
- 接收当前页面冻结后的 `mnote.page_ai_context.v1`
- 为明确中文编辑语句先走本地 operation planner。
- 无法本地解析时才调用小模型生成 operations。
- 最终统一走 `mnote.doc.apply_block_ops`,由 Rust 生成 canonical content 并保存。
### 3.2 简单块操作本地 planner
已支持这类明确表达:
```text
把「A」替换为「B」;
在「C」后插入「D」;
删除「E」。
```
输出直接是:
```json
[
{"op":"replace","matchText":"A","content":"B"},
{"op":"insert_after","matchText":"C","content":"D"},
{"op":"delete","matchText":"E"}
]
```
该层的目的不是做通用自然语言理解,而是把高频、低歧义、可确定的块编辑从模型路径剥离出来。
### 3.3 快路径可观测性
后端日志已记录:
- workflow started/completed
- operation source`local_rule``model`
- operations 数量
- model 耗时
- apply 耗时
- total 耗时
smoke 也已在失败时写出 evidence JSON,并记录 `/api/page-ai/*``/api/hermes/client/*` 请求/响应。
---
## 4. 验证证据
命令:
```bash
cargo test -p mnote-web page_ai_workflow -- --nocapture
cargo test -p mnote-web hermes_tools -- --nocapture
MNOTE_PAGE_AI_FAST_TIMEOUT_MS=60000 node scripts/task-page-ai-block-edit-workflow-smoke.js
```
结果:
- `page_ai_workflow`2 passed。
- `hermes_tools`22 passed。
- 浏览器 smoke:通过。
最新浏览器 evidence
```text
/mnt/Data1T/mnote/tmp/page-ai-block-edit-workflow-smoke/mp86uciu.json
```
关键值:
```json
{
"timingsMs": {
"pageAiWriteVisible": 788
},
"usedFastWorkflow": true,
"usedHermesRun": false,
"finalTexts": [
"第一段 mp86uciu",
"插入段 mp86uciu",
"第二段已修改 mp86uciu"
]
}
```
后端关键日志:
```text
operation_source="local_rule"
model_ms=0
apply_ms=42
total_ms=42
```
---
## 5. 剩余问题
### 5.1 不能把本地 planner 当成完整 AI runtime
当前本地 planner 只覆盖低歧义中文引号表达。它证明了正确的 runtime 方向,但不是最终答案。
需要继续建设:
- PageAIContextBuilder:冻结 selection/page context,减少模型输入。
- PageAIIntentParser:先判定是明确块操作、结构化改写、摘要问答、还是复杂编辑。
- PageAIOperationPlanner:把明确操作转成 `mnote.doc.apply_block_ops`,复杂操作才调用小模型。
- PageAIOperationValidator:模型输出后必须用 projection 校验 blockId/matchText/allowedTargetBlockIds。
- PageAIApplyController:统一处理 yolo 写入、失败展示、回读验证、审计日志。
### 5.2 模型输出 operations 仍需修
当请求不能被本地 planner 解析时,仍会调用模型。该路径必须补:
- 输出 schema 更严格,禁止模型臆造 blockId。
- 优先 `matchText` 或由服务端根据 text resolve block,而不是信任模型 blockId。
- 模型输出后做 dry-run validate,不命中时不要 fallback Hermes agent 重新跑。
- 把 validation error 反馈给用户或进入后续 clarify/retry,而不是隐式整页写。
### 5.3 Hermes agent 不应承担短路径编辑
Hermes 仍适合:
- 多步骤页面理解。
- 跨页面检索。
- 工具不可直接表达的复杂任务。
- 外部 skill/plugin 编排。
但对当前页面小段落块增删改,mnote 自己的 runtime 应该在浏览器/Rust route 内完成 `intent -> operations -> apply -> readback`
---
## 6. 下一步建议
优先级应从“继续 smoke”切到“补 AI runtime 基建”:
1. `PageAIIntentParser`
- 输入:用户 prompt、scope、selection、page context。
- 输出:`direct_block_ops | model_block_ops | question | unsupported`
- 目标:不让每条简单编辑都进入 Hermes agent。
2. `PageAIOperationPlanner`
- 扩展当前本地 planner。
- 支持常见中文/英文明确表达。
- 支持选区内“改成/润色/拆成列表”等可控操作。
3. `PageAIOperationValidator`
- 所有 operations apply 前先 resolve projection。
- 对 blockId、matchText、allowedTargetBlockIds、editable、children、revisionRef 做统一校验。
- 失败返回结构化错误,不 fallback 通用 agent。
4. `PageAIApplyController`
- 统一 yolo 模式下的写入、回读、状态展示和失败提示。
- 后续再接 preview/review session,而不是现在把 review 作为默认阻塞。
5. 模型路径瘦身
- 对必须调用小模型的任务,发送 `page_xml/text + allowed operations schema`
- 模型只负责生成候选 operations;最终定位、校验和写入仍由 Rust runtime 负责。
---
## 7. 当前判断
小段落编辑低于 10s 已经被证明可达,且当前 smoke 为 `788ms`
下一阶段的关键不是再证明“能写”,而是把这条快路径产品化:
```text
用户意图
-> mnote intent/parser
-> operation planner
-> projection validator
-> Rust apply
-> readback
-> UI 状态/审计
```
Hermes agent 应从默认编辑执行器退回到复杂任务编排器。
@@ -1,88 +0,0 @@
# 10 [process] 当前 mnote 项目 AI / Page Aggregate 定向 Review v1
> 更新时间:2026-05-17
>
> 执行状态:`process`
>
> 范围:当前主线中 Page Aggregate 单一真源、页面 AI 快速编辑、`mnote.doc.markdown_edit` 与 ACP / Hermes runtime 的源码级定向审查。
## 1. 本轮结论
本轮没有发现 Page Aggregate 读取主链重新回退到 Next compat builder 的证据;`wolai-frontend/src/lib/documents/page-aggregate-loader.ts` 仍只走 Rust `/api/page-aggregate/:documentId``/api/documents/page` compat 读链保持 `410` 退场口径。
新的风险集中在 `07-ai` 的 markdown 编辑收敛实现:
- `mnote.doc.markdown_edit` 已进入实际写入主线,并被 `/api/page-ai/block-edit-workflow` 调用。
- 该工具当前先在 markdown 字符串上顺序执行 search/replace,再把原始 operations 重新转换成 `mnote.doc.apply_block_ops`
- 转换层仍按“单块文本包含 search”查找目标块,无法忠实表达 markdown 层已经算出的最终结果。
因此,当前 AI 编辑主线不能只看 `operationsApplied > 0` 或 route 返回 `ok=true`。需要优先补充 `markdown_edit` 的中文、多操作、同块、多块、full_content 与失败原子性测试。
## 2. 发现的问题
### R-01 P0:中文归一化匹配使用 byte index 当 char index
关联缺陷:
- `bugs/07-ai/process/7-16-markdown-edit-normalized-search-byte-index-v1.md`
证据:
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:920-936`
`search_replace` 的 Level 2 先在 `norm_line` 上用 `find()` 得到 UTF-8 byte offset,却把这个 offset 传给 `line.char_indices().nth(start)` 当作字符序号使用。中文、多字节标点或中英混排时,替换范围会偏移,可能替错片段或生成错误内容。
影响:
- 页面 AI 对中文正文的“忽略空白 / 全半角差异”替换不可靠。
- 该问题位于 `mnote.doc.markdown_edit` 的基础 search/replace 层,会同时影响在线文档和本地 `.md` 文件。
### R-02 P0:同一块内多次 markdown_edit 会被后续 block op 覆盖
关联缺陷:
- `bugs/07-ai/process/7-17-markdown-edit-same-block-multi-op-overwrite-v1.md`
证据:
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:779-824`
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:854-878`
- `rust/crates/mnote-web/src/hermes_tools/doc.rs:976-1008`
`doc_markdown_edit` 已经在 `md` 变量中顺序应用了所有 search/replace,但 Convex 写入时没有使用这个最终 markdown。它重新读取原始 Page Aggregate blocks,并对每个 operation 用原始 block 文本生成一个 replace block op。
如果两个 operation 命中同一个 block,第二个 block op 的 `content` 仍从原始文本计算,会覆盖第一个 operation 的结果。也就是说,markdown 层算出的最终结果与实际落库结果可能不一致。
影响:
- AI 一次请求中常见的“把同一段里的 A 改成 B,同时把 C 改成 D”可能只保留最后一次修改。
- 前端快路径仍可能显示“已通过页面 markdown 编辑快路径完成写入”,但正文只部分生效。
## 3. 次级风险
- `doc_markdown_edit` 当前允许部分 operation 失败后继续写入已成功的子集,并返回 `ok=true`。如果这是有意设计,需要在 manifest / UI 中明确“非原子”;如果不是,应改为任一 operation 失败时不写入。
- `changedText` 通过 `operations.iter().take(applied)` 生成摘要;当前如果第一个 operation 失败、第二个成功,摘要会错误地展示第一个失败 operation。
- `/api/page-ai/block-edit-workflow` 的系统 prompt 已改成 search/replace,但仍强依赖模型精确复制 `pageText`;一旦模型输出跨块片段,Convex 写回层不能表达该编辑。
## 4. 建议下一步
1. 先给 `search_replace` 补中文归一化替换单测,再修正 byte / char offset 映射。
2.`doc_markdown_edit` 补同一 block 多 operation 的失败用例,明确应以最终 markdown 生成落库内容,或在转换层合并同块操作。
3. 明确 `markdown_edit` 的失败原子性:默认建议任一 operation 失败时不写入,除非请求显式允许 partial apply。
4.`/api/page-ai/block-edit-workflow` 的验收从“route 成功”提升为“Page Aggregate 回读与预期最终 markdown 一致”。
## 5. 本轮验证
静态检查范围:
```bash
rg -n "markdown_edit|apply_block_ops|block-edit-workflow|local_rule|page_ai_workflow|mnote\\.doc\\.fetch" rust wolai-frontend scripts design/07-ai design/10-review --glob '!rust/target/**' --glob '!node_modules/**'
rg -n "page-aggregate|pageAggregate|PageAggregate|blockDocument|projectionSource|documents/save|documents/options|/api/page-aggregate" rust/crates/mnote-web rust/crates/bridge-runtime wolai-frontend/src scripts --glob '!rust/target/**' --glob '!node_modules/**'
```
本轮是 review / bug hunt,没有修改 runtime 源码。后续修复应至少补:
```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web markdown_edit
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow
```
@@ -1,34 +0,0 @@
# 11 [process] 当前完整架构 Review v1
> 更新时间:2026-05-17
>
> 状态:`process`
>
> 参考总文档:[CURRENT_ARCHITECTURE.md](../../../CURRENT_ARCHITECTURE.md)
## 1. 结论
当前 mnote 的主线架构已经成形,但还没有完成单一真源收口。最关键的未闭合点有四个:
1. Tree realtime 的发布口径与前端消费口径不一致。
2. FileTree 资源行仍可能误走页面命令。
3. Page Aggregate 仍保留前端第二真相。
4. AI 写入链路在 `markdown_edit``apply_block_ops`、Hermes / ACP / Reasonix 之间存在合同漂移。
## 2. 关键判断
- Rust kernel 与 bridge-runtime 已经承担语义主导权。
- 前端仍保留若干本地派生状态,适合过渡,不适合作为长期真相层。
- `mnote.doc.markdown_edit` 已是主推路径,但工具级 contract 还没收口。
- `page_ai_workflow` 与 ACP / Reasonix 已接入主链,但运行时 owner 与写入字段不统一。
## 3. 需要继续跟踪的 bug
- Rust Web / realtime / ACP`bugs/03-rust-web/process/3-16``3-21`
- Tree domain`bugs/04-tree-domain/process/4-46`
- Editor mainline`bugs/05-editor-mainline/process/5-15``5-18`
- AI`bugs/07-ai/process/7-18``7-25`
## 4. 备注
本次审查的详细架构说明已统一写入根目录:[CURRENT_ARCHITECTURE.md](../../../CURRENT_ARCHITECTURE.md)。