Lykka is a small, private, iOS-only app for capturing a good moment before it evaporates. Write a line, maybe a photo, and the app hands it back to you later — a push some mornings, a widget glance between meetings, an anniversary a year on. No streaks, no mood graph, no feed. Swedish first, English underneath. That's the whole mechanic, and I built everything else to protect it, including the AI.
The librarian, not the cheerleader
The app talks back to you in four places — a one-line reflection on a memory you just wrote, a slower pass across a batch of memories looking for taste and pattern, a grounded affirmation pulled only from things you've actually written, and a third-person retell for the rare memory you send to someone else. All four run on Claude. The obvious way to write that prompt is warm and chatty, because warm and chatty is what a model reaches for the second you stop telling it not to. I did not want that, and I found out how much work 'not that' actually is.
The rule I wrote in, and the one I've never had to loosen, is that the model hands back something you already own — it doesn't perform an opinion on it. It doesn't rewrite your sentence back to you with nicer adjectives; 'she laughed' stays 'she laughed', because the moment I let it embellish, the app's words start crowding out yours in your own journal. It doesn't invent a feeling or a person that wasn't in the memory, even a small one, even to be kind — a memory too thin to say something true about gets nothing back, not something made up. And it never asks you a question, never ends on 'isn't that something', because the second it does that it's stopped reflecting and started angling for your attention.
I wanted a librarian, not a cheerleader — hand back what's already yours, then get out of the way.
None of that is enforced by hoping the model behaves. There's a grading script next to the prompt, a folder of test memories, and a benchmark that scores every candidate reflection against the same rubric — sentence count, whether it echoes rather than rewrites, whether it invented anything. When I tighten the prompt I look at what moved and what didn't before it ships, the same way I'd look at a diff. A wording change to the app's tone gets exactly as much scrutiny as a change to a currency amount would.
How it actually got built
Lykka is a solo app — one name on the App Store listing — but I didn't type most of it. A hundred and forty-six commits between the first line on May 19th and the version live now, and every single one carries an AI co-author in the trailer. Three different Claude generations show up across those seven weeks as the models themselves moved forward mid-project — Fable 5, then Opus 4.7 with the long context window, then Opus 4.8. The upgrades landed inside the build, not before it started.
Add On This Day, Year in Memories, and Letter to the Future
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>The bug that taught me the most was in On This Day, the anniversary feature. Dismiss a memory and it's supposed to leave you alone for the day; instead the surface just re-rolled the next anniversary into the same spot, and with only one dismissal stamped, it cycled the same two memories back and forth — dismiss one, get the other, dismiss that one, get the first back, forever. The widget had its own version of the same mistake: every timeline reload re-dates whatever sits in slot zero to 'now', so pinning the On This Day card there meant it re-fronted the widget on every single refresh and crowded out the normal rotation. Neither bug shows up reading the diff. Both are obvious inside thirty seconds of actually using the app.
That's why the build loop checks the running app, not just the code. The agent installs the build on a named simulator, launches it, and reads the screen back as data — a JSON dump of the accessibility tree, so a tap lands on a computed centre point instead of a guess — then reads the device logs afterward to see which endpoint actually fired. A widget re-fronting itself on a reload doesn't show up in a code review. It shows up when something actually watches the widget refresh.
Release day runs the same way now. Signing, screenshots in three locales, submission health, crash triage — two dozen small named skills, each pinned to a hash in a lockfile like any other dependency. If one changes upstream, the hash changes and I find out before it starts behaving differently under my hands, not after.
Tests still gate everything
None of this replaces a test suite, it sits in front of one. Push to the backend and it typechecks and runs the real test suite against a Firestore emulator before anything ships; if that's clean and the branch is main, it goes straight to Cloud Run in production — there's no staging copy, because for an app this size a staging environment is just a second production nobody watches as closely. The iOS side runs the full test suite with coverage on every pull request, and only cuts a TestFlight build off an actual version tag, uploaded through the App Store Connect API rather than me clicking a button.
Put next to each other, the two halves are the same discipline pointed in different directions. I spent as much effort teaching the model what not to say to a user as I spent teaching the agent how to prove its own code actually ran. Agents did the typing on both sides of that. I mostly decided what wasn't allowed, then built something that would catch it if that line got crossed anyway.