Truss.
live at truss.fit

Build log

A training log, built to be trained on.

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.

Bench Press 5 × 5 @ 185 lb
What shipped, in order 10 days, 1 builder
Jul 12

Core loop: Programs, Set Logging, Today

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.

Programs Set Logging Deploy pipeline PWA install
Jul 13

Program Import — programs authored by Claude, not clicked

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.

Manual Program Import Import Schema Guide
Jul 14

This Week — a real overview, not just today

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.

This Week View
Jul 14

Multi-User Support — a second real person, not a toggle

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.

Invites Per-user data AUTH_USER_MODEL swap
Jul 18–19

Production, the hard way

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.

production incident resolved same day
Jul 19

Two public-facing pages, and a way back in if you're locked out

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.

Landing page Admin password reset Portable LLM prompt
Jul 19

Two bugs, both found by actually using it

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.

mobile layout This Week
Jul 19–20

Exercise history, collapsed and connected

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.

Exercise History Week Navigation bug fix
Jul 20

Making the log-set screen feel like something, not just a form

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.

Rest Timer Est. 1RM Trend Repeat Set
Jul 20–21

An actual ending to a workout, and a reason to come back tomorrow

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

Workout Summary PR Detection Streak
Jul 21

A real visual identity: Chalk & Iron

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.

Design System Visual Identity
Right now
400
tests passing
2
accounts, fully isolated
main
auto-deploys on green
On deck