Build log
Truss is a self-hosted, mobile-first strength-training tracker — mesocycle programs, day-by-day logging, and now a second real user. Everything below actually shipped, in the order it shipped.
Author a mesocycle — days, weekday assignment, planned exercises with target sets/reps — activate one at a time, and log real sets against it. Quick-add for exercises not yet in the library. Deployed same day: Gunicorn + Nginx + Certbot on a droplet, GitHub Actions CI, a hand-rolled Tailwind design system built for one-handed mid-workout use.
A paste-JSON screen that turns any Claude conversation into a full program in one action — every day, every exercise, atomically or not at all. Shipped alongside a schema + worked-example guide that keeps itself honest: the test suite extracts the guide's own example and feeds it through the real validator, so the docs can't quietly drift from what the app accepts.
Replaced the single-day view with a full week: Upcoming, Today, Done, Missed, Rest — status computed, not clicked in. Split into a pure overview plus a per-day detail page, with exercise-to-exercise navigation mid-session.
Admin-issued, single-use invite links create accounts — no open signup. Every
program, session, and logged set is scoped to its owner: two people can train
the same day, the same lift, without ever seeing each other's data. Under the
hood, this meant swapping the user model out from under a live database —
AUTH_USER_MODEL, a data migration carrying the original account
forward, and a per-model for_user() pattern so ownership can't be
quietly forgotten in a future view.
First deploy of the user-model swap failed safely — no data touched — because the fix was verified against a dev snapshot with zero admin-log rows, and production had one real one. Rebuilt to preserve existing rows unconditionally instead of assuming an empty table, verified against a true reproduction of production's actual state, redeployed clean. Also: added the logout button that, somehow, had never existed anywhere but Django admin.
A real landing page at / replaced an unconditional redirect straight to
login, and this build log went public — both in Truss's own visual language, not the
app's daily-use UI. Also closed a real gap: the custom User model was
never registered with Django admin, so there was no way to reset a password without
SSH access to the droplet — now it's two clicks. The Import Schema Guide gained
a copy-pasteable prompt for any LLM conversation, not just one that can browse
this site.
Invite links overflowed off-screen on mobile instead of wrapping — a flexbox
min-width gotcha, not a typo. And a program with a future
start_date showed its planned days as already Missed for dates before
it had even started, days before the plan technically existed. Both reported from
real use on a real phone, both fixed and verified live the same day.
A reverse-chronological history of past sets, previously a "coming soon" caption
left over from an earlier pass. Same-day sets sharing reps and weight collapse
into one line — four identical sets read as 4x6 - 45lbs, not four
rows — and that summarizer is one shared function, not copy-pasted between the
log-set page and the day-detail page. This Week also gained real navigation:
Prev/Next week, decoupled from what counts as "today" for status, so browsing last
month doesn't relabel it Missed. Browsing forward surfaced a real bug: a future
day's exercise link silently logged against today instead of the day being
viewed — now those exercises just aren't clickable yet.
Five small features, each building on the last: a one-tap repeat of the last set logged, a rest timer with a real end-time (not a decrementing counter, so a throttled or backgrounded tab can't drift) and a Screen Wake Lock so the phone doesn't idle-lock mid-rest, a one-line confirmation after logging instead of a silent redirect, and an estimated-1RM trend — Epley's formula, a 12-point sparkline, drawn with the same de-emphasized-line-plus-accent-endpoint spec a proper chart would get. The log page also started showing the day's actual target ("4x6–10 · 3 of 4 sets logged") instead of leaving you to remember it.
"Next: Squat" became "Done · Next: Squat"; the last exercise of a planned day now offers "Done for the day" instead of just trailing off, landing on a new workout-summary page — exercise count, total sets, total volume, and PR callouts that reuse the same 1RM-trend math rather than a second formula. This Week picked up a streak counter: consecutive planned days actually done, walking backward from today, where a rest day is skipped rather than breaking the count and an unfinished today never zeroes one out.
The brand color was, until today, a literal placeholder — the CSS comment said
so: indigo, "no established brand yet." Three distinct directions grounded in the
actual lifting world (a chalkboard, a meet-day scoreboard, a dot-grid logbook) were
mocked up on real screens as a throwaway comparison page before any app code
changed. The winner: graphite and iron-oxide rust, sharp edges instead of
rounded-xl softness, and bracketed scoreboard-style status tags —
[Done], [Today] — borrowed from the runner-up. Because
every screen already went through the same handful of shared tokens rather than
one-off styling per template, changing six CSS custom properties reached the whole
app at once.