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

@@ -11,9 +11,11 @@ struct DateRangePicker: View {
@Binding var startDate: Date
@Binding var endDate: Date
@Environment(\.colorScheme) private var colorScheme
@Environment(\.isDemoMode) private var isDemoMode
@State private var displayedMonth: Date = Date()
@State private var selectionState: SelectionState = .none
@State private var hasAppliedDemoSelection = false
enum SelectionState {
case none
@@ -89,6 +91,24 @@ struct DateRangePicker: View {
if endDate > startDate {
selectionState = .complete
}
// Demo mode: auto-select dates
if isDemoMode && !hasAppliedDemoSelection {
hasAppliedDemoSelection = true
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay) {
withAnimation(.easeInOut(duration: 0.3)) {
// Navigate to demo month
displayedMonth = DemoConfig.demoStartDate
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + DemoConfig.selectionDelay + 0.5) {
withAnimation(.easeInOut(duration: 0.3)) {
startDate = DemoConfig.demoStartDate
endDate = DemoConfig.demoEndDate
selectionState = .complete
}
}
}
}
.onChange(of: startDate) { oldValue, newValue in
// Navigate calendar to show the new month when startDate changes externally
@@ -159,12 +179,14 @@ struct DateRangePicker: View {
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityIdentifier("wizard.dates.previousMonth")
Spacer()
Text(monthYearString)
.font(.headline)
.foregroundStyle(Theme.textPrimary(colorScheme))
.accessibilityIdentifier("wizard.dates.monthLabel")
Spacer()
@@ -180,6 +202,7 @@ struct DateRangePicker: View {
.background(Theme.warmOrange.opacity(0.15))
.clipShape(Circle())
}
.accessibilityIdentifier("wizard.dates.nextMonth")
}
}
@@ -287,6 +310,12 @@ struct DayCell: View {
calendar.startOfDay(for: date) < calendar.startOfDay(for: Date())
}
private var accessibilityId: String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return "wizard.dates.day.\(formatter.string(from: date))"
}
var body: some View {
Button(action: onTap) {
ZStack {
@@ -330,6 +359,7 @@ struct DayCell: View {
.frame(width: 36, height: 36)
}
}
.accessibilityIdentifier(accessibilityId)
.buttonStyle(.plain)
.disabled(isPast)
.frame(height: 40)