A voice-first nutrition tracker built with Expo (React Native + TypeScript) and Supabase — a real, commercial, subscription-based app on its way to the App Store and Google Play.
This repo is a curated portfolio snapshot, not the app's source. It exists to show how the app is built without publishing the full implementation. It contains:
code-samples/— 7 real files from the codebase (the voice-logging pipeline, the adaptive calorie-target algorithm, database schema/RLS, a flagship screen) kept in their original relative paths. They're read-only excerpts, not a runnable project — see that folder's README for details.screenshots/— a handful of final UI screens..env.example— documents the environment variables the real app reads, as context for the code samples above; there's no app here to run it against.
There is no build here, no full src/ tree, and no license to reuse any of
it — see LICENSE.
Stoke logs food by conversation instead of search-and-tap. A user describes a whole meal in one breath — items, portions, and roughly when they ate it — and the app parses it into structured, editable log entries with macros. Everything else (dashboard, weight tracking, adaptive calorie targets, trends, profile/onboarding) sits on top of a Postgres schema where every table is protected by Row Level Security.
mic press → record (m4a) → groq-proxy Edge Function
→ Whisper transcription (audio → text)
→ LLM structured extraction (text → items, quantities, times as JSON)
→ ranked catalog search + fuzzy match per item
→ confirm sheet (every item editable/removable before it's logged)
The recording never talks to Groq directly. It's uploaded to a Supabase
Edge Function (groq-proxy) that holds the Groq API key server-side,
verifies the caller's session, enforces a per-user daily usage cap, and
pins the model/language — so a model deprecation is a secret update and a
redeploy, not an app release. The client only ever sees a transcript back,
then a list of resolved food items.
See code-samples/supabase/functions/groq-proxy/index.ts (the transcription/proxy step) and code-samples/src/services/foodResolutionService.ts (the LLM extraction + matching step) for the real implementation.
| Area | Choice |
|---|---|
| App framework | Expo SDK 54, React Native, React 19, TypeScript |
| Navigation | React Navigation v7 (native-stack + bottom-tabs) |
| Backend | Supabase — Postgres, Auth, Edge Functions, Row Level Security on every table |
| Voice pipeline | Groq (Whisper transcription + LLM structured-output extraction), proxied through a Supabase Edge Function |
| Food data | Self-hosted USDA-derived catalog (ranked Postgres search) with an Open Food Facts fallback for branded/barcode items |
| State | React Context for auth/session; supabase-js owns the session (AsyncStorage + auto refresh) |
| UI / motion | Custom design system, Reanimated, react-native-svg |
| Search | Fuse.js for fuzzy matching against a user's own food history |
Most trackers nudge your calorie target off a single week of noisy scale data, which overreacts to water-weight swings. Stoke instead fits a least-squares regression line through the last 4 weeks of weigh-ins and only proposes an adjustment when the trend has been consistently off-target across both the 4-week window and the most recent 2 weeks — not just one bad week.
The adjustment itself is deliberately boring: capped at ±100 kcal, at most once every 2 weeks, never below a safety floor, and always presented as a suggestion the user approves rather than something the app applies on its own. The interesting part wasn't the regression — it's the guardrails around it. A trend algorithm that's technically correct but reacts to every noisy data point erodes trust fast; the constraints exist because "mathematically justified" and "a target worth trusting" turned out to be different bars.
See code-samples/src/services/tdeeAlgorithm.ts for the actual regression + guardrail code.
- Row Level Security on every table —
auth.uid()must match the row's owner; queries also filter by user id explicitly as defense in depth. See code-samples/supabase/migrations/0001_init.sql. - No secrets in the client. Only the public Supabase URL and anon key ship in the bundle. The Groq key and the Postgres service role never do — both live server-side as Supabase secrets, used only inside Edge Functions.
- Sessions are owned by supabase-js, persisted in AsyncStorage with automatic token refresh; the app never handles auth tokens by hand.




