# 4-24 [done] Resource Tree / File Tree / Page Tree 真源合同与执行清单 v1 > 更新时间:2026-05-13 > > 2026-05-15 口径更新: > - 本文件完成时的 `index.md` 可见 UI 模型已被 `/mnt/Data1T/mnote/design/04-tree-domain/done/4-35-convex-filetree-title-md-source-alignment-v1.md` 覆盖。 > - 后续 Convex File Tree 默认页面正文行显示为 `{title}.md`,rowId 为 `doc:`;本文中的 `index.md` 仅表示历史正文 object identity / 兼容语义,不再作为默认可见子行继续扩展。 > > 上游依据: > - `/mnt/Data1T/mnote/design/10-review/05-tree.md` > - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md` > - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-20-vscode-explorer-file-tree-alignment-v1.md` > - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md` > - `/mnt/Data1T/mnote/design/10-review/README.md` ## 1. 目标 本清单用于把 `design/10-review/05-tree.md` 中的判断落到树域主线: - `Resource Tree` 是长期对象组织真源,由 Rust kernel 持有语义。 - `File Tree` 是 Resource Tree 的主组织投影,展示页面、`index.md`、附件、mindmap、OnlyOffice、代码附件等资源。 - `Page Tree` 是页面导航投影 / 快捷视图,不拥有排序、父子、附件归属或资源归属的最终真相。 - Sidebar、文件树 UI、页面树 UI 都只能消费 projection 和发 command,不能重新拼第二套结构真相。 ## 2. 术语冻结 - `Resource Tree`:workspace 内对象与资源的 canonical hierarchy。它不是 UI 文件树组件,而是 kernel 层的资源组织语义。 - `File Tree`:Resource Tree 面向 VS Code Explorer 体验的 projection。 - `Page Tree`:Resource Tree 中页面对象的导航 projection,可做快捷显示,但不拥有资源归属。 - `ObjectIdentity`:打开、保存、草稿、tab、command 使用的对象身份。 - `BlockAssetRelation`:页面正文 block 与资源对象之间的稳定关联,例如 `documentId + blockId + assetId + assetKind`。 ## 3. 非目标 - 不在本阶段重写 Convex 底层存储。 - 不在本阶段一次性删除所有 compat/fallback。 - 不把文件树 UI 组件提升为真源。 - 不让 Page Tree 接管附件、mindmap 或 OnlyOffice 的归属。 ## 4. 前置顺序与执行门 本清单是 `5-12` 的前置合同。执行顺序固定为: 1. 先完成本文件的 `Resource Tree / File Tree / Page Tree` 真源合同。 2. 再执行 `design/05-editor-mainline/done/5-12-main-editor-object-tab-resource-alignment-checklist-v1.md` 中的主编辑区 object tab、草稿隔离和打开路径调整。 不允许绕过本文件直接在编辑区里新增第二套 asset / page 归属判断。编辑区可以先做 mindmap 止血,但止血代码必须只消费本文件定义的 object identity / file tree intent,不得把临时前端判断提升为长期合同。 当前代码基线: - `rust/crates/core-protocol/src/kernel.rs` 已有 `KernelProjectionResourceKind`、`KernelProjectionAssetKind`、`KernelProjectionResourceMeta`、`KernelProjectionItem`。 - `rust/crates/bridge-runtime/src/lib.rs` 已有 `build_file_tree_projection_result`、`make_projection_resource_meta`、`normalize_file_tree_asset` 等 file tree projection 构造逻辑。 - `rust/crates/mnote-web/src/routes/tree.rs` 与 `rust/crates/mnote-web/src/ssr/pages/layout.rs` 已消费 `resourceMeta` 并发出 `tree.asset.open`。 - `rust/crates/mnote-web/src/ssr/pages/layout.rs` 当前仍把 mindmap asset 主路径导航到 `/mindmap/{documentId}/{assetId}`,这是 `5-10` bug 的直接落点,但长期合同归属仍应先在本文件冻结。 阶段门: - G1:协议能表达 `index.md`、mindmap、OnlyOffice、普通附件、代码附件的不同 object identity。 - G2:`file_tree` projection row 能稳定输出 object identity 或等价 `resourceMeta.extra.objectIdentity`。 - G3:`page_tree` projection 被约束为页面导航投影,不承载 asset 归属。 - G4:`tree.asset.open` 只表达打开资源对象的 intent,不决定“把资源当正文打开”。 - G5:完成 G1-G4 后,`5-12` 才能把主编辑区打开路径、草稿 key、保存命令接入 object tab / object editor 规则。 ## 5. 执行清单 ### 5.1 协议模型 - [x] 在 `rust/crates/core-protocol/` 增加或扩展资源对象协议。 - 优先落点:`rust/crates/core-protocol/src/kernel.rs`,必要时拆出 `rust/crates/core-protocol/src/resource.rs` 并在 `lib.rs` re-export。 - 当前已有 `KernelProjectionResourceKind`,本阶段应补齐或映射为长期 `ResourceKind` 语义。 - 最小资源种类:`page`、`index`、`mindmap`、`attachment`、`onlyoffice`、`code`。 - `ObjectIdentity` 最小字段:`objectKind`、`documentId`、`blockId`、`assetId`。 - `BlockAssetRelation` 最小字段:`documentId`、`blockId`、`assetId`、`assetKind`。 - 命名规则:Rust 类型使用 `KernelObjectIdentity` / `KernelBlockAssetRelation` 或等价项目内前缀;序列化字段使用 camelCase。 - [x] 补协议单测。 - 文件建议:`rust/crates/core-protocol/tests/resource_tree_contract.rs`。 - 断言:mindmap、OnlyOffice、普通附件都能表达为 resource object。 - 断言:`index.md` 的 object identity 与 mindmap asset 的 object identity 不相等。 - 断言:`BlockAssetRelation` 能表达 `documentId + blockId + assetId + assetKind`,且不会被反序列化为页面正文 identity。 - 运行命令: - `cd /mnt/Data1T/mnote/rust && cargo test -p core-protocol --test resource_tree_contract -- --nocapture` - 预期:新增协议测试通过,失败时不得继续进入 `5-12` 的 P1/P2。 ### 5.2 Projection 合同 - [x] 扩展 `file_tree` projection 的 row contract。 - 每个 row 必须带稳定 `row_id`、`node_id`、`projection_kind`、`resource_meta`。 - `resource_meta` 至少包含 `resourceKind`、`documentId`、`assetId`、`assetKind`、`objectIdentity`。 - 优先修改点:`rust/crates/core-protocol/src/kernel.rs` 的 `KernelProjectionResourceMeta`,以及 `rust/crates/bridge-runtime/src/lib.rs` 的 `make_projection_resource_meta`。 - 兼容策略:已有 `resourceKind/documentId/assetId/assetKind` 保持不破坏;新增字段可先放入 `resourceMeta.extra.objectIdentity`,待前后端消费稳定后再提升为强类型字段。 - [x] 固定 `page_tree` projection 的降级边界。 - `page_tree` 只输出页面导航关系。 - `page_tree` 不输出附件归属、mindmap 归属、OnlyOffice 归属的最终判断。 - 如需显示资源状态,只能引用 `resource_meta` 或 resource projection,不得在 page tree renderer 内拼装。 - 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 中 `page_tree` projection 构造逻辑、`wolai-frontend/src/lib/tree-projection.ts`、`wolai-frontend/src/lib/documents/page-subtree.ts`。 - [x] 补 Rust projection 测试。 - 文件优先看:`rust/crates/bridge-runtime/src/lib.rs`、`rust/crates/mnote-web/src/tree_shell/`。 - 测试要求:同一页面下的 `index.md` 与 mindmap asset 生成不同 row,但共享同一 `documentId`,并通过 `BlockAssetRelation` 建立关系。 - 建议新增或扩展测试: - `kernel_project_view_query_executes_into_file_tree_projection_contract` - `kernel_file_tree_projection_query_supports_extended_resources_and_max_results` - 新增 `kernel_file_tree_projection_separates_index_and_mindmap_object_identity` - 运行命令: - `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` ### 5.3 File Tree 派生规则 - [x] 页面节点下固定派生 `index.md` row。 - `index.md` row 只能打开 Page Aggregate body。 - `index.md` row 不得根据页面内第一个 block 类型改变打开目标。 - 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 的 `build_file_tree_projection_result`,当前已固定创建 `index:{documentId}` node。 - 前端消费检查点:`rust/crates/mnote-web/src/ssr/pages/layout.rs` 的 file tree row click 分支。 - [x] 页面内资源固定派生为 child resource row。 - mindmap row 打开 mindmap object editor。 - OnlyOffice row 打开 OnlyOffice object editor。 - 代码附件 row 打开代码/预览编辑器。 - 普通附件 row 打开签名资源或预览。 - 优先检查点:`rust/crates/bridge-runtime/src/lib.rs` 的 `normalize_file_tree_asset`、`infer_file_tree_asset_shape`、`classify_asset_resource_kind`。 - 必须保留 `documentId` 与 `assetId`,供 `5-12` 的 object tab identity 使用。 - [x] 文件树 row 不得从前端多份数据临时拼真相。 - 允许前端显示、筛选、选中、展开。 - 不允许前端决定资源归属、排序真相或 block-asset relation 真相。 - 前端允许做的本地状态:`expanded`、`selected`、`hover`、`focus`、`dragging`、`drop target`。 - 前端禁止新增长期字段:`assetParentDocumentId`、`mindmapOwnerPageId`、`pageTreeAssetChildren` 这类与 kernel projection 重复的归属真相。 ### 5.4 Page Tree 派生规则 - [x] Page Tree 只消费页面导航投影。 - 可显示页面标题、层级、当前页、快捷入口。 - 不负责附件、mindmap、OnlyOffice、代码附件的生命周期。 - [x] Page Tree command 只发页面导航相关 command。 - 页面新建、重命名、移动、归档继续走 `tree.node.*` 或正式 page/tree command。 - 资源 attach/detach/rename/move 不进入 page tree 私有命令。 - 负向检查:Page Tree renderer / host 中不得新增 mindmap、OnlyOffice、附件归属推导分支。 ### 5.5 Command 边界 - [x] 新增或确认资源关系 command family。 - `tree.asset.attach` - `tree.asset.detach` - `tree.resource.rename` - `tree.resource.move` - [x] 明确不同对象写入 owner。 - 页面正文:`page.body.save` - 页面标题:`page.head.updateTitle` - 页面设置:`page.layout.updateOptions` - mindmap 内容:`mindmap.command.apply` - OnlyOffice 内容:OnlyOffice callback / forcesave 写回链 - 资源归属:`tree.asset.*` / `tree.resource.*` - 优先检查点: - `rust/crates/bridge-runtime/src/lib.rs` command plan - `rust/crates/mnote-web/src/transport/convex.rs` command adapter - `wolai-frontend/src/lib/documents/rust-runtime.test.ts` ### 5.6 Smoke 与验收 - [x] 增加 Resource Tree / File Tree projection smoke。 - 断言文件树同页下可见 `index.md`、mindmap、附件。 - 断言点击 `index.md` 只打开 Page Aggregate body。 - 断言点击 mindmap 不进入 Page Aggregate body 保存链。 - 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`。 - 建议新增断言脚本:`scripts/task170-resource-tree-filetree-source-smoke.js`,或在 task112 中增加独立 case。 - 运行命令: - `node scripts/task112-tree-rust-family-regression-smoke.js` - 若新增脚本:`node scripts/task170-resource-tree-filetree-source-smoke.js` - [x] 增加 Page Tree 负向 smoke。 - 断言 Page Tree 不显示资源归属为自己的结构真相。 - 断言 Page Tree 操作不会改变 mindmap/附件归属。 - 可复用脚本:`scripts/task112-tree-rust-family-regression-smoke.js`。 - 负向检查输出需记录:操作 Page Tree 后,同一 `assetId` 的 `documentId` / `objectIdentity` 未变化。 ## 6. 建议提交切片 本文件只定义设计顺序,不要求一次提交完成全部实现。后续执行时建议按以下切片推进: 1. 协议切片:`core-protocol` 增补 object identity / relation 类型与测试。 2. Projection 切片:`bridge-runtime` 与 `mnote-web` file tree projection 输出 object identity。 3. UI 消费切片:file tree host 只转发 object open intent,不拼归属真相。 4. Page Tree 负向切片:补 page tree 不持有资源归属的单测或 smoke。 5. 验收切片:跑 Rust 测试与真实浏览器 smoke,把证据补回对应 bug / design 文档。 ## 7. 验收命令 最低验收命令: ```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 node scripts/task112-tree-rust-family-regression-smoke.js ``` 如执行环境没有启动 `3000`,先运行仓库常用入口: ```bash npm run desktop:hot ``` 真实浏览器 smoke 必须使用默认测试账号 `mnote.e2e@example.com` / `MnoteE2E123!`,不能用 `devFallback` 代替登录态。 ## 8. Done Gate 本文移动到 `done/` 前必须满足: - [x] `Resource Tree`、`File Tree`、`Page Tree` 三个术语在协议和文档中不再混用。 - [x] `file_tree` row 能表达 `index.md`、mindmap、OnlyOffice、附件、代码附件的不同 object identity。 - [x] `page_tree` 被验证为页面导航 projection,不拥有资源归属真相。 - [x] mindmap 与 `index.md` 的打开、保存、草稿身份互相隔离。 - [x] 至少一条真实浏览器 smoke 覆盖 `index.md + mindmap asset + 切页 + 再打开` 的完整链路。 ## 9. 完成证据 - 协议:`rust/crates/core-protocol/src/kernel.rs` 增加 `KernelObjectIdentity`、`KernelObjectKind`、`KernelBlockAssetRelation`,并由 `rust/crates/core-protocol/tests/resource_tree_contract.rs` 覆盖。 - Projection:`rust/crates/bridge-runtime/src/lib.rs` 的 file tree projection 输出 `resourceMeta.objectIdentity` / `blockAssetRelation`,并覆盖 index、mindmap、OnlyOffice、代码附件、普通附件。 - UI intent:`rust/crates/mnote-web/src/ssr/pages/layout.rs`、`rust/crates/mnote-web/src/routes/tree.rs`、`rust/crates/mnote-web/src/tree_shell/filetree_renderer.rs` 将 `objectIdentity` 传入 file tree DOM 与 `tree.asset.open`。 - Smoke:`node scripts/task112-tree-rust-family-regression-smoke.js` 通过,page tree host / file tree host 主链为 `dom`;debug tree shell 关闭项按当前主线记录为 skipped。 - Mindmap 链路:`node scripts/task169-mindmap-realtime-smoke.js` 通过,覆盖 mindmap asset 打开、长中文编辑、保存、切页、回 `index.md`、再打开 mindmap。 ## 10. 交接给 5-12 的输入 完成本文件后,`5-12` 可以依赖以下输入推进主编辑区: - 文件树 `index.md` row 的 `objectIdentity`:`page:index:{documentId}`。 - 文件树 mindmap row 的 `objectIdentity`:`resource:mindmap:{documentId}:{assetId}`。 - 文件树 OnlyOffice row 的 `objectIdentity`:`resource:onlyoffice:{documentId}:{assetId}`。 - 文件树普通附件 row 的 `objectIdentity`:`resource:attachment:{documentId}:{assetId}`。 - 文件树代码附件 row 的 `objectIdentity`:`resource:code:{documentId}:{assetId}`。 - Page Tree 不再作为资源归属输入;它只提供页面导航选择。