Training Log

A side project: a mobile-first workout planner and tracker built around one idea, zero decisions to start. Open the app and today's workout is already planned for the time you have and the equipment you own. Tap Start, follow the sets and the rest timer, and watch your streak and lifts go up.

Works without signing in. Built with AI coding tools as a way to learn by shipping something I use every day.

Today screen with a ready-to-start upper body workout
Today: a plan on open
Live workout screen showing one set with reps and weight
Live workout, one set at a time
Progress screen in the Night theme
Progress, Night theme
History calendar and recent sessions
History and PR calendar

What it does

How it's built

Everything runs in the browser. Two small cloud services add sync and AI planning, and the app keeps working without either of them.

Training Log architecture The browser holds the UI, app logic, exercise library, stats, localStorage as the source of truth, and a service worker. Optionally it syncs to Firebase Auth and Firestore, and requests AI plans from a Cloudflare Worker, which verifies the Firebase token, enforces daily limits, and calls the Gemini API. Phone / browser index.html · styles.css UI: Today, Progress, History, Settings, live workout app.js State, today's plan, live session, sync glue exercises.js · stats.js Exercise library and planner · stats as pure functions localStorage Source of truth for all data on the device sw.js Service worker caches the app shell for offline use sync (optional) AI plan (optional) Firebase Auth: Google sign-in or guest Firestore: one collection per user Cloudflare Worker Verifies the Firebase ID token Tiered daily limits in a Durable Object keys Google Gemini API
Hosted on GitHub Pages at train.tejasrj.io. Every change is written to localStorage first, then mirrored to the cloud in the background. The LLM key never reaches the client; it lives only in the Worker's secret store.

Principles

Stack

LayerImplementation
UISingle-page vanilla HTML, CSS and JavaScript; no framework
PlanningGreedy time-budget packer over the exercise library, rotating muscle groups and ranking by how recently each exercise was done
StatsPure functions for weeks, goal history, streak, volume, lift trends and PRs, unit tested in Node
Live workoutSession saved on every change; rest timer derived from a timestamp; Web Audio, Vibration and Screen Wake Lock APIs
StoragelocalStorage with versioned keys is the source of truth on each device
SyncFirebase Authentication (Google and anonymous) and Cloud Firestore; workouts merge by id, deletions propagate, settings are last-write-wins
OfflineService worker with stale-while-revalidate caching; installable PWA
AIGemini behind a Cloudflare Worker that checks origin and request size, verifies Firebase ID tokens, and enforces per-tier daily limits in a Durable Object
TestsNode unit tests for stats; Worker tests for auth, tiers, limits, day rollover and API errors

How a plan is made

  1. On open, a plan is built locally from the library for today's focus. If AI is on, a better plan is requested in the background.
  2. The app sends the system prompt, the user prompt (minutes, focus, equipment, the last 10 entries) and a JSON schema to the Worker. Signed-in users attach their Firebase ID token.
  3. The Worker verifies the token, picks a tier (owner, member or public) and checks that tier's daily limit.
  4. The Worker calls Gemini and returns the JSON plan. The app validates it, prefers the user's logged weights, and swaps it in.