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>
This commit is contained in:
Trey t
2026-01-12 18:06:27 -06:00
parent e70b9faab8
commit aa0bc4def8

View File

@@ -0,0 +1,353 @@
# 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