DEV Community

forkcount
forkcount

Posted on

I built a free calorie tracker with a metabolic safety floor. No card, no email, no paywall. And I found out why every other app doesn't.

Every calorie app I tried had the same problem: it let you set a target of 1,200 calories or lower and called it a plan. Some went lower. That's how people end up in the binge-starve cycle — you eat 800, your body fights back, you eat 4,000, you hate yourself, repeat.

So I built one that refuses.

But here's the thing I figured out after two weeks of building it: the apps aren't badly designed. They're designed around a number you can't argue with.

The number that matters

Your BMR is the calories your body burns just staying alive. Mine is 1,396. If you eat below that for weeks, you don't just lose fat — you lose muscle. Your metabolism slows. The deficit that worked at 2,000 calories stops working at 1,400 because your body has adapted to the smaller intake.

Every app I tried let you set a target below your BMR anyway. Because the user asked. Because a lower number is a faster result. Because "it won't let me" is a support ticket.

ForkCount won't let you. It calculates your BMR (Mifflin-St Jeor), your TDEE (BMR × activity multiplier), and a target that keeps you above the BMR floor. If you try to set a target below that line, it refuses. You can override it in the settings, but the app tells you what you're doing and shows the maths.

That one decision — refusing to let the user hurt themselves — is the whole app. Everything else is a food diary.

What it does

A food diary that parses typed text. You type "200g chicken and 150g rice" and it becomes two items with macros. No database search, no barcode scan. Just type.

A weekly meal plan generator that respects your dietary preference. Set vegetarian, get vegetarian. Set vegan, get vegan. Set a calorie target of 1,400, get a plan built around 1,400 — not the default 2,000 that most generators quietly use.

A water tracker, weight log, exercise log with kcal estimates, and a community feed.

Free. No card. No trial. No paywall. Signup is a username, email, and password.

Stack

  • React + Vite + TypeScript
  • Supabase for database and auth (started on Firebase, migrated after the outage below)
  • GitHub Pages for hosting (deployed from docs/, not the repo root)
  • No backend. All logic is client-side.

The two bugs that taught me the most

Bug 1: The database disappeared.

I was on Firebase Firestore. One morning, every account was gone. Login failed. The app showed "user not found" for accounts that existed the day before. The Firestore API returned 404 NOT_FOUND: The database (default) does not exist for project.

It came back four hours later, untouched. Google had a backend issue. My data was fine. But for four hours, I thought I'd lost everything.

That's when I migrated to Supabase. Postgres. Real backups. No Google Cloud 2FA requirement. The migration took a weekend, and now the database is mine.

Bug 2: Firestore rejected undefined. Everywhere. Deeply.

Before the migration, the bug that cost me the most time was this:
Function setDoc() called with invalid data. Unsupported field value: undefined

text

Firestore refuses any document containing undefined anywhere inside it. Not null — undefined. And when it fails, it doesn't tell you which field. Just the document path. If you're saving a nested object with 40 fields, you're searching for a needle in a haystack.

The fix was a recursive cleaner routed through every write:


js
function cleanForFirestore(obj) {
  if (Array.isArray(obj)) return obj.map(cleanForFirestore);
  if (obj instanceof Date) return obj;
  if (obj && typeof obj === 'object') {
    const out = {};
    for (const k in obj) {
      if (obj[k] !== undefined) out[k] = cleanForFirestore(obj[k]);
    }
    return out;
  }
  return obj;
}
Then I wrapped setDoc, updateDoc, and addDoc in a safeFirestore helper that cleans the data before every write. One import change, and no write in the app can send undefined to Firestore again.

The lesson wasn't "fix the bug." It was "there is a class of bug, and you can kill the whole class with one wrapper."

The deploy lesson
The site broke for two days with this:

text
Failed to load module script: Expected a JavaScript-or-Wasm module script but the server responded with a MIME type of "application/octet-stream"
I was staring at the code. The problem was in GitHub Pages settings. It was serving my repo root — which has /src/main.tsx — instead of my built dist/ folder. The fix was to copy dist/ into a docs/ folder, point Pages at /docs, and stop letting it serve source.

The error message was the browser refusing to run a TypeScript file as JavaScript. Once you know that, it makes sense. The code was never broken. The deploy was.

What I'd like feedback on
Is the calorie calculation right? Mifflin-St Jeor for BMR, activity multiplier for TDEE, 300–500 deficit, floor at BMR. I want someone who actually knows nutrition math to sanity-check it. If the numbers are wrong, the app is wrong.

Does the onboarding flow make sense the first time you see it? Country → height/weight/age → fitness level → goal. Four steps. If it's confusing, I want to know where.

Does it break on your phone? I've tested on one device. I need more.

Does the meal plan respect your dietary preference? I fixed a bug where vegetarian users were getting turkey. If you set a diet and get a plan that violates it, that's a bug and I want to hear about it.

forkcount.github.io

I'm 13 and I built this by directing Google AI Studio, not by writing code by hand. So if something looks like a beginner mistake, it probably is. Tell me and I'll fix it.
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
patchandship profile image
PatchAndShip •

Really like that it refuses targets below BMR. One thing since there's no backend: your Supabase RLS policies are what keeps someone's food diary and weight log private. Worth a quick test where you sign up a second account and try loading the first account's entries.