2026-04-30 16:18:54 +08:00
|
|
|
|
# 5-5-1 [done] Page Aggregate Contract v1
|
|
|
|
|
|
|
2026-05-14 05:52:08 +08:00
|
|
|
|
> 更新时间:2026-05-14
|
2026-04-30 16:18:54 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 关联文档:
|
|
|
|
|
|
> - `/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”,而是页面设置的持久化真相。
|
2026-05-14 05:52:08 +08:00
|
|
|
|
- 以下字段已经进入 `leptos-tiptap` island runtime payload:
|
2026-04-30 16:18:54 +08:00
|
|
|
|
- `wideLayout`
|
|
|
|
|
|
- `smallText`
|
|
|
|
|
|
- `layoutDensity`
|
|
|
|
|
|
- `showHeadingNumbers`
|
|
|
|
|
|
- `embedDefaultBlockId`
|
2026-05-14 05:52:08 +08:00
|
|
|
|
- 其中 `showHeadingNumbers` / `embedDefaultBlockId` 已进入 runtime payload,但深层语义仍是阶段性桥接:前者还不能描述成完整 heading 编号渲染闭环,后者还不能描述成完整嵌入默认落点闭环。
|
2026-04-30 16:18:54 +08:00
|
|
|
|
|
|
|
|
|
|
### 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 的附属部分。
|
|
|
|
|
|
|
2026-05-14 05:52:08 +08:00
|
|
|
|
### 2.7 `source` / provenance
|
|
|
|
|
|
|
|
|
|
|
|
作用:
|
|
|
|
|
|
|
|
|
|
|
|
> **标识这份 `PageAggregateProjection` 的真实构建来源。**
|
|
|
|
|
|
|
|
|
|
|
|
当前允许值:
|
|
|
|
|
|
|
|
|
|
|
|
- `KernelProjection`:底层已经由 Rust kernel 原生 page aggregate projection 产出。
|
|
|
|
|
|
- `CompatMetaContentJoin`:对外已经是 Rust `mnote.page_aggregate.v1` 契约,但底层仍由 Rust runtime adapter 消费 `documents:getMeta + documents:getContent` substrate 后聚合。
|
|
|
|
|
|
- `Fixture`:仅允许在测试或显式 dev fixture 场景出现。
|
|
|
|
|
|
|
|
|
|
|
|
说明:
|
|
|
|
|
|
|
|
|
|
|
|
- 当前 `/api/page-aggregate/:id` 主读链属于 `CompatMetaContentJoin`,不是完整 `KernelProjection`。
|
|
|
|
|
|
- `source_label()` / `x-mnote-page-aggregate-owner` 必须与实际来源一致:`KernelProjection -> rust-kernel`,`CompatMetaContentJoin -> compat-join`,`Fixture -> fixture`。
|
|
|
|
|
|
- 前端 loader 只消费 `mnote.page_aggregate.v1` 契约,不应因为来源是 `CompatMetaContentJoin` 而恢复 TS runtime builder 主链。
|
|
|
|
|
|
|
2026-04-30 16:18:54 +08:00
|
|
|
|
## 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 态
|
|
|
|
|
|
- 只影响单次交互的面板/菜单开关
|
2026-05-14 05:52:08 +08:00
|
|
|
|
- `showHeadingNumbers/embedDefaultBlockId` 的“runtime payload 已贯通,但深语义未完成”阶段性桥接逻辑
|
2026-04-30 16:18:54 +08:00
|
|
|
|
|
|
|
|
|
|
约束:
|
|
|
|
|
|
|
|
|
|
|
|
> **允许前端临时持有,不等于允许前端成为第二真相。**
|
|
|
|
|
|
|
|
|
|
|
|
## 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
|
2026-05-14 05:52:08 +08:00
|
|
|
|
- `showHeadingNumbers` 完整进入 heading 编号渲染语义
|
|
|
|
|
|
- `embedDefaultBlockId` 完整进入嵌入默认落点逻辑
|
2026-04-30 16:18:54 +08:00
|
|
|
|
|
|
|
|
|
|
## 7. 当前可对外口径
|
|
|
|
|
|
|
|
|
|
|
|
当前可以准确描述为:
|
|
|
|
|
|
|
2026-05-14 05:52:08 +08:00
|
|
|
|
> **文档页已开始消费统一 `Page Aggregate`,标题/页面设置/正文保存也开始按 `page_head / page_layout / page_body` 收口;当前 Rust route 主读链已成立,但 `/api/page-aggregate/:id` 底层仍是 `CompatMetaContentJoin`,树域投影统一与 AI 正式 page body 写入口也仍未完全完成。**
|