A training log where the set you just did is the source of truth for every chart, record, and next target.
Outliftr is a mobile-first training log for lifters who want every set on record. It covers program planning, live logging in the gym, exercise history, next-session targets, and bodyweight and food logging. I built it alone, from the Convex data model to the Android build and a landing page that runs the real app inside a phone.

- Role
- Full-stack developer, independent project
- Timeframe
- 2025 - 2026
- Product
- Mobile-first strength training app, web and Android
- Core stack
- React, TanStack Start, Convex, Clerk, Capacitor
Outliftr is for people who train on a structured split and want their numbers kept properly. It answers four questions: what to do next, what happened last time, whether progress is real, and what to change in the next session.
It is a rewrite of an earlier Nuxt version of mine. React 19 and TanStack Start run the front end, Convex holds the data model and all of the domain logic, Clerk handles auth, and Capacitor wraps the Android build. Clerk has no Capacitor SDK, so the Android app signs in with Google natively and the server exchanges that for a Clerk session.
The session screen
The screen that matters most is the live session, used on a phone between sets, often one-handed. Each set is one row with weight and reps in large tabular numbers, a check to log it, and last session's top set beneath. Checking a set starts the rest timer. Completing, editing, and stopping rest update the screen before the server answers, the screen stays awake for the whole session, and the Android app gives a small haptic tap when a set is logged.
A real workout is messy. Sets get added or deleted, an exercise gets swapped halfway through, work gets skipped. All of it goes through the same Convex mutations, so the history that comes out is consistent however the workout went.
Plan, train, review
A program is a set of days, each with exercises and planned sets, reps, and loads. Programs come from a template library, a manual builder, or an AI draft written from a short brief. The AI output has a fixed shape, may only use exercises from the 573 in the built-in library, and is rejected if it invents one or gets the day count wrong.
Starting a session copies the day into it, so editing a program later never changes a workout that already happened. Finishing a session updates the performance summaries and records, moves the dashboard's next workout along, and puts the session into each exercise's history.

Completed sets are the source of truth
Templates describe intent, sessions record what happened, and completed sets drive every number in the app. Bodyweight is stored as raw readings, and each food entry keeps a copy of the product's numbers, so correcting a product never rewrites an old diary day.
Summaries exist only where a screen needs to be fast, and each one records the version of the calculation that produced it. That makes the formulas safe to change: when the calculation improves, a backfill rebuilds the summaries in the background while the app keeps serving the previous ones, instead of showing wrong numbers or blocking the screen.
Next-session targets that show their reasoning
Analytics compare exercise exposures: one exercise in one completed session, with its volume, top set, estimated one-rep max, rest, position in the workout, earlier work for the same muscles, and that day's bodyweight. That makes it possible to say whether a lift actually moved. A squat done fourth after three leg exercises on short rest isn't the same test as a squat done first.
The next-session target is a rule, not a model. It reads recent comparable exposures, the rep range, the kind of load, and the smallest practical increment for the equipment, then says add load, hold, or back off, with the reason and a confidence level. It knows a 2.5 kg step means something different on a barbell, a machine, a bodyweight movement, a weighted one, and an assisted one.
Nutrition and bodyweight
Nutrition is a focused diary: calories and macros against daily targets, four meals, barcode lookup, recent foods, copying an earlier day, and manual entry when lookup fails. It lives in the same app because a strength trend reads differently during a cut than during a bulk.
Once there are a few weeks of food and weigh-ins, the app estimates maintenance calories from what was actually eaten and how the bodyweight trend moved, rather than from a formula based on height and age.

A landing page that runs the app
The app and the landing page share one design system, and I wrote its rules down before restyling anything: dark only, two surfaces plus a sunken one for inputs, white as the only accent, one typeface for everything including tabular numbers, and no gradients or glow.
The phone in the hero is not a mockup or a video. It runs the real session, nutrition, and workouts pages against the actual Convex backend functions, executed by convex-test on an in-memory database inside a Web Worker and seeded with twelve weeks of training. The app's own hooks run unchanged against a stand-in for Convex's React client, so the demo can't drift from the product. Every visitor gets their own copy, nothing leaves the browser, and a script logs a set every few seconds through the same mutations the buttons use.
Tests on the contracts
The tests target the domain contracts rather than the UI: starting a session and its snapshots, ownership checks, completing and undoing sets, rest timing, finishing, summaries, and history. They run with Vitest against convex-test, the same in-memory backend that powers the landing demo.
Some of them are performance budgets. They run with Convex's transaction limits switched on, and one builds a long training history and fails if starting a new session reads more data than it should, so a slow query is caught as a failing test before it reaches anyone's phone.
Outcome
Outliftr is live at outliftr.com, and I use it for my own training. What I'd point to is the restraint: no streaks, no badges, no vague AI coaching. AI only drafts programs, inside fixed limits, and every number the app shows traces back to sets that were actually done.