# 5-12 [done] 主编辑区 Object Tab 与 Resource Tree 对齐执行清单 v1 > 更新时间:2026-05-13 > > 2026-05-15 口径更新: > - 本文件中的 `index.md` tab / row 表述是完成当时用于隔离页面正文与资源对象的历史命名。 > - Convex File Tree 默认可见页面正文行已由 `/mnt/Data1T/mnote/design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖为 `doc:` + `{title}.md`;后续 UI、smoke 与 review 不应再把可见 `index.md` 子行作为目标模型。 > > 上游依据: > - `/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-`。 - 必须记录并断言: - `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 方案摘要。