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

@@ -143,6 +143,8 @@ struct TripOptionsView: View {
@State private var citiesFilter: CitiesFilter = .noLimit
@State private var paceFilter: TripPaceFilter = .all
@Environment(\.colorScheme) private var colorScheme
@Environment(\.isDemoMode) private var isDemoMode
@State private var hasAppliedDemoSelection = false
// MARK: - Computed Properties
@@ -272,7 +274,7 @@ struct TripOptionsView: View {
}
// Options in this group
ForEach(group.options) { option in
ForEach(Array(group.options.enumerated()), id: \.element.id) { index, option in
TripOptionCard(
option: option,
games: games,
@@ -281,6 +283,7 @@ struct TripOptionsView: View {
showTripDetail = true
}
)
.accessibilityIdentifier("tripOptions.trip.\(index)")
.padding(.horizontal, Theme.Spacing.md)
}
}
@@ -300,6 +303,26 @@ struct TripOptionsView: View {
selectedTrip = nil
}
}
.onAppear {
if isDemoMode && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
// Auto-select "Most Games" sort after a delay
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
sortOption = DemoConfig.demoSortOption
}
}
// Then navigate to the 4th trip (index 3)
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.8) {
let sortedOptions = filteredAndSortedOptions
if sortedOptions.count > DemoConfig.demoTripIndex {
let option = sortedOptions[DemoConfig.demoTripIndex]
selectedTrip = convertToTrip(option)
showTripDetail = true
}
}
}
}
}
private var sortPicker: some View {
@@ -312,6 +335,7 @@ struct TripOptionsView: View {
} label: {
Label(option.rawValue, systemImage: option.icon)
}
.accessibilityIdentifier("tripOptions.sortOption.\(option.rawValue.lowercased().replacingOccurrences(of: " ", with: ""))")
}
} label: {
HStack(spacing: 8) {
@@ -332,6 +356,7 @@ struct TripOptionsView: View {
.strokeBorder(Theme.textMuted(colorScheme).opacity(0.2), lineWidth: 1)
)
}
.accessibilityIdentifier("tripOptions.sortDropdown")
}
// MARK: - Filters Section