# 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 编辑可用性优先,不因实时刷新或路由切换导致闪烁、跑位或内容错乱。