# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build & Run Commands ```bash # Build the iOS app xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build # Run tests xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test # Run specific test suite xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TripPlanningEngineTests test # Run a single test xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' -only-testing:SportsTimeTests/TestClassName/testMethodName test # Data scraping (Python) cd Scripts && pip install -r requirements.txt python scrape_schedules.py --sport all --season 2026 ``` ## Architecture Overview This is an iOS app for planning multi-stop sports road trips. It uses **Clean MVVM** with feature-based modules. ### Three-Layer Architecture 1. **Presentation Layer** (`Features/`): SwiftUI Views + @Observable ViewModels organized by feature (Home, Trip, Schedule, Settings) 2. **Domain Layer** (`Planning/`): Trip planning logic - `TripPlanningEngine` - Main orchestrator, 7-step algorithm - `RouteOptimizer` - TSP solver (exact for <8 stops, heuristic otherwise) - `ScheduleMatcher` - Finds games along route corridor - `TripScorer` - Multi-factor scoring (game quality, route efficiency, leisure balance) 3. **Data Layer** (`Core/`): - `Models/Domain/` - Pure Swift structs (Trip, Game, Stadium, Team) - `Models/CloudKit/` - CKRecord wrappers for public database - `Models/Local/` - SwiftData models for local persistence (SavedTrip, UserPreferences) - `Services/` - CloudKitService (schedules), LocationService (geocoding/routing) 4. **Export Layer** (`Export/`): - `PDFGenerator` - Generates PDF trip itineraries with maps, photos, and attractions - `ExportService` - Orchestrates PDF export with asset prefetching - `Services/MapSnapshotService` - Generates static map images via MKMapSnapshotter - `Services/RemoteImageService` - Downloads/caches team logos and stadium photos - `Services/POISearchService` - Finds nearby restaurants, attractions via MKLocalSearch - `Services/PDFAssetPrefetcher` - Parallel prefetching of all PDF assets ### Data Architecture (Offline-First) **CRITICAL: `AppDataProvider.shared` is the ONLY source of truth for canonical data.** All code that reads stadiums, teams, games, or league structure MUST use `AppDataProvider.shared`. Never access CloudKit or SwiftData directly for this data. ``` ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ Bundled JSON │ │ SwiftData │ │ CloudKit │ │ (App Bundle) │ │ (Local Store) │ │ (Remote Sync) │ └────────┬─────────┘ └────────▲─────────┘ └────────┬─────────┘ │ │ │ │ Bootstrap │ Read │ Background │ (first launch) │ │ Sync ▼ │ ▼ ┌─────────────────────────────────┴────────────────────────────────────┐ │ AppDataProvider.shared │ │ (Single Source of Truth) │ └─────────────────────────────────┬────────────────────────────────────┘ │ ▼ All Features, ViewModels, Services ``` **App Startup Flow:** 1. **Bootstrap** (first launch only): `BootstrapService` loads bundled JSON → SwiftData 2. **Configure**: `AppDataProvider.shared.configure(with: context)` 3. **Load**: `AppDataProvider.shared.loadInitialData()` reads SwiftData into memory 4. **App usable immediately** with local data 5. **Background sync**: `CanonicalSyncService.syncAll()` fetches CloudKit → updates SwiftData (non-blocking) 6. **Reload**: After sync completes, `loadInitialData()` refreshes in-memory cache **Offline Handling:** - SwiftData always has data (from bootstrap or last successful CloudKit sync) - If CloudKit unavailable, app continues with existing local data - First launch + offline = bootstrap data used **Canonical Data Models** (in SwiftData, synced from CloudKit): - `CanonicalStadium` → `Stadium` (domain) - `CanonicalTeam` → `Team` (domain) - `CanonicalGame` → `Game` (domain) - `LeagueStructureModel` - `TeamAlias`, `StadiumAlias` **User Data Models** (local only, not synced): - `SavedTrip`, `StadiumVisit`, `UserPreferences`, `Achievement` **Correct Usage:** ```swift // ✅ CORRECT - Use AppDataProvider let stadiums = AppDataProvider.shared.stadiums let teams = AppDataProvider.shared.teams let games = try await AppDataProvider.shared.filterGames(sports: sports, startDate: start, endDate: end) let richGames = try await AppDataProvider.shared.filterRichGames(...) let allGames = try await AppDataProvider.shared.allGames(for: sports) // ❌ WRONG - Never access CloudKit directly for reads let stadiums = try await CloudKitService.shared.fetchStadiums() // NO! // ❌ WRONG - Never fetch canonical data from SwiftData directly let descriptor = FetchDescriptor() let stadiums = try context.fetch(descriptor) // NO! (except in DataProvider/Sync/Bootstrap) ``` **Allowed Direct SwiftData Access:** - `DataProvider.swift` - It IS the data provider - `CanonicalSyncService.swift` - CloudKit → SwiftData sync - `BootstrapService.swift` - Initial data population - `StadiumIdentityService.swift` - Specialized identity resolution for stadium renames - User data (e.g., `StadiumVisit`) - Not canonical data ### Key Data Flow ``` TripCreationView → TripCreationViewModel → PlanningRequest → TripPlanningEngine (ScheduleMatcher + RouteOptimizer + TripScorer) → PlanningResult → Trip → TripDetailView → SavedTrip (persist) ``` ## Important Patterns - ViewModels use `@Observable` (not ObservableObject) - All planning engine components are `actor` types for thread safety - Domain models are pure Codable structs; SwiftData models wrap them via encoded `Data` fields - CloudKit container ID: `iCloud.com.sportstime.app` - `PDFGenerator` and `ExportService` are `@MainActor final class` (not actors) because they access MainActor-isolated UI properties and use UIKit drawing ### Themed Background System All views use `.themedBackground()` modifier for consistent backgrounds app-wide. **Components** (`Core/Theme/`): - `ThemedBackground` (ViewModifiers.swift) - Conditionally shows static gradient or animated background - `AnimatedSportsBackground` (AnimatedBackground.swift) - Floating sports icons with route lines - `DesignStyleManager.shared.animationsEnabled` - Toggle controlled in Settings **How it works:** ```swift // All views apply this modifier - animation state is automatic .themedBackground() // The modifier checks DesignStyleManager internally: if DesignStyleManager.shared.animationsEnabled { AnimatedSportsBackground() // Floating icons + route lines } else { Theme.backgroundGradient(colorScheme) // Static gradient } ``` **Adding new screens:** Just apply `.themedBackground()` - no need to handle animation logic. ### iOS 26 API Notes **Deprecated APIs** (use with `@available(iOS, deprecated: 26.0)` annotation): - `CLGeocoder` → Use `MKLocalSearch` with `.address` result type instead - `MKPlacemark` properties (locality, administrativeArea, etc.) → Still work but deprecated; use `MKMapItem` properties where possible - `MKMapItem.location` is non-optional in iOS 26 (returns `CLLocation`, not `CLLocation?`) **Swift 6 Concurrency**: - Use `@retroactive` for protocol conformances on types you don't own (e.g., `CLLocationCoordinate2D: @retroactive Codable`) - When capturing `var` in `async let`, create immutable copies first to avoid Swift 6 warnings ## Key View Components ### TripDetailView (`Features/Trip/Views/TripDetailView.swift`) Displays trip itinerary with conflict detection for same-day games in different cities. **Conflict Detection System:** - `detectConflicts(for: ItineraryDay)` - Checks if multiple stops have games on the same calendar day - Returns `DayConflictInfo` with `hasConflict`, `conflictingStops`, and `conflictingCities` **RouteOptionsCard (Expandable):** - Shows when multiple route options exist for the same day (conflicting games in different cities) - Collapsed: Shows "N route options" with city list, tap to expand - Expanded: Shows each option as a `RouteOptionCard` with numbered badge (Option 1, Option 2, etc.) - Single routes (no conflict): Uses regular `DayCard`, auto-expanded **RouteOptionCard:** - Individual option within the expandable RouteOptionsCard - Shows option number badge, city name, games at that stop, and travel info **DayCard Component (non-conflict mode):** - `specificStop: TripStop?` - When provided, shows only that stop's games - `primaryCityForDay` - Returns the city for the card - `gamesOnThisDay` - Returns games filtered to the calendar day **Visual Design:** - Expandable cards have orange border and branch icon - Option badges are blue capsules - Chevron indicates expand/collapse state ## Scripts `Scripts/scrape_schedules.py` scrapes NBA/MLB/NHL schedules from multiple sources (Basketball-Reference, Baseball-Reference, Hockey-Reference, official APIs) for cross-validation. See `Scripts/DATA_SOURCES.md` for source URLs and rate limits. ## Documentation The `docs/` directory contains project documentation: - `MARKET_RESEARCH.md` - Competitive analysis and feature recommendations based on sports travel app market research (January 2026) ## Test Suites - **TripPlanningEngineTests** (50 tests) - Routing logic, must-see games, required destinations, EV charging, edge cases - **DayCardTests** (11 tests) - DayCard conflict detection, warning display, stop filtering, edge cases - **DuplicateGameIdTests** (2 tests) - Regression tests for handling duplicate game IDs in JSON data ## Bug Fix Protocol Whenever fixing a bug: 1. **Write a regression test** that reproduces the bug before fixing it 2. **Include edge cases** - test boundary conditions, null/empty inputs, and related scenarios 3. **Confirm all tests pass** by running the test suite before considering the fix complete 4. **Name tests descriptively** - e.g., `test_DayCard_OnlyShowsGamesFromPrimaryStop_WhenMultipleStopsOverlapSameDay` Example workflow: ```bash # 1. Write failing test that reproduces the bug # 2. Fix the bug # 3. Verify the new test passes along with all existing tests xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test ``` ## Future Phases See `docs/MARKET_RESEARCH.md` for full competitive analysis and feature prioritization. ### Phase 2: AI-Powered Trip Planning **Natural Language Trip Planning** - Allow users to describe trips in plain English: "plan me a baseball trip from Texas" or "I want to see the Yankees and Red Sox in one weekend" - Parse intent, extract constraints (sports, dates, locations, budget) - Generate trip suggestions from natural language input **On-Device Intelligence (Apple Foundation Models)** - Use Apple's Foundation Models framework (iOS 26+) for on-device AI processing - Privacy-preserving - no data leaves the device - Features to enable: - Smart trip suggestions based on user history - Natural language query understanding - Personalized game recommendations - Conversational trip refinement ("add another game" / "make it shorter") **Implementation Notes:** - Foundation Models requires iOS 26+ and Apple Silicon - Use `@Generable` for structured output parsing - Implement graceful fallback for unsupported devices - See `axiom:axiom-foundation-models` skill for patterns ### Phase 3: Stadium Bucket List **Progress Tracking** - Visual map showing visited vs. remaining stadiums per league - Digital passport/stamps for each visited stadium - Achievement badges (e.g., "All NL West", "Coast to Coast", "10 Stadiums") - Shareable progress cards for social media **Competitors**: Baseball Bucket List, Sports Venue Tracker, MLB BallPark Pass-Port (physical) ### Phase 4: Group Trip Coordination **Collaborative Planning** - Invite friends to collaborate on trip planning - Polling/voting on game choices and destinations - Expense splitting integration - Shared itinerary with real-time sync - Role delegation (lodging, tickets, restaurants) **Competitors**: SquadTrip, Troupe, Howbout ### Phase 5: Fan Community **Social Features** - Stadium tips from locals (best food, parking, pre-game bars) - Fan meetup coordination for away games - Trip reviews and ratings - Discussion forums for specific stadiums **Competitor**: Fantrip (fan-to-fan stays and local tips) ## User Instruction