20260513 mindmap优化01

This commit is contained in:
lix-2026
2026-05-13 22:43:16 +08:00
parent 17c003976b
commit b4a452a8b7
89 changed files with 11557 additions and 707 deletions
@@ -0,0 +1,154 @@
# 5-10 [done][bug] 文件树思维导图打开污染 index.md 单一真源 v1
> 更新时间:2026-05-13
>
> 分类归属:
> - `05-editor-mainline/done`
> - 涉及边界:`04-tree-domain/file_tree asset intent`、`03-rust-web/mindmap standalone shell`、`06-mindmap/projection editor`
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/10-review/README.md`
> - `/mnt/Data1T/mnote/design/10-review/02-frontend-editor-tree-review.md`
> - `/mnt/Data1T/mnote/design/10-review/04-secondary-domains-and-design-governance-review.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
> - `/mnt/Data1T/mnote/design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md`
## 1. 问题定义
当前文件树中的思维导图文件点击打开后,会进入 standalone `/mindmap/{documentId}/{mindmapId}` 壳。这个壳会构造一份只包含单个 mindmap block 的临时 editor bootstrap,而不是读取该页面真实的 `index.md` / Page Aggregate body。
结果是:用户从文件树打开思维导图、编辑、切换页面后,再点击同一页面的 `index.md`,可能看到只剩思维导图默认内容的页面体验。这里的核心问题不是单次保存失败,而是 `index.md` 页面正文与文件树里的 mindmap asset 没有明确收口到同一份页面真源。
## 2. 归类理由
本问题归到 `05-editor-mainline`,而不是只归到 `06-mindmap`,理由如下:
- 用户可见故障发生在文档页壳、文件树点击打开、主编辑区内容切换体验上。
- 受影响对象是 `index.md` 页面正文与主编辑区当前内容,而不只是 mindmap runtime 内部编辑能力。
- `design/10-review` 已明确当前 Page Aggregate 仍是“主链已切、单一真源收口未完”的状态,本问题正是页面正文、asset view 与 standalone shell 边界未闭合的具体缺陷。
- `04-tree-domain` 需要定义 filetree asset row 的 open intent,但真正承载用户体验和页面真源一致性的 owner 仍是编辑主线。
## 3. 真实现象
用户复现结论:
1. 打开文件树中的思维导图文件,编辑后可以正常保存。
2. 切换页面后,文件树中的思维导图仍能保存。
3. 切换页面后点击文件树中的 `index.md`,主编辑区只显示思维导图默认内容,而不是该页面真实正文。
用户判断的根因方向:
1. 之前对文件树中的思维导图文件点击操作会对主页面产生干扰,应取消该干扰。
2. 思维导图文件的点击打开应该在主编辑区弹出新页面或新 tab,类似 VS Code,而不是污染 `index.md`
3. 当前文件树 `index.md` 与其中的思维导图没有只持有一份真源,需要收口成单一来源。
## 4. 证据
设计审查证据:
- `design/10-review/README.md` 明确当前最需要收口的是 `Page Aggregate` 单一真源边界、tree realtime/live cache、side effect 一致性,以及 legacy/compat/fallback 退场边界。
- `design/10-review/02-frontend-editor-tree-review.md` 的 F-01 指出 Page Aggregate 读链已 Rust-first,但客户端仍有本地 aggregate reducer 持有标题、正文、设置、子树快照等临时真相,页面域单一真源尚未闭环。
- `design/10-review/04-secondary-domains-and-design-governance-review.md` 的 Mindmap 段落指出 standalone mindmap shell 已存在,但 compat 数据层仍承载导图数据,不应被描述为长期 canonical truth。
代码锚点:
- `rust/crates/mnote-web/src/ssr/pages/layout.rs:1100` 定义 `buildMindmapOpenPath(documentId, assetId)`,直接生成 `/mindmap/{documentId}/{assetId}`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs:1128``:1134``openConvexAssetFromFileTree` 在识别到 mindmap asset 后直接 `window.location.assign(buildMindmapOpenPath(documentId, assetId))`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs:1180``:1182``tree.asset.open` 统一交给 `openConvexAssetFromFileTree`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs:3551``:3555` 文件树点击 `index/document/markdown` 时导航到文档页,但非文档 row 且有 `assetId` 时 dispatch `tree.asset.open`mindmap asset 因而进入 standalone mindmap 路由。
- `rust/crates/mnote-web/src/routes/mindmap_shell.rs:75``:104` standalone mindmap shell 构造临时 `editor_bootstrap.content`,内容只有一个 `mnoteBlockType: "mindmap"` 的 paragraph block。
- `rust/crates/mnote-web/src/routes/mindmap_shell.rs:105``:123` standalone contract 标记为 `mnote.mindmap_shell.v1`projection source 仍是 `compat-blob`
- `wolai-frontend/src/components/sidebar/sidebar-navigation.ts:21``:33` 的 React 侧 `buildSidebarMindmapOpenTarget``main` 模式下返回 `/documents/{documentId}`,只有 `sidebar` 模式才打开 `/mindmap/{documentId}/{mindmapId}`;这说明至少已有一条路径表达了“主编辑区不应直接进入 standalone mindmap shell”的倾向。
## 5. 当前判断
当前更像是三条路径混在一起:
1. `index.md` 应代表页面正文,并由 Page Aggregate body / page command 持有。
2. 文件树中的 mindmap asset 应代表页面内某个 mindmap block 关联的资源视图或编辑入口。
3. standalone `/mindmap/{doc}/{mindmap}` 是过渡兼容壳,应作为独立编辑页或调试/弹出入口,不应替代主编辑区的 `index.md` 真源。
因此,即使 mindmap asset 自己可以保存,也不能说明 `index.md` 与 mindmap block 已经同源。当前故障的关键是:打开 asset 的动作把用户带进了会伪造临时正文的 shell,导致页面主编辑体验看起来像 `index.md` 被替换成默认 mindmap。
## 6. 建议修复方向
### 阶段一:立即止血
1. 取消文件树 mindmap asset 点击直接 `window.location.assign('/mindmap/{doc}/{asset}')` 的主路径行为。
2. 点击 `index.md` 必须始终打开真实 `/documents/{documentId}?treeView=filetree`,并读取真实 Page Aggregate body。
3. mindmap asset 点击只能发出明确的 asset-open intent,不应改写当前页面正文、不应伪造 `index.md` 内容、不应让主编辑区误认为当前正文就是 standalone bootstrap。
4. 保留 `/mindmap/{doc}/{asset}` 作为显式独立页面入口时,应只用于新窗口、新 tab、调试入口或明确的 asset editor,不作为普通文件树主点击默认行为。
### 阶段二:主编辑区 asset tab
建议把文件树里的 mindmap asset 打开为主编辑区内的 asset tab / preview,而不是替换 `index.md`
- 标签形态类似 `index.md | mindmap.json`
- `index.md` tab 只显示真实页面正文。
- `mindmap.json` 或 mindmap asset tab 使用 `{ documentId, mindmapId }` 加载 mindmap projection。
- asset tab 保存只写 mindmap command / projection 对应资源,不创建第二份页面正文。
- 关闭 asset tab 后回到 `index.md`,正文内容应保持不变。
### 阶段三:Page Aggregate 与 block-asset 单一真源
长期应把 mindmap asset 与页面正文 block 关系收口到 Page Aggregate / kernel projection
1. 页面正文中的 mindmap block attrs 持有稳定 `mindmapId`
2. 文件树 mindmap asset row 来自同一份 page aggregate/resource projection,而不是另起一套对象真相。
3. mindmap asset 的删除、恢复、移动、重命名应同步维护 page body block 与 resource projection 的关系。
4. standalone shell 如果继续存在,也必须声明自己是 asset editor,不再构造会被误认为 `index.md` 的页面正文 bootstrap。
## 7. 建议 smoke 验证
修复完成后至少补一条文件树 mindmap 回归 smoke
1. 登录真实测试账号。
2. 新建测试页面,插入或生成一个 mindmap block,记录 `documentId``mindmapId`
3. 从文件树点击该页面下的 mindmap asset。
4. 断言主编辑区没有导航到会污染 `index.md` 的 standalone bootstrap;如果打开 asset tab,则 tab 标识与 `index.md` 分离。
5. 在 mindmap 中输入一段较长中文内容并保存。
6. 切换到其它页面,再从文件树点击原页面 `index.md`
7. 断言 `index.md` 仍显示真实页面正文,且页面内 mindmap block 仍引用同一个 `mindmapId`
8. 再打开 mindmap asset,断言刚才输入的长中文内容仍存在。
需要保留一条负向断言:
- 普通文件树主点击 mindmap asset 不应触发 `window.location.assign('/mindmap/{doc}/{asset}')` 并替换当前文档页正文。
## 8. 修复证据
本问题已按 `4-24``5-12` 收口:
- `rust/crates/core-protocol/src/kernel.rs` 增加 `KernelObjectIdentity` / `KernelBlockAssetRelation`
- `rust/crates/bridge-runtime/src/lib.rs` 的 file tree projection 为 `index.md`、mindmap、OnlyOffice、代码附件、普通附件输出不同 `resourceMeta.objectIdentity`
- `rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs``rust/crates/mnote-web/src/routes/tree.rs``rust/crates/mnote-web/src/ssr/pages/layout.rs``objectIdentity` 下发到文件树 DOM 与 `tree.asset.open` intent。
- `/mindmap/{documentId}/{mindmapId}``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` 的 standalone bootstrap 使用 `__mindmap_object__:{documentId}:{mindmapId}`,不复用真实页面正文草稿身份。
验证命令:
```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
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
cd /mnt/Data1T/mnote/rust/spikes/leptos-tiptap-spike && cargo test standalone_mindmap_object_uses_isolated_draft_identity -- --nocapture
node scripts/task112-tree-rust-family-regression-smoke.js
node scripts/task169-mindmap-realtime-smoke.js
```
`task169` 已覆盖:文件树 mindmap asset 打开、长中文编辑、保存、切页、从文件树回 `index.md`、再次打开 mindmap,且未触发 `page.body.save` 污染链。
## 9. 流转条件
当前状态:`done`
只有在以下条件都满足后,本文才能移动到 `bugs/05-editor-mainline/done/`
1. [x] 文件树 mindmap asset 普通点击不再污染 `index.md` 主编辑区。
2. [x] `index.md` 与页面内 mindmap block 的关系有明确单一真源,至少不再出现切页后只剩默认导图的现象。
3. [x] `/mindmap/{doc}/{asset}` 的产品定位被明确为独立页、新 tab、debug 或 asset editor,不再被文件树主路径误用。
4. [x] smoke 覆盖长中文 mindmap 编辑、保存、切页、回到 `index.md`、再次打开 asset 的完整链路。
5. [x] 浏览器复测确认 mindmap 编辑可用性优先,不因实时刷新或路由切换导致闪烁、跑位或内容错乱。