GymTracker
A full-stack Android training app on Supabase, with an AI coach that never makes up a number.
- screens across Today, Train, Nutrition, Insights and Journey
- 40+
- components in strict TypeScript
- 100+
- versioned SQL migrations on Supabase Postgres
- 40
- cache layers: memory, AsyncStorage, network
- 3
Overview
GymTracker is the training app I use. It's Expo and React Native in strict TypeScript on a Supabase Postgres backend: 40+ screens across Today, Train, Nutrition, Insights and Journey, 100+ components, and 40 versioned SQL migrations. One rule shapes it: the AI never produces a number. Pure TypeScript engines compute progression, strength scores, nutrition targets and pacing; the models only turn those numbers into words.
Problem → built → result
- Problem
- Training, nutrition and progress numbers that must agree, and an AI coach that mustn't invent any.
- Built
- Expo + Supabase app: 40+ screens, 40 SQL migrations, pure engines for numbers, AI for prose.
- Result
- 40+ screens · 40 migrations · Android release build
What I built
- Expo SDK 55 and React Native in strict TypeScript on Supabase: 40+ screens across Today, Train, Nutrition, Insights and Journey, backed by 40 versioned SQL migrations
- Reads go through a stale-while-revalidate cache over three layers (an in-memory mirror, AsyncStorage and the network) with request de-duplication, so two cards mounting together make one call
- Two AI lanes: Llama 3.3 70B on Groq for the many small, fast calls and the Anthropic API for deep analysis; pure engines compute progression, strength scores and nutrition targets
- Android Health Connect for steps and sessions, barcode food lookup on Open Food Facts, and kilograms stored canonically with pounds for display only
- A schema-drift script that lists every table.column the app queries, written after a missing column made failed queries look like an empty workout history
Decisions
- 1
Engines compute, the model explains.
Progression, strength scores, nutrition targets and pacing come from pure TypeScript engines that are tested on their own. The AI only turns their output into words, so it has no way to invent a number.
- 2
Cache in three layers, fetch once.
Reads go through stale-while-revalidate over an in-memory mirror, AsyncStorage and the network, with request de-duplication: two cards that mount together make one call.
- 3
Two AI lanes, picked by cost.
Llama 3.3 70B on Groq takes the many small, fast calls; the Anthropic API is kept for deep analysis, where the extra cost buys something.
- 4
Make schema drift loud.
A missing column once made failed queries look like an empty workout history. A script now lists every table.column the app queries, so drift shows up as a list, not an empty screen.
Why it's shaped this way
A coach is only worth trusting if it can't make up your numbers. Every figure on screen comes from a deterministic engine that can be tested on its own, and the model only explains it.