From the build log of an AI coach that learns what you can lift and tells you exactly what to do next.
six things got done: Swapping an exercise can now stick for good, not just for one workout. Tap any of these to read the whole thing.
Until now, if you swapped an exercise mid-workout, it only changed that one session, next time, the old exercise was back. There was no way to say "I never want to do this one; always give me that one instead" and have it stick.
Added a permanent swap. Long-press an upcoming exercise in your program and pick "Swap exercise", the coach suggests a same-muscle alternative (the same chat you already know from mid-workout), and when you confirm, it replaces that exercise everywhere it appears in your current program, keeping the same sets and rep target. Your finished workouts keep their original exercise (history stays honest), and it won't let you do this while a workout is paused or running. The change saves the whole program in one safe write.
Tests prove the swap replaces the exercise across every upcoming week of that day, leaves completed days alone, keeps the day's name (so your history stays linked), and does nothing if the exercise isn't there. Full build green.
You can already change how many days a week you train (the stepper in Settings), and rebuilding your program picks that up. But buried in the code were two leftover spots that still assumed "4 days" as a fallback. They weren't actually reached on the real path, but leaving them there is how a future change quietly ends up giving someone a 4-day plan they never asked for.
Deleted the dead "default = 4 days" constant, and made the day count a required input to the program builder so nothing can ever silently assume 4, every path now has to pass your real number. No behaviour change: the Settings control and the rebuild flow already did the right thing.
Added a test proving that when you switch from 4 days to 3, the builder uses 3 and still keeps your existing day names where they overlap (so your history stays attached). Full build + test suite green.
Because the coach now builds programs with fixed math, ten people who sign up the same way would get near-identical plans. The thing that makes it feel like yours is the app talking to you about your numbers. We had the pieces, last session's result, your floor/stretch targets, whether you're trending up, but nothing joined them into a single sentence right before you lift.
Added one narration line on the set card, shown before you start: "LAST TIME 92.5×8 → today FLOOR 95 / STRETCH 100, because this lift is trending up." It only uses real data, if there's no target yet it just shows last time, if you're not actually climbing it drops the "because", and if there's no history at all the line doesn't appear. It never makes a number up and never calls the internet.
Table tests cover every combination, full line, missing target, no history, flat/declining trend (no false "climbing"), and bodyweight moves (shows "BW"). Full build + test suite green.
When you set up the app you can tap the body areas you want to work around (knees, shoulders, lower back, and so on). We'd built the coach to treat those as a hard "don't pick exercises that hurt this" rule when it chooses your movements, but nothing was actually handing your injuries to that rule. So the switch was wired up but never flipped: your injuries were saved, and then ignored when building the plan.
Connected the two ends. When your first program is built, the areas you tapped in onboarding are passed straight into the exercise picker as hard exclusions. When the program is later rebuilt, the same thing happens from your saved injury list, and if you'd added a note in your own words ("left knee ACL"), it quotes that back instead of a generic label. So a bad knee actually keeps deep-knee exercises out of your plan now.
New tests prove an injury reaches the plan request, that an empty injury list stays empty (no noise), and that the wording is right (your note wins over the generic body-part name). Full build + test suite green.
Last week we switched the app to build your daily workout with fast, offline math instead of an AI call. That left the old AI-based builder sitting in the code, dead, nothing called it anymore, but it was still there, cluttering things and making the code look like it does something it no longer does.
Removed the whole dead builder: the function that asked the AI to invent a session, its lift-history/trend/RAG-lookup helpers, and the request/response scaffolding that only it used (~340 lines gone). I kept the parts that are still doing real work, the piece that cleans up day names and enforces "only exercises you have equipment for", plus the prompt file and its data-shape, because our tests still check those. Updated the file's description so it matches what the file actually does now.
Full build + test suite green. The kept pieces (day-name cleanup, equipment enforcement) still have all their tests passing.
One of our automated tests was stuck in the past. It checked that if the AI takes too long, the app gives up and falls back to a safe default. But it assumed "too long" meant 9 seconds, a number from before we raised the real cutoff to 30 seconds (the "Coach is offline" fix). So the test's fake slow-AI (9 seconds) no longer counted as slow, and the test failed even though the app works fine.
Made the cutoff adjustable so the test can dial it down to 2 seconds and prove the give-up-and-fall-back behaviour still works, in about 2 seconds instead of a 30 second wait. The real app is untouched: it still uses the 30 second cutoff.
The timeout test passes in ~2 seconds, and the full test suite is green.