Build Hevy Pro

YESreplaces $2.99/mosaves $35.88/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort multi-daythe files for this build are in the project pack

RuntimeTypeScript with Vite, built as an installable progressive web applicationStorageIndexedDB through a thin wrapperChartsHand-drawn SVG, no charting libraryTimingAbsolute timestamps, never interval countersExportJSON for completeness and CSV for spreadsheets

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

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

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

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

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

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

VariableNeededExampleWhere the value comes from
VITE_WEIGHT_UNIT_DEFAULTrequiredkgThe default display unit. Storage is always canonical, so this only affects presentation.
VITE_EXPORT_REMINDER_DAYSrequired14How often to prompt for an export. Chosen by you; storage eviction is the reason this exists.
VITE_SYNC_ENDPOINToptionalhttps://sync.example.comProduct phase only. Leave unset and the application stays entirely local, which is a legitimate final state.
SYNC_TOKENsecretoptionalreplace-with-a-random-tokenProduct phase only. Shared secret between your devices and your sync endpoint.

The build, in order

  1. Offline shell and the exercise library

    The application opens and works with the network switched off, and logging survives a reload.

    1. 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.webmanifest

      terminal
      npm create vite@latest . -- --template vanilla-ts
      npm install -D vite-plugin-pwa
    2. 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.ts

    3. A 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 passes
    watch 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.
  2. 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.

    1. Large increment and decrement controls for weight and reps, a big done button, and no swipe-to-delete without an undo.

      Files src/session.ts

    2. Store 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.ts

    3. Persist 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 passes
    watch 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.
  3. Routines and prefill

    Starting a familiar session takes one tap and already knows what you did last time.

    1. Reorderable exercises with target sets, reps and rest. Keep targets optional, because plenty of people train without them.

      Files src/routines.ts

    2. Query the latest set rows per exercise. Distinguish clearly between no history and a logged zero.

      Files src/prefill.ts

    3. Editing 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 passes
    watch 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.
  4. Neutral progress, enforced by a test

    Charts show what happened, and the application cannot start moralising without a test failing.

    1. 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.ts

    2. A 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.json

    3. An 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 passes
    watch 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.
  5. Export and import

    Your training history is a file you hold, not a row in someone's database.

    1. Every table, with a schema version so a future import knows what it is reading.

      Files src/export.ts

    2. Join identifiers to names and include units in the header. Nobody wants an exercise id in a spreadsheet.

      Files src/export.ts

    3. Import 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 passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more wellness.