# 4-12 [done] task-003 page tree final renderer 执行清单 v1

> 口径修正（2026-04-28）：本文件的 `done` 状态只表示 page tree 的 Rust initial DOM、hydration patch 与 state-family 阶段完成；不表示 `4-11` 所定义的 final Rust DOM shell 已完成。默认主路径仍依赖 `TreeShellIframeHost` / `iframe_srcdoc` 时，不得引用本文宣称 page tree final renderer 已最终收口。

## 目标

让 `page tree` 默认运行时从 inline compat JS 整树重绘路径继续收口到 Rust family renderer contract：

- 首屏 DOM 继续由 `data-rust-page-renderer="initial_v1"` 输出。
- 焦点、active 高亮与键盘移动不再触发 `renderTree()` 整树重绘，只 patch 已 hydrate 的 Rust DOM row 状态。
- 展开/折叠不再把 Rust 初始 DOM 替换成 compat DOM；在已 hydrate Rust DOM 内 patch `aria-expanded`、toggle 文案与子树 fragment。
- `mnote-web /tree` debug shell 与 3000 inline host 保持同一可测试合同，避免默认路径和 debug 路径继续漂移。

## 执行项

- [x] Rust `page_renderer` 初始 HTML 为有子节点的 page row 输出 `tree-node-toggle`，使 Rust DOM 自身具备展开/折叠交互锚点。
- [x] 3000 inline `buildInlinePageTreeHtml` 对齐 Rust 初始 HTML，避免 `TreeShellIframeHost` 继续生成缺少 toggle 的第二份 page DOM 合同。
- [x] 3000 inline runtime 增加 hydrated page DOM patch：
  - [x] `focusNode` 在 `usedRustInitialRenderer` 下只更新 `data-active / data-focused / tabIndex`。
  - [x] `toggleExpand` 在 `usedRustInitialRenderer` 下只 patch 当前节点 `aria-expanded`、toggle 文案与子树显示/插入。
  - [x] 新插入的 page 子树 row 复用同一 `bindPageRowEvents`，保留 open/create/rename/menu 事件。
- [x] `mnote-web /tree` debug runtime 对齐同一 page DOM patch 合同，保留 direct debug 验证边界。
- [x] 测试覆盖：
  - [x] `cargo test -p mnote-web page_renderer`
  - [x] `pnpm vitest run src/components/sidebar/tree-shell-iframe-host.test.tsx src/components/sidebar/tree-shell-surface.test.tsx`
  - [x] `node scripts/task112-tree-rust-family-regression-smoke.js`
  - [x] 本地 Convex 函数已通过 `npx convex dev --once --tail-logs disable --env-file ../.env.all --run ping:ping` 同步，避免 `documents.move` 运行旧 validator 拒绝 `normalizedMove`。

## 非目标

- 本清单不把 file tree 与 picker 一并切 final renderer；它们对应 `task-004`、`task-005`。
- 本清单不恢复 `3104` 默认入口；`/tree` 仍只作为显式 debug/internal 验证边界。
