6 分钟
独立开发周记29:消失的两个月
上一篇是 3 月底,今天 5 月 14 号。
我也不知道为什么周记总能一拖就是两个月。明明每天都在写代码,每天都在踩坑,但一让我坐下来回头梳理,就发现脑子里只剩"上周好像很忙,但忙了啥忘了"。
好在我现在策略文件里 /recap 一下,AI 就能从所有项目的 git log 和 progress.md 里给我捞出来 30 多条进展。回头看才发现,这两个月其实做了不少打脸自己的事——上一篇刚吹完的功能,这一篇就要承认砍掉了。
那么这次就分两块讲,一块是 StudyThai,一块是 OpenOwl App(顺便聊聊 Owl 系列的近况)。其他项目也有动静,但篇幅有限,挑重的说。
StudyThai
那个贡献了 22.5% 反馈的用户
GitHub Issues 攒了大半年没分诊,5 月 8 号那天我下决心把 80 个 open issue 一口气全部 root-cause 完。
结果挺有意思。
80 条反馈拆解出 23 类根因,但只有大概 13 类是真正需要工程修复的目标——剩下 10 类要么是 UX 偏好、要么是已经在做的、要么是"建议加个 XX 功能"这种长期 backlog。
更有意思的是,有 18 个 issue(22.5%)来自同一个用户。一个邮箱地址,几乎每隔几天就提一条。剔除掉这个用户之后再看,原本以为是"系统性 bug"的那些,很多其实是他个人的使用偏好。
不是说他不对,反而要感谢他——一个肯花时间给你提 issue 的用户,比一万个静默卸载的用户值钱多了。但你不能把一个高敏感度用户的偏好当成全体用户的痛点。我以前没意识到这个问题,看到 issue 就觉得"哎呀又一个用户抱怨这里",分诊后才反应过来:用户量上去之后,抱怨频率 ≠ 问题严重程度。
说白了,issue 是一个有偏的样本,不是民意。
学习者记忆和 AI 漏斗
这两个月 StudyThai 上了一个叫"学习者记忆"的功能。也就是说,AI 对话现在能记住你的学习进度——上次卡在哪个语法、最近常错哪个词、你的口语水平大概在哪——下次对话直接接着上次的状态走,不用每次都从零开始介绍自己。
技术上是给 conversation agent 加了几个 memory tool(add / list / search / consolidate),让模型自己决定什么时候读、什么时候写。设计是参考了我自己用 Claude Code 时的体感——你跟它聊久了会发现,它"记得"什么、"忘了"什么,本质上决定了体验。
配套的还有 AI 对话漏斗埋点(page_loaded / started / message_sent / ended)。其实更早之前埋过,后来重构的时候不小心搞丢了——结果是连续好几周我都不知道用户到底在对话里走到哪一步流失。补回来之后才发现,很多用户进了对话页面但就是不发第一条消息。这个洞察就够我接下来优化好几周了。
毕竟做产品最怕的不是数据不好,是没数据。
国内音频和付费转化
国内访问慢的问题,上一篇说做了 OSS 双写。这一个多月跑下来效果还行——根据头部判断地区,国内走阿里云 OSS,海外走 Cloudflare R2,体验差距肉眼可见。
付费这块也做了一次审计(5/10 那天),发现免费试用到付费的转化漏斗有个奇怪的断点:RevenueCat 给我的 trial period 数据和我自己埋点统计的对不上。排查了一下午,发现是我自己没 honor RevenueCat 的 trial 状态——简单说就是用户明明还在试用期,我已经按"试用结束"的逻辑处理了。
修完之后,Memory 这块的付费墙改走原生桥接(之前是 web 端跳转,体验非常割裂)。用户在 App 里看到的付费弹窗就是 StoreKit 弹窗,点确认就走完了,不再跳出去打个转再回来。
Android Fastlane 终于跑通了
接上一篇的吐槽——移动端打包十年没新东西。
这两个月没办法,硬着头皮把 Android fastlane 也接上了。dev / beta / 生产三条轨道全部跑通,GitHub Actions 触发,构建产物自动上传内测渠道。中间踩了几个奇葩坑,比如有一次构建出来的产物是 .zip 不是 .apk,用户下载下来怎么都装不上——查了半天发现是某个 step 的输出路径配错。
现在两边(iOS + Android)的 release pipeline 算是初步稳定。代价是这一个多月我看 fastlane 报错的时间,比看用户反馈的时间还多。
突然有点理解为什么这么多公司搞 mobile DevOps 团队了——一个人搞这套真的会疯。
OpenOwl App
这一个月发了 5 个版本,从 v1.0.4 一路到 v1.0.8。
听起来很多,但其实大半都在打补丁。真正有方向感的就两件事:一是 v1.0.8 的大重构,二是承认自己上一篇是错的,砍掉了一个功能。
自打脸时刻:local deployment 被我砍了
上一篇是这么写的——
"我的设想不是单单做一个终端,所以我在里面集成了一个轻度部署的应用功能,没使用 docker,因为我觉得你都能开发了,必定有运行的环境"
写得挺有自信的。结果 5 月 10 号那天,我把这个功能删了。
砍掉了 6 个源文件、5 个测试文件,连带删了一个 AppNavigationStore(因为它的唯一作用就是给 local deployment 当导航入口,没它就空了)。31 个测试用例直接没了。REQ-004 / FEAT-005 两份文档归档到 docs/archive/。
为什么删?
其实就是这一个多月用下来,方向越来越清晰:OpenOwl 要做的是一个终端为中心的开发工作台,不是 IDE,也不是 PaaS 入口。一旦你接受了"Terminal-first"这个定位,那么"在 App 里管理本地服务"这件事就显得很尴尬——你都开终端了,为什么不直接在终端里 pnpm dev?
而且它带来了一堆复杂性。比如 v1.0.7 我专门为它写了一套"退出确认"逻辑:用户关 App 时如果有正在跑的本地服务,要弹个框问要不要先停。这套逻辑写得我头大。砍掉 local deployment 之后,整个退出流程一句代码搞定。
说人话就是,一个错误的功能不光会浪费你做它的时间,还会持续不断地给周围功能制造摩擦。砍掉它的那一刻我觉得整个项目轻了 5 公斤。
那么经验是什么?其实就是上一篇那段"我的设想"——写得太自信了。下次新功能进 production 之前,先放在 nightly build 里跑一个月看看自己用不用。我自己都不用,那大概率别人也不用。
v1.0.8 大重构
砍完 local deployment 同一个版本,顺手把整个布局重做了。
之前的设计是顶部一个 4-tab bar(Files / Git / Deploy / Terminal),点哪个就切到哪个——这个设计抄的是 VS Code 的味道,但用着用着发现一个根本问题:终端会消失。点 Files 看一眼文件,终端就被替换了,然后我满世界找"我的终端去哪了"。
新设计是:
-
中央永远是 Terminal,不会被替换
-
右侧多了一个 28pt 宽的垂直 icon rail,永远在那里(Files / Git / Deploy)
-
每个 tab 可以收成"列表 only"(只显示文件树,不显示编辑器内容),节省横向空间
-
Sidebar 加了一个独立的 TERMINALS 区域,可以开"游离终端"——不绑定任何项目,跟原生 ghostty 的多 tab 一样
这个布局是我用了一个多月所有"觉得不舒服的地方"反推出来的。
顺便把 sidebar 加了拖拽排序——以前项目顺序按打开时间排,每次新开一个项目就跳到第一个,强迫症崩溃。现在可以手动按你喜欢的顺序排。
"点击 1.4 MB 文件没反应"的故事
这个 bug 是用户报的,我看到第一反应是"不可能吧"。
复现了一下,真的没反应。点击一个 1.4 MB 的 .dic 词典文件,editor 区域纹丝不动。再点 JSON 文件——稍微大一点的 JSON 也没反应。再点 log——同样没反应。
排查代码才发现,openFileInTab 里有一行硬编码的 if size > 1MB { return },默默返回,没有任何 UI 反馈。也就是说,App 在我不知道的地方"拒绝"了用户的操作,让用户以为是 editor 坏了。
这个 bug 本质是我半年前为了防止打开 100MB 的二进制文件卡死 App,加的一个保护——但保护得太狠了,连合理大小的文本文件都不让看。
最后改成了三级模型:
大小
行为
< 10 MB
正常打开,全功能
10–50 MB
自动进只读模式,关 tree-sitter,顶部橙色 banner 提示"已限制功能"
≥ 50 MB
弹 NSAlert 让用户确认,确认后进只读模式
总之就是无论什么尺寸,都得让用户知道发生了什么。这个 bug 让我反思了一个问题——程序员的"防御性编程"很容易写成"防御用户编程"。你想保护用户不卡崩,结果保护成"用户连点击都没反应"。
滚动条大战
不展开讲了,太琐碎。简单说就是:
NSScrollView 吃掉了 ghostty 的 scroll wheel 事件 → 我把 NSScrollView 拆了用纯 NSScroller → NSScroller 又不肯按我想要的样式渲染 → 最后我自己写了一个 ScrollIndicatorView 替代它。
13 个测试用例,连续 6 个 commit,搞定了一个原生平台早就该有的功能。
如非必要,不要幻想替换原生组件——这句话我下次再印一遍贴在墙上。
Owl 系列的近况
OwlUploader 和 OwlWhisper 这一个多月没新功能,但品牌策略文档写出来了。
简单说就是这三个工具我都不打算靠它们直接赚钱。它们的作用是赚注意力——开源 + 免费 + GPL v3 协议(防商业白嫖),上 GitHub 自然带来一波技术人群的关注,再通过我的 profile 引流到 StudyThai。
工具赚不到钱这件事是被 OwlUploader 验证过的。早期我试过收费,结果几乎没人买。改成全免费挂在 GitHub Release 之后,下载量直接起来了。
也就是说,小众工具收费的天花板是看得见的低,但免费工具带来的注意力价值远高于那点订阅收入。
那么这三个工具的分工就清楚了:
-
OwlWhisper:本地语音转文字,做"隐私优先"这个差异点,目标是 GitHub 自然搜索流量
-
OwlUploader:S3 兼容存储客户端,支持 R2 + OSS,工具属性强
-
OpenOwl:开发工作台,最复杂、最未来感的一个
每次发新版本配一条推文或者公众号文章,内容就是营销,不需要单独花精力做投放。
最后
一个月 5 个版本,听起来像产能爆发。但说实话,里面有 60% 是在打补丁、改 review 反馈、修自己上个版本引入的 regression。
AI 让我加速做事,也让我加速地踩坑、加速地推翻自己。上一篇刚吹的 local deployment,这一篇就承认错了。但我反而觉得这是好事——做错事的速度变快了,从错事里走出来的速度也变快了。
以前一个错误的方向可能要走 3 个月才意识到。现在 3 周就能反应过来:哦,这条路不对,砍掉。
那么这两个月学到的一件事是:
写下来。不写下来,你不会发现自己已经打了自己的脸。
下一篇周记,我不会再让它拖两个月。当然这话我每次都说,每次都食言,毕竟立 flag 是独立开发者的快乐源泉之一。