20260513 mindmap优化01
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
# 4-24 [done] Resource Tree / File Tree / Page Tree 真源合同与执行清单 v1
|
||||
|
||||
> 更新时间:2026-05-13
|
||||
>
|
||||
> 上游依据:
|
||||
> - `/mnt/Data1T/mnote/design/10-review/05-tree.md`
|
||||
> - `/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-20-vscode-explorer-file-tree-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/10-review/README.md`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本清单用于把 `design/10-review/05-tree.md` 中的判断落到树域主线:
|
||||
|
||||
- `Resource Tree` 是长期对象组织真源,由 Rust kernel 持有语义。
|
||||
- `File Tree` 是 Resource Tree 的主组织投影,展示页面、`index.md`、附件、mindmap、OnlyOffice、代码附件等资源。
|
||||
- `Page Tree` 是页面导航投影 / 快捷视图,不拥有排序、父子、附件归属或资源归属的最终真相。
|
||||
- Sidebar、文件树 UI、页面树 UI 都只能消费 projection 和发 command,不能重新拼第二套结构真相。
|
||||
|
||||
## 2. 术语冻结
|
||||
|
||||
- `Resource Tree`:workspace 内对象与资源的 canonical hierarchy。它不是 UI 文件树组件,而是 kernel 层的资源组织语义。
|
||||
- `File Tree`:Resource Tree 面向 VS Code Explorer 体验的 projection。
|
||||
- `Page Tree`:Resource Tree 中页面对象的导航 projection,可做快捷显示,但不拥有资源归属。
|
||||
- `ObjectIdentity`:打开、保存、草稿、tab、command 使用的对象身份。
|
||||
- `BlockAssetRelation`:页面正文 block 与资源对象之间的稳定关联,例如 `documentId + blockId + assetId + assetKind`。
|
||||
|
||||
## 3. 非目标
|
||||
|
||||
- 不在本阶段重写 Convex 底层存储。
|
||||
- 不在本阶段一次性删除所有 compat/fallback。
|
||||
- 不把文件树 UI 组件提升为真源。
|
||||
- 不让 Page Tree 接管附件、mindmap 或 OnlyOffice 的归属。
|
||||
|
||||
## 4. 前置顺序与执行门
|
||||
|
||||
本清单是 `5-12` 的前置合同。执行顺序固定为:
|
||||
|
||||
1. 先完成本文件的 `Resource Tree / File Tree / Page Tree` 真源合同。
|
||||
2. 再执行 `design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md` 中的主编辑区 object tab、草稿隔离和打开路径调整。
|
||||
|
||||
不允许绕过本文件直接在编辑区里新增第二套 asset / page 归属判断。编辑区可以先做 mindmap 止血,但止血代码必须只消费本文件定义的 object identity / file tree intent,不得把临时前端判断提升为长期合同。
|
||||
|
||||
当前代码基线:
|
||||
|
||||
- `rust/crates/core-protocol/src/kernel.rs` 已有 `KernelProjectionResourceKind`、`KernelProjectionAssetKind`、`KernelProjectionResourceMeta`、`KernelProjectionItem`。
|
||||
- `rust/crates/bridge-runtime/src/lib.rs` 已有 `build_file_tree_projection_result`、`make_projection_resource_meta`、`normalize_file_tree_asset` 等 file tree projection 构造逻辑。
|
||||
- `rust/crates/mnote-web/src/routes/tree.rs` 与 `rust/crates/mnote-web/src/ssr/pages/layout.rs` 已消费 `resourceMeta` 并发出 `tree.asset.open`。
|
||||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs` 当前仍把 mindmap asset 主路径导航到 `/mindmap/{documentId}/{assetId}`,这是 `5-10` bug 的直接落点,但长期合同归属仍应先在本文件冻结。
|
||||
|
||||
阶段门:
|
||||
|
||||
- G1:协议能表达 `index.md`、mindmap、OnlyOffice、普通附件、代码附件的不同 object identity。
|
||||
- G2:`file_tree` projection row 能稳定输出 object identity 或等价 `resourceMeta.extra.objectIdentity`。
|
||||
- G3:`page_tree` projection 被约束为页面导航投影,不承载 asset 归属。
|
||||
- G4:`tree.asset.open` 只表达打开资源对象的 intent,不决定“把资源当正文打开”。
|
||||
- G5:完成 G1-G4 后,`5-12` 才能把主编辑区打开路径、草稿 key、保存命令接入 object tab / object editor 规则。
|
||||
|
||||
## 5. 执行清单
|
||||
|
||||
### 5.1 协议模型
|
||||
|
||||
- [x] 在 `rust/crates/core-protocol/` 增加或扩展资源对象协议。
|
||||
- 优先落点:`rust/crates/core-protocol/src/kernel.rs`,必要时拆出 `rust/crates/core-protocol/src/resource.rs` 并在 `lib.rs` re-export。
|
||||
- 当前已有 `KernelProjectionResourceKind`,本阶段应补齐或映射为长期 `ResourceKind` 语义。
|
||||
- 最小资源种类:`page`、`index`、`mindmap`、`attachment`、`onlyoffice`、`code`。
|
||||
- `ObjectIdentity` 最小字段:`objectKind`、`documentId`、`blockId`、`assetId`。
|
||||
- `BlockAssetRelation` 最小字段:`documentId`、`blockId`、`assetId`、`assetKind`。
|
||||
- 命名规则:Rust 类型使用 `KernelObjectIdentity` / `KernelBlockAssetRelation` 或等价项目内前缀;序列化字段使用 camelCase。
|
||||
|
||||
- [x] 补协议单测。
|
||||
- 文件建议:`rust/crates/core-protocol/tests/resource_tree_contract.rs`。
|
||||
- 断言:mindmap、OnlyOffice、普通附件都能表达为 resource object。
|
||||
- 断言:`index.md` 的 object identity 与 mindmap asset 的 object identity 不相等。
|
||||
- 断言:`BlockAssetRelation` 能表达 `documentId + blockId + assetId + assetKind`,且不会被反序列化为页面正文 identity。
|
||||
- 运行命令:
|
||||
- `cd /mnt/Data1T/mnote/rust && cargo test -p core-protocol --test resource_tree_contract -- --nocapture`
|
||||
- 预期:新增协议测试通过,失败时不得继续进入 `5-12` 的 P1/P2。
|
||||
|
||||
### 5.2 Projection 合同
|
||||
|
||||
- [x] 扩展 `file_tree` projection 的 row contract。
|
||||
- 每个 row 必须带稳定 `row_id`、`node_id`、`projection_kind`、`resource_meta`。
|
||||
- `resource_meta` 至少包含 `resourceKind`、`documentId`、`assetId`、`assetKind`、`objectIdentity`。
|
||||
- 优先修改点:`rust/crates/core-protocol/src/kernel.rs` 的 `KernelProjectionResourceMeta`,以及 `rust/crates/bridge-runtime/src/lib.rs` 的 `make_projection_resource_meta`。
|
||||
- 兼容策略:已有 `resourceKind/documentId/assetId/assetKind` 保持不破坏;新增字段可先放入 `resourceMeta.extra.objectIdentity`,待前后端消费稳定后再提升为强类型字段。
|
||||
|
||||
- [x] 固定 `page_tree` projection 的降级边界。
|
||||
- `page_tree` 只输出页面导航关系。
|
||||
- `page_tree` 不输出附件归属、mindmap 归属、OnlyOffice 归属的最终判断。
|
||||
- 如需显示资源状态,只能引用 `resource_meta` 或 resource projection,不得在 page tree renderer 内拼装。
|
||||
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 中 `page_tree` projection 构造逻辑、`wolai-frontend/src/lib/tree-projection.ts`、`wolai-frontend/src/lib/documents/page-subtree.ts`。
|
||||
|
||||
- [x] 补 Rust projection 测试。
|
||||
- 文件优先看:`rust/crates/bridge-runtime/src/lib.rs`、`rust/crates/mnote-web/src/tree_shell/`。
|
||||
- 测试要求:同一页面下的 `index.md` 与 mindmap asset 生成不同 row,但共享同一 `documentId`,并通过 `BlockAssetRelation` 建立关系。
|
||||
- 建议新增或扩展测试:
|
||||
- `kernel_project_view_query_executes_into_file_tree_projection_contract`
|
||||
- `kernel_file_tree_projection_query_supports_extended_resources_and_max_results`
|
||||
- 新增 `kernel_file_tree_projection_separates_index_and_mindmap_object_identity`
|
||||
- 运行命令:
|
||||
- `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime file_tree_projection -- --nocapture`
|
||||
- `cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web file_tree_projection -- --nocapture`
|
||||
|
||||
### 5.3 File Tree 派生规则
|
||||
|
||||
- [x] 页面节点下固定派生 `index.md` row。
|
||||
- `index.md` row 只能打开 Page Aggregate body。
|
||||
- `index.md` row 不得根据页面内第一个 block 类型改变打开目标。
|
||||
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 的 `build_file_tree_projection_result`,当前已固定创建 `index:{documentId}` node。
|
||||
- 前端消费检查点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 的 file tree row click 分支。
|
||||
|
||||
- [x] 页面内资源固定派生为 child resource row。
|
||||
- mindmap row 打开 mindmap object editor。
|
||||
- OnlyOffice row 打开 OnlyOffice object editor。
|
||||
- 代码附件 row 打开代码/预览编辑器。
|
||||
- 普通附件 row 打开签名资源或预览。
|
||||
- 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 的 `normalize_file_tree_asset`、`infer_file_tree_asset_shape`、`classify_asset_resource_kind`。
|
||||
- 必须保留 `documentId` 与 `assetId`,供 `5-12` 的 object tab identity 使用。
|
||||
|
||||
- [x] 文件树 row 不得从前端多份数据临时拼真相。
|
||||
- 允许前端显示、筛选、选中、展开。
|
||||
- 不允许前端决定资源归属、排序真相或 block-asset relation 真相。
|
||||
- 前端允许做的本地状态:`expanded`、`selected`、`hover`、`focus`、`dragging`、`drop target`。
|
||||
- 前端禁止新增长期字段:`assetParentDocumentId`、`mindmapOwnerPageId`、`pageTreeAssetChildren` 这类与 kernel projection 重复的归属真相。
|
||||
|
||||
### 5.4 Page Tree 派生规则
|
||||
|
||||
- [x] Page Tree 只消费页面导航投影。
|
||||
- 可显示页面标题、层级、当前页、快捷入口。
|
||||
- 不负责附件、mindmap、OnlyOffice、代码附件的生命周期。
|
||||
|
||||
- [x] Page Tree command 只发页面导航相关 command。
|
||||
- 页面新建、重命名、移动、归档继续走 `tree.node.*` 或正式 page/tree command。
|
||||
- 资源 attach/detach/rename/move 不进入 page tree 私有命令。
|
||||
- 负向检查:Page Tree renderer / host 中不得新增 mindmap、OnlyOffice、附件归属推导分支。
|
||||
|
||||
### 5.5 Command 边界
|
||||
|
||||
- [x] 新增或确认资源关系 command family。
|
||||
- `tree.asset.attach`
|
||||
- `tree.asset.detach`
|
||||
- `tree.resource.rename`
|
||||
- `tree.resource.move`
|
||||
|
||||
- [x] 明确不同对象写入 owner。
|
||||
- 页面正文:`page.body.save`
|
||||
- 页面标题:`page.head.updateTitle`
|
||||
- 页面设置:`page.layout.updateOptions`
|
||||
- mindmap 内容:`mindmap.command.apply`
|
||||
- OnlyOffice 内容:OnlyOffice callback / forcesave 写回链
|
||||
- 资源归属:`tree.asset.*` / `tree.resource.*`
|
||||
- 优先检查点:
|
||||
- `rust/crates/bridge-runtime/src/lib.rs` command plan
|
||||
- `rust/crates/mnote-web/src/transport/convex.rs` command adapter
|
||||
- `wolai-frontend/src/lib/documents/rust-runtime.test.ts`
|
||||
|
||||
### 5.6 Smoke 与验收
|
||||
|
||||
- [x] 增加 Resource Tree / File Tree projection smoke。
|
||||
- 断言文件树同页下可见 `index.md`、mindmap、附件。
|
||||
- 断言点击 `index.md` 只打开 Page Aggregate body。
|
||||
- 断言点击 mindmap 不进入 Page Aggregate body 保存链。
|
||||
- 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`。
|
||||
- 建议新增断言脚本:`scripts/task170-resource-tree-filetree-source-smoke.js`,或在 task112 中增加独立 case。
|
||||
- 运行命令:
|
||||
- `node scripts/task112-tree-rust-family-regression-smoke.js`
|
||||
- 若新增脚本:`node scripts/task170-resource-tree-filetree-source-smoke.js`
|
||||
|
||||
- [x] 增加 Page Tree 负向 smoke。
|
||||
- 断言 Page Tree 不显示资源归属为自己的结构真相。
|
||||
- 断言 Page Tree 操作不会改变 mindmap/附件归属。
|
||||
- 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`。
|
||||
- 负向检查输出需记录:操作 Page Tree 后,同一 `assetId` 的 `documentId` / `objectIdentity` 未变化。
|
||||
|
||||
## 6. 建议提交切片
|
||||
|
||||
本文件只定义设计顺序,不要求一次提交完成全部实现。后续执行时建议按以下切片推进:
|
||||
|
||||
1. 协议切片:`core-protocol` 增补 object identity / relation 类型与测试。
|
||||
2. Projection 切片:`bridge-runtime` 与 `mnote-web` file tree projection 输出 object identity。
|
||||
3. UI 消费切片:file tree host 只转发 object open intent,不拼归属真相。
|
||||
4. Page Tree 负向切片:补 page tree 不持有资源归属的单测或 smoke。
|
||||
5. 验收切片:跑 Rust 测试与真实浏览器 smoke,把证据补回对应 bug / design 文档。
|
||||
|
||||
## 7. 验收命令
|
||||
|
||||
最低验收命令:
|
||||
|
||||
```bash
|
||||
cd /mnt/Data1T/mnote/rust && cargo test -p core-protocol --test resource_tree_contract -- --nocapture
|
||||
cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime file_tree_projection -- --nocapture
|
||||
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web file_tree_projection -- --nocapture
|
||||
node scripts/task112-tree-rust-family-regression-smoke.js
|
||||
```
|
||||
|
||||
如执行环境没有启动 `3000`,先运行仓库常用入口:
|
||||
|
||||
```bash
|
||||
npm run desktop:hot
|
||||
```
|
||||
|
||||
真实浏览器 smoke 必须使用默认测试账号 `mnote.e2e@example.com` / `MnoteE2E123!`,不能用 `devFallback` 代替登录态。
|
||||
|
||||
## 8. Done Gate
|
||||
|
||||
本文移动到 `done/` 前必须满足:
|
||||
|
||||
- [x] `Resource Tree`、`File Tree`、`Page Tree` 三个术语在协议和文档中不再混用。
|
||||
- [x] `file_tree` row 能表达 `index.md`、mindmap、OnlyOffice、附件、代码附件的不同 object identity。
|
||||
- [x] `page_tree` 被验证为页面导航 projection,不拥有资源归属真相。
|
||||
- [x] mindmap 与 `index.md` 的打开、保存、草稿身份互相隔离。
|
||||
- [x] 至少一条真实浏览器 smoke 覆盖 `index.md + mindmap asset + 切页 + 再打开` 的完整链路。
|
||||
|
||||
## 9. 完成证据
|
||||
|
||||
- 协议:`rust/crates/core-protocol/src/kernel.rs` 增加 `KernelObjectIdentity`、`KernelObjectKind`、`KernelBlockAssetRelation`,并由 `rust/crates/core-protocol/tests/resource_tree_contract.rs` 覆盖。
|
||||
- Projection:`rust/crates/bridge-runtime/src/lib.rs` 的 file tree projection 输出 `resourceMeta.objectIdentity` / `blockAssetRelation`,并覆盖 index、mindmap、OnlyOffice、代码附件、普通附件。
|
||||
- UI intent:`rust/crates/mnote-web/src/ssr/pages/layout.rs`、`rust/crates/mnote-web/src/routes/tree.rs`、`rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs` 将 `objectIdentity` 传入 file tree DOM 与 `tree.asset.open`。
|
||||
- Smoke:`node scripts/task112-tree-rust-family-regression-smoke.js` 通过,page tree host / file tree host 主链为 `dom`;debug tree shell 关闭项按当前主线记录为 skipped。
|
||||
- Mindmap 链路:`node scripts/task169-mindmap-realtime-smoke.js` 通过,覆盖 mindmap asset 打开、长中文编辑、保存、切页、回 `index.md`、再打开 mindmap。
|
||||
|
||||
## 10. 交接给 5-12 的输入
|
||||
|
||||
完成本文件后,`5-12` 可以依赖以下输入推进主编辑区:
|
||||
|
||||
- 文件树 `index.md` row 的 `objectIdentity`:`page:index:{documentId}`。
|
||||
- 文件树 mindmap row 的 `objectIdentity`:`resource:mindmap:{documentId}:{assetId}`。
|
||||
- 文件树 OnlyOffice row 的 `objectIdentity`:`resource:onlyoffice:{documentId}:{assetId}`。
|
||||
- 文件树普通附件 row 的 `objectIdentity`:`resource:attachment:{documentId}:{assetId}`。
|
||||
- 文件树代码附件 row 的 `objectIdentity`:`resource:code:{documentId}:{assetId}`。
|
||||
- Page Tree 不再作为资源归属输入;它只提供页面导航选择。
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
# 5-12 [done] 主编辑区 Object Tab 与 Resource Tree 对齐执行清单 v1
|
||||
|
||||
> 更新时间:2026-05-13
|
||||
>
|
||||
> 上游依据:
|
||||
> - `/mnt/Data1T/mnote/design/10-review/05-tree.md`
|
||||
> - `/mnt/Data1T/mnote/bugs/05-editor-mainline/done/5-10-mindmap-filetree-index-single-truth-split-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md`
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本清单用于把主编辑区从“只等于页面正文编辑器”推进为 workspace object tab host:
|
||||
|
||||
- `index.md` tab 只编辑 Page Aggregate body。
|
||||
- mindmap tab 只编辑 mindmap projection / command。
|
||||
- OnlyOffice tab 只编辑对应附件对象。
|
||||
- 代码附件 / 普通附件 tab 只编辑或预览对应资源对象。
|
||||
- 所有 tab 使用独立 `ObjectIdentity`、草稿身份和保存命令,不互相伪装。
|
||||
|
||||
## 2. 当前优先级
|
||||
|
||||
P0 是关闭当前 mindmap 文件树打开污染 `index.md` 的缺陷。
|
||||
|
||||
P1 是建立主编辑区 object tab 的最小协议,让后续 mindmap、OnlyOffice、附件都能进入同一套打开/关闭/保存/恢复模型。
|
||||
|
||||
P2 是把 Page Aggregate 的 block-asset relation 与 Resource Tree projection 串起来。
|
||||
|
||||
执行前置:
|
||||
|
||||
- 必须先按 `design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md` 固定 Resource Tree / File Tree / Page Tree 的真源合同。
|
||||
- 本文件不定义新的资源归属真相,只消费 `4-24` 输出的 `ObjectIdentity`、`resourceMeta` 和 `tree.asset.open` intent。
|
||||
- P0 可以作为止血先落地,但 P0 中所有草稿 key、保存命令、打开路径都必须与 `4-24` 的 object identity 兼容。
|
||||
|
||||
当前关键代码锚点:
|
||||
|
||||
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:file tree asset 打开事件、`buildMindmapOpenPath`、`openConvexAssetFromFileTree`、`tree.asset.open` listener。
|
||||
- `rust/crates/mnote-web/src/routes/mindmap_shell.rs`:standalone mindmap shell 与临时 bootstrap。
|
||||
- `rust/crates/mnote-web/src/ssr/pages/mindmap.rs`:mindmap object editor 页面标记与 command form。
|
||||
- `rust/crates/mnote-web/src/routes/mindmap_api.rs`:mindmap command / put 保存链。
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs`:`page.body.save` 页面正文保存链。
|
||||
- `rust/spikes/leptos-tiptap-spike/src/lib.rs`:文档页 island 草稿、bootstrap、保存 runtime。
|
||||
- `scripts/task169-mindmap-realtime-smoke.js`:当前 mindmap 长中文、保存、实时刷新 smoke,应升级为本 bug 的主验收脚本。
|
||||
|
||||
## 3. P0:mindmap object editor 止血清单
|
||||
|
||||
### 3.1 打开路径
|
||||
|
||||
- [x] 文件树 mindmap asset 普通点击必须打开明确的 mindmap object editor。
|
||||
- 当前允许继续使用 `/mindmap/{documentId}/{mindmapId}`。
|
||||
- 该路由必须声明自己是 object editor / asset editor。
|
||||
- 不允许被 `index.md` 吞掉。
|
||||
- 不允许把临时 mindmap-only bootstrap 视为真实页面正文。
|
||||
- 推荐实现口径:
|
||||
- 保留 `/mindmap/{documentId}/{mindmapId}` 作为独立 object editor 路由。
|
||||
- 在 HTML 根节点或主容器加可测标记,例如 `data-mnote-object-editor="mindmap"`、`data-mnote-object-identity="resource:mindmap:{documentId}:{mindmapId}"`。
|
||||
- file tree 点击 mindmap 后,若继续整页导航,必须让页面显式进入 mindmap object editor,而不是复用文档页正文 island 的页面草稿 key。
|
||||
- 后续 object tab host 接入后,再把整页导航替换为主编辑区 tab 打开。
|
||||
|
||||
- [x] `index.md` 点击必须只打开真实文档页。
|
||||
- 目标为 `/documents/{documentId}?treeView=filetree`。
|
||||
- 读取来源为 Rust `/api/page-aggregate/{documentId}`。
|
||||
- 不读取 mindmap standalone 草稿。
|
||||
- 推荐检查点:
|
||||
- `layout.rs` 中 file tree row click 对 `index/document/markdown` 的分支。
|
||||
- `leptos-tiptap` island 初始化时是否优先使用服务端 Page Aggregate bootstrap。
|
||||
|
||||
### 3.2 草稿与保存身份
|
||||
|
||||
- [x] standalone mindmap 的草稿 key 必须使用 object identity。
|
||||
- 推荐格式:`__mindmap_object__:{documentId}:{mindmapId}`。
|
||||
- 禁止使用真实页面的 `workspaceId:documentId` 草稿 key。
|
||||
- 推荐检查点:`rust/spikes/leptos-tiptap-spike/src/lib.rs` 内 localStorage 草稿读写 key 生成逻辑。
|
||||
- 负向要求:打开 `/mindmap/{documentId}/{mindmapId}` 后,localStorage 中不得新增或覆盖文档正文草稿 key。
|
||||
|
||||
- [x] 文档页有服务端 bootstrap content 时,不得被 localStorage 旧草稿覆盖。
|
||||
- 如果存在历史坏草稿,文档页应优先使用 Page Aggregate body。
|
||||
- 打开文档页后应把坏草稿覆盖或隔离,不让其继续污染后续打开。
|
||||
- 推荐策略:
|
||||
- 当 bootstrap contract 是 `mnote.page_aggregate.v1` 时,优先采用 bootstrap content。
|
||||
- 对与 mindmap object key 匹配的草稿直接隔离,不参与文档页正文恢复。
|
||||
- 对历史坏草稿只允许在同一 `ObjectIdentity` 下恢复,不允许跨 `page:index` 与 `resource:mindmap` 恢复。
|
||||
|
||||
- [x] mindmap 保存只走 mindmap command。
|
||||
- 保存命令:`mindmap.command.apply` 或当前正式 mindmap put/apply 链。
|
||||
- 禁止触发 `page.body.save`。
|
||||
- 推荐检查点:
|
||||
- `rust/crates/mnote-web/src/routes/mindmap_api.rs`
|
||||
- `rust/crates/mnote-web/src/transport/convex.rs`
|
||||
- `wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts`
|
||||
- 负向测试:mindmap object editor 保存期间拦截或记录网络请求,不能出现 `/api/documents/save` 或 canonical `page.body.save`。
|
||||
|
||||
### 3.3 真实网页 smoke
|
||||
|
||||
- [x] 更新 `scripts/task169-mindmap-realtime-smoke.js`。
|
||||
- 新建测试页面。
|
||||
- 插入或生成 mindmap block。
|
||||
- 从文件树点击本轮 `documentId + mindmapId` 对应 mindmap asset。
|
||||
- 断言进入 mindmap object editor,而不是 `index.md`。
|
||||
- 输入长中文并保存。
|
||||
- 切到其他页面。
|
||||
- 再点击原页面 `index.md`。
|
||||
- 断言 `index.md` 仍显示 Page Aggregate body。
|
||||
- 再打开 mindmap asset。
|
||||
- 断言长中文仍存在。
|
||||
- 测试数据前缀:`TEST-T169-MINDMAP-OBJECT-<timestamp>`。
|
||||
- 必须记录并断言:
|
||||
- `documentId`
|
||||
- `mindmapId`
|
||||
- mindmap object editor URL 或 tab identity
|
||||
- `index.md` 返回 URL
|
||||
- 长中文内容保存后的重开结果
|
||||
|
||||
- [x] 负向断言。
|
||||
- 点击 mindmap asset 后不得调用 `page.body.save`。
|
||||
- 打开 `index.md` 后不得加载 `__mindmap_object__` 草稿。
|
||||
- mindmap live refresh 不得在长中文编辑过程中重挂载或覆盖输入。
|
||||
- 修复前失败时 bug 留在 `bugs/05-editor-mainline/process/5-10-mindmap-filetree-index-single-truth-split-v1.md`;本轮通过后已移动到 done。
|
||||
|
||||
### 3.4 P0 建议实施顺序
|
||||
|
||||
- [x] Step 1:先补 smoke 红灯。
|
||||
- 修改:`scripts/task169-mindmap-realtime-smoke.js`。
|
||||
- 目标:当前实现应能暴露 `index.md` 与 mindmap object editor 身份混淆或草稿污染风险。
|
||||
- 运行:`node scripts/task169-mindmap-realtime-smoke.js`。
|
||||
- 预期:修复前至少一个负向断言失败,或者脚本明确输出当前路径仍有污染风险。
|
||||
|
||||
- [x] Step 2:标记 mindmap object editor 身份。
|
||||
- 修改:`rust/crates/mnote-web/src/routes/mindmap_shell.rs`、`rust/crates/mnote-web/src/ssr/pages/mindmap.rs`。
|
||||
- 目标:`/mindmap/{documentId}/{mindmapId}` 输出明确 object editor contract,不能被识别为 Page Aggregate body。
|
||||
- 验收:页面 DOM 能被 smoke 定位到 `data-mnote-object-editor="mindmap"` 或等价稳定标记。
|
||||
|
||||
- [x] Step 3:隔离 mindmap 草稿 key。
|
||||
- 修改:`rust/spikes/leptos-tiptap-spike/src/lib.rs`。
|
||||
- 目标:mindmap standalone 使用 `__mindmap_object__:{documentId}:{mindmapId}`;文档页使用 `page:index:{documentId}` 或现有页面草稿 key,但不得互读。
|
||||
- 验收:smoke 检查 localStorage 中 mindmap key 与 page key 分离。
|
||||
|
||||
- [x] Step 4:确认保存命令隔离。
|
||||
- 修改:优先不改实现,先用测试确认;如失败,再收口 `mindmap_api.rs` / `documents.rs` / transport adapter。
|
||||
- 目标:mindmap 保存只走 `mindmap.command.apply`,`index.md` 保存只走 `page.body.save`。
|
||||
- 验收:网络请求和 command result 中的 canonical command 分别正确。
|
||||
|
||||
- [x] Step 5:跑完整 smoke 并补 bug 证据。
|
||||
- 运行:`node scripts/task169-mindmap-realtime-smoke.js`。
|
||||
- 预期:长中文编辑、保存、切页、回 `index.md`、再开 mindmap 全链路通过。
|
||||
|
||||
## 4. P1:Object Tab Host 最小合同
|
||||
|
||||
### 4.1 Tab Identity
|
||||
|
||||
- [x] 定义主编辑区 tab identity。
|
||||
- `page:index:{documentId}`
|
||||
- `resource:mindmap:{documentId}:{mindmapId}`
|
||||
- `resource:onlyoffice:{documentId}:{assetId}`
|
||||
- `resource:attachment:{documentId}:{assetId}`
|
||||
- `resource:code:{documentId}:{assetId}`
|
||||
|
||||
- [x] 每个 tab 必须声明:
|
||||
- `objectKind`
|
||||
- `documentId`
|
||||
- `blockId`
|
||||
- `assetId`
|
||||
- `title`
|
||||
- `dirtyState`
|
||||
- `saveCommand`
|
||||
- `closeBehavior`
|
||||
|
||||
### 4.2 Tab 打开规则
|
||||
|
||||
- [x] `index.md` row 打开 `page:index:{documentId}`。
|
||||
- [x] mindmap row 打开 `resource:mindmap:{documentId}:{mindmapId}`。
|
||||
- [x] OnlyOffice row 打开 `resource:onlyoffice:{documentId}:{assetId}`。
|
||||
- [x] 代码附件 row 打开 `resource:code:{documentId}:{assetId}`。
|
||||
- [x] 普通附件 row 打开 `resource:attachment:{documentId}:{assetId}`。
|
||||
- 输入来源只能是 `4-24` 的 file tree row `resourceMeta.objectIdentity` 或等价字段。
|
||||
- 如果字段缺失,允许临时由 `resourceKind + documentId + assetId` 推导,但必须在代码注释或测试名中标明是兼容推导,不得作为新真源。
|
||||
|
||||
### 4.3 Tab 保存规则
|
||||
|
||||
- [x] Page tab 保存只调用 `page.body.save`。
|
||||
- [x] Mindmap tab 保存只调用 `mindmap.command.apply`。
|
||||
- [x] OnlyOffice tab 保存只通过 OnlyOffice callback / forcesave。
|
||||
- [x] Code tab 保存只写对应 asset。
|
||||
- [x] Attachment preview tab 默认无正文保存行为。
|
||||
- 负向规则:任一 resource tab 保存不得调用 `page.body.save`,除非该操作明确修改的是 Page Aggregate body 中的引用 block。
|
||||
|
||||
### 4.4 P1 建议实施顺序
|
||||
|
||||
- [x] Step 1:定义 tab identity 类型。
|
||||
- 首选落点:`rust/crates/core-protocol/src/kernel.rs` 或 `rust/crates/core-protocol/src/editor/model.rs`。
|
||||
- 前端适配落点:`wolai-frontend/src/components/sidebar/tree-shell-dom-model.ts`、`wolai-frontend/src/components/sidebar/sidebar-navigation.ts`。
|
||||
|
||||
- [x] Step 2:让 file tree open intent 携带 tab identity。
|
||||
- 修改:`rust/crates/mnote-web/src/routes/tree.rs`、`rust/crates/mnote-web/src/ssr/pages/layout.rs`。
|
||||
- 验收:`tree.asset.open` detail 中包含 `objectIdentity` 或可无歧义生成 object identity 的字段。
|
||||
|
||||
- [x] Step 3:主编辑区 host 消费 tab identity。
|
||||
- 初期允许只有 `page:index` 与 `resource:mindmap` 两类 tab。
|
||||
- 不要求一次完成 OnlyOffice / code / attachment 的 UI,但必须保留类型位。
|
||||
|
||||
- [x] Step 4:补 tab identity 单测。
|
||||
- 建议测试文件:`wolai-frontend/src/components/sidebar/sidebar-navigation.test.ts`、`wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx`。
|
||||
- 断言:`index.md` 与 mindmap asset 打开生成不同 tab identity。
|
||||
|
||||
## 5. P2:Page Aggregate 与 Block-Asset Relation
|
||||
|
||||
- [x] 页面正文中的 mindmap block attrs 必须持有稳定 `mindmapId`。
|
||||
- [x] Page Aggregate 输出 mindmap block 与 resource relation。
|
||||
- [x] File Tree mindmap row 来源于同一 relation。
|
||||
- [x] 删除 mindmap block 时,资源关系必须进入 detach / archive 计划。
|
||||
- [x] 删除 mindmap asset 时,页面正文 block 必须进入明确处理计划:删除 block、保留占位、或标记资源缺失,不能静默断链。
|
||||
- 优先落点:`rust/crates/core-protocol/src/page_aggregate.rs`、`rust/crates/bridge-runtime/src/lib.rs`、`wolai-frontend/src/lib/documents/tiptap-content-converter.ts`。
|
||||
- 验收:同一个 `mindmapId` 能同时从 Page Aggregate block attrs 与 File Tree resource row 追踪到。
|
||||
|
||||
## 6. P3:OnlyOffice / 附件 / 插件对象推广
|
||||
|
||||
- [x] OnlyOffice 附件打开复用 Object Tab identity。
|
||||
- [x] 代码附件编辑复用 Object Tab identity。
|
||||
- [x] 普通附件预览复用 Object Tab identity。
|
||||
- [x] 插件型对象只能通过 block-asset relation 接入页面正文,不能把插件 runtime data 当 Page Aggregate body。
|
||||
- OnlyOffice 参考落点:`rust/crates/mnote-web/src/routes/onlyoffice.rs`、`scripts/task174-rust-onlyoffice-attachment-open-smoke.js`。
|
||||
- 上传 / 普通附件参考落点:`rust/crates/mnote-web/src/routes/media.rs`、`scripts/task175-rust-upload-entry-smoke.js`。
|
||||
|
||||
## 7. 验收命令
|
||||
|
||||
最低验收顺序:
|
||||
|
||||
```bash
|
||||
node scripts/task169-mindmap-realtime-smoke.js
|
||||
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web mindmap -- --nocapture
|
||||
cd /mnt/Data1T/mnote/rust && cargo test -p mnote-web documents_save_route_executes_page_body_save_command -- --nocapture
|
||||
```
|
||||
|
||||
扩展验收:
|
||||
|
||||
```bash
|
||||
pnpm --dir wolai-frontend test -- sidebar-navigation.test.ts tree-shell-host.test.tsx
|
||||
node scripts/task174-rust-onlyoffice-attachment-open-smoke.js
|
||||
node scripts/task175-rust-upload-entry-smoke.js
|
||||
```
|
||||
|
||||
如本地服务未启动,先执行:
|
||||
|
||||
```bash
|
||||
npm run desktop:hot
|
||||
```
|
||||
|
||||
真实浏览器 smoke 必须使用默认测试账号 `mnote.e2e@example.com` / `MnoteE2E123!`,并优先从 `http://localhost:3000/auth` 的“测试账号快速登录”进入。
|
||||
|
||||
## 8. Done Gate
|
||||
|
||||
本文移动到 `done/` 前必须满足:
|
||||
|
||||
- [x] 当前 mindmap bug 文档 `5-10` 可移动到 `bugs/05-editor-mainline/done/`。
|
||||
- [x] 真实浏览器 smoke 证明 mindmap 文件树点击、长中文编辑、保存、切页、回 `index.md`、再开 mindmap 全链路正常。
|
||||
- [x] `index.md`、mindmap、OnlyOffice、附件的 object identity 已在打开路径和保存路径中区分。
|
||||
- [x] 至少 mindmap 与 `index.md` 已完成草稿隔离。
|
||||
- [x] 文件树打开行为不再让 asset 伪装成页面正文。
|
||||
|
||||
## 9. 完成证据
|
||||
|
||||
- Object identity:`rust/crates/core-protocol/src/kernel.rs` 定义 `KernelObjectIdentity`,file tree projection 与 DOM 传递 `page/index/mindmap/only_office/code/attachment` identity。
|
||||
- Mindmap object editor:`rust/crates/mnote-web/src/ssr/pages/mindmap.rs` 输出 `data-mnote-object-editor="mindmap"` 与 `data-mnote-object-identity="resource:mindmap:{documentId}:{mindmapId}"`。
|
||||
- 草稿隔离:`rust/crates/mnote-web/src/routes/mindmap_shell.rs` 使用 `__mindmap_object__:{doc}:{mindmap}` bootstrap identity;`rust/spikes/leptos-tiptap-spike/src/lib.rs` 的 `standalone_mindmap_object_uses_isolated_draft_identity` 通过。
|
||||
- 保存隔离:`cargo test -p mnote-web mindmap -- --nocapture` 与 `cargo test -p mnote-web documents_save_route_executes_page_body_save_command -- --nocapture` 通过。
|
||||
- 真实 smoke:`node scripts/task169-mindmap-realtime-smoke.js` 通过,覆盖文件树 mindmap 打开、长中文编辑、保存、切页、回 `index.md`、再打开 mindmap。
|
||||
|
||||
## 10. 完成后回填
|
||||
|
||||
实现完成后需要回填以下位置:
|
||||
|
||||
- `bugs/05-editor-mainline/done/5-10-mindmap-filetree-index-single-truth-split-v1.md`:已补真实修复证据并移动到 `done/`。
|
||||
- `design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md`:已标记 `ObjectIdentity` 与 file tree row contract 被编辑区消费。
|
||||
- `design/10-review/05-tree.md`:补最终采用的 object editor / object tab 方案摘要。
|
||||
@@ -0,0 +1,475 @@
|
||||
# 6 [process] Mindmap Phase 6 KMind/simple-mind-map Parity Detail Checklist v1
|
||||
|
||||
> 日期:2026-05-11
|
||||
>
|
||||
> 当前阶段:Phase 6 `leptos-mindmap` 已完成真实 `simple-mind-map` runtime + Leptos/Rust floating overlay shell 的第一轮收口;本 checklist 是后续细节优化入口。
|
||||
>
|
||||
> 执行规则:每完成一个节点,必须基于代码、测试、截图或人工复核证据勾选对应项。未验证的实现不得勾选。
|
||||
|
||||
## 0. 目标与结论
|
||||
|
||||
**目标:**把当前 mindmap shell 从“可运行的 Leptos/Rust 浮动 UI 壳”推进到接近 KMind 与 simple-mind-map 官方编辑器的工作台体验。
|
||||
|
||||
当前不需要改变 Phase 6 的大方向:
|
||||
|
||||
```text
|
||||
Rust kernel truth
|
||||
-> mindmap.simple_mind_map_scene.v1
|
||||
-> leptos-tiptap NodeView
|
||||
-> simple-mind-map runtime
|
||||
-> Leptos/Rust floating UI shell
|
||||
-> command bridge / compatPayload.patch
|
||||
```
|
||||
|
||||
本轮要修正的是 UI 与交互细节,而不是回退到以下路线:
|
||||
|
||||
- 不直接嵌入完整 Vue `lx-doc/mind-map` 应用。
|
||||
- 不恢复旧 React `MindmapBlock.tsx` 为 3000 文档页默认主链。
|
||||
- 不启动 Rust-native renderer 重写。
|
||||
- 不把 `simple-mind-map` runtime data 保存为 canonical truth。
|
||||
- 不把 TypeScript NodeView 扩写成长期 toolbar/sidebar/navigator UI 框架;长期 UI shell 继续归到 Leptos/Rust。
|
||||
|
||||
## 1. 本次取证结论
|
||||
|
||||
### 1.1 当前实现差距
|
||||
|
||||
已对照:
|
||||
|
||||
- 当前实现截图:`/mnt/Data1T/mnote/tmp/image copy 80.png`
|
||||
- KMind 目标截图:`/mnt/Data1T/mnote/tmp/image copy 81.png`
|
||||
- 隐藏 chrome 目标截图:`/mnt/Data1T/mnote/tmp/image copy 82.png`
|
||||
- 右侧详细设置栏目标截图:`/mnt/Data1T/mnote/tmp/image copy 83.png`
|
||||
|
||||
当前主要差距:
|
||||
|
||||
- Toolbar 当前视觉上仍是三行:`rust/spikes/leptos-tiptap-spike/src/lib.rs` 中 `.mnote-mindmap-command-toolbar` 与 `.mnote-mindmap-toolbar-primary` 都允许 `flex-wrap: wrap`,并且每个分组都带文字分组名,导致不可能稳定保持 KMind/simple-mind-map 的单行工具组。
|
||||
- Toolbar 当前没有真正的 `更多` 溢出模型。lx-doc `Toolbar.vue` 通过测量工具栏宽度,把可见按钮放入 `horizontalList`,把溢出按钮放入 `verticalList`,这比单纯 CSS 横向滚动更接近目标。
|
||||
- 当前没有完整全屏状态。lx-doc `Fullscreen.vue` 同时提供“全屏查看”和“全屏编辑”,并在 fullscreen change 后调用 `mindMap.resize()`。
|
||||
- 当前没有“鼠标移出后隐藏 chrome,鼠标移入不自动恢复,点击才恢复”的状态机。KMind changelog 明确提到 floating toolbar 会在特定场景自动隐藏,并推荐结合 Zen mode 获得更大的编辑视图。
|
||||
- 当前右侧栏只是窄 tab + 148px 简化 body;KMind/simple-mind-map 是“右侧触发条 + 可隐藏把手 + 约 300px 详细抽屉”,结构面板中有布局卡片,设置项密度明显更高。
|
||||
- 当前 navigator 是文字按钮和常驻搜索输入框;目标是底部右侧图标工具条,搜索按需展开,全屏、小地图、只读、缩放、鼠标行为、设置等是同一条浮动控件。
|
||||
|
||||
### 1.2 参考实现证据
|
||||
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/pages/Edit/components/Toolbar.vue`
|
||||
- `computeToolbarShow()` 根据宽度计算单行 `horizontalList` 与 `verticalList`。
|
||||
- `showMoreBtn` 显示 `更多` popover。
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/pages/Edit/components/ToolbarNodeBtnList.vue`
|
||||
- 记录 toolbar action、禁用状态、active node 规则和 runtime command。
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/pages/Edit/components/SidebarTrigger.vue`
|
||||
- 右侧触发条有 `show` 状态和 `toggleShowBtn` 隐藏把手。
|
||||
- 激活 panel 后容器从 `right: 0` 推到 `right: 305px`。
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/pages/Edit/components/NavigatorToolbar.vue`
|
||||
- 底部 navigator 是图标工具条,不是常驻表单。
|
||||
- 包含回根、搜索、鼠标行为、小地图、只读、全屏、缩放、暗色、源码、演示等入口。
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/pages/Edit/components/Fullscreen.vue`
|
||||
- 区分 `fullscreenShow` 和 `fullscreenEdit`。
|
||||
- `design/05-editor-mainline/reference-code/lx-doc/mind-map/src/config/zh.js`
|
||||
- `sidebarTriggerList` 包含 `nodeStyle/baseStyle/theme/structure/outline/shortcutKey`。
|
||||
- `design/05-editor-mainline/reference-code/kmind-plugin/README_en_US.md`
|
||||
- 明确提到 desktop floating toolbar、Zen mode、自动隐藏 UI、全局配置。
|
||||
|
||||
- [x] 已完成当前实现、截图和参考代码的首轮取证。
|
||||
|
||||
## 2. 文件职责边界
|
||||
|
||||
### 2.1 主要运行代码
|
||||
|
||||
- `rust/spikes/leptos-tiptap-spike/src/lib.rs`
|
||||
- 当前 Leptos/Rust shell、CSS、mount/unmount、toolbar/sidebar/navigator 渲染集中在此。
|
||||
- 下一阶段应优先把 mindmap shell 拆出到专门模块,避免 `lib.rs` 继续膨胀。
|
||||
- `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- 当前 action、toolbar group、sidebar panel、navigator item、context menu 的 schema 来源。
|
||||
- 下一阶段需要扩展为可表达单行 toolbar、溢出、全屏、chrome visibility、详细 panel controls 的中立 schema。
|
||||
- `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
|
||||
- UI action 到 runtime/local/kernel/compat 的映射。
|
||||
- 下一阶段需要补全 `fullscreen`、`toggleChrome`、`toggleSidebar`、`toggleSearch`、`setScale`、`setMouseBehavior` 等 local view action。
|
||||
- `wolai-frontend/src/lib/mindmap/mindmap-ui-state.ts`
|
||||
- 当前只覆盖 active node、readonly、capability、disabled state。
|
||||
- 下一阶段需要补 chrome visibility、fullscreen、sidebar open、search open、toolbar overflow、navigator active state。
|
||||
- `wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts`
|
||||
- 当前 runtime bridge。
|
||||
- 下一阶段需要确保 fullscreen/resize、search、minimap、scale input、sidebar style 写入都能通过 bridge 安全触达 runtime。
|
||||
- `wolai-frontend/src/lib/mindmap/leptos-mindmap-adapter.ts`
|
||||
- projection/command endpoint 与 adapter 初始化。
|
||||
- 下一阶段继续保持薄桥接,不承载 UI 细节。
|
||||
- `scripts/task166-mindmap-phase6-block-smoke.js`
|
||||
- 当前 Phase 6 smoke。
|
||||
- 下一阶段建议新增或扩展为 KMind parity smoke,保存对齐截图证据。
|
||||
|
||||
### 2.2 建议新增/拆分文件
|
||||
|
||||
- Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Leptos/Rust shell 组件、状态结构、事件派发。
|
||||
- Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell_style.rs` 或保留同模块内常量
|
||||
- 如果现有 CSS 继续增长,单独收口 mindmap shell 样式字符串。
|
||||
- Create: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
- 专门覆盖 toolbar 单行、全屏、chrome hide、右侧抽屉、截图对比。
|
||||
- Output: `tmp/task167-mindmap-kmind-parity-smoke/*.png`
|
||||
- 保存每个视觉验收节点截图。
|
||||
|
||||
## 3. 状态模型先行
|
||||
|
||||
后续 UI 不应继续靠 CSS 和零散按钮状态拼凑。先把 shell state 定义清楚。
|
||||
|
||||
### Task 1: 扩展 Mindmap Shell State Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-state.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-state.test.ts`
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
|
||||
- [x] 定义 `MindmapChromeVisibilityState`:
|
||||
- `visible`
|
||||
- `hiddenByPointerLeave`
|
||||
- `hiddenByToggle`
|
||||
- `hiddenByFullscreen`
|
||||
- [x] 定义 `MindmapToolbarOverflowState`:
|
||||
- `availableWidth`
|
||||
- `visibleActionIds`
|
||||
- `overflowActionIds`
|
||||
- `moreOpen`
|
||||
- [x] 定义 `MindmapFullscreenState`:
|
||||
- `mode: "none" | "canvas" | "page"`
|
||||
- `isFullscreen`
|
||||
- `target: "mindmap-root" | "document-body"`
|
||||
- [x] 定义 `MindmapSidebarState`:
|
||||
- `triggerVisible`
|
||||
- `panelOpen`
|
||||
- `activePanelId`
|
||||
- `drawerWidth`
|
||||
- `collapsedByToggle`
|
||||
- [x] 定义 `MindmapNavigatorState`:
|
||||
- `searchOpen`
|
||||
- `minimapOpen`
|
||||
- `readonly`
|
||||
- `zoomPercent`
|
||||
- `mouseBehavior`
|
||||
- [x] TS 单测覆盖:鼠标移出后为 `hiddenByPointerLeave`;再次 mouseenter 不恢复;点击画布或点击显式恢复按钮才回到 `visible`。
|
||||
- [x] TS 单测覆盖:sidebar toggle 隐藏触发条后,不会清空 `activePanelId`;再次显示后保留最近面板。
|
||||
- [x] Rust/Leptos shell options 能接收上述 state,并向 DOM 输出稳定 `data-*` 标记,供 smoke 断言。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-ui-state`
|
||||
- 2026-05-11 通过:`mindmap-ui-state.test.ts` 6 项通过;同时复核 `npm run typecheck`、`npm run build`、`cargo check`、`node scripts/build-leptos-tiptap-island.js` 均通过。
|
||||
|
||||
## 4. 单行 Toolbar 与更多菜单
|
||||
|
||||
目标:对齐 `/mnt/Data1T/mnote/tmp/image copy 81.png` 与 lx-doc `Toolbar.vue`,主 toolbar 只占一行;溢出进入 `更多` 菜单;不要回到当前 `/mnt/Data1T/mnote/tmp/image copy 80.png` 的三行状态。
|
||||
|
||||
### Task 2: 设计单行 Toolbar Schema
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.test.ts`
|
||||
|
||||
- [x] 为 toolbar action 增加 `iconKey`、`shortLabel`、`longLabel`、`priority`、`overflowGroup`。
|
||||
- [x] 移除默认渲染中强依赖的分组文字标签;分组仅作为视觉分隔线和溢出归类,不占据单行宽度。
|
||||
- [x] 把 import/export/historyRecord 规划为右侧独立 toolbar cluster,避免和节点编辑按钮互相挤压。
|
||||
- [x] 增加 `more` 虚拟 action,只有存在 `overflowActionIds` 时显示。
|
||||
- [x] 单测断言默认 toolbar 主按钮顺序与 lx-doc/KMind 对齐:undo、redo、editNode、insertSiblingAfter、deleteNode、insertChild、tag、hyperlink、note、image、icon、summary、associativeLine、formula、more。
|
||||
- [x] 单测断言 action id 不重复,且每个可见 action 都能映射到 action map。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-ui-schema mindmap-action-map`
|
||||
- 2026-05-11 通过:`mindmap-ui-schema.test.ts` 11 项通过;同轮 `mindmap-action-map.test.ts` 4 项通过。
|
||||
|
||||
### Task 3: 实现 Leptos 单行 Toolbar Layout
|
||||
|
||||
**Files:**
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `rust/spikes/leptos-tiptap-spike/src/lib.rs`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] Toolbar CSS 改为单行:`flex-wrap: nowrap`,固定按钮宽高,禁用 group 内换行。
|
||||
- [x] 顶部 toolbar 分为左/中/右 cluster:主编辑工具、导入导出/历史、更多。
|
||||
- [x] 用 `ResizeObserver` 或等价测量机制计算可见按钮与溢出按钮;窄宽度下不允许撑成第二行。
|
||||
- [x] `更多` 点击后打开浮层菜单,菜单内纵向列出溢出 action。
|
||||
- [x] `更多` 菜单失焦、点击菜单项或按 Escape 后关闭。
|
||||
- [x] 按钮视觉从“英文 icon 名 + 中文文字”修正为图标主导、短文字辅助;不得把 `palette/sliders/layout` 这类内部 icon key 作为可见正文。
|
||||
- [x] Smoke 断言 toolbar 高度不超过 72px,`toolbarRows <= 1` 或等价 DOM 断言通过。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/01-toolbar-single-row.png`,肉眼能看到 toolbar 单行而不是三行。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/design/05-editor-mainline/reference-code/leptos-tiptap/tiptap && npm run typecheck && npm run build`
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && node scripts/build-leptos-tiptap-island.js`
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage toolbar`
|
||||
- 2026-05-11 通过:桌面 toolbar `height=56`、`toolbarRows=1`;窄宽度 toolbar `height=56`、`toolbarRows=1`,并产生 `overflowActions`;Escape 后 `moreOpen=false` 且 `toolbarRows=1`。
|
||||
|
||||
## 5. 全屏与大画布模式
|
||||
|
||||
目标:至少提供 KMind/simple-mind-map 级别的全屏按钮与状态。优先做 block/canvas fullscreen,随后再补 page fullscreen。
|
||||
|
||||
### Task 4: 增加 Fullscreen Action Contract
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-state.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.test.ts`
|
||||
|
||||
- [x] 新增 action:`fullscreenCanvas`,对应 lx-doc 的“全屏查看”。
|
||||
- [x] 新增 action:`fullscreenPage`,对应 lx-doc 的“全屏编辑”,第一阶段先作为 schema/action contract 保留。
|
||||
- [x] 新增 action:`exitFullscreen`。
|
||||
- [x] `fullscreenCanvas/fullscreenPage/exitFullscreen` 均标记为 `localView`,不得产生 kernel command。
|
||||
- [x] `Fullscreen API` 不可用时,action disabled,并在 UI 有稳定 `data-disabled-reason="fullscreen-unavailable"`。
|
||||
- [x] 单测覆盖 fullscreen action 不依赖 active node,readonly 下仍可使用。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-action-map mindmap-ui-state`
|
||||
- 2026-05-11 通过:`mindmap-action-map.test.ts` 新增 fullscreen localView 合同与 readonly 断言;`mindmap-ui-state.test.ts` 断言 readonly/无 active node 下 fullscreen 仍可用。
|
||||
|
||||
### Task 5: 实现全屏按钮与 resize
|
||||
|
||||
**Files:**
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `rust/spikes/leptos-tiptap-spike/src/lib.rs`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] 底部 navigator 增加全屏图标按钮,默认显示 canvas fullscreen。
|
||||
- [x] 点击后对 `data-testid="mnote-mindmap-editor-root"` 或等价 canvas root 调用 Fullscreen API。
|
||||
- [x] 监听 `fullscreenchange`,退出时同步 shell state。
|
||||
- [x] fullscreen change 后调用 `mindMap.resize()` 或 bridge 暴露的 resize 方法,并居中或保持当前 view transform。
|
||||
- [x] 全屏时保持 toolbar/sidebar/navigator 浮动在画布上;不出现文档页滚动条错位。
|
||||
- [x] 全屏模式下 Escape 退出后,toolbar/sidebar/navigator 状态恢复到进入前状态。
|
||||
- [x] Smoke 断言点击全屏后 `document.fullscreenElement` 为 mindmap root 或其包含节点。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/02-fullscreen-canvas.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage fullscreen`
|
||||
- 2026-05-11 通过:`fullscreenElementIsRoot=true`,`shellFullscreenActive=true`;Escape 退出后 `document.fullscreenElement === null` 且 `shellFullscreenActive=false`。
|
||||
|
||||
## 6. 鼠标移出隐藏 Chrome,点击恢复
|
||||
|
||||
目标:对齐 `/mnt/Data1T/mnote/tmp/image copy 82.png`。鼠标移出后隐藏 toolbar/sidebar/navigator 等菜单;鼠标重新移入不自动恢复;用户点击画布或显式按钮才恢复。
|
||||
|
||||
### Task 6: 实现 Chrome Visibility State Machine
|
||||
|
||||
**Files:**
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `rust/spikes/leptos-tiptap-spike/src/lib.rs`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] 在 mindmap root 上监听 pointer enter/leave/click,不依赖 document 全局 hover。
|
||||
- [x] pointer leave root 后进入 `hiddenByPointerLeave`,隐藏 toolbar、sidebar drawer、sidebar trigger、navigator、minimap;count 可保留或按 KMind 目标隐藏,需在 smoke 固定判定。
|
||||
- [x] pointer enter root 不改变 `hiddenByPointerLeave`。
|
||||
- [x] 点击画布空白区或节点后恢复 `visible`。
|
||||
- [x] 点击恢复后不触发误插入、不改变 active node,除非点击目标本身就是节点选择。
|
||||
- [x] 打开 `更多` 菜单或右侧 drawer 时,pointer leave 先关闭弹层,再隐藏 chrome,避免残留孤立菜单。
|
||||
- [x] fullscreen 模式与 hidden state 组合时,退出 fullscreen 不强制显示 chrome,除非进入 fullscreen 前是 visible。
|
||||
- [x] Smoke 操作:移动鼠标到 root 外 -> 断言 toolbar/sidebar/navigator hidden;移动回 root -> 仍 hidden;点击 root -> visible。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/03-chrome-hidden-after-leave.png`。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/04-chrome-restored-after-click.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage chrome-hide`
|
||||
- 2026-05-11 通过:`sceneChromeVisibility/shellChromeVisibility` 从 `visible -> hiddenByPointerLeave -> hiddenByPointerLeave -> visible`;toolbar/sidebar/navigator 与截图状态一致。
|
||||
- 2026-05-11 补充通过:`fullscreen` stage 已覆盖 `visible -> enter fullscreen -> exit -> visible` 与 `hiddenByPointerLeave -> enter fullscreen -> exit -> hiddenByPointerLeave`,退出 fullscreen 不再强制显示 chrome。
|
||||
|
||||
## 7. 右侧详细设置栏与隐藏把手
|
||||
|
||||
目标:对齐 `/mnt/Data1T/mnote/tmp/image copy 83.png`。右侧不只是简化 tab,而是完整 panel drawer;触发条可以隐藏;隐藏后有把手可恢复。
|
||||
|
||||
### Task 7: 扩展 Sidebar Schema 到详细控件
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.test.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.test.ts`
|
||||
|
||||
- [x] Sidebar panel 增加 `kind`:`nodeStyle`、`baseStyle`、`theme`、`structure`、`outline`、`shortcutKey`、`settings`。
|
||||
- [x] Sidebar option 增加控件类型:`button`、`swatch`、`segmented`、`slider`、`numberInput`、`select`、`layoutCard`、`treeItem`、`toggle`。
|
||||
- [x] `structure` panel 定义布局卡片:逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图。
|
||||
- [x] `theme` panel 定义主题卡片,至少包含 classic、classic4/KMind-like、simple、dark 入口。
|
||||
- [x] `nodeStyle` panel 定义节点填充、文字颜色、字号、加粗、斜体、形状、边框、线条颜色、线条宽度。
|
||||
- [x] `baseStyle` panel 定义连线风格、曲线/直线、彩虹线条、背景、节点间距、概要样式。
|
||||
- [x] `outline` panel 从 projection/runtime 派生树,不允许成为第二套树真相。
|
||||
- [x] `settings` 或 `shortcutKey` panel 第一阶段可显示只读内容,但需要在 schema 中有位置。
|
||||
- [x] 单测覆盖:每个 sidebar option 要么有 actionId,要么是只读控件;可写控件必须有 compat path 或 kernel command 映射。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-ui-schema mindmap-action-map`
|
||||
- 2026-05-11 通过:`mindmap-ui-schema.test.ts` 13 项通过;同轮 `mindmap-action-map.test.ts` 5 项通过。
|
||||
|
||||
### Task 8: 实现右侧 Trigger、Drawer、隐藏把手
|
||||
|
||||
**Files:**
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `rust/spikes/leptos-tiptap-spike/src/lib.rs`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] 右侧 trigger 宽度对齐 KMind/simple-mind-map:约 60px,图标在上、文字在下。
|
||||
- [x] trigger active 状态使用蓝色强调条或蓝色文字,不再用整块按钮边框作为主要视觉。
|
||||
- [x] drawer 打开时宽度约 300px,右侧固定,覆盖画布,不挤压 runtime。
|
||||
- [x] drawer 顶部包含 panel 标题和关闭按钮。
|
||||
- [x] trigger 左侧或 drawer 边缘提供隐藏把手,点击后隐藏整个 trigger。
|
||||
- [x] 隐藏后保留一个小把手;鼠标移入不自动展开,点击把手才恢复。
|
||||
- [x] 点击 active trigger:若 drawer 已打开同一 panel,则关闭 drawer;若点击不同 panel,则切换 drawer 内容。
|
||||
- [x] 结构 panel 的布局卡片使用稳定尺寸,不能因文字或 hover 导致布局跳动。
|
||||
- [x] 右侧 drawer 打开时,底部 navigator 与 drawer 不重叠;必要时 navigator 向左避让或保持在 drawer 左侧。
|
||||
- [x] Smoke 断言:打开 `structure` panel 后可见标题“结构”,可见至少 6 个布局卡片,存在关闭按钮和隐藏把手。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/05-sidebar-structure-drawer.png`。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/06-sidebar-hidden-handle.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage sidebar`
|
||||
- 2026-05-11 通过:`drawerWidth=300`,`title=结构`,`structureCardCount=6`;close/hide/restore 均通过。
|
||||
|
||||
### Task 9: 打通右侧设置到 Runtime/Kernel/Compat
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/leptos-mindmap-adapter.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-command-diff.ts`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] `setLayout` 走 kernel command,并刷新 adapter projection。
|
||||
- [x] `setTheme` 走 kernel command,并刷新 adapter projection。
|
||||
- [x] 节点样式类 action 先走 runtime 预览,再以 `compatPayload.patch` 保存样式字段。
|
||||
- [x] 基础样式类 action 先走 runtime config/themeConfig,再以 `compatPayload.patch` 保存无法语义化字段。
|
||||
- [x] option 点击失败时显示 `command_failed` 或等价错误层,不能只改变 UI 本地状态。
|
||||
- [x] Smoke 点击结构卡片后 reload,断言 projection layout 保持。
|
||||
- [x] Smoke 点击主题卡片后 reload,断言 theme 保持。
|
||||
- [x] Smoke 点击节点样式后 reload,断言 compatPayload patch 可见或节点样式保留。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-action-map simple-mind-map-bridge`
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage sidebar-actions`
|
||||
- 2026-05-11 通过:`sidebarActions.beforeReload/afterReload` 均为 `layout=mindMap`、`theme=dark`、`rootFillColor=#dbeafe`;同时修正 metadata-only 本地 `put` 误分流,统一回到 `mindmap.command.apply -> mnote-web server-side apply -> mindmaps.put` 主链。
|
||||
|
||||
## 8. 底部 Navigator 图标化与 MiniMap/Search
|
||||
|
||||
目标:对齐 KMind/simple-mind-map 底部右侧工具条。搜索不应长期占用输入框宽度;全屏、只读、小地图、缩放应统一成 icon/action。
|
||||
|
||||
### Task 10: Navigator Schema 与 UI 重排
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] Navigator item 改为图标按钮为主:回根、搜索、小地图、只读/编辑、全屏、缩小、缩放值、放大、设置。
|
||||
- [x] 搜索默认只显示图标;点击后展开输入框;再次点击或 Escape 收起。
|
||||
- [x] 缩放值允许输入百分比;非法输入恢复上一次有效值。
|
||||
- [x] 小地图打开后显示在 navigator 上方或右下安全位置,不遮挡 drawer。
|
||||
- [x] 只读状态显示明确 active 状态,并同步 `mindMap.setMode("readonly" | "edit")`。
|
||||
- [x] Count 固定在左下角,文本保持简短:`字数 17`、`节点 4`。
|
||||
- [x] Smoke 断言默认 navigator 没有常驻搜索输入框;点击搜索后输入框出现。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/07-navigator-icons.png`。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/08-search-expanded.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage navigator`
|
||||
- 2026-05-11 通过:默认 `searchOpen=false`、无常驻输入框;展开搜索后截图已保存;最终 `minimapOpen=true`、`readonly=true`、非法缩放输入恢复为 `100%`。
|
||||
|
||||
## 9. Context Menu 与快捷键基础对齐
|
||||
|
||||
目标:当前右键菜单已有第一阶段能力,但要对齐 KMind/simple-mind-map 的禁用状态、显示位置和隐藏行为。
|
||||
|
||||
### Task 11: Context Menu Parity
|
||||
|
||||
**Files:**
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-ui-schema.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts`
|
||||
- Modify/Create: `rust/spikes/leptos-tiptap-spike/src/mindmap_shell.rs`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] 区分节点右键菜单与画布右键菜单。
|
||||
- [x] 节点菜单包含插入子节点、插入同级、删除、展开/收起、概要、关联线、复制文本。
|
||||
- [x] 画布菜单包含回根、适应画布、搜索、只读切换、显示菜单。
|
||||
- [x] root/generalization 等特殊节点禁用不适用 action。
|
||||
- [x] 右键菜单打开时 pointer leave 不立即隐藏 chrome;点击菜单项、Escape、点击画布空白后关闭。
|
||||
- [x] 菜单位置不能超出 mindmap root 边界。
|
||||
- [x] Smoke 断言节点右键菜单与画布右键菜单的 action 列表不同。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/09-node-context-menu.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage context-menu`
|
||||
- 2026-05-11 通过:节点菜单仅包含 node actions,root 节点 `insertSiblingAfter/deleteNode` 为 disabled;画布菜单为 `centerRoot/fitView/search/readonly/showMenu`;pointer leave 后菜单与 chrome 保持可见,Escape 后关闭。
|
||||
|
||||
## 10. KMind 视觉基线
|
||||
|
||||
目标:不要只实现功能按钮,要让默认观感接近 KMind/simple-mind-map 工作台。
|
||||
|
||||
### Task 12: 默认 Theme/Layout 视觉对齐
|
||||
|
||||
**Files:**
|
||||
- Modify: `rust/crates/bridge-runtime/src/lib.rs`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts`
|
||||
- Modify: `wolai-frontend/src/lib/mindmap/mindmap-projection.ts`
|
||||
- Modify: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
|
||||
- [x] 默认 root 节点使用红色/橙红色背景、白色粗体文字,接近 KMind 截图。
|
||||
- [x] 默认二级节点使用蓝色背景、白色文字。
|
||||
- [x] 分支主题使用蓝色文字,概要括号线使用红色或主题强调色。
|
||||
- [x] 默认 layout 选择与 KMind 截图一致的右向逻辑结构。
|
||||
- [x] 连线位置不再错位;节点中心线、概要括号线和子节点垂直中心对齐。
|
||||
- [x] Smoke 读取 canvas/svg 或截图像素,确认节点和连线非空、可见、位置不重叠。
|
||||
- [x] Screenshot:`tmp/task167-mindmap-kmind-parity-smoke/10-kmind-theme-baseline.png`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js --stage theme`
|
||||
- 2026-05-11 通过:runtime 默认 `layout=logicalStructure`、`theme=default`,`themeConfig` 生效为 root 红底白字、二级蓝底白字、分支蓝字、红色概要线;smoke 读取到 `nodeCount=5`、`pathCount=9`、`overlaps=0`。
|
||||
|
||||
## 11. 验证体系
|
||||
|
||||
### Task 13: 新增 KMind Parity Smoke
|
||||
|
||||
**Files:**
|
||||
- Create: `scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
- Output: `tmp/task167-mindmap-kmind-parity-smoke/result.json`
|
||||
- Output: `tmp/task167-mindmap-kmind-parity-smoke/*.png`
|
||||
|
||||
- [x] 启动前检查 `http://127.0.0.1:3000/` 可用,不强制重启已存在服务。
|
||||
- [x] 使用默认测试账号登录。
|
||||
- [x] 新建或打开临时文档,插入 mindmap block。
|
||||
- [x] 断言 `simple-mind-map` runtime 存在,canvas rect 非零。
|
||||
- [x] 断言 Leptos/Rust shell 来源为 `data-ui-shell-source="leptos-rust-shell"`。
|
||||
- [x] 断言 toolbar 单行。
|
||||
- [x] 断言 fullscreen 按钮存在并可进入/退出。
|
||||
- [x] 断言 pointer leave 隐藏 chrome、pointer enter 不恢复、click 恢复。
|
||||
- [x] 断言右侧 drawer 可打开、关闭、隐藏 trigger、点击把手恢复。
|
||||
- [x] 断言 navigator 搜索按需展开,小地图可打开。
|
||||
- [x] 断言 reload 后 layout/theme/view/compat patch 不丢。
|
||||
- [x] 输出 `result.json`,至少包含 `ok`、`baseUrl`、`documentId`、`mindmapId`、`toolbarRows`、`fullscreenOk`、`chromeHideOk`、`sidebarDrawerOk`、`navigatorOk`、`screenshots`。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task167-mindmap-kmind-parity-smoke.js`
|
||||
- 2026-05-11 通过:默认执行 `all` stage,`result.json` 输出 `ok/baseUrl/documentId/mindmapId/toolbarRows/fullscreenOk/chromeHideOk/sidebarDrawerOk/navigatorOk/contextMenuOk/themeOk/reloadOk/screenshots`,截图 01-10 全量生成。
|
||||
|
||||
### Task 14: 回归现有 Phase 6 Smoke
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/task166-mindmap-phase6-block-smoke.js` only if needed
|
||||
|
||||
- [x] `task167` 新增后,`task166` 继续保持 runtime/projection/command/reload 基础验证,不与视觉 parity 重复过多。
|
||||
- [x] `task166` 不因 toolbar 单行、drawer、fullscreen 变化而误判旧 testid 缺失。
|
||||
- [x] `task166` 与 `task167` 输出目录分离。
|
||||
- [x] 验证:`cd /mnt/Data1T/mnote && node scripts/task166-mindmap-phase6-block-smoke.js`
|
||||
- 2026-05-11 通过:`task166` 保持基础链路验证,兼容 navigator 搜索折叠与 zoom 输入框;`result.json` 输出在 `tmp/task166-mindmap-phase6-block-smoke/`,与 `task167` 分离。
|
||||
|
||||
## 12. 执行顺序建议
|
||||
|
||||
建议按以下顺序执行,避免先做 CSS 后返工状态模型:
|
||||
|
||||
1. Task 1:状态模型。
|
||||
2. Task 2-3:单行 toolbar 与更多菜单。
|
||||
3. Task 4-5:全屏。
|
||||
4. Task 6:chrome hide/click restore。
|
||||
5. Task 7-9:右侧详细设置栏与持久化。
|
||||
6. Task 10:navigator 图标化。
|
||||
7. Task 11:context menu。
|
||||
8. Task 12:视觉主题与连线对齐。
|
||||
9. Task 13-14:smoke 与回归。
|
||||
|
||||
每次实现完成后,至少保存对应截图,再勾选节点。
|
||||
|
||||
## 13. 阶段验收标准
|
||||
|
||||
本 checklist 视为完成时,必须同时满足:
|
||||
|
||||
- [x] `/mnt/Data1T/mnote/tmp/task167-mindmap-kmind-parity-smoke/01-toolbar-single-row.png` 显示单行 toolbar。
|
||||
- [x] `/mnt/Data1T/mnote/tmp/task167-mindmap-kmind-parity-smoke/02-fullscreen-canvas.png` 显示全屏画布与浮动 chrome。
|
||||
- [x] `/mnt/Data1T/mnote/tmp/task167-mindmap-kmind-parity-smoke/03-chrome-hidden-after-leave.png` 显示鼠标移出后的隐藏状态。
|
||||
- [x] `/mnt/Data1T/mnote/tmp/task167-mindmap-kmind-parity-smoke/05-sidebar-structure-drawer.png` 显示详细右侧结构面板。
|
||||
- [x] `/mnt/Data1T/mnote/tmp/task167-mindmap-kmind-parity-smoke/07-navigator-icons.png` 显示图标化底部 navigator。
|
||||
- [x] `node scripts/task166-mindmap-phase6-block-smoke.js` 通过。
|
||||
- [x] `node scripts/task167-mindmap-kmind-parity-smoke.js` 通过。
|
||||
- [x] `pnpm test -- mindmap-ui-schema mindmap-action-map mindmap-ui-state simple-mind-map-bridge` 通过。
|
||||
- [x] `npm run typecheck && npm run build` 在 `design/05-editor-mainline/reference-code/leptos-tiptap/tiptap` 通过。
|
||||
- [x] Rust `page.body.save` 主链保存 `mindmap` block 后,`/api/documents/content` 读回仍为 `type: "mindmap"`,不再退化成 `paragraph`。
|
||||
- 2026-05-12 通过:新增 `cargo test -p core-protocol preserves_mindmap_paragraph_placeholder`、`cargo test -p core-protocol tiptap_mindmap_placeholder_round_trip_preserves_mindmap_attrs`、`cargo test -p bridge-runtime documents_save_command_plan_preserves_mindmap_placeholder`;同时在真实 `http://127.0.0.1:3000` 上手工验证 `content[0].type === "mindmap"` 且 `pageSubtree.subtree.nodes[1].metadata.blockType === "mindmap"`。
|
||||
- [x] `GET /documents/{documentId}?workspaceId=...` 对有效导图文档返回 HTML 页面壳,而不是 `documents.content.get 未返回文档内容` 的错误 JSON。
|
||||
- 2026-05-12 通过:在前台 `mnote-web` 实例上重新创建临时导图文档后复测,返回 `200 text/html; charset=utf-8`,首屏 HTML 正常包含 document shell。
|
||||
- [x] 3000 文档页默认主链仍为 Leptos/Rust shell,不加载旧 React mindmap 主链。
|
||||
|
||||
## 14. 明确延期项
|
||||
|
||||
以下能力可参考 KMind,但不进入本 checklist 的完成条件:
|
||||
|
||||
- 多根节点。
|
||||
- MOC 模式。
|
||||
- 思源块预览、镜像块、PDF 标注跳转。
|
||||
- Freemind/XMind 全量导入导出。
|
||||
- 主题设计器与主题分享。
|
||||
- 完整快捷键自定义配置。
|
||||
- 演示模式。
|
||||
- Rust-native renderer。
|
||||
@@ -0,0 +1,362 @@
|
||||
# 9 [process] SiYuan 参考边界与可借鉴能力 v1
|
||||
|
||||
> 更新时间:2026-05-11
|
||||
>
|
||||
> 上位依据:
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
|
||||
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.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`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md`
|
||||
>
|
||||
> 外部参考:
|
||||
> - `https://github.com/siyuan-note/siyuan`
|
||||
> - `https://raw.githubusercontent.com/siyuan-note/siyuan/master/README_zh_CN.md`
|
||||
> - `https://raw.githubusercontent.com/siyuan-note/siyuan/master/API_zh_CN.md`
|
||||
>
|
||||
> 本稿定位:
|
||||
> - 本稿是 `mnote` 的参考与借鉴边界稿。
|
||||
> - 本稿不是新的上位架构来源。
|
||||
> - 本稿不覆盖 `01-05` 当前主线优先级。
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份稿只回答一个问题:
|
||||
|
||||
> **思源笔记对当前 `mnote` 主线,哪些地方值得参考,哪些地方不应照搬。**
|
||||
|
||||
当前结论固定为:
|
||||
|
||||
> **思源更适合作为产品能力与交互参考,不适合作为 `mnote` 长期架构模板。**
|
||||
|
||||
原因不是思源做得不成熟,而是两边长期目标不同:
|
||||
|
||||
- 思源更接近 `本地优先工作空间 + 块文档 + Go kernel + TS/Electron 产品壳`
|
||||
- `mnote` 当前主线是 `tree-first graph kernel + Rust 持有语义 + Page Aggregate / Tree Realtime / Tree Command 收口`
|
||||
|
||||
因此,后续引用思源时必须先区分:
|
||||
|
||||
1. 是在参考产品层能力
|
||||
2. 还是在引入架构层真相
|
||||
|
||||
只有第一类默认允许,第二类默认不允许。
|
||||
|
||||
---
|
||||
|
||||
## 2. 先给结论
|
||||
|
||||
### 2.1 思源值得参考的层级
|
||||
|
||||
思源当前最值得参考的是:
|
||||
|
||||
- 块级引用、双向链接、反链、图谱、大纲这一整套产品能力的配套闭环
|
||||
- 属性 / 数据库视图的用户心智、操作颗粒度和投影形态
|
||||
- 本地优先工作区的数据组织、导入导出、资源目录和恢复路径
|
||||
- 大体量单机笔记产品的交互密度、功能面排布和“一个能力带一圈配套能力”的产品完成度
|
||||
- 编辑器相关的局部交互细节,以及导图插件这一类挂件的集成方式
|
||||
|
||||
### 2.2 思源不应成为 `mnote` 的长期架构模板
|
||||
|
||||
思源当前不应被拿来直接替代或覆盖:
|
||||
|
||||
- `tree-first graph kernel`
|
||||
- `Rust kernel` 的语义主导权
|
||||
- `mnote-web` 作为 `3000` 主执行面
|
||||
- `Page Aggregate` 作为页面域单一真相收口方向
|
||||
- `tree.*` 正式命令面与 `tree events` realtime 主链
|
||||
|
||||
一句话收口:
|
||||
|
||||
> **思源可以提供“功能长什么样”的答案,但不能替代 `mnote` 对“系统真相由谁持有”的既定答案。**
|
||||
|
||||
---
|
||||
|
||||
## 3. 为什么会觉得像思源
|
||||
|
||||
用户会感觉当前实现和思源接近,并不是错觉,主要有下面这些原因:
|
||||
|
||||
- 都是块式文档体验,而不是传统线性文档页
|
||||
- 都强调页面树、块引用、双向链接、嵌入、导图或挂件类能力
|
||||
- 都不是纯 Markdown 文件列表产品,而是更接近“知识对象 + 多视图”的产品
|
||||
- 都会同时出现页面、块、资源、搜索、反链、图谱、数据库视图这些能力面
|
||||
|
||||
但相似主要停留在产品表层,不等于底层事实源一致。
|
||||
|
||||
当前两边关键差异是:
|
||||
|
||||
- 思源偏 `workspace/data + .sy + API + 本地工作区`
|
||||
- `mnote` 偏 `kernel truth + projection + command + realtime stream`
|
||||
|
||||
这条差异决定了:
|
||||
|
||||
> **参考思源时,应优先借它的产品形态,不要把它的数据真相层和 API 哲学直接搬进来。**
|
||||
|
||||
---
|
||||
|
||||
## 4. 思源当前可见的能力面
|
||||
|
||||
从公开仓库、README 和 API 可见,思源不是“只有一个块编辑器”,而是已经形成下面这些稳定能力面:
|
||||
|
||||
- 块:插入、更新、删除、移动、折叠、展开、块引用
|
||||
- 属性:块属性读写
|
||||
- SQL:查询与事务刷新
|
||||
- 属性视图 / 数据库视图:表格、看板、画廊等
|
||||
- 大纲、反链、图谱、搜索
|
||||
- 工作空间、文件树、资源文件、模板、插件、代码片段
|
||||
- 历史、同步、导出、剪藏、闪卡、OCR、AI、移动端与 Docker
|
||||
|
||||
这说明思源真正有参考价值的不是某个单点组件,而是:
|
||||
|
||||
> **当一个系统把“块”作为核心对象后,周边需要跟着长出来的整圈配套能力。**
|
||||
|
||||
---
|
||||
|
||||
## 5. 可直接借鉴的部分
|
||||
|
||||
## 5.1 块级引用不是单点功能,而是一整圈产品能力
|
||||
|
||||
思源把块级引用、双向链接、反链、搜索、图谱、大纲做成了互相咬合的一组能力。
|
||||
|
||||
对 `mnote` 的启发不是“做一个 `((block))` 就够了”,而是:
|
||||
|
||||
- 一旦有块引用,就应该有稳定的引用目标解析
|
||||
- 一旦有引用目标解析,就应该有反链和搜索投影
|
||||
- 一旦有反链和搜索投影,就应该考虑页面页头、阅读态、导图、AI 上下文如何共享这一组对象语义
|
||||
|
||||
这与当前 `mnote` 主线是相容的,因为这些都应该继续落到 Rust projection family,而不是前端各自维护一份块真相。
|
||||
|
||||
## 5.2 属性视图 / 数据库视图值得作为单独对象域评估
|
||||
|
||||
思源已经证明:
|
||||
|
||||
- 属性不是补充字段
|
||||
- 数据库视图也不是“在文档里画个表格”就结束
|
||||
|
||||
它更接近:
|
||||
|
||||
- 对象属性定义
|
||||
- 多视图投影
|
||||
- 排序 / 分组 / 过滤 / relation / rollup 一组相关语义
|
||||
|
||||
对 `mnote` 的启发是:
|
||||
|
||||
> **如果后续做属性视图,不应只把它当成编辑器里的一个特殊块,而应评估它是否需要独立的 kernel object / projection / command family。**
|
||||
|
||||
这里可以借思源的产品心智,但不要直接借它的存储组织。
|
||||
|
||||
## 5.3 本地工作区组织与导入导出心智值得参考
|
||||
|
||||
思源 README 明确公开了工作空间 `data/` 下的目录组织,例如:
|
||||
|
||||
- `assets`
|
||||
- `templates`
|
||||
- `snippets`
|
||||
- `plugins`
|
||||
- `public`
|
||||
- 文档与笔记本目录
|
||||
|
||||
这对 `mnote` 的参考价值主要在用户体验层:
|
||||
|
||||
- 本地导出时,哪些内容算“工作区资产”
|
||||
- 用户如何理解模板、资源、插件与公开文件
|
||||
- 发生故障时,用户该如何备份与迁移
|
||||
|
||||
这里适合作为:
|
||||
|
||||
- 本地文件夹体验
|
||||
- 导入导出 UX
|
||||
- 恢复与备份说明
|
||||
|
||||
的参考,不适合作为新的 canonical source。
|
||||
|
||||
## 5.4 导图与插件化挂件有参考意义
|
||||
|
||||
当前 `mnote` 已经在 `design/05-editor-mainline/reference-code/` 下保留了 `siyuan-kmind-plugin` 参考代码,这个方向是合理的。
|
||||
|
||||
对导图线的正确借法是:
|
||||
|
||||
- 参考思源插件 / KMind 的 UI、行为、挂件边界
|
||||
- 参考其与文档块、引用预览、搜索、资源插入的交互方式
|
||||
- 不复制它的思源宿主耦合
|
||||
|
||||
这与当前 `Phase 6` 已经明确的口径一致:
|
||||
|
||||
> **可以参考 KMind 的 UI 与行为,但不要复制它的思源插件耦合。**
|
||||
|
||||
## 5.5 “成熟单机笔记产品的密度”本身值得参考
|
||||
|
||||
思源的一个重要价值,不是某个 API,而是它已经证明:
|
||||
|
||||
- 用户愿意接受高密度功能面
|
||||
- 页面 / 树 / 搜索 / 反链 / 数据库 / 插件 / 导出 / 历史可以共存
|
||||
- 关键不在于功能少,而在于对象边界和入口是否清晰
|
||||
|
||||
这对 `mnote` 的启发是:
|
||||
|
||||
> **后续不需要因为主线在收口,就把能力面理解成必须长期极简;真正要避免的是语义散乱,而不是能力丰富。**
|
||||
|
||||
---
|
||||
|
||||
## 6. 不建议照搬的部分
|
||||
|
||||
## 6.1 不照搬 `.sy + 工作空间文件` 作为系统真相层
|
||||
|
||||
思源的数据组织很适合它自己的本地优先单机模型,但 `mnote` 当前已经明确:
|
||||
|
||||
- `tree-first graph kernel` 是长期对象真相层
|
||||
- `Convex` 继续保留为当前存储 / 实时 / 文件协作底座
|
||||
- `mnote-web` 与 Rust kernel 持有主执行语义
|
||||
|
||||
因此后续即使增加本地文件夹能力,也不能把:
|
||||
|
||||
- 工作空间目录结构
|
||||
- 导出文件形状
|
||||
- 调试缓存
|
||||
|
||||
误提升为新的系统真相层。
|
||||
|
||||
## 6.2 不把 SQL 暴露成长期核心产品契约
|
||||
|
||||
思源公开提供 SQL 查询接口,这很适合本地单机高级用户,但对 `mnote` 有明显风险:
|
||||
|
||||
- 会绕开 `projection` 与 `command` 边界
|
||||
- 会破坏页面域和树域单一真源收口
|
||||
- 会让 AI、CLI、前端和脚本各自形成第二套数据读取口径
|
||||
|
||||
所以对 `mnote` 来说,正确借法是:
|
||||
|
||||
- 借“高级查询能力”这个需求
|
||||
- 不借“把底层 SQL 直接暴露为长期主接口”这个做法
|
||||
|
||||
若未来需要高级查询,应优先考虑:
|
||||
|
||||
- kernel query family
|
||||
- projection query endpoint
|
||||
- 受控 DSL
|
||||
|
||||
而不是直接给业务面开放底层 SQL。
|
||||
|
||||
## 6.3 不把前端运行时做成第二语义中心
|
||||
|
||||
思源前端 `protyle` 与周边 TS runtime 很大,说明它有相当一部分产品组织与交互复杂度留在前端。
|
||||
|
||||
这对当前 `mnote` 不是该追的方向,因为你们当前最重要的事是:
|
||||
|
||||
- 继续收口 `Page Aggregate`
|
||||
- 继续收口 `tree command`
|
||||
- 继续收口 `tree realtime`
|
||||
|
||||
因此不应因为参考思源,就重新把:
|
||||
|
||||
- 标题语义
|
||||
- 页面设置语义
|
||||
- 引用解析语义
|
||||
- 数据库视图真相
|
||||
|
||||
重新扩散到前端壳层或 compat 层。
|
||||
|
||||
## 6.4 不默认接受它的单用户 / 本地优先假设
|
||||
|
||||
思源大量设计天然偏:
|
||||
|
||||
- 单机优先
|
||||
- 工作空间文件优先
|
||||
- 用户直接接触本地数据目录
|
||||
|
||||
而 `mnote` 当前仍保留:
|
||||
|
||||
- Convex 自托管底座
|
||||
- realtime 协作链
|
||||
- Rust Web `3000` 主入口
|
||||
|
||||
因此参考思源时必须先问:
|
||||
|
||||
> **这个能力是在单用户前提下成立,还是在当前 `mnote` 的协作 / realtime 前提下也能成立。**
|
||||
|
||||
只在前者成立的方案,默认不能直接进入主线。
|
||||
|
||||
---
|
||||
|
||||
## 7. 对 `mnote` 的建议落点
|
||||
|
||||
| 参考主题 | 是否建议借鉴 | 推荐落点 |
|
||||
| --- | --- | --- |
|
||||
| 块级引用 / 双向链接 / 反链 | 建议 | Rust query / projection family,统一到 page/tree 相关投影 |
|
||||
| 属性视图 / 数据库视图 | 建议 | 先做对象域评估,再决定 command / projection 边界 |
|
||||
| 图谱 / 大纲 / 搜索 | 建议 | 作为引用体系的配套视图,而不是孤立功能 |
|
||||
| 工作区导出 / 资源目录 / 模板心智 | 建议 | 本地文件夹、导出导入、恢复与帮助文档 |
|
||||
| 导图插件交互 | 建议 | 继续作为 `leptos-mindmap` 的参考行为层 |
|
||||
| `.sy` 文档真相层 | 不建议 | 不进入 `mnote` 主线 |
|
||||
| SQL 直接开放给产品层 | 不建议 | 改用 kernel query / projection / DSL |
|
||||
| 前端重语义运行时 | 不建议 | 继续把长期语义收口到 Rust kernel |
|
||||
| 单机假设下的 API 组织 | 谨慎 | 只借需求,不借真相层与执行面 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 当前优先级下的使用方式
|
||||
|
||||
考虑到当前 `mnote` 的第一优先级仍然是:
|
||||
|
||||
1. `Page Aggregate`
|
||||
2. `Tree Command Cutover`
|
||||
3. `Tree Realtime Event Stream`
|
||||
|
||||
所以本稿的执行口径应固定为:
|
||||
|
||||
### 8.1 允许用思源来校准需求
|
||||
|
||||
例如:
|
||||
|
||||
- 块引用之后,用户预期还需要什么
|
||||
- 反链页、图谱页、搜索页应该有哪些最小能力
|
||||
- 属性视图第一版做成什么形态才不失真
|
||||
- 导出与本地资源管理要不要显式工作区概念
|
||||
|
||||
### 8.2 不允许用思源来打断当前收口顺序
|
||||
|
||||
例如不能因为思源已有某个能力,就跳过:
|
||||
|
||||
- `Page Aggregate` 的主链闭环
|
||||
- `tree.*` 命令统一
|
||||
- `/api/tree/events` live cache 收口
|
||||
|
||||
否则会把参考稿误用成新的需求插队入口。
|
||||
|
||||
### 8.3 参考思源时优先写成“能力映射”,不要写成“照搬实现”
|
||||
|
||||
后续如果再新增思源相关设计稿,优先采用:
|
||||
|
||||
- “思源某能力对 `mnote` 的需求映射”
|
||||
- “思源某交互对 `mnote` 的行为对照”
|
||||
|
||||
而不是:
|
||||
|
||||
- “把思源 API / 数据格式搬进来”
|
||||
- “按思源目录结构重建 `mnote`”
|
||||
|
||||
---
|
||||
|
||||
## 9. 当前建议的后续拆分
|
||||
|
||||
如果后续继续使用思源作为参考,建议只沿下面几个方向继续出稿:
|
||||
|
||||
1. `块引用 / 反链 / 图谱 / 搜索` 的能力映射稿
|
||||
2. `属性视图 / 数据库视图` 的对象域评估稿
|
||||
3. `本地工作区 / 导入导出 / 资源目录` 的 UX 参考稿
|
||||
4. `导图插件 / 挂件 / 引用预览` 的行为对照稿
|
||||
|
||||
不建议出的稿:
|
||||
|
||||
1. “按思源重写 `mnote` 数据层”
|
||||
2. “引入思源式 SQL 主接口”
|
||||
3. “以思源前端 runtime 替代当前 Rust 主线”
|
||||
|
||||
---
|
||||
|
||||
## 10. 一句话收口
|
||||
|
||||
当前 `mnote` 对思源的正确态度应固定为:
|
||||
|
||||
> **把思源当成成熟块知识产品的参考样本库,重点吸收它的能力面、交互完成度和配套闭环;但 `mnote` 的长期事实源、命令面、projection 与 realtime 主链,继续严格沿 `tree-first graph kernel` 推进。**
|
||||
@@ -0,0 +1,119 @@
|
||||
# Rust Kernel / Web 实现偏差审查
|
||||
|
||||
## 范围
|
||||
|
||||
本次只审查 Rust kernel / protocol / bridge-runtime / mnote-web / storage-convex-bridge 与当前设计主线的一致性。
|
||||
|
||||
重点对照设计:
|
||||
|
||||
- `ARCHITECTURE.md`
|
||||
- `design/01-05-current-priority-overview.md`
|
||||
- `design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
|
||||
- `design/01-tree-first-graph-kernel/process/1-1-tree-first-graph-kernel-checklist-v2.md`
|
||||
- `design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
|
||||
- `design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
|
||||
- `design/03-rust-web/process/3-1-rust-web-long-term-checklist-v2.md`
|
||||
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
- `design/03-rust-web/process/3-15-runtime-fallback-retirement-checklist-v1.md`
|
||||
- `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
|
||||
重点代码:
|
||||
|
||||
- `rust/crates/core-protocol/`
|
||||
- `rust/crates/core-domain/`
|
||||
- `rust/crates/bridge-runtime/`
|
||||
- `rust/crates/mnote-web/`
|
||||
- `rust/crates/storage-convex-bridge/`
|
||||
|
||||
## 结论
|
||||
|
||||
Rust kernel / protocol / bridge-runtime / mnote-web 的主干方向与当前设计基本一致:`core-protocol` 已有统一 kernel node / edge / projection / page aggregate 协议;`bridge-runtime` 已能执行 kernel query / command / projection;`mnote-web` 已持有 3000 主入口、文档页 shell、`/api/page-aggregate/:id`、tree command、`/api/tree/events`、Search shell 等关键接缝;`storage-convex-bridge` 明确把 `tree.*` / `page.*` 等语义命令映射到底层 Convex mutation。
|
||||
|
||||
主要偏差不是“Rust 主线没有落地”,而是当前仍存在几类收口缺口:
|
||||
|
||||
1. 部分 Rust Web 主路径仍会静默合成 fallback / fixture 数据,和 `Runtime Fallback 退场` 的激进口径不完全一致。
|
||||
2. Page Aggregate 对外标记为 `KernelProjection`,但 Rust Web 仍先取 `documents.meta/content` 再在 runtime 内拼成聚合;这是合理过渡实现,但“单一 kernel 真源”口径容易被写得过满。
|
||||
3. Tree realtime 已有正式 SSE 主链,但实现仍偏轮询 bridge overview,WS 仍是 snapshot/resync 骨架,尚未成为完整实时主链。
|
||||
4. Legacy / compat 能力默认大多关闭,但配置位、proxy 辅助函数和兼容源枚举仍在,设计文档中“全部退场”的勾选状态可能偏乐观。
|
||||
|
||||
## 关键发现表格
|
||||
|
||||
| 编号 | 分类 | 严重度 | 发现 | 证据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| RK-01 | 未完成 | 中 | Kernel projection 仍主要从 `sidebar.dataset.list` 这一份 Convex 数据集构建,广义 node pool / reference edge / summary/index 真相层尚未完全独立。 | `rust/crates/mnote-web/src/routes/snapshot_support.rs:27` 定义 `sidebar.dataset.list`;`:93` 先加载 sidebar dataset 再执行 `kernel.project_view`;`rust/crates/bridge-runtime/src/lib.rs:8010` 后续由 sidebar 数据构建 subtree;`:8111` 再转为 projection。 |
|
||||
| RK-02 | 实现偏差 / 风险 | 高 | Rust Web 主路径仍存在静默 fallback:workspace shell 在 projection 加载失败时无条件合成最小 workspace/documents;sidebar/filetree 在 `allow_dev_fixtures` 时合成开发数据;Search 在 Convex query 失败时返回内置搜索数据。这与 `3-15-runtime-fallback-retirement` 的“默认不再 fallback”口径冲突。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2379` 到 `:2401` 无条件 fallback 最小 dataset;`:2432` 到 `:2461` 合成 sidebar dev dataset;`:2493` 到 `:2519` 合成 filetree dev dataset;`rust/crates/mnote-web/src/routes/search.rs:233` 到 `:247` 搜索失败后走 `fallback_search_dataset`。 |
|
||||
| RK-03 | 方向变化 / 文档滞后 | 中 | Page Aggregate 已由 Rust Web 暴露正式 route,但实现仍是先读 `documents.meta/content`,再把 joined data 交给 `page.aggregate.get` 生成 projection。代码对外 source 标成 `KernelProjection`,但底层仍是 meta/content join 的迁移形态;需要文档明确这是 Rust runtime adapter,而非完整 kernel storage 真源。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2282` 到 `:2315` 先读 meta/content 再执行 `page.aggregate.get`;`rust/crates/bridge-runtime/src/lib.rs:8246` 到 `:8253` 将 source 传为 `PageAggregateSource::KernelProjection`;`:5303` 到 `:5324` 从 meta/content 中抽取 title/content/revision。 |
|
||||
| RK-04 | 未完成 | 中 | Tree realtime 正式 route 已存在,但当前 SSE 仍通过 polling `bridge.workspace.overview` 生成 snapshot/delta/resync;WS 只发送初始 snapshot,并只支持客户端请求 resync,不是完整主实时链路。 | `rust/crates/mnote-web/src/routes/sse.rs:51` 到 `:91` 循环 sleep/poll overview 后生成 delta;`rust/crates/mnote-web/src/routes/ws.rs:32` 发送 snapshot,`:40` 到 `:80` 只处理 resync 或 unsupported ack。 |
|
||||
| RK-05 | 风险 / 文档滞后 | 低 | Legacy Next compat 默认关闭,但代码仍保留 env-gated proxy 能力、Next proxy 辅助函数和 compat 配置位。若文档继续写“legacy compat 已删除”,会与代码事实不一致;若保留,应写成显式调试/迁移边界。 | `rust/crates/mnote-web/src/app.rs:42` 到 `:54` 仍读取 `MNOTE_WEB_LEGACY_NEXT_BASE_URL` / `MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT`;`rust/crates/mnote-web/src/routes/gateway.rs:86` 到 `:87` auth API 可转 legacy proxy;`:402` 到 `:499` legacy proxy 实现仍在;`rust/crates/mnote-web/src/routes/documents.rs:153` 当前 `should_proxy_via_next` 返回 false,但 Next proxy helper 仍保留。 |
|
||||
| RK-06 | 实现一致 | 低 | Tree command cutover Stage 2 的长期命名面已在 Rust route / bridge / storage mapping 中落地,`documents.*` 仍作为 alias/底层 Convex mutation 名称存在,和 Stage 2A/2B 过渡口径基本一致。 | `rust/crates/storage-convex-bridge/src/mapping.rs:37` 到 `:43` 映射 `tree.*`;`:52` 到 `:68` 保留 `documents.*` / `page.*` alias;`rust/crates/mnote-web/src/tree_shell/dispatcher.rs:16` 到 `:23` 使用 `tree.*`;`rust/crates/mnote-web/src/routes/tree.rs:7634` 附近测试确认 documents alias 映射仍存在。 |
|
||||
| RK-07 | 实现一致 | 低 | Debug shell 默认关闭,符合 `3104`/debug 壳默认退场口径。 | `rust/crates/mnote-web/src/routes/mod.rs:120` 到 `:124` 仅在 `enable_debug_shell_routes` 时挂 `/tree` 和 `/document-debug`;同文件测试 `:142` 到 `:184` 验证默认 404。 |
|
||||
|
||||
## 证据
|
||||
|
||||
### 1. Kernel / projection 已落地,但底层仍主要基于 sidebar dataset
|
||||
|
||||
- `rust/crates/core-protocol/src/kernel.rs:7` 到 `:42` 定义 `KernelNodeType`、`KernelEdgeType`、`KernelProjectionKind`。
|
||||
- `rust/crates/mnote-web/src/routes/kernel.rs:62` 到 `:85` 暴露 projection route,并经 `load_projection_snapshot` 获取投影。
|
||||
- `rust/crates/mnote-web/src/routes/snapshot_support.rs:27` 到 `:47` 固定 `sidebar.dataset.list` 为数据获取入口。
|
||||
- `rust/crates/bridge-runtime/src/lib.rs:8119` 到 `:8137` 在没有 root 时由当前数据构建 subtree,再生成 projection;`file_tree` 另走 `build_file_tree_projection_result`。
|
||||
|
||||
判断:这符合 `1-1` 中“真实主线已落地,但更广义 node pool/reference edge 仍后续”的口径,不应被写成“尚未开始”;也不应被写成“完整 kernel 真源已闭环”。
|
||||
|
||||
### 2. Runtime fallback 仍在主路径附近
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2379` 到 `:2401`:workspace shell projection 加载失败时合成 `active_workspace_id`、`workspaces`、`documents`。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2432` 到 `:2461`:`allow_dev_fixtures` 时合成 sidebar dev dataset。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2493` 到 `:2519`:`allow_dev_fixtures` 时合成 filetree dev dataset。
|
||||
- `rust/crates/mnote-web/src/routes/search.rs:233` 到 `:247`:搜索 Convex 查询失败后执行 `fallback_search_dataset(workspace_id)`。
|
||||
|
||||
判断:这与 `design/03-rust-web/process/3-15-runtime-fallback-retirement-checklist-v1.md` 的“默认运行时不再 fallback / 不静默降级”存在偏差。尤其 Search fallback 会产生看似真实的固定结果,风险高于空状态降级。
|
||||
|
||||
### 3. Page Aggregate 是 Rust route,但仍是迁移期 adapter
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/mod.rs:53` 到 `:56` 挂载 `/api/page-aggregate/{document_id}`。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2264` 到 `:2319` 构建 aggregate。
|
||||
- `rust/crates/bridge-runtime/src/lib.rs:5291` 到 `:5442` 从 meta/content 形状构建 `PageAggregateProjection`。
|
||||
- `rust/crates/core-protocol/src/page_aggregate.rs:4` 到 `:10` 协议层仍保留 `KernelProjection`、`CompatMetaContentJoin`、`Fixture` 三种 source。
|
||||
- `rust/crates/mnote-web/src/page_aggregate/builder.rs:66` 默认 source 仍是 `CompatMetaContentJoin`,但当前检索未发现该 builder 进入主要 route 主路径。
|
||||
|
||||
判断:当前代码已摆脱 TS builder runtime 主链,但还不是“页面域全部直接来自独立 kernel storage 真源”。文档应保留“Rust-first Page Aggregate 过渡态”的精确口径。
|
||||
|
||||
### 4. Tree realtime 主链已成立,但不是完整实时闭环
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/mod.rs:113` 挂载 `/api/tree/events`,`:114` 挂载 `/api/stream/events`,`:115` 挂载 `/api/realtime/ws`。
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:127` 到 `:146` 给 `/api/tree/events` 打 `x-mnote-web-owner: mnote-web` 与 `x-mnote-tree-stream-owner: rust-web`。
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:51` 到 `:91` 通过 sleep/poll bridge overview 发现变化。
|
||||
- `rust/crates/mnote-web/src/routes/ws.rs:32` 到 `:80` WebSocket 当前只发 snapshot,并响应 resync 请求。
|
||||
|
||||
判断:这与 `3-3` 中“已完成 route / snapshot / delta / resync 基础,WS 尚未成为主链,live cache 未完全统一”一致;若 `3-15` 写成 tree realtime 补偿链已完全删除,则偏乐观。
|
||||
|
||||
### 5. Compat / legacy 未成为默认主链,但未完全删除
|
||||
|
||||
- `rust/crates/mnote-web/src/app.rs:42` 到 `:54` 仍读取 legacy Next 相关环境变量,默认 `enable_legacy_next_compat` 为 false。
|
||||
- `rust/crates/mnote-web/src/routes/gateway.rs:86` 到 `:87` 在 compat 开启且配置 legacy base URL 时,`/api/auth` 可走 legacy proxy。
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs:153` 当前 `should_proxy_via_next` 返回 false,说明文档 API 默认不走 Next proxy。
|
||||
|
||||
判断:代码实际状态更像“默认关闭、残留显式迁移/调试能力”,不是“所有 legacy proxy 代码已删除”。
|
||||
|
||||
## 建议优先级
|
||||
|
||||
### P0
|
||||
|
||||
- 移除或显式隔离 Search 的 `fallback_search_dataset`。失败时应返回明确错误或空 projection,并带可观测错误头;不要返回固定假结果。
|
||||
- 对 `load_workspace_shell_projection` 的无条件合成 dataset 做决策:若用于首屏容错,应在响应或 contract 中显式标记 degraded;若严格执行 fallback 退场,应改为失败或空树,不再伪装成真实 workspace projection。
|
||||
|
||||
### P1
|
||||
|
||||
- 将 `allow_dev_fixtures` 相关 fallback 全部加上更明确的 debug/dev 标识,并确认 `desktop:hot` / 3000 默认启动链不会误开。
|
||||
- 补一条 Rust Web smoke:Convex/query 不可用时,搜索、Sidebar、filetree 不应返回假业务数据。
|
||||
- Page Aggregate 文档补一句:当前 Rust route 已是主读链,但底层仍通过 Rust runtime adapter 消费 meta/content substrate;完整页面域单一真源仍在推进。
|
||||
|
||||
### P2
|
||||
|
||||
- Tree realtime 后续应把 SSE polling overview 与真正 domain event stream 的边界写清楚,并继续推进 page subtree / filetree / preferred snapshot 同一 live cache。
|
||||
- Legacy Next proxy 若仍需保留,建议统一命名为 explicit migration/debug boundary;若不再需要,后续单独删除 proxy helper、配置位和测试样例,避免和 `3-15` 的退场状态长期冲突。
|
||||
- `PageAggregateSource::CompatMetaContentJoin` / `Fixture` 是否继续保留在 `core-protocol` 需要架构决策:保留则标记为迁移态 source;删除则需先确认没有测试、local folder、fixture 依赖。
|
||||
|
||||
## 修改的文件路径
|
||||
|
||||
- `/mnt/Data1T/mnote/design/10-review/01-rust-kernel-web-review.md`
|
||||
@@ -0,0 +1,123 @@
|
||||
# 前端编辑器 / 树体验实现偏差审查
|
||||
|
||||
## 范围
|
||||
|
||||
本报告只审查 `wolai-frontend` 当前文档页、`leptos-tiptap` island host、`Page Aggregate` 消费链、Sidebar / tree shell / tree stream 与设计文档的一致性。
|
||||
|
||||
重点对照设计:
|
||||
|
||||
- `ARCHITECTURE.md`
|
||||
- `design/01-05-current-priority-overview.md`
|
||||
- `design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md`
|
||||
- `design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
|
||||
- `design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
- `design/04-tree-domain/done/4-18-tree-final-dom-shell-cutover-hard-gate-v1.md`
|
||||
- `design/04-tree-domain/process/4-23-local-cloud-explicit-bridge-p3-candidate-v1.md`
|
||||
- `design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
|
||||
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
|
||||
重点读取代码:
|
||||
|
||||
- `wolai-frontend/src/app/(app)/documents/`
|
||||
- `wolai-frontend/src/components/editor/`
|
||||
- `wolai-frontend/src/lib/documents/`
|
||||
- `wolai-frontend/src/components/sidebar/`
|
||||
- `wolai-frontend/src/lib/tree-stream/`
|
||||
- `wolai-frontend/src/lib/documents/tree-command-client.ts`
|
||||
|
||||
## 结论
|
||||
|
||||
当前实现总体方向与最新设计主线一致:文档页 SSR 入口已消费 Rust `mnote.page_aggregate.v1`,默认主编辑器已折返到页面内 `leptos_tiptap_island`,tree command 前端主调用已进入 `/api/tree/commands` + `tree.*` 元数据口径,Sidebar 的默认 tree shell 已切到 `rust_wasm_dom_shell_host`,并且 `iframe_srcdoc` 只在显式 legacy flag 下存在。
|
||||
|
||||
但不能把当前状态描述为“前端编辑器 / 树体验已经完全收口到 Rust 单一真源”。主要未完成点集中在三处:页面本地 aggregate reducer 仍承担临时真相与补偿选择,`pageSubtree` 在本地正文变更后会被置空而不是形成同一份可持续 projection,Sidebar 仍通过 initial / query / tree stream 三源 freshness 选择维持一致性。此外,部分页面设置仍明确是 `planned` / `downgrade`,属于设计清单中尚未完成的范围。
|
||||
|
||||
## 关键发现表格
|
||||
|
||||
| 编号 | 分类 | 严重度 | 发现 | 证据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| F-01 | 未完成 | 中 | Page Aggregate 读链已 Rust-first,但客户端仍有本地 aggregate reducer 持有标题、正文、设置、子树快照等临时真相;设计中的“页面域单一真源”尚未闭环。 | `wolai-frontend/src/components/editor/page-aggregate-client-state.ts:7` 定义本地 `PageAggregateClientState`,包含 `serverPageTitle / persistedPageTitle / draftPageTitle / options / content / serverPageSubtreeSnapshot / contentRevision / conflictDetectionKey`;`wolai-frontend/src/components/editor/document-content.tsx:144` 使用 reducer 作为文档页核心状态。 |
|
||||
| F-02 | 实现偏差 | 中 | `pageSubtree` 不是跟随本地正文和标题持续更新的 page projection;一旦本地正文对象与服务器快照不同,就直接返回 `null`,AI 面板和阅读结构面会失去这份 projection。 | `wolai-frontend/src/components/editor/page-aggregate-client-state.ts:163` 通过 `content === serverContentSnapshot` 判断;`wolai-frontend/src/components/editor/page-aggregate-client-state.ts:166` 只有未改动时返回 `serverPageSubtreeSnapshot`,否则 `:169` 返回 `null`;`wolai-frontend/src/components/editor/document-content.tsx:867` 将该选择结果作为 AI snapshot 的 `pageSubtree` 来源,`:1162` 也传给阅读视图。 |
|
||||
| F-03 | 未完成 | 中 | Sidebar / Breadcrumb / 文档页头已共享 preferred snapshot,但 live cache 仍不是唯一来源;当前仍在 initial、query refetch、tree stream 之间做 freshness 选择。 | `wolai-frontend/src/components/app-layout-shell.tsx:20` 同时创建 `useSidebarData` 与 `useSidebarTreeStream`;`:22` 用 `usePreferredSidebarSnapshot` 选择;`wolai-frontend/src/components/sidebar/use-preferred-sidebar-snapshot.ts:29` 在 `query / tree_stream / initial` 间选择;`wolai-frontend/src/components/sidebar/sidebar.tsx:201` Sidebar 内部也消费同样选择结果。 |
|
||||
| F-04 | 未完成 | 低 | 页面设置运行时语义已有代码级分类,但仍有正式 UI 中可见的 planned / downgrade 项;这与 `5-6` 清单中“页面设置未整体收口”的口径一致。 | `wolai-frontend/src/lib/documents/page-option-semantics.ts:60` 将 `protectEditing` 标记为 `planned`,`:84` 将 `showBlockRefCount` 标记为 `planned`;`wolai-frontend/src/components/editor/page-options-sidebar.tsx:416` 对 downgrade 项显示 `待接线`,`:71` 让 `showBlockRefCount` 成为不可交互占位。 |
|
||||
| F-05 | 方向变化/文档滞后 | 低 | `5-5-1` 中仍写着 `showHeadingNumbers / embedDefaultBlockId` 只完成字段贯通,但当前代码已把它们纳入 island runtime payload;这里更像文档滞后,而不是实现偏差。 | `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md` 第 2.3 节仍说明两项“不可描述成正式支持”;`wolai-frontend/src/lib/documents/page-option-semantics.ts:48` 与 `:88` 均标记为 `wired`;`:121` 的 `pickLeptosTiptapRuntimePageOptions` 会把 `showHeadingNumbers / embedDefaultBlockId` 传给 island,`wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx:717` 运行时更新这些选项。 |
|
||||
| F-06 | 风险 | 低 | Tree final DOM shell 默认路径已符合 `4-18`,但 legacy iframe host 仍可被环境变量显式打开;后续需要保持负向 smoke,防止默认路径回退。 | `wolai-frontend/src/components/sidebar/tree-shell-host.tsx:131` 默认 `rust_family` 且有 workspace 时使用 DOM host,`:133` 仅在 `NEXT_PUBLIC_TREE_SHELL_LEGACY_IFRAME_HOST=1` 时启用 legacy iframe;`wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx:182` 覆盖默认不能进入 `iframe_srcdoc`,`:199` 覆盖显式 legacy flag。 |
|
||||
|
||||
## 证据
|
||||
|
||||
### 已对齐部分
|
||||
|
||||
1. 文档页读取主链已进入 Rust Page Aggregate。
|
||||
- `wolai-frontend/src/app/(app)/documents/[id]/page.tsx:51` 调用 `loadPageAggregateFromNextHeaders`。
|
||||
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts:170` 请求 `/api/page-aggregate/:id`。
|
||||
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts:130` 校验 schema 必须是 `mnote.page_aggregate.v1`。
|
||||
- `wolai-frontend/src/lib/documents/page-aggregate-loader.ts:225` 的 `loadPageAggregate` 只返回 Rust snapshot,不再在运行时 fallback 到 TS builder。
|
||||
|
||||
2. 默认编辑器 host 已是页面内 `leptos_tiptap_island`。
|
||||
- `wolai-frontend/src/components/editor/editor-host-config.ts:1` 只保留 `EditorHostKind = "leptos_tiptap_island"`。
|
||||
- `wolai-frontend/src/components/editor/editor-host.tsx:7` 动态加载 `leptos-tiptap-island-editor-host`。
|
||||
- `wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx:619` 直接调用 runtime module 的 `mount(container, ...)`,不是 iframe。
|
||||
|
||||
3. 页面写命令已经开始按 page command family 收口。
|
||||
- `wolai-frontend/src/lib/documents/page-command-contract.ts:15` 固定 `page.head.updateTitle / page.layout.updateOptions / page.body.save`。
|
||||
- `wolai-frontend/src/lib/documents/page-command-client.ts:72` 标题写入发送 `commandName: page.head.updateTitle`。
|
||||
- `wolai-frontend/src/lib/documents/page-command-client.ts:89` 页面设置写入发送 `commandName: page.layout.updateOptions`。
|
||||
- `wolai-frontend/src/app/api/documents/save/route.ts:35` 服务端 envelope 使用 `PAGE_COMMAND_NAMES.saveBody`。
|
||||
|
||||
4. tree command 前端调用主面已以 `tree.*` 为结果元数据口径。
|
||||
- `wolai-frontend/src/lib/documents/tree-command-client.ts:26` 定义 `tree.node.create / tree.node.rename / tree.subtree.move / tree.node.archive` 等 preferred command。
|
||||
- `wolai-frontend/src/lib/documents/tree-command-client.ts:185` 统一 POST 到 `/api/tree/commands`。
|
||||
- `wolai-frontend/src/app/api/tree/commands/route.ts:205` 由 `action` 分发 tree command,`:278` 创建命令使用 `tree.node.create`。
|
||||
|
||||
5. tree final DOM shell 默认路径已符合硬门禁。
|
||||
- `wolai-frontend/src/components/sidebar/tree-shell-host.tsx:139` 默认 implementation 为 `rust_wasm_dom_shell_host`。
|
||||
- `wolai-frontend/src/components/sidebar/tree-shell-surface.tsx:113` 旧 React page tree renderer 已显示为 removed fallback,不再作为正常 renderer。
|
||||
- `wolai-frontend/src/components/sidebar/tree-shell-host.test.tsx:189` 测试断言默认 implementation 是 `rust_wasm_dom_shell_host`,`:194` 断言没有 iframe host。
|
||||
|
||||
### 仍需收口部分
|
||||
|
||||
1. 客户端 `PageAggregateClientState` 仍是混合态。
|
||||
- 它把 server snapshot、persisted title、draft title、本地 content、server subtree snapshot、revision/conflict key 放在同一个前端 reducer 中。
|
||||
- 这符合当前过渡态,但不等于 Rust page aggregate 已成为页面域唯一运行时真相。
|
||||
|
||||
2. `pageSubtree` 与正文编辑没有同源更新。
|
||||
- 本地正文变更只会触发 `apply_local_content_snapshot`,不会同步生成新的 `pageSubtree`。
|
||||
- selector 在内容对象不同于 server snapshot 时返回 `null`,因此 `AI / read view / TOC` 只能等待后续持久化与重新拉取。
|
||||
|
||||
3. Sidebar live cache 仍有 freshness 选择层。
|
||||
- `AppLayoutShell` 和 `Sidebar` 都依赖 `usePreferredSidebarSnapshot`。
|
||||
- 当前算法没有基于 Rust stream cursor / projection version 做统一仲裁,而是通过 sync key 与 `treeStreamStatus` 在 `query` 与 `tree_stream` 之间选择。
|
||||
|
||||
4. 页面设置中仍有显式未完成项。
|
||||
- `protectEditing` 与 `showBlockRefCount` 的代码状态已经诚实标为 `planned / downgrade`。
|
||||
- 这避免了误导用户,但也说明 `Page Aggregate -> island runtime` 的设置语义还没有完全完成。
|
||||
|
||||
## 建议优先级
|
||||
|
||||
### P0
|
||||
|
||||
暂无需要立即阻断主链的 P0。默认 Page Aggregate 读链、默认 island host、默认 DOM tree shell 均未发现与最新主线相反的实现。
|
||||
|
||||
### P1
|
||||
|
||||
1. 收口 `pageSubtree` 与本地正文编辑的关系。
|
||||
- 至少明确它是“server projection only”还是“本地编辑也应生成临时 projection”。
|
||||
- 如果 AI 面板需要稳定结构上下文,不应在用户正常编辑后直接拿到 `null`。
|
||||
|
||||
2. 将 Sidebar 的三源 preferred snapshot 推进为统一 live cache。
|
||||
- 建议以 Rust stream cursor / projection version / query snapshot version 为仲裁字段,减少仅靠 sync key 的 freshness 判断。
|
||||
|
||||
### P2
|
||||
|
||||
1. 继续移除或强门禁 legacy tree iframe host。
|
||||
- 当前显式 env flag 符合设计,但应保留负向 smoke,避免后续默认路径误回退。
|
||||
|
||||
2. 同步修正文档滞后。
|
||||
- `5-5-1` 对 `showHeadingNumbers / embedDefaultBlockId` 的描述已经落后于当前代码,应在后续文档整理时更新。
|
||||
|
||||
3. 页面设置 planned 项保持降级展示,直到真正进入 island runtime 或被产品侧移除。
|
||||
|
||||
## 修改的文件
|
||||
|
||||
- `/mnt/Data1T/mnote/design/10-review/02-frontend-editor-tree-review.md`
|
||||
@@ -0,0 +1,215 @@
|
||||
# Convex / Realtime / Storage 实现偏差审查
|
||||
|
||||
## 范围
|
||||
|
||||
本报告审查 Convex / storage bridge / realtime / Page Aggregate 数据底座与当前设计主线的一致性,重点对照以下方向:
|
||||
|
||||
- Convex 保留为自托管 storage / realtime substrate,不作为树语义 owner。
|
||||
- Rust kernel / bridge-runtime / mnote-web 持有 tree-first graph、projection、command、Page Aggregate 的语义主导权。
|
||||
- `/api/page-aggregate/:id` 作为文档页 Rust-first 读取主链。
|
||||
- `/api/tree/events` 作为 Rust Web tree realtime snapshot / delta / resync 主链。
|
||||
- 前端只消费稳定 projection、Page Aggregate 与 tree stream,不重新持有第二套对象真相。
|
||||
|
||||
重点读取范围:
|
||||
|
||||
- `ARCHITECTURE.md`
|
||||
- `design/01-05-current-priority-overview.md`
|
||||
- `design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
|
||||
- `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
- `design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
|
||||
- `design/05-editor-mainline/process/5-6-page-aggregate-alignment-checklist-v1.md`
|
||||
- `design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
|
||||
- `infra/convex/README.md`
|
||||
- `wolai-frontend/convex/`
|
||||
- `rust/crates/storage-convex-bridge/`
|
||||
- `rust/crates/mnote-web/src/transport/convex.rs`
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs`
|
||||
- `rust/crates/mnote-web/src/routes/tree.rs`
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs`
|
||||
- `rust/crates/mnote-web/src/routes/stream_support.rs`
|
||||
- `wolai-frontend/src/lib/documents/`
|
||||
- `wolai-frontend/src/lib/tree-stream/`
|
||||
|
||||
## 结论
|
||||
|
||||
整体方向与设计主线基本一致:Convex 没有被拆掉,仍承担本地自托管存储、文件和实时底座;Rust Web 已注册 `/api/page-aggregate/:document_id` 与 `/api/tree/events`;前端 tree stream 已直接使用 `EventSource("/api/tree/events")`;Next 的 `/api/documents/page` 与 `/api/mnote-web/stream` 已明确作为 410 compat 边界退场。
|
||||
|
||||
但当前实现仍是明显过渡态,主要问题集中在三处:
|
||||
|
||||
1. Page Aggregate 读链虽然是 Rust endpoint,但实际数据仍由 `documents:getMeta` + `documents:getContent` 分别取回后在 bridge-runtime 中拼 projection,`source` 标识存在“看起来比真实实现更 canonical”的风险。
|
||||
2. Rust Web 的 `/api/documents/title`、`/api/documents/options` 与 Next route 的 page write adapter 在 bridge artifact 记录上不一致;tree stream 依赖 bridgeLogs 时,Rust page write 路径可能漏掉 command/domain event。
|
||||
3. `/api/tree/events` 是正式 SSE 入口,但当前 change detection 依赖周期性查询 `bridgeLogs:listWorkspaceOverview`,且 Convex query 内部会按 workspace collect 全量 command/domain rows 后内存过滤排序,实时性和规模风险较高。
|
||||
|
||||
## 关键发现表格
|
||||
|
||||
| 编号 | 分类 | 严重度 | 发现 | 证据 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| F1 | 实现偏差 | 高 | Rust Page Aggregate endpoint 对外是 `mnote.page_aggregate.v1`,但构建过程仍先分别读取 meta/content,再在 bridge-runtime 中拼 projection;`source=KernelProjection` 容易掩盖底层仍是兼容 join。 | `rust/crates/mnote-web/src/routes/web_shell.rs:2282`、`:2291`、`:2301`;`rust/crates/bridge-runtime/src/lib.rs:8246`、`:8252`、`:5291`、`:5303`、`:5386` |
|
||||
| F2 | 风险 | 高 | Rust Web 的 title/options 写路由只执行 Convex mutation,不记录 bridge command/domain artifacts;而 tree stream 以 bridgeLogs 为事件来源,主路径 page 写入可能不进入实时流。 | `rust/crates/mnote-web/src/routes/documents.rs:714`、`:802`;`rust/crates/mnote-web/src/routes/command_support.rs:65`;对比 `rust/crates/mnote-web/src/routes/tree.rs:6529` 与 `wolai-frontend/src/lib/documents/page-write-command-adapter.ts:63` |
|
||||
| F3 | 风险 | 高 | tree realtime 当前是 SSE transport,但后端通过 `sleep(poll_ms)` 周期性轮询 Convex bridgeLogs,而不是直接利用 Convex subscription/push;默认 2s,实时性和负载都需继续验证。 | `rust/crates/mnote-web/src/routes/sse.rs:25`、`:26`、`:51`、`:58`、`:60`;`rust/crates/mnote-web/src/routes/stream_support.rs:610` |
|
||||
| F4 | 风险 | 中高 | `bridgeLogs:listWorkspaceOverview` 会按 workspace collect 全量 `command_logs` 与 `domain_events` 后在内存中过滤、排序、分页;SSE 轮询叠加后,历史日志增长会放大 Convex 读压力。 | `wolai-frontend/convex/bridgeLogs.ts:283`、`:287`、`:292`、`:301`、`:305` |
|
||||
| F5 | 方向变化/文档滞后 | 中 | `storage-convex-bridge` 把 `page.aggregate.get` 映射到 `documents:getPageAggregate`,但 Convex `documents.ts` 只发现 `getMeta` / `getContent`,未发现 `getPageAggregate` export;当前真实 Page Aggregate 路线已绕到 Rust adapter。 | `rust/crates/storage-convex-bridge/src/mapping.rs:101`;`wolai-frontend/convex/documents.ts:684`、`:803`;全仓 `rg getPageAggregate` 未发现 Convex 实现 |
|
||||
| F6 | 已完成/方向一致 | 中 | Next compat 读链和旧 stream alias 已显式退场,前端 tree stream 直接构造 `/api/tree/events`,符合当前主线口径。 | `wolai-frontend/src/app/api/documents/page/route.ts:17`;`wolai-frontend/src/app/api/mnote-web/stream/route.ts:9`;`wolai-frontend/src/lib/tree-stream/protocol.ts:66`、`:72` |
|
||||
| F7 | 未完成 | 中 | Page Aggregate TS builder 已不在 runtime 主链中被引用,但文件仍保留;当前定位应继续写成 fallback / adapter / 测试材料,不能作为运行时主路径描述。 | `wolai-frontend/src/lib/documents/page-aggregate-builder.ts:51`;全仓非测试引用仅剩定义,runtime loader 只调用 Rust snapshot:`wolai-frontend/src/lib/documents/page-aggregate-loader.ts:225` |
|
||||
|
||||
## 证据
|
||||
|
||||
### 1. Page Aggregate 读链已收口到 Rust endpoint,但仍由 meta/content join 生成
|
||||
|
||||
`mnote-web` 注册了正式 endpoint:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/mod.rs:53` 到 `:56` 注册 `/api/page-aggregate/{document_id}`。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2227` 到 `:2252` 返回 `schema: "mnote.page_aggregate.v1"` 和 `result: aggregate`。
|
||||
|
||||
但实际构建流程仍是:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2282` 到 `:2290` 读取 `load_document_meta_result`。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2291` 到 `:2299` 读取 `load_document_content_result`。
|
||||
- `rust/crates/mnote-web/src/routes/web_shell.rs:2301` 到 `:2315` 把 `meta + content` 作为数据传给 `page.aggregate.get`。
|
||||
- `rust/crates/bridge-runtime/src/lib.rs:5291` 到 `:5312` 的 `build_page_aggregate_projection_result` 明确从 `data.meta` 和 `data.content` 中拆字段。
|
||||
|
||||
这说明“Rust-first 读取主链”成立,但“Page Aggregate 已经由 kernel 原生投影独立产出”仍未成立。该点与 `5-5/5-6` 的过渡态判断一致,但需要在后续文档和汇报中保持精确。
|
||||
|
||||
需进一步验证:`PageAggregateSource::KernelProjection` 是否应在这种 `meta/content join` 场景下继续使用,还是应保留 `CompatMetaContentJoin` 以避免 provenance 误导。
|
||||
|
||||
### 2. Page write side effects 在 Rust route 与 Next adapter 之间不一致
|
||||
|
||||
Next route 侧:
|
||||
|
||||
- `wolai-frontend/src/app/api/documents/title/route.ts:58` 调用 `executePageWriteBridgeCommand`。
|
||||
- `wolai-frontend/src/app/api/documents/options/route.ts:47` 调用 `executePageWriteBridgeCommand`。
|
||||
- `wolai-frontend/src/app/api/documents/save/route.ts:43` 调用 `executePageWriteBridgeCommand`。
|
||||
- `wolai-frontend/src/lib/documents/page-write-command-adapter.ts:63` 到 `:69` 成功后调用 `recordRustBridgeCommandArtifacts`。
|
||||
|
||||
Rust route 侧:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs:579` 的正文保存使用 `execute_runtime_command_via_convex_with_artifacts`。
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs:714` 的标题更新使用 `execute_runtime_command_via_convex`。
|
||||
- `rust/crates/mnote-web/src/routes/documents.rs:802` 的页面设置更新使用 `execute_runtime_command_via_convex`。
|
||||
- `rust/crates/mnote-web/src/routes/command_support.rs:65` 到 `:73` 的 `execute_runtime_command_via_convex` 只执行 Convex command plan,不持久化 artifacts。
|
||||
- `rust/crates/mnote-web/src/routes/command_support.rs:75` 到 `:93` 的 artifact 版本才会调用 `execute_convex_command_plan_with_artifacts`。
|
||||
|
||||
tree command route 是一致的:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/tree.rs:6529` 使用 `execute_runtime_command_via_convex_with_artifacts`。
|
||||
|
||||
风险是:如果当前 3000 主入口走 Rust Web `/api/documents/title` 或 `/api/documents/options`,这些 page 写入不会进入 `command_logs/domain_events`,而 `/api/tree/events` 正是通过 bridgeLogs 检测变化。标题类写入尤其可能影响 sidebar/breadcrumb/page tree 的实时一致性。
|
||||
|
||||
需进一步验证:当前文档页标题编辑在 3000 主壳中到底走 `/api/documents/title` 还是 `/api/tree/commands`;如果走前者,应补 Rust route artifact 记录或统一到 tree/page command route。
|
||||
|
||||
### 3. Tree realtime 是正式 SSE 入口,但实现仍是 polling-backed stream
|
||||
|
||||
正式入口成立:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/mod.rs:130` 注册 `/api/tree/events`。
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:127` 到 `:146` 给 tree events 加 `x-mnote-tree-stream-owner: rust-web`。
|
||||
- `wolai-frontend/src/lib/tree-stream/protocol.ts:66` 到 `:77` 构造 `/api/tree/events` URL。
|
||||
- `wolai-frontend/src/lib/tree-stream/use-sidebar-tree-stream.ts:136` 到 `:143` 用 `EventSource` 监听 `snapshot/delta/resync`。
|
||||
|
||||
但服务端 change detection 是轮询:
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:25` 到 `:26` 读取 `max_polls` 和 `poll_ms`,默认 `2000ms`,最低 `250ms`。
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:51` 到 `:58` 循环中 `sleep(Duration::from_millis(poll_ms))`。
|
||||
- `rust/crates/mnote-web/src/routes/sse.rs:60` 到 `:67` 每轮调用 `load_stream_overview` 再 `resolve_stream_change`。
|
||||
- `rust/crates/mnote-web/src/routes/stream_support.rs:610` 到 `:625` 的 `load_stream_overview` 通过 `bridge.workspace.overview` 查询 Convex。
|
||||
|
||||
这与“Rust Web 提供正式实时 transport”一致,但还不是“基于 Convex realtime subscription 的 push stream”。短期可接受为过渡实现,长期如果继续承载主链,需要明确性能和延迟边界。
|
||||
|
||||
### 4. bridgeLogs overview 查询存在规模风险
|
||||
|
||||
`wolai-frontend/convex/bridgeLogs.ts` 的 `listWorkspaceOverview`:
|
||||
|
||||
- `:283` 到 `:286` 按 workspace 查询并 `collect()` 所有 `command_logs`。
|
||||
- `:287` 到 `:290` 按 workspace 查询并 `collect()` 所有 `domain_events`。
|
||||
- `:292` 到 `:301` 在内存中过滤、排序、切片 command logs。
|
||||
- `:303` 到 `:312` 再用 commandId 集合过滤 domain events。
|
||||
|
||||
该实现作为调试/小数据过渡可以工作,但被 `/api/tree/events` 默认每 2 秒调用时,日志增长后会造成:
|
||||
|
||||
- Convex query 读放大。
|
||||
- SSE 连接数量增加时成倍放大。
|
||||
- cursor 语义依赖内存排序,历史数据规模变大后容易出现延迟或超时。
|
||||
|
||||
建议后续至少增加按 `workspace_id + created_at/id` 的索引分页,避免每次 stream poll 全量扫描。
|
||||
|
||||
### 5. storage-convex-bridge 中存在疑似过时的 `page.aggregate.get` 映射
|
||||
|
||||
`rust/crates/storage-convex-bridge/src/mapping.rs:101`:
|
||||
|
||||
```text
|
||||
"page.aggregate.get" => "documents:getPageAggregate"
|
||||
```
|
||||
|
||||
但当前 `wolai-frontend/convex/documents.ts` 中只发现:
|
||||
|
||||
- `getMeta`:`wolai-frontend/convex/documents.ts:684`
|
||||
- `getContent`:`wolai-frontend/convex/documents.ts:803`
|
||||
|
||||
全仓搜索未发现 Convex `documents:getPageAggregate` 实现。当前真实 Page Aggregate 读取是 Rust Web 先读 meta/content,再由 bridge-runtime 生成 projection。因此该映射要么是未来目标,要么已经滞后;如果有代码路径直接通过 storage-convex-bridge 执行 `page.aggregate.get`,会存在运行时函数不存在风险。
|
||||
|
||||
需进一步验证:`storage-convex-bridge` 是否仍有生产路径直接执行 `page.aggregate.get -> documents:getPageAggregate`;如果没有,应把映射标注为 future/stale,或改为当前真实读链。
|
||||
|
||||
### 6. Convex substrate 定位总体一致
|
||||
|
||||
正向证据:
|
||||
|
||||
- `infra/convex/README.md` 明确自托管 Convex backend/dashboard、HTTP Actions 和文件存储。
|
||||
- `wolai-frontend/convex/schema.ts:35` 到 `:91` 的 `documents` 表继续承接页面结构、正文、页面设置、统计字段。
|
||||
- `wolai-frontend/convex/schema.ts:121` 到 `:162` 的 `media_assets` 与 `_storage` 字段保留文件底座。
|
||||
- `rust/crates/storage-convex-bridge/README.md:5` 到 `:13` 明确 bridge 只做协议到 Convex 读写请求映射,不复制主事实层、不维护第二数据库。
|
||||
|
||||
这与“Convex 不拆,Rust 收口语义”的设计一致。
|
||||
|
||||
## 建议优先级
|
||||
|
||||
### P0:修正 Rust page write route 的 artifact 一致性
|
||||
|
||||
目标:让当前 3000 主入口的 `/api/documents/title`、`/api/documents/options` 至少在 side effect 上与 Next `page-write-command-adapter` 保持一致。
|
||||
|
||||
建议:
|
||||
|
||||
- 将 `rust/crates/mnote-web/src/routes/documents.rs` 中 title/options 的 `execute_runtime_command_via_convex` 改为 artifact 版本,或统一复用 tree/page command route。
|
||||
- 对标题更新补最小回归:更新标题后 `bridgeLogs:listWorkspaceOverview` 能看到对应 command/domain event,`/api/tree/events` 能输出 delta 或 resync。
|
||||
- 对 artifact 写失败的策略重新定级:当前 `rust/crates/mnote-web/src/transport/convex.rs:759` 到 `:770` 是主 mutation 成功、artifact 失败仍返回成功;对 tree-relevant command 至少应打可观测错误并触发保守 resync。
|
||||
|
||||
### P1:收口 Page Aggregate provenance
|
||||
|
||||
目标:避免把 `meta/content join` 误标为已完成的 kernel-native projection。
|
||||
|
||||
建议:
|
||||
|
||||
- 明确 `PageAggregateSource::KernelProjection` 与 `CompatMetaContentJoin` 的使用边界。
|
||||
- 如果 `build_page_aggregate_snapshot` 仍通过 `load_document_meta_result + load_document_content_result` 构建,应在返回 source 或 header 中反映真实来源。
|
||||
- 如果目标是 kernel-native projection,则补正式 kernel/query 数据路径,避免 route 层长期手工拼装。
|
||||
|
||||
### P1:优化 tree stream 的 Convex 查询模型
|
||||
|
||||
目标:让 `/api/tree/events` 可以长期承载主链,而不是随着日志增长退化。
|
||||
|
||||
建议:
|
||||
|
||||
- 为 `command_logs`、`domain_events` 增加按 `workspace_id + created_at/id` 的查询索引和 cursor 查询。
|
||||
- `listWorkspaceOverview` 不再 `collect()` 全量 workspace 日志后内存分页。
|
||||
- 明确 polling-backed SSE 是过渡实现,还是长期 realtime transport;若长期使用,应补连接数、日志量、延迟上限的 smoke/bench。
|
||||
|
||||
### P2:清理或标注 stale mapping
|
||||
|
||||
目标:降低后续 worker 误用 `page.aggregate.get -> documents:getPageAggregate` 的风险。
|
||||
|
||||
建议:
|
||||
|
||||
- 若 `documents:getPageAggregate` 不计划实现,移除或注释 `storage-convex-bridge` 中的映射。
|
||||
- 若计划实现,补 Convex query 与最小测试,并让 mnote-web Page Aggregate route 直接消费它或说明为什么不消费。
|
||||
|
||||
### P2:保留 Next compat 退场边界,但避免双实现继续发散
|
||||
|
||||
目标:Next route 继续作为 legacy/adapter 参考时,不与 Rust Web 主入口形成不同 side effects。
|
||||
|
||||
建议:
|
||||
|
||||
- 对 `/api/documents/title/options/save` 明确 owner:Rust Web 主路径与 Next legacy 路径只能有一份 canonical side-effect 规则。
|
||||
- 对 `wolai-frontend/src/lib/documents/page-aggregate-builder.ts` 保留测试/adapter 标签,避免被重新接回 runtime 主链。
|
||||
|
||||
## 修改的文件路径
|
||||
|
||||
- `design/10-review/03-convex-realtime-storage-review.md`
|
||||
@@ -0,0 +1,64 @@
|
||||
# 次级域与设计治理实现偏差审查
|
||||
|
||||
## 范围
|
||||
|
||||
本次只审查 Mindmap、AI、OnlyOffice、Wolai-aline、SiYuan reference,以及 `design/process/done` 治理口径与当前实现方向的一致性。重点读取了 `ARCHITECTURE.md`、`design/README.md`、`design/06-mindmap/process/*`、`design/07-ai/process/*`、`design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`、`design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md`、`design/90-reference/*`,并对照了 Mindmap、AI route/lib、OnlyOffice route/adapter 与相关 Convex/Rust runtime 代码。
|
||||
|
||||
## 结论
|
||||
|
||||
总体方向与当前主线基本一致:Mindmap 已明确降为 `tree-first graph kernel` 的视图/编辑挂件,AI 主执行面正在向 `mnote-cli` 收口,OnlyOffice 仍是独立页面型编辑器边界,SiYuan 参考稿没有发现被提升为上位架构来源的证据。
|
||||
|
||||
主要风险集中在三处:OnlyOffice 在 `mnote-web` 主入口里的 callback/forcesave 仍是 no-op,而 legacy Next route 已有真实写回链;Mindmap 的导出 action 在 schema/action map 中暴露,但 bridge 安全命令集不支持;Mindmap AI 补完 route 当前硬返回 501,和 Rust tool 已登记的能力面不闭合。
|
||||
|
||||
## 关键发现表格
|
||||
|
||||
| ID | 分类 | 级别 | 发现 | 简短证据 | 建议 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| F-01 | 实现偏差 / 风险 | P1 | OnlyOffice `mnote-web` 主入口已挂载 callback/forcesave,但当前实现不写回,只返回成功或 noop,可能让 3000 主链下的 OnlyOffice 保存丢失。 | `rust/crates/mnote-web/src/routes/mod.rs:78-84` 挂载 `/api/onlyoffice/callback` 与 `/api/onlyoffice/forcesave`;`rust/crates/mnote-web/src/routes/onlyoffice.rs:717-759` callback 只记录日志并返回 `{error:0}`,forcesave 返回 `mnote-web-rust-noop`;而 `wolai-frontend/src/app/api/onlyoffice/callback/route.ts:93-192` 有真实 Convex 写回链。 | 优先把 Rust route 接到 `onlyoffice_prepare_callback` / media asset writeback,或显式把该路由代理到 legacy Next,避免主入口 shadow 掉真实写回。 |
|
||||
| F-02 | 未完成 | P2 | Mindmap `export` 动作暴露给 UI action map,但 simple-mind-map 安全执行器不允许 `EXPORT`,默认路径可能显示能力却执行失败。 | `wolai-frontend/src/lib/mindmap/mindmap-action-map.ts:65` 将 `export` 映射为 runtimeCommand `EXPORT`;`wolai-frontend/src/lib/mindmap/simple-mind-map-bridge.ts:119-129` 的 `SIMPLE_MIND_MAP_SAFE_COMMANDS` 不包含 `EXPORT`。 | 要么把 `EXPORT` 加入安全命令并补 smoke,要么在 UI state 中继续禁用导出并标注为延期。 |
|
||||
| F-03 | 未完成 / 方向变化 | P2 | Mindmap AI 补完 route 当前直接 501,后续大段旧 Supabase/在线 AI 实现被注释;但 Rust tool registry 已登记 `mindmap_expand_node`,形成“工具存在、产品入口不可用”的断层。 | `wolai-frontend/src/app/api/mindmap-ai/expand-node/route.ts:90-97` 校验后直接返回 `501`;同文件后续注释块仍保留旧 Supabase/AI 逻辑;`rust/crates/core-protocol/src/tool.rs:231-240` 与 `rust/crates/bridge-runtime/src/lib.rs:3803-3812` 已有 `mindmap_expand_node`。 | 若该能力仍在 Phase 6/7 范围内,应按 CLI/Rust bridge 路线重接;若延期,应把 route 标成 retired/debug,避免前端或测试误以为可用。 |
|
||||
| F-04 | 方向变化 / 文档滞后 | P2 | AI 主 Web route 已收口到 `mnote-cli` host,但 `wolai-backend` 仍暴露 `openai_agents_python` 文档 agent route 与旧工具面;是否仍部署为可访问入口需进一步验证。 | `wolai-frontend/src/app/api/ai-agent/run/route.ts:36-57` 明确拒绝 codex/hermes/claudecode 并进入 `startMnoteCliAgentHostRun`;`wolai-backend/app/routers/ai_agent.py:34-57` 仍暴露 `/ai-agent/health`、`/ai-agent/document/run`,health 返回 `bridge: openai_agents_python`;`wolai-backend/app/services/ai_document_agent.py:1174-1470` 仍指令 agent 使用 `doc_insert_blocks`、`doc_replace_range`、`slash_run`。 | 在设计或代码注释中明确该后端只作为可插拔外置 agent/对照链;若不再使用,补退场计划和访问边界,尤其确认 `mnote_ai_orchestrator_api_key` 未配置时的开放行为。 |
|
||||
| F-05 | 文档治理风险 | P3 | `design/90-reference` 符合“参考资料”目录定位,但内容仍带有问答式残留,容易被后续 worker 误用为正式设计结论。 | `design/README.md` 明确 `90-reference/` 不参与 process/done 状态判断;`design/90-reference/90-1-filetree.md` 末尾保留“需要我给你...”类对话尾巴;`design/90-reference/90-2-yemianshu.md` 同样保留示例请求口吻。 | 低优先级清理为中性参考笔记,并在引用时强制以 `ARCHITECTURE.md` 和主线设计为上位依据。 |
|
||||
|
||||
## 证据
|
||||
|
||||
### Mindmap
|
||||
|
||||
- 设计口径清晰:`design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` 明确 Phase 6 主线是 `Rust kernel truth -> mindmap.simple_mind_map_scene.v1 -> leptos-mindmap editor island -> simple-mind-map runtime -> command bridge`,并禁止把 runtime data 当唯一事实源。
|
||||
- 实现已对齐主线壳:`rust/crates/mnote-web/src/routes/mindmap_shell.rs:105-123` 输出 `mnote.mindmap_shell.v1`、`mindmap.simple_mind_map_scene.get` 与 `mindmap.command.apply`;`rust/crates/mnote-web/src/ssr/pages/mindmap.rs:50-56` 提供 standalone island 挂载点。
|
||||
- 旧 React 块已自我标注为 legacy/compat/reference:`wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx:3-5` 明确 3000 文档页默认主链是 `leptos-tiptap NodeView + leptos-mindmap adapter`。
|
||||
- 仍有 compat 数据层:`wolai-frontend/convex/schema.ts:164-184` 的 `mindmaps.data: v.any()` 仍承载导图数据;这与当前过渡态兼容,但不应被描述为长期 canonical truth。
|
||||
|
||||
### AI
|
||||
|
||||
- CLI-first 入口基本对齐:`wolai-frontend/src/app/api/ai-agent/run/route.ts:36-57` 将默认执行入口限定到 `mnote-cli host`,拒绝旧 provider。
|
||||
- 结构化 artifact 设计仍有未完成项:`design/07-ai/process/7-phase7-ai-kernel-projection-plan-v4.md` 中“后续权限收口”仍未勾选,说明 AI 写链权限模型尚未完全闭环。
|
||||
- 旧后端 agent 仍存在:`wolai-backend/app/routers/ai_agent.py:47-57` 仍提供 streaming run;这可作为外置 agent,但需要避免被误认为长期默认主编排。
|
||||
|
||||
### OnlyOffice
|
||||
|
||||
- 正确边界已有:`rust/crates/adapter-onlyoffice/src/lib.rs:124-126` 明确 adapter 只定义资产定位、会话、签名、代理、callback/forcesave 边界,不把 OnlyOffice 变成主事实层。
|
||||
- `mnote-web` 页面是独立编辑页:`rust/crates/mnote-web/src/routes/onlyoffice.rs:325-630` 直接渲染 `/onlyoffice`,通过 DocsAPI 创建编辑器,未嵌入正文主编辑画布。
|
||||
- 关键风险是写回链 owner 分裂:Rust 主入口 no-op 与 legacy Next 真写回并存,见 F-01。
|
||||
|
||||
### Wolai-aline
|
||||
|
||||
- 流程文档严格要求“Wolai 基线 -> RED smoke -> 小范围实现 -> 本地验证 -> subagent 复测 -> 主线程截图复核”:`design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`。
|
||||
- 本次不是具体 Wolai 对标实现任务,未执行浏览器对标;未发现该流程被错误提升为产品架构来源。
|
||||
|
||||
### SiYuan Reference
|
||||
|
||||
- `design/09-siyuan-reference/process/9-siyuan-reference-boundary-and-adoption-v1.md` 的边界与当前主线一致:思源只作为产品能力与交互参考,不替代 `tree-first graph kernel`、Rust kernel、Page Aggregate、tree command 与 tree realtime。
|
||||
- 当前只读检索未发现将 SiYuan `.sy`、SQL API 或前端 runtime 直接提升为 mnote 长期事实源的实现证据;若后续新增属性视图/数据库视图,应继续先做对象域评估。
|
||||
|
||||
## 建议优先级
|
||||
|
||||
1. P1:补齐或明确代理 OnlyOffice Rust callback/forcesave 写回链,避免主入口下保存成功但内容未持久化。
|
||||
2. P2:收口 Mindmap action 可用性,先修 `export` 映射与安全命令不一致,再继续扩展 UI 能力。
|
||||
3. P2:处理 Mindmap AI route:要么按 Rust bridge/CLI-first 重接 `mindmap_expand_node`,要么显式退役该 Next route。
|
||||
4. P2:明确 `wolai-backend` AI agent 的外置/对照定位与访问边界,避免与 `mnote-cli` 唯一执行面口径冲突。
|
||||
5. P3:清理 `design/90-reference` 的问答残留,并在引用规范里再次强调它不是 process/done 设计稿。
|
||||
|
||||
## 本次修改文件
|
||||
|
||||
- `/mnt/Data1T/mnote/design/10-review/04-secondary-domains-and-design-governance-review.md`
|
||||
@@ -0,0 +1,152 @@
|
||||
结论
|
||||
我基本同意你的方向,但要把“文件树为根源”说得更精确:不应让“文件树 UI 组件”成为真源,而应让 Rust kernel 中的 workspace resource tree / file tree projection 背后
|
||||
的资源层级 成为页面、附件、mindmap、OnlyOffice 等对象的组织根源。页面树则是从这棵资源/对象树派生出的导航投影,类似快捷方式、收藏视图或文档视图。
|
||||
|
||||
对当前 bug 来说,最核心的规则应固定为:
|
||||
|
||||
1. index.md 只代表页面正文,即 Page Aggregate body。
|
||||
2. mindmap 不是主编辑区正文替身,而是页面内某个 block 关联的 object / asset editor。
|
||||
3. 文件树点击 mindmap 应打开 mindmap object editor,不能被 index.md 吞掉,也不能用临时 mindmap-only bootstrap 覆盖真实页面正文。
|
||||
4. 页面树不应再持有独立结构真相,只显示“文档导航关系”;文件树显示“资源组织关系”。
|
||||
|
||||
主编辑区与插件
|
||||
主编辑区应该是一个工作区 tab host,而不是只等于文档正文编辑器。index.md 是其中一种 tab,mindmap、OnlyOffice、附件预览、代码附件都是其他 object tab/model。
|
||||
|
||||
Siyuan 这里值得借鉴:doc 和 asset 是不同 model/tab,asset 打开不伪装成 doc 正文;插件、widgets、assets、.sy 文档也在存储层分开。mnote 也应采用类似原则:插件/附
|
||||
件/mindmap 可以嵌入页面,但它们的持久化真源不能变成页面正文本身,只能通过 block-asset relation 被 Page Aggregate 引用。
|
||||
|
||||
文件树与页面树
|
||||
我建议改成三层表述:
|
||||
|
||||
- Resource Tree:长期 canonical 对象组织树,Rust kernel 持有语义,Convex 只是底层存储。
|
||||
- File Tree:Resource Tree 的主组织投影,展示页面文件夹、index.md、附件、mindmap、OnlyOffice、代码附件等。
|
||||
- Page Tree:面向阅读/导航的快捷投影,只显示页面关系,不拥有排序、父子、附件归属的最终真相。
|
||||
|
||||
这样你的“文件树为根源”可以落地,但不会和现有 tree-first graph kernel 冲突。更准确地说:文件树背后的 resource hierarchy 是根源,页面树是它的页面导航视图。
|
||||
|
||||
建议路线
|
||||
短期先修当前 bug:保留 /mindmap/{doc}/{asset} 作为 object editor,但彻底隔离草稿 key、保存 command 和 Page Aggregate body,smoke 必须真实网页验证“点击 mindmap、
|
||||
编辑长中文、保存、切回 index.md、再打开 mindmap 内容仍在”。
|
||||
|
||||
中期做主编辑区 asset tab:index.md | mindmap.json | xxx.docx,每个 tab 有自己的 object identity 和保存命令。
|
||||
|
||||
长期补 resource projection / block-asset relation:文件树 row、页面内 mindmap block、mindmap projection 必须来自同一关系索引,而不是前端临时按路由拼。
|
||||
|
||||
|
||||
|
||||
推荐路线
|
||||
我建议采用“分阶段收口 Resource Tree”的方案,不做一次性大重构。
|
||||
|
||||
可选方案有三种:
|
||||
|
||||
- 只修 mindmap bug:最快,但会继续留下页面树/文件树真源分裂。
|
||||
- 分阶段建立 Resource Tree -> File Tree/Page Tree projection:最稳,能解决当前 bug,也能承接附件、OnlyOffice、插件对象。
|
||||
- 全量重写 tree/page/resource 模型:理论干净,但当前工作区和主链都很活跃,风险太高。
|
||||
|
||||
我推荐第二种。
|
||||
|
||||
阶段 0:冻结合同
|
||||
先把 design/10-review/05-tree.md 升级为正式设计合同,建议后续移动或补一份到:
|
||||
|
||||
- design/04-tree-domain/process/4-24-resource-tree-filetree-pagetree-source-contract-v1.md
|
||||
- 或 design/05-editor-mainline/process/5-11-main-editor-object-tab-resource-tree-alignment-v1.md
|
||||
|
||||
合同里固定四句话:
|
||||
|
||||
- Resource Tree 是组织真源。
|
||||
- File Tree 是资源组织主投影。
|
||||
- Page Tree 是页面导航投影/快捷视图。
|
||||
- index.md、mindmap、OnlyOffice、附件是不同 object model,不能互相伪装。
|
||||
|
||||
阶段 1:先关闭当前 mindmap bug
|
||||
目标:文件树 mindmap 仍可编辑保存,但不污染 index.md。
|
||||
|
||||
重点文件:
|
||||
|
||||
- rust/crates/mnote-web/src/ssr/pages/layout.rs
|
||||
- rust/crates/mnote-web/src/routes/mindmap_shell.rs
|
||||
- rust/spikes/leptos-tiptap-spike/src/lib.rs
|
||||
- scripts/task169-mindmap-realtime-smoke.js
|
||||
|
||||
验收必须是真实浏览器链路:
|
||||
|
||||
1. 从文件树点击 mindmap asset。
|
||||
2. 进入明确的 mindmap object editor。
|
||||
3. 输入长中文并保存。
|
||||
4. 切回同页 index.md,正文仍是 Page Aggregate body。
|
||||
5. 再打开 mindmap,刚才内容仍在。
|
||||
6. 确认没有用真实 documentId 写 standalone 草稿 key。
|
||||
|
||||
阶段 2:补 Resource Projection 协议
|
||||
在 Rust 协议层明确资源节点和 block-asset 关系。
|
||||
|
||||
建议新增或扩展:
|
||||
|
||||
- ResourceNode
|
||||
- ResourceKind: page | index | mindmap | attachment | onlyoffice | code
|
||||
- ObjectIdentity: { objectKind, documentId, blockId?, assetId? }
|
||||
- BlockAssetRelation: { documentId, blockId, assetId, assetKind }
|
||||
|
||||
落点优先看:
|
||||
|
||||
- rust/crates/core-protocol/
|
||||
- rust/crates/bridge-runtime/
|
||||
- rust/crates/mnote-web/src/tree_shell/
|
||||
|
||||
这个阶段不要求立刻迁移所有数据,只要协议和 projection 能表达清楚。
|
||||
|
||||
阶段 3:让 File Tree 从 Resource Tree 派生
|
||||
当前 file tree 已有 resourceMeta、assetKind、file_tree projection 基础。下一步是收口为:
|
||||
|
||||
- 页面节点下面固定有 index.md
|
||||
- mindmap/附件/OnlyOffice 都是同一 Resource Tree 下的 child resource
|
||||
- 文件树 row 不再临时从多份前端数据拼出第二真相
|
||||
|
||||
同时保留 tree.asset.open,但它必须只表达 object open intent,不直接决定“把谁当正文”。
|
||||
|
||||
阶段 4:Page Tree 降级为导航投影
|
||||
Page Tree 不再拥有资源归属、附件归属、mindmap 归属。它只显示页面导航关系:
|
||||
|
||||
- 页面标题
|
||||
- 页面层级
|
||||
- 快捷入口/收藏/最近打开这类导航语义
|
||||
|
||||
如果页面树需要显示某个资源状态,也只读 Resource Projection,不自己维护。
|
||||
|
||||
阶段 5:主编辑区变成 Object Tab Host
|
||||
这是中期关键体验:
|
||||
|
||||
- index.md tab:加载 Page Aggregate body。
|
||||
- mindmap.json tab:加载 mindmap projection/command。
|
||||
- xxx.docx tab:打开 OnlyOffice object editor。
|
||||
- attachment tab:预览/代码编辑/外部打开。
|
||||
|
||||
这样 mindmap 不会被 index.md 吞掉,也不会伪装成正文。
|
||||
|
||||
阶段 6:命令面统一
|
||||
后续命令应逐步收口到:
|
||||
|
||||
- tree.asset.attach
|
||||
- tree.asset.detach
|
||||
- tree.resource.rename
|
||||
- tree.resource.move
|
||||
- page.body.save
|
||||
- mindmap.command.apply
|
||||
|
||||
关键规则:改资源关系走 tree/resource command,改页面正文走 page command,改导图内容走 mindmap command。
|
||||
|
||||
|
||||
落地入口
|
||||
|
||||
以上判断已落实到两个主线 done checklist:
|
||||
|
||||
- /mnt/Data1T/mnote/design/04-tree-domain/done/4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md
|
||||
用于冻结 Resource Tree / File Tree / Page Tree 的真源合同和 projection / command 边界。
|
||||
- /mnt/Data1T/mnote/design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md
|
||||
用于落实主编辑区 Object Tab、mindmap object editor、草稿隔离和真实浏览器 smoke 验收。
|
||||
|
||||
执行顺序固定为:
|
||||
|
||||
1. 先完成 4-24:协议、projection、command 边界必须能表达 `index.md`、mindmap、OnlyOffice、附件、代码附件的不同 object identity,并确认 Page Tree 只是页面导航投影。
|
||||
2. 再完成 5-12:主编辑区只消费 4-24 输出的 object identity / resourceMeta / open intent,先关闭 mindmap 污染 `index.md` 的 P0 bug,再推进 Object Tab Host。
|
||||
3. 验收以真实浏览器 smoke 为准,尤其是 `scripts/task169-mindmap-realtime-smoke.js` 覆盖“打开 mindmap、长中文编辑、保存、切页、回 `index.md`、再开 mindmap”的链路。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 10-review 审查总览
|
||||
|
||||
本目录汇总当前设计与实现的偏差审查结果。四份分域报告分别覆盖 Rust kernel/web、前端编辑器与树体验、Convex/realtime/storage、次级域与设计治理。
|
||||
|
||||
## 结论
|
||||
|
||||
当前主线方向总体没有跑偏,但实现仍停留在“主链已切、收口未完”的状态。最需要继续收口的是:
|
||||
|
||||
1. `Page Aggregate` 的单一真源边界
|
||||
2. `tree realtime` 的统一 live cache 与查询模型
|
||||
3. `documents.*` / `tree.*` / `page.*` 的 side effect 一致性
|
||||
4. legacy / compat / fallback 的显式退场边界
|
||||
|
||||
## 优先级摘要
|
||||
|
||||
### P0
|
||||
|
||||
- Rust page write 路由与 artifact 一致性
|
||||
- 搜索 / workspace shell / fallback 数据不要再伪装成真实主链
|
||||
|
||||
### P1
|
||||
|
||||
- `pageSubtree` 与正文编辑的关系需要明确
|
||||
- Sidebar 的 `initial / query / tree_stream` 三源选择要继续向统一 live cache 收口
|
||||
- `PageAggregateSource` 的 provenance 口径要和真实读链对齐
|
||||
- Convex overview 查询要避免全量扫描放大
|
||||
|
||||
### P2
|
||||
|
||||
- 继续清理或标注 legacy compat、debug host、stale mapping
|
||||
- 对 `design/90-reference` 和次级参考材料保持只读、非上位来源定位
|
||||
|
||||
## 报告文件
|
||||
|
||||
- [Rust Kernel / Web 实现偏差审查](./01-rust-kernel-web-review.md)
|
||||
- [前端编辑器 / 树体验实现偏差审查](./02-frontend-editor-tree-review.md)
|
||||
- [Convex / Realtime / Storage 实现偏差审查](./03-convex-realtime-storage-review.md)
|
||||
- [次级域与设计治理实现偏差审查](./04-secondary-domains-and-design-governance-review.md)
|
||||
|
||||
## 统一判断
|
||||
|
||||
当前最准确的描述不是“已经完成单一真源收口”,而是:
|
||||
|
||||
> Rust 已经持有主语义主导权,前端与 Convex 也已切到新主线,但 projection、command、realtime、fallback 仍有若干兼容态残留,需要继续按优先级收口。
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
# design 设计稿索引
|
||||
|
||||
> 更新时间:2026-04-20
|
||||
> 更新时间:2026-05-11
|
||||
>
|
||||
> 状态口径以当前仓库真实代码为准:
|
||||
> - `[done]`:对应阶段或收口目标已经在当前主线代码中成立
|
||||
@@ -36,6 +36,10 @@
|
||||
7. `07-ai/`
|
||||
- `process/` 放推进中的主线稿
|
||||
- `done/` 放已在真实代码中成立的主线稿
|
||||
8. `08-wolai-aline-test-flow/`
|
||||
- `process/` 放推进中的测试流程与对标执行稿
|
||||
9. `09-siyuan-reference/`
|
||||
- `process/` 放思源参考、借鉴边界与能力盘点稿
|
||||
|
||||
## 迁移规则
|
||||
|
||||
|
||||
Reference in New Issue
Block a user