Files
mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md
T
lix-2026 5f97800489 chore: align local-first control plane and editor fixes
- wire SQLite control-plane access/session paths into Rust web local-folder routes

- preserve local Markdown attachment semantics across upload, reload, and secondary-pane resource tabs

- refresh design governance docs, Reasonix task templates, and bug records

- retire root .mcp.json local MCP config
2026-05-23 23:38:42 +08:00

7.8 KiB
Raw Blame History

5-5-1 [done] Page Aggregate Contract v1

更新时间:2026-05-14

关联文档:

  • /mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md
  • /mnt/Data1T/mnote/design/05-editor-mainline/done/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 runtime payload
    • wideLayout
    • smallText
    • layoutDensity
    • showHeadingNumbers
    • embedDefaultBlockId
  • 其中 showHeadingNumbers / embedDefaultBlockId 已进入 runtime payload,但深层语义仍是阶段性桥接:前者还不能描述成完整 heading 编号渲染闭环,后者还不能描述成完整嵌入默认落点闭环。

2.4 page_body

作用:

承载正文内容与保存元数据。

当前最小字段:

  • content
  • revision
  • conflictDetectionKey

说明:

  • 正文相关命令都应收口到 page_body,而不是继续发明新的“页面内临时写回”路径。
  • /api/documents/save -> documents.save -> documents:updateContent 是当前正式 page body 保存入口。
  • 2026-04-28 更新:page.body.saveddocument.snapshot.saved 已拆成两条正式 domain event。page.body.saved 只表达正文块集合保存,document.snapshot.saved 承载 snapshot version / content hash / updatedAt,避免正文事件 payload 继续夹带快照语义。
  • 2026-04-28 更新:tree.node.embedpageReference 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 的附属部分。

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-kernelCompatMetaContentJoin -> compat-joinFixture -> fixture
  • 前端 loader 只消费 mnote.page_aggregate.v1 契约,不应因为来源是 CompatMetaContentJoin 而恢复 TS runtime builder 主链。

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 的“runtime payload 已贯通,但深语义未完成”阶段性桥接逻辑

约束:

允许前端临时持有,不等于允许前端成为第二真相。

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 收口;当前 Rust route 主读链已成立,但 /api/page-aggregate/:id 底层仍是 CompatMetaContentJoin,树域投影统一与 AI 正式 page body 写入口也仍未完全完成。