All work

AI meal planning for iOS

Built twice. The second time in 127 days.

Chef Gaston started as a Danish receipt-scanning app in React Native. It was rebuilt from scratch in SwiftUI with every AI call behind a server function, and reached the App Store 127 days after the first commit of the rewrite.

Chef Gaston weekly meal plan screen
Chef Gaston pantry screen with expiry warnings
127
days from the first SwiftUI commit to the App Store
178
recipes in the catalog, every one translated to Danish
6
Edge Functions, where every AI call runs
418
tests across the Swift app and the Deno functions

From a receipt scanner to a chef that plans your week.

  1. Version one starts, in React Native

    An Expo app called ChefGaston, Danish first, built around scanning grocery receipts into a pantry. Sixty seven commits over four weeks, with eleven Edge Functions calling Claude, Gemini, and OpenAI.

  2. The rewrite begins, in SwiftUI

    A new repo, a new scaffold, and a Supabase schema with row level security on day one. The v1 recipe catalog was dropped rather than ported, and a new one was generated from scratch with a Claude Sonnet pipeline.

  3. Plan generation, Cook Mode, and a paywall, the same day

    Builds 0 to 11 landed in one day: a generate-plan function on Claude Sonnet, Cook Mode with Ask Gaston on Claude Haiku, and RevenueCat subscriptions.

  4. Design overhaul and onboarding rebuild

    One checkpoint commit covering the May to July work: 123 files changed and close to 18,000 lines added, with launch readiness as the goal.

  5. Rejected by App Review

    Build 5 of 1.0 failed on guidelines 4 and 2.1(b). The fix changed how the trial starts, removed a hardcoded price, and cut the subscription to $3.99 a month with a seven day introductory offer.

  6. 1.0 live on the App Store

    Five tabs: Plan, Recipes, Gaston, Pantry, and Shopping. Meal plans, grocery lists, and voice guided cooking at the stove.

  7. Danish, after all

    The launch plan said US first and no Danish. Nine days after launch the app was fully localized, with all 178 recipes translated and stored in their own table.

  8. Recipe import from photos, links, and pasted text

    A feature cut from v1 came back on Claude Sonnet 5, with transcripts from TikTok, Instagram, and YouTube capped at three minutes of video to keep costs predictable.

  9. The Gaston tab

    Tell him what you have and get a recipe. A server side topic guard answers off topic requests with a fixed line, never the model's own words.

  10. Swift 6 language mode

    Strict concurrency turned on across the whole app target.

  11. Sign in with Apple becomes the only login

    Email and Google sign in were removed. Fewer paths, less surface, one identity provider.

  12. A second plan ranker, behind a flag

    TypeSafe Jev can rank plans next to Claude, with a shadow mode that runs after the response is sent. The same day, AI allowances became atomic: a per user lock closes the race where parallel requests all passed the limit.

  13. Version 1.4, build 18

    208 commits in the rewrite repo, 163 days after the first one.

The stack

Swift on the phone, Postgres and Deno behind it. The phone never holds an API key.

Interface
SwiftUI
Swift 6 with strict concurrency, iOS 17.6, iPhone only. UIKit appears in exactly two wrappers: the camera picker and the document scanner.
Data
Supabase Postgres
10 tables, 27 row level security policies, 26 migrations. Supabase is the source of truth; there is no local database.
AI
Claude
Sonnet 4.6 plans the week, Haiku 4.5 answers at the stove, Sonnet 5 imports recipes and runs the Gaston tab. All through Edge Functions.
Ranking
TypeSafe Jev
An optional second ranker for meal plans. Safety rules for allergens and diets stay in code, and any failure falls back to Claude.
Auth
Sign in with Apple
The only login since September. Account deletion runs through its own Edge Function.
Payments
RevenueCat
StoreKit subscriptions with a webhook function that keeps the server in sync with Apple.
Voice
Speech and AVFoundation
Push to talk with on device recognition where supported, and spoken read back in Cook Mode.
Analytics
PostHog
Product analytics in the app. The website uses Google Analytics.
Tests
Swift Testing and Deno
279 Swift tests and 139 Deno tests. Edge Functions are tested as units, with the model mocked.
Website
Next.js 16
React 19, Tailwind 4, and Neon Postgres, with separate English and Danish route groups. 23 routes.

A rewrite, not a port

Version one was a receipt scanner. You photographed the receipt, the app read it into a pantry, and recipes followed from what was on the shelf. It shipped as an Expo app, Danish first, with six tabs and a generic chatbot. It never reached the App Store.

The rewrite kept the name and the mascot and threw away the rest. Receipt scanning, the curated chefs, recipe import, and the chatbot were all cut on day one. The v1 catalog was deleted instead of ported, and a new one was generated with a Claude Sonnet pipeline. The bet was that a meal plan you trust is worth more than a pantry you maintain.

Three of the cut features came back once the core worked: recipe import in September, a general kitchen chat as the Gaston tab, and Danish as a full localization nine days after a US only launch.

Every AI call is a server call

The phone never talks to Claude. Six Deno Edge Functions on Supabase do: plan generation, Ask Gaston, the Gaston tab, recipe import, the RevenueCat webhook, and account deletion. API keys live only there, and every function is unit tested with the model mocked.

Allowances are claimed before any money is spent. A per user lock inserts a pending usage row first, which closed a race where parallel requests all passed the limit at once. Video transcripts are capped at three minutes, and off topic requests to the Gaston tab get a fixed line from the server rather than whatever the model would say.

Since September a second ranker, TypeSafe Jev, can score meal plans next to Claude. It runs behind a flag with a shadow mode that evaluates after the response has already been sent, so it can be measured without a user ever waiting for it.

Rejected, then live

Build 5 of 1.0 was rejected on 17 August under guidelines 4 and 2.1(b). The fixes were product decisions, not patches: the trial now starts when the paywall is accepted instead of automatically, the hardcoded $7.99 was removed, and the price became $3.99 a month with a seven day introductory offer.

Build 6 passed and 1.0 went live on 26 August. Five versions followed in five weeks, including Swift 6 strict concurrency and a move to Sign in with Apple as the only login.

Next case

Winelingo