Files
Sportstime/docs/plans/2026-01-12-todo-bugs-design.md
Trey t aa0bc4def8 docs: add bug fixes design for 9 TODO issues
Covers performance (launch freeze, list lag), UI polish (animations,
missing location info, timezone display), and scraper alias fix.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 18:06:27 -06:00

9.6 KiB

TODO Bugs Design Document

Date: 2026-01-12 Status: Ready for Implementation

Overview

This document outlines fixes for 9 bugs from TO-DOS.md affecting performance, UI polish, and the data scraper.


Category A: Performance Issues (3 bugs)

Problem: App freezes on launch while generating suggested trips.

Root Cause: SuggestedTripsGenerator is @MainActor, so all TripPlanningEngine work (including TSP solving) blocks the main thread.

Files to Modify:

  • SportsTime/Core/Services/SuggestedTripsGenerator.swift
  • SportsTime/Features/Home/Views/HomeView.swift

Fix:

  1. Move heavy computation to background using Task.detached
  2. Show skeleton/placeholder UI immediately
  3. Keep only UI state updates on @MainActor

Implementation:

func generateTrips() async {
    guard !isLoading else { return }
    isLoading = true
    loadingMessage = await loadingTextGenerator.generateMessage()

    // Move heavy work off main actor
    let result = await Task.detached(priority: .userInitiated) { [dataProvider, planningEngine] in
        // Ensure data is loaded
        let teams = await dataProvider.teams
        let stadiums = await dataProvider.stadiums
        let games = try? await dataProvider.filterGames(...)

        // All planning engine work here
        // Return generated trips
    }.value

    // Back on MainActor for UI update
    self.suggestedTrips = result
    isLoading = false
}

Bug 2: Lag on Big Data Sets ("By Game")

Problem: Schedule list view with 1000+ games causes UI lag.

Root Cause: SwiftUI List struggles with large datasets. Each GameRowView performs:

  • Rich game lookups
  • Date formatting on every render
  • No virtualization

Files to Modify:

  • SportsTime/Features/Schedule/Views/ScheduleListView.swift
  • SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift

Fix:

  1. Implement pagination with visible window
  2. Cache formatted dates and lookups
  3. Use LazyVStack with explicit .id() for efficient diffing

Implementation:

struct ScheduleListView: View {
    @State private var visibleRange: Range<Int> = 0..<50
    private let pageSize = 50

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 0) {
                ForEach(visibleGames) { game in
                    GameRowView(game: game, showDate: true)
                        .id(game.id)
                        .onAppear {
                            loadMoreIfNeeded(game)
                        }
                }
            }
        }
    }

    private func loadMoreIfNeeded(_ game: RichGame) {
        // Load next page when approaching end
    }
}

Bug 4: Select Games View Laggy and Two Different Colors

Problem: Game selection view has performance issues and visual inconsistency.

Root Cause: Same as Bug 2 - large list without virtualization. Color issue likely from inconsistent background modifiers.

Files to Modify:

  • SportsTime/Features/Trip/Views/TripCreationView.swift (game selection section)

Fix:

  1. Apply same pagination fix as Bug 2
  2. Audit background colors - use consistent .background() modifier
  3. Ensure selected/unselected states use same base colors

Category B: Missing Info & Animation Issues (3 bugs)

Bug 3: "By Games" Missing Location Info

Problem: Games grouped by date don't show stadium/city.

Root Cause: GameRowView designed for team-grouped context where location is implicit.

Files to Modify:

  • SportsTime/Features/Schedule/Views/GameRowView.swift
  • SportsTime/Features/Schedule/Views/ScheduleListView.swift

Fix:

  1. Add showLocation: Bool parameter to GameRowView
  2. Pass true when groupBy == .byGame

Implementation:

struct GameRowView: View {
    let game: RichGame
    var showDate: Bool = false
    var showLocation: Bool = false  // New parameter

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            // Existing matchup content...

            if showLocation {
                Text("\(game.stadium.name), \(game.stadium.city)")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
    }
}

// In ScheduleListView:
GameRowView(game: game, showDate: true, showLocation: groupBy == .byGame)

Bug 5: Weird Animation When Selecting a Game

Problem: Unexpected animation when tapping a game in selection view.

Root Cause: SwiftUI animation conflict between List selection and state update.

Files to Modify:

  • SportsTime/Features/Trip/Views/TripCreationView.swift

Fix:

  1. Wrap selection in transaction to disable implicit animation
  2. Or use .animation(nil, value:) to suppress

Implementation:

// Option A: Disable animation on selection
Button {
    var transaction = Transaction()
    transaction.disablesAnimations = true
    withTransaction(transaction) {
        viewModel.toggleGameSelection(game)
    }
} label: {
    GameSelectionRow(game: game, isSelected: isSelected)
}
.buttonStyle(.plain)

// Option B: On the selection indicator
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
    .animation(nil, value: isSelected)

Bug 6: Trip Game Times Should Be UTC to Local

Problem: Game times display in device timezone, not stadium local time.

Root Cause: DateFormatter without explicit timezone uses device default.

Files to Modify:

  • SportsTime/Core/Models/Domain/Stadium.swift
  • SportsTime/Core/Models/Domain/Game.swift

Fix:

  1. Add timeZoneIdentifier: String to Stadium (or derive from coordinates)
  2. Create localGameTime computed property on RichGame
  3. Display with timezone abbreviation: "7:05 PM ET"

Implementation:

// Stadium.swift - add timezone
struct Stadium: Identifiable, Codable, Hashable {
    // ... existing properties
    let timeZoneIdentifier: String?  // e.g., "America/New_York"

    var timeZone: TimeZone? {
        timeZoneIdentifier.flatMap { TimeZone(identifier: $0) }
    }
}

// RichGame extension
extension RichGame {
    var localGameTime: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "h:mm a z"
        formatter.timeZone = stadium.timeZone ?? .current
        return formatter.string(from: game.dateTime)
    }
}

Note: Requires populating timeZoneIdentifier for all stadiums in:

  • Bundled JSON bootstrap data
  • CloudKit stadium records
  • Scraper stadium resolver

Category C: UI Polish Issues (2 bugs)

Bug 7: Pace Capsule Animation Looks Off

Problem: "packed"/"moderate"/"relaxed" label has glitchy animation.

Root Cause: Implicit animation conflict or text morphing transition.

Files to Modify:

  • SportsTime/Features/Home/Views/SavedTripsListView.swift (or wherever pace capsule is)

Fix:

  1. Use .contentTransition(.identity) to prevent text morphing
  2. Disable animation on the capsule when trip data changes

Implementation:

Text(trip.paceLabel)
    .font(.caption2)
    .fontWeight(.medium)
    .padding(.horizontal, 8)
    .padding(.vertical, 4)
    .background(paceColor.opacity(0.2))
    .foregroundStyle(paceColor)
    .clipShape(Capsule())
    .contentTransition(.identity)
    .animation(nil, value: trip.id)

Bug 8: Remove "My Trips" Title from Tab View

Problem: Redundant title appearing in the My Trips tab.

Files to Modify:

  • SportsTime/Features/Home/Views/HomeView.swift (or main TabView)

Fix:

  1. Remove .navigationTitle() from SavedTripsListView root
  2. Or hide navigation bar if title is inherited

Implementation:

// In TabView:
NavigationStack {
    SavedTripsListView(trips: savedTrips)
        .toolbar(.hidden, for: .navigationBar)
        // Or just remove any .navigationTitle("My Trips")
}
.tabItem {
    Label("My Trips", systemImage: "suitcase")
}

Category D: Scraper Issue (1 bug)

Bug 9: Frost Bank Center Team Mapping Missing

Problem: NBA scraper can't resolve Frost Bank Center (Spurs home).

Root Cause: Scraper likely returns old name "AT&T Center" which doesn't match. Stadium exists in resolver but alias is missing.

Files to Modify:

  • Scripts/stadium_aliases.json

Fix:

  1. Add alias for "AT&T Center" → "stadium_nba_frost_bank_center"
  2. Check scraper logs for exact unresolved name

Implementation:

{
  "canonical_id": "stadium_nba_frost_bank_center",
  "alias_name": "AT&T Center",
  "sport": "nba",
  "valid_from": "2002-10-18",
  "valid_to": "2024-07-01",
  "notes": "Renamed to Frost Bank Center in 2024"
}

Investigation: Run NBA scraper with verbose logging to confirm the exact stadium name being returned.


Implementation Priority

  1. High Impact (Performance):

    • Bug 1: Launch responsiveness (blocking UX)
    • Bug 2 & 4: List performance (core feature usability)
  2. Medium Impact (UX Polish):

    • Bug 3: Location info (missing data)
    • Bug 6: Timezone display (incorrect data)
    • Bug 5: Selection animation (visual glitch)
  3. Low Impact (Minor Polish):

    • Bug 7: Pace capsule animation
    • Bug 8: Remove redundant title
    • Bug 9: Scraper alias (data pipeline)

Testing Checklist

  • App launches with responsive UI, trips load in background
  • "By Game" view scrolls smoothly with 1000+ games
  • "By Game" shows stadium and city for each game
  • Game selection has no weird animation on tap
  • Trip game times show stadium local time with timezone (e.g., "7:05 PM ET")
  • Pace capsule has no animation glitch
  • "My Trips" tab has no redundant title
  • NBA scraper resolves Spurs games to Frost Bank Center