Skip to content

JDMAXilius/Recipe-App

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

506 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🦦 Otto β€” the quieter kind of cookbook

A warm, hand-painted recipe app led by Otto the otter chef.
750+ honest recipes, a place for yours, and nobody shouting five stars at you.


What Otto does today

Discover & cook

  • Browse and search 750+ recipes (seeded from TheMealDB, served from Otto's own DB) with painted category tiles and an "Otto's pick" hero
  • Recipe detail: parallax photo hero, serif title on cream, source attribution, computed meta (servings Β· ingredients Β· steps), live ingredient scaling with US/Metric conversion, inline video, semantic-ink method steps, nutrition estimate card, related-recipes exit
  • Cook mode: mise-en-place β†’ big-type steps with hand-painted Otto action art (chop/mix/sautΓ©/simmer/bake/wait/season/pour/serve), tappable durations that start named timers, a multi-timer hub, alarm sound + vibration, keep-awake, swipe navigation, exit protection, and a Proud-Otto finish with "Snap your plate" journal capture

Your cookbook

  • Save any recipe with the paw mark (Otto's signature)
  • Import from any recipe site β€” paste a URL, Otto reads the ingredients and steps (deterministic schema.org parsing, no AI), you review, it's on the shelf with the source credited forever
  • Ask Otto β€” chat with Otto to generate a recipe, or snap a photo of one; write your own in the same editor; edit and delete freely. The editor's Ask-Otto row (new + edit modes) hands whatever's typed to the chat so Otto continues from your half-finished recipe
  • Cookbook tab with All Β· Saved Β· My recipes segments and a Cooked filter

Plan & shop

  • Otto's week: a loose 7-day planner (no meal-slot guilt), add from any recipe or from the plan, cooked check-offs, "What's cooking tonight?" surfaced on Discover
  • Shopping list built from the week on demand: one row per ingredient with summed quantities, aisle sections, provenance ("for World's Best Lasagna"), check-offs that never reorder mid-store, your own extras
  • Shared kitchen: start a household or join one with an invite code β€” everyone adds to and checks off the same list in real time
  • Share a recipe or a shopping list as a link (capability tokens) or a rendered share card

The rest

  • 3-screen painted onboarding + splash (still image + animated lid-lift, reduced-motion aware)
  • "You" tab: earned stats (cooked/saved/yours β€” each a door), a private cooking journal, inline US/Metric, editable name + avatar, quiet sign-out, visible account deletion
  • Otto Club membership β€” $34.99/yr Β· $4.99/mo Β· 5-day trial, honest pricing math; RevenueCat wired end-to-end (purchase/restore/entitlement + Supabase membership webhook), running on the RC Test Store until the App Store products go live

Tech stack

Expo SDK 54 Β· React Native 0.81 Β· React 19 Β· TypeScript Β· expo-router v6 Β· TanStack Query v5 Β· zod v4 Β· Supabase (Auth + Postgres + Edge Functions + Realtime) Β· Lora (serif). No custom server β€” server-side logic runs as Supabase Edge Functions. RLS is the security boundary.

Project structure

Recipe-App/
β”œβ”€β”€ app/                    expo-router routes (file-based; a file = a screen)
β”‚   β”œβ”€β”€ _layout.tsx           root: GestureHandler β†’ ErrorBoundary β†’ SafeArea β†’
β”‚   β”‚                         QueryClientProvider β†’ AuthProvider β†’ <Stack> (+ ToastHost, NotifSync)
β”‚   β”œβ”€β”€ index.tsx             launch gate β†’ onboarding / sign-in / tabs (resolveRoute)
β”‚   β”œβ”€β”€ (auth)/               sign-in Β· sign-up Β· forgot-password (+ redirect guard)
β”‚   β”œβ”€β”€ (tabs)/               Discover (index) Β· Cookbook Β· raised οΌ‹ (create/chat) Β· Plan Β· Account
β”‚   β”œβ”€β”€ recipe/[id].tsx       recipe detail
β”‚   β”œβ”€β”€ recipe/cook/[id].tsx  Cook mode
β”‚   β”œβ”€β”€ recipe/edit.tsx       recipe editor (write/edit your own)
β”‚   β”œβ”€β”€ add.tsx               Add sheet (URL / paste / photo import)
β”‚   β”œβ”€β”€ shopping.tsx          shopping list
β”‚   β”œβ”€β”€ household.tsx         shared kitchen (create/join by code)
β”‚   β”œβ”€β”€ journal.tsx           cooking journal
β”‚   β”œβ”€β”€ otto-club.tsx         membership / paywall surface
β”‚   β”œβ”€β”€ chats.tsx             recent Ask-Otto chats
β”‚   └── onboarding Β· notifications Β· preferences Β· faq Β· auth/callback Β· *-password
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ features/           feature-first modules β€” each owns its screens, components,
β”‚   β”‚                       *.queries.ts (TanStack Query), pure logic (*.ts) + tests (*.test.mjs)
β”‚   β”‚   β”œβ”€β”€ auth/             session (AuthProvider), sign in/up, password reset,
β”‚   β”‚   β”‚                     OAuth (Apple/Google/Facebook), social + username helpers
β”‚   β”‚   β”œβ”€β”€ recipes/          Discover, recipe detail, cards, filters, video embed,
│   │   │                     TheMealDB→recipe transform, ingredient scaling
β”‚   β”‚   β”œβ”€β”€ cookbook/         saved + "my recipes" with segments (All Β· Saved Β· Mine)
β”‚   β”‚   β”œβ”€β”€ cook/             Cook mode + engines: session (step split/match),
β”‚   β”‚   β”‚                     stepEnrich (timers/temps), stepAction (which Otto art)
β”‚   β”‚   β”œβ”€β”€ nutrition/        the on-device nutrition engine (see below) +
β”‚   β”‚   β”‚                     NutritionCard / CalorieRing + category estimates
β”‚   β”‚   β”œβ”€β”€ planner/          Otto's week + shopping list; week + shoppingList engines
β”‚   β”‚   β”œβ”€β”€ household/        shared kitchen: queries + realtime hooks (useHousehold)
β”‚   β”‚   β”œβ”€β”€ import/           Add sheet (URL/paste/photo) + recipe editor + draft hand-off
β”‚   β”‚   β”œβ”€β”€ chat/             "Ask Otto" chat β†’ recipe generation
β”‚   β”‚   β”œβ”€β”€ share/            share cards + capability share links (recipe / list) + tokens
β”‚   β”‚   β”œβ”€β”€ journal/          private "snap your plate" cooking journal
β”‚   β”‚   β”œβ”€β”€ notifications/    reminder sync (NotifSync) + notification preferences
β”‚   β”‚   β”œβ”€β”€ profile/          Account, Preferences, Household screen, Otto Club, FAQ
β”‚   β”‚   └── onboarding/       carousel + splash + the onboarding/route gate
β”‚   β”‚
β”‚   β”œβ”€β”€ shared/             cross-feature primitives
β”‚   β”‚   β”œβ”€β”€ theme/            design tokens + semantic-ink (terracotta = computed/interactive)
β”‚   β”‚   β”œβ”€β”€ ui/               shared components (Screen, buttons, mascot states, toast…)
β”‚   β”‚   β”œβ”€β”€ supabase/         the Supabase JS client
β”‚   β”‚   β”œβ”€β”€ lib/              fdaCalories (21 CFR 101.9 rounding), format helpers
β”‚   β”‚   β”œβ”€β”€ haptics.ts        haptic feedback wrapper
β”‚   β”‚   β”œβ”€β”€ storage.ts        AsyncStorage key-value store
β”‚   β”‚   β”œβ”€β”€ imagePicker.ts    camera / photo picker
β”‚   β”‚   β”œβ”€β”€ bus.ts            Otto event bus (mascot reacts to app events)
β”‚   β”‚   β”œβ”€β”€ assets.ts         asset registry (mascot/action/food art)
β”‚   β”‚   └── motion.ts         reanimated helpers (no layout anims on web)
β”‚   β”‚
β”‚   └── types/database.ts   generated Supabase types (regenerate after each migration)
β”‚
β”œβ”€β”€ supabase/
β”‚   β”œβ”€β”€ functions/          Edge Functions = the "backend" (Deno, deployed to Supabase)
β”‚   β”‚   β”œβ”€β”€ content/          TheMealDB proxy (key server-side; the app never sees it)
β”‚   β”‚   β”œβ”€β”€ import-recipe/    URL β†’ recipe via schema.org JSON-LD (deterministic, SSRF-guarded)
β”‚   β”‚   β”œβ”€β”€ generate-recipe/  Ask Otto β€” Claude (opus) recipe gen: prompt Β· chat Β· photo modes
β”‚   β”‚   β”œβ”€β”€ resolve-nutrition/ Claude (haiku) picks a real USDA record for unseen ingredients
β”‚   β”‚   β”œβ”€β”€ revenuecat-webhook/ RevenueCat events β†’ public.memberships (shared-secret auth)
β”‚   β”‚   β”œβ”€β”€ canonicalize/     otto_recipes curation: original recipe β†’ canonical structured record
β”‚   β”‚   └── delete-account/   completes account deletion (auth user + storage)
β”‚   └── migrations/         Postgres schema + RLS as timestamped SQL (source of truth)
β”‚
β”œβ”€β”€ assets/                 mascot art, Otto action paintings, food-category icons, app icons, sounds
β”œβ”€β”€ docs/                   reference/ (living docs) Β· history/ (build record + captures) Β· legal/
β”‚                           see docs/README.md for the index
β”œβ”€β”€ e2e/                    Playwright journeys (auth Β· cook Β· plan Β· share Β· …)
β”œβ”€β”€ test/                   node --test unit suites (co-located *.test.mjs run here too)
β”œβ”€β”€ eas.json                EAS Build profiles (production = native, no OTA channel)
β”œβ”€β”€ app.json                Expo config (bundle id com.otto.recipes, plugins, scheme otto://)
└── references/             non-app material: the Altavida brand project + reusable app/website scaffolds

Architecture & data flow

Your app (Expo / RN)
   β”‚  Supabase JS client + TanStack Query
   β–Ό
Supabase
   β”œβ”€ Postgres  ── your data behind Row-Level Security (RLS is the security boundary)
   β”œβ”€ Auth      ── email/password + Apple/Google/Facebook OAuth
   β”œβ”€ Realtime  ── the shared shopping list syncs via postgres_changes
   └─ Edge Functions ── the few jobs that must run server-side:
        content β†’ TheMealDB    import-recipe β†’ schema.org    delete-account
        generate-recipe β†’ Claude (opus)      resolve-nutrition β†’ Claude (haiku) + USDA
        revenuecat-webhook β†’ memberships     canonicalize β†’ otto_recipes curation
  • No custom server. The app queries Postgres directly through the Supabase client; RLS decides what each user can see. The Edge Functions hold the only secrets (TheMealDB / Anthropic / USDA keys, the RevenueCat webhook secret, the service-role key) so they never touch the app bundle.
  • Memberships: RevenueCat is the purchase layer (entitlement club); its webhook writes public.memberships (read-own-row RLS, service-role writes) so the backend always knows who's in the Club.
  • Two content sources, one job each β€” TheMealDB supplies recipes + ingredient lines (it carries no nutrition, by design); USDA FoodData Central (CC0) supplies the per-ingredient nutrition those lines are costed against.
  • Semantic ink β€” terracotta = computed/interactive, ink = human-authored. Honesty law β€” never fabricate data; null beats a guess.

The nutrition engine (src/features/nutrition/engine/)

Runs on-device with zero network calls β€” usdaTable.json ships in the app. An ingredient line is parsed to grams (parse.ts), looked up against the bundled USDA table (lookup.ts), and costed Γ— per-100g Γ· servings with guards for raw/cooked, coverage, and kcal plausibility (compute.ts + guards.ts). Only an unseen ingredient falls back to the resolve-nutrition Edge Function (Claude picks a real USDA record), then caches forever. The cook/scale/shopping engines (cook/session, stepEnrich, planner/shoppingList, recipes/recipe.scale) are likewise pure, deterministic, and unit-tested β€” no LLM in the render path.

Design system

Light-only, token-pure. Terracotta #C4562E accent, cream paper surfaces, Lora serif display, warm ink. Otto appears at emotional beats; the paw is the save mark everywhere.

  • docs/reference/DESIGN_SYSTEM.md (authoritative) Β· docs/reference/MASCOT.md Β· docs/reference/SCREEN_MAP.md
  • Tokens: src/shared/theme/; mascot/action/food art under assets/ via src/shared/assets.ts
  • Figma DS file: X1eGT54CTwtowHNve30vvE; master board mM0uWkHod9rL1Ff1VJ64Au

Run it

# app (web + Expo Go)
npm install && npx expo start                     # :8081

# native iOS dev build (needs the native modules β€” expo-audio/keep-awake/image-picker)
npx expo run:ios --device "iPhone 17 Pro Max"

Server-side logic (Edge Functions) and schema (migrations) live under supabase/ and deploy to the Supabase project β€” there is no separate server to run.

CocoaPods on a stock Mac (no Homebrew, system Ruby 2.6): gem install --user-install activesupport -v 6.1.7.10 --no-document && gem install --user-install cocoapods --no-document, then put $HOME/.gem/ruby/2.6.0/bin on PATH. ios/ and android/ are generated (expo prebuild) and gitignored. Splash/plugin changes need expo prebuild -p ios --clean.

.env

# .env (root) β€” publishable client config, safe to expose; RLS is the security boundary
EXPO_PUBLIC_SUPABASE_URL=...
EXPO_PUBLIC_SUPABASE_ANON_KEY=...
EXPO_PUBLIC_USE_OTTO_RECIPES=true   # serve recipe display from Otto's DB (not TheMealDB)

EXPO_PUBLIC_* vars are inlined at build time β€” restart Expo after changing them, and keep the same set registered in EAS env (eas env:list production) or TestFlight builds silently ship without them. Server-side secrets (ANTHROPIC_API_KEY, THEMEALDB_KEY, USDA_API_KEY, the service-role key for account deletion) live in Supabase Edge Function config, never in the app bundle.

Checks

npm run typecheck     # tsc --noEmit
npm run lint          # eslint app src test
npm test              # node --test (unit + engine + edge-function logic)

CI (.github/workflows/ci.yml) runs all three on every push to main and PRs into it.

Release (TestFlight)

Native builds only β€” no OTA channel (native modules change too often). Credentials are local (credentials/ios/, gitignored: distribution cert .p12 + provisioning profile + the App Store Connect API key), so no Apple ID login is ever needed. The loop:

# 1. bump ios.buildNumber (and version if it's a new release) in app.json, commit
# 2. build (~20 min; env vars come from EAS `production` environment, NOT .env)
EXPO_NO_CAPABILITY_SYNC=1 npx eas-cli build -p ios --profile production --non-interactive

# 3. submit β€” temporarily add ascApiKeyPath/ascApiKeyId/ascApiKeyIssuerId to
#    eas.json submit.production.ios (the CLI ignores the env-var route), then revert
npx eas-cli submit -p ios --profile production --latest --non-interactive
git checkout eas.json

# 4. attach to the internal group β€” REQUIRED, the group doesn't auto-distribute;
#    a submitted-but-unattached build reaches nobody
node ~/.claude/skills/eas-ios-testflight/scripts/tf-attach.mjs "Otto Insiders"

Internal testing needs no Beta App Review β€” testers install the moment the build is attached. ITSAppUsesNonExemptEncryption: false in app.json skips the export-compliance prompt.

Docs map

Doc What it holds
docs/reference/ARCHITECTURE.md Full v2 codebase map (grounded in the source)
docs/reference/API_ARCHITECTURE.md The data-access model β€” RLS queries, Edge Functions, RPCs, Realtime
docs/reference/DESIGN_SYSTEM.md The authoritative design system
docs/reference/CONTEXT_ENGINEERING.md "How to navigate the tree" + task→file map
docs/reference/contracts/ Feature-module, database, engine, UI, persistence, testing, data-ownership contracts
docs/tickets/ Active work: nutrition accuracy + the otto-recipes data-ownership plan
docs/history/REDESIGN_NOTES.md The decision log β€” every call with rationale
docs/history/ Β· docs/README.md The full build record (tickets, rebuild, research, captures)

Status

On TestFlight β€” v1.0.15 (build 31), internal "Otto Insiders" group. The feature-first v2 rebuild is live on main (the old RN-JS app + Express backend are archived on the v1-legacy branch). Backend runs entirely on Supabase β€” all Edge Functions active, Claude generation + nutrition resolution working, security advisors cleaned up. Otto Club paywall shows launch pricing against the RevenueCat Test Store.

Next: a device pass on the OS-level paths (Apple/Google/Facebook login, timer audio, camera, push); real App Store subscription products (Paid Apps agreement β†’ ASC products β†’ swap the appl_ RevenueCat key) and feature gating on the club entitlement; then App Store submission.

Project identity

  • Domain: ottosapp.com β€” marketing site lives in its own repo (Otto_Website); Terms + Privacy hosted at /terms and /privacy (App Review requirement, linked from the paywall)
  • Auth: email/password + Apple, Google, and Facebook OAuth (Supabase Auth); transactional email over Google Workspace SMTP
  • Deep-link scheme: otto:// (OAuth callbacks + share links); bundle id com.otto.recipes, Apple team A6J6HGNWZK
  • Active data work (docs/tickets/): nutrition accuracy (usdaTable is per-100g β€” the weight layer owns accuracy) and OWN_RECIPE_DB β€” migrating the 750 seed recipes into curated otto_recipes rows via the canonicalize function

Credits

Recipe content by TheMealDB. Nutrition from USDA FoodData Central (public domain, CC0). Built with free-tier tools throughout β€” two sources, one job each: TheMealDB supplies recipes and ingredient lines; USDA supplies the per-ingredient nutrition those lines are costed against.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages