对齐 Wolai 侧栏体验并收拢设计入库
This commit is contained in:
+526
@@ -0,0 +1,526 @@
|
||||
# 4-1 [recycle] Sidebar / 页面树 / 文件树 产品级差距分析 v1
|
||||
|
||||
> 更新时间:2026-04-17
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/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/process/1-tree-first-graph-kernel-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-1-tree-first-graph-kernel-checklist-v2.md`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档不回答:
|
||||
|
||||
- “当前新树能不能先凑合继续用”
|
||||
- “再补一点样式是不是就够了”
|
||||
|
||||
这份文档回答的是:
|
||||
|
||||
> **当前 git 中已经接入的 Rust Web tree shell,距离“文件树对标 VS Code、页面树对标 Wolai / Notion”的产品目标还差什么,以及下一步应该如何推进。**
|
||||
|
||||
结论先固定:
|
||||
|
||||
- 当前方向没有跑偏
|
||||
- 当前实现仍然只是过渡 shell,不是产品级树控件
|
||||
- 下一步重点不是继续修饰过渡 shell,而是进入“产品级树域控件重建”
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前实现所处阶段
|
||||
|
||||
根据当前 git 中未提交改动,现状应被定义为:
|
||||
|
||||
- 已完成 tree shell 挂载位
|
||||
- 已完成 `mnote-web /tree` 路由与基本 command 回写
|
||||
- 已完成 Sidebar / 文件树 / picker 的渐进切流入口
|
||||
- 尚未完成产品级树交互能力重建
|
||||
|
||||
也就是说,当前完成的是:
|
||||
|
||||
- **协议验证**
|
||||
- **切流验证**
|
||||
- **最小可运行壳验证**
|
||||
|
||||
而不是:
|
||||
|
||||
- **VS Code 级文件树**
|
||||
- **Wolai / Notion 级页面树**
|
||||
|
||||
### 2.1 当前代码中已经成立的部分
|
||||
|
||||
- `wolai-frontend` 已经可以把树域挂到 `mnote-web tree shell`
|
||||
- `mnote-web` 已经可以输出基础 tree projection 并承接 create / rename / move
|
||||
- 新旧树之间已经有 feature flag 与 fallback
|
||||
|
||||
对应实现可参考:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/MnoteWebTreeShell.tsx`
|
||||
- `/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/tree.rs`
|
||||
|
||||
### 2.2 当前实现不能被误判为“已完成重构”的原因
|
||||
|
||||
当前 `mnote-web tree shell` 仍然具有明显过渡壳特征:
|
||||
|
||||
- 通过 `iframe + postMessage + fallback` 接入主站
|
||||
- 壳内 UI 仍是手写 HTML / CSS / DOM 逻辑
|
||||
- 交互动作仍大量依赖 `prompt` / `alert`
|
||||
- 没有完成稳定的 row model / focus model / selection model / keyboard model / DnD state machine
|
||||
|
||||
这类实现适合做:
|
||||
|
||||
- 协议对齐
|
||||
- route 验证
|
||||
- 真机切流
|
||||
|
||||
不适合直接作为:
|
||||
|
||||
- 最终产品树控件
|
||||
|
||||
---
|
||||
|
||||
## 3. 现阶段的关键判断
|
||||
|
||||
### 3.1 方向没有错
|
||||
|
||||
当前路线与长期架构是一致的:
|
||||
|
||||
- 树真相继续下沉到 kernel / projection
|
||||
- 页面树与文件树继续作为 projection family
|
||||
- Sidebar 继续降级为树域承载壳
|
||||
|
||||
所以,“把树域逐步从旧 React 组件中剥离出来”这个方向是对的。
|
||||
|
||||
### 3.2 体验回退是阶段性真实现象
|
||||
|
||||
用户现在觉得新树在操作和界面上与目标产品差距很大,这个判断是正确的。
|
||||
|
||||
原因不是:
|
||||
|
||||
- 目标错了
|
||||
|
||||
而是:
|
||||
|
||||
- 当前切进去的是过渡 shell
|
||||
- 旧 React 树实际上已经沉淀了一部分成熟交互
|
||||
- 新 Rust Web 壳还没有把这些产品交互能力重新建起来
|
||||
|
||||
### 3.3 当前最危险的误区
|
||||
|
||||
当前最应该避免的,不是“改慢一点”,而是下面两个误区:
|
||||
|
||||
- 误区 A:把过渡 shell 继续当最终产品打磨
|
||||
- 误区 B:看到效果差,就退回“继续长期维持旧树 + 新协议双轨”
|
||||
|
||||
正确做法是:
|
||||
|
||||
- 承认当前只是过渡壳
|
||||
- 用它验证协议与切流
|
||||
- 然后进入产品级树控件重建
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前实现与目标产品的差距矩阵
|
||||
|
||||
## 4.1 文件树:目标应对标 VS Code Explorer
|
||||
|
||||
这里的“对标”不是抄界面,而是对标:
|
||||
|
||||
- 行为模型
|
||||
- 信息密度
|
||||
- 交互反馈
|
||||
- 结构表达
|
||||
|
||||
### 当前已经具备的部分
|
||||
|
||||
- 已有文件树入口
|
||||
- 已有页面与附件的基础层级表达
|
||||
- 已有打开页面 / 打开附件的最小动作链路
|
||||
- 主站旧文件树中已沉淀部分 VS Code 风格拖放语义
|
||||
|
||||
### 当前缺失的关键能力
|
||||
|
||||
- 缺少稳定的文件树专用 row model
|
||||
- 缺少真实的 `filetree` 模式闭环
|
||||
- 缺少多选与连续选择
|
||||
- 缺少键盘导航
|
||||
- 缺少目录型拖放状态机
|
||||
- 缺少重命名内联编辑
|
||||
- 缺少右键菜单体系
|
||||
- 缺少 hover 工具动作
|
||||
- 缺少大树虚拟化与增量展开策略
|
||||
- 缺少图标语义与资源类型区分
|
||||
|
||||
### 当前与 VS Code 的本质差距
|
||||
|
||||
当前新壳更像:
|
||||
|
||||
- “能渲染树结构的调试页”
|
||||
|
||||
而 VS Code Explorer 是:
|
||||
|
||||
- “高密度、可键盘驱动、可多选、可拖放、可重命名、可上下文操作的文件资源管理器”
|
||||
|
||||
所以文件树下一阶段不应再以“补几个按钮”为目标,而应以“重建 Explorer 行为模型”为目标。
|
||||
|
||||
## 4.2 页面树:目标应对标 Wolai / Notion
|
||||
|
||||
这里的“对标”不是纯视觉复刻,而是对标:
|
||||
|
||||
- 页面层级表达方式
|
||||
- hover 操作节奏
|
||||
- 新建 / 展开 / 拖拽 / 上下文动作的一致性
|
||||
- 页面树作为知识库导航入口的轻量感
|
||||
|
||||
### 当前已经具备的部分
|
||||
|
||||
- 已有页面树 projection 主链
|
||||
- 已有基础展开 / 新建 / 重命名 / 上下移动作
|
||||
- 已有主站切流挂载位
|
||||
|
||||
### 当前缺失的关键能力
|
||||
|
||||
- 缺少 hover 暴露的轻量动作区
|
||||
- 缺少更细的页面类型 / 状态表达
|
||||
- 缺少更自然的层级拖拽交互
|
||||
- 缺少键盘导航与 focus 管理
|
||||
- 缺少右键菜单与上下文操作体系
|
||||
- 缺少行级局部状态管理
|
||||
- 缺少高密度列表的稳定渲染与滚动体验
|
||||
- 缺少与搜索、跳转、最近访问状态的联动边界
|
||||
|
||||
### 当前与 Wolai / Notion 的本质差距
|
||||
|
||||
当前新壳更像:
|
||||
|
||||
- “展示树数据并支持几个命令”
|
||||
|
||||
而 Wolai / Notion 页面树更接近:
|
||||
|
||||
- “低干扰导航器 + 轻量页面管理器”
|
||||
|
||||
所以页面树下一阶段的重心不是“增加更多按钮”,而是:
|
||||
|
||||
- 让操作默认隐藏、按需显现
|
||||
- 让层级结构更轻
|
||||
- 让拖拽、展开、新建、上下文菜单进入一致的节奏
|
||||
|
||||
## 4.3 Sidebar:目标不是单独对标某产品,而是成为稳定壳
|
||||
|
||||
在长期架构中,Sidebar 的任务不是持有树真相,而是:
|
||||
|
||||
- 承载 projection
|
||||
- 承载搜索入口
|
||||
- 承载快速切换
|
||||
- 承载少量工作区级操作
|
||||
|
||||
所以 Sidebar 的核心问题不是“左栏长什么样”,而是:
|
||||
|
||||
- 树域壳与主站其它能力之间的耦合是否被切干净
|
||||
|
||||
当前 Sidebar 仍然偏重,说明下一阶段除了树控件本身,还要继续做:
|
||||
|
||||
- Sidebar 壳职责收敛
|
||||
- 树域与其它面板解耦
|
||||
- 搜索 / AI / 树域三者的边界整理
|
||||
|
||||
---
|
||||
|
||||
## 5. 当前代码中的具体偏差
|
||||
|
||||
## 5.1 `filetree` 模式闭环仍不完整
|
||||
|
||||
当前主站已向新壳传入 `mode=\"filetree\"`,但 Rust route 与壳内脚本对模式的识别还没有完整闭环。
|
||||
|
||||
这意味着当前“文件树已切到新壳”不能简单视为已经完成。
|
||||
|
||||
这一点必须优先修正,因为它会直接影响:
|
||||
|
||||
- 文件树功能判断
|
||||
- 测试结果判断
|
||||
- 后续重构任务拆分
|
||||
|
||||
## 5.2 新壳仍是协议验证页,不是产品树控件
|
||||
|
||||
当前树壳使用:
|
||||
|
||||
- 手写 HTML 模板
|
||||
- 手写 DOM 生成树节点
|
||||
- 手写按钮动作
|
||||
|
||||
这在协议验证阶段是合理的,但不应继续长期积累。
|
||||
|
||||
如果继续在这一层追加:
|
||||
|
||||
- hover 细节
|
||||
- 菜单
|
||||
- 多选
|
||||
- DnD
|
||||
- keyboard
|
||||
|
||||
最终只会把过渡壳演化成难维护的第二套前端。
|
||||
|
||||
## 5.3 旧树的成熟交互能力尚未被系统迁移
|
||||
|
||||
旧文件树与旧页面树中,已经沉淀出一部分成熟能力:
|
||||
|
||||
- 文件树的拖放反馈
|
||||
- 文件树的多选与内部拖放
|
||||
- 页面树的虚拟化
|
||||
- 页面树的拖拽排序
|
||||
|
||||
这些能力现在还没有以“协议化行为模型”的方式迁移进新树域,而是仍然留在旧 React 实现里。
|
||||
|
||||
这意味着下一阶段不能只盯新壳,还要做一件关键工作:
|
||||
|
||||
- 把旧树中已经证明有效的交互经验抽象成正式产品合同
|
||||
|
||||
---
|
||||
|
||||
## 6. 下一阶段应如何指导修改
|
||||
|
||||
## 6.1 原则一:停止把过渡 shell 当最终实现打磨
|
||||
|
||||
接下来不应继续以如下方式推进:
|
||||
|
||||
- “再补几个按钮”
|
||||
- “再修一版样式”
|
||||
- “再把这个 iframe 页面做像一点”
|
||||
|
||||
这些动作只能缓解表面问题,不能得到产品级树控件。
|
||||
|
||||
正确方式是:
|
||||
|
||||
- 把当前壳明确标记为过渡验证层
|
||||
- 只修协议、切流、阻塞性错误
|
||||
- 不在这里继续堆复杂交互
|
||||
|
||||
## 6.2 原则二:先冻结产品交互合同,再进入 Rust Web 正式实现
|
||||
|
||||
下一阶段首先要做的,不是直接写更多 UI,而是冻结两份合同:
|
||||
|
||||
- 文件树产品交互合同
|
||||
- 页面树产品交互合同
|
||||
|
||||
这两份合同至少应明确:
|
||||
|
||||
- 行模型
|
||||
- 层级缩进规则
|
||||
- active / selected / focused / dragging 的区别
|
||||
- hover 暴露策略
|
||||
- 右键菜单入口
|
||||
- 键盘导航规则
|
||||
- 多选规则
|
||||
- 拖放语义
|
||||
- 内联重命名规则
|
||||
- 空白区与容器区行为
|
||||
|
||||
## 6.3 原则三:旧树不是要照抄,而是要提炼成熟行为
|
||||
|
||||
当前仓库里旧树实现仍然有直接参考价值:
|
||||
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/file-tree.tsx`
|
||||
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/private-tree.tsx`
|
||||
|
||||
它们的价值不在于:
|
||||
|
||||
- 继续长期保留 React 版本
|
||||
|
||||
而在于:
|
||||
|
||||
- 可以作为“已在当前产品中验证过的交互基线”
|
||||
|
||||
下一阶段应从它们中提炼:
|
||||
|
||||
- 哪些交互是必须保留的
|
||||
- 哪些只是过渡做法
|
||||
- 哪些要升级成正式协议字段或 UI 状态机
|
||||
|
||||
## 6.4 原则四:文件树与页面树应共用一套树行为骨架
|
||||
|
||||
下一阶段不要再走两套完全独立实现。
|
||||
|
||||
应采用:
|
||||
|
||||
- 同一套 tree row model
|
||||
- 同一套 selection / focus / keyboard / drag state
|
||||
- 同一套 command dispatch
|
||||
|
||||
然后在 projection 层区分:
|
||||
|
||||
- `page_tree`
|
||||
- `file_tree`
|
||||
|
||||
这样才能保持:
|
||||
|
||||
- 文件树不是孤立系统
|
||||
- 页面树不是孤立系统
|
||||
- 两者继续服从 `tree-first graph kernel`
|
||||
|
||||
## 6.5 原则五:最终目标应是产品级 Rust Web tree shell,而不是长期 iframe 壳
|
||||
|
||||
长期上,目标应当是:
|
||||
|
||||
- Rust Web 正式树控件
|
||||
- 直接消费 projection protocol
|
||||
- 直接发 command protocol
|
||||
- 主站以内嵌挂载或原生整合方式承载
|
||||
|
||||
而不是:
|
||||
|
||||
- 长期保留 `iframe + postMessage + fallback` 作为正式方案
|
||||
|
||||
`iframe` 在当前阶段的价值是:
|
||||
|
||||
- 降风险切流
|
||||
- 降低对主站的侵入
|
||||
|
||||
它不应成为最终交付形态。
|
||||
|
||||
---
|
||||
|
||||
## 7. 建议采用的参考体系
|
||||
|
||||
## 7.1 最值得参考:行为模型参考
|
||||
|
||||
优先级最高的不是某个“现成树组件”,而是行为模型参考。
|
||||
|
||||
推荐优先参考:
|
||||
|
||||
- `headless-tree`
|
||||
|
||||
主要借鉴内容:
|
||||
|
||||
- row model
|
||||
- selection
|
||||
- focus
|
||||
- keyboard
|
||||
- drag and drop 状态拆分
|
||||
|
||||
这类参考最适合解决:
|
||||
|
||||
- 为什么树一旦复杂就开始失控
|
||||
- 为什么文件树与页面树经常需要重复写交互
|
||||
|
||||
## 7.2 产品结构参考
|
||||
|
||||
推荐参考:
|
||||
|
||||
- `AppFlowy`
|
||||
- `AFFiNE`
|
||||
|
||||
主要借鉴内容:
|
||||
|
||||
- Sidebar 与页面树的产品层边界
|
||||
- 工作区 / 页面 / 文档之间的协作关系
|
||||
- 页面树如何作为知识产品的导航层
|
||||
|
||||
不建议直接借:
|
||||
|
||||
- 大量样式实现细节
|
||||
- 与当前架构不一致的事实源模型
|
||||
|
||||
## 7.3 Rust Web UI 参考
|
||||
|
||||
推荐参考:
|
||||
|
||||
- `Leptos`
|
||||
- `radix-leptos`
|
||||
- `thaw`
|
||||
|
||||
主要用途:
|
||||
|
||||
- 实现正式 Rust Web tree shell
|
||||
- 补齐 collapsible / menu / scroll area / overlay 等 primitives
|
||||
|
||||
## 7.4 不建议作为主导参考的对象
|
||||
|
||||
以下可以看,但不应成为主导路线:
|
||||
|
||||
- 终端树组件
|
||||
- 桌面 GUI 树组件
|
||||
- 纯列浏览器方案
|
||||
|
||||
原因是它们无法直接回答当前最核心的问题:
|
||||
|
||||
- 如何在 Web 主站中实现产品级页面树 / 文件树
|
||||
|
||||
---
|
||||
|
||||
## 8. 下一阶段的任务拆分建议
|
||||
|
||||
## 8.1 阶段 A:修正当前过渡壳中的协议闭环问题
|
||||
|
||||
只处理阻塞项:
|
||||
|
||||
- 修正 `filetree` 模式闭环
|
||||
- 校正 projection / mode / consumer 对应关系
|
||||
- 补齐最小测试
|
||||
|
||||
这一阶段不做:
|
||||
|
||||
- 大规模 UI 打磨
|
||||
- 新交互堆叠
|
||||
|
||||
## 8.2 阶段 B:产出页面树 / 文件树产品交互合同
|
||||
|
||||
必须单独落文档,至少包含:
|
||||
|
||||
- 文件树对标 VS Code 的功能矩阵
|
||||
- 页面树对标 Wolai / Notion 的功能矩阵
|
||||
- 当前已实现 / 未实现 / 不做 的判定
|
||||
- 对 projection 和 command 的新增要求
|
||||
|
||||
## 8.3 阶段 C:实现正式 tree row model 与状态骨架
|
||||
|
||||
这一阶段要优先完成:
|
||||
|
||||
- row model
|
||||
- selection model
|
||||
- focus model
|
||||
- keyboard model
|
||||
- drag state
|
||||
- context menu entry model
|
||||
|
||||
这一层最好先独立,再挂 UI。
|
||||
|
||||
## 8.4 阶段 D:实现产品级 Rust Web tree shell
|
||||
|
||||
这一阶段才进入正式 UI:
|
||||
|
||||
- 页面树 renderer
|
||||
- 文件树 renderer
|
||||
- picker renderer
|
||||
- 统一 shell 挂载
|
||||
|
||||
## 8.5 阶段 E:收敛旧树实现
|
||||
|
||||
最后再做:
|
||||
|
||||
- 旧文件树 helper 清理
|
||||
- 旧页面树 helper 清理
|
||||
- 旧 Sidebar 树域状态清理
|
||||
- 旧 consumer 收口
|
||||
|
||||
---
|
||||
|
||||
## 9. 最终结论
|
||||
|
||||
当前实现与设计之间,不存在“方向性错误”,但存在明显的“阶段性落差”。
|
||||
|
||||
这个落差的本质不是:
|
||||
|
||||
- 少几个按钮
|
||||
- 样式还不够像
|
||||
|
||||
而是:
|
||||
|
||||
- 当前完成的是树域过渡 shell
|
||||
- 目标要求的是产品级树控件系统
|
||||
|
||||
因此,接下来应明确口径:
|
||||
|
||||
- 当前 git 中的新树实现,定义为**过渡验证层**
|
||||
- 下一阶段任务,定义为**产品级树域控件重建**
|
||||
|
||||
只有这样,团队后续的修改方向才不会继续发散。
|
||||
@@ -0,0 +1,658 @@
|
||||
# 4-10 [recycle] 树域 Rust 家族化执行清单 v1
|
||||
|
||||
> 更新时间:2026-04-26
|
||||
>
|
||||
> 回收说明(2026-04-28):本文是早期总执行清单,未完成项已被 `4-11`、`4-16`、`4-18` 拆分并覆盖。本文保留为历史执行口径,不再作为当前 `process` 入口;后续以 `design/04-tree-domain/process/4-16-tree-rust-family-remaining-final-runtime-checklist-v1.md` 及 `done/4-11`、`done/4-18` 为准。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/old/04-tree-domain/process/4-9-tree-rust-family-cutover-remaining-architecture-and-capability-preservation-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.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-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-11-tree-rust-family-final-renderer-and-host-thinning-checklist-v1.md`
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份清单只服务一件事:
|
||||
|
||||
> **把树域从“Rust 持有语义、React 持有主执行面”的过渡态,推进到“Rust 家族同时持有语义与主执行面”的下一阶段。**
|
||||
|
||||
本文不是上位方案稿,不重复讨论:
|
||||
|
||||
- 为什么要继续 Rust 家族化
|
||||
- 为什么 `Convex` 要保留
|
||||
- 为什么 `page tree / file tree` 不能退化
|
||||
|
||||
这些已经在 `4-9` 与关联文档中固定。
|
||||
|
||||
本文只给:
|
||||
|
||||
- 分阶段执行顺序
|
||||
- 每阶段 checklist
|
||||
- 每阶段阻塞项
|
||||
- 每阶段验收口径
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前基线
|
||||
|
||||
当前默认真实状态(2026-04-25):
|
||||
|
||||
- 树域 truth 已收口到 `tree-first graph kernel`
|
||||
- `Convex` 继续提供存储、同步、内容与实时底座
|
||||
- `mnote-web` 已具备 projection / command / transport 基础出口
|
||||
- `page tree / file tree / picker` 已开始消费 projection family
|
||||
- 主站默认 `treeRendererFamily` 已切到 `rust_family`
|
||||
- `page tree / file tree / picker` 默认主路径已进入 same-origin iframe compat host
|
||||
- 但当前切主的是 `compat host`,不是 final Rust renderer;宿主与 fallback 仍保留
|
||||
|
||||
因此本清单的目标不是“再做一轮 projection 收口”,而是:
|
||||
|
||||
- 补齐 Rust 侧剩余契约
|
||||
- 迁掉主路径 React 树 renderer
|
||||
- 保住 `page tree / file tree` 现有能力
|
||||
|
||||
---
|
||||
|
||||
## 3. 总体执行顺序
|
||||
|
||||
建议固定为下面五段:
|
||||
|
||||
1. `Phase A`:补齐 `file_tree` Rust 直接输出
|
||||
2. `Phase B`:完成 `tree.*` 命令主路径切换
|
||||
3. `Phase C`:补齐树域正式 realtime contract
|
||||
4. `Phase D`:迁移 `page tree` Rust 家族 renderer
|
||||
5. `Phase E`:迁移 `file tree / picker` Rust 家族 renderer,并收薄宿主
|
||||
|
||||
原因:
|
||||
|
||||
- 先补 `file_tree` 输出,避免 renderer 迁移时继续背前端 adapter。
|
||||
- 先收命令与 realtime,避免 UI 壳迁完后还卡在旧协议。
|
||||
- `page tree` 先迁,能先验证主行 renderer 与交互骨架。
|
||||
- `file tree` 后迁,避免最复杂对象面成为第一块落地风险点。
|
||||
|
||||
---
|
||||
|
||||
## 4. Phase A:补齐 `file_tree` Rust 直接输出
|
||||
|
||||
### 当前进度(2026-04-25)
|
||||
|
||||
- [x] 前端 `file tree` 主路径已优先消费 Rust `kernelFileTreeProjection.items`
|
||||
- [x] `/api/sidebar`、SSR loader、`/api/mnote-web/stream` 已补 `kernelFileTreeProjection`
|
||||
- [x] 非过滤主路径不再默认依赖 `pageRows + assetsByDoc` 拼装
|
||||
- [x] `file tree` 同源 host 的 inline override 已收口到 `kernel file_tree items`
|
||||
- `inlineFileTreeRows` 旧注入边界已移除
|
||||
- `TreeShellIframeHost` 不再接受 `FileTreeRow[]` 作为正式 inline renderer 输入
|
||||
- [x] 搜索过滤态不再回退 `pageRows + assets` 二次重建
|
||||
- `buildVisibleRows` 缺少 `kernel file_tree items` 时稳定返回空列表
|
||||
- 旧 `filterKernelFileTreeProjectionItems` 宿主裁剪 helper 已移除
|
||||
- 正式 Rust 搜索 projection 已在 `4-11 Phase H` 接入 3000 同源主链
|
||||
|
||||
### 目标
|
||||
|
||||
让 `file_tree` 不再依赖当前前端在 `pageRows + assets` 上继续拼主路径可见行语义。
|
||||
|
||||
### checklist
|
||||
|
||||
- [x] 盘点当前 `file_tree` 仍由前端 adapter 补齐的对象语义:
|
||||
- `index.md`
|
||||
- 附件
|
||||
- `mindmap` 文件夹
|
||||
- 导图子附件
|
||||
- 表格 / 书籍 / PDF 等扩展对象
|
||||
- [x] 在 Rust 侧明确 `file_tree` projection 的正式输出字段:
|
||||
- `resource_kind`
|
||||
- `asset_kind`
|
||||
- `icon_hint`
|
||||
- `expandable`
|
||||
- `expanded_by_default`
|
||||
- [x] 为 `index / asset / asset_folder / mindmap / table / book / pdf` 补 fixture
|
||||
- `tree-delta.test.ts` 已覆盖 delta 重建后的 `mindmap / pdf / book / table`
|
||||
- `route.test.ts` 已覆盖 snapshot route 中的 `index / asset / asset-folder / mindmap / table / book / pdf`
|
||||
- [x] 为 `file_tree` route 增加契约测试
|
||||
- `/api/mnote-web/stream` route snapshot 已断言 `file_tree` item contract 与 `iconHint`
|
||||
- [x] 把前端 `file tree` 的主路径输入改成“消费 Rust 直接输出的 item 列表”,而不是继续拼对象真相
|
||||
- `sidebar` 主链默认输入已收口到 `kernelFileTreeProjection.items`
|
||||
- `rust_family` host 已不再接受 `inlineFileTreeRows` 旧 rows 注入
|
||||
|
||||
### 阻塞项
|
||||
|
||||
- `file_tree` canonical 基础对象语义已由 projection contract 覆盖
|
||||
- 搜索态已接入 Rust 搜索 projection 主链;更完整的搜索输入扩展与 final renderer 收口已转入 `4-11 Phase H/F`
|
||||
|
||||
### 验收口径
|
||||
|
||||
- `file_tree` 主路径不再依赖临时 adapter 维持对象层级
|
||||
- Rust route 输出足以直接驱动 renderer
|
||||
- `index.md / asset-folder / asset` 行语义在 fixture 与 route test 中稳定存在
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase B:完成 `tree.*` 命令主路径切换
|
||||
|
||||
### 当前进度(2026-04-25)
|
||||
|
||||
- [x] 已确认正式命令名:
|
||||
- `create -> tree.node.create`
|
||||
- `rename -> tree.node.rename`
|
||||
- `move -> tree.subtree.move`
|
||||
- [x] 已新增前端 `/api/tree/commands` route,主路径可直接发正式 tree action
|
||||
- [x] `createDocumentCommand` 已切到 `/api/tree/commands`
|
||||
- [x] `renameDocumentCommand` 已切到 `/api/tree/commands`
|
||||
- [x] `moveDocumentCommand` 已切到 `/api/tree/commands`
|
||||
- [x] `delete / restore / purge / embed / copy-tree` 已切到 `/api/tree/commands`
|
||||
- [x] `create` 保留本地 `ensureDocumentScaffold` 副作用,不因切流丢失
|
||||
- [x] `/api/documents/create` 已收薄为 compat alias,转发 `/api/tree/commands`
|
||||
- [x] `/api/documents/title` 已按命令语义分流:
|
||||
- 树重命名兼容请求 -> `/api/tree/commands`
|
||||
- `page.head.updateTitle` -> `page write`
|
||||
- [x] `/api/documents/move` 已收薄为 compat alias,转发 `/api/tree/commands`
|
||||
- [x] `/api/documents/delete / restore / purge / embed / copy-tree` 已收薄为 compat alias,转发 `/api/tree/commands`
|
||||
- [x] `bridge-runtime` 已补 `tree.node.archive / restore / purge / embed` 与 `tree.subtree.copy` 别名执行计划
|
||||
- [x] `tree.*` 主路径已补路由 / shared client / compat alias / runtime 回归测试
|
||||
- [x] `move` 的 target-parent legality、self / descendant 拦截、canonical `parent_id / sort_order / workspace_id / updated_at` 已下沉到 Convex `documents.move`
|
||||
- [x] `move legality` 已继续离开 Next route 结果拼装,开始转到 Rust bridge-runtime 计算
|
||||
- `/api/tree/commands` 不再自己构造 `movePreflight`
|
||||
- Next route 现在只把 `documents` 快照作为 `preflightData` 交给 Rust
|
||||
- Rust bridge-runtime 已可从快照推导 target parent / ancestor chain 并拒绝 descendant / missing-parent
|
||||
- [ ] `move legality` 与更完整排序规则仍未完全进入最终 Rust kernel 主链;当前实际写入仍停留在 Convex compat mutation
|
||||
- Rust `bridge-runtime` 已新增 canonical move order plan,并在 command plan 中输出 `normalizedMove`
|
||||
- Convex `documents.move` 已增加 `normalizedMove` 可选校验,执行前确认当前 patch plan 与 Rust plan 一致
|
||||
- 最终 Rust 写路径执行下沉仍转入 `4-11 Phase I`
|
||||
|
||||
### 目标
|
||||
|
||||
让树域主路径默认走正式 `tree.*` 命令面,`documents.*` 退到 compat。
|
||||
|
||||
### checklist
|
||||
|
||||
- [x] 盘点当前主路径仍在调用的 `documents.*` 入口:
|
||||
- create
|
||||
- rename
|
||||
- move
|
||||
- archive
|
||||
- restore
|
||||
- purge
|
||||
- embed
|
||||
- copy-tree
|
||||
- [x] 明确每条命令对应的 `tree.*` 正式名
|
||||
- [x] 前端 shared command client 默认发 `tree.*`
|
||||
- [x] `documents.*` 仅保留 alias / compat
|
||||
- [ ] 把 move legality、target parent、position normalize 继续从 Convex compat mutation 回收给 Rust kernel
|
||||
- legality 与 normalized order plan 已在 Rust 可测试;TS transport 已透传 `normalizedMove`;Convex 已可选校验 Rust plan;最终写入仍待迁移
|
||||
- [x] 为 `tree.*` 路径补命令回归测试
|
||||
|
||||
### 阻塞项
|
||||
|
||||
- `embed / copy-tree` 仍有较强前端语义残留
|
||||
- move legality 与排序规则已离开 Next route 拼装;排序计划已进入 Rust 可观测 plan 并由 Convex 可选校验,但实际执行仍待下沉
|
||||
|
||||
### 验收口径
|
||||
|
||||
- 新增主路径不再继续扩写 `documents.*`
|
||||
- 主调用路径默认发 `tree.*`
|
||||
- 组件层不再承担长期树命令语义解释
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase C:补齐树域正式 realtime contract
|
||||
|
||||
### 当前进度(2026-04-24)
|
||||
|
||||
- [x] `tree stream` 前端主链已从“freshness 仲裁”收口到“stream live 优先,query/fallback 兜底”
|
||||
- [x] `usePreferredSidebarSnapshot` 已按 `treeStream.status` 做主链选择:
|
||||
- `live / connecting / idle` 有 stream 数据时优先 stream
|
||||
- `fallback` 时回退 query
|
||||
- [x] `useSidebarData` 手动 `refetch` 已收窄为临时 snapshot:
|
||||
- 仅在当前 live 基线未变化时暂时覆盖
|
||||
- live 基线变化后回到新的 live snapshot
|
||||
- [x] `/api/mnote-web/stream` 已升级为同源长连接 SSE:
|
||||
- 首帧固定发 `snapshot`
|
||||
- 后续按最新 `command_log` cursor 轮询检测变化
|
||||
- 当前已支持最小 `delta/noop/resync` 分流,不伪造不稳定的复杂树增量
|
||||
- [x] stream cursor 已从“复用 overview 分页 cursor”改成“stream 自己的 latest command-log cursor”
|
||||
- [x] stream cursor 已补 `domain_events` 感知
|
||||
- 当 `command_logs` 未推进、但 `domain_events` 新增时,不再静默停在旧 cursor
|
||||
- 当前最小口径先触发 `resync`,避免 workspace realtime 漏感知事件侧推进
|
||||
- [x] `workspace / subtree` 双 scope 已在 route + test 形成正式 envelope 边界:
|
||||
- `workspace -> sidebar_tree`
|
||||
- `subtree(rootNodeId) -> page_tree`
|
||||
- [x] `/api/tree/commands` 主路径已开始写入 `bridgeLogs`:
|
||||
- create / rename / archive / purge / embed / copy / move / restore 都会推进 stream 可见 cursor
|
||||
- failed mutation 也会写 failure artifact,但 stream 主查询已收窄为只看 `succeeded`
|
||||
- [x] stream 当前已能消费显式 `streamDelta`:
|
||||
- `create -> upsert_document`
|
||||
- `rename -> upsert_document(局部 patch)`
|
||||
- `page.head.updateTitle -> upsert_document(局部 patch)`
|
||||
- `archive / purge -> remove_document`
|
||||
- `embed -> noop`
|
||||
- `move -> move_document(细粒度 delta)`
|
||||
- `restore -> upsert_document(细粒度 delta)`
|
||||
- `copy -> upsert_documents(细粒度 delta)`
|
||||
- `documents` 列表不可用时,仍保留 `replace_sidebar / replace_documents` 作为兼容兜底
|
||||
- [x] `move / restore / copy` 已从“只能纯 `resync`”推进到显式 delta
|
||||
- `move` 已推进到 `move_document`
|
||||
- `restore` 已推进到 `upsert_document`
|
||||
- `copy` 已推进到 `upsert_documents`
|
||||
- Rust `bridge-runtime` 已开始输出 `streamDeltaHint`,Next route 仅按 Rust plan hint 与 mutation result 物化 delta
|
||||
- [x] 已新增树域 renderer 统一 delta 应用边界:
|
||||
- `tree-delta.ts` 已补 `applyTreeStreamDeltaToProjectionState`
|
||||
- `sidebar_tree / page_tree / file_tree` 已可从同一 delta 边界派生各自输入
|
||||
- [x] `replace_documents` delta 重建时已保留 `mindmapAssetChildren`
|
||||
- `file_tree` 不再把 `asset-folder` 在 delta 重建时退化成普通 `asset`
|
||||
- [x] Rust / 3000 同源 stream 已能从单条新 `domain_event.payload.streamDelta` 发出 `delta`
|
||||
- [x] Rust / 3000 同源 stream 已能对同一 `command_id` 的 command log + domain event 双写做 delta 去重
|
||||
- `streamDelta` 一致时发一次 `delta`
|
||||
- 不一致或多条混杂时继续保守 `resync`
|
||||
- [x] Rust command 侧已开始生成正式 tree delta / event hint:
|
||||
- `streamDeltaHint` 覆盖 create / rename / archive / restore / purge / embed / move / copy
|
||||
- `domainEventHint` 覆盖 `tree.node.created / renamed / archived / restored / purged / embedded / duplicated` 与 `tree.subtree.moved / copied`
|
||||
- Next route 不再按 action 手写主 delta,而是按 Rust plan hint + mutation result 物化 `streamDelta`
|
||||
- [x] resource command 侧已开始进入正式 delta / event hint:
|
||||
- `tree.resource.copy / move / upload` 已输出 `asset_result -> upsert_assets`
|
||||
- `domainEventHint` 覆盖 `tree.resource.copied / moved / uploaded`
|
||||
- 资源 copy/move/upload 的 artifact 已由 3000 Rust artifact writer 持久化
|
||||
- [ ] Rust 写路径仍未覆盖所有主写入口的完整 domain event 持久化
|
||||
- `mnote-web /api/tree/commands` 已可由 Rust transport 直接生成并持久化 `command_logs / domain_events`
|
||||
- 3000 tree/media 主写入口与文档类 Rust transport adapter 已改为 Rust artifact writer:
|
||||
- `/api/tree/commands`
|
||||
- `/api/media/batch`
|
||||
- `/api/media/upload`
|
||||
- `page-command-adapter`
|
||||
- `page-lifecycle-command-adapter` 的 `create / move / delete / restore / duplicate / copy_tree`
|
||||
- `save-command-adapter`
|
||||
- `metadata-command-adapter` 的 `title / stats / options`
|
||||
- `lib/documents/block-command-adapter`
|
||||
- `page-write-command-adapter` 的 `page.head.updateTitle / page.layout.updateOptions / page.body.save`
|
||||
- 当前显式 adapter 层已不再直接调用 TS `recordBridgeCommandArtifacts(...)`
|
||||
- [ ] `3000` 仍是浏览器入口,但 polling transport 主体已在 Rust SSE;Next route 仍保留薄代理与过渡职责
|
||||
|
||||
### 目标
|
||||
|
||||
让树域从“可用 stream + fallback”进入正式 `snapshot + delta + resync` 合同。
|
||||
|
||||
### checklist
|
||||
|
||||
- [x] 明确 workspace 与 subtree 两类 stream scope
|
||||
- [x] 统一 `snapshot / delta / resync / cursor` 协议
|
||||
- `snapshot / cursor` 已在同源 stream route 固定
|
||||
- `delta` 已支持最小显式 contract,但复杂树变更仍待 Rust 正式事件面补齐
|
||||
- `resync` 仍是复杂树变更与漂移场景的保守回退
|
||||
- 当前勾选口径以 `3000` 同源 Next route contract 为准,`server.ts` / `server.test.ts` / `route.test.ts` 已覆盖;Rust 原生 transport 仍待继续收口
|
||||
- [x] 补 cursor 漂移与 resync 策略
|
||||
- [x] 明确大漂移时的回退口径
|
||||
- 当前保守口径:无法稳定解释的新 cursor 统一退 `resync snapshot`
|
||||
- [x] 为 `page tree / file tree / sidebar_tree` 补事件 fixture
|
||||
- `server.test.ts` 已覆盖 `sidebar_tree` snapshot / delta / resync
|
||||
- `route.test.ts` 已覆盖 `page_tree` subtree snapshot fixture
|
||||
- `route.test.ts` 与 `tree-delta.test.ts` 已覆盖 `file_tree` 的 `document / index / asset-folder / asset` 行语义
|
||||
- [x] 为树域 renderer 定义统一 delta 应用边界
|
||||
- 当前口径是 `documents/sidebar -> projection state` 的统一边界,不等于 final Rust domain-event 细粒度 reducer
|
||||
- [x] 让主路径不再主要依赖 freshness 选择与补偿式 refetch
|
||||
- sidebar stream 主链已变成“长连接 snapshot/delta/resync”
|
||||
- query/fallback 继续保留为护栏
|
||||
|
||||
### 阻塞项
|
||||
|
||||
- Next route 级过渡 transport 仍在承担一部分正式职责
|
||||
- `delta` payload 的对象覆盖面还不够完整
|
||||
- Rust command 已给 `move / restore / copy` 输出 delta/event hint;3000 tree/media 主写入口 artifact 持久化与显式 adapter 层已切到 Rust artifact writer;剩余未完成项转入更正式的 block/save-snapshot domain-event contract 收口
|
||||
- final domain event delta contract 已转入 `4-11 Phase J`
|
||||
|
||||
### 验收口径
|
||||
|
||||
- 树域实时主链存在正式协议说明与测试
|
||||
- 主路径能稳定处理 snapshot、delta、resync
|
||||
- fallback 成为护栏,而不是主同步策略本身
|
||||
|
||||
---
|
||||
|
||||
## 7. Phase D:迁移 `page tree` Rust 家族 renderer
|
||||
|
||||
### 当前进度(2026-04-24)
|
||||
|
||||
- [x] 前端 `page tree / file tree` surface 已显式暴露 `rendererFamily` 边界
|
||||
- [x] `SidebarTreeSurface` 已成为更正式的 renderer host 边界,而不只是隐式 React 直连
|
||||
- [x] 已新增 `TreeShellHost`,`page tree / file tree / picker` 在 `rust_family` 下会进入显式宿主分支,而不再只是透传 `data-renderer-family`
|
||||
- [x] 主站已存在极薄挂载位;当前 `rust_family` 分支先受控包住 React fallback,后续再替换成真正的 Rust family renderer
|
||||
- [x] 已新增 `3000` 同源 `/api/tree/shell` proxy route;浏览器不再直接持有 `mnote-web` base url
|
||||
- [x] 未过滤 `page tree` 在 `rust_family` 下已可切到 same-origin iframe compat host,并 bridge:
|
||||
- `tree.navigate`
|
||||
- `tree.page.context-menu`
|
||||
- `tree.node.created / renamed / moved`
|
||||
- [x] `page tree` 已新增 `tree.page.focus.changed` bridge
|
||||
- 宿主已显式持有 `focusedDocumentId` 本地状态
|
||||
- page focus 不再只停留在 iframe 内部私有状态
|
||||
- iframe host 与 Rust shell contract 已补定向测试
|
||||
- [x] 过滤态 `page tree` 在 `rust_family` 下已可通过 inline override 进入 same-origin iframe compat host
|
||||
- 浏览器端直接注入过滤后的 projection rows
|
||||
- 不再因为 filter 非空而强制回退 React fallback
|
||||
- [x] 过滤态 `page tree` 即使筛到 0 条,也会继续通过 inline override 留在 same-origin iframe compat host
|
||||
- [x] 默认主路径已切到 `rust_family` same-origin compat host
|
||||
- `task112` 主站 smoke 已验证 page tree hostKind 为 `iframe`
|
||||
- 当前切主的是 compat host,不等于 final Rust renderer 已完成
|
||||
- [x] `page tree` 主路径 React renderer 已退出
|
||||
- `TreeShellSurface` 缺少 iframe 宿主时只显示 `page-tree-renderer-removed`
|
||||
- 主路径运行时已不再挂载旧 React `PrivateTree`
|
||||
- [x] `task112` 已补 page tree 的 create / rename / 大树滚动真实 smoke
|
||||
- compat shell hover 动作区已用于实跑新建子页面与重命名
|
||||
- 大树场景下,键盘导航可把目标行稳定带入可视区
|
||||
- [ ] 当前 `page tree` 仍是 `mnote-web tree shell compat host`,不是最终 Rust renderer
|
||||
- [ ] 过滤态 `page tree` 当前仍是 compat host + inline override,不是最终 Rust family renderer
|
||||
|
||||
### 目标
|
||||
|
||||
让 `page tree` 的主渲染与主交互骨架退出当前 React 主壳。
|
||||
|
||||
### checklist
|
||||
|
||||
- [ ] 明确 `page tree` renderer 最小输入:
|
||||
- projection items
|
||||
- local UI state
|
||||
- command dispatcher
|
||||
- [ ] 抽离正式模块边界:
|
||||
- row model
|
||||
- focus model
|
||||
- keyboard model
|
||||
- DnD state machine
|
||||
- context action registry
|
||||
- [ ] 实现 Rust 家族 page tree renderer
|
||||
- [x] 迁移展开/折叠
|
||||
- [x] 迁移当前页高亮与祖先自动展开
|
||||
- [x] 迁移 hover 动作区
|
||||
- [x] 迁移右键菜单入口
|
||||
- [x] 迁移键盘导航
|
||||
- [x] 迁移基础拖拽排序
|
||||
- [x] 在主站预留极薄挂载位
|
||||
- [x] 用 feature flag 控制新旧 page tree 切换
|
||||
|
||||
### page tree 能力保留硬门槛
|
||||
|
||||
- [x] 稳定层级展开/折叠
|
||||
- [x] 当前页高亮
|
||||
- [x] 祖先自动展开
|
||||
- [x] 新建子页面
|
||||
- [x] 重命名
|
||||
- [x] 页面移动
|
||||
- [x] 上下文菜单
|
||||
- [x] 键盘导航
|
||||
- [x] 基础拖拽排序
|
||||
- [x] 大树场景稳定滚动
|
||||
|
||||
说明:
|
||||
|
||||
- 上面已勾选的 `右键菜单入口 / 上下文菜单 / feature flag`,当前口径是“same-origin compat host + 宿主 bridge 已闭环”,不等于 final Rust renderer 已切主。
|
||||
- 本轮新增勾选的 `展开/折叠 / 当前页高亮 / 祖先自动展开 / 键盘导航 / 拖拽排序 / 页面移动 / 新建子页面 / 重命名 / 大树场景稳定滚动 / hover 动作区`,口径同样是“默认 `rust_family` compat host + `task112` 真实 smoke 已通过”,不等于 final Rust renderer 已完成。
|
||||
|
||||
### 阻塞项
|
||||
|
||||
- 宿主边界仍过厚
|
||||
- 旧 React 组件仍承担主交互状态机
|
||||
|
||||
### 验收口径
|
||||
|
||||
- 至少一条默认真实流量使用 Rust 家族 page tree renderer
|
||||
- 不再依赖当前 React `PrivateTree` 作为主路径 renderer
|
||||
- `page tree` 功能不低于当前产品合同
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase E:迁移 `file tree / picker` Rust 家族 renderer,并收薄宿主
|
||||
|
||||
### 目标
|
||||
|
||||
在 `page tree` 主路径稳定后,迁移 `file tree` 与 `picker`,并继续收薄主站宿主职责。
|
||||
|
||||
### 当前进度(2026-04-24)
|
||||
|
||||
- [x] `picker` 空查询态与搜索态已统一复用 `TreePickerSurface`
|
||||
- [x] `picker` 已开始复用与 tree surface 同一 renderer family 边界
|
||||
- [x] `file tree / picker / page tree` surface 已显式输出 `data-renderer-family`
|
||||
- [x] `file tree / picker / page tree` 已统一经由 `TreeShellHost` 进入显式 host 选择边界,并输出 `data-tree-host-kind`
|
||||
- [x] `picker` 已接入 `rust_family` host 边界;当前 host 仍先包住 React fallback,不代表已切到 Rust renderer
|
||||
- [x] 未过滤 `picker` 空查询态在 `rust_family` 下已切到 same-origin iframe compat host
|
||||
- [x] `picker` 搜索结果态在 `rust_family` 下已切到 same-origin iframe compat host:
|
||||
- 浏览器端通过 inline override 注入搜索结果
|
||||
- 不再因为搜索态而强制回退 React fallback
|
||||
- [x] `picker` 在 `rust_family` 下,空查询且无可选页面时也会继续留在 same-origin iframe compat host
|
||||
- 不再因为空结果而直接退回宿主内纯文本 div
|
||||
- [x] `picker` 根节点与排除项逻辑已补组件回归:
|
||||
- 空查询时保留根节点
|
||||
- `excludeIds` 不再把当前页误放回候选列表
|
||||
- 组件测试与真实浏览器核对已覆盖该口径
|
||||
- [x] `picker` 空查询态也已支持键盘高亮与 Enter 选中
|
||||
- 不再只在搜索结果态依赖输入框 keydown hack
|
||||
- React fallback 与 same-origin compat host 都会同步当前高亮项
|
||||
- [x] `picker` same-origin compat host 已透传 `activePickerItemKey`
|
||||
- 根节点高亮不再只能停留在 React fallback
|
||||
- Rust tree shell picker 页面测试已覆盖该 query/state
|
||||
- [x] `picker` 在 `rust_family` 下已把输入框键盘命令继续下沉到 same-origin compat shell
|
||||
- 宿主改为发送 `tree.picker.command`
|
||||
- iframe 会回传 `tree.picker.focus.changed`
|
||||
- `MoveEmbedPickerDialog` 不再直接决定 rust_family 主路径的高亮切换与 Enter 选中
|
||||
- [x] `picker` runtime 状态更新已对齐 Rust renderer input reducer contract
|
||||
- `mnote-web /tree` 与 3000 inline host 均暴露 `rust_picker_state_reducer_v1`
|
||||
- `tree.picker.command` 的 next/previous/home/end/pick/focus 已先统一经 `applyPickerStateAction`
|
||||
- 当前仍是 compat JS 镜像执行合同,不代表 final Rust runtime 已接管
|
||||
- [x] 未过滤 `file tree` 在 `rust_family` 下已可切到 same-origin iframe compat host,并 bridge:
|
||||
- `tree.navigate`
|
||||
- `tree.filetree.context-menu`
|
||||
- `tree.asset.open`
|
||||
- `tree.node.created / renamed / moved`
|
||||
- [x] `file tree` 已新增 `tree.filetree.selection.changed` bridge:
|
||||
- 宿主可同步 `selectedRowIds / anchorRowId / focusedRowId`
|
||||
- 现有复制 / 删除 / 粘贴等依赖宿主选择态的链路不再只能停留在 React `FileTree`
|
||||
- [x] `file tree` selection truth 已从宿主 legacy reducer 收薄为 renderer event snapshot:
|
||||
- `rust_family` 下 `Sidebar` 只订阅 `tree.filetree.selection.changed`
|
||||
- 删除 / 复制 / 粘贴 / 上传 / 内部 drop 只读消费 renderer snapshot
|
||||
- `SidebarTreeSurface` 不再向 Rust file tree host 传 `selectedRowIds` 控制 prop
|
||||
- 该项不代表 compat runtime 内部选择算法已经 Rust runtime 化;后续仍转入 `4-11 Phase F/G`
|
||||
- [x] `file tree` runtime 选择入口已对齐 Rust renderer input reducer contract
|
||||
- `mnote-web /tree` 与 3000 inline host 均暴露 `rust_filetree_selection_reducer_v1`
|
||||
- 单选 / 多选 / Shift 范围选 / 右键选中 / 可见行归一化 / drag rows 解析已先统一经 `applyFileTreeSelectionAction`
|
||||
- 当前仍是 compat JS 镜像执行合同,不代表 final Rust runtime 已接管
|
||||
- [x] `file tree` 删除目标归一化已进入 Rust preflight 主链:
|
||||
- `tree.filetree.delete.preflight` 已由 Rust `bridge-runtime` 输出 `fileTreeDeletePlan`
|
||||
- 3000 同源 `/api/tree/filetree/delete-preflight` route 与 client 已覆盖
|
||||
- `Sidebar` 删除链已不再自己推导 rust_family 主路径的 doc/asset 删除列表
|
||||
- [x] `file tree` 粘贴目标与复制分类已进入 Rust preflight 主链:
|
||||
- `tree.filetree.paste.preflight` 已由 Rust `bridge-runtime` 输出 `fileTreePastePlan`
|
||||
- 3000 同源 `/api/tree/filetree/paste-preflight` route 与 client 已覆盖
|
||||
- `Sidebar` 粘贴链在 `rust_family` 下已不再自己推导目标页面、`doc/index` 递归语义和可复制 asset 列表
|
||||
- [x] `file tree` 外部上传目标推导已进入 Rust preflight 主链:
|
||||
- `tree.filetree.upload-target.preflight` 已由 Rust `bridge-runtime` 输出 `fileTreeUploadTargetPlan`
|
||||
- 3000 同源 `/api/tree/filetree/upload-target-preflight` route 与 client 已覆盖
|
||||
- `Sidebar` 外部文件 drop 链已不再自己推导 target row、focused row、workspace fallback 与 mindmap target
|
||||
- [x] same-origin compat shell 已补最小 `file tree` 交互骨架:
|
||||
- 单选 / 多选 / Shift 范围选
|
||||
- 右键聚焦选中
|
||||
- 空白区清空选择
|
||||
- `doc / index / asset` 双击打开
|
||||
- [x] same-origin compat shell 已补 `file tree` 内部拖放 bridge:
|
||||
- iframe 可发 `tree.filetree.internal-drop`
|
||||
- 宿主已回接现有 `onInternalDrop`
|
||||
- `Alt` copy / move 区分继续沿用宿主已有语义
|
||||
- [x] same-origin compat shell 已补 `file tree` 外部文件拖入 bridge:
|
||||
- 行级 drop 与空白区 drop 都会向宿主发 `tree.filetree.external-drop`
|
||||
- 宿主已回接现有 `onDropFiles`
|
||||
- [x] same-origin compat shell 已补最小 `asset-folder` 行语义:
|
||||
- `mindmap` 不再只表现为普通 `asset`
|
||||
- 已可渲染 `asset-folder -> asset` 层级
|
||||
- `asset-folder` 已接入双击打开、右键菜单与 drop target
|
||||
- [x] `file tree` 资源类型图标语义已补最小闭环
|
||||
- same-origin compat shell 已区分 `pdf / image / video / audio / book / table / mindmap`
|
||||
- React fallback `FileTree` 也已补同口径图标分支,避免切流前后视觉合同分裂
|
||||
- [x] 过滤态 `file tree` 在 `rust_family` 下已可通过 inline override 进入 same-origin iframe compat host
|
||||
- 浏览器端已改为直接注入过滤后的 `kernel file_tree items`
|
||||
- 不再因为 filter 非空而强制回退 React fallback
|
||||
- [x] 过滤态 `file tree` 已先从 `kernelFileTreeProjection.items` 收敛正式 item contract,再统一生成 rows 与 same-origin host inline 输入
|
||||
- 不再回退 `pageRows + assets` 二次重建主可见行
|
||||
- `sidebar` 主路径过滤态已优先消费 kernel file tree items
|
||||
- [x] `file tree` 搜索态已切到 Rust projection query 主链
|
||||
- `core-protocol` 的 `KernelProjectionFilter` 已包含 `query`
|
||||
- `bridge-runtime` 的 `kernel.project_view(file_tree)` 可按 `query` 输出命中项与必要祖先,并裁剪 edges
|
||||
- `mnote-web` `/api/tree/projections/file?...&query=` 已透传 query 到 Rust projection
|
||||
- `wolai-frontend` 新增 3000 同源 `/api/tree/projections/file` route,Sidebar 搜索态只消费该 route 返回的 Rust `file_tree` projection items
|
||||
- 旧 `filterKernelFileTreeProjectionItems` 宿主裁剪主链已移除
|
||||
- [x] `file tree` same-origin compat host 已能直接消费 kernel file tree items
|
||||
- 过滤态宿主不再必须先把 `FileTreeRow[]` 重新翻译回 inline item
|
||||
- [x] `file tree` inline override 已移除 `FileTreeRow[]` 正式输入
|
||||
- `TreeShellIframeHost` 现在只接受 `kernel file_tree items` 作为 inline projection contract
|
||||
- [x] `file tree` 默认主路径已切到 `rust_family` same-origin compat host
|
||||
- `task112` 主站 smoke 已验证 file tree hostKind 为 `iframe`
|
||||
- 当前切主的是 compat host;复杂交互执行面仍依赖宿主 bridge
|
||||
- [x] `file tree` 主路径 React renderer 已退出
|
||||
- `TreeShellSurface` 缺少 iframe 宿主时只显示 `file-tree-renderer-removed`
|
||||
- 主路径运行时已不再挂载旧 React `FileTree`
|
||||
- [x] `file tree` 首屏 Rust initial DOM 的 hydrate 后状态 patch 已继续收薄
|
||||
- `TreeShellIframeHost` 在 `data-rust-filetree-renderer=initial_v1` hydrate 成功后,对宿主 active / selection patch 只更新 `data-active / data-selected`
|
||||
- 该项只是减少 compat JS 整树重绘,不等于 final Rust renderer 已完成
|
||||
- [x] 3000 inline host 已减少第二份 projection 注入真相
|
||||
- `TreeShellIframeHost` 主路径现在把 inline projection items 放入 `tree-shell-state.items`
|
||||
- `rendererInput` 与 `items` 同时进入 appState JSON,compat runtime 优先从该合同读取 expanded / selection / picker active / exclude
|
||||
- `__MNOTE_TREE_SHELL_OVERRIDE__` 不再作为 3000 主路径默认注入第二份 items,只保留旧兼容入口
|
||||
- [x] 3000 inline host 已对齐 Rust state reducer 合同
|
||||
- page focus/keyboard 暴露 `rust_page_focus_keyboard_reducer_v1`
|
||||
- filetree selection 暴露 `rust_filetree_selection_reducer_v1`
|
||||
- picker state 暴露 `rust_picker_state_reducer_v1`
|
||||
- 当前只是把默认主路径 compat runtime 的状态更新入口统一到 Rust contract,不等于 final renderer 完成
|
||||
- [ ] `file tree / picker` 的真正 Rust family renderer 仍未接入;当前只是接入 same-origin compat host,而不是最终 renderer
|
||||
- [ ] 宿主仍承担实际命令调度、上传执行与部分可见行 / DOM runtime 过渡职责;还不是最终 Rust family 正式模块边界
|
||||
- 内部 drop / 删除 / 粘贴 / 外部上传目标的目标与对象分类已下沉 Rust preflight,但文件字节读取、菜单状态、实际 transport 与通知仍在宿主
|
||||
|
||||
### checklist
|
||||
|
||||
- [x] 明确 `file tree` renderer 的最小输入与可见行模型
|
||||
- 当前主链最小输入已先统一为 `kernel file_tree items + expanded document ids + expanded asset-folder ids`
|
||||
- [x] 迁移 `doc -> index -> asset-folder -> asset` 可见行语义(compat shell 最小版)
|
||||
- [x] 迁移资源图标与资源菜单分支
|
||||
- 资源图标 contract 已在 `route.test.ts` / `tree-delta.test.ts` 覆盖
|
||||
- `tree.filetree.context-menu` bridge 已在 `tree-shell-iframe-host.test.tsx` 覆盖
|
||||
- [x] 迁移单选 / 多选 / Shift 范围选 / 右键选中
|
||||
- [x] 迁移双击打开资源
|
||||
- [x] 迁移内部拖放(bridge 到宿主既有 drop 处理)
|
||||
- [x] 迁移外部文件拖入上传(bridge 到宿主既有上传处理)
|
||||
- [x] 迁移 copy / move 区分与非法投放校验(Rust preflight 主链 + 宿主 transport 过渡)
|
||||
- [x] 迁移粘贴目标推导与复制分类(Rust preflight 主链 + 宿主 transport 过渡)
|
||||
- [x] 让 `picker` 复用同一 renderer / state family 的轻量模式(先完成统一 `TreePickerSurface`)
|
||||
- [x] 让 `picker` 搜索结果态进入 `rust_family` same-origin compat host
|
||||
- [x] 收掉主路径 React `FileTree` renderer
|
||||
- [x] 把 `picker` 键盘高亮 / Enter 主链继续从宿主 input keydown 收到 iframe shell
|
||||
- [x] 收掉宿主内剩余树结构重建与主交互状态骨架(本批可验证范围)
|
||||
- 3000 主路径已固定为 `state.items + rendererInput`,不再通过 `__MNOTE_TREE_SHELL_OVERRIDE__` 注入第二份 items;React FileTree/PageTree fallback 不再作为 rust_family 主路径。完整 Rust runtime 替换仍归入后续 final renderer。
|
||||
|
||||
### file tree 能力保留硬门槛
|
||||
|
||||
- [x] 页面
|
||||
- [x] `index.md`
|
||||
- [x] 附件
|
||||
- [x] `mindmap` 文件夹
|
||||
- [x] 导图子附件
|
||||
- [x] 多选
|
||||
- [x] 范围选
|
||||
- [x] 资源级右键菜单
|
||||
- [x] 双击打开
|
||||
- [x] 内部拖放
|
||||
- [x] 外部文件拖入
|
||||
- [x] 资源类型图标语义
|
||||
|
||||
### picker 能力保留硬门槛
|
||||
|
||||
- [x] 空态稳定显示
|
||||
- [x] 搜索结果稳定显示
|
||||
- [x] 高亮与键盘选中稳定
|
||||
- 组件回归与 Rust picker shell 定向测试已补
|
||||
- `task113` 已在 `3000` 主站默认 `rust_family` 配置下实跑通过
|
||||
- [x] 根节点与排除项逻辑不回退
|
||||
|
||||
### 阻塞项
|
||||
|
||||
- `file_tree` 搜索过滤已不再由宿主裁剪 items,但 compat shell 内仍有一层把 items 规范化为 DOM 行的过渡逻辑
|
||||
- 选择 truth 对宿主已变成 renderer event snapshot,但 compat runtime 内部仍有 TS 选择/拖放/菜单 DOM 状态机,仍未形成完全独立的 Rust family 正式模块
|
||||
- `file tree` 复杂拖放 / 粘贴 / 上传态的执行仍依赖宿主 bridge;内部 drop、删除、粘贴目标、外部上传目标 preflight 已下沉 Rust,过滤态已不再强制回退 React fallback
|
||||
- `picker` 搜索态虽已进入 same-origin compat host,但当前仍是 compat shell + inline override,不是最终 Rust renderer / 正式搜索合同
|
||||
- `picker` 键盘高亮链已补组件回归、shell query/state 测试与 `3000` 主站真实 smoke
|
||||
- 当前剩余差距不在“键盘能否工作”,而在“仍是 compat shell + 宿主状态机,而非 final Rust renderer”
|
||||
- final renderer 与宿主收薄已转入 `4-11 Phase F/G`
|
||||
|
||||
### 验收口径
|
||||
|
||||
- `file tree` 不再依赖当前 React `FileTree` 作为主路径 renderer
|
||||
- `picker` 复用统一 renderer 家族
|
||||
- 宿主不再承担主要树域交互状态机
|
||||
- 当前实现距离该验收口径仍有明确差距:
|
||||
- same-origin compat host 已接入
|
||||
- `picker` 搜索态已进入 compat host,但仍不是最终 Rust renderer
|
||||
- `file tree` 的内部拖放 / 外部上传 / `asset-folder` 最小语义与搜索 projection 已接入,但还停留在 compat shell + 宿主 bridge
|
||||
- 还不能宣称 `Phase E` 已完成
|
||||
|
||||
---
|
||||
|
||||
## 9. 跨阶段通用回归要求
|
||||
|
||||
每一阶段切流前都必须至少补齐以下回归:
|
||||
|
||||
- [x] 页面树切页 smoke
|
||||
- [x] 页面树展开/折叠 smoke
|
||||
- [x] 页面树拖拽 smoke
|
||||
- [x] 页面树右键菜单 smoke
|
||||
- [x] 文件树多选与范围选 smoke
|
||||
- [x] 文件树双击打开 smoke
|
||||
- [x] 文件树内部拖放 smoke
|
||||
- [x] 文件树外部文件拖入 smoke
|
||||
- [x] picker 搜索 / 空态 / 选中 smoke
|
||||
- [x] stream 中断 fallback smoke
|
||||
|
||||
要求:
|
||||
|
||||
- 不只验证“组件能渲染”
|
||||
- 要验证“关键交互能力未退化”
|
||||
|
||||
补充口径(2026-04-24):
|
||||
|
||||
- 当前以上 10 项由 `scripts/task112-tree-rust-family-regression-smoke.js` 实跑覆盖:宿主页负责切页、右键菜单、文件树双击打开、picker 与 stream fallback;`/api/tree/shell` 直连页负责页面树展开/折叠/拖拽,以及文件树多选、范围选、内部拖放、外部文件拖入。
|
||||
- 其中页面树 / 文件树拖放与 picker 当前主要验证 same-origin compat shell、宿主 bridge 与命令回写链路;宿主完全收薄后的 final renderer 端到端 smoke,仍应在 Phase D / E 收尾时继续补强。
|
||||
|
||||
---
|
||||
|
||||
## 10. 当前最近一步
|
||||
|
||||
按当前优先级,最近一步建议固定为:
|
||||
|
||||
1. 先补 `Phase A`,把 `file_tree` Rust 直接输出缺口补齐。
|
||||
2. 同时推进 `Phase B`,把主路径命令面收口到 `tree.*`。
|
||||
3. 在这两件事没有完成前,不建议宣称“树域主执行面已经可以正式切到 Rust 家族 renderer”。
|
||||
|
||||
原因:
|
||||
|
||||
- 如果 `file_tree` 语义还要靠前端补
|
||||
- 如果命令面还主要停在 compat
|
||||
|
||||
那么换 renderer 只会把现有双轨问题搬进新壳,而不是收掉它。
|
||||
|
||||
---
|
||||
|
||||
## 11. 完成判定
|
||||
|
||||
只有同时满足下面几条,才可以宣称“树域 Rust 家族化这一轮完成”:
|
||||
|
||||
- `page tree / file tree / picker` 主路径 renderer 已进入 Rust 家族
|
||||
- `file_tree` 已由 Rust 直接输出正式对象投影
|
||||
- 主调用路径默认走 `tree.*`
|
||||
- realtime 已进入正式 `snapshot + delta + resync` 合同
|
||||
- 宿主只保留极薄挂载与桥接职责
|
||||
- `page tree / file tree` 的现有能力没有因迁移退化
|
||||
|
||||
---
|
||||
|
||||
## 12. 一句话收口
|
||||
|
||||
这轮工作的正确推进方式不是:
|
||||
|
||||
- “先把现有 React 组件翻译成 Rust”
|
||||
|
||||
而是:
|
||||
|
||||
> **先补齐 `file_tree / command / realtime` 三条主链,再以能力不退化为硬门槛,逐步把 `page tree / file tree / picker` 的主 renderer 和主交互骨架迁入 Rust 家族。**
|
||||
+514
@@ -0,0 +1,514 @@
|
||||
# 4-9 [recycle] 树域 Rust 家族化剩余架构事项与能力保留方案 v1
|
||||
|
||||
> 更新时间:2026-04-23
|
||||
>
|
||||
> 回收说明(2026-04-28):本文是 Rust 家族化下一阶段的早期架构拆解稿,后续已由 `4-10` 执行清单、`4-11` final renderer / host thinning、`4-16` remaining final runtime、`4-18` final DOM shell hard gate 继续拆分和覆盖。本文保留为历史能力保留口径,不再作为当前活跃 `process` 入口。
|
||||
>
|
||||
> 关联文档:
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.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-6-tree-command-protocol-cutover-stage2-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-7-tree-shell-ui-state-boundary-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
|
||||
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
|
||||
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档只回答两个问题:
|
||||
|
||||
1. 在当前长期方向已经固定为 `Rust kernel + Rust web + Leptos island + Convex substrate` 的前提下,树域继续朝 Rust 家族化推进,还剩哪些真正的架构事项。
|
||||
2. 在 `page tree / file tree` 从当前 `TypeScript + Next + React` 主壳向 Rust 家族执行面迁移时,哪些现有能力必须被完整保留,不能为了“换语言”而退化。
|
||||
|
||||
这份文档不是为了重复证明:
|
||||
|
||||
- `Convex` 是否还保留
|
||||
- 树域是否已经具备 projection contract
|
||||
- `Page Aggregate` 是否应该存在
|
||||
|
||||
这些上位结论已经在关联文档中固定。
|
||||
|
||||
本文只做当前阶段最需要的补充收口:
|
||||
|
||||
> **树域已经完成 truth / projection / command 边界的第一轮收口,但主渲染壳仍主要停留在 `TypeScript + Next + React`。下一阶段要解决的,不再是“树是不是 kernel projection”,而是“树域主执行面怎样继续向 Rust 家族迁移,同时不丢掉现有交互能力”。**
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前判断
|
||||
|
||||
## 2.1 已经成立的部分
|
||||
|
||||
当前已经成立且不应回退的事实:
|
||||
|
||||
- `tree-first graph kernel` 继续是树域真相层。
|
||||
- `Convex` 继续承担存储、实时同步、内容与协作底座。
|
||||
- `mnote-web` 已经具备树域 projection / command / transport 的正式入口能力。
|
||||
- `sidebar_tree / page_tree / file_tree` 已经形成同一 projection family。
|
||||
- 树域主路径不应再回到“前端自己定义第二套树真相”。
|
||||
|
||||
也就是说,当前真正成立的结构是:
|
||||
|
||||
- `Rust kernel` 持有树语义。
|
||||
- `Convex` 提供 substrate。
|
||||
- `Rust web` 已开始承接 route / projection / command / stream。
|
||||
- 当前主站中的树 consumer 已经开始消费 projection family。
|
||||
|
||||
## 2.2 仍未完成的部分
|
||||
|
||||
当前仍未完成、且正是下一阶段主任务的部分是:
|
||||
|
||||
- `page tree / file tree / picker` 的主渲染壳仍主要是 `TypeScript + Next + React`。
|
||||
- `file_tree` 的一部分 row 语义仍由前端 adapter 继续补齐,而不是完全由 Rust 直接输出。
|
||||
- `tree.*` 的长期命令面虽已冻结,但主调用路径仍未完全退出 `documents.*` 兼容入口。
|
||||
- 树域 realtime 仍未完全收口为 Rust Web 正式 `snapshot + delta + resync` 主链。
|
||||
- Sidebar 整体仍是超大客户端壳,树域虽然已收 truth,但执行面与宿主边界还不够薄。
|
||||
|
||||
因此当前正确判断不是:
|
||||
|
||||
- “树域 Rust 家族化已经完成”
|
||||
|
||||
而是:
|
||||
|
||||
> **树域已经完成“语义与协议收口”,但还没有完成“主执行面 Rust 家族化”。**
|
||||
|
||||
---
|
||||
|
||||
## 3. 为什么继续 Rust 家族化有真实意义
|
||||
|
||||
## 3.1 不是语言洁癖,而是减少第二套运行时语义
|
||||
|
||||
如果长期保持下面这种结构:
|
||||
|
||||
- Rust 负责 kernel / query / command / projection
|
||||
- Next/React 继续负责主树 UI、主交互壳、主状态骨架
|
||||
|
||||
那么树域会长期同时维护两套复杂度:
|
||||
|
||||
1. Rust 侧的 projection / command / stream 语义
|
||||
2. 前端壳里的 row model / selection / focus / keyboard / DnD / fallback / adapter 语义
|
||||
|
||||
这会带来:
|
||||
|
||||
- 协议在两侧重复解释
|
||||
- 调试时要同时跨 TS / Rust 两套执行面
|
||||
- tree shell 的边界始终无法真正稳定
|
||||
- 后续一切性能优化都要先穿过旧前端壳
|
||||
|
||||
因此这里的“继续 Rust 家族化”真正要减少的,不是文件后缀的种类,而是:
|
||||
|
||||
> **树域存在两套长期执行语义的状态。**
|
||||
|
||||
## 3.2 对加载速度与运行成本有真实潜力
|
||||
|
||||
树域是高频常驻 UI,不是偶尔打开一次的边缘面板。
|
||||
|
||||
如果它继续作为大型 `use client` 组件族存在,就会长期保留这些成本:
|
||||
|
||||
- 客户端组件挂载
|
||||
- 虚拟列表初始化
|
||||
- DnD 状态机初始化
|
||||
- 菜单与选择状态初始化
|
||||
- 浏览器侧 row model 构建
|
||||
- 兼容 fallback 与 stream 选择逻辑
|
||||
|
||||
这些成本不会因为“数据真相已经交给 Rust”而自动消失。
|
||||
|
||||
把树域主壳继续迁向 Rust 家族,真实收益主要来自:
|
||||
|
||||
- 进一步压低浏览器端常驻 JS
|
||||
- 让 projection -> renderer 的路径更短
|
||||
- 减少 hydration 与挂载层数
|
||||
- 降低 Sidebar 切页与刷新时的重渲染放大
|
||||
- 为后续更彻底的 server-first 壳收口创造条件
|
||||
|
||||
这里必须强调:
|
||||
|
||||
- Rust 化 **不自动等于** 更快
|
||||
- 但在当前项目的长期方向里,Rust 家族化是“继续减轻旧前端壳”的必要手段
|
||||
|
||||
## 3.3 对协作冲突与长期维护也有真实意义
|
||||
|
||||
这里的“冲突”不只指 git merge 冲突,更主要是:
|
||||
|
||||
- projection contract 与 renderer 行为漂移
|
||||
- TS adapter 与 Rust route 的边界反复变化
|
||||
- 同一个交互在两侧同时维护 legality / normalization / fallback
|
||||
|
||||
当树域主执行面仍留在 React 壳里时:
|
||||
|
||||
- Rust 改协议
|
||||
- 前端就要继续补 adapter
|
||||
- 测试也要跨两族运行时拼接验证
|
||||
|
||||
继续 Rust 家族化的意义,是让下面这条链尽量收敛为一条语言家族更统一的主链:
|
||||
|
||||
- kernel truth
|
||||
- projection
|
||||
- tree command
|
||||
- tree shell
|
||||
- renderer
|
||||
|
||||
---
|
||||
|
||||
## 4. 下一阶段剩余架构事项
|
||||
|
||||
下面这些事项,才是树域继续 Rust 家族化时真正还没完成的主任务。
|
||||
|
||||
## 4.1 正式定义“树域主执行面”的完成标准
|
||||
|
||||
首先必须明确:
|
||||
|
||||
- “树域已经吃 projection” 不等于 “树域已经 Rust 家族化完成”
|
||||
- “存在 Leptos scaffold / Rust tree_shell 模块” 不等于 “主路径 renderer 已迁完”
|
||||
|
||||
下一阶段完成标准应固定为:
|
||||
|
||||
- 至少一条真实默认用户流量,主树 renderer 不再依赖当前 React `PrivateTree` / `FileTree`
|
||||
- `page tree / file tree / picker` 的核心交互骨架不再由 Next/React 主壳承担
|
||||
- TS 宿主只保留挂载、局部桥接、页面路由与极薄 compat
|
||||
|
||||
如果这一标准不先冻结,后续很容易再次把“有 Rust route”误当成“已经 Rust 化完成”。
|
||||
|
||||
## 4.2 把 page tree renderer 真正迁入 Rust 家族
|
||||
|
||||
`page tree` 下一阶段不是继续补前端 helper,而是把以下能力迁成 Rust 家族正式 renderer 能力:
|
||||
|
||||
- 行渲染
|
||||
- 展开/折叠
|
||||
- 当前页高亮
|
||||
- hover 动作区
|
||||
- 键盘导航
|
||||
- 拖拽反馈
|
||||
- 上下文菜单入口
|
||||
|
||||
这里的重点不是“把视觉样式翻译一遍”,而是:
|
||||
|
||||
- 把行级行为骨架从 React 组件迁走
|
||||
- 让 page tree 只围绕正式 projection item 工作
|
||||
- 不再由现有 TS 组件长期持有主路径交互状态机
|
||||
|
||||
## 4.3 把 file tree 从“前端 adapter 文件树”收口为“Rust 直接输出 + Rust 家族 renderer”
|
||||
|
||||
`file_tree` 是当前最关键也最容易退化的一段。
|
||||
|
||||
下一阶段必须同时做两件事:
|
||||
|
||||
1. Rust 侧继续把 `file_tree` 输出补完整。
|
||||
2. renderer 不再依赖当前前端在 `pageRows + assets` 上继续拼 visible rows。
|
||||
|
||||
必须继续向 Rust 收口的内容包括:
|
||||
|
||||
- `index.md`
|
||||
- 附件
|
||||
- `mindmap` 文件夹
|
||||
- 导图子附件
|
||||
- 表格、书籍、PDF 等对象提示
|
||||
- `resource_kind / asset_kind / icon_hint`
|
||||
|
||||
否则文件树即使换了新壳,也仍然只是:
|
||||
|
||||
- “页面树 + 前端附件补丁”
|
||||
|
||||
这不符合长期目标。
|
||||
|
||||
## 4.4 把树域交互骨架拆成可替换的正式模块
|
||||
|
||||
下一阶段不应继续把交互都堆在单一大组件里,而应显式收口为几类正式模块:
|
||||
|
||||
- row model
|
||||
- selection model
|
||||
- focus model
|
||||
- keyboard model
|
||||
- DnD state machine
|
||||
- context action registry
|
||||
|
||||
无论这些模块最终落在:
|
||||
|
||||
- `Leptos`
|
||||
- 或极薄 Rust/TS bridge
|
||||
|
||||
都必须满足两条:
|
||||
|
||||
- 它们不再定义结构真相
|
||||
- 它们可以独立测试与演进
|
||||
|
||||
## 4.5 完成 tree command cutover Stage 2
|
||||
|
||||
树域继续 Rust 家族化时,命令面不能长期继续停在双轨。
|
||||
|
||||
下一阶段必须完成:
|
||||
|
||||
- 主路径默认发 `tree.*`
|
||||
- `documents.*` 退到 compat
|
||||
- legality / normalize / move target 等树策略继续回收给 Rust
|
||||
|
||||
否则会出现一个长期问题:
|
||||
|
||||
- UI 壳换成 Rust 家族了
|
||||
- 但命令面仍主要经由旧 `documents.*` 兼容语义运行
|
||||
|
||||
这样并没有完成真正的主执行面收口。
|
||||
|
||||
## 4.6 完成 Rust Web realtime contract
|
||||
|
||||
树域如果要继续往正式主链推进,realtime 必须从“可用”进入“单一正式合同”。
|
||||
|
||||
下一阶段需要补齐:
|
||||
|
||||
- `snapshot`
|
||||
- `delta`
|
||||
- `resync`
|
||||
- cursor 漂移处理
|
||||
- 局部 subtree 与 workspace scope 的统一
|
||||
|
||||
目标不是简单保住 fallback,而是让树域主路径不再依赖:
|
||||
|
||||
- 查询快照
|
||||
- 本地 freshness 选择
|
||||
- React 侧补偿式同步
|
||||
|
||||
## 4.7 收薄宿主边界
|
||||
|
||||
长期目标不是让主站彻底消失,而是让主站对树域只保留最小宿主职责:
|
||||
|
||||
- 页面布局挂载位
|
||||
- 路由跳转
|
||||
- 样式容器
|
||||
- 极薄 feature flag / compat
|
||||
- 与其他页面面板的最小桥接
|
||||
|
||||
不应继续保留在宿主中的内容:
|
||||
|
||||
- 树结构重建
|
||||
- 主交互状态骨架
|
||||
- 文件树资源行语义补丁
|
||||
- 主要 keyboard / DnD / selection 逻辑
|
||||
|
||||
---
|
||||
|
||||
## 5. page tree 迁移时必须保留的能力
|
||||
|
||||
`page tree` 在迁移到 Rust 家族主执行面时,必须显式保证下面这些能力不退化。
|
||||
|
||||
## 5.1 结构与导航
|
||||
|
||||
必须保留:
|
||||
|
||||
- 稳定的层级展开/折叠
|
||||
- 当前页高亮
|
||||
- 祖先自动展开
|
||||
- 搜索/过滤后层级仍可理解
|
||||
|
||||
禁止退化为:
|
||||
|
||||
- 纯扁平列表
|
||||
- 只有打开功能、没有层级语义的按钮组
|
||||
|
||||
## 5.2 行级动作
|
||||
|
||||
必须保留:
|
||||
|
||||
- 新建子页面
|
||||
- 重命名
|
||||
- 移动
|
||||
- 归档/恢复类上下文操作入口
|
||||
- 行级 hover 动作区
|
||||
|
||||
要求:
|
||||
|
||||
- 不能为了换 renderer 把动作缩减到“点开页面 + 更多菜单”
|
||||
- 不能丢失当前工作区级高频动作入口
|
||||
|
||||
## 5.3 键盘与焦点
|
||||
|
||||
必须保留:
|
||||
|
||||
- 上下移动
|
||||
- 左右展开/折叠
|
||||
- Enter 打开
|
||||
- 稳定的 focus 行
|
||||
|
||||
要求:
|
||||
|
||||
- `focus` 不能重新混成 `selected`
|
||||
- 展开与选择变化后焦点归一化必须稳定
|
||||
|
||||
## 5.4 拖拽与移动
|
||||
|
||||
必须保留:
|
||||
|
||||
- 基础拖拽排序
|
||||
- 父节点切换
|
||||
- 非法拖拽校验
|
||||
|
||||
要求:
|
||||
|
||||
- drop feedback 不能退化成不可预测的闪烁
|
||||
- 目标父节点与位置推导必须有稳定、可测试的协议
|
||||
|
||||
## 5.5 大树体验
|
||||
|
||||
必须保留:
|
||||
|
||||
- 稳定滚动
|
||||
- 大树下的可见区域性能
|
||||
- 展开/折叠与切页不会出现明显回闪
|
||||
|
||||
这部分是迁移时的硬验收项,不是可选优化项。
|
||||
|
||||
---
|
||||
|
||||
## 6. file tree 迁移时必须保留的能力
|
||||
|
||||
`file_tree` 比 `page tree` 更容易因迁移而退化。
|
||||
|
||||
下一阶段必须把“功能保留”明确写成硬门槛。
|
||||
|
||||
## 6.1 资源层级
|
||||
|
||||
必须保留:
|
||||
|
||||
- 页面
|
||||
- `index.md`
|
||||
- 附件
|
||||
- `mindmap` 文件夹
|
||||
- 导图子附件
|
||||
- 表格、书籍、PDF 等资源对象语义
|
||||
|
||||
禁止退化为:
|
||||
|
||||
- 页面树下挂一个普通附件列表
|
||||
- mindmap 资源被拍平成普通文件
|
||||
|
||||
## 6.2 选择能力
|
||||
|
||||
必须保留:
|
||||
|
||||
- 单选
|
||||
- 多选
|
||||
- Shift 范围选
|
||||
- 右键选中
|
||||
- 可见行变化后的选择归一化
|
||||
|
||||
这部分如果退化,文件树就不再是资源管理器语义,而会退回文档树换皮。
|
||||
|
||||
## 6.3 打开与操作
|
||||
|
||||
必须保留:
|
||||
|
||||
- 单击选择
|
||||
- 双击打开资源
|
||||
- 资源级右键菜单
|
||||
- 按资源类型分支的动作入口
|
||||
|
||||
要求:
|
||||
|
||||
- 资源图标语义必须稳定
|
||||
- `asset_kind / icon_hint` 不允许再次回到壳内临时猜测
|
||||
|
||||
## 6.4 拖放
|
||||
|
||||
必须保留:
|
||||
|
||||
- 内部拖放
|
||||
- 外部文件拖入上传
|
||||
- copy / move 区分
|
||||
- 非法投放校验
|
||||
|
||||
要求:
|
||||
|
||||
- 文件树不能只保留视觉拖拽,没有正式动作协议
|
||||
- 外部文件拖入必须继续是正式主路径能力
|
||||
|
||||
## 6.5 文件树特有的可见行模型
|
||||
|
||||
必须保留:
|
||||
|
||||
- `doc -> index -> asset-folder -> asset` 的可见行语义
|
||||
- 扁平化 rows 与深度的稳定映射
|
||||
- 可见行变化后的选择、焦点、拖放范围归一化
|
||||
|
||||
这里允许实现换掉,但不允许语义丢掉。
|
||||
|
||||
---
|
||||
|
||||
## 7. 推荐迁移顺序
|
||||
|
||||
为了尽量保持体验不退化,推荐顺序如下:
|
||||
|
||||
1. 先补齐 `file_tree` 的 Rust 直接输出,避免迁 renderer 时还要继续背前端 adapter。
|
||||
2. 再把 `page tree` renderer 迁入 Rust 家族,因为它对象更单纯、验收面更小。
|
||||
3. 然后迁 `file tree` renderer,因为它依赖更多资源与交互语义。
|
||||
4. 再迁 `picker` 轻量模式,让它复用同一套 renderer / state family。
|
||||
5. 最后继续削薄宿主,收掉剩余主路径 React 树壳。
|
||||
|
||||
原因:
|
||||
|
||||
- `page tree` 先迁可以先验证主行 renderer 与状态骨架。
|
||||
- `file_tree` 后迁可以避免一开始就在最复杂对象面上同时处理 renderer 与 contract 漂移。
|
||||
- `picker` 最适合作为共用 renderer 的轻量消费场景,而不是主线路的先导。
|
||||
|
||||
---
|
||||
|
||||
## 8. 迁移阶段的验收门槛
|
||||
|
||||
只有同时满足下面几条,才可以把树域继续 Rust 家族化的某一阶段视为成立:
|
||||
|
||||
- 页面树与文件树都没有因切流而减少已有能力。
|
||||
- `page tree / file tree` 的关键能力有 smoke 覆盖:
|
||||
- 切页
|
||||
- 展开/折叠
|
||||
- 拖拽
|
||||
- 右键菜单
|
||||
- 多选与范围选
|
||||
- 外部文件拖入
|
||||
- `file_tree` 的对象投影不再依赖前端临时补丁维持主路径。
|
||||
- 主调用路径默认走正式 `tree.*` 命令面。
|
||||
- 正式 realtime 至少已经进入统一 `snapshot + delta + resync` 合同,而不是继续主要靠补偿式 freshness 选择。
|
||||
- 主站宿主不再持有树结构真相,也不再承担主要树域交互状态机。
|
||||
|
||||
---
|
||||
|
||||
## 9. 当前优先级建议
|
||||
|
||||
在当前总优先级下,树域继续 Rust 家族化的最近顺序建议固定为:
|
||||
|
||||
1. 继续推进 `4-6 tree command protocol cutover stage2`
|
||||
2. 明确 `file_tree` 直接输出的剩余缺口,并以 Rust 侧补齐
|
||||
3. 为 `page tree / file tree` 迁移补正式功能保留回归矩阵
|
||||
4. 选择并落一条真实默认流量进入 Rust 家族 renderer
|
||||
5. 再逐步收掉当前主路径 React 树壳
|
||||
|
||||
也就是说,当前下一步不是:
|
||||
|
||||
- 继续在现有 React 树壳里堆更多交互补丁
|
||||
|
||||
而是:
|
||||
|
||||
> **在功能不退化的前提下,把树域从“Rust 持有语义、React 持有主执行面”的过渡态,推进到“Rust 家族同时持有语义与主执行面”的下一阶段。**
|
||||
|
||||
---
|
||||
|
||||
## 10. 最终结论
|
||||
|
||||
当前树域继续往全 Rust 方向发展,是有真实意义的。
|
||||
|
||||
意义不在于:
|
||||
|
||||
- 代码库里减少一种语言
|
||||
|
||||
而在于:
|
||||
|
||||
- 收掉旧前端壳的长期主执行职责
|
||||
- 减少第二套复杂运行时语义
|
||||
- 为首屏与常驻交互链路继续减重
|
||||
- 让 `tree-first graph kernel -> Rust Web -> Rust family shell` 形成更一致的长期主链
|
||||
|
||||
但这个推进必须满足一个前提:
|
||||
|
||||
> **page tree / file tree 的功能能力不能因为迁移而退化。**
|
||||
|
||||
因此,下一阶段的正确目标不是“尽快把组件翻译成 Rust”,而是:
|
||||
|
||||
> **先把剩余 projection / command / realtime / host 边界补齐,再以 page tree 与 file tree 的能力保留为硬约束,把树域主执行面稳步迁入 Rust 家族。**
|
||||
Reference in New Issue
Block a user