Files
mnote/design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md
T
2026-05-13 22:43:16 +08:00

15 KiB
Raw Blame History

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.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 输出的 ObjectIdentityresourceMetatree.asset.open intent。
  • P0 可以作为止血先落地,但 P0 中所有草稿 key、保存命令、打开路径都必须与 4-24 的 object identity 兼容。

当前关键代码锚点:

  • rust/crates/mnote-web/src/ssr/pages/layout.rsfile tree asset 打开事件、buildMindmapOpenPathopenConvexAssetFromFileTreetree.asset.open listener。
  • rust/crates/mnote-web/src/routes/mindmap_shell.rsstandalone mindmap shell 与临时 bootstrap。
  • rust/crates/mnote-web/src/ssr/pages/mindmap.rsmindmap object editor 页面标记与 command form。
  • rust/crates/mnote-web/src/routes/mindmap_api.rsmindmap command / put 保存链。
  • rust/crates/mnote-web/src/routes/documents.rspage.body.save 页面正文保存链。
  • rust/spikes/leptos-tiptap-spike/src/lib.rs:文档页 island 草稿、bootstrap、保存 runtime。
  • scripts/task169-mindmap-realtime-smoke.js:当前 mindmap 长中文、保存、实时刷新 smoke,应升级为本 bug 的主验收脚本。

3. P0mindmap 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-tiptap island 初始化时是否优先使用服务端 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:indexresource:mindmap 恢复。
  • 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

  • 更新 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>
    • 必须记录并断言:
      • documentId
      • mindmapId
      • 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。

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.rsrust/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.applyindex.md 保存只走 page.body.save
    • 验收:网络请求和 command result 中的 canonical command 分别正确。
  • Step 5:跑完整 smoke 并补 bug 证据。

    • 运行:node scripts/task169-mindmap-realtime-smoke.js
    • 预期:长中文编辑、保存、切页、回 index.md、再开 mindmap 全链路通过。

4. P1Object 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 必须声明:

    • objectKind
    • documentId
    • blockId
    • assetId
    • title
    • dirtyState
    • saveCommand
    • closeBehavior

4.2 Tab 打开规则

  • index.md row 打开 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 row resourceMeta.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。

4.4 P1 建议实施顺序

  • Step 1:定义 tab identity 类型。

    • 首选落点:rust/crates/core-protocol/src/kernel.rsrust/crates/core-protocol/src/editor/model.rs
    • 前端适配落点:wolai-frontend/src/components/sidebar/tree-shell-dom-model.tswolai-frontend/src/components/sidebar/sidebar-navigation.ts
  • Step 2:让 file tree open intent 携带 tab identity。

    • 修改:rust/crates/mnote-web/src/routes/tree.rsrust/crates/mnote-web/src/ssr/pages/layout.rs
    • 验收:tree.asset.open detail 中包含 objectIdentity 或可无歧义生成 object identity 的字段。
  • Step 3:主编辑区 host 消费 tab identity。

    • 初期允许只有 page:indexresource:mindmap 两类 tab。
    • 不要求一次完成 OnlyOffice / code / attachment 的 UI,但必须保留类型位。
  • Step 4:补 tab identity 单测。

    • 建议测试文件:wolai-frontend/src/components/sidebar/sidebar-navigation.test.tswolai-frontend/src/components/sidebar/tree-shell-host.test.tsx
    • 断言:index.md 与 mindmap asset 打开生成不同 tab identity。

5. P2Page 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.rsrust/crates/bridge-runtime/src/lib.rswolai-frontend/src/lib/documents/tiptap-content-converter.ts
    • 验收:同一个 mindmapId 能同时从 Page Aggregate block attrs 与 File Tree resource row 追踪到。

6. P3OnlyOffice / 附件 / 插件对象推广

  • 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.rsscripts/task174-rust-onlyoffice-attachment-open-smoke.js
    • 上传 / 普通附件参考落点:rust/crates/mnote-web/src/routes/media.rsscripts/task175-rust-upload-entry-smoke.js

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 identityrust/crates/core-protocol/src/kernel.rs 定义 KernelObjectIdentityfile tree projection 与 DOM 传递 page/index/mindmap/only_office/code/attachment identity。
  • Mindmap object editorrust/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 identityrust/spikes/leptos-tiptap-spike/src/lib.rsstandalone_mindmap_object_uses_isolated_draft_identity 通过。
  • 保存隔离:cargo test -p mnote-web mindmap -- --nocapturecargo test -p mnote-web documents_save_route_executes_page_body_save_command -- --nocapture 通过。
  • 真实 smokenode 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 方案摘要。