15 KiB
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.mdtab 只编辑 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.openintent。 - P0 可以作为止血先落地,但 P0 中所有草稿 key、保存命令、打开路径都必须与
4-24的 object identity 兼容。
当前关键代码锚点:
rust/crates/mnote-web/src/ssr/pages/layout.rs:file tree asset 打开事件、buildMindmapOpenPath、openConvexAssetFromFileTree、tree.asset.openlistener。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 打开路径
-
文件树 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 打开。
- 保留
- 当前允许继续使用
-
index.md点击必须只打开真实文档页。- 目标为
/documents/{documentId}?treeView=filetree。 - 读取来源为 Rust
/api/page-aggregate/{documentId}。 - 不读取 mindmap standalone 草稿。
- 推荐检查点:
layout.rs中 file tree row click 对index/document/markdown的分支。leptos-tiptapisland 初始化时是否优先使用服务端 Page Aggregate bootstrap。
- 目标为
3.2 草稿与保存身份
-
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。
- 推荐格式:
-
文档页有服务端 bootstrap content 时,不得被 localStorage 旧草稿覆盖。
- 如果存在历史坏草稿,文档页应优先使用 Page Aggregate body。
- 打开文档页后应把坏草稿覆盖或隔离,不让其继续污染后续打开。
- 推荐策略:
- 当 bootstrap contract 是
mnote.page_aggregate.v1时,优先采用 bootstrap content。 - 对与 mindmap object key 匹配的草稿直接隔离,不参与文档页正文恢复。
- 对历史坏草稿只允许在同一
ObjectIdentity下恢复,不允许跨page:index与resource:mindmap恢复。
- 当 bootstrap contract 是
-
mindmap 保存只走 mindmap command。
- 保存命令:
mindmap.command.apply或当前正式 mindmap put/apply 链。 - 禁止触发
page.body.save。 - 推荐检查点:
rust/crates/mnote-web/src/routes/mindmap_api.rsrust/crates/mnote-web/src/transport/convex.rswolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts
- 负向测试:mindmap object editor 保存期间拦截或记录网络请求,不能出现
/api/documents/save或 canonicalpage.body.save。
- 保存命令:
3.3 真实网页 smoke
-
更新
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>。 - 必须记录并断言:
documentIdmindmapId- mindmap object editor URL 或 tab identity
index.md返回 URL- 长中文内容保存后的重开结果
-
负向断言。
- 点击 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。
- 点击 mindmap asset 后不得调用
3.4 P0 建议实施顺序
-
Step 1:先补 smoke 红灯。
- 修改:
scripts/task169-mindmap-realtime-smoke.js。 - 目标:当前实现应能暴露
index.md与 mindmap object editor 身份混淆或草稿污染风险。 - 运行:
node scripts/task169-mindmap-realtime-smoke.js。 - 预期:修复前至少一个负向断言失败,或者脚本明确输出当前路径仍有污染风险。
- 修改:
-
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"或等价稳定标记。
- 修改:
-
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 分离。
- 修改:
-
Step 4:确认保存命令隔离。
- 修改:优先不改实现,先用测试确认;如失败,再收口
mindmap_api.rs/documents.rs/ transport adapter。 - 目标:mindmap 保存只走
mindmap.command.apply,index.md保存只走page.body.save。 - 验收:网络请求和 command result 中的 canonical command 分别正确。
- 修改:优先不改实现,先用测试确认;如失败,再收口
-
Step 5:跑完整 smoke 并补 bug 证据。
- 运行:
node scripts/task169-mindmap-realtime-smoke.js。 - 预期:长中文编辑、保存、切页、回
index.md、再开 mindmap 全链路通过。
- 运行:
4. P1:Object Tab Host 最小合同
4.1 Tab Identity
-
定义主编辑区 tab identity。
page:index:{documentId}resource:mindmap:{documentId}:{mindmapId}resource:onlyoffice:{documentId}:{assetId}resource:attachment:{documentId}:{assetId}resource:code:{documentId}:{assetId}
-
每个 tab 必须声明:
objectKinddocumentIdblockIdassetIdtitledirtyStatesaveCommandcloseBehavior
4.2 Tab 打开规则
index.mdrow 打开page:index:{documentId}。- mindmap row 打开
resource:mindmap:{documentId}:{mindmapId}。 - OnlyOffice row 打开
resource:onlyoffice:{documentId}:{assetId}。 - 代码附件 row 打开
resource:code:{documentId}:{assetId}。 - 普通附件 row 打开
resource:attachment:{documentId}:{assetId}。- 输入来源只能是
4-24的 file tree rowresourceMeta.objectIdentity或等价字段。 - 如果字段缺失,允许临时由
resourceKind + documentId + assetId推导,但必须在代码注释或测试名中标明是兼容推导,不得作为新真源。
- 输入来源只能是
4.3 Tab 保存规则
- Page tab 保存只调用
page.body.save。 - Mindmap tab 保存只调用
mindmap.command.apply。 - OnlyOffice tab 保存只通过 OnlyOffice callback / forcesave。
- Code tab 保存只写对应 asset。
- Attachment preview tab 默认无正文保存行为。
- 负向规则:任一 resource tab 保存不得调用
page.body.save,除非该操作明确修改的是 Page Aggregate body 中的引用 block。
- 负向规则:任一 resource tab 保存不得调用
4.4 P1 建议实施顺序
-
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。
- 首选落点:
-
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.opendetail 中包含objectIdentity或可无歧义生成 object identity 的字段。
- 修改:
-
Step 3:主编辑区 host 消费 tab identity。
- 初期允许只有
page:index与resource:mindmap两类 tab。 - 不要求一次完成 OnlyOffice / code / attachment 的 UI,但必须保留类型位。
- 初期允许只有
-
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
- 页面正文中的 mindmap block attrs 必须持有稳定
mindmapId。 - Page Aggregate 输出 mindmap block 与 resource relation。
- File Tree mindmap row 来源于同一 relation。
- 删除 mindmap block 时,资源关系必须进入 detach / archive 计划。
- 删除 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 / 附件 / 插件对象推广
- OnlyOffice 附件打开复用 Object Tab identity。
- 代码附件编辑复用 Object Tab identity。
- 普通附件预览复用 Object Tab identity。
- 插件型对象只能通过 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。
- OnlyOffice 参考落点:
7. 验收命令
最低验收顺序:
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
扩展验收:
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
如本地服务未启动,先执行:
npm run desktop:hot
真实浏览器 smoke 必须使用默认测试账号 mnote.e2e@example.com / MnoteE2E123!,并优先从 http://localhost:3000/auth 的“测试账号快速登录”进入。
8. Done Gate
本文移动到 done/ 前必须满足:
- 当前 mindmap bug 文档
5-10可移动到bugs/05-editor-mainline/done/。 - 真实浏览器 smoke 证明 mindmap 文件树点击、长中文编辑、保存、切页、回
index.md、再开 mindmap 全链路正常。 index.md、mindmap、OnlyOffice、附件的 object identity 已在打开路径和保存路径中区分。- 至少 mindmap 与
index.md已完成草稿隔离。 - 文件树打开行为不再让 asset 伪装成页面正文。
9. 完成证据
- Object identity:
rust/crates/core-protocol/src/kernel.rs定义KernelObjectIdentity,file tree projection 与 DOM 传递page/index/mindmap/only_office/code/attachmentidentity。 - 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 方案摘要。