Sprint M3 — dogfooding to the store runway
Sprint M2 ended with a contract: my phone could read my household and capture into it, offline, against a server surface two AI agents had negotiated over a message bridge. M3’s job was less glamorous and more important — make it a product, then get it onto the runway to the App Store. Nine days later: 57 merged PRs, the production app on my phone via TestFlight, and offline editing live — plus the most instructive week of the project so far, which had almost nothing to do with writing features.
The product showed up first
The first days were pure surface: a calendar that finally earns the tab — agenda, day, 3-day, and a month view that scrolls week-by-week forever, Apple style; camera capture that queues encrypted photos offline and uploads when it can; chat with streamed replies, a Stop button, photo attachments, and confirm cards for everything Domi proposes; shopping lists you can drill into and check off at the store with no signal; a task composer with descriptions, a date picker, and photos that attach to the task itself.
Then a deliberate “store-ready pass”: local reminders (tasks at due time, events thirty minutes before, snoozes respected, lock-screen privacy option), Face ID app lock, language override, and the account-deletion entry Apple requires. None of it needed the server; all of it is what separates a demo from something you’d let a reviewer hold.
MP1: the production app in my pocket
Mid-sprint we closed the milestone that matters psychologically: App Store
Connect record, API key, first production build, eas submit, TestFlight —
and the production app, against the production server, running my real
household next to the dev build. From repo creation to TestFlight: eleven
days.
The same infrastructure gave us an over-the-air update channel, which we then proceeded to prove in anger — about ten production OTAs this week, with a median fix-to-my-device time under fifteen minutes.
Then I used it. That’s when the real sprint started.
A week of actually living in the app produced four bugs, and I want to record what found them, because it wasn’t the CI, and it wasn’t either agent.
A version number that didn’t match. The production app said “staging” in Settings after an update. Root cause: publishing an OTA without pinning the app variant ships the dev runtime config to the production channel — the exact cross-wiring the architecture forbids in bold type. The fix took minutes; the guard (a publish script that makes the mistake impossible) took an hour; my habit of reading version stamps took years.
A Sign-out button on a signed-out screen. Revoking my phone’s session from the web left the app happily browsing cached data, with no way back to sign-in. The fix is the behavior the spec always wanted: no session → the sign-in screen, instantly, even mid-use. While we were in there, the app stopped ever forgetting who you are — identity now persists in the Keychain until you actually sign out.
A grocery list that existed on the web but not in my hand. The deepest one, and it was on the server: household-group rows created through the assistant were invisible to the mobile sync’s visibility resolution. Every “my item never arrived” ghost of the past week traced to this one hole. The web-side agent reproduced it with a diagnostic, fixed it, and — on my word — fast-tracked it to production the same evening.
A card the assistant swore it had shown me. The chat said “confirm the card below” and there was no card. The proposal had been on the wire all along, inside stream events the mobile parser was built before anyone invented. Now cards render mid-stream as Domi types, and survive restarts.
Four bugs. Four same-day fixes. Three delivered over the air while I kept testing. If you want a case for dogfooding before App Review instead of during, this week is it.
Editing arrives — the last “go use the web for that”
The one thing testers would have hit in minutes: you couldn’t fix a typo in a task from the phone. The sync protocol was deliberately append-only — my own scoping decision from M2 — so extending it was a contract negotiation, not a patch: five new push actions, each carrying the row version the device last saw; if someone edited first, the server rejects, the phone refreshes, and nothing is ever overwritten silently. No merge engine. The same server mutations now serve the web UI, the chat assistant, and the phone — three doors, one room.
By the numbers
- 57 PRs merged in 9 days (every one through CI on a protected main)
- 58 unit tests (up from 38), 3 end-to-end flows, a 10-gate offline release suite, an accessibility pass in two languages
- ~10 production OTAs; 2 server prod-promotes carried mobile-driven work
- 17-point device validation: 16 passed, 1 pending a row inspection
- 11 days from
git initto TestFlight
What’s next
M4 is the endgame sprint: version 1.0.0, the release-gate device pass, screenshots in both languages, the reviewer demo account, and the Submit button. The store pack — listings, privacy labels, review notes — is already written. The remaining work is mostly fingers on glass.
The app I’m carrying now reminds me about the septic tank, knows my week starts on Monday, and lets me fix a typo from the couch. That last one took a contract amendment between two AI agents and a veto I eventually lifted. Software is still software.