Progression
A lifting log for people running an actual strength programme — it computes today's weights from the programme's own rules, and shows the arithmetic.
- Role
- Sole developer — Flutter app, local persistence, release pipeline
- Year
- 2025
- Status
- Not yet released
- Stack
- Flutter · Dart · sembast · Firebase

The constraint
The people who log every single session are the ones running a written programme — 5/3/1, GZCLP, nSuns, Starting Strength — where today’s weight is a function of last session’s performance. They are currently doing that arithmetic in Google Sheets, between sets, with chalk on their hands.
That audience is unusually hard to serve, because they can check your work. If the app says 82.5kg and their spreadsheet says 85kg, the app is wrong and they will know within one session.
Estimated 1RM uses Epley, because the spreadsheet does
There are better one-rep-max formulas than Epley. Brzycki fits some rep ranges more closely; several others fit better still.
It uses Epley anyway, because that is the formula in the 5/3/1 spreadsheet the lifter is migrating from. The number has to match the one they already trust. A more accurate estimate that disagrees with their sheet reads as a bug, not as an improvement — and being right is worth less here than being consistent with the thing being replaced.
What it cost: a knowingly less accurate number, and an explanation owed to anyone who checks.
Programme state is a fold over history, not a stored value
Nothing about progression is persisted as state. Training max, stall count, deload status — all of it is recomputed from the full session history on read.
The reason is typos. Log 200kg instead of 20kg and a stored-state engine records a new training max, then schedules a deload off the bogus number, and both survive fixing the typo. Recomputing means correcting the entry unwinds every decision that depended on it, automatically. No competitor’s engine can rewind; this one has nothing to rewind because it never stored a conclusion.
What it cost: more computation on every read, and a history that can never be truncated for performance without losing the ability to recompute.
Local-first, and the free tier needs no account
The free tier is entirely on-device in sembast. Install, log, done — no sign-up, no server, no account to delete later. Sign-in exists only where cloud sync does.
What it cost: no cross-device sync without the account, and every sync-related decision deferred rather than designed in from the start.
Where it stands
Shipped on Android through a signed release pipeline that builds the APK, attaches it to a GitHub release and pushes it to testers via Firebase App Distribution, plus a TestFlight path for iOS. Both are manual-dispatch rather than push-triggered — a docs change shouldn’t ship a build to testers, and macOS runners bill at ten times the Linux rate.
Monetisation is undecided and the app is free today.
What I’d do differently
I built the tracker first and the progression engine second. But recording a workout is the part every competitor already does well and gives away — the engine is the only reason this app has a right to exist. Building the differentiator second meant months of work on the part that was never going to distinguish it.
Screens
Captured from the running app against seeded demo data — the people and figures in them are fictional.


