# Mindmap Phase 6 Projection Editor v1 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 在不等待 `design/01-05` 全量收口的前提下,按 `design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` 的方向推进 `Mindmap Phase 6` 主线重构:让 Mindmap 从“独立对象中心 + blob 真相 + 前端重壳”降级成“kernel subtree / graph 的一种 projection 与 editor”,并适配当前已有的 Rust 主链;本轮优先收口文档内 `/` 命令插入的 `mindmap block` 最小读写真相链,再把独立导图页挂到同一套 truth / adapter 上。 **Architecture:** 这一版不重写画布,不替换 `simple-mind-map`,也不要求 page aggregate / tree realtime 彻底闭环。先建立 block 与独立页共用的 Rust projection + 最小 command adapter,让 `simple-mind-map` 降级成 renderer / editor adapter,再把 `/` 插入示例骨架、默认隐藏 chrome、hover 后显示工具栏 / 右侧面板 / 节点操作这些产品体验挂到这条新主链上。测试和截图验收只负责证明主线成立,不反向限制重构范围;本轮允许 blob 存储暂时作为 compat substrate 存在,但不再把它当长期对象真相。 **Tech Stack:** Next.js / React、Convex、Rust `core-protocol`、Rust `bridge-runtime`、Rust `mnote-web`、`simple-mind-map` --- ## 方向确认 - [ ] 当前 Phase 6 的方向,明确是按 `design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` 推进 - [ ] 目标明确是让 Mindmap 从“独立对象中心 + blob 真相 + 前端重壳”降级成“kernel subtree / graph 的一种 projection 与 editor” - [ ] `design/01-05` 仍然是当前仓库的上位主线与优先级依据,但不是这轮 Mindmap Phase 6 的硬前置阻塞 - [ ] 这轮只在不破坏 `Page Aggregate / tree command / tree realtime` 主线口径的前提下,收口 Mindmap 自身的 truth / projection / command 边界 - [ ] 测试验收、截图留档、浏览器 smoke 只从属于主线重构,不反向决定主线方案 - [ ] 独立导图页必须适配同一套 truth / adapter,但文档内 `/` 命令插入的 `mindmap block` 仍是本轮主验收入口 ## 范围冻结 ### 本计划明确要做 - [ ] 明确按 `6-mindmap-kernel-phase6-projection-editor-v1.md` 的口径推进,不把 Mindmap 再当独立对象真相层 - [ ] 让 Mindmap 降级为 kernel subtree / graph 的一种 projection 与 editor,并接入当前 Rust 主链 - [ ] 文档内 `/` 命令插入的 `mindmap block` 是本轮主验收面 - [ ] 先收口 block 与独立页共用的最小读写真相链,再叠加产品体验 - [ ] 独立导图页读链优先消费 Rust `mindmap.projection.get` - [ ] 前端导图 projection 类型与 Rust projection contract 对齐 - [ ] 为最小可用动作集建立 Rust command / op 适配层 - [ ] 保留 `simple-mind-map` 作为 renderer / editor adapter - [ ] 为独立页与内嵌块建立同一套“projection truth + adapter runtime”口径 - [ ] `/` 插入后默认生成示例骨架:根节点 + 二级节点 + 两个分支主题 - [ ] 默认只显示导图本体;hover block 后显示工具栏 / 右侧面板 / 节点 hover 操作 - [ ] 补齐最小测试,确保 Phase 6 不回退成“前端摘要 + blob 真相” ### 本计划明确不做 - [ ] 不要求先完成 `Page Aggregate` 全闭环 - [ ] 不要求先完成 `tree realtime` 全域 live cache 统一 - [ ] 不重写 `MindmapBlock.tsx` 的整套工具栏、动画、导入导出、图片工具 - [ ] 不把 Convex `mindmaps` blob 存储在本轮强行改成 kernel node / edge 持久化 - [ ] 不把所有 `simple-mind-map execCommand` 一次性收口到 Rust - [ ] 不为了浏览器 smoke 或截图留档而保守维持旧的 blob-first 主路径 ### 开工判定 - [ ] 确认当前主线口径仍是 `tree-first graph kernel` - [ ] 确认当前方向是“Mindmap 降级成 kernel projection/editor 并适配 Rust 主链”,而不是继续补一套独立 blob 系统 - [ ] 确认 `Page Aggregate` / `tree.*` / `/api/tree/events` 已是正式主链,而不是待验证方向 - [ ] 确认本轮目标是 `projection/editor v1`,不是 `mindmap full kernel cutover` - [ ] 确认测试验收从属于重构主线,而不是主线为测试妥协 - [ ] 若任务被重新扩大到“顺手收口 page aggregate / realtime 尾项”,暂停并拆分 ## 产品体验目标 - [ ] 主验收入口:文档页正文内通过 `/` 命令插入 `mindmap block` - [ ] 插入后立即看到示例骨架,而不是空白画布或只有一个根节点 - [ ] 默认只显示画布与导图本体,不常驻显示顶部工具栏、右侧面板、节点操作 - [ ] 鼠标移入 block 区域后,显示顶部工具栏与右侧面板 - [ ] 鼠标移入节点后,显示节点级 hover 操作 - [ ] 独立页与 block 共享同一套 truth / adapter,只允许 chrome 壳层不同 ## 参考对标 - [ ] 体验参考:`/mnt/Data1T/mnote/tmp/image copy 59.png` - [ ] 更接近现有实现的参考:`/mnt/Data1T/mnote/tmp/image copy 60.png` - [ ] 默认隐藏 chrome 的参考:`/mnt/Data1T/mnote/tmp/image copy 61.png` - [ ] 外部参考实现: - `https://github.com/suka233/siyuan-kmind-plugin` - `https://github.com/wanglin2/mind-map` - `https://github.com/wanglin2/mind-map-mcp` - `https://github.com/wanglin2/lx-doc` - [ ] 参考只用于体验与结构借鉴,不要求机械复刻外部产品 --- ### Task 1: 冻结 Phase 6 执行边界 **Files:** - Modify: `design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` - Reference: `design/01-05-current-priority-overview.md` - Reference: `ARCHITECTURE.md` - [ ] 在设计稿里补一段“本轮执行边界”,明确: - 方向是把 Mindmap 降级成 kernel subtree / graph projection + editor - 继续适配并复用当前已有的 Rust 主链 - 先切 block / page 共用的最小读写真相链 - 再把 `/` 插入、示例骨架、hover chrome 挂到新主链 - 继续保留 `simple-mind-map` renderer - 不把 `01-05` 尾项作为硬前置 - [ ] 在设计稿里补一段“非目标说明”,明确: - 本轮不是继续把 mindmap blob 打磨成长期对象真相 - 本轮不是先做前端重壳再以后接 Rust - 本轮不是一次性完成最终 kernel-native graph storage cutover - [ ] 在设计稿里补一段“主验收面”,明确: - 主验收入口是文档内 `/` 命令插入的 `mindmap block` - 独立页必须共用同一真相链,但不是产品验收主画面 - [ ] 在设计稿里补一段“测试从属关系”,明确: - smoke / 截图用于证明主线成立 - 不允许为了更好测而维持旧主路径 - [ ] 在设计稿里补一段“阻塞条件”,明确只有以下情况才暂停: - Rust projection contract 不可用 - 现有 bridge 无法表达最小 MindmapOp - 独立页与内嵌块入口无法共享 projection contract - [ ] 在设计稿里补一段“延期项”,把以下内容显式标为后续阶段: - blob 持久化移除 - page aggregate 深度对齐 - realtime delta 深度接入 - 全量画布命令收口 - [ ] 验证:设计稿读起来不会再误导执行者去先重写画布或等待 `01-05` 全完成 --- ### Task 2: 建立 Mindmap Projection 真实合同 **Files:** - Inspect/Modify: `rust/crates/core-protocol/src/mindmap.rs` - Inspect/Modify: `rust/crates/bridge-runtime/src/lib.rs` - Inspect/Modify: `rust/crates/mnote-web/src/routes/mindmap_shell.rs` - Inspect/Modify: `wolai-frontend/src/lib/mindmap/mindmap-projection.ts` - Test: `rust/crates/bridge-runtime/src/lib.rs` - Test: `wolai-frontend/src/lib/mindmap/*.test.ts` - [ ] 盘点 Rust 侧现有 `MindmapProjection` 字段,列出前端真正需要消费的最小字段: - `documentId` - `mindmapId` - `rootNodeId` - `title` - `nodeCount` - `nodes` - `raw/tree payload` - `owner/source/meta` - [ ] 判断前端 `MindmapProjection` 与 Rust contract 的差异,特别检查: - 前端是否仍把 `projection: "mindmap_subtree"` 当私有摘要 - 是否缺少 source / owner / version 语义 - 是否把 `data` 原树和 projection 摘要混成一层 - [ ] 冻结一份统一口径: - Rust 输出什么 - 前端只做什么适配 - 哪些字段是 renderer 所需,哪些字段是系统真相 - [ ] 在 contract 里显式区分三层: - projection truth - renderer input - chrome / hover UI state - [ ] 确认 block 和独立页都消费同一份 projection truth,不允许各自再拼一份摘要对象 - [ ] 补测试,至少覆盖: - Rust `mindmap.projection.get` 返回稳定 schema - 前端 adapter 能解析 Rust projection - 当原始 blob 字段缺失时,adapter 仍只做渲染兜底,不篡改 projection 语义 - [ ] 验证命令: - `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime mindmap` - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-projection` --- ### Task 3: 建立最小 Command Adapter,只接本轮必须动作 **Files:** - Inspect/Modify: `rust/crates/core-protocol/src/mindmap.rs` - Modify: `rust/crates/bridge-runtime/src/lib.rs` - Modify: `wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts` - Create/Modify: `wolai-frontend/src/lib/mindmap/mindmap-command-client.ts` - Test: `rust/crates/bridge-runtime/src/lib.rs` - Test: `wolai-frontend/src/lib/mindmap/*.test.ts` - [ ] 冻结 v1 支持的最小动作集,只选: - `rename/updateText` - `addChild` - `deleteNode` - `addSiblingAfter` - 节点 hover 操作所需的最小命令 - `/` 插入后的示例骨架创建 - [ ] `setRefs`、`move` 不作为本轮 block 主验收前置,除非实现时已在同一 adapter 中顺手闭环 - [ ] 为这些动作建立前端 command client,不再默认整棵 `POST data blob` - [ ] Rust bridge 明确区分: - projection query - command apply - compat `mindmaps.put` - [ ] 只有确实无法表达的动作,才回退到整棵 blob 保存;回退只作为 compat 行为存在,不在这里展开做标签治理 - [ ] 补测试,至少覆盖: - 每个最小动作都能转成 `MindmapOp` 或 `MindmapCommand` - 非支持动作不会偷偷走“无差别全量 blob 覆盖” - compat 回退有显式标记 - [ ] 验证命令: - `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime mindmap_apply_ops` - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-command-client` --- ### Task 4: 建立共享 Entry Contract 与 Renderer Adapter 基础 **Files:** - Modify: `wolai-frontend/src/lib/mindmap/mindmap-projection.ts` - Create/Modify: `wolai-frontend/src/lib/mindmap/mindmap-entry-contract.ts` - Create/Modify: `wolai-frontend/src/lib/mindmap/mindmap-renderer-adapter.ts` - Modify: `wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx` - Modify: `wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx` - Test: `wolai-frontend/src/lib/mindmap/*.test.ts` - Test: `wolai-frontend/src/components/editor/blocks/*.test.tsx` - Test: `wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/*.test.tsx` - [ ] 把“系统真相字段”和“画布运行时字段”分成三层: - projection truth - renderer input - chrome / hover UI state - [ ] 建立 `mindmap-entry-contract`,统一 block 与独立页入口的最小约定: - 如何拿 projection - 如何构建 renderer input - 如何声明 UI 壳层差异 - [ ] 提取 `mindmap-renderer-adapter`,专门负责: - projection -> `simple-mind-map` 可渲染数据 - renderer 事件 -> 规范化编辑意图 - [ ] 避免继续在 `MindmapBlock.tsx` 或独立页内直接把 `buildMindmapProjection(blob)` 当正式读链 - [ ] 补测试,至少覆盖: - adapter 不改写 projection 语义 - adapter 的兜底只作用于 renderer - block 与独立页能共用同一 entry contract / renderer adapter - [ ] 验证命令: - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- mindmap-projection mindmap-renderer-adapter src/app/mindmap src/components/editor/blocks` --- ### Task 5: 收口文档内 mindmap block 的主读写真相链 **Files:** - Modify: `wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx` - Modify: `wolai-frontend/src/lib/mindmap/mindmap-projection.ts` - Modify: `wolai-frontend/src/lib/mindmap/mindmap-renderer-adapter.ts` - Modify: `wolai-frontend/src/lib/mindmap/mindmap-command-client.ts` - Inspect/Modify: `rust/crates/bridge-runtime/src/lib.rs` - Test: `wolai-frontend/src/components/editor/blocks/*.test.tsx` - Test: `wolai-frontend/src/lib/mindmap/*.test.ts` - Test: `rust/crates/bridge-runtime/src/lib.rs` - [ ] 让文档内 `mindmap block` 改为消费统一 projection truth,而不是 `buildMindmapProjection(blob)` 私有摘要 - [ ] 让 block 的最小编辑动作统一走 Task 3 的 command adapter - [ ] 把节点 hover 操作建立在这条最小写入链上,不再默认走全量 blob 覆盖 - [ ] 确认 `MindmapBlock.tsx` 不再同时扮演 truth / adapter / chrome 三层 - [ ] 补测试,至少覆盖: - block 主读链优先消费 projection truth - block 最小动作集能转成 `MindmapOp` / command - 非支持动作不会偷偷回退成无差别整棵覆盖 - [ ] 验证命令: - `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime mindmap` - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- MindmapBlock mindmap-command-client` --- ### Task 6: 把 `/` 插入示例骨架与 Hover Chrome 体验挂到新主链 **Files:** - Modify: `wolai-frontend/src/components/editor/blocks/MindmapBlock.tsx` - Inspect/Modify: `wolai-frontend/src/store/editor-bridge.ts` - Inspect/Modify: `wolai-frontend/src/components/editor/*` - Inspect/Modify: `wolai-frontend/src/app/(app)/documents/[id]/*` - Test: `wolai-frontend/src/components/editor/blocks/*.test.tsx` - [ ] 找到文档页 `/` 命令插入 `mindmap block` 的真实入口,并把它改为直接创建示例骨架 truth - [ ] 示例骨架固定为: - 根节点 - 1 个二级节点 - 2 个分支主题 - [ ] `mindmap block` 默认只显示画布与导图本体 - [ ] 鼠标移入 block 区域后,显示顶部工具栏与右侧面板 - [ ] 鼠标移入节点后,显示节点级 hover 操作 - [ ] chrome 显隐只依赖新 truth / adapter 主链,不在旧 blob 路径上额外补逻辑 - [ ] 补测试,至少覆盖: - `/` 插入后不是空白壳 - 默认不常显 chrome - hover block 后 chrome 出现 - hover 节点后节点操作出现 --- ### Task 7: 独立导图页同步挂到同一套 Truth **Files:** - Modify: `wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx` - Modify: `wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts` - Inspect/Modify: `wolai-frontend/src/lib/documents/rust-runtime.ts` - Inspect/Modify: `rust/crates/bridge-runtime/src/lib.rs` - Inspect/Modify: `rust/crates/mnote-web/src/routes/mindmap_shell.rs` - Modify: `wolai-frontend/src/lib/mindmap/mindmap-entry-contract.ts` - Test: `wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/*.test.tsx` - Test: `wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/*.test.ts` - [ ] 让独立页 SSR 读链优先请求 Rust `mindmap.projection.get` - [ ] 让独立页与 block 共用 Task 3/4 产出的 entry contract / renderer adapter / command adapter - [ ] 如果仍需兼容 blob 返回,必须把它降级为 fallback,并在代码里标出 compat 边界 - [ ] 保证 route 返回的主体语义是 projection,而不是“顺便附带 data blob 的摘要” - [ ] 校验 `initialProjection` 命名与真实内容一致,不再出现“其实是本地摘要却叫 projection”的假象 - [ ] 明确哪些行为允许不同: - block 以文档内 hover chrome 为主 - 独立页可保留更完整的工作区 chrome - [ ] 补测试,至少覆盖: - projection 正常返回时,页面首屏直接消费 Rust projection - projection 失败时,compat fallback 行为可见且受控 - 独立页与 block 读取的是同一 contract,不再分叉 - [ ] 验证命令: - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test -- src/app/mindmap src/app/api/mindmap` --- ### Task 8: 标记并隔离仍未切掉的 Compat 区域 **Files:** - Modify: `wolai-frontend/src/app/api/mindmap/[docId]/[mindmapId]/route.ts` - Modify: `wolai-frontend/src/lib/documents/rust-runtime.ts` - Modify: `wolai-frontend/src/lib/mindmap/mindmapLocalStore.ts` - Modify: `wolai-frontend/convex/mindmaps.ts` - Optional Doc: `design/06-mindmap/process/6-mindmap-kernel-phase6-projection-editor-v1.md` - [ ] 给仍保留 blob 真相的路径打清楚标签: - `compat_blob_read` - `compat_blob_write` - `renderer_only_fallback` - [ ] 只在 Task 5 / Task 7 主链稳定后,集中补 compat 标记;不和 Task 3 的命令适配职责混写 - [ ] 避免继续在新代码里把 compat 路径写成正式主路径 - [ ] 明确 localStorage / 附件广播 / `mindmap-.json` 属于过渡资产,不属于长期真相合同 - [ ] 给 Convex `mindmaps` 的整棵 blob 存储加一句清晰注释:当前仅是 substrate / compat 持久化,不是长期对象语义 - [ ] 验证:新执行者一眼能区分哪条是正式路径,哪条只是过渡保底 --- ### Task 9: 补齐最小回归与浏览器验收 **Files:** - Test: `wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/*.test.tsx` - Test: `wolai-frontend/src/components/editor/blocks/*.test.tsx` - Test: `wolai-frontend/src/lib/mindmap/*.test.ts` - Test: `rust/crates/bridge-runtime/src/lib.rs` - Optional Script: `scripts/task*-mindmap-smoke.js` - [ ] 单测覆盖: - projection 主读链 - compat fallback - renderer adapter - 最小 command adapter - block / 独立页共享 truth - [ ] 补一个浏览器 smoke,至少验证: - 新建任意页面后,可通过 `/` 命令插入 `mindmap block` - 插入后立即出现示例骨架,而不是空白壳 - 默认不常显 chrome - hover block 后顶部工具栏和右侧面板出现 - hover 节点后节点级操作出现 - 至少一个最小编辑动作能成功回显 - [ ] 独立页 smoke 只作为同链补充验证,不再是本轮产品主验收面 - [ ] 补一个“禁止回退”断言: - 当 projection 可用时,不允许默认走 `buildMindmapProjection(blob)` 主路径 - [ ] 验证命令: - `cd /mnt/Data1T/mnote/wolai-frontend && pnpm test` - `cd /mnt/Data1T/mnote/rust && cargo test -p bridge-runtime` - 浏览器 smoke 使用仓库现有 `scripts/task*-smoke.js` 风格新增或复用 ## 依赖关系 - [ ] Task 1 完成后,才能开始代码实现,避免目标漂移 - [ ] Task 2 是 Task 3 和 Task 4 的共同前置 - [ ] Task 3 必须先于 Task 4、Task 5、Task 6、Task 7,否则 entry contract 和 block/page 主链没有稳定命令面 - [ ] Task 4 必须先于 Task 5 和 Task 7,否则 block / page 没有共享 contract 与 renderer adapter - [ ] Task 5 是 block 主验收面的第一落点,必须先于 `/` 插入和 hover 体验 - [ ] Task 6 依赖 Task 5,不能先在旧主路径上做 UI 伪收口 - [ ] Task 7 依赖 Task 3 和 Task 4;建议在 Task 5 之后执行,确保 page 跟随 block 主链接入 - [ ] Task 8 不再与 Task 3 重复承担命令适配职责,只负责集中标记和隔离仍残留的 compat 区域 - [ ] Task 9 只在 Task 5/6/7/8 形成最小闭环后收尾 ## 退出标准 - [ ] 独立导图页主读链已不是 `mindmaps.get + 前端摘要` - [ ] 文档内 `/` 插入的 `mindmap block` 已成为本轮主验收面,并挂在新 truth / adapter 主链上 - [ ] `simple-mind-map` 被明确降级为 renderer/editor adapter - [ ] 至少一组最小编辑动作已走 Rust `MindmapOp` / command,而不是默认全量 blob 覆盖 - [ ] `/` 插入后默认生成示例骨架,且不是空白壳 - [ ] 默认隐藏 chrome,hover block / 节点后再显示相应操作 - [ ] 独立页与内嵌块共享同一套 projection truth 口径 - [ ] 仍未移除的 blob / localStorage / 附件路径都被清楚标成 compat - [ ] 测试和 smoke 能证明 Phase 6 已经开始脱离 blob-first,而不是只换了命名 ## 暂不作为阻塞的尾项 - [ ] `Page Aggregate` 写后完整回灌统一 projection - [ ] page subtree 与正文本地态完全同源 - [ ] tree realtime 全域单 live cache - [ ] 导图 blob 存储彻底移除 - [ ] 全量画布命令 Rust 化 ## 建议执行顺序 - [ ] 第 1 天:Task 1 + Task 2 - [ ] 第 2 天:Task 3 - [ ] 第 3 天:Task 4 - [ ] 第 4 天:Task 5 - [ ] 第 5 天:Task 6 - [ ] 第 6 天:Task 7 + Task 8 - [ ] 第 7 天:Task 9 + 设计稿回填