Skip to content

Repository files navigation

Weight Tracker

A single-purpose weight-tracking PWA for cut / bulk cycles. Log a weigh-in in one tap and judge progress by the 7-day rolling average, never the noisy daily number. Four screens, each with one job — where am I, what if, what happened, the rules — over an ink-black "Neon" theme.

On Android it also reads your daily calories from MyFitnessPal (via Health Connect) and turns the weight trend plus intake into an adaptive-TDEE maintenance estimate and a calorie target for your weekly goal.

Ships as a web PWA and as a native Android app via Capacitor.

The accent follows your current phase — cyan cutting, magenta bulking, a calmer steel while holding — cross-fading on a phase change with a one-shot shimmer across the coloured chrome.

Today screen, Cut phase Today screen, Bulk phase Today screen, Maintain phase

Trends screen History screen Setup screen

The four screens

  • Today — where am I. The 7-day average and week-over-week, a rate bar against your weekly target, a Mon–Sun strip of this week's weigh-ins (today ringed), 14-day / rate / last-week stats, a per-week change strip, and — on Android — the implied energy balance from the trend.
  • Trends — what if. The Reach card sits up top: set a target weight to get a date, or a date to get a weight, and it drives the dashed projection on the chart below. Weekly averages with a phase-scoped line of best fit; a window control (8W / 3M / 6M / ALL, or anchored to the current phase / last deload / last maintenance) that also sets the projection's slope; and change, fit-slope and R².
  • History — what happened. Weekly averages grouped into phase cards, each showing that phase's total weight change and average rate, expandable to the individual days. Deload and maintenance weeks are tagged in place.
  • Setup — the rules. Current phase (Cut / Bulk / Maintain) — changing it is staged behind a confirm with an undo, and commits by cross-fading the app's accent to match; one-tap Deload / Maintenance week markers that don't reset the phase; weekly target and week counter; units; the Health Connect connection; and your data totals.

Stack

  • React + Vite + TypeScript, no router — four screens (Today, Trends, History, Setup) switch on a single screen field in a React Context + useReducer store.
  • Supabase (Postgres + email magic-link auth) for storage, with a localStorage cache and an offline write queue in front of it — logging a weigh-in works with no network and syncs when back online.
  • Capacitor wraps the same web build as a native Android app.
  • Health Connect (Android only) through a small custom Kotlin plugin (android/app/src/main/java/com/blurryq/weighttracker/HealthConnectPlugin.kt) reads MyFitnessPal's daily calorie totals; the energy-balance math is in src/lib/energy.ts.
  • Charts are hand-rolled inline SVG (src/lib/chartGeometry.ts + src/components/chart/) — no charting library.

Project layout

src/
  lib/        pure, unit-tested math: rolling averages, phase spans, least-squares fit,
              the bidirectional Reach solver, chart geometry, adaptive-TDEE energy math
  store/      state shape + reducer + the AppContext that wires in persistence/sync
  data/       Supabase client, offline queue, sync, auth gate, Health Connect bridge
  components/ shared UI (nav, chart, entry sheet, ui primitives)
  screens/    Today, Trends, History, Setup
tests/        vitest, run against a fixture of 317 real weigh-ins (tests/fixtures/weight-data.ts)
supabase/
  migrations/ 0001 schema (entries, phase_log, settings + RLS), 0004 daily_nutrition,
              0006-0007 Trends' phase-anchored window mode, 0008 drops dead columns
android/      Capacitor-generated native project

Local development

npm install
cp .env.example .env.local   # fill in your Supabase project URL + anon key
npm run dev

Without Supabase credentials the app still runs — every src/data/api.ts call checks supabaseConfigured and no-ops, so you get a fully local, offline-only session. Fresh installs boot with no entries (log a weigh-in to get started).

Database setup

Run the migrations in supabase/migrations/ in order via the Supabase SQL Editor (or the Supabase CLI once you're linked to a project). 0001_init.sql creates the core schema; 0004_nutrition.sql adds the daily_nutrition table for the calories feature; 0006 and 0007_trend_window_mode_maintain.sql add Trends' phase-anchored window mode (weeks / phaseStart / lastDeload / lastMaintain); 0008_drop_dead_columns.sql drops two columns the app never ended up reading or writing (settings.trend_horizon, daily_nutrition.source).

Other scripts

npm test     # vitest — pure-function tests (math, chart geometry, energy, reducer) vs. the real 317-entry fixture
npm run lint # oxlint
npm run build

Android

npm run build
npx cap sync android
cd android && ./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Needs a JDK (not just a JRE) and the Android SDK on $ANDROID_HOME. The debug APK is unsigned — fine for sideloading to your own device, but a release build needs a signing key before wider distribution.

The calories feature needs the READ_NUTRITION Health Connect permission, requested from the Setup screen's "Calories" section. On web builds that section just shows an "Android only" note.

Magic-link sign-in on native builds redirects through a custom weighttracker://login-callback URL scheme (see src/data/AuthGate.tsx and the intent-filter in android/app/src/main/AndroidManifest.xml) so the email link opens the app instead of a browser. That URL also needs to be added to the Supabase project's Authentication → URL Configuration → Redirect URLs allow-list.

About

Ignores the noisy daily number. One-tap weigh-ins judged on the 7-day rolling average, weekly trend with a best-fit line, a bidirectional 'when do I hit X?' solver, and adaptive-TDEE targets from MyFitnessPal via Health Connect.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages