Personal training appDrawing 26 of 28Role Sole author
Gym Plan
I lift on a fixed weekly split and kept losing track of it in whatever notes app I was using that month, so I built a small gated app that holds the plan itself: a week of days, forty-eight exercises, and one tap to check a set done.
Specification / as recorded
Status
Private, in active use
Role
Sole author
Stack
Vanilla JS, no framework or build step
Sync
LocalStorage first, then a small server endpoint
Visibility
Gated behind login, not public
PlatePL-01
TitleToday's plan
ViewDetail
Two exercises checked off and the progress bar filling in.
Sheet 02 of 04/Why
A plan that lives in one place
Every notes app I tried held the plan for a week or two before I stopped opening it. The friction was small but constant: find the note, remember where the swap list was, remember which exercise I did last time I skipped this one. None of that is worth building software for on its own, but all of it together was worth an evening.
The app is one page, today's day open by default, and every exercise the plan calls for as a single row with a check-off. Nothing to navigate to before I can log a set.
PlatePL-02
TitleWeek view
ViewDetail
Seven days each with their own focus.
Sheet 03 of 04/Swaps
A swap that never breaks the streak
Some days the planned exercise is not available - the rack is taken, the machine is broken, my shoulder is not having it - so every exercise carries a curated list of substitutes grouped by muscle group. Swapping is a two-tap picker, not a detour into a different screen.
The detail that took longest to get right: a day's completion is always keyed to the planned slug, never the one actually performed. Swap an exercise and the streak, the day's total, and the history all read exactly as if nothing had changed - because from the plan's point of view, nothing did. Dates are read off the local calendar, never converted through UTC, so an evening session under-the-wire still files under the right day.
Sheet 04 of 04/Sync
Two phones, one log
The log writes to local storage first, so it works with no signal, then syncs to a small server endpoint in the background. Two devices logging the same day is the normal case, not an edge case, so a sync conflict is resolved by union rather than by picking a winner: the two devices' completed-set lists are merged together and the newer timestamp wins only for metadata, never for silently discarding a set one of them recorded.