All work
Project Lean screenshot built by Vector9
Project Lean app icon

Project Lean

AI nutrition coach

Live in Production

Seven weeks from empty repository to an AI nutrition coach on both app stores.

Role
Design and engineering, end to end
Timeline
7 weeks, 2026
Type
AI nutrition coaching app
Platforms
iOS and Android

Stack

Expo / React NativeTypeScriptNativeWindZustand + TanStack QuerySupabase (Postgres, Auth, RLS)Gemini VisionStripeEAS

Calorie tracking fails for a boring reason: most people who quit tracking never quit the goal, they quit the data entry. Project Lean removes both halves of the chore. Point a camera at the plate instead of searching a food database, and get a coach's read on your patterns instead of a running total.

The constraints were sharp: two platforms shipping at once, a data model that had to be trustworthy from the first user, and an AI bill that runs per scan. Seven weeks separated the first commit from version 1.1 on both stores, so every architectural call had to earn its place.

Scan a meal, get real numbers

The scan pipeline runs on Gemini vision with a strict JSON schema enforced at the model level, so a response either parses or fails loudly. Production taught the interesting lessons: the model's hidden reasoning tokens were quietly eating the output budget and truncating JSON mid-string, fixed by zeroing the thinking budget; the SDK accepts no abort signal, so a race against a timer guards every call; and when a scan still fails after one jittered retry, the app degrades to manual entry instead of a dead end. Every call also logs its cost, because the feature spends money on each tap.

Scan-first logging: the camera front and centre, the day's meals already parsed to calories
Scan-first logging: the camera front and centre, the day's meals already parsed to calories

A coach that argues from your own week

The coach is a grounded assistant, not a chatbot bolted onto a tracker. Before the model is called, six queries fan out in parallel to assemble a privacy-scoped snapshot of the user's week: today's check-in and macros against target, the prior seven days of training, sleep, mood and recurring patterns, the active streak, and the last ten turns for memory. A behavior-change persona reasons over that context with hard rails, so it names concrete foods instead of reading back a macro total and states only numbers that exist in the data, never invented ones. Reliability is where it earns trust: the user's message is committed to the database before the model call so a transient failure never loses their input, partial failures surface as typed errors with retry-safe UI instead of duplicate rows, and every reply logs its tokens and latency behind a rolling per-user rate limit. It runs server-side in an edge function on Gemini 2.5 Flash with a lighter fallback model, so the key never touches the device and a single provider hiccup fails over instead of failing.

The coach's reply, drawn from the week's own check-ins and macros rather than a generic tip
The coach's reply, drawn from the week's own check-ins and macros rather than a generic tip

Every timezone, one cron

Daily reminders for users spread across timezones run on a single scheduled sweep every fifteen minutes. Each pass computes every user's local time, sends inside their chosen window, catches up anyone a failed sweep skipped, and dedupes to one nudge per local day; if you already checked in, it stays quiet. Delivery is closed-loop: push tickets are reconciled against delivery receipts on a second job and dead device tokens get revoked, so the system knows which pushes went through and stops sending to devices that are gone.

Notification settings: the reminder categories and the evening send-window each user picks
Notification settings: the reminder categories and the evening send-window each user picks

A database that does not trust the client

Every table carries row-level security: user data is scoped to the signed-in user and enforced in Postgres, not trusted to the client. The coach's admin view routes through a privileged database function, so the policies stay simple and never end up checking themselves. The schema then had to evolve under live users: more than sixty migrations in the two months around launch, including dropping a uniqueness constraint that would have locked out returning subscribers, widening a status machine to match Stripe's real lifecycle, and backfilling defensively before any column went non-nullable. Migrations were written to be re-runnable, because a migration you can only run once is a migration you will eventually run twice.

The insights tab: a weekly summary, habit grid and mood trends, derived from the user's own rows
The insights tab: a weekly summary, habit grid and mood trends, derived from the user's own rows

Version 1.0 shipped to the App Store; 1.1 followed with the Android launch on Google Play and multi-plan subscriptions through Stripe, gated to the UAE and Egypt. The app has iterated in production through over-the-air updates ever since.

Under the surface sits the part nobody screenshots: adaptive calorie and protein targets that re-base themselves on a rolling weight average, and an admin console the human coach actually uses to read client conversations.

Want something like this?

Tell us what you're building and we'll give you an honest assessment of scope, timeline, and cost.

Book a Call