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:
353
docs/plans/2026-01-12-todo-bugs-design.md
Normal file
353
docs/plans/2026-01-12-todo-bugs-design.md
Normal 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
|
||||
Reference in New Issue
Block a user