Commit Graph

134 Commits

Author SHA1 Message Date
Trey t
11adfc10dd wip 2026-01-19 23:53:37 -06:00
Trey t
19dd1791f1 wip 2026-01-19 23:21:33 -06:00
Trey t
1355c94236 feat(home): restore planning tips and add map buttons to itinerary rows
- Create shared TipsSection component for displaying planning tips
- Add TipsSection to all 22 home content variants
- Fix displayedTips population with onAppear in HomeView
- Add map buttons to GameRowCompact (opens stadium in Apple Maps)
- Add map buttons to TravelRowView (opens driving directions)
- Add map buttons to CustomItemRowView (opens location when GPS available)
- Add AppleMapsLauncher.openLocation() and openDirections() methods

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 13:02:17 -06:00
Trey t
0e7fcb65fc fix(pdf): include all trip days in PDF export
Fixed PDF export missing the last day when games occur on departure date.

Root cause: Trip.itineraryDays() calculated lastActivityDate as departure - 1,
assuming departure is always after the last activity. When games happen ON the
departure date, that day was skipped.

Fix: Check if the last stop has games. If so, include the departure date in
the itinerary loop.

Also includes:
- buildCompleteItineraryItems() to merge games, travel, and custom items
- Magazine-style PDF layout with sport-specific accent colors
- Proper iteration over all trip days in PDF generator

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 11:47:25 -06:00
Trey t
239d22a872 feat(trip): add Open in Apple Maps button to trip detail map
- Add floating action button (bottom-right) on trip map
- Create AppleMapsLauncher service to handle route opening
- Collect all routable stops (trip stops + custom items with coords)
- Handle >16 waypoints with alert offering to open in parts
- Silently skip stops without coordinates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:59:53 -06:00
Trey t
1b0abe2cc1 feat(trip): redesign add custom item UI with polished visuals
Replace confusing dual-mode AddItemSheet with streamlined QuickAddItemSheet:
- Single flow with optional location (vs Search/Custom toggle)
- Card-based layout with shadows, borders, and theme colors
- Enhanced CategoryPicker with emoji circles and press animations
- Separate PlaceSearchSheet for focused location search
- Improved header button with capsule style and accessibility labels

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 10:49:13 -06:00
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
72447c61fe refactor(itinerary): extract reordering logic into pure functions
Extract all itinerary reordering logic from ItineraryTableViewController into
ItineraryReorderingLogic.swift for testability. Key changes:

- Add flattenDays, dayNumber, travelRow, simulateMove pure functions
- Add calculateSortOrder with proper region classification (before/after games)
- Add computeValidDestinationRowsProposed with simulation+validation pattern
- Add coordinate space conversion helpers (proposedToOriginal, originalToProposed)
- Fix DragZones coordinate space mismatch (was mixing proposed/original indices)
- Add comprehensive documentation of coordinate space conventions

Test coverage includes:
- Row flattening order and semantic travel model
- Sort order calculation for before/after games regions
- Travel constraints validation
- DragZones coordinate space correctness
- Coordinate conversion helpers
- Edge cases (empty days, multi-day trips)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 20:04:52 -06:00
Trey t
143b364553 wip 2026-01-18 12:32:58 -06:00
Trey t
cd1666e7d1 not working, claude is ass 2026-01-17 23:23:22 -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
e79f29d9c7 feat(export): respect custom itinerary order in PDF export
Update PDFGenerator to accept optional ItineraryItem array and render
items in user-specified sortOrder within each day. Adds support for:
- Custom items with icon, title, time, and address
- Travel segments from ItineraryItem (not just TravelSegment)
- Fallback to derived order when no itinerary items provided

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:07:06 -06:00
Trey t
9c40721af0 feat(itinerary): add constraint-aware drag and drop with visual feedback
- Add constraint validation during drag using ItineraryConstraints
- Calculate invalid zones and barrier games when drag starts
- Apply visual dimming (alpha 0.3) to invalid drop zones during drag
- Highlight barrier games with gold border when dragging travel segments
- Block invalid drops using ItineraryConstraints.isValidPosition validation
- Add haptic feedback for drag interactions:
  - Medium impact on pickup
  - Light impact when entering valid zone
  - Warning notification when entering invalid zone
  - Soft impact on drop

The drag state is tracked via draggingItem, invalidRowIndices, and
barrierGameIds properties. Visual feedback is applied and removed
via applyDragVisualFeedback/removeDragVisualFeedback methods.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:59:57 -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
b008af1c71 refactor(itinerary): consolidate duplicate optional unwrapping in CustomItemRow 2026-01-17 21:34:18 -06:00
Trey t
3a4b9f23e8 fix(itinerary): add empty state and single-line date format to DayHeaderRow
- Add isEmpty parameter to show "No items yet, tap + to add" text
- Change date format to single line: "Day 1 - Friday, January 17"
- Update preview to test both empty and non-empty states

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:32:18 -06:00
Trey t
7700104597 feat(itinerary): add row views for itinerary items
Add specialized row components for the new unified itinerary system:
- DayHeaderRow: Day number, date display, and add item button
- GameItemRow: Prominent card with sport color bar for games
- TravelItemRow: Gold-styled travel segments with drag handle
- CustomItemRow: Minimal custom items with icon, title, and optional time

All views follow existing Theme patterns and use SportColorBar for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 21:28:11 -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
c658d5f9f4 refactor(itinerary): embed Add button in day header row
Merge Add button into DaySectionHeaderView to prevent items from being
dragged between the day header and Add button. The Add button now uses
a SwiftUI Button with its own tap handler instead of row selection.

Changes:
- Remove .addButton case from ItineraryRowItem enum
- Update DaySectionHeaderView to include Add button on the right
- Pass onAddTapped callback through configureDayHeaderCell
- Remove AddButtonRowView (no longer needed)
- Update documentation to reflect new row structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 10:13:03 -06:00
Trey t
f84addb39d feat(itinerary): reorder Add button after day header + comprehensive docs
- Move Add button to appear immediately after Day header (before games)
- Split games out of dayHeader into separate row for correct ordering
- Add 600+ lines of inline documentation to ItineraryTableViewController
- Document architecture decisions, data flow, constraints, and algorithms
- Add function-level comments explaining drag/drop, sortOrder calculation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 10:07:37 -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
43501b6ac1 feat(itinerary): add UITableView-based itinerary with unified scrolling
- Replace SwiftUI drag-drop with native UITableViewController for fluid reordering
- Add ItineraryTableViewController with native cell reordering and validation
- Add ItineraryTableViewWrapper for SwiftUI integration with header support
- Fix infinite layout loop by tracking header adjustment state
- Map and stats now scroll as table header with itinerary content
- Travel segments constrained to valid day ranges during drag
- One Add button per day (after game > after travel > rest day)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 22:35:27 -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
29942eee57 fix(polls): prevent jarring reload when switching to My Trips tab
- Only load polls once on initial view appearance, not every tab switch
- Only show spinner when there's no existing data (first load)
- Subsequent refreshes update content in place without showing spinner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 15:42:25 -06:00
Trey t
0130141f8a Merge branch 'custom_ish' 2026-01-16 15:38:28 -06:00
Trey t
aca394cefa fix(itinerary): improve drag-drop reordering with stable anchors and visual feedback
- 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>
2026-01-16 15:35:09 -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
00a5e4ef0e fix(planning): enforce daily driving limit for same-day games and group itinerary by city
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>
2026-01-15 09:42:08 -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
aa34c6585a feat(wizard): add mode-specific trip wizard inputs
- 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>
2026-01-14 22:21:57 -06:00
Trey t
1301442604 fix(ui): improve SuggestedTripCard padding and height
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>
2026-01-14 13:28:20 -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
3d4952e5ff feat(ui): add sport backgrounds to share cards, achievement filtering, and wizard validation
- 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>
2026-01-14 12:02:57 -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
2ad458bffd refactor: TripDetailView loads games on demand, improve poll UI
- 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>
2026-01-14 11:31:05 -06:00
Trey t
b5aea31b1a refactor: remove legacy trip creation flow, extract shared components
- 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>
2026-01-14 11:02:39 -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
fe36f99bca feat(sharing): implement unified sharing system for social media
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>
2026-01-14 08:54:37 -06:00
Trey t
16514a40ac Merge branch 'feature/sync-reliability' 2026-01-13 22:09:26 -06:00
Trey t
573c57aead random ish 2026-01-13 22:07:20 -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
136c356384 Merge branch 'feature/group-trip-polling' into group_voting
# Conflicts:
#	SportsTime/Core/Store/StoreManager.swift
2026-01-13 21:55:27 -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
e66f8056c8 fix(wizard): enable Plan My Trip button with default values
- 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>
2026-01-13 21:49:58 -06:00