nine things got done: A clearer "you've got a workout going" badge above the tab bar. Tap any of these to read the whole thing.
The little dot on the Workout tab was meant to glow blue when a workout was live and amber when it was paused. But the iPhone refuses to colour that kind of dot, it just paints its own red dot for both. So the dot could only tell you "a workout exists," never which: live or paused. (That's why it "stayed red" after pausing.) The dot also couldn't be tapped to get back to the workout.
The dot is gone. In its place, a small floating pill sits just above the tab bar, visible from the Program, Progress, and Settings tabs. When a workout is live it shows a gently pulsing dot and the word "Training" in blue; when paused it shows a still pause icon and "Paused, tap to resume" in amber; when nothing's going on, it's not there. The movement is the main "this is alive" signal, with colour, the icon, and the words all backing it up, so it still reads clearly for colourblind users and for anyone who turns off animations. Tapping the pill jumps you to the Workout tab (it doesn't restart anything, the paused screen from earlier today handles that). It's hidden while you're already on the Workout tab, since you can see the workout right there (same way Apple Music hides its mini bar on the now-playing screen).
Added tests for the bit that decides live-vs-paused-vs-nothing, including that "live" always wins if both signals are set. Built clean on the iPhone 17 simulator; full suite of 633 tests passed, 0 failures. An independent reviewer dug into the two scariest risks, could the floating pill block taps on the tab bar, and does it update when the workout state changes, and both came back fine. The exact spacing above the tab bar still needs a real eyes-on check on a device.
If you paused a workout and later just tapped back onto the Workout tab, the app quietly started the workout again, no button, no question. Merely looking at the tab resumed it. That felt jarring: you couldn't peek at where you were, or sit on a paused workout, without it springing back to life. The same thing happened after the app was force-closed mid-workout.
Now, opening the tab on a paused workout shows a calm "Workout paused" screen instead of restarting. It tells you where you left off (which exercise, which set, and the time you paused), and gives you three clear choices: Resume, View today's plan (just look, don't resume), or Discard. The workout only starts again when you tap Resume, which runs the exact same resume machinery as before, just triggered by your finger instead of by the screen appearing. All the existing safety checks (right day, exercises unchanged, right account) still run, now on the tap. Returning to a workout that's genuinely still running re-attaches automatically as before, that was never the annoying part.
Added a test locking in "a poll or a tab open never auto-resumes, only a deliberate resume goes live." Built clean on the iPhone 17 simulator; full suite of 631 tests passed, 0 failures. An independent reviewer went through it and flagged two small edges (a stale screen if the day changed underneath you, and a brief window where a just-discarded workout could pop back), both fixed before merge. The look of the new screen still needs a real eyes-on check on a device.
The single "live or paused" brain we built (#440) asked the workout engine three separate questions in a row, what state are you in, which day, which session, to figure out its answer. Because they were three separate questions, the engine could change its mind in between, so in theory the answers could come from slightly different moments and not line up. It was safe in practice (a built-in check caught it), but fragile, exactly the kind of thing that was already fixed everywhere else in the app by asking for everything in one snapshot.
The brain now grabs the whole answer, state, day, and session together, in a single snapshot, so the pieces always come from the same instant and can't drift apart. Nothing about how it behaves changed; it's just sturdier under the hood. (#458)
Test-first: added a test that the snapshot now includes the session id and matches the engine. Built clean, all 39 tests pass (the 8 brain tests unchanged and still green, confirming behaviour didn't move).
There was a leftover sticky note inside the app called "crash resume day." When you came back to a workout that lived on a different day than the next one due, the app stuck a note saying "you're really on day B." That note was only torn up once you tapped Done. So if you started day B, then wandered off without finishing, paused it, switched tabs, the note stayed stuck, and the next thing you finished got marked against day B instead of the day you actually did. Wrong day, silently.
The sticky note is gone entirely. Now the app asks the one "live or paused" brain (built yesterday, #440) which day you're really on, both for which workout to show AND for which day to tick off when you finish. Because the same single source answers both questions, they can't drift apart, so marking the wrong day is no longer even possible (not just patched, structurally impossible). The two tangled "resume a workout" code paths were also merged into one. The crash-recovery pop-up on reopening still works exactly as before. (#441)
Built test-first in an isolated copy, then independently and adversarially reviewed by a second agent whose job was to break it, it manually re-played the exact wrong-day scenario against the new code and confirmed it can't happen anymore, with no blockers. Verified a third time here: full app builds clean, 46 tests pass (8 new). One tiny cosmetic edge in the paused banner (pre-existing, not caused by this) was noted for later.
Different parts of the app each figured out on their own whether you had a workout in progress or paused, the little dot on the Workout tab, the "you have a paused workout" banner, the calendar's highlight of today, and the day screen each asked a different place and checked at a different moment. So they could disagree: the tab dot still said "live" for a few seconds after you'd paused, while the banner already said "paused." Confusing, and the root of a lot of the jumble.
There's now a single source of truth, one small "coordinator" that holds exactly one answer: are you idle, live on day X, or paused on day X. Every part of the app, the tab dot, the banner, the calendar highlight, the day screen, now reads that one answer, so they can't disagree anymore. The old separate watcher that each part used to poll was folded into this and deleted. A live workout always wins over a leftover "paused" note, and the earlier safety check (#447) still blocks resuming a workout whose exercises changed. (#440)
Built test-first by one agent in an isolated copy, then a second agent reviewed it independently and adversarially (its only job was to find what's wrong), it confirmed every rule held and found no blockers. Verified a third time here: full app builds clean, 38 tests pass (8 new ones for the coordinator). One small fragility the reviewer spotted (the coordinator reads three values from the workout engine in three steps instead of one) is harmless today but logged as a follow-up (#458) so it can be made rock-solid.
When you pause a workout and come back later, the app restores where you left off, which exercise, which set. But if the day's exercise list had changed in the meantime (say the programme was edited), the app still trusted its old place-markers and quietly logged your sets against the wrong exercises. It only checked that the day was the same day, not that the day's exercises were still the same.
When you pause, the app now saves a small fingerprint of that day's exercise list. When you resume, it re-checks the fingerprint against the day as it is now. If they don't match, it stops instead of guessing, it shows the "this workout changed, start it fresh" recovery screen and keeps your paused session safe so you can still save or abandon it. Paused sessions from before this change have no fingerprint, and those resume normally as before, so nothing old breaks. (#447)
This was finished test-first: built the app clean and ran the full workout-session test set, 29 tests, all green, covering the changed-list case, the normal case, the old-no-fingerprint case, and that the fingerprint is stable across app restarts. (Side note: this fix had been written in an earlier run that was wiped by the laptop running out of disk; the work was recovered from disk, the one half-finished test was completed, and then verified.)
Two deeper data problems from the same audit. First, a workout's "which day was this" was only a private ID the phone made up, the server never stored it, so when the app tried to recover an interrupted session it had to invent a new ID, which never matched and produced "Session Not Found." Second, when you finished or skipped a day, that status was only saved on the phone; the server's copy of your programme never learned about it, so reinstalling the app could make finished days look unstarted again.
training_day_id
(a small, safe database change, a new optional column; old rows simply leave it empty).
Recovery now matches on the real ID instead of guessing. (#443)Two helpers built each piece test-first; #443 got an extra reviewer whose only job was to audit the database change (it confirmed the column is optional, reversible, and safe). Both built clean with new tests passing. The database change is written but applied to the live database by the deploy step, not by hand.
After the first batch of fixes, three smaller-but-real issues were still open from the same audit: (1) the app counted "how many days you've finished" in four different places with copy-pasted logic that could drift apart; (2) the day screen used the wall-clock calendar to decide whether you could skip a day, so your actual next day could be wrongly un-skippable because of its date; and (3) when your training programme hadn't saved to the server yet, starting a workout would quietly fail a database check, retry for half a minute, then lose the logged sets with no warning.
Three helpers built each piece test-first in its own sandbox; an independent reviewer checked each diff against the plan and re-ran the tests. All built clean (one ran 42 of the data-layer tests green). The three changes touch completely separate files, so they merged with no conflicts.
If you opened a session from the Programme screen and started lifting, the Workout tab and the Programme screen could end up showing different days while secretly sharing one live workout underneath. The worst part: finishing could mark the wrong day as done, so your plan quietly jumped past a day you never trained. There were knock-on errors too, a "Session Not Found" message after starting a new programme, and stale buttons offering to "Start" days that were already finished.
Three focused helpers each built one piece test-first in its own sandbox, and an independent reviewer checked each against the plan. Each built clean and its new tests pass (one ran the full 875-test suite). The shared CI "Build & Test" is red, but it's been red on the main line for many commits on an unrelated test crash, not caused by this work, so we didn't block on it.
How we made sure what was broken. We ran a big audit first, a panel of helper agents read the code, then a second set tried to disprove each finding so only real ones survived. It pointed at one root cause: nothing owned "the workout you're doing right now," so two screens each guessed the day on their own. We turned that into tracked tickets, made seven product decisions, and shipped the cheap, high-value fixes first.