← 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.

Jul 3 2026

seven things got done: Your workouts can now fit how you actually train. Tap any of these to read the whole thing.

Your workouts can now fit how you actually train

What you told me.

Done
how I tested it

Each piece was built test-first and merged only once its tests passed. Not yet tried on a real phone.

What you told me. You're not in the mood for long sessions, you do four or five of the exercises the app gives you, then stop. So we looked at what actually happens to those "unfinished" workouts, and what we could change so the app stops handing you sessions that are longer than you want.

The good news first. Your unfinished workouts were never being thrown away. The exercises you do log are saved, they teach the coach, they count toward your next weights, and the app moves your programme forward. The only two places that ignored them were the Progress charts and the gym streak.

What I added (three pieces). - A "why did you stop?" question. When you end a workout early, the app now asks with one tap, ran out of time, too long, tired, something hurt, or got what I wanted. The answer is saved so the coach can learn your pattern (and so a busy day never gets mistaken for "make my workouts shorter").

  • A "Session size" setting. In Settings you can now choose how many exercises you want per workout (3 to 8). Your sessions trim down to that, but safely: it always keeps at least one exercise for each muscle the day trains, so nothing drops to zero. If it can't hit your number without starving a muscle, it keeps the session a touch longer.
  • Honest tracking of how many exercises a session offered versus how many you did, so a future "learn it automatically" feature has real data to work from.

What I deliberately left for later. The fully automatic version (the app quietly adjusting your session length on its own) and a "want to add a training day to keep your volume up?" prompt. With only a couple of people using the app, there isn't enough real data to tune those safely yet, so we're waiting rather than guessing. The manual setting does the real job today. An outside reviewer agent double-checked this call and agreed.

Merged to main as pull requests 614, 615, and 616.

Fixed a greyed-out "Start Workout" that wouldn't generate

On a program day, "Start Workout" was greyed out and nothing happened, no error, no spinner.

Done
the problem

On a program day, "Start Workout" was greyed out and nothing happened, no error, no spinner. Your programme was made by an older version of the app, before a big change at the end of June. The new code builds each workout from a fixed list of exercises baked into the programme; your older programme didn't carry that list, so the app was quietly producing an empty workout you couldn't start.

what I changed

The app now refuses to build an empty workout. Instead of a dead greyed button, it tells you the programme needs rebuilding and points you to "Regenerate Program" in Settings.

Merged to main as pull request 613. (The quickest unblock for an older programme is Settings → Reset all data → set up again, which rebuilds it in the new format.)

The Programme tab now always takes you back to the calendar

Now tapping "Programme" always lands you on the calendar, no matter which tab you were on before.

what I changed

Now tapping "Programme" always lands you on the calendar, no matter which tab you were on before. One simple rule instead of two special cases.

The gap. Earlier today I made tapping "Programme" send you back to the calendar, but only if you were already on the Programme tab when you tapped it. If you were looking at a day's exercises, popped over to the Workout tab, then tapped "Programme" to come back, the app dropped you back on that day's exercises instead of the calendar. That's the phone's normal habit, each tab quietly remembers where you last were, but it's not what you wanted here.

Heads-up. you'll only see this (and the earlier tab fix) once the app is rebuilt and reinstalled, it isn't in an older build. Merged to main as pull request #611.

Fixed a jumbled program screen, and made the tab button take you back

The bottom bar now has a solid black background so nothing shows through it (with just a short fade above it).

what I changed

The bottom bar now has a solid black background so nothing shows through it (with just a short fade above it). And the gap under the list now measures the bar's real height instead of guessing, so the last exercise always clears it no matter how tall the bar gets.

how I tested it

Both changes built cleanly on a fresh, isolated copy of the latest code. The tab-tap behaviour hasn't been tried on a real phone yet. Merged to main as pull request #607 (two commits: the overlap fix, then the tab-back behaviour).

What you spotted. On a program day screen (the one listing a session's exercises), a line of text, "This session unlocks when it becomes your next workout.", was landing on top of the exercise details and the button at the bottom, so it all read as a garbled pile.

Why it happened. That bar of buttons at the bottom floats over the scrolling list. It was set up for the simple case where there's just one big "Start Workout" button, so two shortcuts were taken: the bar faded to see-through at its top edge, and the list left only a fixed gap of space beneath it. On a day that isn't your next one, the bar grows taller, it adds the "unlocks later" note plus a couple more buttons, so its text sat in the see-through zone with the exercise card showing through, and the last exercise slid underneath it. Both shortcuts broke at once.

The second thing you asked for. When you're looking at a day's detail and you tap "Program" in the bottom nav again, it now slides you back to the calendar, the familiar "tap the tab you're already on to go back to the top" behaviour. (Tapping Program from a different tab still just takes you back to where you left off.)

Double-checked that your workouts really get saved, and closed two gaps

Are your sets actually being saved and uploaded, both while you're mid-workout and after you finish?

how I tested it

Built and ran the new tests on a clean copy of the code (isolated from unrelated in-progress work), all green: the reachability trigger (4 tests) and the manual-log durability (2 tests). Both PRs merged to main.

The question. Are your sets actually being saved and uploaded, both while you're mid-workout and after you finish? Worth being sure, because it's the one thing that must never fail.

What we found (the good news). Yes. The moment you log a set, it's written to your phone's local store first, then uploaded. If the upload fails, the set isn't lost, it stays queued and retries on its own, and if it still can't get through it's parked in a recovery bin rather than dropped. Kill the app mid-workout and it stitches the local sets back together with the server copy on relaunch. We also checked the real server: 378 logged sets across 45 sessions from 3 people are sitting there safely. So the core path is solid.

Two gaps we found and fixed. 1. The code claimed it would retry uploads the instant your internet came back, but that part was never actually wired up. It only retried on your next set or when you reopened the app. Now it genuinely retries the moment the connection returns (PR #606). 2. The "log a past workout by hand" screen wasn't using the safe save-and-retry path. If your internet was down, hand-entered sets were quietly thrown away. It now uses the exact same durable path as a live workout, so nothing gets dropped (PR #608).

Still open (flagged, not touched). 9 older sessions on the server never got their "finished" stamp, 7 of them have real sets logged underneath. The sets are safe, but those sessions look unfinished (one is a full 20-set workout). Left as a decision for later, reconciling them is a separate job.

Your training history now survives losing your phone

Until today, your identity in the app lived only on the phone in your hand.

Done
the problem

Until today, your identity in the app lived only on the phone in your hand. There was no account, the app quietly made up an identity the first time it launched, and everything you logged hung off it. Lose the phone, reset it, or even restore a backup onto a new one, and that identity was gone for good. Months of training history, orphaned. This was the biggest missing table-stake in the app.

Our automatic checks, the safety net that runs on every change, had been silently failing for a while. That's dangerous: once the build is red, a new break looks the same as the old one, so real bugs can slip through unnoticed. That's roughly how it survived several merges.

Not the app, a test. A while back we taught the coach to adjust each muscle's weekly-volume target to how often you train it (hit a muscle every day and its short window needs a smaller target). One old test still expected the answer from before that change, so it failed. Nothing was broken; the test's expected number was just out of date.

what I changed

Onboarding now has one new required step: Sign in with Apple. One tap, no email, no password. Behind the scenes it does something quietly important: instead of creating a new account, it ties your Apple ID to the identity the app already made. Your ID stays exactly the same, so nothing needs to move, all the security rules and all your data keep working untouched. On a new phone, you sign in with Apple and the app finds you again: same identity, program and history intact, onboarding skipped. People who finished onboarding before this existed get a gentle one-tap prompt on launch (they can say "Not now") so their data gets the same protection.

One number in that test (and its comment), from 3 to 0, to match the correct new behaviour. I worked the right answer out straight from the real maths first, a tight 1-day training cadence scales the chest target from 18 down to 10, and the logged volume (15 sets) already clears that, so the shortfall is 0, then the real check suite ran it and went green.

The trap we dodged. The obvious way to wire this up, just sending the current login token along with the Apple sign-in, silently does the wrong thing: the server ignores it and creates a second, empty account. We caught this before writing the feature by testing against the real server and reading the auth server's source: the correct recipe needs an explicit "link, don't create" flag. The code refuses to accept any answer where the identity changed when it shouldn't, that's tested, hard.

What's still waiting on a human. The Apple side needs one-time setup only the owner can do (Apple developer portal + flipping the Apple switch in the Supabase dashboard). Until then the button can't complete a real sign-in, the app just carries on as before, nothing breaks. The checklist is on issue #595.

The bigger call. I also shelved the whole "digital twin" brain overhaul for now (see yesterday's entry for what it is). The twin proved it can out-guess the old tracker, but not in a way that matters yet, and the data is too thin to prove more (training's been infrequent, and only a couple of people have real logs). So the twin stays parked safely in shadow, learning quietly at zero risk; we don't build more of it until there's real data and a smarter test. Nothing was ripped out.

merged as PR 600, closes 593, the build is green again.

Built a "digital twin" of each lifter, then let the data tell us an uncomfortable truth

The twin itself (a small filter that updates after every set), a replay rig that runs any brain over the full logged history and scores it…

what I changed

The twin itself (a small filter that updates after every set), a replay rig that runs any brain over the full logged history and scores it honestly, and a "shadow" hookup, the twin now quietly learns from every new workout in production, but it touches nothing the user sees and can't break anything. Its math is double-checked against a second implementation written independently, and the whole thing gives byte-identical answers on repeat runs.

how I tested it

41 new tests (filter math vs an independent reference, covariance health, replay determinism, a synthetic golden run in CI), plus the full replay of all 378 real logged sets, run twice to prove identical output.

The idea (in plain words). The big plan for the coach's brain is to stop tracking one number per exercise and instead keep a small living model of each person, how strong they really are, how fast they're improving, how tired they carry into a session. Before rebuilding everything on top of that idea, we made it pass a test: replay every set our real users ever logged, have both the old brain and the new twin predict each next workout, and see who's closer.

What the data said. The twin beats the old tracker clearly, its guesses were less than half as wrong. But a dumb baseline that just says "they'll do about the same reps as usual" beat BOTH, by a lot. Why: people pick weights so they land on their target reps, so the rep count barely carries any signal, the real information is in how the weight climbs, and there "same as last time" is nearly unbeatable too. Also, the twin's uncertainty bands were too narrow (60% of results landed inside its "90%" range).

What this means. The honest verdict is recorded in ADR-0031: we do NOT green-light the rest of the brain overhaul on this evidence. The twin stays in shadow, learning quietly at zero risk, and the test rig is ready to re-run once we either design a smarter test (planned "experiment sets" would create exactly the data this needs) or simply have more users and months of history. Learning this now, for the cost of a week of shadow code instead of a quarter of rebuilding, that was the whole point of doing Phase 1 first.