I rewrote a working app. And lost a feature nobody knew existed
· Not by Prompt Alone · episode 3 · Czytaj po polsku
For half a year Zakupomat was an app in the browser. Ours, homemade, battle-tested. Marta fills in the list, I do the shopping, everything works.
In the summer I decided I wanted to share it. And it quickly turned out a PWA wasn't enough.
Why the PWA wasn't enough
On paper a PWA sounds great - one codebase, runs everywhere, no app stores to go through (and no paying a fair bit for developer accounts!). In practice, once you want the app to be used by more than just friends you set it up for yourself, four things come up:
- People look for apps in the store. "Go to the website and add it to your home screen" is - as it turned out - the instruction where most people give up ;-)
- Notifications. They work from the browser on phones... sort of.
- The camera. I wanted people to join a family by scanning a QR code from someone else's phone (no e-mail involved, and a generated family code isn't that short at all, while a self-chosen one could be guessed by someone else, who could then mess with our list). From the browser it always worked worse than in a native app.
- Prestige. People see apps from the stores as more professional and trust them a lot more (especially people with iPhones).
No way around it - you need an app. Marta and I have iPhones, so I wondered whether to stick to the App Store, but in Poland - where we started - Android has almost 70% of the phone market! So I'm building for both. The question was: how, when I'm developing Zakupomat on my own and after hours?
Three options
- Stay with the PWA and live with the limitations. Cheapest, but no stores :-(
- Two native apps - Swift on iOS and Kotlin with Compose on Android, separately. The most "by the book", but the same code written twice. Agents supposedly handle that well, but it's still two slightly different apps, each needing tests and attention. For one person - a no-go, especially since my knowledge of Swift is passing at best ;-)
- A cross-platform solution - one codebase for the logic and one for the UI on both platforms. Kotlin Multiplatform + Compose Multiplatform, Flutter, React Native and contraptions like .NET MAUI. I don't know Dart (from Flutter), .NET MAUI has terrible AI support and on top of that it's a Microsoft thing ;-), so the choice was potentially between the other two.
I went with the third, in the KMP+CMP flavour (the details of that choice maybe another time). And that was the moment I really felt the rules of the game had changed.
July 4th
The whole migration was done by the freshly released (after some turbulence) Claude Fable model, which burned through my usage limits like there was no tomorrow! One commit: 65 files, around 4 thousand lines of code and 56 tests. That same day the new code got its own repository.
And from there it just flowed. A week later, on July 12th, six more versions were built in a single day - QR code invites, a scanner on the login screen, a change queue that survives no network and the app being closed. In the evening the app went to the App Store - for the first time! (What happened next, I told you in the previous episode ;-))
Sounds like a fairy tale. And it almost was.
The feature nobody knew about
The old version had a small thing. If you typed a quantity on the shopping list for a product you hadn't checked yet, the app checked it for you. Makes sense - if you type "3", you obviously want to buy that product.
In the new version the quantity you typed simply... vanished. The product stayed unchecked, and your "3" was gone.
Nobody noticed it during the migration. Nobody noticed it for weeks afterwards. It only came out when Marta reported that something was "weird".
Why it got lost
Because nobody knew this feature existed. It wasn't in any description, in any feature list, in any test. It lived only in the code handling a single field and in people's habits.
And I'd been checking whether the new version matched the old one "by eye" - by clicking through the screens. Except behaviour like this isn't visible on a screen. It only happens in the interaction: what happens when you leave the field, when you confirm with the keyboard, when you type something in the wrong order.
And here's the trap that gets even bigger with AI agents. An agent will rewrite your app in a day. But it will rewrite what you show it or what it finds on its own. If nothing anywhere says that the quantity field should check the product, then nobody - neither a human nor an agent - has anything to compare the new version against.
The faster you rewrite, the faster you lose things.
July 25th
July 25th, 2026 was a Saturday - the day of the week when the most families shop with Zakupomat. That morning Marta was also tapping in the products to buy and noticed "something weird". She showed me and I knew right away what had happened. The fix was ready the same day.
But something else matters more. That same day the project's brain was born - the memory Claude reads at the start of every session. Decisions, lessons, working rules. And a feature map - a list of everything the app can do, with a column saying whether each feature has a test. The brain audit catches features that are in the code but missing from the map. It came in handy quickly, when after a wave of changes in the mobile apps it was time to bring the PWA up to speed (it's still there and does the job on desktop).
The lesson in the brain has a title that says it all: "A migration without a brain loses features silently".
And to be honest - I still don't have a complete list of "the old version could do X, the new one can't". So if anything else got lost, I'll find out the same way as the first time - from users who still remember the PWA. The feature map protects future changes. It won't fix the past.
What this means for your team
Every system that's a few years old has features nobody on the team knows about or remembers. They live in the code handling some field, in an exception added at one client's request, in a workaround for some operational process, in users' habits. In a migration or a big refactor they're the first to go - and they surface over months, one at a time, as "weird behaviour".
AI agents make rewriting a system faster today than ever. I've already seen huge systems rewritten into a completely different technology - one that AI agents work with much better - in less than 24 hours.
All the more reason to start with an inventory of what the system really does. The good news - an agent can also help rebuild that inventory before it starts rewriting anything. Unfortunately, the worse our documentation and code (and tests), the longer this takes, e.g. through extra logging and collecting the actions users perform, or going back to watching how people work with the product.
There's one more "thing" worth considering in a migration - the development team! The fact that we can rewrite a system or an app into another technology relatively easily doesn't mean our team is immediately ready to keep developing and maintaining it. Rarely does a team know both technologies. And by changing the team, we can lose a big chunk of domain knowledge or great, committed people who fit our company's culture.
At ANSLAN that's exactly where we start - with an inventory of what the system really does: we rebuild the knowledge about the system and write it down so that both people and AI agents can use it. Only then do we start the migration.
And Zakupomat itself, now with a working quantity field, you can grab and use: zakupomat.app. Free forever, no ads, no selling data, no subscription. Android, iPhone and the browser.
See you next week :-)