来源:improve-codebase-architecture 架构审查(2026-07-20)。本 issue 作为记录,尚未经过 /grilling 决策树细化。
推荐强度:Strong · 依赖类别:in-process
Files
src/main/flow-diagram/flow-diagram-service.ts · 44 symbols
src/main/flow-diagram/flow-diagram-revision-store.ts · 24 symbols
src/main/flow-diagram/flow-diagram-scene.ts · 21 symbols
src/renderer/src/components/FilePanel · 编辑器侧
Problem
最近约 10 个提交都是并发修复(原子替换、compare-and-swap、冲突隔离、reload 协调),每次都跨多个文件——因为没有单一模块拥有一致性规则。一致性不变量在 service、revision-store、scene 与渲染进程之间泄漏。
Solution
一个深的 FlowDiagramStore,在小接口背后拥有 autosave 排序、替换原子性、revision 快照与冲突检测;service 退化为薄 adapter。
Wins
- locality:一致性 bug 集中在一个模块
- leverage:一个接口服务 service、tool、renderer
- 测试直接命中一致性 seam
- revision-store 与 scene 不再浅薄
ADR 关联
触及 ADR-0071 / ADR-0072(Editable Flow Diagrams 使用本地 Excalidraw 文档 + 隔离 git 历史)。该决策保持不变;本候选加深的是实现它的 模块,而非决策本身。
Spec(/to-spec 调研补充,2026-07-26)
调研结论:问题仍然存在,且比审查时的描述更具体。提交史确认:功能合入(#192-#196)后连续 7 个并发修复提交(原子替换、compare-and-swap、外部变更保留、并发编辑保留、autosave 与导出协调、revision 加固),每个都跨多文件。
新发现的强度不一致:同一份 .excalidraw 文档存在两条互不知晓的写路径——用户编辑器 autosave 走通用文件写入 IPC 的 expectedContent 参数(读-比较-写,非原子,有 TOCTOU 窗口;该参数全库仅此一个消费方),Agent 编辑走 flow-diagram 服务内部的原子 CAS 替换。写排序、冲突检测、reload 协调则由渲染进程编辑器里 6 个 ref + 手写保存队列承担。一致性不变量确实泄漏在 service、revision-store、scene、通用文件服务与渲染进程五处。
Problem Statement
用户与 Agent 会并发编辑同一个 Editable Flow Diagram。保证"谁都不丢改动、失败可回滚、各表面及时刷新"的一致性规则没有唯一归属:每修一个并发 bug 都要同时改 Agent 工具服务、通用文件写入、渲染进程编辑器中的两处或更多,而且两条写路径的原子性强度不同——用户 autosave 路径至今是非原子写。下一个并发 bug 大概率仍要跨文件修,且可能再次只修一条路径。
Solution
建立一个深的 Flow Diagram 文档存储模块(主进程),作为 .excalidraw 文档一致性的唯一拥有者:按文档串行化写入、单一实现的原子 compare-and-swap 替换、写边界的场景校验、Agent 编辑前的 revision 快照与回滚(ADR-0072 隔离 git 历史内化其中)、冲突检测结果与跨表面变更通知。Agent 工具服务退化为薄 adapter(动作解析、澄清、产物展示保留);用户编辑器 autosave 从通用文件写入迁移到该存储背后的专用文档 IPC,冲突时返回当前文档内容,渲染进程只保留展示状态(saved/dirty/saving/error、冲突横幅),删除手写的写排序与 reload 协调。
User Stories
- 作为 CDF 用户,我在画布上持续编辑时,autosave 不会覆盖 Agent 刚写入的版本,也不会被 Agent 覆盖——后写的一方基于对方的结果检测冲突。
- 作为 CDF 用户,autosave 落盘是原子的:断电、崩溃或写入中断后文档要么是旧版本要么是新版本,绝不半写损坏。
- 作为 CDF 用户,当我的编辑与外部变更(Agent 或外部程序改文件)冲突时,编辑器给我明确的冲突提示与当前磁盘内容,我的未保存改动不丢失。
- 作为 CDF 用户,Agent 修改现有图前自动记录 revision,Agent 编辑产出无效内容或中途失败时自动恢复原文档(ADR-0072 行为不变)。
- 作为 CDF 用户,我表达回滚意图后 Agent 执行 rollback 动作,图恢复到 Agent 编辑前的版本(ADR-0072 行为不变)。
- 作为 CDF 用户,Conversation 产物卡、文件面板、编辑器多个表面同时打开同一张图时,任一表面的写入让其他表面及时刷新到最新内容。
- 作为 CDF 用户,
manage_flow_diagram 工具的全部动作(read_format / create / get / edit / rollback / export)行为与错误码不变,增量编辑继续保留未触及元素与我的布局(ADR-0071 行为不变)。
- 作为 CDF 维护者,我想让下一个并发 bug 只需要在一个模块里定位与修复,并且修一次就同时覆盖用户路径与 Agent 路径。
- 作为 CDF 维护者,我想删除通用文件写入服务上只为流程图存在的 CAS 参数,让通用文件服务回到简单语义。
- 作为 CDF 维护者,我想让渲染进程编辑器收缩为纯展示组件:不再持有写排序队列、磁盘内容镜像与 reload promise 协调。
- 作为测试作者,我想直接在存储接口上写并发场景测试(交错写、CAS 失败、中断恢复、快照/回滚),不需要拉起渲染进程或完整 Agent Run。
- 作为 CDF 维护者,隔离 git 历史继续不触碰用户仓库的分支、索引、提交与 hooks(ADR-0072 约束不变)。
Implementation Decisions
- 新模块:Flow Diagram 文档存储,主进程内,唯一拥有:按文档的写串行化、原子 CAS 替换(temp 文件 + rename 语义,单一实现)、写边界场景校验(复用现有场景解析/规范化模块)、revision 快照与回滚(现有 revision store 成为其内部实现)、冲突结果类型、变更通知。
- Agent 工具服务保留
execute(action) 对外契约(动作集、结果与错误码不变),内部把文档读写、快照、回滚全部委托给存储;服务中现有的原子替换与 CAS 辅助函数迁入存储,不再从服务导出。
- 编辑器写路径迁移:
.excalidraw 文档的读写走新的存储背书的 IPC 通道(读返回内容 + 版本标识;写带版本标识,冲突时返回失败 + 当前内容)。通用文件写入 IPC 的 expectedContent 参数随其唯一消费方一并删除;通用文本编辑器路径不受影响。
- 渲染进程收缩:编辑器保留 UX 状态机(saved/dirty/saving/error、冲突横幅、Excalidraw 原生 undo/redo),删除手写保存队列、磁盘内容镜像 ref 与外部 reload promise 协调——写排序与冲突判定由主进程存储保证。防抖仍留在渲染进程(纯 UX 节奏)。
- 导出不动:PNG/SVG 导出的离屏渲染桥保持现状,只从存储读取当前文档。
- 进程边界:存储位于主进程(文件系统 + git 历史属于高权限能力),渲染进程经 preload 暴露的最小 API 访问,符合 Electron 安全边界。
- ADR 关联:ADR-0071 / ADR-0072 决策均不变,本票只加深实现它们的模块。
Testing Decisions
- 好的测试只验证外部行为:给定并发/中断/冲突场景,断言最终磁盘内容、返回的冲突结果、revision 可恢复性、通知触达;不断言内部锁或队列结构。
- 主测试面是存储接口:真实临时目录 + 真实文件系统,覆盖交错写序、CAS 失败返回当前内容、中断写不损坏文档、Agent 编辑失败自动恢复、rollback 恢复到编辑前 revision。先例:现有 flow-diagram 服务集成测试(temp 目录 + 真实 fs 的写法)。
- Agent 工具层测试保持现有契约测试(动作→结果/错误码),把一致性场景下沉到存储测试,不重复。
- 渲染进程编辑器测试聚焦用户可见行为(冲突横幅出现、保存状态指示、不丢未保存改动),沿用 Testing Library 模式。
- 回归测试先行:迁移前先把 7 个并发修复提交所覆盖的场景在存储接口上重写为测试并确保全绿,再动结构(修 bug 先补失败测试的项目纪律,应用到重构上即为行为锚点)。
- 验证:
pnpm test 覆盖 flow-diagram 全簇 + 文件服务 + 相关渲染组件;涉及 preload/IPC 变更,交付前跑 pnpm run build。
Out of Scope
- 不改
manage_flow_diagram 工具的动作集、参数与错误码,不新增图表功能。
- 不改 Excalidraw 文档格式、版本常量与场景规范化规则。
- 不做图表版本浏览器或手动回滚 UI(ADR-0072 明确不提供)。
- 不改通用文本编辑器(EditorPane)的保存行为。
- 不动导出渲染链路。
- 不迁移其他文件类型到该存储——它只服务 Editable Flow Diagram 文档。
Further Notes
Files
src/main/flow-diagram/flow-diagram-service.ts· 44 symbolssrc/main/flow-diagram/flow-diagram-revision-store.ts· 24 symbolssrc/main/flow-diagram/flow-diagram-scene.ts· 21 symbolssrc/renderer/src/components/FilePanel· 编辑器侧Problem
最近约 10 个提交都是并发修复(原子替换、compare-and-swap、冲突隔离、reload 协调),每次都跨多个文件——因为没有单一模块拥有一致性规则。一致性不变量在 service、revision-store、scene 与渲染进程之间泄漏。
Solution
一个深的
FlowDiagramStore,在小接口背后拥有 autosave 排序、替换原子性、revision 快照与冲突检测;service 退化为薄 adapter。Wins
ADR 关联
触及 ADR-0071 / ADR-0072(Editable Flow Diagrams 使用本地 Excalidraw 文档 + 隔离 git 历史)。该决策保持不变;本候选加深的是实现它的 模块,而非决策本身。
Spec(/to-spec 调研补充,2026-07-26)
Problem Statement
用户与 Agent 会并发编辑同一个 Editable Flow Diagram。保证"谁都不丢改动、失败可回滚、各表面及时刷新"的一致性规则没有唯一归属:每修一个并发 bug 都要同时改 Agent 工具服务、通用文件写入、渲染进程编辑器中的两处或更多,而且两条写路径的原子性强度不同——用户 autosave 路径至今是非原子写。下一个并发 bug 大概率仍要跨文件修,且可能再次只修一条路径。
Solution
建立一个深的 Flow Diagram 文档存储模块(主进程),作为
.excalidraw文档一致性的唯一拥有者:按文档串行化写入、单一实现的原子 compare-and-swap 替换、写边界的场景校验、Agent 编辑前的 revision 快照与回滚(ADR-0072 隔离 git 历史内化其中)、冲突检测结果与跨表面变更通知。Agent 工具服务退化为薄 adapter(动作解析、澄清、产物展示保留);用户编辑器 autosave 从通用文件写入迁移到该存储背后的专用文档 IPC,冲突时返回当前文档内容,渲染进程只保留展示状态(saved/dirty/saving/error、冲突横幅),删除手写的写排序与 reload 协调。User Stories
manage_flow_diagram工具的全部动作(read_format / create / get / edit / rollback / export)行为与错误码不变,增量编辑继续保留未触及元素与我的布局(ADR-0071 行为不变)。Implementation Decisions
execute(action)对外契约(动作集、结果与错误码不变),内部把文档读写、快照、回滚全部委托给存储;服务中现有的原子替换与 CAS 辅助函数迁入存储,不再从服务导出。.excalidraw文档的读写走新的存储背书的 IPC 通道(读返回内容 + 版本标识;写带版本标识,冲突时返回失败 + 当前内容)。通用文件写入 IPC 的expectedContent参数随其唯一消费方一并删除;通用文本编辑器路径不受影响。Testing Decisions
pnpm test覆盖 flow-diagram 全簇 + 文件服务 + 相关渲染组件;涉及 preload/IPC 变更,交付前跑pnpm run build。Out of Scope
manage_flow_diagram工具的动作集、参数与错误码,不新增图表功能。Further Notes