20260513 mindmap优化01
This commit is contained in:
+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 方案摘要。
|
||||
Reference in New Issue
Block a user