Voice-first AI cooking assistant for iOS. Plan your weekly meals and cook along step-by-step as Chomp gets to know your cooking and taste preferences.
Chomp (previously named weat) is a SwiftUI iOS app that uses OpenAI's Realtime API (gpt-realtime-2) over WebRTC for low-latency voice conversations. Uses a Patch of the swift-realtime-openai library. Uses Supabase backend for memory, meal plans, and cooking history. Remembers what you like and don't like over time through extracting facts from natural conversation.
Chomp introduces itself and prompts the user to enter their name. It then asks the user a series of questions to extract and populate initial facts (dietary restrictions, allergies, equipment, cuisines, skill level, time) to build the meal plan.
The MealPlanView is the Home view that displays the meal plan that Chomp creates. The user can modify the meal plan conversationally by starting a Plan with Chomp session. Chomp edits the meal plan in real time through tool calls.
When the user taps Cook Now on any meal card, Chomp will guide the user through the recipe conversationally. Chomp can receive and show images (currently just through querying Pexel) and set timers through AlarmKit. The user can rate and add notes about the recipe when they end the session.
The user can view their past recipes by opening their History view on the top-left icon of the Home view.
The user can view their extracted facts by opening their Profile view on the top-right icon of the Home view.
The three conversational user journeys β Onboarding, Planning, Cooking β all use the same (ConversationViewModel + WebRTC + function tools). What differs is the prompt, the tool set, the hosting screen, and the end-of-session hook.
βββββββββββββββββββββββββββββββββββββββββββ
β iOS App (weat) β
β β
βββββββββββββ β βββββββββββββββββββββββββββββββββββββ β
β Apple ID βββββΌβββ AuthService (Sign in with Apple) β β
βββββββββββββ β βββββββββββββββββββββββββββββββββββββ β
β β
β ContentView ββ routes by auth + β
β ββ onboarding state β
β β β
β ββ SignInView β
β ββ OnboardingView β
β ββ HomeView β MealPlanView β
β β β
β ββ ConversationViewβ
β β
β ConversationViewModel (one per session)β
β ββ Conversation (patched WebRTC)β
β ββ LiveActivityManager β
β ββ MealPlanService β
β ββ MemoryService β
β ββ AlarmService (cook only) β
βββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββββββ βββββββββββββ ββββββββββββββββ
β Supabase β β OpenAI β β Pexels β
β Auth (SIWA) β β Realtime β β (proxied β
β Postgres + RLS β β API β β via Edge β
β Edge Functions: β β (WebRTC) β β Function) β
β realtime-token β β β ββββββββββββββββ
β pexels-search β β gpt- β
β extract-facts β β realtime-2β
ββββββββββββββββββββ βββββββββββββ
Realtime session lifecycle:
- Mint ephemeral key via
realtime-tokenEdge Function. - Open WebRTC peer connection to
api.openai.com/v1/realtime/calls?model=gpt-realtime-2. - On
session.created, pushsession.updatewith instructions + tools + turn-detection config. - Audio + events stream both ways over an SCTP data channel for the rest of the session.
endSession()cancels observation, disconnects WebRTC, runsextract-factsin the background, tears down the Live Activity.
greeting β nameInput β askingQuestions β buildingPlan β (ContentView β HomeView)
Entry: first sign-in. OnboardingCoordinator.check() finds zero facts β .needsOnboarding β ContentView mounts OnboardingView.
Mode: SessionMode.onboarding(state:) β facts written live as Chomp learns them.
Tools: add_fact, display_options, begin_planning, add_meal, finish_onboarding.
Kickoff: yes β Chomp speaks first ("Hi! Nice to meet you β what should I call you?").
USER OnboardingView ConversationVM OpenAI Supabase
β β β β β
β signs in β β β β
β β create VM ββββββββββββββΆβ β β
β β β mint token βββββββββββββββββββββββββββΆ realtime-token
β β ββββββββββββββββββββββββββββββββββββββ β
β β β WebRTC handshake ββΆ β
β β ββββ session.created β β
β β β session.update β β
β β β (onboarding prompt + tools) ββββββΆ β
β β β response.create (kickoff) βββββββββΆ β
β β ββββ "Hi! Nice to meet youβ¦" β
β hears greeting β chompHasSpoken = true β β β
β β phase: nameInput β β β
β types name β β β β
β βββββββββββββββββΆβ submitName ββββββββββββΆ β updateDisplayName βββββββββββββββββββΆβ auth.users
β β β send(text:"Please call me X") ββββΆ β
β β ββββ ack + Q1 audio + display_options β
β β β dispatch display_options β
β β phase: askingQuestions β (cards appear in UI + Live Activity)β
β β option cards animate β β β
β β β β β
β answers (voice) β β ββ audio ββββββββββΆβ β
β β ββββ add_fact(category, value) β
β β β dispatch βββββββββββββββββββββββββββ β INSERT facts
β β fact card appears β β
β β (cards loop ~6Γ) β β
β β ββββ begin_planning β
β β isPlanningStarted=true β (UI flag flip; no DB write) β
β β phase: buildingPlan β β
β β meal-plan animation β β
β β ββββ add_meal Γ 3-5 β
β β β dispatch βββββββββββββββββββββββββββΆ β INSERT meals
β β meals appear in stack β β
β β ββββ finish_onboarding β
β β isComplete = true β β
β β finishUp(): wait for β ββ farewell audio ββββββββββββββββββββ
β β ~2s silence β β
β β endSession βββββββββββββΆβ disconnect WebRTC β
β β β extract-facts (bg) βββββββββββββββββΆ β facts merge
β β coordinator.markCompleteβ β
β sees meal plan β (ContentView swaps) β β
Entry: user taps "Plan with Chomp" on MealPlanView.
Mode: SessionMode.mealPlan β facts + upcoming meals pre-baked into the prompt so the model doesn't need to call get_meals for routine context.
Tools: add_meal, update_meal, delete_meal, mark_meal_made, suggest_meals, get_meals.
Kickoff: no β user speaks first. Planning is user-driven.
USER MealPlanView ConversationVM OpenAI Supabase
β β β β β
β taps Plan β β β β
β βββββββββββββββββΆβ startSession ββββββββββΆ β loadActiveFacts ββββββββββββββββββββββΆ facts
β β β mealPlan.load() ββββββββββββββββββββββΆ meals (today+)
β β β mint token, WebRTC, session.update β
β β β (plan prompt embeds facts + meals) β
β β PlanningSessionBar β β
β β appears β β
β "show me three β β ββ audio ββββββββββΆ β
β weeknight β β β β
β dinners" β ββββ suggest_meals([3]) β
β β β dispatch: β
β β carousel shows 3 cards β ββ mealPlan.setSuggestions(...) β
β β Live Activity chips β ββ liveActivityShowChips(...) β
β β on lock screen β β
β "yes, pad thai β β ββ audio ββββββββββΆ β
β tomorrow" β β β
β β ββββ add_meal(...) β
β β β dispatch βββββββββββββββββββββββββββΆ β INSERT meals
β β new meal card on plan β ββ suggestions cleared β
β β Live Activity chips β ββ liveActivityShowChips(empty) β
β β clear β β
β taps End β β β
β βββββββββββββββββΆβ endSession ββββββββββββΆ β disconnect β
β β β extract-facts (bg) βββββββββββββββββΆ β facts merge
β β reload meals β β
β back on plan β β β
Drag-and-drop on MealPlanView (re-scheduling meals) and the servings stepper on MealDetailView both bypass the realtime session β they call MealPlanService directly. The session is only for voice-driven editing.
Entry: user taps "Cook Now" on a planned meal card.
Mode: SessionMode.cookRecipe(meal:) β full recipe + user's facts pre-baked into the prompt.
Tools: show_reference, start_timer, cancel_timer.
Kickoff: yes β Chomp opens by naming the recipe and going straight into the first step.
Turn detection: semanticVad(eagerness: .low, interruptResponse: false) β kitchens are loud, Chomp can't be cut off by clatter.
Any active planning session is torn down before the cook cover presents (no two realtime sessions at once).
USER MealPlanView ConversationVM OpenAI Pexels AlarmKit Supabase
β β β β β β β
β "Cook Now" β β β β β β
βββββββββββββΆβ endSession() β β β β β
β β (plan, if β β β β β
β β running) β β β β β
β β cookingMeal= β β β β β
β β meal β β β β β
β β fullScreen β β β β β
β β Cover ββββββΆβ loadActiveFacts βββββββββββββββββββββββββββββββββββββΆβ facts
β β β mint token, WebRTC, session.update β
β β β (cook prompt embeds recipe + facts) β
β β β response.create (kickoff) ββΆ β
β β ββββ "Pad Thai. Grab your wokβ¦" β
β hears step β β β β β β
β "what does β β ββ audio ββββββββββΆ β β β
β it look β β β β β β
β like?" β ββββ show_reference("caramelized onions") β
β β β dispatch: β
β β β ImageReference.search ββββΆ pexels-search (Edge Fn) β
β β β β βββββΆ Pexels API β
β β ββββββββββββββββ imageURL ββββββ β
β β β injectAssistantImage: β
β β image bubble β ββ chat transcript β
β β in chat β ββ Live Activity (cache to App Group container) β
β β β β
β "set 10min β β ββ audio ββββββββββΆ β
β timer" β β β
β β ββββ start_timer(seconds:600, label:"Pasta") β
β β β AlarmService.scheduleTimer ββββββββββββββΆβ β
β β timer LA on β (CookingTimerLiveActivity widget) β β
β β lock screen β β
β taps β β β
β "All Done" β β β
βββββββββββββΆβ endSession ββΆβ disconnect β
β β β extract-facts (bg) ββββββββββββββββββββββββββββββββββΆβ facts merge
β β phase=.ratingβ β
β β MealRatingView β
β rates 5β β β β
βββββββββββββΆβ markMade ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΆβ UPDATE meals
β β cover dismiss β
β back on β β
β plan β β
The CookingTimerLiveActivity is independent of the session Live Activity β AlarmKit owns its lifecycle, so the countdown survives endSession() and keeps ticking on the lock screen / Dynamic Island until it fires.
Runs after every session regardless of mode. Each conversation is a chance to learn more about the user's tastes; we don't restrict extraction to onboarding.
endSession()
β
ββ POST /functions/v1/extract-facts ββββββΆ Edge Function (Deno)
β β
β ββ load existing active facts (1 query)
β ββ ask gpt-4o-mini to extract + revoke
β ββ in-memory merge plan:
β β β’ exact match β reinforce confidence
β β β’ new value β insert
β β β’ single-value β supersede + insert
β β β’ deactivate_ids β mark inactive
β ββ apply via Promise.all ββΆ Postgres facts
ββ disconnect WebRTC peer connection
ββ liveActivity.end()
ββ clear cached reference photo from App Group container
One LiveActivityManager per realtime session; local-only (no APNs push). Updates fire from the observation loop AND from individual tool dispatchers.
| Source | What pushes |
|---|---|
| Observation loop (every tick) | status β connecting / listening / speaking / muted / ended |
suggest_meals (planning) |
chips = suggestion titles, chipsTitle = "Suggestions" |
display_options (onboarding) |
chips = option labels, chipsTitle = question prompt |
add_meal / add_fact |
clears chips once the user has answered |
show_reference (cooking) |
imageFilename = path in App Group container |
endSession() |
status = .ended, then immediate dismissal |
Reference photos download to the App Group container (group.com.fasai.weat) so the widget extension can render them via local file I/O. (AsyncImage is unreliable inside a widget snapshot.)
Claude Opus 4.6 with CLAUDE.md.
npx skills add dpearson2699/swift-ios-skills \
--skill activitykit \
--skill widgetkit \
--skill alarmkit \
--skill authentication \
--skill permissionkit \
--skill swiftui-patterns \
--skill swift-concurrency
npx skills add supabase/agent-skills
- Less Coding, More Discussion: Don't rush to coding - always confirm your understanding, think critically about the suggested approach, ask questions, and make suggestions on how to improve the design.
- Web Search Documentation understanding: Read through the documentation here and reiterate your understanding. Write down step by step what you plan to do and what information you need from me.
- Delegate To Me: Don't try to read, search, or execute on everything yourself. If it's easier for me to do it, please give me instructions and I can acquire the information or perform the action for you.
- Break Down Tasks & Test: That's too big of a task. Break it down more so we can test in between.
- Generate Documentation: Create documentation for ____.
- For a really big project, I ask Claude to summarize and save architecture details not apparent in code to memory. I start a new session when I switch to a new area of the codebase. For this prototype, I only used a single session with two /compact runs.
- I always use direct file and folder references (@) so Claude knows exactly where I expect it to read / write.
- I use /btw to explore alternative options a lot.
- I help it course correct the larger design instead of going down a rabbit hole. If it's struggling to debug something, it's likely that the APIs / libraries were misused.
- Polish conversation prompts manually for more natural conversation
- Conversation Reliability & Latency Hillcimb
- Set up Remote Notification Server to push Live Activities to notify the user to order groceries, to prepare for cooking time, or when new meals have been generated and planned.
- Automatic meal planning based on cooking patterns.
- Integration with Instacart / Ubereats / etc. for Grocery Ordering