CODEX // oaoisme wiki

§ Forge — hypertrophy tracker

Architecture — stack & data model

updated 2026-06-08

Forge is a small, sharply-separated Node app. The rule that keeps it maintainable: the database only shapes rows, the engine does all the math, and the renderers (art/charts) only draw. Each layer is independently testable.

The stack

  • Express + EJS — server-rendered pages; a thin JSON API under /api for the interactive bits (set logging, rest timer, program edits).
  • Postgres 16 (in Docker) — the only persistent store.
  • Vanilla client JS (public/app.js) — no framework; handles set logging, the rest timer, PR stamps, and program editing via fetch.
  • Docker Composedb + app, app bound to 127.0.0.1:3600 so nginx is the only way in (the box's UFW/Docker rule).
  • PWAmanifest.webmanifest + a cache-first sw.js for the static shell (never caches /api).

File map

File Responsibility
catalog.js Domain data: 16 muscle groups, a 22-exercise catalog (each with muscle weightings, rep range, increment), and the seed PPL+UL program. Pure data.
engine.js The math: double progression, Epley 1RM, the thermal fatigue model, weekly volume. Pure functions, no DB, no clock reads (caller passes now).
db.js Schema, idempotent seeding from catalog.js, and every query — shaped exactly for the engine.
art.js The anatomical SVG body map + exercise muscle diagrams + the plate-math glyph.
charts.js Dependency-free inline SVG line/bar charts in the Forge palette.
server.js Express routes; assembles the "today" model (engine targets per exercise) and the JSON API.
views/*.ejs Today, Program, Body, Progress, Exercise, 404.
engine.test.js Node test-runner unit tests for the pure math.
e2e.mjs Exhaustive Playwright pass against the live URL.

The data model

Five tables plus two catalog tables. The important design choice: fatigue and progression are derived from set_logs, never stored. set_logs is the single source of truth; everything else is computed on read.

  • muscles — key, name, body-map side (front/back/both).
  • exercises — key, equipment, increment, rep range, muscles JSONB ({muscleKey: contribution} where 1.0 = prime mover, ~0.3–0.8 = assist), cues.
  • program_days + program_exercises — the editable split.
  • sessions — one per workout (day, started/finished, status).
  • set_logsthe core record: session, exercise, set #, weight, reps, RPE, warmup flag, timestamp.

Idempotent seeding

On every boot, db.init() upserts muscles and exercises from catalog.js (so catalog edits — a new cue, a tweaked muscle weighting — propagate), but seeds the program only if it's empty (so your edits to the split survive restarts). This "code is the catalog, DB is your data" split means you can evolve the exercise library without migrations and without clobbering user changes.

The two interesting computations get their own pages: the progression engine and the fatigue model. The shared visual that powers both the heatmap and every exercise image is the body map.

Subpages