[0514]AI EDITOR VIBE_LOG

📅 2026/7/3 5:43:44 👁️ 阅读次数 📝 编程学习
[0514]AI EDITOR VIBE_LOG

耗时:约 6 小时(从空脚手架到打包出 .exe)


灵感与初衷 (The Spark)

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

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

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

所以核心需求就三句话:

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

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

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

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

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

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

后来在实现阶段发现 Milkdown v7 的 API 和文档里的 v6 有很大差异(useEditorvsuseInstancelistenerCtx的配置方式、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 编辑器不会自动聚焦。我经历了三次迭代才搞定:

尝试方式结果
1requestAnimationFrame(() => view.focus())失败,DOM 还没就绪
2setTimeout(() => view.focus(), 100)不稳定,取决于机器速度
3useInstance()监听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 + 用户测试反馈"这个