- 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>
12 tasks with TDD workflow:
- Domain, CloudKit, and SwiftData models
- PollService for CloudKit operations
- Creation and detail ViewModels
- SwiftUI views with vote ranking
- Deep link handling
Design for CloudKit-based group coordination feature:
- Ranked choice voting on trip options
- Share via link with 6-char codes
- Anonymous results (aggregate only)
- Real-time updates via CloudKit subscriptions
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>
Design covers three improvements:
- Paginated CloudKit fetches to handle >400 records
- Graceful cancellation with per-entity progress saving
- NWPathMonitor for auto-sync on network restoration
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>
Freemium model with StoreKit 2 local-only entitlement checking.
Pro features: unlimited trips, PDF export, progress tracking.
Monthly ($4.99) and annual ($49.99) pricing with Family Sharing.
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>
13 tasks covering LoadingSpinner, LoadingPlaceholder, LoadingSheet
creation, ProgressView replacements, and deprecated component removal.
Includes TDD with 13 new tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete design spec for overhauling all loading views, progress
indicators, and spinners. Covers LoadingSpinner, LoadingPlaceholder,
and LoadingSheet components with Apple-style minimal aesthetic.
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>
Hybrid approach: keep Sport enum for existing 7 sports,
add DynamicSport struct for CloudKit-defined leagues.
Unified via AnySport protocol for seamless UI integration.
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>
NBA scraper now resolves old stadium name to current Spurs arena.
The AT&T Center was renamed to Frost Bank Center in July 2024.
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>
Adds stadium name and city to game rows in the schedule list.
The GameRowView now has a showLocation parameter that displays
a location row with mappin icon when enabled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes launch freeze by running TripPlanningEngine in background task.
UI now responsive immediately while trips generate.
- Move heavy computation to Task.detached with .userInitiated priority
- Change all helper methods to nonisolated static functions
- Create local TripPlanningEngine in background task instead of instance property
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>