11 min read
Independent Development Diary 29: The Two Months I Disappeared
The last article was at the end of March and today is May 14th.
I don’t know why the weekly diary always drags on for two months. Obviously I am writing code every day and stepping on pitfalls every day, but as soon as I sit down and look back, I find that the only thing left in my mind is "I seemed very busy last week, but I forgot what I was busy with."
Fortunately, I now /recap in the strategy file, and AI can retrieve more than 30 pieces of progress for me from the git log and progress.md of all projects. Looking back, I discovered that I have actually done a lot of things to slap myself in the face in the past two months - the functions I just boasted about in the last article will be cut off in this article.
So this time I will talk about it in two parts, one is StudyThai and the other is OpenOwl App (by the way, let’s talk about the current situation of the Owl series). There is also news about other projects, but the space is limited and it is important to mention it.
StudyThai
The user who contributed 22.5% of the feedback
GitHub Issues have been saved for more than half a year without being triaged. On May 8th, I made up my mind to root-cause all 80 open issues in one go.
The results are quite interesting.
80 pieces of feedback were broken down into 23 categories of root causes, but only about 13 categories are targets that really need engineering fixes - the remaining 10 categories are either UX preferences, things that are already being done, or long-term backlogs such as "recommended to add XX feature".
What’s more interesting is that 18 issues (22.5%) come from the same user. An email address that I mention almost every few days. After excluding this user, we found that many of the things that we originally thought were "systemic bugs" were actually his personal preferences.
It's not that he's wrong, but I want to thank him - a user who takes the time to raise an issue with you is much more valuable than 10,000 users who silently uninstall. But you can’t treat the preferences of one highly sensitive user as the pain points of all users. I was not aware of this problem before. When I saw the issue, I thought, "Oh, another user is complaining here." It was only after triage that I realized: after the number of users increased, complaint frequency ≠ problem severity.
To put it bluntly, issue is a biased sample, not public opinion.
Learner Memory and AI Funnels
In the past two months, StudyThai has launched a feature called "Learner Memory". In other words, AI dialogue can now remember your learning progress - which grammar you were stuck on last time, which words you often made mistakes recently, and where your speaking level is approximately - the next dialogue will directly continue from the last state, without having to introduce yourself from scratch every time.
Technically, several memory tools (add / list / search / consolidate) are added to the conversation agent, allowing the model to decide by itself when to read and when to write. The design is based on my own experience when using Claude Code - after chatting with it for a long time, you will find that what it "remembers" and "forgets" essentially determines the experience.
It is also equipped with AI dialogue funnel buried points (page_loaded / started / message_sent / ended). In fact, I had buried it earlier, and then I accidentally lost it during reconstruction. As a result, I didn’t know exactly where the user was lost in the conversation for several weeks. After making up for it, I discovered that many users entered the conversation page but just did not send the first message. This insight is enough for me to optimize for several weeks.
After all, the biggest fear in building products is not bad data, but no data.
Domestic audio and paid conversions
Regarding the problem of slow domestic access, the previous article mentioned that OSS dual-write was done. After running for more than a month, the effect is pretty good - judging the region based on the head, I use Alibaba Cloud OSS domestically and Cloudflare R2 overseas. The difference in experience is visible to the naked eye.
I also did an audit on the paid side (on May 10), and found that there was a strange breakpoint in the conversion funnel from free trial to paid: the trial period data given to me by RevenueCat did not match my own statistics. After an afternoon of troubleshooting, I found that I did not have the trial status of Honor RevenueCat - simply put, the user is still in the trial period, and I have processed it according to the logic of "trial end".
After the repair, the paywall for Memory was changed to native bridging (previously it was redirected to the web side, and the experience was very fragmented). The paid pop-up window that users see in the App is the StoreKit pop-up window. Just click Confirm and you are done. There is no need to jump out and go back again.
Android Fastlane finally ran through
Continuing from the previous article’s complaint – mobile packaging has not seen anything new in ten years.
I had no choice in the past two months, so I bit the bullet and connected Android fastlane. All three tracks of dev/beta/production are running smoothly, GitHub Actions are triggered, and the built products are automatically uploaded to the internal testing channel. There were a few strange pitfalls in the process. For example, once the built product was .zip instead of .apk, and the user could not install it after downloading it. After checking for a long time, I found that the output path of a certain step was mismatched.
The release pipelines on both sides (iOS + Android) are now considered initially stable. The price is that for more than a month, I spent more time reading fastlane error reports than reading user feedback.
Suddenly I understand why so many companies have mobile DevOps teams - one person would really be crazy to do this alone.
OpenOwl App
Five versions were released this month, from v1.0.4 all the way to v1.0.8.
It sounds like a lot, but actually most of it is being patched. There are only two things that really give me a sense of direction: one is the major refactoring of v1.0.8, and the other is admitting that my last article was wrong and a feature was cut off.
Self-defeating moment: local deployment was cut off by me
The previous article was written like this——
"My idea is not to just make a terminal, so I integrated a lightly deployed application function into it without using docker, because I think if you can develop it, you must have a running environment."
It is written quite confidently. As a result, on May 10th, I deleted this function.
6 source files, 5 test files, and an AppNavigationStore were cut off (because its only function is to serve as a navigation entry for local deployment, and it would be empty without it). 31 test cases were simply lost. REQ-004 / FEAT-005 Two documents archived to docs/archive/.
Why delete it?
In fact, after more than a month of use, the direction has become clearer and clearer: OpenOwl is to be a terminal-centered development workbench, not an IDE, nor a PaaS entrance. Once you accept the positioning of "Terminal-first", then "managing local services in the App" seems very awkward - you already have a terminal open, why not just pnpm dev in the terminal?
And it comes with a bunch of complexities. For example, in v1.0.7, I wrote a set of "exit confirmation" logic specifically for it: when the user closes the app, if there are local services running, a box will pop up to ask if they want to stop first. This set of logic makes my head spin. After cutting off the local deployment, the entire exit process can be completed with one line of code.
To put it bluntly, a wrong function will not only waste your time working on it, but will also continue to create friction for surrounding functions. The moment I chopped it off I felt like the whole project was 5kg lighter.
So what is the experience? In fact, it is the "my vision" in the previous article - it was written too confidently. Next time before putting a new feature into production, run it in a nightly build for a month to see if you can use it. If I don’t use it myself, then there’s a good chance that others won’t use it either.
v1.0.8 major refactoring
After cutting down the same version of local deployment, I redid the entire layout.
The previous design was a 4-tab bar at the top (Files/Git/Deploy/Terminal), which you click to switch to. This design copied the flavor of VS Code, but after using it, I discovered a fundamental problem: The terminal will disappear. Click Files and take a look at the file. The terminal has been replaced. Then I searched all over the world for "where is my terminal?"
The new design is:
-
The center is always Terminal and will not be replaced.
-
There is an additional 28pt wide vertical icon rail on the right side, which is always there (Files / Git / Deploy)
-
Each tab can be harvested as "list only" (only the file tree is displayed, not the editor content), saving horizontal space
-
Sidebar adds an independent TERMINALS area, which can open a "free terminal" - not bound to any project, just like the multi-tab of native ghostty.
This layout was derived from all the "uncomfortable places" I used for more than a month.
By the way, drag and drop sorting has been added to the sidebar - in the past, the order of projects was based on opening time. Every time a new project was opened, it would jump to the first one, and the obsessive-compulsive disorder collapsed. Now you can manually sort them in the order you like.
"No response when clicking on 1.4 MB file" story
This bug was reported by a user, and my first reaction when I saw it was "It's impossible."
I tried it again, but there was really no response. Clicking on a 1.4 MB .dic dictionary file, the editor area didn't move at all. Click on the JSON file again - slightly larger JSON does not respond. Click log again - no response.
After checking the code, I found that there is a hard-coded line of if size > 1MB { return } in openFileInTab, which returns silently without any UI feedback**. In other words, the App "rejected" the user's operation in a place I didn't know about, making the user think that the editor was broken.
The essence of this bug is that I added a protection half a year ago to prevent the app from freezing when opening a 100MB binary file - but the protection is too harsh, and even text files of reasonable size are not allowed to be viewed.
Finally it was changed to a three-level model:
size
Behavior
< 10 MB
Open normally, fully functional
10–50 MB
Automatically enter read-only mode, turn off tree-sitter, and the orange banner at the top prompts "Function Restricted"
≥ 50 MB
Pop NSAlert to ask the user to confirm, then enter read-only mode after confirmation.
In short, no matter what the size, the user must know what is happening. This bug made me reflect on an issue - Programmer's "defensive programming" can easily be written as "defensive user programming". You want to protect users from crashes, but the result is "the user doesn't even respond to clicks".
scroll war
I won’t go into details, it’s too trivial. Simply put it is:
NSScrollView ate ghostty's scroll wheel event → I dismantled NSScrollView and used pure NSScroller → NSScroller refused to render according to the style I wanted → Finally, I wrote a ScrollIndicatorView to replace it.
13 test cases and 6 consecutive commits have completed functions that a native platform should have long ago.
Don’t fantasize about replacing native components unless necessary – I will print this sentence again and post it on the wall next time.
The current status of the Owl series
OwlUploader and OwlWhisper have no new features for more than a month, but the brand strategy document has been written.
To put it simply, I don’t intend to make money directly from these three tools. Their function is to earn attention - open source + free + GPL v3 protocol (prevention of commercial prostitution). Being on GitHub naturally brings a wave of attention from technical people, and then leads to StudyThai through my profile.
The fact that the tool does not make money is verified by OwlUploader. I tried charging in the early days, but almost no one bought it. After changing to completely free and hanging on GitHub Release, the number of downloads increased immediately.
In other words, the ceiling for charging for niche tools is visibly low, but the attention value brought by free tools is much higher than that subscription income.
Then the division of labor between these three tools is clear:
-
OwlWhisper: local speech-to-text, making "privacy first" the difference, targeting GitHub natural search traffic
-
OwlUploader: S3 compatible storage client, supports R2 + OSS, and has strong tool attributes
-
OpenOwl: Development workbench, the most complex and futuristic one
Every time you publish a new version, add a tweet or public account article. The content is marketing, and you don’t need to spend separate energy on distribution.
at last
Five releases a month sounds like a burst of capacity. But to be honest, 60% of it is patching, changing review feedback, and fixing the regression introduced in the previous version.
AI allows me to do things faster, step into pitfalls faster, and overthrow myself faster. I just talked about local deployment in the last article, but I admit that I was wrong in this article. But I think this is a good thing - the speed of doing wrong things has become faster, and the speed of getting out of wrong things has also become faster.
In the past, it might take 3 months to realize a wrong direction. Now it takes 3 weeks to realize: Oh, this path is wrong, cut it off.
So one thing I learned in these two months is:
Write it down. If you don’t write it down, you won’t realize you’ve slapped yourself in the face.
I won’t let the next weekly diary post for another two months. Of course, I say this every time, and I break my promise every time. After all, setting up a flag is one of the sources of happiness for independent developers.