Files
Sportstime/docs/plans/2026-01-11-bug-fixes-design.md
Trey t c2f52aaccc docs: add bug fixes design for 12 planning and UI issues
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>
2026-01-11 17:55:08 -06:00

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.swift
  • SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift

Fix:

  1. Replace the text field in Follow Team mode with a button that opens LocationSearchSheet
  2. On selection, set both startLocation (resolved LocationInput) and startLocationText (display name)
  3. Preserve startLocationText when switching to .followTeam mode (currently cleared in switchPlanningMode())

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:

  1. In planTrip(), ensure gameFirstDateRange is computed before validation
  2. Update formValidationMessage to show "Select at least one game" instead of date range error for gameFirst mode
  3. Verify effectiveStartDate/effectiveEndDate are 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:

  1. Verify DateRangePicker initializes with viewModel.startDate and viewModel.endDate
  2. Check that selectionState is set to .complete when both dates exist
  3. Ensure visual highlighting shows the selected range on initial render

Issue #4 & #8: Must-Stop Finds Wrong Games / Shows Away Games

Problem:

  1. Must-stop locations are accepted but never used in the planning logic
  2. 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.swift
  • SportsTime/Planning/Models/PlanningModels.swift (for config)

Fix:

  1. Add must-stop filtering after date/region filtering in Step 2
  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:

  1. Add didSet to planningMode property
  2. Call switchPlanningMode() from the observer to ensure state is reset
  3. Alternatively, verify UI is observing planningMode changes correctly (since @Observable should 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:

  1. Add @State private var sortOption: TripSortOption = .date
  2. Add sort picker to toolbar
  3. 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:

  1. Filter coast-to-coast trips to only the top 2 by stop count
  2. Sort by stops.count descending 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:

  1. Disable map interaction using interactionModes: [] (iOS 17+) or .allowsHitTesting(false)
  2. 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:

  1. Add "TODAY" badge or background highlight for games on current date
  2. 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:

  1. Check current photo picker implementation (PHPickerViewController vs UIImagePickerController)
  2. Verify if using UIImage compression strips EXIF
  3. May need to use PHAsset directly 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

  1. Critical (Core Planning):

    • #4 & #8: Must-stop filtering (completely broken)
    • #1 & #6: Follow Team location search
    • #2: By Game date range error
  2. High (UX Impact):

    • #5: Scenario switching
    • #3: Date range display
    • #12: Today highlight
    • #11: Lock maps
  3. Medium (Enhancement):

    • #7: Sort options
    • #9: C2C filter
  4. 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