Most nutrition and health trackers I’ve tried either missed the feature I actually needed, buried it three menus deep, or locked it behind a subscription. Each had something going for it, but none quite fit. So I built my own.

MyTracker is a vanilla JS web app backed by Supabase. No framework, no build step, no subscription. You can host it yourself on GitHub Pages, and the only external services it touches are your own Supabase instance and the Claude API for AI-assisted logging.

Try it

Live Demo: gonespral.github.io/my-tracker
Source: github.com/gonespral/my-tracker


Logging should be frictionless

The biggest reason I stopped using most trackers was logging friction. Searching a food database, hunting for the right entry and eyeballing a portion size was tedious enough that I’d skip it on busy days, which defeats the point. I wanted to describe what I ate in plain language and have that be the whole interaction.

The AI assistant is built around that. Type or speak a description like “bowl of oats with banana and a flat white”, Claude estimates the macros, and you confirm. Attach a photo and it reads the meal visually. Not always perfectly accurate, but accurate and fast enough that I actually use it every day. Frequent items surface as presets automatically, so re-logging a regular breakfast is one tap.

bowl of oats with banana and a coffee with milk
Breakfast logged
Oats (80 g) · banana · flat white
480 kcal  ·  18 g protein  ·  72 g carbs  ·  9 g fat
45 min easy run this morning, felt good
Workout logged
Running · 45 min · easy intensity
~340 kcal net burn  ·  Added to today's target
mic photo_camera
Describe a meal or activity…
arrow_upward

Voice input uses the Web Speech API, useful when your hands are full. The app detects activity type from the description and assigns the right icon and category automatically.


Calorie rings

The Today tab is built around four circular progress rings. The large ring shows calories consumed against your daily target. Three smaller rings track protein, carbs, and fat. Log a meal and all four update instantly.

CLICK A MEAL TO LOG IT
0
/ 2,000 kcal
0g
/ 150g
Protein
0g
/ 200g
Carbs
0g
/ 67g
Fat

Training days vs rest days

Most trackers give you a fixed daily target. A day where you ran for an hour is not the same as a rest day. MyTracker adjusts the target automatically when a workout is logged.

The increase uses net active calories: gross burn minus the resting burn you’d have had over that duration anyway, so baseline metabolism isn’t double-counted. The base target comes from Mifflin-St Jeor, multiplied by an activity level factor and offset by your chosen deficit.

INTERACTIVE DEMO
2,150
kcal target
2,150 kcal base target

Macro targets (30% protein / 40% carbs / 30% fat) update automatically when the calorie target changes. Everything is overridable in settings if you want different splits or a fixed custom target.


Strava sync and the spoofing problem

Both integrations use OAuth PKCE flows with no backend prox. That is, tokens stay in localStorage and never leave the device. On load the app pulls activities from each source and reconciles them locally.

The interesting part is pushing workouts back to Strava. Strava’s API ignores calorie data on manually created activities. The only way to get a calorie count into a Strava entry is to let Strava calculate it itself, from heart rate. So the app inverts the Keytel formula to find the average heart rate that would produce the target calorie total, generates a synthetic TCX file with that heart rate trace, and uploads it. Strava reads the heart rate, runs its own calculation, and the number comes out right.

Enter a workout to see what heart rate gets encoded:

HEART RATE ESTIMATOR — calorie spoofing calculator
140
estimated avg heart rate (bpm)
Encoded as synthetic HR in the TCX upload to Strava

The result is clamped to 60–200 bpm. If it falls outside that range, spoofing is skipped and the activity is pushed without heart rate data.


Duplicate detection

The same run can arrive from both Strava and Google Health. Naively importing both would pollute the activity list fast.

Deduplication happens at the database: before inserting, the app checks for an existing entry with a matching source ID, timestamp, and type. Already-imported activities are skipped. When the same workout legitimately arrives from two different sources, they’re grouped into a single stacked card with expand/collapse. You see one card per workout; expanding shows which sources contributed.


Under the hood

Supabase handles auth (GitHub OAuth), storage, and row-level security. The Claude API is called directly from the browser. API keys and OAuth tokens live in localStorage only and never leave the device.

The codebase is heavily vibecoded. It exists because I needed it and nothing else quite fit.