# 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)

### Bug 1: UI Unresponsive on Launch Until Featured Loads

**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:**
```swift
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:**
```swift
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:**
```swift
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:**
```swift
// 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:**
```swift
// 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:**
```swift
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:**
```swift
// 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:**
```json
{
  "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
