Commit Graph

66 Commits

Author SHA1 Message Date
Trey t
e72da7c5a7 fix(itinerary): add city to game items for proper constraint validation
Travel constraint validation was not working because ItineraryConstraints
had no game items to validate against - games came from RichGame objects
but were never converted to ItineraryItem for constraint checking.

Changes:
- Add city parameter to ItemKind.game enum case
- Create game ItineraryItems from RichGame data in buildItineraryData()
- Update isValidTravelPosition to compare against actual game sortOrders
- Fix tests to use appropriate game sortOrder conventions

Now travel is properly constrained to appear before arrival city games
and after departure city games.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 22:46:40 -06:00
Trey t
64e54b3b11 fix: improve debounce task cancellation handling
- Add cancelPendingSync() method for explicit cleanup
- Use [weak self] capture to prevent potential retain issues
- Check Task.isCancelled before and after sleep
- Catch CancellationError from Task.sleep for immediate cancellation response
- Extract debounceInterval as constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:19:55 -06:00
Trey t
cd00384010 refactor(itinerary): replace CustomItineraryItem with ItineraryItem across codebase
- Update CKModels.swift to remove deleted type references
- Migrate LocalCustomItem to LocalItineraryItem in SavedTrip.swift
- Update AppDelegate to handle subscription removal
- Refactor AddItemSheet to create ItineraryItem with CustomInfo
- Update ItineraryTableViewController and Wrapper for new model
- Refactor TripDetailView state, methods and callbacks
- Fix TripMapView to display custom items with new model structure

This completes the migration from the legacy CustomItineraryItem/TravelDayOverride
model to the unified ItineraryItem model with ItemKind enum.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:51:32 -06:00
Trey t
af2a5cd204 chore: remove legacy itinerary models and services
Removed:
- CustomItineraryItem
- TravelDayOverride
- CustomItemService
- CustomItemSubscriptionService
- TravelOverrideService
- CustomItemRow

These are replaced by unified ItineraryItem model and service.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:24:47 -06:00
Trey t
cae24efa90 feat: add ItineraryItemService with CloudKit sync
Debounced updates (1.5s), local-first with silent retry.
Supports game, travel, and custom item kinds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:23:01 -06:00
Trey t
12c2de8a1b feat: add ItineraryConstraints with full test coverage
Validates travel positions based on game locations:
- Travel must be after ALL departure city games
- Travel must be before ALL arrival city games
- Custom items have no constraints
- Games are fixed (cannot be moved)

12 tests covering all constraint scenarios including edge cases.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:14:02 -06:00
Trey t
d14976812a feat: add unified ItineraryItem model
Replaces CustomItineraryItem and TravelDayOverride with single model.
Supports game, travel, and custom item kinds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 20:57:56 -06:00
Trey t
2a8bfeeff8 refactor(itinerary): replace anchor-based positioning with day/sortOrder
Replace complex anchor system (anchorType, anchorId, anchorDay) with
simple (day: Int, sortOrder: Double) positioning for custom items.

Changes:
- CustomItineraryItem: Remove anchor fields, add day and sortOrder
- CKModels: Add migration fallback from old CloudKit fields
- ItineraryTableViewController: Add calculateSortOrder() for midpoint insertion
- TripDetailView: Simplify callbacks, itinerarySections, and routeWaypoints
- AddItemSheet: Take simple day parameter instead of anchor
- SavedTrip: Update LocalCustomItem SwiftData model

Benefits:
- Items freely movable via drag-and-drop
- Route waypoints follow exact visual order
- Simpler mental model: position = (day, sortOrder)
- Midpoint insertion allows unlimited reordering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 09:47:11 -06:00
Trey t
8df33a5614 WIP: map route updates and custom item drag/drop fixes (broken)
- Fixed map header not updating in ItineraryTableViewWrapper using Coordinator pattern
- Added routeVersion UUID to force Map re-render when routes change
- Fixed determineAnchor to scan backwards for correct anchor context
- Added location support to CustomItineraryItem (lat/lng/address)
- Added MapKit place search to AddItemSheet
- Added extensive debug logging for route waypoint calculation

Known issues:
- Custom items still not routing correctly after drag/drop
- Anchor type determination may still have bugs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 00:00:57 -06:00
Trey t
bf9619a207 feat(itinerary): add draggable travel day positioning with CloudKit persistence
- Add TravelDayOverride model for storing user-customized travel day positions
- Add TravelOverrideService for CloudKit CRUD operations on travel overrides
- Add CKTravelDayOverride CloudKit model wrapper
- Refactor itinerarySections to validate travel day bounds (must be after last game in departure city)
- Travel segments can now be dragged to different days within valid range
- Persist travel day overrides to CloudKit for cross-device sync

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 19:00:52 -06:00
Trey t
0130141f8a Merge branch 'custom_ish' 2026-01-16 15:38:28 -06:00
Trey t
8162b4a029 refactor(tests): TDD rewrite of all unit tests with spec documentation
Complete rewrite of unit test suite using TDD methodology:

Planning Engine Tests:
- GameDAGRouterTests: Beam search, anchor games, transitions
- ItineraryBuilderTests: Stop connection, validators, EV enrichment
- RouteFiltersTests: Region, time window, scoring filters
- ScenarioA/B/C/D PlannerTests: All planning scenarios
- TravelEstimatorTests: Distance, duration, travel days
- TripPlanningEngineTests: Orchestration, caching, preferences

Domain Model Tests:
- AchievementDefinitionsTests, AnySportTests, DivisionTests
- GameTests, ProgressTests, RegionTests, StadiumTests
- TeamTests, TravelSegmentTests, TripTests, TripPollTests
- TripPreferencesTests, TripStopTests, SportTests

Service Tests:
- FreeScoreAPITests, RouteDescriptionGeneratorTests
- SuggestedTripsGeneratorTests

Export Tests:
- ShareableContentTests (card types, themes, dimensions)

Bug fixes discovered through TDD:
- ShareCardDimensions: mapSnapshotSize exceeded available width (960x480)
- ScenarioBPlanner: Added anchor game validation filter

All tests include:
- Specification tests (expected behavior)
- Invariant tests (properties that must always hold)
- Edge case tests (boundary conditions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 14:07:41 -06:00
Trey t
495ef88303 feat(itinerary): add custom itinerary items with drag-to-reorder
- 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>
2026-01-16 00:31:44 -06:00
Trey t
57eab22746 fix(polls): fetch existing CloudKit record before updating vote
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>
2026-01-14 23:59:40 -06:00
Trey t
d34be05d61 feat(design): add Classic Animated home screen style
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>
2026-01-14 13:23:13 -06:00
Trey t
f7f1bbd87a feat(settings): add dark/light/system appearance mode toggle
- 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>
2026-01-14 13:08:38 -06:00
Trey t
1e26cfebc8 fix: standardize trip name display with arrow separators app-wide
- 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>
2026-01-14 11:36:13 -06:00
Trey t
d034ee8612 fix: multiple bug fixes and improvements
- 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>
2026-01-14 09:35:18 -06:00
Trey t
16514a40ac Merge branch 'feature/sync-reliability' 2026-01-13 22:09:26 -06:00
Trey t
2f9546f792 feat(polls): add DeepLinkHandler and test mock helpers
- 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>
2026-01-13 22:03:22 -06:00
Trey t
13385b6562 feat(polls): implement group trip polling MVP
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>
2026-01-13 21:54:42 -06:00
Trey t
5686af262f feat(sync): add pagination, cancellation, and network restoration
- 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>
2026-01-13 19:18:55 -06:00
Trey t
b99c25d8f6 feat(sync): add background task system for nightly CloudKit sync
Add BGTaskScheduler-based background sync for keeping local data fresh:

- BackgroundSyncManager: New singleton managing background tasks
  - BGAppRefreshTask for periodic CloudKit sync (system-determined frequency)
  - BGProcessingTask for overnight sync + database cleanup (2 AM)
  - Auto-archives games older than 1 year during cleanup

- Info.plist: Added BGTaskSchedulerPermittedIdentifiers
  - com.sportstime.app.refresh (periodic sync)
  - com.sportstime.app.db-cleanup (overnight processing)

- SportsTimeApp: Integrated background task lifecycle
  - Register tasks in init() (required before app finishes launching)
  - Schedule tasks after bootstrap and when app enters background

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 18:36:35 -06:00
Trey t
f180e5bfed feat(sync): add CloudKit sync for dynamic sports
- 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>
2026-01-13 18:27:56 -06:00
Trey t
dc278085de feat(domain): add DynamicSport model for CloudKit-defined sports
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>
2026-01-13 16:22:02 -06:00
Trey t
e781eaa17c feat(domain): add AnySport conformance to Sport enum
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>
2026-01-13 16:17:04 -06:00
Trey t
3b1add024f feat(domain): add AnySport protocol for unified sport handling
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>
2026-01-13 16:11:53 -06:00
Trey t
56869ce479 feat(ui): add 23 home screen design variants with picker
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>
2026-01-13 14:44:30 -06:00
Trey t
04b62f147e fix: resolve compiler warnings across codebase
- 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>
2026-01-13 13:16:40 -06:00
Trey t
22772fa57f feat(store): add In-App Purchase system with Pro subscription
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>
2026-01-13 11:41:40 -06:00
Trey t
c0f1645434 feat(ui): replace loading indicators with Apple-style LoadingSpinner
- 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>
2026-01-12 22:43:33 -06:00
Trey t
0524284ab8 feat: add planning tips and grouped trip options sorting
- 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>
2026-01-12 21:49:04 -06:00
Trey t
d39e8cff30 fix: use dynamic team lookup in scraper for stadium renames
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>
2026-01-12 20:36:36 -06:00
Trey t
37a1347ce3 perf: lazy hierarchical loading for game picker
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>
2026-01-12 20:19:46 -06:00
Trey t
64c64093c4 feat: add timezone support for stadium-local game times
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>
2026-01-12 18:58:35 -06:00
Trey t
b14f72a0fb perf: move trip generation off main actor
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>
2026-01-12 18:27:49 -06:00
Trey t
3978429716 feat: complete delta sync implementation - add allGames, update callers
- 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>
2026-01-12 11:04:52 -06:00
Trey t
b3ad386d2b refactor: rename fetch methods to filter, add allGames
- fetchGames → filterGames (clarifies local SwiftData query)
- fetchRichGames → filterRichGames
- Add allGames(for:) method for unfiltered game access

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:51:26 -06:00
Trey t
b89c0d58e2 feat(sync): update CanonicalSyncService to use delta sync
- syncStadiums now passes lastSync to CloudKit fetch
- syncTeams simplified to single CloudKit call (not per-sport loop)
- syncGames removes 6-month date range, uses modificationDate delta

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:49:53 -06:00
Trey t
3bb903ab09 feat(sync): update CloudKit sync methods to use modificationDate delta
- fetchStadiumsForSync now accepts since: Date? parameter
- fetchTeamsForSync changed from per-sport to all teams with delta sync
- fetchGamesForSync uses modificationDate instead of game dateTime

When lastSync is nil, fetches all records (first sync).
When lastSync has value, fetches only modified records (delta sync).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 10:48:34 -06:00
Trey t
1703ca5b0f refactor: change domain model IDs from UUID to String canonical IDs
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>
2026-01-12 09:24:33 -06:00
Trey t
5c13650742 fix: resolve specificStadium achievement ID mismatch
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>
2026-01-11 22:22:29 -06:00
Trey t
dcd5edb229 feat: add sliding window trip generation for By Games mode
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>
2026-01-11 19:19:46 -06:00
Trey t
81095a8170 fix: resolve 4 UI/planning bugs from issue tracker
- 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>
2026-01-11 18:46:40 -06:00
Trey t
f7faec01b1 feat: add Follow Team Mode (Scenario D) for road trip planning
Adds a new planning mode that lets users follow a team's schedule
(home + away games) and builds multi-city routes accordingly.

Key changes:
- New ScenarioDPlanner with team filtering and route generation
- Team picker UI with sport grouping and search
- Fix TravelEstimator 5-day limit (was 2-day) for cross-country routes
- Fix DateInterval end boundary to include games on last day
- Comprehensive test suite covering edge cases:
  - Multi-city routes with adequate/insufficient time
  - Optimal game selection per city for feasibility
  - 5-day driving segment limits
  - Multiple driver scenarios

Enables trips like Houston → Chicago → Anaheim following the Astros.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 12:42:43 -06:00
Trey t
475f444288 refactor: extract reusable SportSelectorGrid component
Create unified sport selector grid used across Home (Quick Start),
Trip Creation, and Progress views. Removes duplicate button implementations
and ensures consistent grid layout with centered bottom row.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:38:10 -06:00
Trey t
2d48f1411a feat: implement Dynamic Type with Apple text styles
Replace all custom Theme.FontSize values and hardcoded font sizes with
Apple's built-in text styles (.largeTitle, .title2, .headline, .body,
.subheadline, .caption, .caption2) to support accessibility scaling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 10:23:16 -06:00
Trey t
c9e5bd9909 chore: remove college basketball (CBB) from iOS app
Remove CBB (~5,000+ games per season) to reduce complexity.

Changes:
- Remove .cbb enum case from Sport
- Remove CBB theme color (cbbMint)
- Update documentation to reflect 7 supported leagues

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:44:35 -06:00
Trey t
55c6d6e5e8 feat(planning): add trip filtering and fix departure date logic
- Add Trip.status property for status tracking
- Add RouteFilters trip list methods (filterBySport, filterByDateRange, filterByStatus, applyFilters)
- Add TravelEstimator max driving hours validation
- Fix ScenarioA/B departureDate to use last game day (not day after)
- Update GameDAGRouter comments for buffer logic

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:18:52 -06:00
Trey t
e8e47e0e2e fix(theme): remove blue hue from monochrome dark mode
Replace Tailwind CSS gray palette colors (which have blue tint) with
pure grayscale values (R=G=B) for the monochrome theme.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 17:44:33 -06:00