[0514]AI EDITOR VIBE_LOG

Vibe Coding 过程复盘日志

项目:AI Editor — 本地 Markdown WYSIWYG 桌面编辑器
日期:2026-05-14
耗时:约 6 小时(从空脚手架到打包出 .exe)


灵感与初衷 (The Spark)

起因很简单:想要一个本地的、Typora 风格的 Markdown 编辑器

不要云同步、不要订阅、不要臃肿的插件系统。就是打开、写、保存——干净利落。当前市面上要么是 Typora 开始收费,要么是 Obsidian 太重,要么就是纯源码编辑器看不出渲染效果。

另外有一个很有意思的伏笔:现在不需要 AI 能力,但架构上要留口子。将来某天想加 AI 功能的时候,不需要把整个项目推翻重来。

所以核心需求就三句话:

  1. 所见即所得的 Markdown 编辑(像 Typora)
  2. 多标签页管理(不是单文件,也不是重型文件树)
  3. AI 扩展槽位(预留接口,不实现)

头脑风暴与方案定型 (Brainstorming)

编辑器引擎:三个方案,一个选择

这是整个项目最关键的技术决策。三个备选方案:

方案 引擎 优点 缺点
A Milkdown (ProseMirror) 开箱即用的 WYSIWYG、插件生态好 深度定制需要了解 ProseMirror 内部
B mdxeditor (Lexical) Meta 出品、React 友好 Markdown 支持不如 Milkdown 成熟
C Monaco + 自研 WYSIWYG 完全可控 工作量巨大,大概率造出烂轮子

选了 A — Milkdown。原因很实在:Milkdown 是为「Typora 式 Markdown 编辑器」这个场景设计的,而不是通用富文本编辑器缝了个 Markdown 插件。

后来在实现阶段发现 Milkdown v7 的 API 和文档里的 v6 有很大差异(useEditor vs useInstancelistenerCtx 的配置方式、getMarkdown 要用 serializerCtx),但整体方向是对的。

架构定型

Electron Main Process (文件 I/O + 图片处理)
        │  IPC (contextBridge, contextIsolation)
        ▼
React Renderer (Context + useReducer + Milkdown)

关键设计决策:

  • 文件 I/O 放主进程,不开放 nodeIntegration,安全第一
  • Context + useReducer 而不是 Redux——这个项目状态不复杂,Redux 属于杀鸡用牛刀
  • 5 分钟自动保存——用户明确要求(最初讨论是 3 秒 debounce,用户改成 5 分钟)
  • 图片存 images/ 同级目录——像 Typora 默认行为,相对路径引用,搬家不丢图
  • AI 扩展槽——定义 AIExtension 接口(transform + transformStream),AIPanel 组件占位

核心功能实现路径 (The Build)

实现用的是 Subagent-Driven Development 模式——把 16 个 task 逐个派发独立子代理,每个 task 走"实现 → spec review → code quality review"三轮。

实现时间线

Phase 1:基础设施 (Task 1-3)

先搭骨架。vitest + testing-library 测试环境、TypeScript 类型定义(TabEditorStateEditorActionAIExtension)、EditorContext 状态管理。

这里有个小插曲:EditorContext 的 code review 发现 context value 没包 useMemo,每次渲染都创建新对象。虽然当时测试都过了,但确实是个性能隐患。加上后 clean。

Phase 2:组件搭建 (Task 4-10)

一口气搞了 7 个组件:ErrorBoundaryTitleBarTabBarStatusBarMilkdownEditorEditorAreaAIPanel

Milkdown 集成是这里最难的部分。代理在实现时发现 v7 的 API 和 task 描述不一样——listenerCtx 要用 ListenerManager 实例而不是回调数组,getMarkdown() 要经过 serializerCtx。代理自己消化了这些差异,没有上来就问,这点很赞。

Phase 3:Electron 层 (Task 11-13)

preload.ts(IPC 桥接)、fileHandlers.ts(打开/保存/另存为 + 大文件 2MB 警告)、imageHandlers.ts(图片写入 images/ 目录 + MD5 命名)。

Phase 4:总装 (Task 14)

App.tsx 彻底重写,接上所有组件 + 键盘快捷键 + IPC 调用。代理还发现了 vite-env.d.ts 里有一个旧版的 electronAPI 类型声明,和新的 electron.d.ts 冲突,导致 TypeScript 报错。删掉后 clean。

Phase 5:用户测试 → 5 个 bug 修复

全部测试通过后启动 app,用户上手试,立刻发现了 5 个问题。这是今天最有价值的环节——


踩坑与调试实录 (Debug & Fixes)

Bug 1:奇怪的边距和滚动条

现象: 编辑器周围有莫名边距和滚动条。

根因: 两个问题叠加——① index.html 没有 CSS reset(body 默认 margin);② EditorAreaoverflow: auto 让容器本身出现滚动条。

修复: 加 CSS reset(* { margin:0; padding:0; box-sizing:border-box });overflow: autooverflow: hidden。后来用户反馈长内容无法滚动时又改成 overflow-y: auto + min-height: 100%

Bug 2:没有按钮,只能快捷键

现象: 用户不知道 Ctrl+N/O/S,界面上没有任何可点击的东西。

根因: 设计 spec 里根本没提工具栏。我只关注了键盘操作,忘了 GUI 的基本可用性。

修复: 新增 Toolbar 组件,放 New / Open / Save 三个按钮。TabBar 里的 + 按钮也移到了工具栏。同时需要更新 TabBar 测试(原本测试 "+" 按钮的功能)。

Bug 3:新文件保存报错 "fail to save to..."

现象: Ctrl+N 新建标签页后 Ctrl+S 保存,弹窗报错。

根因: 新建标签页的 path 是空字符串 ''saveFile('', content) 直接把空字符串当路径传给了 fs.writeFileSync

修复: handleSave 里加判断——如果 path 为空,调 saveFileAs 弹出另存为对话框,成功后更新 tab 的 title 和 path。这个修复的细节比较微妙:需要 closeTab + openTab 来更新已有 tab 的 path(因为 EditorAction 没有 UPDATE_PATH)。

Bug 4:编辑器不自动聚焦

现象: 新建标签页后一片空白,没有光标闪烁,必须鼠标点击一个很小的区域才能激活编辑。

根因: Milkdown 的 ProseMirror 编辑器不会自动聚焦。我经历了三次迭代才搞定:

尝试 方式 结果
1 requestAnimationFrame(() => view.focus()) 失败,DOM 还没就绪
2 setTimeout(() => view.focus(), 100) 不稳定,取决于机器速度
3 useInstance() 监听 loading 状态 成功

第三次用了 useInstance() 返回的 loading——只有当 Milkdown 报告 editor 已就绪时才聚焦。这才是正确的时机。同时在 EditorArea 加了 useEffect 监听 activeTab.id 变化,tab 切换时也主动聚焦。

Bug 5:Typora 风格

现象: 整个界面是 VS Code 暗色风格,和 Typora 的清新浅色完全不同。

修复: 6 个组件全面换肤——#1e1e1e#ffffff#cccccc#333333,蓝色 #007acc 状态栏 → 浅灰 #f5f5f5,Tab 指示条从无到蓝色底部边框。Toolbar 加浅灰背景和圆角按钮。

Bug 6 & 7:光标不到位 + 黄边框

用户反馈两个细节问题:光标还是不自动出现(需要进一步调试) + 编辑器聚焦时 ProseMirror 有黄色 outline。

光标问题的根因是 useEditor 的时机——需要用 useInstance() 而不是固定延迟。黄边框修起来简单:.ProseMirror:focus { outline: none !important }

高光时刻

"一次性写对"的模块: EditorContext 的 reducer 逻辑。6 个 action 的状态转换、CLOSE_TAB 的邻居选择算法、UPDATE_DOC 的自动 dirty 标记——代理第一次就完全写对了,12 个测试一举通过。这可能是因为 discriminated union 的类型约束足够强,让 reducer 实现几乎没有犯错空间。

"反复调整"的模块: 自动聚焦。从 requestAnimationFramesetTimeout(100)useInstance().loading,前前后后改了 4 次代码。这个教训是:不要猜测异步组件的就绪时机,要找框架提供的就绪信号。


当前成果与遗憾 (Outcome & Todo)

今天交付的成果

  • Electron 桌面应用 — 可安装的 .exe 文件(80MB NSIS installer)
  • 27 项单元测试 — 全部通过,覆盖状态管理 + 4 个组件
  • TypeScript 零错误 — 渲染进程 + 主进程双配置
  • 完整文档 — 设计规格、实现计划、PROJECT_README、知识图谱、本日志

功能清单

已实现 状态
Milkdown WYSIWYG Markdown 编辑
多标签页管理(打开/关闭/切换)
文件保存/另存为/打开
图片粘贴与拖拽 → images/ 目录
5 分钟自动保存
标签页切换前自动保存
大文件警告(>2MB)
崩溃降级(ErrorBoundary → raw Markdown)
键盘快捷键(Ctrl+N/O/S)
工具栏按钮(New/Open/Save)
AI 扩展接口 + 占位面板 ✅(接口就绪,未实现)
Electron 打包(Windows NSIS)

留给明天的 TODO

  1. 右键菜单 — 标签页上右键"关闭其他"/"关闭右侧"是刚需
  2. 拖拽排序标签页 — 目前标签页顺序固定
  3. 最近打开的文件列表 — 无文件树的情况下,这个很实用
  4. Markdown 语法工具栏 — 加粗/斜体/标题/列表的格式化按钮(目前只靠 Milkdown 内置 markdown 快捷键)
  5. macOS 打包测试electron-builder 配置了 dmg 目标但没验证
  6. 崩溃恢复 — spec 里写了"检测 5 分钟自动保存的临时副本,下次打开时恢复",还没实现
  7. AI 功能原型 — 既然接口已经有了,可以先接一个简单的 LLM 调用试试
  8. 图标 — electron-builder 提示用的是默认图标,缺一个应用 icon

开发体验收获

整个流程走下来,最大的感受是 "Subagent-Driven Development + 用户测试反馈" 这个组合拳的高效。16 个 task 被逐个派发独立子代理,每个 task 都有 spec compliance review + code quality review 两道关卡——这意味着大部分低级错误在提交前就被拦下了。

但用户测试发现的 5 个问题,没有一个是测试能查出来的:界面风格、工具栏缺失、光标位置、滚动条行为——这些都是体验问题,不是逻辑 bug。这再次验证了:自动化测试保证代码正确性,但只有人真正上手用,才能知道体验对不对。


以上就是 2026-05-14 的 Vibe Coding 日志。一个空脚手架出发,经历 16 个 task、7 个 bug、多次样式调整,最终产出了一个可以发给朋友试用的桌面 Markdown 编辑器。充实的一天。

posted on 2026-05-18 15:57  fox_charon  阅读(8)  评论(0)    收藏  举报

导航