Blog.

What we built, what broke and what we learned. Notes from the zxn.uk studio on the apps, websites and plumbing we run, with the real numbers left in.

195GitHub repositories since May 2026
5,795keeper logs recorded in ZooLog+
1 dayfrom a Google Cloud outage to TipBop being off Google Cloud
23house rules our AI agents work to
06:02 and 15:45, joined by an arrow, set large on a pale grey background beside a 24-hour clock face with the stretch from 06:02, when the zone stopped answering, to 15:45, when we were off Google Cloud, drawn as a thick arc, above the words: Google Cloud fails at dawn. By mid-afternoon we have left.
EngineeringDecision record21 October 2026

The outage that moved us to Cloudflare

At 06:02 a Google Cloud zone stopped answering and took two products, both still in beta, with it. Our own review said stay and pay for high availability. By 15:45 we had left anyway. Here is the reasoning, and the objections to it.

Read the story

Every post

Newest first. Dated to when the work happened.

2026

  1. Eleven pieces of psychology we design with, and where to see them

    Deep dive

    A field guide to the design psychology behind our apps and sites: the published source for each idea, the screen where we used it, and the dark-pattern version of it we will not ship.

  2. Six months in, by the numbers

    By the numbers

    Mediant Partners Ltd was incorporated on 11 May 2026. Six months on: 195 repos, 5,795 animal logs in ZooLog, 4,710 commits in the five repos we work in most, and a November repo count that needs an asterisk.

  3. What Cloudflare can still do to us

    Decision record

    We moved TipBop and TyreStack to Cloudflare in a day and made it the default for everything. This is the case against that, from our own standard: five ways it fails, the limits we accepted, and four conditions that would send us back.

  4. The entitlement arrived. The feature still is not live.

    Field notes

    On 6 November Apple granted the Tap to Pay entitlement we had asked for since September. Workers still cannot use it, because the build sitting in Apple's separate App Store review would lose its place in the queue if we swapped it now.

  5. The checklist before a client site goes live

    Deep dive

    Every line on our launch checklist traces to an incident, a decision or a question nobody could answer by looking. Here is where each one came from, what we automated so the list stays short, and what no checklist can prove.

  6. The ten pound rule

    Decision record

    In October our GitHub Actions use ran to nearly five times the minutes our plan includes, and a spending cap we kept raising turned two dozen alerts red. On 5 November we set one rule for every metered vendor: free tier or £10 a month.

  7. Every guard on our agents started as an incident

    Deep dive

    Our AI coding agents run behind five small scripts that can refuse a shell command before it runs. None of them was designed up front. Each one is the scar from something that went wrong, and each says so in its first lines.

  8. Load average 14, and the CPU was mostly idle

    Postmortem

    One of our Macs crawled, with load readings up to 30 and eight agent sessions open. We blamed the cores. Measured at a load of 8 to 14, they were 68 to 80% idle. The memory had run out, and our first fix watched the wrong number.

  9. We designed for wet gloves and never tested a glove

    Deep dive

    Five design choices in ZooLog+ for keepers working outdoors, the published research behind each, and the glove test we have not run.

  10. We moved every ticket into a Markdown file

    Decision record

    On 12 October we retired our ticket tracker and put every open task in a Markdown file in each business's own git repo. It worked for just over two weeks. Then many agents writing at once pulled six of those repos apart.

  11. Who watches the alerts?

    Deep dive

    On 18 October we found our Slack bot tokens had died and every alert since had gone nowhere. We rebuilt the path so it is much harder to fail quietly, then spent a week learning that an alert path can also fail loudly, by paging for things nobody could fix.

  12. Rewriting ZooLog in Swift and Kotlin in a week

    Case study

    On 19 October the ZooLog mobile app was 134,442 lines of Dart. On 26 October it was Swift and Kotlin, opening the old app's database and settings where they lay. What it took, and why we have no better reason than a director's call.

  13. An undo button that tells the truth

    Field notes

    A TyreStack fitter now gets 30 seconds to take back a Done tap, but only while undoing it would not falsify the stock or money records. The same audit found our board plotting jobs at invented places.

  14. Postgres to D1, with the rollback timed first

    Deep dive

    D1 has no interactive transactions, and TipBop's payouts relied on them. How we moved a tipping app in beta off Postgres in a day: a lease row for the locks, one schema for 37 migrations, and a rollback we timed before we needed it.

  15. Ten thousand worlds before the first real fitter

    Deep dive

    TyreStack's scheduler had no customers yet, so before launch we built an audited oracle and threw ten thousand generated worlds of bookings, holds and late vans at it, comparing the old engine and the new one call for call.

  16. The outage that moved us to Cloudflare

    Decision record

    At 06:02 a Google Cloud zone stopped answering and took two products, both still in beta, with it. Our own review said stay and pay for high availability. By 15:45 we had left anyway. Here is the reasoning, and the objections to it.

  17. 24 files we are not allowed to edit

    Deep dive

    Ed proved his scheduling engine against ten thousand simulated worlds. TyreStack imports it unchanged, locks 24 files to his exact bytes with a hash, and the gate fails the moment one of them differs.

  18. Sign-in without passwords, SMS or an auth vendor

    Decision record

    On 16 October we wrote one sign-in standard for every app we run: an emailed code plus Apple, Google and Microsoft, our own revocable sessions, and no passwords, SMS codes or auth vendors. Here is why, and the linking rule that matters most.

  19. A thousand synthetic finger drags

    Deep dive

    A bot playing our iOS puzzle game through real touch events caught SwiftUI dropping finger positions whenever a frame ran late, turning a +672 chain into +80. It also caught a Release build that no longer compiled.

  20. Three things ZooLog refuses to do, with the numbers

    Decision record

    We built photo recognition and left it switched off. Voice can write a keeper's note but never a drug dose. And there is no keeper leaderboard. Three refusals in a welfare app, and the numbers behind each one.

  21. 20.5 hours of agent time spent watching CI

    By the numbers

    We read two weeks of our AI coding-agent sessions by hand: 129 sessions, 496 prompts. The agents were rarely slow at the work. They were slow at finishing it: they kept stopping to ask, and 20.5 hours went on watching CI.

  22. A green health check and an empty database

    Postmortem

    TipBop moved to Cloud Run on 17 September with a test-mode Stripe key, placeholder secrets, no data and no photos. The health check said ok the whole time. What it was checking, what it should have checked, and the two guards we added.

  23. The customer pays the fee, so the worker gets every penny

    Decision record

    On a £5 tip the customer pays £5.60 and the worker gets exactly £5.00. Taking a cut out of the tip instead loses money on small tips and breaks the one promise TipBop makes. Here is the arithmetic.

  24. Tappy was taken, so we became TipBop

    Decision record

    We built our tipping app as Tappy on 21 August 2026. Fifty-eight hours later it was TipBop, because the old name belonged to somebody else. How we screened more than 60 names, and why the code still says tappy.

  25. ZooLog is on Cloudflare from the first commit

    Decision record

    Two days in, the keeper app runs on Workers, D1, Queues and R2, with no other backend behind it. That was the easy decision. The hard one is what happens when a keeper logs a feed where the phone has no signal.