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.
185 lines
11 KiB
Markdown
185 lines
11 KiB
Markdown
# [recycle] 7-16 [done] Page Block AI 真实页面 Smoke 后续矩阵 v1
|
||
|
||
> 创建时间:2026-05-18
|
||
>
|
||
> 当前状态:`DONE`
|
||
>
|
||
> 归档说明(2026-05-21):本文范围内可执行的真实页面 smoke 已完成;剩余 UI / Review Mode 属于 Phase C 冻结范围,解冻时应新建设计稿承接。
|
||
>
|
||
> 来源:
|
||
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
||
> - `/mnt/Data1T/mnote/design/07-ai/reference/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md`
|
||
> - `/mnt/Data1T/mnote/design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md`
|
||
|
||
---
|
||
|
||
## 1. 目的
|
||
|
||
`7-10` 已完成大量工具合同、Rust route 测试和部分真实 3000 smoke,但仍有一组“必须真实页面验证”的矩阵项未完成。
|
||
|
||
本文只承接这些剩余 smoke,不新增 AI 功能面,不改变当前主路径:
|
||
|
||
- local-first 普通 Markdown 编辑主路径仍是“授权文件引用 + agent 原生 patch/diff + watcher 同步”;本文只验证 mnote tools 的历史结构性辅助和兼容证据。
|
||
- 2026-05-22 之后,`mnote.doc.markdown_edit` 不再作为 local-first 默认、fallback 或 remote fallback;本文中的 smoke 只代表历史在线 / compat 回归证据。
|
||
- `mnote.block.*` 仍只作为结构性辅助。
|
||
- `mnote.page.save` 仍只作为页面级粗粒度兜底。
|
||
- Phase C review / streaming apply 仍冻结,当前只验证基础合同和安全边界。
|
||
|
||
---
|
||
|
||
## 2. 当前已具备的前置证据
|
||
|
||
- `mnote.doc.fetch scope=selection format=page_xml/text`、`mnote.block.fetch format=page_xml/text`、manifest annotations、`mnote.doc.apply_block_ops allowedTargetBlockIds` 越界阻断已有真实 3000 smoke:
|
||
- `tmp/page-block-ai-context-format-smoke/mp8ddr4n.json`
|
||
- `tmp/page-block-ai-context-format-smoke/mp8ddr4n-page.png`
|
||
- `mnote.block.replace` stale revision / stale blockRevisionRef / idempotency replay 已有真实 3000 smoke:
|
||
- `scripts/task-page-block-ai-conflict-idempotency-smoke.js`
|
||
- 证据记录在 `08` 的 Page Block AI Tooling 章节。
|
||
- `mnote.block.insert_after` 多块插入和 20 个边界已有 Rust route 回归:
|
||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_block_insert_after -- --nocapture`
|
||
- `mnote.block.move_after` 复杂块阻断矩阵已有隔离 route smoke:
|
||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_block_move_after_blocks_complex_and_nested_blocks -- --nocapture`
|
||
|
||
---
|
||
|
||
## 3. 后续真实页面 Smoke 矩阵
|
||
|
||
### 3.1 Page Aggregate Block Projection
|
||
|
||
- [x] 登录测试账号并创建综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
|
||
- [x] 写入段落、标题、todo、列表、mindmap/resource 占位。
|
||
- [x] 调 `/api/page-aggregate/:id` 并保存响应到 `tmp/hermes-tester/page-block-ai-tools-<run-id>/page-aggregate.json`。
|
||
- [x] 断言所有普通可编辑块有 `blockId` 和 `revisionRef`。
|
||
- [x] 页面刷新后 block ids 不变化。
|
||
- [x] projection 中 `blockCount` 与页面块数量一致;复杂块返回 `editable=false` 与 `unsupportedReason`。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- Page Aggregate:`tmp/hermes-tester/page-block-ai-tools-mpag4966/page-aggregate.json`
|
||
- 覆盖:Page Aggregate `blockProjectionVersion=1`、`projectionSource=documents.content`、`aggregateBlockCount=10`,与 `mnote.doc.fetch` 块顺序一致;刷新后 `afterRefreshBlockIds` 保持最终顺序;`table_1/mindmap_1/resource_1` 返回 `editable=false` 与 `unsupportedReason=复杂块暂不开放 AI 精确写入`。
|
||
|
||
### 3.2 `mnote.doc.fetch` / `mnote.doc.find`
|
||
|
||
- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
|
||
- [x] `mnote.doc.fetch scope=full detail=with_ids` 读取整页。
|
||
- [x] `mnote.doc.fetch scope=outline detail=with_ids` 只返回标题结构。
|
||
- [x] `mnote.doc.find query=<唯一前缀>` 定位目标段落。
|
||
- [x] 保存工具返回到 `tmp/hermes-tester/page-block-ai-tools-<run-id>/doc-fetch-find.json`。
|
||
- [x] 确认读取不依赖浏览器 DOM。
|
||
- [x] `doc.find` 返回的 block id 能被 `block.fetch` 读取。
|
||
- [x] 大页面默认分块或限制输出,不默认返回无限正文。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- 命令:`PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium 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-tools-smoke.js`
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 工具返回:`tmp/hermes-tester/page-block-ai-tools-mpag4966/doc-fetch-find.json`
|
||
- 覆盖:`doc.fetch.initial` 读取 `heading_1/p_1/p_2/p_3` 等 10 个块;`doc.fetch scope=outline` 只返回 `heading_1/heading_parent`;`doc.find query="第二段 <suffix>"` 定位 `p_2`;`maxBlocks=2` 返回 `truncated=true`。
|
||
- 静态边界:`mnote.doc.fetch` 从 Page Aggregate block projection 构建上下文,不读取浏览器 DOM;默认 `maxBlocks=120` 且 clamp 到 `1..=240`。
|
||
|
||
### 3.3 `mnote.block.fetch`
|
||
|
||
- [x] 从 `doc.find` 结果选择一个段落 block。
|
||
- [x] 调 `mnote.block.fetch includeChildren=true contextBefore=1 contextAfter=1`。
|
||
- [x] 断言 before/after 只来自同父级。
|
||
- [x] `revisionRef` 可被后续 dry-run 使用。
|
||
- [x] 复杂块不会伪装成完全可编辑。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 覆盖:`block.fetch` 读取 `p_2`,返回 `revisionRef=pageRev:1:block:p_2:...`,同父级 `before=["p_1"]`、`after=["p_3"]`;`table_1/mindmap_1/resource_1` 均返回 `editable=false` 与 `unsupportedReason=复杂块暂不开放 AI 精确写入`。
|
||
|
||
### 3.4 `mnote.doc.plan_update` / dry-run
|
||
|
||
- [x] `command=str_replace` 不再作为当前 `plan_update` 目标;普通正文 search/replace 已由 `mnote.doc.markdown_edit` 合同承接。
|
||
- [x] 对目标段落执行 `block_replace dryRun=true`。
|
||
- [x] 对目标段落执行 `block_insert_after dryRun=true`。
|
||
- [x] 对两个同父级普通块执行 `block_move_after dryRun=true`。
|
||
- [x] dry-run 前后分别读取 `/api/page-aggregate/:id` 与 `mnote.doc.fetch`,确认 revision / conflictDetectionKey / 正文文本不变。
|
||
- [x] dry-run 不写 Convex。
|
||
- [x] plan 能解释 before/after。
|
||
- [x] 不支持场景返回 `blocked=true`。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 覆盖:`plan_update.dry_run.replaceDiff.before/after`、`insertDiff.after/content`、不支持 `move_after p_3 -> p_3` 返回 `blocked=true`、dry-run 后 `revisionAfterDryRun=1` 且正文未变化。
|
||
|
||
### 3.5 `mnote.block.replace`
|
||
|
||
- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
|
||
- [x] 写入两段不同文本。
|
||
- [x] `doc.find` 找到第二段 block id。
|
||
- [x] `block.replace dryRun=true` 查看 plan。
|
||
- [x] `block.replace dryRun=false` 替换第二段。
|
||
- [x] 页面截图和工具回读证明只替换第二段。
|
||
- [x] `/api/page-aggregate/:id` 回读证明持久化。
|
||
- [x] `mnote.doc.fetch` 再次证明 AI 可读回。
|
||
- [x] 相邻块不变化。
|
||
- [x] 目标 block id 保持不变。
|
||
- [x] 旧 revision 写入返回 conflict。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 截图:`tmp/page-block-ai-tools-smoke/mpag4966-page.png`
|
||
- 覆盖:`block.replace.changedBlocks[0]={blockId:"p_2",op:"replace"}`,`targetBlockStillExists=true`,`adjacentTexts=["第一段 mpag4966","第三段 mpag4966"]`,`mnote.doc.fetch` 与 Page Aggregate 均回读到 `第二段已替换 mpag4966`,Page Aggregate `aggregateRevision=2`、`aggregateConflictDetectionKey=tree_1779062903017_3:2`。
|
||
- 冲突 / 幂等补充 JSON:`tmp/page-block-ai-conflict-idempotency-smoke/mpafrevs.json`,截图:`tmp/page-block-ai-conflict-idempotency-smoke/mpafrevs-page.png`。
|
||
- 覆盖:首次 `block.replace` 写入 `p_2` 后,重复 `idempotencyKey=idem_conflict_replace_mpafrevs` replay 同一 `commandId=page_body_save_req_1779062303997_525` 且 `revisionAfterFirst=2`、`revisionAfterReplay=2`;旧 `revision/conflictDetectionKey` 与旧 `blockRevisionRef` 均返回 HTTP `400` / `mnote_tool_conflict`,最终文本保持 `第二段首次替换 mpafrevs`。
|
||
|
||
### 3.6 `mnote.block.insert_after`
|
||
|
||
- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`。
|
||
- [x] 在第一段后插入 todo。
|
||
- [x] 截图证明位置准确。
|
||
- [x] 刷新页面后再次确认。
|
||
- [x] `mnote.block.fetch` 能读取新 block id。
|
||
- [x] 插入位置准确。
|
||
- [x] 新 block id 不是 AI 自造未校验 id。
|
||
- [x] 重复同一 `idempotencyKey` 不重复插入。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- 命令:`PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium 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-tools-smoke.js`
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 截图:`tmp/page-block-ai-tools-smoke/mpag4966-page.png`
|
||
- 说明:该 smoke 使用 `TEST-AI-BLOCK-TOOLS-*` 综合页面,不是单独 `TEST-AI-BLOCK-INSERT-*` 页面;已覆盖 todo 插入、插入后顺序、block.fetch 回读、刷新后可见和重复 idempotencyKey replay。
|
||
|
||
### 3.7 `mnote.block.move_after`
|
||
|
||
- [x] 使用综合页面 `TEST-AI-BLOCK-TOOLS-<timestamp>`,包含三段普通段落。
|
||
- [x] dry-run 移动第三段到第一段后。
|
||
- [x] 正式执行同父级移动。
|
||
- [x] 截图证明顺序为第一段、第三段、第二段。
|
||
- [x] `mnote.doc.fetch` 回读顺序一致。
|
||
- [x] 对标题带子块、列表项、表格、mindmap 执行 move dry-run,必须返回 blocked。
|
||
- [x] moving block id 保持不变。
|
||
- [x] 同父级顺序正确。
|
||
- [x] 复杂块不被误移动。
|
||
|
||
证据(2026-05-18):
|
||
|
||
- JSON:`tmp/page-block-ai-tools-smoke/mpag4966.json`
|
||
- 截图:`tmp/page-block-ai-tools-smoke/mpag4966-page.png`
|
||
- 覆盖:`heading_parent/list_item_1/table_1/mindmap_1/resource_1` 的 `plan_update block_move_after dryRun=true` 与正式 `mnote.block.move_after dryRun=false` 均返回 `blocked=true` / `block_move_after_blocked`,阻断后 `revisionAfterBlocked=1` 且正文不变化;普通 `p_3 -> p_1` 正式移动后顺序为 `heading_1,p_1,p_3,ai_block_...,p_2,...`,`movingBlockStillExists=true`。
|
||
|
||
### 3.8 UI / Review Mode 边界
|
||
|
||
- [ ] Hermes tool event UI 展示 `plan/diff/warnings/risk`。
|
||
- [ ] 本地 preview/suggestion 与持久 comment/tracked-change 分开。
|
||
- [ ] 协作可见审阅必须另走正式 comment/history/tracked-change 设计。
|
||
- [ ] 用户能看到 AI 将改哪个 block。
|
||
- [ ] `blocked=true` 的工具调用不会出现写入按钮。
|
||
- [ ] preview 不写入正式 comment/history。
|
||
|
||
---
|
||
|
||
## 4. 验证原则
|
||
|
||
- 所有真实页面 smoke 必须使用测试账号,优先从 `http://localhost:3000/auth` 快速登录。
|
||
- 证据必须保留 JSON 和截图,默认放入 `tmp/hermes-tester/<run-id>/` 或专项 `tmp/page-block-ai-*` 目录。
|
||
- 发现失败时按 owner 写入 `bugs/<category>/process/`,不要只在本矩阵中留描述。
|
||
- 不用此矩阵扩展新 AI 功能;只补真实页面证据和安全边界。
|