← Rich Shakespeare

Trust from the outside: shipping an app AI wrote

3 June 2026

This month I shipped an app to the App Store and Google Play without reading a single line of its code as it was written. (Not quite true — I broke that rule twice before launch, and the second time is the point. I'll get there.)

I didn't skim it or spot-check it. Zero lines, all the way through the build. For someone who has spent fourteen years telling other people that unreviewed code is unshipped code, this took some getting used to.

The app is Tally, fitness leagues you run with friends. It felt like the right place to try it: my own product, my own risk, nobody else's members database on the line. I already knew AI could write an app. What I wanted to know was where my experience would actually show up, if I never saw a line of it.

The first answer turned out to be taste.

Every hour I would once have spent in the diff went on the screen instead. Fiddling with button padding and colours for way too long. Removing the things AI loves and people don't: flashing dots, "Live now!!" hype Live now, everything shouting for attention. Checking what it feels like on a slow connection, at the bus stop, with one bar of signal. AI would happily generate ten versions of a screen, and all ten would be fine, and fine is exactly the problem. Knowing which one is right isn't in the training data, and I don't think it ever will be. That part of the job grew until it was taking most of my time.

But taste only covers the parts you can see. The things that scare an engineer are invisible. Will it be performant? Will it fall over at a thousand users, or ten thousand? When you've read every line, you carry a mental model around with you, and you know where the bodies are buried. Ship without reading and that model just isn't there. The code probably works. "Probably" is a horrible word to build on, and I found that trust hard to quantify. There's no dashboard for it.

Somewhere in the middle of all this, the feeling turned familiar. I'd seen it before, from the other side of the table. This must be what it's like to commission an app from an agency when you can't read code, or to sit at board level signing off a system you'll never see inside. For years at ChangeLab I was the agency, and our clients had to take the build on trust. All they could do was ask questions, watch demos, poke at the edges and hope the answers held up. I used to think of that as a limitation on their side. Now it was my whole toolkit. The code was a black box, and I had to come at verification from every angle that didn't involve opening it: planning harder up front, reviewing behaviour by hand, and asking the builder "how did you do this?" the way a sharp client interrogates a supplier.

The one advantage I kept was knowing what a bad answer sounds like. So I built the trust from the outside.

I covered the app in something like ten times the tests I'd normally write. AI code doesn't fail more often than human code. But the tests were now the only part of the system whose behaviour I could hold in my head, so I wanted a lot of them. I set up a harness to load test it locally, throwing far more users at it than it will ever see, and watched what buckled. Plenty did: unoptimised queries that were fine at a hundred users and ugly at ten thousand, hot spots with no caching in front of them because nothing had ever asked for any. AI builds for the users it can see. And I broke my own rule twice. The first break was small: I read the handful of high-touch queries, the ones that run on every screen, because a bad query is the difference between an app and an outage, and no amount of visual review will show you one.

The second break was bigger, and deliberate. The experiment ended at TestFlight. Taste and performance are my problem; if the feed is slow, the worst case is embarrassment. Other people's personal data is not mine to gamble with. So once the TestFlight build was done, before anything went near the stores, I reviewed and tested the app for security exactly as I would production code at work: authentication, authorisation, what gets stored, what leaves the device, who can see whose data. By hand, line by line, the boring way.

The load testing paid for itself. Tally has a feed of your friends' workouts and the banter that follows. AI's version assembled it at read time: query everyone you follow, gather their posts, sort, render. Big naive reads, on every open of the app. Works beautifully in the demo. Dies at scale. The fix is the oldest trick in the feed builder's book: fan out on write. When a post is created you do the work once, pushing its ID into each follower's precomputed feed, and reading becomes one cheap lookup. You pay a little on every write to make the read nearly free, and reads happen a hundred times more often than writes.

Diagram comparing the two feed architectures: assemble on read, where every app open queries, fetches and sorts everything; and fan out on write, where a post is saved once and pushed onto each follower's feed, so opening the app is one lookup

It's how every big social feed has worked for nearly two decades, and Tally is the ideal case for it: small friendly networks, nobody with a million followers to make the writes hurt. Across the entire build, AI never once suggested it. I asked for it in one sentence, and it built it perfectly.

I still felt useful. That was a relief.