# Architecture **Analysis Date:** 2026-01-09 ## Pattern Overview **Overall:** Clean MVVM with Feature-Based Modules + Offline-First Data Architecture **Key Characteristics:** - Three-layer architecture (Presentation, Domain, Data) - Single source of truth (`AppDataProvider.shared`) - Offline-first with background CloudKit sync - Scenario-based trip planning (A/B/C discriminated scenarios) - Explicit failure handling (no silent errors) ## Layers **Presentation Layer (`Features/`):** - Purpose: SwiftUI Views + @Observable ViewModels - Contains: Feature modules (Home, Trip, Schedule, Settings, Progress) - Depends on: Domain layer for planning, Data layer for models - Used by: App entry point **Domain Layer (`Planning/`):** - Purpose: Trip planning business logic - Contains: TripPlanningEngine, Scenario Planners, GameDAGRouter, TravelEstimator - Depends on: Domain models only (Game, Stadium, Team) - Used by: Presentation layer ViewModels **Data Layer (`Core/`):** - Purpose: Models, services, persistence - Contains: Domain models, SwiftData models, CloudKit sync, location services - Depends on: Foundation, SwiftData, CloudKit, MapKit - Used by: All layers **Export Layer (`Export/`):** - Purpose: PDF generation with asset prefetching - Contains: PDFGenerator, map snapshots, remote images, POI search - Depends on: Domain models, UIKit/PDFKit - Used by: TripDetailView ## Data Flow **App Startup Sequence:** 1. `SportsTimeApp.swift` (@main) → WindowGroup 2. `BootstrappedContentView` initializes data: - `BootstrapService.bootstrapIfNeeded()` - JSON → SwiftData (first launch) - `AppDataProvider.configure(with: context)` - Sets ModelContext - `AppDataProvider.loadInitialData()` - SwiftData → memory 3. App immediately usable with local data 4. Background: `CanonicalSyncService.syncAll()` - CloudKit → SwiftData (non-blocking) **Trip Planning Request:** 1. User Input → `TripCreationView` 2. `TripCreationViewModel` builds `PlanningRequest` 3. `TripPlanningEngine.planItineraries(request:)` - Detects scenario (A: date range, B: selected games, C: locations) - Delegates to appropriate `ScenarioPlanner` 4. `GameDAGRouter` finds routes with diversity 5. Returns `ItineraryResult` (success with ranked options or explicit failure) 6. User selects option → `SavedTrip` persisted **State Management:** - ViewModels use `@Observable` (not ObservableObject) - SwiftData `@Query` for saved trips - In-memory cache in `AppDataProvider` for canonical data ## Key Abstractions **AppDataProvider:** - Purpose: Single source of truth for all canonical data reads - Location: `Core/Services/DataProvider.swift` - Pattern: Singleton with in-memory cache - Usage: `AppDataProvider.shared.stadiums`, `.teams`, `.fetchGames()` **ScenarioPlanner Protocol:** - Purpose: Encapsulates planning algorithm for each scenario type - Examples: `ScenarioAPlanner`, `ScenarioBPlanner`, `ScenarioCPlanner` - Pattern: Strategy pattern with factory (`ScenarioPlannerFactory`) **GameDAGRouter:** - Purpose: DAG-based route finding with multi-dimensional diversity - Location: `Planning/Engine/GameDAGRouter.swift` - Pattern: Beam search with diversity pruning **TripPlanningEngine:** - Purpose: Thin orchestrator delegating to scenario planners - Location: `Planning/Engine/TripPlanningEngine.swift` - Pattern: Facade wrapping scenario detection and filter application ## Entry Points **App Entry:** - Location: `SportsTime/SportsTimeApp.swift` - Triggers: App launch - Responsibilities: SwiftData schema, ModelContainer, BootstrappedContentView **Main UI Entry:** - Location: `Features/Home/Views/HomeView.swift` - Triggers: After bootstrap completes - Responsibilities: TabView with 4 tabs (Home, Schedule, Trips, Progress) **Trip Creation:** - Location: `Features/Trip/Views/TripCreationView.swift` - Triggers: User taps "New Trip" - Responsibilities: Form input, calls TripCreationViewModel **Trip Display:** - Location: `Features/Trip/Views/TripDetailView.swift` - Triggers: User selects trip - Responsibilities: Itinerary display, conflict detection, PDF export ## Error Handling **Strategy:** Explicit failure types with reasons, no silent errors **Patterns:** - `ItineraryResult` enum: `.success([ItineraryOption])` or `.failure(PlanningFailure)` - `PlanningFailure` contains `FailureReason` and user-friendly message - ViewModels expose `.error` state for UI display - Services use `throws` for recoverable errors ## Cross-Cutting Concerns **Logging:** - `print()` with emoji prefixes for debugging - No production logging framework **Validation:** - Input validation in ViewModels before planning - `ConstraintViolation` type for planning constraints **Thread Safety:** - `@MainActor` for UI-bound services - `actor` types for planning engine components - Explicit isolation annotations throughout **Data Consistency:** - Canonical IDs (string) + UUIDs for stable identity - SwiftData ↔ Domain model conversion via `.toDomain()` methods --- *Architecture analysis: 2026-01-09* *Update when major patterns change*