feat: EditorRuntimeActor - 三层缓存/delta/事件架构
Phase A — EditorRuntimeActor 内存缓存层 - 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init - block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径 - editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启) - bridge-runtime 三个核心函数公开化 - rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞) Phase B — 编辑器增量 delta channel - BlockDelta/DeltaOperation 类型 + actor.build_block_delta() - leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch - DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent - 工具响应含 blockDelta 字段供前端消费 Phase C — 事件 stream delta - broadcast channel 在 AppState/actor/SSE 三层贯通 - tree_events SSE 端点发 block.delta 事件 - 旧客户端降级兼容 环境修复 - rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出) - run-convex-deploy.js(封装 Convex function 部署到本地后端 3210) ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
This commit is contained in:
-402
@@ -1,402 +0,0 @@
|
||||
# 5-13 [process] 页面块身份与命令合同 v1
|
||||
|
||||
> 更新时间:2026-05-16
|
||||
>
|
||||
> 当前状态:`PROCESS`。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/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`
|
||||
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-1-page-block-storage-projection-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/07-ai/process/7-9-page-block-ai-tooling-roadmap-v1.md`
|
||||
> - `/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. 完成定义
|
||||
|
||||
本设计不能标记 `DONE`,直到:
|
||||
|
||||
- [ ] Page Aggregate 输出 canonical `blockDocument` 或等价稳定 block projection。
|
||||
- [ ] `EditorBlockDocument` 与 Tiptap JSON bridge 通过 paragraph/heading/list/todo/code/table/image/mindmap 的 round trip 测试。
|
||||
- [ ] `editor.block.replace` 能生成 canonical content 并回写当前 `documents.content`。
|
||||
- [ ] `editor.block.insert_after` 能生成新 block id、正确插入并回读。
|
||||
- [ ] `editor.block.move_after` 至少通过同父级叶子块 dry-run。
|
||||
- [ ] AI 工具不再把裸 Tiptap JSON 或 Convex `documents.content` 私有结构当长期合同。
|
||||
@@ -1,502 +0,0 @@
|
||||
# 5-4 [process] `leptos-tiptap` 主编辑器纠偏与落地方案 v1
|
||||
|
||||
> 更新时间:2026-04-29
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-editor-baseline-reset-v2.md`
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-1-main-editor-cutover-entry-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-3-tiptap-leptos-rust-migration-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档只解决一个已经开始偏掉的问题:
|
||||
|
||||
> **当 `mnote` 已经采用 `Tiptap + leptos-tiptap` 作为默认主编辑器输入 runtime 时,如何避免把剩余过渡适配层误当成“Rust + Leptos 主线已经完全成立”。**
|
||||
|
||||
当前需要纠偏的不是:
|
||||
|
||||
- 是否继续使用 `Tiptap`
|
||||
- 是否继续使用 `leptos-tiptap`
|
||||
- 是否继续让 Rust 掌握 editor truth
|
||||
|
||||
当前真正需要纠偏的是:
|
||||
|
||||
- **哪些改动已经把“过渡接法”抬成了主链**
|
||||
- **哪些底层工作虽然还不完整,但应当保留**
|
||||
- **后续应该按什么顺序推进,才能真正体现 Rust + Leptos 的长期优势**
|
||||
|
||||
---
|
||||
|
||||
## 2. 先给结论
|
||||
|
||||
结论固定为五句:
|
||||
|
||||
### 2.1 方向本身没有错
|
||||
|
||||
`Tiptap + leptos-tiptap + Rust truth` 仍然是当前最现实的主路线。
|
||||
|
||||
原因很简单:
|
||||
|
||||
- `Tiptap` 仍然是当前最成熟的浏览器富文本/块编辑 runtime 体系
|
||||
- `leptos-tiptap` 已经提供了可在 Leptos 中稳定挂载、读写内容、触发命令、订阅变更的正式接缝
|
||||
- `mnote` 当前没有更成熟的 Rust-native 输入层,可以在短期内替代 `Tiptap` 级的 selection / IME / history / slash / toolbar / handle 行为
|
||||
|
||||
因此:
|
||||
|
||||
> **当前不应放弃 `leptos-tiptap`,也不应回到“重新造一个 Rust 版 Tiptap”作为当前主交付。**
|
||||
|
||||
### 2.2 当前偏差也已经很明确
|
||||
|
||||
当前需要继续纠偏的主要偏差不是“换成了 `leptos-tiptap`”,而是:
|
||||
|
||||
- 默认文档页主链虽然已经切到页面内 `leptos-tiptap` island
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/editor-host.tsx`
|
||||
- 但仍保留 TS converter、fallback host、兼容保存链与部分 host/runtime 过渡层
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/tiptap-content-converter.ts`
|
||||
- `/mnt/Data1T/mnote/recycle/wolai-frontend/src/components/editor/blocknote-editor.tsx`
|
||||
|
||||
这条线的问题不是“代码不能运行”,而是:
|
||||
|
||||
> **它虽然已经是页面内主编辑器,但剩余 fallback / adapter / 兼容保存链还没有完全退到过渡边界。**
|
||||
|
||||
### 2.3 当前最该回退的不是 Tiptap,而是默认主链切换方式
|
||||
|
||||
当前最该继续收口的是:
|
||||
|
||||
- **把 TS converter 继续降级为过渡适配层**
|
||||
- **把 fallback host / 兼容保存链继续收回到非主路径**
|
||||
- **让默认 `leptos-tiptap` 主链更多直接消费 Rust-first projection / command 边界**
|
||||
|
||||
当前不该回退的是:
|
||||
|
||||
- `core-protocol` 中新增的 `EditorBlockDocument <-> Tiptap` 桥接模型
|
||||
- `leptos-tiptap spike` 中已经完成的实际交互 surface
|
||||
- 官方模板行为模型对 `slash / floating toolbar / drag handle / turn into` 的约束
|
||||
|
||||
### 2.4 真正要体现 Rust + Leptos 优势,必须把主目标改写成下面这句话
|
||||
|
||||
> **让 `Tiptap/leptos-tiptap` 只负责 live editing surface;让 Rust 持有块真相、保存合同、命令合同、AI 合同;让 Leptos 成为正式页面内编辑 island,而不是额外 `iframe runtime`。**
|
||||
|
||||
### 2.5 AI 入口不能继续挂在浏览器壳边上
|
||||
|
||||
如果长期目标是:
|
||||
|
||||
- AI 直接进入主编辑区编写
|
||||
- 人类只负责审核和辅助编辑
|
||||
|
||||
那么 AI 不能继续以:
|
||||
|
||||
- 直接拼 HTML
|
||||
- 直接拼 Tiptap JSON
|
||||
- 直接驱动前端壳层临时桥接逻辑
|
||||
|
||||
作为正式路径。
|
||||
|
||||
长期正确路径应固定为:
|
||||
|
||||
> **AI / CLI / 自动化先操作 Rust `EditorCommand` 与 `EditorBlockDocument`,编辑器 surface 只消费 Rust truth 的变更投影。**
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前问题的具体判断
|
||||
|
||||
## 3.1 当前实现为什么没有体现 Rust + Leptos 的优势
|
||||
|
||||
从当前未提交改动看,问题集中在四个点。
|
||||
|
||||
### 3.1.1 文档页已经提前默认切流
|
||||
|
||||
当前:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
|
||||
|
||||
已把编辑态默认入口从 `BlockNoteEditor` 切到了:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/editor-host.tsx`
|
||||
|
||||
这意味着当前不是“有一个新 host 在旁路验证”,而是“主链已经被切了”。
|
||||
|
||||
### 3.1.2 host 仍然是 iframe runtime,不是 Leptos 主编辑 island
|
||||
|
||||
当前:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-editor-host.tsx`
|
||||
|
||||
的真实结构是:
|
||||
|
||||
`DocumentContent`
|
||||
-> `EditorHost`
|
||||
-> `iframe`
|
||||
-> `http://localhost:8123/?embedded=1`
|
||||
-> `postMessage`
|
||||
-> host 接收 `ready/change/save-request`
|
||||
-> 前端转换后调 `/api/documents/save`
|
||||
|
||||
这条链的问题在于:
|
||||
|
||||
- 编辑器实例不在真实页面树内
|
||||
- 父子之间不是同一运行时对象图
|
||||
- `undo / redo / selection / current block id / reference insert` 仍是最小占位
|
||||
- AI 面板与主编辑器之间无法共享一份真正稳定的 live editor handle
|
||||
|
||||
也就是说:
|
||||
|
||||
> **现在更像“Next 把一个外部 editor runtime 嵌了进来”,而不是“Leptos editor 成为页面内正式 island”。**
|
||||
|
||||
### 3.1.3 当前保存边界仍然由前端壳层掌控
|
||||
|
||||
当前:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-editor-host.tsx`
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/tiptap-content-converter.ts`
|
||||
|
||||
承担了下面这些职责:
|
||||
|
||||
- Tiptap doc -> blocks
|
||||
- blocks -> Tiptap doc
|
||||
- save payload 组装
|
||||
- stats 计算
|
||||
- revision / conflict key 维护
|
||||
|
||||
这会造成一个长期问题:
|
||||
|
||||
> **Rust 还没有真正拥有唯一正式的 editor truth 边界,前端 host 仍然在定义一套过渡期格式语义。**
|
||||
|
||||
### 3.1.4 当前已经开始把“过渡默认值”写死
|
||||
|
||||
当前:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/runtime-config.ts`
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/editor-host-config.ts`
|
||||
|
||||
已经出现了把:
|
||||
|
||||
- `documentEditorHost`
|
||||
- `EditorHostKind = "leptos_tiptap"`
|
||||
|
||||
提前写成固定默认值的趋势。
|
||||
|
||||
这会直接带来一个误判:
|
||||
|
||||
> **看起来像“主编辑器切流已完成”,实际上只是“默认把一条过渡桥接线抬上来了”。**
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前哪些需要回退,哪些应保留
|
||||
|
||||
下面只针对“主编辑器纠偏”相关改动判断,不涉及与本任务无关的其它脏改动。
|
||||
|
||||
## 4.1 需要立即回退或降级的部分
|
||||
|
||||
| 范围 | 文件 | 动作 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| 主编辑器默认切流 | `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx` | 回退到“默认仍由现有正式可写链路承载”,`EditorHost` 不再直接替代默认主编辑器 | 当前接法仍是过渡 host,不是正式 Rust + Leptos 主链 |
|
||||
| host 默认绑定 | `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/editor-host.tsx` | 降级为实验入口,不再代表默认 editor host | 当前只返回 `LeptosTiptapEditorHost`,会掩盖“还没完成正式切流”的事实 |
|
||||
| iframe runtime 主链承载 | `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-editor-host.tsx` | 降级为 `debug/prototype bridge`,不再作为默认主文档页保存链路 | 它是跨 iframe bridge,不是长期正式 island 形态 |
|
||||
| 编辑页强行自动进入编辑 | `/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx` | 回退 `edit=1 -> autoEnterEdit` 作为默认主链推进手段 | 当前真正问题不是“默认直接编辑”,而是“切入的是哪条编辑器链” |
|
||||
| 运行时默认值写死 | `/mnt/Data1T/mnote/wolai-frontend/src/lib/runtime-config.ts` | 回退 `documentEditorHost` 这类提前写死的默认事实 | 当前 host 形态仍在纠偏,不能先把默认值冻结成产品事实 |
|
||||
| host 配置空壳 | `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/editor-host-config.ts` | 降级为后续使用的配置草稿,不作为当前切流依据 | 现在只有单值返回,没有真实 host 策略价值 |
|
||||
| TS converter 生产化倾向 | `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/tiptap-content-converter.ts` | 从“正式保存边界”降级为“过渡适配器 / 测试夹具” | 不能让前端继续持有 editor truth 的唯一转换权 |
|
||||
|
||||
### 4.1.1 对“回退”的精确定义
|
||||
|
||||
这里的“回退”不是删除所有相关代码,而是:
|
||||
|
||||
1. **回退它们在主文档页中的默认地位**
|
||||
2. **回退它们在正式保存链路中的事实地位**
|
||||
3. **回退它们对最终架构口径的误导**
|
||||
|
||||
也就是说:
|
||||
|
||||
- 代码可以暂时保留
|
||||
- 但必须从“默认主链”退回“实验/过渡/对照用途”
|
||||
|
||||
## 4.2 应明确保留的部分
|
||||
|
||||
| 范围 | 文件 | 动作 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| Rust Tiptap 协议桥 | `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/tiptap.rs` | 保留并继续演进 | 这是 Rust 收口 editor truth 的必要桥层,不应因当前 host 方案错误而被否掉 |
|
||||
| Rust Tiptap 导出入口 | `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/mod.rs` | 保留 | 说明 Tiptap 映射已进入协议层,而不是停留在前端 |
|
||||
| Rust 桥接测试 | `/mnt/Data1T/mnote/rust/crates/core-protocol/tests/editor_tiptap_bridge.rs` | 保留并扩充 | 这是正式语义边界回归的基础 |
|
||||
| 8123 spike | `/mnt/Data1T/mnote/rust/spikes/leptos-tiptap-spike/src/main.rs` | 保留,但只作为 surface 行为样本和 embed 验证页 | 这里保存了最接近未来主编辑器的交互行为模型 |
|
||||
| 官方模板行为参考 | `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-template/**` | 保留 | 它是体验 benchmark,不是迁移代码对象 |
|
||||
| `leptos-tiptap` 参考库 | `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/leptos-tiptap/**` | 保留 | 这是正式 Leptos 接入 runtime 的能力边界,不应再被降回 fallback |
|
||||
|
||||
### 4.2.1 对“保留”的精确定义
|
||||
|
||||
这些内容保留,不代表它们已经是最终主链。
|
||||
|
||||
它们保留的原因是:
|
||||
|
||||
- 已经证明方向是对的
|
||||
- 已经沉淀了有价值的协议和行为模型
|
||||
- 应该被吸收到正式主线,而不是被误伤回滚
|
||||
|
||||
---
|
||||
|
||||
## 5. 纠偏后的目标形态
|
||||
|
||||
纠偏后应固定下面四层边界。
|
||||
|
||||
## 5.1 Surface 层:`Tiptap + leptos-tiptap`
|
||||
|
||||
这一层只负责:
|
||||
|
||||
- 人类输入
|
||||
- 光标与 selection
|
||||
- slash / toolbar / handle / block menu
|
||||
- 文本样式、块切换、拖拽等浏览器 runtime 行为
|
||||
|
||||
它不负责:
|
||||
|
||||
- 最终块真相
|
||||
- 持久化格式真相
|
||||
- AI 改写正式合同
|
||||
- 页面树 / projection 真相
|
||||
|
||||
## 5.2 Truth 层:Rust `EditorBlockDocument`
|
||||
|
||||
这一层负责:
|
||||
|
||||
- 块身份 `block_id`
|
||||
- 块结构与块类型
|
||||
- 命令合同
|
||||
- 保存合同
|
||||
- AI/CLI 共享的编辑真相
|
||||
|
||||
这里必须固定一句:
|
||||
|
||||
> **`Tiptap JSON` 不是长期事实源,Rust `EditorBlockDocument` 才是。**
|
||||
|
||||
## 5.3 Orchestration 层:Rust command / save / AI contract
|
||||
|
||||
这一层负责:
|
||||
|
||||
- `EditorCommand`
|
||||
- AI 生成与改写
|
||||
- 保存与冲突检测
|
||||
- 审计、trace、历史
|
||||
- 对外导入导出合同
|
||||
|
||||
也就是说:
|
||||
|
||||
> **AI 的正式入口必须先进入 Rust 命令层,而不是直接进入浏览器编辑器 JSON。**
|
||||
|
||||
## 5.4 Host 层:Leptos island,而不是长期 iframe
|
||||
|
||||
长期正式形态必须是:
|
||||
|
||||
- 主编辑区作为页面内正式 editor island
|
||||
- 与真实页面共享同一交互壳和状态上下文
|
||||
- 能被 AI 面板、侧栏、评论、引用面板通过正式接口驱动
|
||||
|
||||
长期不应固定成:
|
||||
|
||||
- `iframe + postMessage + save-request`
|
||||
|
||||
因为那只适合作为:
|
||||
|
||||
- 迁移期桥接
|
||||
- 单独验收
|
||||
- embed 验证
|
||||
|
||||
不适合作为长期主编辑区的正式形态。
|
||||
|
||||
---
|
||||
|
||||
## 6. 纠偏后的落地顺序
|
||||
|
||||
后续推进建议分五段,不再平均推进所有功能。
|
||||
|
||||
## 6.1 Phase A:主链降温与事实回正
|
||||
|
||||
目标:
|
||||
|
||||
- 把当前过渡 host 从默认主链降回实验地位
|
||||
- 恢复“还未正式切流完成”的事实口径
|
||||
|
||||
清单:
|
||||
|
||||
- [x] 回退 `DocumentContent` 中直接以 `EditorHost` 替换默认主编辑器的改动
|
||||
- [x] 回退 `runtime-config.ts` 中把 `documentEditorHost` 提前写死成事实默认值的改动
|
||||
- [x] 回退 `page.tsx` 中把 `edit=1` 当成主推进抓手的做法
|
||||
- [x] 明确 `EditorHost`、`LeptosTiptapEditorHost` 当前只作为 `debug/prototype bridge`
|
||||
- [x] 保留 spike、保留 Rust Tiptap bridge、保留测试
|
||||
|
||||
退出标准:
|
||||
|
||||
- 默认文档页不再假装“主编辑器已切流完成”
|
||||
- 当前架构讨论重新回到真实状态
|
||||
|
||||
## 6.2 Phase B:Rust truth 单边界收口
|
||||
|
||||
目标:
|
||||
|
||||
- 停止让前端 host 掌握正式保存边界
|
||||
|
||||
清单:
|
||||
|
||||
- [x] 把 `EditorBlockDocument <-> Tiptap doc` 的正式边界收口到 Rust 协议层
|
||||
- [ ] TS `tiptap-content-converter.ts` 降级为测试/过渡适配器,不再作为唯一正式保存边界
|
||||
- [ ] 统一 `block_id` 口径:Rust 真相优先,`UniqueID` 仅作 runtime 辅助
|
||||
- [x] 明确最小块型正式覆盖:`Paragraph`、`Heading`、`BulletListItem`、`NumberedListItem`、`Todo`、`Quote`、`CodeBlock`
|
||||
- [ ] 明确 AI / CLI / 人类编辑共享同一套 `EditorCommand`
|
||||
|
||||
退出标准:
|
||||
|
||||
- 任一文档编辑变更都能回到 Rust `EditorBlockDocument`
|
||||
- 前端不再拥有第二套长期 editor truth
|
||||
|
||||
## 6.3 Phase C:把编辑器本体迁到正式 Leptos island
|
||||
|
||||
目标:
|
||||
|
||||
- 把 `leptos-tiptap` 从“外部 runtime / bridge host”变成“正式页面内 Leptos island 本体”
|
||||
|
||||
清单:
|
||||
|
||||
- [x] 明确长期 host 不再以 `iframe + postMessage` 为主
|
||||
- [x] 明确“正式 island”不等于 React 壳内调用 `bridge.create/document/command`;正式主链必须是 Leptos/WASM 编辑器本体挂到页面内指定容器
|
||||
- [x] 设计正式的 Leptos editor mount 方式,使主编辑区与页面壳共享同一状态上下文
|
||||
- [x] 把 `8123 spike` 从 `mount_to_body` 页面演示形态改成“可挂到指定 DOM 节点、可 mount/unmount 的 island 本体”
|
||||
- [x] 把宿主与 island 的通信收敛成同页 typed 接口或自定义事件,而不是 `iframe` 消息协议
|
||||
- [x] 让 `undo / redo / current block / selection / insert reference / replace content` 进入 island 内部真实接口
|
||||
- [ ] 让 AI 面板与编辑区之间共享正式 editor handle 或等价命令入口
|
||||
|
||||
退出标准:
|
||||
|
||||
- 主编辑区已不再依赖跨窗口消息维持基本交互
|
||||
- 主编辑区已不再依赖 React `runtime bridge` 作为长期控制面
|
||||
- Leptos 编辑器本体真正进入主文档页主交互链
|
||||
|
||||
## 6.4 Phase D:正式 island 接入后的真实回归锁定
|
||||
|
||||
目标:
|
||||
|
||||
- 在 Rust truth 和正式 Leptos island 都成立之后,先把默认切流前必须锁定的真实回归做完整
|
||||
|
||||
清单:
|
||||
|
||||
- [x] 以真实 `/documents/[id]` 页面完成“正式 island”打开、编辑、保存、刷新回填回归
|
||||
- [x] AI 改文档时先走 Rust `EditorCommand`
|
||||
- [ ] 人类编辑与 AI 编辑都回到同一份块真相
|
||||
- [x] `block menu / slash / toolbar / turn into / 引用插入 / undo / redo` 在真实页面中行为稳定
|
||||
- [ ] 宿主只保留 mount/ref、初始化数据、最小回调与 editorBridge 出口,不再自己维护编辑器内部状态机
|
||||
- [x] 补齐默认切流前的 smoke / rollback / 观测检查项
|
||||
|
||||
退出标准:
|
||||
|
||||
- 进入 Phase E 时,不再依赖主观判断说“应该可以切默认了”
|
||||
|
||||
## 6.5 Phase E:正式切换默认主编辑器为页面内 `leptos-tiptap` island
|
||||
|
||||
目标:
|
||||
|
||||
- 正式把默认主编辑器从 `BlockNote` 切到页面内 `leptos-tiptap` island
|
||||
- 让 `BlockNote` 退出运行时主路径,只保留 `recycle/` 历史参考副本,而不是继续承担默认主链
|
||||
- 让默认主链不再依赖 `iframe runtime` 或 React `bridge runtime`
|
||||
|
||||
清单:
|
||||
|
||||
- [x] 收敛正式 host 命名:`leptos_tiptap_runtime` 改为“正式 island host”语义,`leptos_tiptap_inline` 仅保留为兼容别名;历史 `leptos_tiptap_iframe_debug` 已退出文档页 host 选择面,仅剩源码参考实现
|
||||
- [x] 修改 `DocumentContent`、`page.tsx`、`runtime-config.ts` 的默认分发逻辑,使不带任何参数的 `/documents/[id]` 直接进入正式 `leptos-tiptap` island 主链
|
||||
- [x] 删除默认主链对 `loadLeptosTiptapRuntime()` / `bridge.create()` / `bridge.document()` / `bridge.command()` 这种 React 控制面的依赖
|
||||
- [x] 历史 `blocknote` 显式回退链已退到 `recycle/`,不再作为当前 runtime config、query 参数或 feature flag 组成部分
|
||||
- [x] 把 `BlockNoteEditor` 降为 `recycle/` 历史参考实现,不再继续承接新的长期 editor truth、save contract、AI contract 语义
|
||||
- [x] 为默认切流补齐真实 smoke:打开、输入、撤销、重做、引用插入、AI 改写、保存、刷新回填、异常回退
|
||||
- [x] 为默认切流补齐观测项:runtime 加载失败、hydration 失败、Rust command 失败、保存失败、自动回退次数
|
||||
- [x] 先完成开发环境默认切流,再进入受控范围的真实使用默认切流;每一步都保留 kill switch
|
||||
- [x] 更新设计稿、运行时注释与口径,明确“默认主编辑器已切到页面内 `leptos-tiptap` island,`BlockNote` 仅保留 `recycle/` 历史参考副本”
|
||||
|
||||
### 6.5.1 当前验证记录(2026-04-21,2026-04-29 复核)
|
||||
|
||||
- [x] `http://127.0.0.1:3000/documents/[id]` 在不带 `editorHost` 参数时默认进入页面内 `leptos_tiptap` island,而不是 `iframe` 或 React `bridge runtime`
|
||||
- [x] 侧边栏“新建页面”不再依赖 `edit=1`;当前 3000 Rust Web 侧栏 `+` 会通过 `/api/tree/commands` 新建页面并直接进入默认 `leptos-tiptap` 主链,`task122` 已覆盖
|
||||
- [x] `task108-document-default-editor-cutover-smoke.js` 已移入 `recycle/scripts/`,不再作为当前 3000 主链验收项
|
||||
- [x] `editorHost=blocknote` 显式回退已退出当前 3000 Rust Web shell 的活跃验收范围;旧 Next/React fallback 只保留历史参考意义
|
||||
- [x] `DocumentContent` 已暴露主链观测点:`runtime_load_failed`、`host_init_failed`、`command_failed`、`save_failed`、`fallback_count`
|
||||
- [x] 已修正 island `embedded` 模式判定,不再因为缺少 `?embedded=1` 而把 spike 页面壳误渲染进 3000 正式文档页
|
||||
- [x] 已移除正式主链中的 spike 开发态壳元素:`hero`、重复标题、开发态提示文案、调试抽屉不再出现在 `/documents/[id]` 默认视图
|
||||
- [x] 已在真实浏览器验证:`slash` 菜单、选区浮动工具栏、空页首屏块手柄都能在 3000 默认主链中出现
|
||||
- [x] 已在真实浏览器验证:默认 island 首屏不再刷 `Maximum update depth exceeded`,`change` 风暴已收敛,普通文本输入后可进入 `saved`,刷新后可回填
|
||||
- [x] 已在真实浏览器验证:双文档 A/B 切换后各自内容不串页,刷新后按页回填;`/api/documents/save` 请求中的 `documentId/workspaceId` 已按当前页面正确绑定
|
||||
- [x] 已修正正式主链中的本地草稿隔离:Rust island 不再使用全局单一 `localStorage` key,改为按 `workspaceId/documentId` 分桶
|
||||
- [x] 已修正正式主链中的布局偏移:页面内 island 宿主不再额外施加横向 padding,编辑正文列、块手柄和 drop indicator 已回到同一几何基准
|
||||
- [x] 已收敛切页期的 disposed signal 访问风险:窗口级 `mousemove/mouseup/keydown` listener 已改为 `try_get_untracked/try_set` 安全访问,避免在页面切换时继续触碰已释放 reactive value
|
||||
- [x] `task108` 已随 `BlockNote` fallback 退场移入 `recycle/`;不再用 blocknote fallback 证明当前 3000 Rust shell 完成度
|
||||
- [x] `task121` / `task122` 已覆盖当前 3000 默认 island 的 hydrate、输入、保存、reload 读回和 UI 新建页面进入主链
|
||||
- [ ] 当前 `slash / floating toolbar / drag handle / turn into` 仍是 island 内部的最小自制实现,尚未对齐官方 notion-like 模板的组件层级、视觉细节与菜单能力
|
||||
|
||||
### 6.5.2 Phase E 硬约束
|
||||
|
||||
- 正式主链中的 `leptos-tiptap` 必须是页面内 Leptos island 本体,不接受 `iframe`、外部 runtime 页面壳或 React `bridge runtime` 作为“已完成”实现。
|
||||
- `8123` 只允许作为能力参考、资产构建来源或调试入口;不允许继续把 `8123` 页面本身嵌进 3000 主文档页。
|
||||
- 宿主只允许保留容器挂载、初始化数据、最小回调和兼容期 `editorBridge` 出口;`slash / toolbar / block menu / selection / top-level mutation` 必须回到 island 内部。
|
||||
|
||||
退出标准:
|
||||
|
||||
- 不带参数的 `/documents/[id]` 默认进入 `leptos-tiptap`
|
||||
- 出现问题时优先修正式 `leptos-tiptap` 主链,不再把回退到 `BlockNote` 作为默认策略
|
||||
- 默认链路下的 Rust truth、AI contract、save contract 保持不变
|
||||
- `BlockNote` 不再是默认主编辑器,也不再是当前运行时保险丝;仅保留 `recycle/` 参考副本
|
||||
|
||||
---
|
||||
|
||||
## 7. 当前阶段明确不做什么
|
||||
|
||||
为了避免再次跑偏,下面这些项当前不进入前排:
|
||||
|
||||
- 不继续围绕 `runtime shell` 做 polish
|
||||
- 不把 `iframe host` 继续包装成“正式页面”
|
||||
- 不先做图片上传整链
|
||||
- 不先做表格
|
||||
- 不先做协作
|
||||
- 不先接 Tiptap Cloud AI
|
||||
- 不先做一轮大规模 Wolai 对标 UI 细节打磨
|
||||
- 不回到“自研 Rust-native 输入层”作为当前主交付
|
||||
|
||||
这些项并非永久放弃,而是:
|
||||
|
||||
> **它们都不该排在“纠正主链边界错误”之前。**
|
||||
|
||||
---
|
||||
|
||||
## 8. 对 AI-first 目标的直接收口
|
||||
|
||||
如果 `mnote` 的长期预期是:
|
||||
|
||||
- AI 直接在主编辑区编写
|
||||
- 人类只做审核和辅助编辑
|
||||
|
||||
那么后续必须固定下面这条规则:
|
||||
|
||||
1. AI 正式入口只走 Rust `EditorCommand`。
|
||||
2. `EditorBlockDocument` 是 AI、人类、CLI 的共享真相。
|
||||
3. `Tiptap/leptos-tiptap` 只负责把这份真相投影成可编辑 surface。
|
||||
4. AI 不直接成为 HTML/Tiptap JSON 拼接器。
|
||||
|
||||
这条规则的意义在于:
|
||||
|
||||
- 不把系统命运绑定到某个浏览器编辑器格式
|
||||
- 为未来替换 surface 保留空间
|
||||
- 保持 Rust 语义主导权
|
||||
|
||||
---
|
||||
|
||||
## 9. 最终结论
|
||||
|
||||
这轮 `leptos-tiptap` 集成真正的问题,不是路线选错,而是:
|
||||
|
||||
> **把一条过渡桥接线过早抬成了默认主链,导致 `Tiptap` 看起来像在主导系统,而 Rust + Leptos 还没有真正主导页面内编辑、保存合同和 AI 合同。**
|
||||
|
||||
因此这份纠偏文档固定如下结论:
|
||||
|
||||
1. `Tiptap + leptos-tiptap` 继续保留为主路线,不回到“自研 Rust 版 Tiptap”。
|
||||
2. 当前需要回退的不是 `Tiptap` 本身,而是默认主链中 `iframe host + TS converter + 旧 save path` 的提前切流。
|
||||
3. 当前需要保留的是 Rust `EditorBlockDocument <-> Tiptap` 桥层、测试和 spike 中已经成立的 surface 行为。
|
||||
4. 下一步不是继续 polish runtime shell,而是先把 editor truth、save contract、AI contract 收回 Rust,再把 `leptos-tiptap` 做成正式 Leptos island。
|
||||
5. 只有在这之后,`leptos-tiptap` 才能真正体现 Rust + Leptos 架构的优势,而不是继续表现成一个被宿主页面嵌入的外来编辑器。
|
||||
@@ -3,7 +3,7 @@
|
||||
> 更新时间:2026-05-09
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-3-tiptap-leptos-rust-migration-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
|
||||
@@ -534,6 +534,8 @@ Rust 侧需要提供一个统一的页面聚合投影,至少包含:
|
||||
|
||||
## 7.5 Phase J: AI 写入入口对齐 page aggregate
|
||||
|
||||
> 2026-05-16 口径补充:`mnote.doc.fetch/find/plan_update` 与 `mnote.block.fetch/replace/insert_after/move_after` 已进入 Rust Hermes tool manifest 与 dispatch,最小块级读写闭环已启动。本文继续保留本节,是因为 `scope=selection`、`format=page_xml/text`、多块插入、复杂块移动矩阵和持久审阅 UI 仍由 `7-10` 继续验收。
|
||||
|
||||
目标:
|
||||
|
||||
- AI 不再绕过 page aggregate 直接拼前端对象
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/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-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
|
||||
|
||||
@@ -83,6 +83,12 @@
|
||||
|
||||
补充:这里的“Rust-first”只表示 `/api/page-aggregate/:id` 的对外 route、契约校验和前端消费主链已经收口到 Rust,不表示底层已经完全 kernel-native。2026-05-13 复核后,当前 route 仍由 Rust runtime adapter 消费 `documents:getMeta + documents:getContent` substrate 构建聚合快照,因此 response `source` / `x-mnote-page-aggregate-owner` 应反映为 `CompatMetaContentJoin` / `compat-join`。只有底层真实改为 kernel 原生 page aggregate projection 后,才应标记为 `KernelProjection` / `rust-kernel`。
|
||||
|
||||
补充:2026-05-16 复核 `page-aggregate-loader.ts`、`page-aggregate-builder*` 与 `/api/documents/page` compat route 后,确认读取主链仍然只消费 Rust `/api/page-aggregate/:id`,TS builder 仅保留为历史 adapter / 单测材料,Next `/api/documents/page` 继续明确返回 `410`。验证命令:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate`、`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate`、`cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts`。全仓 `git diff --check` 因既有删除的 `rust/spikes/leptos-tiptap-spike/trunk-8123.err` 无法生成 checkdiff,本轮未处理该无关脏改动。
|
||||
|
||||
补充:2026-05-16 在 `http://127.0.0.1:3000` 的 Rust `mnote-web` 主入口实跑 Page Aggregate smoke。`task110-page-title-single-truth-smoke.js` 验证标题修改后页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json`。文档打开 smoke 验证文档 HTML 包含 `data-page-aggregate-snapshot="mnote.page_aggregate.v1"` 与 `data-page-tree-source="page_aggregate.tree.pageSubtree"`,同一临时页 `/api/page-aggregate/:id` 回读 `schema=mnote.page_aggregate.v1`,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json` 与 `page-open-snapshot.png`。字段完整性 smoke 验证 `identity/head/body/tree/stats` 全部存在,证据为 `tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json`。当前 `projectionSource=documents.content`,仍符合“Rust-first 读取链、非 kernel-native 落库完成态”的过渡口径。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-body-sync-smoke.js`,验证真实页面正文编辑后 `/api/documents/save` 与 `/api/page-aggregate/:id` 回读闭环。证据为 `tmp/page-aggregate-body-sync-smoke/mp87mgz7.json` 与 `mp87mgz7.png`:`body.revision` 从 `0` 到 `1`,`body.conflictDetectionKey` 从 `tree_1778927703753_1:0` 到 `tree_1778927703753_1:1`,`body.blockDocument.blocks[0]` 回读到新段落文本和 `revisionRef`。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command` 与 `cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document` 已通过。
|
||||
|
||||
### 4.3 退出标准
|
||||
|
||||
- [x] 后续再讨论标题、页面设置、正文保存时,能够直接定位到它属于 `page_head / page_layout / page_body / page_tree` 的哪一层。
|
||||
@@ -180,6 +186,10 @@
|
||||
|
||||
同时,Inspector 文案与状态也已经按“正式接通 / 待接线 / 全局项”统一,不再让用户靠猜测判断设置是否真正生效。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-options-sync-smoke.js`,在 `http://127.0.0.1:3000` 的 Rust `mnote-web` 主入口实跑页面设置写入与 Page Aggregate 回读闭环。该 smoke 新建临时页后依次通过页面设置 UI 修改 `wideLayout=true`、`smallText=true`、`layoutDensity=compact`,确认 `/api/documents/options` 返回 `page.layout.updateOptions`,随后轮询 `/api/page-aggregate/:id` 直到 `layout.pageOptions` 同步,并同时断言 island/runtime DOM 属性同步。证据为 `tmp/page-aggregate-options-sync-smoke/mp87y6j3.json` 与 `tmp/page-aggregate-options-sync-smoke/mp87y6j3.png`:Page Aggregate 回读到 `wideLayout=true / smallText=true / layoutDensity=compact`,运行时同步为 `data-page-wide-layout="true"`、`data-page-small-text="true"`、`data-layout-density="compact"`,编辑器字号从 `16px` 到 `15px`,段落间距从 `8px` 到 `4px`。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options` 与 `cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts` 已通过。
|
||||
|
||||
补充:2026-05-16 继续新增 `scripts/task-page-aggregate-refresh-persistence-smoke.js`,在同一临时页内通过页面头、正文编辑和页面设置 UI 依次写入最新值,等待 `/api/page-aggregate/:id` 回读最新 `head/body/layout` 后刷新页面,再断言标题、正文、页面设置和 runtime DOM 不回退。证据为 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json` 与 `tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png`:刷新前后 `head.title=Page Aggregate refresh mp88fr6k`、`body.revision=1`、`conflictDetectionKey=tree_1778929070007_1:1`、`blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k`、`layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact` 均保持稳定;刷新后页头、`.ProseMirror` 正文、设置控件、`documentElement`、`.document-shell`、island root 和 `.editor-surface` 都保留最新值。修复点为 `rust/crates/mnote-web/src/ssr/pages/layout.rs` 将 `initializePageUiSurfaces` 延后到 `DOMContentLoaded` 后执行。配套命令 `cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate` 与 `cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options` 已通过。
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase I:树域与页面域标题统一
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-4-leptos-tiptap-mainline-correction-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`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`
|
||||
|
||||
@@ -392,7 +392,7 @@ GREEN:
|
||||
- E26 执行边界:实现与 smoke 必须把 `EditorBlockDocument -> Tiptap attrs.blockId/data-block-id -> DOM 锚点 -> page.body.save -> /api/documents/content -> reload/hash 定位` 串成一条链。允许在浏览器 runtime 内用 `UniqueID` 或等价插件帮助定位节点,但只能读取/补齐已有 Rust block id;若某块缺少正式 id,应通过 Rust/保存适配链生成并持久化,而不是让前端临时 id 成为长期合同。Convex 只验证持久化结果可读可刷新,不作为语义真源。
|
||||
- E26 最小真源闭环:2026-05-02 已完成第一段 anchor 切片。`leptos-tiptap` paragraph/heading/blockquote/codeBlock/image/table 的 `blockId` 渲染同时输出 `data-block-id` 与 DOM `id`,浏览器 hash 命中走 `id=:blockId` + CSS `:target`,不是手工给 ProseMirror DOM 写临时 class;Rust runtime 复制链接和 hash 滚动继续只消费 `EditorBlock.block_id` 派生的 `attrs.blockId`。`mnote-web` 保存 payload 不再发送空 `content: []`,而是派生 `editorDocument`、legacy `content`、`tiptapDocument` 和 `blockCount` 写回 Convex-backed 持久化底座;reload 侧 legacy block 恢复继续补 `attrs.blockId`。本地 smoke `node scripts/task154-e26-anchor-smoke.js` 已覆盖保存请求、`/api/documents/content`、复制链接、DOM `id/:target` 和 reload/hash 定位,截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task154-e26-anchor-local-smoke`。剩余:列表项/分割线等更多块类型的统一 anchor attr 覆盖、Wolai 视觉细节、块引用/页面引用预览与移动端行为另拆后续。
|
||||
- E27 执行前口径纠偏:AI 编辑能力包不是“补 AI 菜单文案”或“点击后显示 feedback”。最小闭环必须从 `leptos-tiptap` 的 slash / 块菜单入口出发,携带当前 `documentId`、`workspaceId`、Rust `blockId`、selection 摘要和 Tiptap JSON 快照,进入 Hermes run/session context;Hermes 通过 mnote skill/plugin 调用稳定工具,再由 Rust runtime / `page.body.save` / Convex-backed content 链写回。2026-05-13 起长期口径进一步修正:`/api/ai-agent/run`、`openai-agents-python` sidecar 和 `mnote-cli host` 都只作为历史/兼容路径,不再代表页面 AI 长期主入口;`mnote-cli` 只可作为 mnote plugin 内部适配器或调试入口。E27 第一刀已完成的 sidecar / `/api/ai-agent/run` 路径只作为过渡基线,不宣称是长期主线。
|
||||
- E27 AI agent 入口与写入闭环:2026-05-02 已完成历史主路径纠偏,修正此前把 `/api/hermes/bridge` 当作 E27 主入口的误导口径。块菜单 `AI 助理` 当时从当前 `leptos-tiptap` editor 读取 `documentId`、`workspaceId`、Rust `blockId`、selection state、selected text 与 `tiptapDocument` 快照,发起 `/api/ai-agent/run` 请求,payload 固定 `scope=document`、`stream=true`、`options.ai.provider=online`,并显示 `mnote-leptos-tiptap-ai-status` 的 `idle/pending/ready/error` 状态;请求上下文标记 `source=leptos-tiptap-island`、`action=ask_ai`,Next sidecar adapter 已透传 `workspaceId / selectedBlockId / selection / tiptapDocument` 等 block 级上下文给 `openai-agents-python`。本地 smoke `node scripts/task155-e27-ai-edit-smoke.js` 已先 RED 于 Hermes 主入口,再 GREEN 覆盖请求 URL、payload 真源字段和 ready 状态;`node scripts/task156-e27-ai-writeback-smoke.js` 已先 RED 于只返回不写入,再 GREEN 覆盖 SSE `doc_replace_range` tool_result -> 编辑器改写 -> `/api/documents/save` -> `/api/documents/content` 真源读回 -> reload 后页面读回。截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task156-e27-ai-writeback-local-smoke`。2026-05-14 起,页面 AI 主入口改为 Hermes client proxy、上下文进入 Hermes run/session context、写入工具注册为 mnote Hermes plugin 的部分已由 `7-4` 完成;剩余为 Wolai AI 菜单视觉基线、slash AI/selection toolbar AI、Improve/continue/regenerate/summarize/translate 子命令、`doc_insert_blocks` 多块插入和标题写入等细分入口如何复用同一条 Hermes tool chain。
|
||||
- E27 AI agent 入口与写入闭环:2026-05-02 已完成历史主路径纠偏,修正此前把 `/api/hermes/bridge` 当作 E27 主入口的误导口径。块菜单 `AI 助理` 当时从当前 `leptos-tiptap` editor 读取 `documentId`、`workspaceId`、Rust `blockId`、selection state、selected text 与 `tiptapDocument` 快照,发起 `/api/ai-agent/run` 请求,payload 固定 `scope=document`、`stream=true`、`options.ai.provider=online`,并显示 `mnote-leptos-tiptap-ai-status` 的 `idle/pending/ready/error` 状态;请求上下文标记 `source=leptos-tiptap-island`、`action=ask_ai`,Next sidecar adapter 已透传 `workspaceId / selectedBlockId / selection / tiptapDocument` 等 block 级上下文给 `openai-agents-python`。本地 smoke `node scripts/task155-e27-ai-edit-smoke.js` 和 `node scripts/task156-e27-ai-writeback-smoke.js` 是历史执行命令,脚本现已迁入本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`,只保留显式环境变量下的历史对照;截图目录:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task156-e27-ai-writeback-local-smoke`。2026-05-14 起,页面 AI 主入口改为 Hermes client proxy、上下文进入 Hermes run/session context、写入工具注册为 mnote Hermes plugin 的部分已由 `7-4` 完成;剩余为 Wolai AI 菜单视觉基线、slash AI/selection toolbar AI、Improve/continue/regenerate/summarize/translate 子命令、`doc_insert_blocks` 多块插入和标题写入等细分入口如何复用同一条 Hermes tool chain。
|
||||
- E27 Auth 真源纠偏:2026-05-02 补充确认,AI 编辑和在线 smoke 的用户身份必须以真实 Convex Auth 为主线;不同账号的数据隔离依赖 Convex `getAuthUserId(ctx)` 解析真实 identity subject,并由 workspace/member 权限链校验。`/auth` 复用既有 Convex Auth 页面和统一测试账号 `test@example.com` / `Test123456`;`/api/auth/session`、`/api/auth/whoami`、AI orchestrator 转发和 Convex transport 均应真实 token / forwarded actor 优先。`DEV_USER_ID` / admin acting identity 只允许作为本地底层 fallback 或显式 `MNOTE_DEV_AUTH=1` 联调模式,不能作为 E27 在线验收、多账号隔离或正式数据真源。
|
||||
- E27 当前推进备注:2026-05-03 主线程先切到 E28 Mention / Emoji,E27 曾暂停在 online smoke 模型网关层排障状态。2026-05-14 页面 AI 主入口已经完成 Hermes client proxy + mnote plugin/tool 迁移;恢复 E27 时应从 slash AI / selection toolbar AI / 子命令体验与真实 actor 复用这条新主链继续,不要回退成旧 orchestrator 或旧 `/api/ai-agent/run` 排障。
|
||||
- E28/E29 暂停与 E30 先行口径:2026-05-03 主线程先暂停 E28 Mention / Emoji 与 E29 Comment / History,转入 E30 Menu / Floating 状态机能力包。E28 已有 Wolai Hermes baseline 显示正文输入 `@` 当前弹出提醒/会议/成员候选,并可插入成员 mention,不是页面引用搜索;证据目录为 `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task157-e28-wolai-mention-baseline/`,恢复 E28 时需先重新定 `@mention` 口径,不要按“页面引用第一刀”继续。E29 在评论/历史后端边界未重新确认前保持暂停。E30 第一刀只收敛已有 slash、块菜单、二级菜单、selection/image/table floating toolbar 的打开互斥、Esc、外部点击、方向键、Enter 与层级收起,不扩新业务命令。
|
||||
|
||||
Reference in New Issue
Block a user