# 5-41 Page Aggregate compat 瘦身剩余缺口

## 状态

- 状态：done
- Owner：05-editor-mainline / Page Aggregate / browser document conversion
- 发现时间：2026-06-01

## 现象

Page Aggregate 已经完成 local-first 主链和多个单一真源修复。2026-06-01 复核后，`body.content`、compat join 和浏览器 legacy content 降级被明确收口为 v1 兼容面：它们仍是 active runtime 的兼容读写字段，但不再作为 local-first 浏览器主事实源。

## 证据

- `rust/crates/core-protocol/src/page_aggregate.rs` 中 `PageBody.content` 仍是必填字段。
- `rust/crates/mnote-web/src/routes/local_folder_source.rs` 的 local-first aggregate 仍会填充 `body.content`。
- `rust/crates/bridge-runtime/src/lib.rs` 的 cloud/compat aggregate 路径仍支持 `CompatMetaContentJoin`。
- `rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js` 已在 2026-06-01 调整为 `blockDocument/editorDocument` 优先，`local_markdown.content` 与 `compat.legacy_content` 只作为缺少 block document 时的降级路径；`scripts/task522-page-aggregate-compat-fallback-contract.js` 覆盖了这几个 source 分支。
- `rust/crates/mnote-web/src/routes/documents.rs` 的保存兼容面仍同时携带 `editorDocument`、`content`、`tiptapDocument`。
- `rust/crates/mnote-web/src/page_aggregate/builder.rs` 的默认 source 已在 2026-06-01 改为 `KernelProjection`，显式 `.source(PageAggregateSource::CompatMetaContentJoin)` 仍保留 compat 路径。

## 影响

- “Page Aggregate 单一真源”仍保留 v1 兼容数据面：前端和后端都需要继续维护 legacy content，但只作为镜像 / fallback。
- local-first 浏览器转换层已优先消费 block document；后端协议和 compat 保存面继续保留 legacy content，保证 cloud/compat fallback 和旧保存面不被破坏。
- 后续若要删除或改为可选 `body.content`，必须进入 `mnote.page_aggregate.v2` 或等价协议升级 checklist，不能在 v1 中直接删除。

## 下一步建议

- `PageBody.content` 在 `mnote.page_aggregate.v1` 中继续保持必填兼容字段，用于 legacy/cloud/compat fallback、旧保存面和降级回读。
- `body.blockDocument` / `editorDocument` 是 local-first 浏览器主消费面；`body.content` 不再作为 active editor 初始化的优先事实源。
- `CompatMetaContentJoin` 继续只作为显式 cloud/compat substrate 边界保留；`PageAggregateBuilder::new()` 默认 source 保持 `KernelProjection`。
- `body.content` 改为可选或删除的退出条件：必须先有 `mnote.page_aggregate.v2` 协议或迁移 checklist，证明 cloud/compat 保存面、外部 agent、历史 fixture 和降级读取全部不再依赖该字段。

## 验收

- [x] local-first 浏览器转换优先消费 `body.blockDocument`，`pageBodyTiptapDocumentSource(...)` 在 `blockDocument` 存在时返回 `page_aggregate.block_document`。
- [x] `body.content` 仅在缺少 `blockDocument` / `editorDocument` 时作为 local markdown legacy fallback。
- [x] Builder 默认 source 不再是 `CompatMetaContentJoin`。
- [x] `compat.legacy_content` 降级路径有明确 source 标记和测试覆盖。
- [x] 设计稿明确 `body.content` 的长期状态：v1 保留为必填兼容镜像 / fallback；v2 或后续协议升级再评估可选或删除。

## 2026-06-01 验证

- `node --check rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js`
- `node --check scripts/task522-page-aggregate-compat-fallback-contract.js`
- `node scripts/task522-page-aggregate-compat-fallback-contract.js`
- `cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --test-threads=1`
- `cargo test -p mnote-web page_aggregate::builder::tests -- --test-threads=1`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js`

## 2026-06-01 收口决策

- `PageBody.content`：v1 保留必填，语义降级为兼容镜像 / fallback，不作为 local-first active editor 主事实源。
- `body.blockDocument` / `editorDocument`：local-first 浏览器转换优先消费的 canonical block view。
- `compat.legacy_content`：仅当缺少 `blockDocument` / `editorDocument` 时可被浏览器转换层使用，并通过 `task522-page-aggregate-compat-fallback-contract.js` 固化 source 标记。
- `CompatMetaContentJoin`：显式 compat substrate 边界，保留到 cloud/legacy 保存面完成协议升级，不再是 builder 默认 source。
