# 5-5-1 [done] Page Aggregate Contract v1 > 更新时间:2026-04-22 > > 关联文档: > - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md` > - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md` ## 1. 文档目的 这份文档只定义一件事: > **文档页在前端与 Rust 主链之间,当前最小可落地的 `Page Aggregate` 契约。** 它的作用不是取代长期 Rust kernel contract,而是先冻结当前主文档页的聚合边界,阻止: - 标题、正文、页面设置、page subtree 继续各自散长字段 - 页面壳继续在 props 链上拼第二份页面真相 - `leptos-tiptap` island 继续只接正文而不接页面布局语义 ## 2. 当前最小契约 当前最小 `Page Aggregate` 固定为五层: - `page_identity` - `page_head` - `page_layout` - `page_body` - `page_tree` 以及一组附属统计: - `page_stats` ### 2.1 `page_identity` 作用: > **标识这份页面聚合属于哪一个 page aggregate。** 当前最小字段: - `documentId` - `workspaceId` 说明: - 这层是聚合身份,不是 UI 展示字段。 - 后续如果 Rust kernel 侧补 `nodeId / aggregateId / projectionVersion`,应继续落在这一层,而不是散回页面 props。 ### 2.2 `page_head` 作用: > **承载页面头部的正式真相。** 当前最小字段: - `title` - `updatedAt` - `permissions.readOnly` - `permissions.disableDownload` - `permissions.disableCopy` 说明: - 页头标题属于 `page_head truth`,不是组件局部输入框自己的真相。 - 前端仍可保留一个“输入中”的临时编辑态,但提交后必须回到 `page_head.title`。 ### 2.3 `page_layout` 作用: > **承载页面布局与编辑器 runtime 相关设置。** 当前最小字段: - `pageOptions.wideLayout` - `pageOptions.smallText` - `pageOptions.layoutDensity` - `pageOptions.showHeadingNumbers` - `pageOptions.showToc` - `pageOptions.showStructure` - `pageOptions.protectEditing` - `pageOptions.showWordCount` - `pageOptions.collapseBacklinks` - `pageOptions.pageFont` - `pageOptions.hideChildPages` - `pageOptions.showBlockRefCount` - `pageOptions.embedDefaultBlockId` 说明: - 这层不是“页面设置面板 UI state”,而是页面设置的持久化真相。 - 其中只有一部分已经进入 `leptos-tiptap` island 运行时: - `wideLayout` - `smallText` - `layoutDensity` - 下面两项当前只完成字段贯通,不可描述成“正式支持”: - `showHeadingNumbers` - `embedDefaultBlockId` ### 2.4 `page_body` 作用: > **承载正文内容与保存元数据。** 当前最小字段: - `content` - `revision` - `conflictDetectionKey` 说明: - 正文相关命令都应收口到 `page_body`,而不是继续发明新的“页面内临时写回”路径。 - `/api/documents/save -> documents.save -> documents:updateContent` 是当前正式 page body 保存入口。 - 2026-04-28 更新:`page.body.saved` 与 `document.snapshot.saved` 已拆成两条正式 domain event。`page.body.saved` 只表达正文块集合保存,`document.snapshot.saved` 承载 snapshot version / content hash / updatedAt,避免正文事件 payload 继续夹带快照语义。 - 2026-04-28 更新:`tree.node.embed` 的 `pageReference` block 结构与插入位置由 Rust `pageAggregateEmbedPlan` 产出;3000 route 只提供源页面标题、目标内容、anchor block 等 substrate preflight,不再自行拼装页面聚合正文结构。 ### 2.5 `page_tree` 作用: > **承载当前页面对应的子树投影。** 当前最小字段: - `pageSubtree` 说明: - 当前 `pageSubtree` 仍是兼容过渡态 projection,不应夸大为最终 Rust page aggregate tree contract。 - 但在文档页入口和阅读态/TOC 消费层,它已经归属于 `page_tree`,不再继续散成独立 props。 ### 2.6 `page_stats` 作用: > **承载页面统计的附属投影。** 当前最小字段: - `wordCount` - `characterCount` - `blockCount` - `todoTotal` - `todoDone` 说明: - 统计不是页面身份,也不是布局或正文真相,但它是 page aggregate 的附属部分。 ## 3. 哪些字段是 aggregate truth,哪些只是 UI state ### 3.1 Aggregate Truth 下面这些字段属于 page aggregate truth: - `page_identity.*` - `page_head.*` - `page_layout.pageOptions.*` - `page_body.*` - `page_tree.pageSubtree` - `page_stats.*` ### 3.2 UI State 下面这些只能算前端临时态,不是正式真相: - 标题输入框当前未提交文本 - 当前是否处于编辑态/阅读态 - host fallback banner / runtime observability - inspector 当前 tab - history drawer / comments drawer / AI panel 的打开状态 - slash 菜单、浮动工具条、块手柄 hover 等 island 交互态 约束: > **UI state 允许存在,但不能再反客为主冒充 page aggregate truth。** ## 4. 哪些字段必须由 Rust 主导,哪些当前允许前端临时持有 ### 4.1 必须由 Rust 主导 - `page_head.title` 的持久化结果 - `page_body.content/revision/conflictDetectionKey` - `page_layout.pageOptions` 的持久化结果 - `page_tree.pageSubtree` 的正式投影来源 ### 4.2 当前允许前端临时持有 - 标题输入中的 debounce 缓冲态 - 正文 host 内的未保存 dirty 态 - 只影响单次交互的面板/菜单开关 - `showHeadingNumbers/embedDefaultBlockId` 的“字段已贯通,但深语义未完成”阶段性桥接逻辑 约束: > **允许前端临时持有,不等于允许前端成为第二真相。** ## 5. 当前命令收口口径 当前最小命令口径: - `page.head.updateTitle` - 当前前端别名映射到 `documents.title.update` - `page.layout.updateOptions` - 当前前端别名映射到 `documents.options.update` - `page.body.save` - 当前正式入口映射到 `documents.save` 说明: - 这轮只是把命名与调用口径收口到 page aggregate 子域。 - 底层 bridge / Convex / Rust mutation 名称暂不要求一次性全部重命名。 ## 6. 当前未完成边界 以下事项当前仍未完成,不应在 checklist 中超前打钩: - 树标题与页头标题已经证明消费同一份更新后的 projection - AI 写入口已经完全脱离 editor bridge,正式执行 page body command - `showHeadingNumbers` 真正进入 heading 渲染语义 - `embedDefaultBlockId` 真正进入嵌入默认落点逻辑 ## 7. 当前可对外口径 当前可以准确描述为: > **文档页已开始消费统一 `Page Aggregate`,标题/页面设置/正文保存也开始按 `page_head / page_layout / page_body` 收口;但树域投影统一与 AI 正式 page body 写入口仍未完全完成。**