Note
This application was built largely as an exercise in using an LLM to code. Nearly all of the code is AI-generated. This should also explain some of the peculiarities of the project, including the minimal file structure (which was chose to make code changes more efficient for rapid development). Everything from here on out in this README is AI-generated.
Personal macro tracking web app: React + TypeScript + Vite frontend, FastAPI + sqlite backend, served from a single port. Designed to run locally on a Mac and be exposed on the public internet via Cloudflare Tunnel + Cloudflare Access (gated by Google SSO + passkey) using the lab-server orchestrator.
Architecture history: originally built on Supabase (cloud Postgres + auth). Migrated to local sqlite + FastAPI on 2026-05-09 — auth removed (Cloudflare Access handles it at the edge), data lives in
backend/data/macro.db.
- Real-time macro tracking with progress bars (protein, carbs, fat).
- Workout vs. rest day macro targets.
- Food database with frequency tracking.
- Historical preservation: edits to a food's macros only affect future entries; past food_entries snapshot the values at log time.
- Mobile-first responsive layout.
- Frontend: React 18 + TypeScript + Vite. Built once via
npm run build→ static assets indist/. - Backend: FastAPI + sqlite (Python 3.11+). Serves both
/api/*JSON endpoints and the built frontend's static files from a single uvicorn process — bound to the Unix socket~/.lab-sockets/macro.sockwhen lab-served, or a local TCP port for manual dev. - Auth: none in-app. Cloudflare Access at the edge gates
macro.emmilco.combehind the operator's Google account + passkey. - Hosting: registered as the
macrosub-app inlab-server'sapps.toml. Lab-server is launchd-managed, always-on while the Mac is awake; cloudflared routesmacro.emmilco.comto the lab auth gateway, which authenticates the request and forwards it to the app's Unix socket~/.lab-sockets/macro.sock.
macro-tracker/
├── src/ # React frontend
│ ├── App.tsx # Main component (no auth — Cloudflare Access handles it)
│ ├── App.module.css # Styles
│ ├── api.ts # fetch-based wrapper for /api/* (replaces former supabase.ts)
│ ├── types.ts # TypeScript interfaces
│ └── main.tsx # React entry point
├── backend/ # Python backend (NEW since Path B migration)
│ ├── server.py # FastAPI app: /api/* endpoints + static-file serving
│ ├── init_db.py # Migration: parse Supabase SQL exports → local sqlite
│ ├── requirements.txt # fastapi, uvicorn
│ ├── .venv/ # virtualenv (gitignored)
│ └── data/
│ └── macro.db # sqlite (gitignored)
├── database/ # Legacy Supabase schema (reference only; not used at runtime)
│ ├── database-schema.sql
│ ├── add-auth-schema.sql
│ └── update-foods.sql
├── dist/ # Built React assets (gitignored, generated by `npm run build`)
├── index.html # Vite entry
├── package.json
├── vite.config.ts
└── tsconfig.json
# 1. Backend deps + venv
cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
# 2. Frontend deps + build
cd ..
npm install
npm run build # writes to dist/
# 3. Start the server
backend/.venv/bin/uvicorn backend.server:app --host 127.0.0.1 --port 7100To hit the lab-served instance directly (no auth needed for same-user local access): curl --unix-socket ~/.lab-sockets/macro.sock http://localhost/ (or the helper ~/Documents/GitHub/lab-server/labcurl macro /). For active hot-reload development, run a throwaway TCP dev server — add --reload to the uvicorn command above and visit http://127.0.0.1:7100/. The FastAPI app serves the React app at / and JSON endpoints at /api/*.
The macro sub-app is registered in lab-server/apps.toml:
[apps.macro]
working_dir = "/Users/elliotmilco/Documents/GitHub/macro-tracker"
start_command = "backend/.venv/bin/uvicorn backend.server:app --uds /Users/elliotmilco/.lab-sockets/macro.sock"
enabled = trueWhen the lab-server LaunchAgent is loaded, this starts automatically and the app is reachable at https://macro.emmilco.com (after Google SSO via Cloudflare Access).
To pick up frontend changes after editing React code: npm run build, then launchctl kickstart -k gui/$(id -u)/com.emmilco.lab-server to restart the daemon (which restarts uvicorn with the new dist/).
Single sqlite file at backend/data/macro.db. Tables:
foods— master food database with frequency tracking.daily_entries— daily logs withday_type(workout / rest).food_entries— per-day food consumption with snapshotted food data for historical preservation.user_settings— workout / rest macro targets (single row, single user).
Schema file (sqlite-flavored) is created by backend/init_db.py at first run.
backend/init_db.py parses PostgreSQL INSERT exports from the legacy Supabase project and imports them into local sqlite. The migration filtered to a single user (multi-user fields were dropped since auth is now external). Re-running with --reset drops the local db and re-imports.
JSON endpoints exposed by backend/server.py:
| Method | Path | Purpose |
|---|---|---|
GET |
/api/foods |
list, ordered by frequency desc |
POST |
/api/foods |
create new food |
PATCH |
/api/foods/{id} |
update food |
DELETE |
/api/foods/{id} |
delete food |
POST |
/api/foods/{id}/increment-frequency |
bump usage counter |
GET |
/api/daily-entries/by-date/{date} |
get day's entry (or null) |
POST |
/api/daily-entries |
create day entry |
PATCH |
/api/daily-entries/{id} |
update day type |
GET |
/api/daily-entries/historical?exclude={date} |
list history |
GET |
/api/food-entries?daily_entry_id={id} |
list day's foods |
POST |
/api/food-entries |
log a food |
PATCH |
/api/food-entries/{id} |
update multiplier |
DELETE |
/api/food-entries/{id} |
delete a food log |
GET |
/api/settings |
get macro targets |
PUT |
/api/settings |
update macro targets |
The src/api.ts module mirrors these as typed TypeScript functions.
- Workout Day: 180g protein, 250g carbs, 80g fat (~2,040 cal)
- Rest Day: 180g protein, 150g carbs, 100g fat (~1,940 cal)
Both editable in the settings page (writes via PUT /api/settings).
The legacy Supabase project was paused (free-tier inactivity) and increasingly fragile (third-party auth in front of a personal app, vendor lock-in, slow cold starts). Migrating to local sqlite + Cloudflare Access:
- Single Mac is the source of truth — no cloud dependency.
- Cloudflare Access provides stronger auth (Google + passkey) than Supabase's password-based flow, and is shared across every other lab-server app.
- Free (no Supabase tier, no Netlify deploy).
- Faster (no cold start, sqlite is microseconds).