# 4-4 [done] Tree Projection Protocol Contract v1 > 更新时间:2026-04-18 > > 关联: > - `/mnt/Data1T/mnote/design/04-tree-domain/process/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md` > - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/reference/1-tree-first-graph-kernel-v1.md` ## 1. 目的 这份文档用于冻结树域 projection contract,避免 `sidebar_tree`、`page_tree`、`file_tree` 在后续 Rust route、Leptos tree shell、Next 挂载切流阶段继续各自长字段。 这里固定三条原则: - 树域只消费 projection,不消费前端自己拼出来的结构真相 - Rust 与前端共享同一组 projection 字段语义 - `sidebar_tree`、`page_tree`、`file_tree` 是同一协议家族,不是三套无关返回值 ## 2. 协议分层 ### 2.1 Rust canonical contract Rust 侧的 canonical contract 统一表达为: - `projection_id` - `projection` - `root_node_id` - `items` - `edges` `items` 里的字段语义冻结如下。 ### 2.2 TypeScript transport contract 前端 TypeScript 侧继续使用 camelCase 字段,但语义必须与 Rust 一致: - `projectionId` - `projectionKind` - `rootNodeId` - `parentNodeId` - `resourceMeta` - `iconHint` - `expandedByDefault` 也就是说: - Rust 是 canonical truth - TypeScript 只是命名风格映射,不允许再引入第二套语义 ## 3. 共享字段 下面这些字段属于 `sidebar_tree`、`page_tree`、`file_tree` 的共享基线。 | canonical | TS | 说明 | | --- | --- | --- | | `node_id` | `nodeId` | 当前 projection item 对应的 kernel node | | `parent_node_id` | `parentNodeId` | 上级 node,根节点为 `null` | | `node_type` | `nodeType` | `workspace/folder/page/section/asset/book/pdf/mindmap/table/index` 等语义类型 | | `projection_kind` | `projectionKind` | 当前 item 属于哪种 projection:`sidebar_tree/page_tree/file_tree` | | `title` | `title` | 展示标题,允许兜底为“无标题” | | `depth` | `depth` | 当前树深度 | | `position` | `position` | 同级排序位置 | | `child_count` | `childCount` | 子项数量 | | `expandable` | `expandable` | 当前 item 是否理论上可展开 | | `expanded_by_default` | `expandedByDefault` | 默认展开建议 | | `capabilities` | `capabilities` | 当前行允许的交互能力 | | `resource_meta` | `resourceMeta` | 绑定到业务资源的元信息 | | `icon_hint` | `iconHint` | 给 shell / renderer 的图标提示 | ## 4. `capabilities` 冻结口径 当前允许的共享 `capabilities` 为: - `expand` - `open` - `drag` - `drop` - `select` - `create-child` - `rename` - `archive` - `restore` - `context-menu` - `reorder` - `open-asset` - `pick` 约束: - `capabilities` 只表达“允许做什么” - 它不表达局部 UI 状态 - 它不表达 hover、selected、dragging、drop target 这类临时态 ## 5. `resource_meta` 冻结口径 `resource_meta` 统一用于表达 projection item 背后的真实资源。 共享字段: - `resource_kind` - `document_id` - `asset_id` - `workspace_id` - `asset_kind` - `icon_hint` - `extra` ### 5.1 `resource_kind` 当前冻结为: - `workspace` - `document` - `index` - `asset` - `asset_folder` - `mindmap` - `table` - `book` - `pdf` ### 5.2 `asset_kind` 当前冻结为: - `file` - `mindmap` - `table` - `book` - `pdf` - `image` - `video` - `audio` - `unknown` ## 6. 三类 projection 的差异字段 ### 6.1 `sidebar_tree` `sidebar_tree` 是最轻的导航 projection。 要求: - 保留共享字段 - 不引入 `row_id` - 资源主语义通常落在 `resource_kind=document` - 允许后续继续作为 workspace 首屏 / stream snapshot 主链 ### 6.2 `page_tree` `page_tree` 是页面树 projection。 它在共享字段基础上新增: - `row_id` 当前 `row_id` 命名约束: - `page:` 要求: - `page_tree` 必须可直接生成 picker 轻量列表 - `page_tree` 必须可直接生成可见 rows - `page_tree` 不允许重新回退成前端自行 flatten 嵌套树 ### 6.3 `file_tree` `file_tree` 是页面树骨架上的更宽对象投影。 它在共享字段基础上强调这些字段必须稳定存在: - `resource_kind` - `asset_kind` - `icon_hint` - `expandable` - `expanded_by_default` 要求: - `file_tree` 不能继续由前端用 `asset-folder` / `asset` / `index` 临时猜语义作为长期真相 - `file_tree` 必须由 Rust 直接输出 `document/index/asset/asset_folder/mindmap/table/book/pdf` 等对象投影 ## 7. 当前稳定项与过渡项 ### 7.1 已稳定 - `sidebar_tree` 基础 contract - `page_tree` 的 `row_id/node_id/parent_node_id/depth/position/capabilities/resource_meta` - `expanded_by_default` - `child_count` ### 7.2 仍处于过渡 - `file_tree` 里的 `asset_folder/asset/index` 仍有前端 adapter 残留 - `icon_hint` 还没有完全由 Rust 主导 - `asset_kind` 仍需要从更宽对象类型扩展到 `book/pdf` ## 8. Fixture 与 Contract Test 要求 后续所有树域 fixture / contract tests 至少覆盖: - `sidebar_tree` 基础 fixture - `page_tree` fixture - `file_tree` fixture - `resource_meta` 差异字段 - `capabilities` - `icon_hint` - `expanded_by_default` ## 9. 完成判定 当以下条件同时成立时,视为本 contract 冻结完成: - `sidebar_tree`、`page_tree`、`file_tree` 的共享字段与差异字段都已写成文档与共享类型 - Rust 与前端共享同一组字段语义 - `file_tree` 不再依赖前端“补字段猜语义” - 后续 shell / renderer / route 改造不再新增破坏性字段