High-level shape of the app. Read this when you’re about to make a structural decision; otherwise jump straight to the relevant folder README.
┌─────────────────────────────────────────────┐
│ Views (Features/*/<Screen>View.swift) │ SwiftUI, declarative
├─────────────────────────────────────────────┤
│ View Models (*ViewModel.swift, @Observable)│ per-feature, when needed
├─────────────────────────────────────────────┤
│ Services (protocol seams) │ FoodResolving, …
├─────────────────────────────────────────────┤
│ Persistence (SwiftData @Model) │ PersistenceController owns container
└─────────────────────────────────────────────┘
@Query for lists, push mutations through their view-model.Tasks for async work, and the imperative SwiftData writes (insert / update / delete).App/AppEnvironment.swift.ModelContainer (Services/Persistence/PersistenceController.swift). Both @Query and modelContext come from this container.No tab bar. RootView (in App/) holds a MenuDestination enum and swaps the visible feature screen based on what the user picks in the menu sheet. Each feature can host its own NavigationStack if it needs one — the root stays flat.
Reference: BACKEND_SETUP.md.
iOS ──► FoodResolving protocol ──► (offline) MockFoodResolver
──► (configured) RemoteFoodResolver
│
▼
Supabase Edge Function
│
▼
AI Gateway → LLM provider
iOS NEVER holds an LLM API key. The backend Edge Function:
.xcdatamodeld artifact to manage in xcodegen).CombineSwiftUI bindings + @Observable cover the reactive surface. Async work uses Task + async/await. There’s no need for Combine here.
Features/.Services/. Each has a protocol + at least one concrete (Mock + Live).WorkoutAppTests/<Feature>Tests.swift.UserProfile for targets, FoodLogEntry for actuals.