feat: rewrite bootstrap, fix CloudKit sync, update canonical data, and UI fixes

- Rewrite BootstrapService: remove all legacy code paths (JSONStadium,
  JSONGame, bootstrapStadiumsLegacy, bootstrapGamesLegacy, venue aliases,
  createDefaultLeagueStructure), require canonical JSON files only
- Add clearCanonicalData() to handle partial bootstrap recovery (prevents
  duplicate key crashes from interrupted first-launch)
- Fix nullable stadium_canonical_id in games (4 MLS games have null)
- Fix CKModels: logoUrl case, conference/division field keys
- Fix CanonicalSyncService: sync conferenceCanonicalId/divisionCanonicalId
- Add sports_canonical.json and DemoMode.swift
- Delete legacy stadiums.json and games.json
- Update all canonical resource JSON files with latest data
- Fix TripWizardView horizontal scrolling with GeometryReader constraint
- Update RegionMapSelector, TripDetailView, TripOptionsView UI improvements
- Add DateRangePicker, PlanningModeStep, SportsStep enhancements
- Update UI tests and marketing-videos config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-06 00:06:19 -06:00
parent 12f959ab8d
commit fdcecafaa3
29 changed files with 93279 additions and 157943 deletions

View File

@@ -12,6 +12,7 @@ import UniformTypeIdentifiers
struct TripDetailView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.colorScheme) private var colorScheme
@Environment(\.isDemoMode) private var isDemoMode
let trip: Trip
private let providedGames: [String: RichGame]?
@@ -33,6 +34,7 @@ struct TripDetailView: View {
@State private var isLoadingRoutes = false
@State private var loadedGames: [String: RichGame] = [:]
@State private var isLoadingGames = false
@State private var hasAppliedDemoSelection = false
// Itinerary items state
@State private var itineraryItems: [ItineraryItem] = []
@@ -113,7 +115,18 @@ struct TripDetailView: View {
} message: {
Text("This trip has \(multiRouteChunks.flatMap { $0 }.count) stops, which exceeds Apple Maps' limit of 16. Open the route in parts?")
}
.onAppear { checkIfSaved() }
.onAppear {
checkIfSaved()
// Demo mode: auto-favorite the trip
if isDemoMode && !hasAppliedDemoSelection && !isSaved {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.5) {
if !isSaved {
saveTrip()
}
}
}
}
.task {
await loadGamesIfNeeded()
if allowCustomItems {
@@ -348,6 +361,7 @@ struct TripDetailView: View {
.clipShape(Circle())
.shadow(color: .black.opacity(0.2), radius: 4, y: 2)
}
.accessibilityIdentifier("tripDetail.favoriteButton")
.padding(.top, 12)
.padding(.trailing, 12)
}