Covers: - Planning mode bugs (follow team location, must-stop filtering, date range) - UI improvements (sort options, map locking, today highlight) - Coast-to-coast filter to show top 2 by stops Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
Bug Fixes Design Document
Date: 2026-01-11 Status: Ready for Implementation Priority: High (ASAP)
Overview
This document outlines fixes for 12 bugs affecting trip planning and UI functionality. Issues are grouped by category and prioritized by impact on core functionality.
Category A: Planning Mode Bugs (7 issues)
These affect the main trip planning flow and should be fixed first.
Issue #1 & #6: Follow Team Start/End Location
Problem: Follow Team mode uses a plain text field for home location input, while Must-Stop uses LocationSearchSheet with MKLocalSearch. The text field doesn't provide proper geocoding or location suggestions.
Root Cause: TripCreationView uses different UI patterns for location input depending on context.
Files to Modify:
SportsTime/Features/Trip/Views/TripCreationView.swiftSportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift
Fix:
- Replace the text field in Follow Team mode with a button that opens
LocationSearchSheet - On selection, set both
startLocation(resolved LocationInput) andstartLocationText(display name) - Preserve
startLocationTextwhen switching to.followTeammode (currently cleared inswitchPlanningMode())
Implementation:
// In Follow Team section, replace TextField with:
Button {
cityInputType = .homeLocation // Add new case
showCityInput = true
} label: {
HStack {
Text(viewModel.startLocationText.isEmpty ? "Set Home Location" : viewModel.startLocationText)
Spacer()
Image(systemName: "magnifyingglass")
}
}
// In LocationSearchSheet callback:
case .homeLocation:
viewModel.startLocation = location
viewModel.startLocationText = location.name
Issue #2: "By Game" Shows Date Range Error
Problem: Selecting games in "By Game" mode shows "date range required" error even though the date range should be computed from selected games.
Root Cause: gameFirstDateRange computed property may return nil if no games selected yet, but the error message appears prematurely.
Files to Modify:
SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift
Fix:
- In
planTrip(), ensuregameFirstDateRangeis computed before validation - Update
formValidationMessageto show "Select at least one game" instead of date range error for gameFirst mode - Verify
effectiveStartDate/effectiveEndDateare set from selected games before planning
Verification: Lines 287-292 in planTrip() already handle this, but need to verify the error isn't coming from ScenarioBPlanner.
Issue #3: Date Range Not Showing Current Selection
Problem: Date range picker doesn't display the currently selected start/end dates.
Files to Modify:
SportsTime/Features/Trip/Views/TripCreationView.swift(DateRangePicker)
Fix:
- Verify
DateRangePickerinitializes withviewModel.startDateandviewModel.endDate - Check that
selectionStateis set to.completewhen both dates exist - Ensure visual highlighting shows the selected range on initial render
Issue #4 & #8: Must-Stop Finds Wrong Games / Shows Away Games
Problem:
- Must-stop locations are accepted but never used in the planning logic
- When must-stop is used, it should only show games where the must-stop city is the home team's city
Root Cause: ScenarioAPlanner.swift receives mustStopLocations via PlanningRequest but never filters games by it.
Files to Modify:
SportsTime/Planning/Engine/ScenarioAPlanner.swiftSportsTime/Planning/Models/PlanningModels.swift(for config)
Fix:
- Add must-stop filtering after date/region filtering in Step 2
- Filter to games where the stadium's city matches the must-stop city AND it's a home game
Implementation:
// In ScenarioAPlanner.plan(), after line 81:
// Step 2b: Filter by must-stop locations (if any)
var gamesAfterMustStop = gamesInRange
if let mustStop = request.mustStopLocation {
gamesAfterMustStop = gamesInRange.filter { game in
guard let stadium = request.stadiums[game.stadiumId] else { return false }
// Must be in must-stop city AND be a home game (stadium is home team's stadium)
let cityMatches = stadium.city.lowercased() == mustStop.name.lowercased()
// Home games only: the game's stadium is in the must-stop city
return cityMatches
}
// If must-stop filtering removed all games, fail with clear message
if gamesAfterMustStop.isEmpty {
return .failure(
PlanningFailure(
reason: .noGamesInRange,
violations: [
ConstraintViolation(
type: .mustStop,
description: "No home games found in \(mustStop.name) during selected dates",
severity: .error
)
]
)
)
}
}
Issue #5: Scenario Not Updating When Switched
Problem: Switching planning modes may not update the UI correctly.
Root Cause: planningMode property has no didSet observer to trigger dependent state updates.
Files to Modify:
SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift
Fix:
- Add
didSettoplanningModeproperty - Call
switchPlanningMode()from the observer to ensure state is reset - Alternatively, verify UI is observing
planningModechanges correctly (since@Observableshould handle this)
Implementation:
var planningMode: PlanningMode = .dateRange {
didSet {
if oldValue != planningMode {
// Reset mode-specific state
viewState = .editing
availableGames = []
}
}
}
Category B: UI/Sort/Filter Issues (4 issues)
Issue #7: Add "Most Cities" Sort to All Trips
Problem: SavedTripsListView has no sort options; trips are shown by updatedAt only.
Files to Modify:
SportsTime/Features/Home/Views/HomeView.swift(SavedTripsListView)
Fix:
- Add
@State private var sortOption: TripSortOption = .date - Add sort picker to toolbar
- Sort trips based on selected option
Implementation:
enum TripSortOption: String, CaseIterable {
case date = "Date"
case mostCities = "Most Cities"
case mostGames = "Most Games"
}
struct SavedTripsListView: View {
let trips: [SavedTrip]
@State private var sortOption: TripSortOption = .date
var sortedTrips: [SavedTrip] {
switch sortOption {
case .date:
return trips.sorted { $0.updatedAt > $1.updatedAt }
case .mostCities:
return trips.sorted { ($0.trip?.stops.count ?? 0) > ($1.trip?.stops.count ?? 0) }
case .mostGames:
return trips.sorted { ($0.trip?.totalGames ?? 0) > ($1.trip?.totalGames ?? 0) }
}
}
var body: some View {
// ... use sortedTrips instead of trips
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
Picker("Sort", selection: $sortOption) {
ForEach(TripSortOption.allCases, id: \.self) { option in
Text(option.rawValue).tag(option)
}
}
} label: {
Image(systemName: "arrow.up.arrow.down")
}
}
}
}
}
Issue #9: Coast-to-Coast Filter by Most Stops
Problem: Coast-to-coast suggested trips should only show the top 2 trips with the most stops (C2C should be massive, epic trips).
Files to Modify:
SportsTime/Features/Home/Views/HomeView.swift(suggestedTripsSection)- Or
SportsTime/Core/Services/SuggestedTripsGenerator.swift
Fix:
- Filter coast-to-coast trips to only the top 2 by stop count
- Sort by
stops.countdescending before taking top 2
Implementation:
// In suggestedTripsSection or SuggestedTripsGenerator:
var coastToCoastTrips: [SuggestedTrip] {
suggestedTripsGenerator.suggestedTrips
.filter { $0.type == .coastToCoast }
.sorted { $0.trip.stops.count > $1.trip.stops.count }
.prefix(2)
.map { $0 } // Convert ArraySlice to Array
}
Issue #11: Maps Should Not Be Movable
Problem: Maps on various screens can be panned/zoomed, but should be locked to show North America only.
Files to Modify:
SportsTime/Features/Progress/Views/ProgressMapView.swift- Any other views with maps
Fix:
- Disable map interaction using
interactionModes: [](iOS 17+) or.allowsHitTesting(false) - Set fixed region to show continental US
Implementation:
// iOS 17+ approach:
Map(initialPosition: .region(usRegion), interactionModes: []) {
// annotations
}
// Or disable hit testing:
Map(coordinateRegion: .constant(usRegion), annotationItems: stadiums) { ... }
.allowsHitTesting(false)
// Fixed US region:
let usRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 39.8283, longitude: -98.5795),
span: MKCoordinateSpan(latitudeDelta: 50, longitudeDelta: 60)
)
Issue #12: Highlight Today in Schedule View
Problem: Schedule view doesn't visually distinguish games happening today.
Files to Modify:
SportsTime/Features/Schedule/Views/ScheduleListView.swift
Fix:
- Add "TODAY" badge or background highlight for games on current date
- Use
Calendar.current.isDateInToday()to detect today's games
Implementation:
struct GameRowView: View {
let game: RichGame
var showDate: Bool = false
private var isToday: Bool {
Calendar.current.isDateInToday(game.game.dateTime)
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
if showDate {
HStack {
Text(formattedDate)
.font(.caption)
.fontWeight(.medium)
.foregroundStyle(.secondary)
if isToday {
Text("TODAY")
.font(.caption2)
.fontWeight(.bold)
.foregroundStyle(.white)
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(Color.orange)
.clipShape(Capsule())
}
}
}
// ... rest of view
}
.listRowBackground(isToday ? Color.orange.opacity(0.1) : nil)
}
}
Category C: Other Issues (1 issue)
Issue #10: Photo Import Loses Metadata
Problem: Importing photos from iPhone doesn't preserve EXIF metadata.
Investigation Needed:
- Check current photo picker implementation (
PHPickerViewControllervsUIImagePickerController) - Verify if using
UIImagecompression strips EXIF - May need to use
PHAssetdirectly to preserve metadata
Potential Fix:
// Use PHPickerConfiguration with .current to get PHAsset
let config = PHPickerConfiguration(photoLibrary: .shared())
config.selectionLimit = 1
config.preferredAssetRepresentationMode = .current // Preserves original
// Or request full asset data:
let result: PHPickerResult = ...
if let assetId = result.assetIdentifier {
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
// Work with PHAsset to preserve metadata
}
Status: Requires further investigation to identify exact implementation.
Implementation Priority
-
Critical (Core Planning):
- #4 & #8: Must-stop filtering (completely broken)
- #1 & #6: Follow Team location search
- #2: By Game date range error
-
High (UX Impact):
- #5: Scenario switching
- #3: Date range display
- #12: Today highlight
- #11: Lock maps
-
Medium (Enhancement):
- #7: Sort options
- #9: C2C filter
-
Low (Separate Investigation):
- #10: Photo metadata
Testing Checklist
- Follow Team mode: Can search and select home location via MKLocalSearch
- By Game mode: No false "date range required" error
- Date range picker: Shows currently selected dates on open
- Must-stop with Chicago: Finds Cubs/White Sox HOME games only
- Switching modes: UI updates correctly, stale state cleared
- All Trips: Can sort by Most Cities, Most Games
- Coast-to-coast: Shows only top 2 trips by stop count
- Maps: Cannot pan or zoom, fixed to North America
- Schedule: Today's games have visual indicator