← back to the timeline

From the build log of an AI coach that learns what you can lift and tells you exactly what to do next.

Jun 22 2026

four things got done: The app now remembers when a set hurt (groundwork). Tap any of these to read the whole thing.

The app now remembers when a set hurt (groundwork)

During a workout you can tap "something hurt" or "my form broke down" after a set.

Shipped
the problem

During a workout you can tap "something hurt" or "my form broke down" after a set. Until now the coach only used that in the moment, it was forgotten the instant the workout ended. So it could never notice "you've flagged your shoulder on bench three weeks running." Genuinely useful information was being thrown away.

what I changed

Gave those flags a permanent home in the database and started saving them against every set you log. This is deliberately just the plumbing: the flags are now kept safely, but the coach doesn't yet read your history of them or change its advice because of it. That next step is held back on purpose, deciding how the coach should react to a run of pain flags is a careful, safety-related call I want to make properly rather than by default. I also taught the server to check the flags so only real ones ("pain", "form broke down") can be stored.

how I tested it

Ran the server test suite, 38 checks pass, including five new ones for good flags, no flags, and bad flags being turned away, and built the whole app clean (no errors). It's backwards-safe: every set saved before today simply counts as "no flags".

Merged to main, PR 550, part of 43. The "actually use the flag history to coach" half stays as its own follow-up. (Carried a small live database change, applied on merge.)

Stopped a test from failing at random

One of our automated checks would sometimes fail for no real reason, a coin-flip, not a genuine bug.

Done
the problem

One of our automated checks would sometimes fail for no real reason, a coin-flip, not a genuine bug. It was the test that makes sure the app handles "you're offline" gracefully. Behind the scenes, when the internet is down the app quietly retries a few times (waiting 1, then 2, then 4 seconds) before giving up, all inside an 8-second budget. The test insisted on one exact outcome ("gave up with an error message"), but a tiny bit of random timing meant the app would sometimes hit the 8-second budget first and report "timed out" instead. Both are perfectly fine ways to say "you're offline", but the over-strict test treated the second one as a failure, so it flaked, especially on the busy build server. That red check kept showing up on unrelated changes.

what I changed

Loosened the test to check what actually matters: when the network fails, the app never pretends it worked, and it always shows a real message, accepting either "error" or "timed out" as a valid offline outcome. I changed nothing about how the app behaves; only the test got more honest. (I left a note that the deeper question, whether "no internet" should be retried at all, is a separate decision for another day.)

how I tested it

Built and ran the test three times in a row, passed every time, zero failures.

Merged to main, PR 548, part of the pre-launch hardening list (369). Clears the recurring random red check that had been appearing on unrelated pull requests.

Two clean-ups: a tidier memory, and choosing how a hand-added set counts

Two unrelated loose ends from a backlog pass.

Shipped
the problem

Two unrelated loose ends from a backlog pass. First, behind the scenes the coach keeps a little list of the dates you trained each movement, and that list quietly grew forever, one date added every workout, slowly bloating your saved profile. Second, when you add a set by hand to a finished workout, the app always filed it as a "backoff" set with no way to say otherwise, even though that label changes how the coach reads the set.

what I changed
  • The coach now keeps only the last 10 training dates per movement instead of an ever-growing list. Everything that uses those dates only ever looks at the recent ones, so nothing it does changes, the saved data just stops bloating. Older extra dates get trimmed the next time that movement is trained.
  • The "Add Set" sheet now has an intent picker, the same warmup / top / backoff / technique / AMRAP chips the live workout uses. It still starts on "backoff" (the sensible default for a set added after the fact) but you can change it before saving, and your choice is what the coach actually records. I also made the sheet scroll so the Add button is always reachable.
how I tested it
  • The memory trim: ran the server test suite (94 tests green, including new ones proving the trim drops the oldest dates, keeps everything when under the limit, and never touches the separate session counter the coach learns from).
  • The intent picker: built the whole app clean (0 errors, iPhone 17 Pro). Not yet eyeballed on a real device, reaching that sheet needs a finished workout with logged sets, but the chips are a direct reuse of the live workout's, so the look should match.
Both shipped to main, PR 539 (292, memory trim, auto-deployed to the server) and PR 545 (65, intent picker). Part of an "auto mode" backlog-cleanup pass.

The last few screens now match the rest of the app

Most of the app already wears the new "Brutalist" look, pure black, bold condensed type, one lime highlight for the main action.

Shipped
the problem

Most of the app already wears the new "Brutalist" look, pure black, bold condensed type, one lime highlight for the main action. But a handful of smaller screens never got the makeover and still showed the old dark-navy-and-blue style, so they stuck out. None of them are screens you see every day, which is why they were left for last: the "no program yet" and "all done" messages on the Workout tab, the workout error screen, the little amber "workout paused" banner, the equipment list you reach from Settings, and the "this build needs setup" notice.

what I changed

Restyled all of them to the shared look and pulled out the leftover blue. The Workout tab's empty and finished screens, the error screen, and the paused banner now use the same black background, bold type, and lime/amber accents as everything else. The equipment screen (where you add or tweak your gym gear) got the same treatment. The tab bar along the bottom also picked up a black, no-more-blue style. Nothing about how any of these screens work changed, only how they look.

how I tested it

Built the whole app cleanly for each change and once more all together at the end, every build passed. Two notes for me: a couple of system pop-ups (the "session mismatch" and confirm dialogs) can't be repainted, iOS draws those itself, so they're left as-is; and the bottom tab bar's exact selected colour is a judgement call I made (white on black) that's worth an eyeball on a real phone.

Shipped, PRs 540, 541, 542, 543, 544. Umbrella 538 stays open until I've looked at them on a real device. The debug-only developer screen was deliberately skipped (it never ships to anyone).