The iOS app is fully usable without a backend — it falls back to MockFoodResolver. Once you wire Supabase + Groq, food typing routes through the real resolver and per-user data syncs to the cloud.
| Layer | Choice | Notes |
|---|---|---|
| Database | Supabase Postgres | Free tier; foods catalog + per-user logs all here. |
| Auth | Supabase Auth (Sign in with Apple) | Mandatory at first launch. RLS scopes user data by auth.uid(). |
| API | Supabase Edge Functions (Deno) | Holds the Groq key server-side; iOS never sees it. |
| AI | Groq (Llama 3.3 70B) | Free tier; called only on foods cache miss. |
| Seed | USDA FoodData Central + Open Food Facts | One-shot Node import populates the canonical catalog. |
iOS holds two values: SUPABASE_URL (project URL) + SUPABASE_ANON_KEY (designed to be embedded). Everything sensitive (Groq key, DB master password, service role key) stays on the server.
Project Settings → API.console.groq.com.brew install supabase/tap/supabase. Verify with supabase --version.node --version.The migration creates the foods catalog, per-user log tables, and RLS policies.
# From the repo root
supabase login
supabase link --project-ref evsufhtdimexwghqkazy # use your ref
supabase db push # applies supabase/migrations/0001_init.sql
Alternatively (if you don’t want the CLI yet): open the Supabase dashboard → SQL Editor → paste the contents of supabase/migrations/0001_init.sql → Run. It’s idempotent.
Verify in Database → Tables:
public.foods, public.food_aliases, public.food_servingspublic.user_profiles, public.food_log_entries, public.weight_log_entriespublic.exercises, public.workout_templates, public.workout_sessions, public.workout_setsThe resolve-food Edge Function (deployed in Step 4) needs the Groq key and a service role key (to write to foods/food_aliases after AI resolution).
Get the service role key from Project Settings → API (labeled service_role secret). Treat it like a master password — never put it in any iOS file or commit it.
supabase secrets set \
GROQ_API_KEY=gsk_yourkeyhere \
SUPABASE_URL=https://evsufhtdimexwghqkazy.supabase.co \
SUPABASE_SERVICE_ROLE_KEY=eyJh-service-role-key
These secrets only exist in the Edge Function runtime — never in the repo, never in the iOS bundle.
Run the Node seed script (lives in scripts/, written next iteration):
cd scripts
cp .env.example .env # gitignored; fill with SERVICE_ROLE key
npm install
node seed-usda.mjs # ~5 min: 1500 USDA Foundation Foods
node seed-open-food-facts.mjs # ~30 min: ~50k branded items (optional)
The script normalizes every entry to per-100g and inserts both foods rows and common food_aliases (English; locale tags will be populated on later passes).
(Edge Function code lives in supabase/functions/resolve-food/, written next iteration.)
supabase functions deploy resolve-food
Test it without the iOS app:
curl -X POST \
"https://evsufhtdimexwghqkazy.supabase.co/functions/v1/resolve-food" \
-H "Authorization: Bearer <user-jwt>" \
-H "Content-Type: application/json" \
-d '{"input": "150g chicken breast"}'
(iOS wiring lands in a follow-up iteration. For now MockFoodResolver is still active.)
WorkoutApp/Resources/Secrets.xcconfig already has SUPABASE_URL + SUPABASE_ANON_KEY filled in.xcodegen generateAppEnvironment.live() will register RemoteFoodResolver automatically.USER TYPES "150g chicken"
│
▼
iOS parses quantity (150g) + name (chicken)
│
▼
iOS calls supabase.functions.invoke('resolve-food', { input })
│
▼
Edge Function:
1. Search foods + food_aliases for "chicken"
HIT → scale per-100g macros to 150g, return
MISS → POST to Groq with the resolver prompt
↓
Validate JSON, write canonical foods row + aliases + servings
↓
Return resolved entry
│
▼
iOS writes food_log_entries row (RLS scopes to user)
food_aliases lets chicken, chicken breast, poulet all map to the same foods row. food_servings lets 1 cup broccoli resolve via the broccoli row + cup → grams lookup.
We push food/water/workout/weight logs to HealthKit via NSHealthUpdateUsageDescription. We never read from HealthKit — the user’s data lives in our DB, and HealthKit is just an output. (NSHealthShareUsageDescription will be removed in project.yml next iteration.)
Profile → About reads AppConfig.fromBundle() and shows:
Mock (offline) — Secrets.xcconfig is empty.Configured — Supabase keys are present.