Two fixes for route planning and display:
1. GameDAGRouter: Same-day game transitions now respect maxDailyDrivingHours
constraint. Previously, a 12:05 AM game in Arlington could connect to an
11:40 PM game in Milwaukee (19+ hour drive) because the code only checked
available time, not the 8-hour daily limit.
2. TripDetailView: Itinerary sections now group by (day, city) not just day.
Games in different cities on the same calendar day are shown as separate
sections with travel segments between them.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When updating a vote, CloudKit requires the server's changeTag to modify
existing records. Creating a new CKRecord caused "record to insert already
exists" errors. Now fetches the existing record first before saving.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add GamePickerStep with sheet-based Sport → Team → Game selection
- Add TeamPickerStep with sheet-based Sport → Team selection
- Add LocationsStep for start/end location selection with round trip toggle
- Update TripWizardViewModel with mode-specific fields and validation
- Update TripWizardView with conditional step rendering per mode
- Update ReviewStep with mode-aware validation display
- Fix gameFirst mode to derive date range from selected games
Each planning mode now shows only relevant steps:
- By Dates: Dates → Sports → Regions → Route → Repeat → Must Stops
- By Games: Game Picker → Route → Repeat → Must Stops
- By Route: Locations → Dates → Sports → Route → Repeat → Must Stops
- Follow Team: Team Picker → Dates → Route → Repeat → Must Stops
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Increase card height from 160 to 175 and add extra bottom padding
to prevent date text from being cut off.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New design style combines the Classic layout with subtle animated
backgrounds featuring floating sports icons and route lines.
Animations are slow and unobtrusive to avoid distracting from content.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AppearanceMode enum with system, light, and dark options
- Add AppearanceManager singleton to persist user preference
- Add appearance section in SettingsView with icon and description
- Apply preferredColorScheme at app root for immediate effect
- Include appearance mode in reset to defaults
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ShareCardSportBackground with floating sport icons for share cards
- Share cards now show sport-specific backgrounds (single or multiple sports)
- Achievement collection share respects sport filter selection
- Add ability to share individual achievements from detail sheet
- Trip wizard ReviewStep highlights missing required fields in red
- Add FieldValidation model to TripWizardViewModel
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add `displayName` computed property to Trip model that always
generates city list with " → " separator for consistent display
- Replace all `trip.name` usages with `trip.displayName` in UI files
- Update SuggestedTripsGenerator to use " → " separator
- Update PDFGenerator to use displayName for PDF titles
This ensures all trip names display consistently regardless of when
the trip was created or how the name was originally stored.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor TripDetailView to fetch games from AppDataProvider when not
provided, adding loading state indicator for better UX
- Update all callers (25+ HomeContent variants, TripOptionsView, HomeView)
to use simpler TripDetailView(trip:) initializer
- Fix PollDetailView sheet issue by using sheet(item:) instead of
sheet(isPresented:) to prevent blank screen on first tap
- Improve PollDetailView UI with Theme styling, icons, and better
visual hierarchy for share code, voting status, and results sections
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Delete TripCreationView.swift and TripCreationViewModel.swift (unused)
- Extract TripOptionsView to standalone file
- Extract DateRangePicker and DayCell to standalone file
- Extract LocationSearchSheet and CityInputType to standalone file
- Fix TripWizardView to pass games dictionary to TripOptionsView
- Remove debug print statements from TripDetailView
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix suggested trips showing wrong sports for cross-country trips
- Remove quick start sections from home variants (Classic, Spotify)
- Remove dead quickActions code from HomeView
- Fix pace capsule animation in TripCreationView
- Add text wrapping to achievement descriptions
- Improve poll parsing with better error handling
- Various sharing system improvements
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace old ProgressCardGenerator with protocol-based sharing architecture
supporting trips, achievements, and stadium progress. Features 8 color
themes, Instagram Stories optimization (1080x1920), and reusable card
components with map snapshots.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract deep link handling into dedicated DeepLinkHandler service
- Add MockData+Polls.swift with reusable test mocks for Trip, TripStop,
TripPoll, PollVote, and PollResults
- Update SportsTimeApp to use DeepLinkHandler.shared
- Add error alert for deep link failures
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add complete group trip polling feature allowing users to share trips
with friends for voting using Borda count scoring.
New components:
- TripPoll and PollVote domain models with share codes and rankings
- LocalTripPoll and LocalPollVote SwiftData models for persistence
- CKTripPoll and CKPollVote CloudKit record wrappers
- PollService actor for CloudKit CRUD operations and subscriptions
- PollCreation/Detail/Voting views and view models
- Deep link handling for sportstime://poll/{code} URLs
- Debug Pro status override toggle in Settings
Integration:
- HomeView shows polls section in My Trips
- SportsTimeApp registers SwiftData models and handles deep links
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add debug toggle in Settings to override Pro subscription status (DEBUG builds only, defaults to true)
- Auto-validate wizard step flags on appear so button enables without explicit user interaction:
- DatesStep: calls updateHasSetDates() on appear
- RoutePreferenceStep: sets hasSetRoutePreference on appear
- RepeatCitiesStep: sets hasSetRepeatCities on appear
Previously, canPlanTrip required all flags to be explicitly set by user interaction, even when valid defaults were showing.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add themed backgrounds for each onboarding feature page:
- Unlimited Trips: animated route map with dotted paths and traveling car
- Export & Share: floating documents with radiating share lines
- Track Your Journey: stadium map with pins and achievement badges
- Add sports-themed pricing page background with random glow effects
- Display introductory offer pricing in subscription rows
- Add feature bullets to each onboarding page for better value prop
- Add crown icon header and feature pills to pricing page
- Add debug button in Settings to preview onboarding flow
- Create StoreKit configuration file for testing IAP
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- CloudKit pagination: fetchAllRecords() handles >400 record batches
with cursor-based pagination (400 records per page)
- Cancellation support: SyncCancellationToken protocol enables graceful
sync termination when background tasks expire
- Per-entity progress: SyncState now tracks timestamps per entity type
so interrupted syncs resume where they left off
- NetworkMonitor: NWPathMonitor integration triggers sync on network
restoration with 2.5s debounce to handle WiFi↔cellular flapping
- wasCancelled flag in SyncResult distinguishes partial from full syncs
This addresses critical data sync issues:
- CloudKit queries were limited to ~400 records but bundled data has ~5000 games
- Background tasks could be killed mid-sync without saving progress
- App had no awareness of network state changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CKSport model to parse CloudKit Sport records
- Add fetchSportsForSync() to CloudKitService for delta fetching
- Add syncSports() and mergeSport() to CanonicalSyncService
- Update DataProvider with dynamicSports support and allSports computed property
- Update MockAppDataProvider with matching dynamic sports support
- Add comprehensive documentation for adding new sports
The app can now sync sport definitions from CloudKit, enabling new sports
to be added without app updates. Sports are fetched, merged into SwiftData,
and exposed via AppDataProvider.allSports alongside built-in Sport enum cases.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Struct representing sports synced from CloudKit. Conforms to AnySport
protocol for interchangeable use with Sport enum in UI and planning.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Existing Sport enum now conforms to AnySport protocol, enabling
unified handling with future DynamicSport types.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Defines protocol that both Sport enum and DynamicSport will conform to,
enabling interchangeable use in UI and planning engine.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add design style system with 23 unique home screen aesthetics:
- Classic (original SportsTime design, now default)
- 12 experimental styles (Brutalist, Luxury Editorial, etc.)
- 10 polished app-inspired styles (Flighty, SeatGeek, Apple Maps,
Things 3, Airbnb, Spotify, Nike Run Club, Fantastical, Strava,
Carrot Weather)
Includes settings picker to switch between styles and persists
selection via UserDefaults.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- PaywallView: remove unnecessary nil coalescing for currencyCode
- GameDAGRouter: change var to let for immutable compositeKeys
- GamesHistoryRow/View: add missing wnba and nwsl switch cases
- VisitDetailView: fix unused variable in preview
- AchievementEngine: use convenience init to avoid default parameter warning
- ProgressCardGenerator: use method overload instead of default parameter
- StadiumProximityMatcher: extract constants to ProximityConstants enum
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement freemium model with StoreKit 2:
- StoreManager singleton for purchase/restore/entitlements
- ProFeature enum defining gated features
- PaywallView and OnboardingPaywallView for upsell UI
- ProGate view modifier and ProBadge component
Feature gating:
- Trip saving: 1 free trip, then requires Pro
- PDF export: Pro only with badge indicator
- Progress tab: Shows ProLockedView for free users
- Settings: Subscription management section
Also fixes pre-existing test issues with StadiumVisit
and ItineraryOption model signature changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add LoadingSpinner component with small/medium/large sizes using system gray color
- Add LoadingPlaceholder for skeleton loading states
- Add LoadingSheet for full-screen blocking overlays
- Replace ThemedSpinner/ThemedSpinnerCompact across all views
- Remove deprecated loading components from AnimatedComponents.swift
- Delete LoadingTextGenerator.swift
- Fix PhotoImportView layout to fill full width
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add scenePhase observer to sync when app returns to foreground
- Remove misleading "Sync Schedules" button from Settings (it only
reloaded local data, didn't actually sync from CloudKit)
- Fix GamesHistoryView to refresh list after deleting a visit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PlanningTips data model with 105 tips across 7 categories
- Wire random tips into HomeView (3 tips per session)
- Add TripOptionsGrouper for grouping by city/game count and mileage
- Update TripOptionsView with sectioned display when sorting
- Recommended and Best Efficiency remain flat lists
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Enable zoom/pan on progress map with reset button
- Add visit count badges to stadium chips
- Create GamesHistoryView with year grouping and sport filters
- Create StadiumVisitHistoryView for viewing all visits to a stadium
- Add VisitListCard and GamesHistoryRow components
- Add "See All" navigation from Recent Visits to Games History
- Add tests for map interactions, visit lists, and games history
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace progressive reveal with single fade-in of all steps
- Add canPlanTrip validation requiring all fields before planning
- Disable Plan Trip button until all selections are made
- Simplify ViewModel by removing step-by-step visibility logic
- Update tests for new validation-based approach
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Reorder wizard steps: dates before sports (enables availability check)
- Add contentShape(Rectangle()) for full tap targets on all cards
- Fix route preference showing preselected value
- Fix sport cards having inconsistent heights
- Speed up step reveal animation (0.3s → 0.15s)
- Add debounced scroll delay to avoid interrupting selection
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Auto-scroll to newly revealed sections
- Integration with TripPlanningEngine
- Navigation to TripOptionsView on success
- Error handling with alerts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- DatesStep: date range selection with duration indicator
- RegionsStep: geographic region selection
- RoutePreferenceStep: direct/scenic/balanced preference
- RepeatCitiesStep: unique vs repeat city visits
- MustStopsStep: optional must-stop locations
- ReviewStep: summary and plan button
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace hardcoded stadiumTeamMap with AppDataProvider lookup so
stadium renames (e.g., AT&T Center → Frost Bank Center) work
automatically via CloudKit sync without code changes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add city/state location and broadcast channel to GameCalendarRow
in the "By Games" trip building flow. Users now see:
- Clock icon with game time
- Building icon with stadium name
- Location pin with city, state
- TV icon with broadcast channel (when available)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace upfront loading of all games with lazy Sport → Team → Game
hierarchy. Games are now fetched per-team when expanded and cached
to prevent re-fetching. Also removes pagination workaround and
pre-computes groupings in ScheduleViewModel to avoid per-render work.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes lag and color inconsistency in trip creation game selection.
- Add pagination with 50 games per page to TripCreationViewModel
- Add displayedAvailableGames, loadInitialAvailableGames, loadMoreAvailableGames
- Update GamePickerSheet to use paginated data with infinite scroll trigger
- Show "Load more games" button with progress indicator and count
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Loads 50 games at a time to fix lag with large datasets.
- Add pagination state (displayedGames, currentPage, allFilteredGames)
- Add loadInitialGames() and loadMoreGames() methods
- Update gamesBySport to use displayedGames
- Add infinite scroll trigger via onAppear on last game
- Add ProgressView indicator when more games available
- Reset pagination when search text changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds timeZoneIdentifier to Stadium model and localGameTime/localGameTimeShort
computed properties to RichGame. Game times can now display in venue local
timezone. Also adds timezone field to Python StadiumInfo dataclass with
example entries for Pacific, Central, Mountain, and Toronto timezones.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds contentTransition(.identity) to pace filter icon and text to prevent
SwiftUI's default text morphing animation when changing pace filter
(All/Packed/Moderate/Relaxed). Also adds animation(nil, value:) to
disable animations on the capsule container.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wrap selectedIds.removeAll() in withTransaction with disablesAnimations
to prevent animation glitches when clearing all selections at once.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Disables implicit animation when toggling game selection to prevent
visual glitches/morphing effect. Uses both withTransaction to disable
animations on the state change and .animation(nil, value:) on the
selection indicator and row background.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>