- Add visual drop target indicator showing where items will land
- Use stable travel anchor IDs (city names) instead of UUIDs that regenerate
- Fix findDayForTravelSegment to look forward to arrival day, not backward
- Add moveItemToBeginning() for inserting at position 0 when dropping on sections
- Sort custom items by sortOrder in all filters
- Sync shifted items to CloudKit after reorder
- Add opaque backgrounds to CustomItemRow and TravelSection to hide timeline
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CustomItineraryItem domain model with sortOrder for ordering
- Add CKCustomItineraryItem CloudKit wrapper for persistence
- Create CustomItemService for CRUD operations
- Create CustomItemSubscriptionService for real-time sync
- Add AppDelegate for push notification handling
- Add AddItemSheet for creating/editing items
- Add CustomItemRow with drag handle
- Update TripDetailView with continuous vertical timeline
- Enable drag-to-reorder using .draggable/.dropDestination
- Add inline "Add" buttons after games and travel segments
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
- 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>
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>
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>
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 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>
Previously, browsing by team (e.g., Houston Astros) only showed home games.
Now games are associated with both home AND away teams, so selecting a team
shows all their games regardless of whether they're playing at home or away.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add allRichGames method to DataProvider
- Update TripCreationViewModel.loadGamesForBrowsing to use allGames (removes 90-day limit)
- Update MockCloudKitService sync methods to use new delta sync signatures
- Update MockAppDataProvider with renamed methods and new allGames/allRichGames
- Fix all callers: ScheduleViewModel, TripCreationViewModel, SuggestedTripsGenerator, GameMatcher
- Update CLAUDE.md documentation with new method names
This completes the delta sync implementation:
- CloudKit sync now uses modificationDate for proper delta sync
- First sync fetches ALL data, subsequent syncs only fetch modified records
- "By Games" mode now shows all available games (not just 90 days)
- All data types (stadiums, teams, games) use consistent delta sync pattern
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This refactor fixes the achievement system by using stable canonical string
IDs (e.g., "stadium_mlb_fenway_park") instead of random UUIDs. This ensures
stadium mappings for achievements are consistent across app launches and
CloudKit sync operations.
Changes:
- Stadium, Team, Game: id property changed from UUID to String
- Trip, TripStop, TripPreferences: updated to use String IDs for games/stadiums
- CKModels: removed UUID parsing, use canonical IDs directly
- AchievementEngine: now matches against canonical stadium IDs
- All test files updated to use String IDs instead of UUID()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Green Monster (Fenway) and Ivy League (Wrigley) achievements
weren't working because:
1. Symbolic IDs use lowercase sport (stadium_mlb_bos)
2. Sport enum uses uppercase raw values (MLB)
3. Visits store stadium UUIDs, not symbolic IDs
Added resolveSymbolicStadiumId() helper that:
- Uppercases the sport string before Sport(rawValue:)
- Looks up team by abbreviation and sport
- Returns the team's stadiumId as UUID string
Also fixed:
- getStadiumIdsForLeague returns UUID strings (not symbolic IDs)
- AchievementProgress.isEarned computed from progress OR stored record
- getStadiumIdsForDivision queries CanonicalTeam properly
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds a trip duration stepper (2-21 days) to the By Games planning mode.
When users select specific games, the planner now generates ALL possible
N-day windows containing those games (e.g., 7-day trips starting on
different days), finding additional games in each window to maximize
route options.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The resolveLocations() function was overwriting valid LocationInput
objects (with coordinates) from LocationSearchSheet. Now it only
resolves locations that don't already have coordinates.
Added debug logging to trace scenario selection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces inline text fields with buttons that open LocationSearchSheet,
matching the must-stop location picker UI for consistency.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Lock all maps to North America (no pan/zoom) in ProgressMapView and TripDetailView
- Sort saved trips by most cities (stops count)
- Filter cross-country trips to top 2 by stops on home screen
- Use LocationSearchSheet for Follow Team home location (consistent with must-stop)
- Initialize DateRangePicker to show selected dates on appear
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>