Build Hevy Pro
YESreplaces $2.99/mosaves $35.88/yrback to the verdict
A progressive web application that works with no signal in a gym basement: an exercise library, routines, live set logging with a rest timer that survives a locked screen, previous-session prefill, neutral progress charts, and export of everything you have logged as JSON and CSV.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the build and the copy lint.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - have readyfree
Why Every important behaviour here is a phone behaviour: locked screen timers, one-handed use, storage eviction. Testing only in a desktop browser will mislead you.
Get it Serve the development build over your local network and open it on your phone, or use a tunnelling tool to get an HTTPS URL, which service workers require.
Verify
The application installs to the home screen and opens without browser chrome - installfree with cloudflared
Why Service workers and installation require a secure context, and your laptop's local address is not one on a phone.
Get it A tunnelling tool such as cloudflared, which is free, or a self-signed certificate you trust on the device. open ↗
Verify
The phone loads the site over HTTPS and the service worker registers - have readyfree
Why Seeding a few hundred movements saves an hour of typing, but a scraped list from a commercial app is someone else's data.
Get it Write your own from the movements you actually do, or use an openly licensed list such as the one published by the wger project, and keep the licence note. open ↗
Verify
The source of every seeded exercise is documented - decidefree
Why Phase 6 needs a second reader for tone. You cannot audit your own wording for pressure because you know what you meant.
Get it Ask one person, give them the printed strings, and ask specifically whether anything reads as judgement.
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- exercises(id, name, muscle_group, equipment, is_custom) - routines(id, name, notes, created_at) - routine_items(id, routine_id, exercise_id, position, target_sets, target_reps, rest_seconds) - sessions(id, routine_id, started_at, ended_at, notes) - sets(id, session_id, exercise_id, position, weight, reps, rpe, completed_at) - Weight is stored in one canonical unit with the display unit as a preference, so switching units never rewrites history.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
VITE_WEIGHT_UNIT_DEFAULT | required | kg | The default display unit. Storage is always canonical, so this only affects presentation. |
VITE_EXPORT_REMINDER_DAYS | required | 14 | How often to prompt for an export. Chosen by you; storage eviction is the reason this exists. |
VITE_SYNC_ENDPOINT | optional | https://sync.example.com | Product phase only. Leave unset and the application stays entirely local, which is a legitimate final state. |
SYNC_TOKENsecret | optional | replace-with-a-random-token | Product phase only. Shared secret between your devices and your sync endpoint. |
The build, in order
Offline shell and the exercise library
The application opens and works with the network switched off, and logging survives a reload.
Vite with a plugin that generates the service worker, a web app manifest with icons, and a precache of the shell.
Files
vite.config.tsmanifest.webmanifestterminalnpm create vite@latest . -- --template vanilla-ts npm install -D vite-plugin-pwa
One place that opens the database and applies upgrades by version. Getting this wrong later means users with a half-migrated store.
Files
src/db.tsA few hundred common movements with muscle group and equipment, plus the ability to add your own. Ship the seed as a JSON file so it is reviewable.
Files
src/data/exercises.json
done when · tick each as it passeswatch out- Browser storage can be evicted. Safari in particular is aggressive with sites that are not installed to the home screen, and an eviction takes your training history with it. Ship export from the beginning, prompt for it periodically, and say plainly in the interface that installing the application makes storage far more durable.
Logging a workout, and a timer that does not lie
You can run a whole session one-handed, and the rest timer is right after the phone has been in your pocket.
Large increment and decrement controls for weight and reps, a big done button, and no swipe-to-delete without an undo.
Files
src/session.tsStore started_at. Compute remaining on each animation frame and on the visibilitychange event. Never hold the remaining value as the source of truth.
Files
src/timer.tsPersist on completion of each set, not at the end of the session. A crashed browser must cost you one set at most.
Files
src/db.ts
done when · tick each as it passeswatch out- setInterval is throttled in background tabs and stops on a locked screen, so a rest timer built by decrementing a counter will show ninety seconds remaining after four minutes in your pocket. Store the start time, compute elapsed on every render and on visibility change, and use a notification for completion rather than relying on the tab staying alive.
Routines and prefill
Starting a familiar session takes one tap and already knows what you did last time.
Reorderable exercises with target sets, reps and rest. Keep targets optional, because plenty of people train without them.
Files
src/routines.tsQuery the latest set rows per exercise. Distinguish clearly between no history and a logged zero.
Files
src/prefill.tsEditing today's session must never write into a past session's rows. Add a test that edits a prefilled value and asserts the source session is unchanged.
Files
src/prefill.test.ts
done when · tick each as it passeswatch out- Prefilling from the last session and then presenting it as a goal turns a convenience into pressure. Show it as what happened last time, in neutral wording, and never highlight a lower number as a failure.
Neutral progress, enforced by a test
Charts show what happened, and the application cannot start moralising without a test failing.
Volume per week, estimated one-rep max per exercise over time, and sessions per week. Gaps stay gaps rather than being connected by a line that implies data.
Files
src/charts.tsA build step that scans interface strings for shaming and diagnostic language, including streak, failed, cheat, guilty, earned, burn off and body fat percentage judgements. Fails the build, not a warning.
Files
scripts/copy-lint.tsscripts/banned-words.jsonAn estimated one-rep max is a formula applied to a set, not a measurement. Say so where it appears, so nobody treats it as a tested number.
Files
src/charts.ts
done when · tick each as it passeswatch out- Streak counters and grade letters feel motivating for a fortnight and then quietly punish illness, travel and rest weeks. If you want a nudge, show consistency as a count of sessions in a window, which recovers naturally, rather than a streak that breaks.
Export and import
Your training history is a file you hold, not a row in someone's database.
Every table, with a schema version so a future import knows what it is reading.
Files
src/export.tsJoin identifiers to names and include units in the header. Nobody wants an exercise id in a spreadsheet.
Files
src/export.tsImport into an empty database and compare a normalised checksum with the source. Round-trip equality is the test worth having.
Files
src/import.tssrc/export.test.ts
done when · tick each as it passeswatch out- A CSV export that loses the unit, the timezone or the exercise identity is a spreadsheet, not a backup. Keep JSON as the lossless format and treat CSV as a convenience, and say which is which in the interface.
Sync, devices and a safety reviewproduct builder
It works on more than one device, and someone other than you has read the words it says.
Every row carries updated_at and a deleted flag. Sync exchanges rows since a watermark. Deletions travel as tombstones, never as absences.
Files
src/sync.tsWhen both sides changed the same row, keep both and show a short list. Training data is small and a person can resolve four conflicts.
Files
src/conflicts.tsPrint every interface string and read it as though you had missed three weeks. Record what you changed. This is the phase where the rule zero promise is kept or broken.
Files
docs/tone-review.md
done when · tick each as it passeswatch out- Sets are appended far more often than edited, so last-write-wins per row is genuinely adequate here, but only if deletions are tombstones. A deletion that is simply an absent row will be resurrected by the other device on the next sync.
That is the whole plan for Hevy Pro. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Wearable and watch data, which needs hardware and platform health permissions.
- The social feed, following and the community that makes the paid app feel populated.
- Programme generation and coaching of any kind.
- Any diagnostic, clinical or body composition claim.
- mobile apps, social features, watch integration, sync, and polished tracking
- wearable hardware data
- coach network
- clinical validation
- large content library and social network
- Plate maths for loading a barbell, which is small and genuinely useful
- Per-exercise notes carried forward, such as a bar height or a grip
- A printable session card for a phone-free workout
Need the files? The project pack on the verdict page hands your agent the whole brief · more wellness.