Files
Sportstime/CLAUDE.md
Trey t dbb0099776 chore: remove scraper, add docs, add marketing-videos gitignore
- Remove Scripts/ directory (scraper no longer needed)
- Add themed background documentation to CLAUDE.md
- Add .gitignore for marketing-videos to prevent node_modules tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 18:13:12 -06:00

13 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Build & Run Commands

# 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):

  • CanonicalStadiumStadium (domain)
  • CanonicalTeamTeam (domain)
  • CanonicalGameGame (domain)
  • LeagueStructureModel
  • TeamAlias, StadiumAlias

User Data Models (local only, not synced):

  • SavedTrip, StadiumVisit, UserPreferences, Achievement

Correct Usage:

// ✅ 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<CanonicalStadium>()
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:

// 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:

# 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