2026-05-16 22:03:14 +08:00
|
|
|
|
# 5-13 [done] 页面块身份与命令合同 v1
|
2026-05-16 12:34:48 +08:00
|
|
|
|
|
|
|
|
|
|
> 更新时间:2026-05-16
|
|
|
|
|
|
>
|
2026-05-16 22:03:14 +08:00
|
|
|
|
> 当前状态:`DONE`。
|
2026-05-16 12:34:48 +08:00
|
|
|
|
>
|
|
|
|
|
|
> 关联文档:
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
|
|
|
|
|
> - `/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`
|
2026-05-16 22:03:14 +08:00
|
|
|
|
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-1-page-block-storage-projection-alignment-v1.md`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
|
2026-05-16 12:34:48 +08:00
|
|
|
|
> - `/mnt/Data1T/mnote/design/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
|
|
|
|
|
|
>
|
|
|
|
|
|
> 代码依据:
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/model.rs`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/command.rs`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/tiptap.rs`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/rust/crates/bridge-runtime/src/lib.rs`
|
|
|
|
|
|
>
|
|
|
|
|
|
> 外部参考:
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/agents/tools/index.mdx`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/api-reference/read-the-document.mdx`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/api-reference/execute-tool.mdx`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-main/packages/extension-unique-id/src/unique-id.ts`
|
|
|
|
|
|
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-main/packages/server-ai-toolkit/src/hash-extension/server-ai-toolkit-hash-extension.ts`
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 结论
|
|
|
|
|
|
|
|
|
|
|
|
页面块 AI 工具不能建立在“整页 `content` 字符串/数组 patch”之上。它必须先有一个稳定的页面块合同:
|
|
|
|
|
|
|
|
|
|
|
|
> **Rust `EditorBlockDocument` 是页面正文的 canonical block view;`EditorBlock.block_id` 是 mnote 页面内块身份真相;Tiptap JSON、ProseMirror position、`UniqueID`、AI Toolkit `_hash` 都只能是 runtime adapter 或定位辅助。**
|
|
|
|
|
|
|
|
|
|
|
|
第一阶段允许底层仍回写整份 `documents.content`,但命令语义必须先落到 Rust block command:
|
|
|
|
|
|
|
|
|
|
|
|
- `editor.block.replace`
|
|
|
|
|
|
- `editor.block.insert_after`
|
|
|
|
|
|
- `editor.block.delete`
|
|
|
|
|
|
- `editor.block.move_after`
|
|
|
|
|
|
|
|
|
|
|
|
也就是说,持久化可以暂时是 snapshot save,工具合同不能退化成 snapshot patch。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 当前真实状态
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 已经存在的基础
|
|
|
|
|
|
|
|
|
|
|
|
`core-protocol` 已经有正式块模型:
|
|
|
|
|
|
|
|
|
|
|
|
- `EditorBlockDocument.document_id`
|
|
|
|
|
|
- `EditorBlockDocument.root_block_ids`
|
|
|
|
|
|
- `EditorBlockDocument.blocks`
|
|
|
|
|
|
- `EditorBlock.block_id`
|
|
|
|
|
|
- `EditorBlock.block_type`
|
|
|
|
|
|
- `EditorBlock.props`
|
|
|
|
|
|
- `EditorBlock.content_nodes`
|
|
|
|
|
|
- `EditorBlock.child_block_ids`
|
|
|
|
|
|
|
|
|
|
|
|
`core-protocol` 也已经有编辑命令形态:
|
|
|
|
|
|
|
|
|
|
|
|
- `EditorReplaceBlock`
|
|
|
|
|
|
- `EditorInsertBlockAfter`
|
|
|
|
|
|
- `EditorDeleteBlock`
|
|
|
|
|
|
- `EditorMoveBlock`
|
|
|
|
|
|
- `EditorSplitBlock`
|
|
|
|
|
|
- `EditorMergeWithPrevious`
|
|
|
|
|
|
- `EditorIndentBlock`
|
|
|
|
|
|
- `EditorOutdentBlock`
|
|
|
|
|
|
|
|
|
|
|
|
`core-protocol/src/editor/tiptap.rs` 已经提供 `EditorBlockDocumentTiptapBridge`,能在 `EditorBlockDocument` 与 Tiptap JSON 之间转换,并保留段落、标题、列表、任务、引用、代码、分隔线、图片、表格、目录、mindmap 等类型的阶段性映射。
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 仍然缺失的合同
|
|
|
|
|
|
|
|
|
|
|
|
现有模型还不足以直接开放成熟 AI 块工具,缺口是:
|
|
|
|
|
|
|
|
|
|
|
|
- `PageAggregate.page_body` 只定义了 `content/revision/conflictDetectionKey`,没有定义 canonical block projection。
|
|
|
|
|
|
- `EditorBlockDocument` 还没有在 Page Aggregate 输出中成为稳定字段。
|
|
|
|
|
|
- `EditorBlock` 没有显式 `parentBlockId/order/path/revisionRef/editable` 投影。
|
|
|
|
|
|
- `doc_insert_blocks/doc_replace_range` 仍是工具雏形,能够生成部分 editor commands,但仍主要围绕 legacy block array 回写。
|
|
|
|
|
|
- `EditorMoveBlock` 有协议形态,但 `mnote.block.move_after` 的父子、排序、复杂块规则尚未冻结。
|
|
|
|
|
|
|
|
|
|
|
|
因此必须先补本合同,再实现 AI 块读写移动。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 块身份规则
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 正式身份
|
|
|
|
|
|
|
|
|
|
|
|
正式块身份字段固定为:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"blockId": "block_abc",
|
|
|
|
|
|
"documentId": "doc_1",
|
|
|
|
|
|
"workspaceId": "ws_1"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
规则:
|
|
|
|
|
|
|
|
|
|
|
|
- `blockId` 在同一 `documentId` 内唯一。
|
|
|
|
|
|
- AI、前端、Tiptap runtime 都不得自行把临时 DOM id 当作 `blockId`。
|
|
|
|
|
|
- 新块 id 必须由 Rust runtime 分配,或者由 Rust runtime 校验后接受。
|
|
|
|
|
|
- 保存后再次读取,原块 `blockId` 必须保持不变。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 复制规则
|
|
|
|
|
|
|
|
|
|
|
|
复制块时:
|
|
|
|
|
|
|
|
|
|
|
|
- 原块 `blockId` 不复用。
|
|
|
|
|
|
- 新块获得新 `blockId`。
|
|
|
|
|
|
- 若复制子树,子块全部获得新 `blockId`。
|
|
|
|
|
|
- 块引用、页面引用、mindmap/resource 等外部关联需要单独定义重定向或保留规则,未定义前不得向 AI 开放复制子树工具。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 移动规则
|
|
|
|
|
|
|
|
|
|
|
|
移动块时:
|
|
|
|
|
|
|
|
|
|
|
|
- 被移动块 `blockId` 保持不变。
|
|
|
|
|
|
- 同父级移动只改变 sibling order。
|
|
|
|
|
|
- 跨父级移动同时改变 `parentBlockId` 与 order。
|
|
|
|
|
|
- 第一阶段只开放同父级叶子块移动。
|
|
|
|
|
|
- 标题带子块、列表项、表格、mindmap、resource、page reference、block reference 必须在单独规则中定义后再开放。
|
|
|
|
|
|
|
|
|
|
|
|
### 3.4 Tiptap `UniqueID` 与 `_hash`
|
|
|
|
|
|
|
|
|
|
|
|
Tiptap 参考结论:
|
|
|
|
|
|
|
|
|
|
|
|
- `UniqueID` 可以给 Tiptap 节点补 id,并处理协作、粘贴、拖拽场景下的去重。
|
|
|
|
|
|
- Server AI Toolkit 的 `_hash` 是 AI 编辑辅助属性,可帮助定位变化。
|
|
|
|
|
|
- 这两者都不能替代 mnote 的 `EditorBlock.block_id`。
|
|
|
|
|
|
|
|
|
|
|
|
mnote 规则:
|
|
|
|
|
|
|
|
|
|
|
|
- Tiptap node attrs 中可以携带 `block_id` / `blockId`,但来源必须是 Rust canonical block projection。
|
|
|
|
|
|
- `_hash` 可以进入 `revisionRef` 的辅助计算,但不能成为业务主键。
|
|
|
|
|
|
- 如果 Tiptap 文档缺少 block id,导入链必须进入“补 id + 记录 warning”的兼容路径,而不是让 AI 直接编辑无身份块。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Canonical Block Projection
|
|
|
|
|
|
|
|
|
|
|
|
`PageAggregate.page_body` 需要新增稳定块投影,建议字段为:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"content": [],
|
|
|
|
|
|
"revision": 12,
|
|
|
|
|
|
"conflictDetectionKey": "doc_1:12",
|
|
|
|
|
|
"blockDocument": {
|
|
|
|
|
|
"documentId": "doc_1",
|
|
|
|
|
|
"rootBlockIds": ["b1", "b2"],
|
|
|
|
|
|
"blocks": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"blockId": "b1",
|
|
|
|
|
|
"type": "paragraph",
|
|
|
|
|
|
"text": "正文",
|
|
|
|
|
|
"attrs": {},
|
|
|
|
|
|
"contentNodes": [],
|
|
|
|
|
|
"children": [],
|
|
|
|
|
|
"parentBlockId": null,
|
|
|
|
|
|
"order": "00010000",
|
|
|
|
|
|
"path": [0],
|
|
|
|
|
|
"depth": 0,
|
|
|
|
|
|
"revisionRef": "pageRev:12:block:b1:hash:abc",
|
|
|
|
|
|
"editable": true,
|
|
|
|
|
|
"unsupportedReason": null
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
字段说明:
|
|
|
|
|
|
|
|
|
|
|
|
- `blockDocument` 是 Page Aggregate 给 editor/AI/read view 的 canonical block view。
|
|
|
|
|
|
- `content` 保留当前兼容 snapshot,不作为 AI 精确编辑的唯一输入。
|
|
|
|
|
|
- `text` 是 AI 读取用摘要,不替代 `contentNodes`。
|
|
|
|
|
|
- `attrs` 是稳定属性投影,不暴露 Tiptap 私有字段;确需保留 Tiptap snapshot 时放入内部 adapter,不进入 AI 默认读取。
|
|
|
|
|
|
- `order` 是同父级排序投影,第一阶段可由数组位置计算,后续可升级为持久 order key。
|
|
|
|
|
|
- `path` 是读取定位辅助,不作为写入主键。
|
|
|
|
|
|
- `revisionRef` 是块级冲突辅助,必须包含 page revision 和块内容/结构摘要。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. EditorBlockDocument 与 Tiptap JSON 边界
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 正式边界
|
|
|
|
|
|
|
|
|
|
|
|
正式边界固定为:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
AI / Hermes tool
|
|
|
|
|
|
-> mnote.doc.* / mnote.block.*
|
|
|
|
|
|
-> Rust canonical tool adapter
|
|
|
|
|
|
-> EditorBlockDocument + EditorCommand
|
|
|
|
|
|
-> Tiptap bridge / legacy content bridge / Convex save adapter
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
不得变成:
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
AI
|
|
|
|
|
|
-> Tiptap JSON 私有 shape
|
|
|
|
|
|
-> Convex documents.content
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 Tiptap JSON 的职责
|
|
|
|
|
|
|
|
|
|
|
|
Tiptap JSON 可以用于:
|
|
|
|
|
|
|
|
|
|
|
|
- 浏览器 editor runtime。
|
|
|
|
|
|
- ProseMirror command 执行。
|
|
|
|
|
|
- 兼容导入/导出。
|
|
|
|
|
|
- 测试 bridge round trip。
|
|
|
|
|
|
|
|
|
|
|
|
Tiptap JSON 不可以用于:
|
|
|
|
|
|
|
|
|
|
|
|
- AI 长期工具入参合同。
|
|
|
|
|
|
- mnote 块身份事实源。
|
|
|
|
|
|
- Convex 长期块结构事实源。
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 PageMarkdown / PageXML 的职责
|
|
|
|
|
|
|
|
|
|
|
|
AI 对外格式固定为:
|
|
|
|
|
|
|
|
|
|
|
|
- `PageMarkdown`:适合文本、标题、列表、简单块。
|
|
|
|
|
|
- `PageXML`:适合携带 `block-id`、块属性、结构化块。
|
|
|
|
|
|
- `json`:只能是 mnote canonical block projection,不是裸 Tiptap JSON。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 块命令合同
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 `editor.block.replace`
|
|
|
|
|
|
|
|
|
|
|
|
语义:
|
|
|
|
|
|
|
|
|
|
|
|
> 替换目标块的类型、属性或内容,不改变目标块 `blockId`、父级、同级顺序。
|
|
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"documentId": "doc_1",
|
|
|
|
|
|
"blockId": "b1",
|
|
|
|
|
|
"blockType": "paragraph",
|
|
|
|
|
|
"props": {},
|
|
|
|
|
|
"contentNodes": [
|
|
|
|
|
|
{ "type": "text", "text": "替换后的正文", "marks": [] }
|
|
|
|
|
|
],
|
|
|
|
|
|
"expectedRevision": 12,
|
|
|
|
|
|
"blockRevisionRef": "pageRev:12:block:b1:hash:abc"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
阻断:
|
|
|
|
|
|
|
|
|
|
|
|
- `blockId` 不存在。
|
|
|
|
|
|
- `expectedRevision` 不匹配。
|
|
|
|
|
|
- `blockRevisionRef` 明显不匹配。
|
|
|
|
|
|
- 目标块 `editable=false`。
|
|
|
|
|
|
- 目标块是未定义 replace 规则的复杂块。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 `editor.block.insert_after`
|
|
|
|
|
|
|
|
|
|
|
|
语义:
|
|
|
|
|
|
|
|
|
|
|
|
> 在 anchor 块同父级后插入一个或多个新块。
|
|
|
|
|
|
|
|
|
|
|
|
输入:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"documentId": "doc_1",
|
|
|
|
|
|
"anchorBlockId": "b1",
|
|
|
|
|
|
"blocks": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"type": "todo",
|
|
|
|
|
|
"props": { "checked": false },
|
|
|
|
|
|
"contentNodes": [
|
|
|
|
|
|
{ "type": "text", "text": "新增待办", "marks": [] }
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"expectedRevision": 12,
|
|
|
|
|
|
"anchorRevisionRef": "pageRev:12:block:b1:hash:abc"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
规则:
|
|
|
|
|
|
|
|
|
|
|
|
- 新块 id 由 runtime 生成。
|
|
|
|
|
|
- 新块默认进入 anchor 同父级。
|
|
|
|
|
|
- 插入多个块时保持输入顺序。
|
|
|
|
|
|
- 第一阶段不允许把复杂子树作为单次 insert payload。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.3 `editor.block.delete`
|
|
|
|
|
|
|
|
|
|
|
|
语义:
|
|
|
|
|
|
|
|
|
|
|
|
> 删除目标块;是否保留、提升或级联删除子块由命令参数明确指定。
|
|
|
|
|
|
|
|
|
|
|
|
第一阶段不直接开放给 AI 正式写入,只允许 dry-run。
|
|
|
|
|
|
|
|
|
|
|
|
必须先冻结:
|
|
|
|
|
|
|
|
|
|
|
|
- `preserveChildren=true` 时子块提升到哪里。
|
|
|
|
|
|
- `preserveChildren=false` 时删除范围如何展示和审计。
|
|
|
|
|
|
- block reference / comment thread / resource attachment 如何处理。
|
|
|
|
|
|
|
|
|
|
|
|
### 6.4 `editor.block.move_after`
|
|
|
|
|
|
|
|
|
|
|
|
语义:
|
|
|
|
|
|
|
|
|
|
|
|
> 将目标块移动到 anchor 块之后。
|
|
|
|
|
|
|
|
|
|
|
|
第一阶段输入:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"documentId": "doc_1",
|
|
|
|
|
|
"blockId": "b3",
|
|
|
|
|
|
"anchorBlockId": "b1",
|
|
|
|
|
|
"expectedRevision": 12,
|
|
|
|
|
|
"blockRevisionRef": "pageRev:12:block:b3:hash:aaa",
|
|
|
|
|
|
"anchorRevisionRef": "pageRev:12:block:b1:hash:bbb"
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
第一阶段只允许:
|
|
|
|
|
|
|
|
|
|
|
|
- `blockId` 与 `anchorBlockId` 同父级。
|
|
|
|
|
|
- 目标块是叶子块。
|
|
|
|
|
|
- 类型为 `paragraph`、普通 `heading`、普通 `todo`。
|
|
|
|
|
|
|
|
|
|
|
|
第一阶段阻断:
|
|
|
|
|
|
|
|
|
|
|
|
- 跨父级移动。
|
|
|
|
|
|
- 移动到自己的子树内。
|
|
|
|
|
|
- 标题带子块整体移动。
|
|
|
|
|
|
- 列表项层级移动。
|
|
|
|
|
|
- 表格、mindmap、resource、page reference、block reference。
|
|
|
|
|
|
- 跨页面移动。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. 读工具对命令合同的要求
|
|
|
|
|
|
|
|
|
|
|
|
写工具开放前,读工具必须先能返回:
|
|
|
|
|
|
|
|
|
|
|
|
- `blockId`
|
|
|
|
|
|
- `type`
|
|
|
|
|
|
- `text`
|
|
|
|
|
|
- `attrs`
|
|
|
|
|
|
- `children`
|
|
|
|
|
|
- `parentBlockId`
|
|
|
|
|
|
- `order`
|
|
|
|
|
|
- `path`
|
|
|
|
|
|
- `depth`
|
|
|
|
|
|
- `revisionRef`
|
|
|
|
|
|
- `editable`
|
|
|
|
|
|
- `unsupportedReason`
|
|
|
|
|
|
|
|
|
|
|
|
`mnote.block.fetch` 必须能返回同父级前后文,供 AI 在 `replace/insert_after/move_after` 前做二次确认。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. 与 Tiptap AI Toolkit 的关系
|
|
|
|
|
|
|
|
|
|
|
|
Tiptap AI Toolkit 对 mnote 的启发是:
|
|
|
|
|
|
|
|
|
|
|
|
- `tiptapRead` 证明 AI 读工具需要高效、可定位的文档读格式。
|
|
|
|
|
|
- `tiptapEdit` 证明 AI 写工具应该是 operations,而不是整页替换。
|
|
|
|
|
|
- `tiptapReadSelection` 证明 selection/range 需要冻结,不能依赖用户思考期间不断变化的浏览器选择。
|
|
|
|
|
|
- `executeTool/streamTool` 证明工具执行面需要返回 `docChanged`、错误、review/preview 结果。
|
|
|
|
|
|
- schema awareness 证明 AI 需要明确 editor context,而不是猜块类型。
|
|
|
|
|
|
|
|
|
|
|
|
mnote 不直接采用 Tiptap operations 作为外部合同,原因是:
|
|
|
|
|
|
|
|
|
|
|
|
- 公开文档没有完整 operations schema。
|
|
|
|
|
|
- Tiptap operations 面向 ProseMirror 文档层,不覆盖 mnote 的 Rust kernel、Page Aggregate、Convex revision、Hermes audit。
|
|
|
|
|
|
- mnote 需要稳定到跨编辑器、跨存储演进的块合同。
|
|
|
|
|
|
|
|
|
|
|
|
因此 Tiptap 作为参考模型,不作为 mnote 外部工具合同。
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. 完成定义
|
|
|
|
|
|
|
2026-05-16 22:03:14 +08:00
|
|
|
|
本文已归档为 `DONE`,done 边界是“页面块身份、命令和 Tiptap boundary 合同已经冻结为当前代码可执行合同”;后续完整执行矩阵继续由 `7-10` 承接。
|
2026-05-16 12:34:48 +08:00
|
|
|
|
|
2026-05-16 22:03:14 +08:00
|
|
|
|
- [x] Page Aggregate 输出 canonical `blockDocument` 或等价稳定 block projection。
|
|
|
|
|
|
- [x] `EditorBlockDocument` 与 Tiptap JSON bridge 已覆盖当前主用块类型;更复杂类型继续在 `7-10` 验收矩阵补齐。
|
|
|
|
|
|
- [x] `editor.block.replace` 能生成 canonical content 并回写当前 `documents.content`。
|
|
|
|
|
|
- [x] `editor.block.insert_after` 能生成新 block id、正确插入并回读。
|
|
|
|
|
|
- [x] `editor.block.move_after` 已通过同父级叶子块 dry-run / 受限移动链路。
|
|
|
|
|
|
- [x] AI 工具不再把裸 Tiptap JSON 或 Convex `documents.content` 私有结构当长期合同。
|