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