# 5-15 [done][bug] PageAggregateClientState 仍在前端生成第二份 page tree 真相 v1

> 发现时间：2026-05-17
>
> 状态：`[done]`
>
> 关联主线：`05-editor-mainline`

## 1. 问题定义

文档页已经从 Rust `page-aggregate` 读取快照，但前端 `PageAggregateClientState` reducer 在标题或正文变化后仍会本地构建 page subtree projection，并把它作为 AI context 等链路的输入。

## 2. 证据

- [document-content.tsx](/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx:144) 初始化 page aggregate client reducer。
- [page-aggregate-client-state.ts](/mnt/Data1T/mnote/wolai-frontend/src/components/editor/page-aggregate-client-state.ts:179) 到 [page-aggregate-client-state.ts](/mnt/Data1T/mnote/wolai-frontend/src/components/editor/page-aggregate-client-state.ts:197) 本地构建 subtree projection。
- [page-subtree.ts](/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-subtree.ts:246) 仍保留 TS 本地 page tree builder。

## 3. 影响

- 文档页、AI context、Rust Page Aggregate 可能看到不同 page tree。
- 前端本地派生逻辑会继续承载长期语义，违反 Page Aggregate 单一真源收口。
- 后续 Debug 时很难判断页面结构到底来自 Rust 还是 TS builder。

## 4. 建议修复

- 明确 reducer 只能作为 UI 草稿态，不得生成长期 page tree projection。
- AI context 应优先读取 Rust Page Aggregate 的稳定 projection。
- 增加测试：本地编辑后 AI context 与 Rust 回读 projection 必须一致。

## 5. 修复

- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:227) `currentPageAiContextSnapshot` 不再在编辑器 DOM 与服务端 aggregate 不一致时构造 `local` page subtree；AI context 的 subtree 只来自 Rust Page Aggregate 的 `aggregate.tree.pageSubtree`。
- [layout.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/ssr/pages/layout.rs:7448) 增加字符串合同测试，禁止 `pageSubtreeSource: 'local'` 与 `buildPageAiLocalSubtree(localBlocks)` 回到 AI context 主链。

## 6. 验证

- `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_context_uses_page_aggregate_subtree_as_single_truth -- --nocapture`
  - 结果：1 passed
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web ssr::pages::layout::tests -- --nocapture`
  - 结果：12 passed
