# 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 功能；只补真实页面证据和安全边界。
