docs: update planning documents and todos
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
799
docs/plans/2026-01-12-todo-bugs-implementation.md
Normal file
799
docs/plans/2026-01-12-todo-bugs-implementation.md
Normal file
@@ -0,0 +1,799 @@
|
||||
# TODO Bug Fixes Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Fix 9 bugs affecting app performance, UI polish, and data pipeline.
|
||||
|
||||
**Architecture:** Refactor `SuggestedTripsGenerator` to run heavy work off main actor; add pagination to large lists; fix UI display issues and animations; add stadium timezone support; add scraper alias.
|
||||
|
||||
**Tech Stack:** Swift, SwiftUI, @Observable, async/await, Python (scraper)
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Fix Launch Freeze (Bug 1)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Services/SuggestedTripsGenerator.swift`
|
||||
|
||||
**Step 1: Identify the blocking code**
|
||||
|
||||
The entire `generateTrips()` method runs on `@MainActor`. We need to move heavy computation to a background task while keeping UI updates on main.
|
||||
|
||||
**Step 2: Create a non-isolated helper struct for planning**
|
||||
|
||||
Add this struct above `SuggestedTripsGenerator`:
|
||||
|
||||
```swift
|
||||
// Non-isolated helper for background trip generation
|
||||
private struct TripGenerationHelper: Sendable {
|
||||
let stadiums: [Stadium]
|
||||
let teams: [Team]
|
||||
let games: [Game]
|
||||
|
||||
func generateRegionalTrips(
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
) -> [SuggestedTrip] {
|
||||
let planningEngine = TripPlanningEngine()
|
||||
let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
|
||||
let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }
|
||||
|
||||
var generatedTrips: [SuggestedTrip] = []
|
||||
|
||||
// Regional trips (copy existing logic from generateTrips)
|
||||
for region in [Region.east, Region.central, Region.west] {
|
||||
let regionStadiumIds = Set(
|
||||
stadiums
|
||||
.filter { $0.region == region }
|
||||
.map { $0.id }
|
||||
)
|
||||
let regionGames = games.filter { regionStadiumIds.contains($0.stadiumId) }
|
||||
guard !regionGames.isEmpty else { continue }
|
||||
|
||||
// Generate trips using existing private methods (will need to move them)
|
||||
}
|
||||
|
||||
return generatedTrips
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Refactor generateTrips() to use Task.detached**
|
||||
|
||||
Replace the heavy computation section in `generateTrips()`:
|
||||
|
||||
```swift
|
||||
func generateTrips() async {
|
||||
guard !isLoading else { return }
|
||||
|
||||
isLoading = true
|
||||
error = nil
|
||||
suggestedTrips = []
|
||||
loadingMessage = await loadingTextGenerator.generateMessage()
|
||||
|
||||
// Ensure data is loaded
|
||||
if dataProvider.teams.isEmpty {
|
||||
await dataProvider.loadInitialData()
|
||||
}
|
||||
|
||||
guard !dataProvider.stadiums.isEmpty else {
|
||||
error = "Unable to load stadium data"
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
let calendar = Calendar.current
|
||||
let today = Date()
|
||||
guard let startDate = calendar.date(byAdding: .weekOfYear, value: 4, to: today),
|
||||
let endDate = calendar.date(byAdding: .weekOfYear, value: 8, to: today) else {
|
||||
error = "Failed to calculate date range"
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let allSports = Set(Sport.supported)
|
||||
let games = try await dataProvider.filterGames(
|
||||
sports: allSports,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
)
|
||||
|
||||
guard !games.isEmpty else {
|
||||
error = "No games found in the next 4-8 weeks"
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
// Capture data for background work
|
||||
let stadiums = dataProvider.stadiums
|
||||
let teams = dataProvider.teams
|
||||
|
||||
// Heavy computation off main actor
|
||||
let generatedTrips = await Task.detached(priority: .userInitiated) {
|
||||
self.generateTripsInBackground(
|
||||
games: games,
|
||||
stadiums: stadiums,
|
||||
teams: teams,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
)
|
||||
}.value
|
||||
|
||||
suggestedTrips = generatedTrips
|
||||
} catch {
|
||||
self.error = "Failed to generate trips: \(error.localizedDescription)"
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
// New nonisolated method for background work
|
||||
nonisolated private func generateTripsInBackground(
|
||||
games: [Game],
|
||||
stadiums: [Stadium],
|
||||
teams: [Team],
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
) -> [SuggestedTrip] {
|
||||
let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
|
||||
let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }
|
||||
|
||||
var generatedTrips: [SuggestedTrip] = []
|
||||
|
||||
// Copy existing regional trip generation logic here...
|
||||
// (Move the for loop from the original generateTrips)
|
||||
|
||||
return generatedTrips
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Move helper methods to be nonisolated**
|
||||
|
||||
Mark these methods as `nonisolated`:
|
||||
- `generateRegionalTrip`
|
||||
- `generateCrossCountryTrip`
|
||||
- `buildRichGames`
|
||||
- `buildCoastToCoastRoute`
|
||||
- `buildTripStop`
|
||||
- `buildTravelSegments`
|
||||
- `convertToTrip`
|
||||
- `generateTripName`
|
||||
- `buildCorridorTrip`
|
||||
- `haversineDistance`
|
||||
- `validateNoSameDayConflicts`
|
||||
|
||||
**Step 5: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
Expected: Build succeeds
|
||||
|
||||
**Step 6: Manual test**
|
||||
|
||||
Launch app in simulator. UI should be responsive immediately while "Suggested Trips" section shows loading state.
|
||||
|
||||
**Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Services/SuggestedTripsGenerator.swift
|
||||
git commit -m "perf: move trip generation off main actor
|
||||
|
||||
Fixes launch freeze by running TripPlanningEngine in background task.
|
||||
UI now responsive immediately while trips generate."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Add Location Info to "By Game" View (Bug 3)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Schedule/Views/ScheduleListView.swift`
|
||||
|
||||
**Step 1: Find GameRowView and add showLocation parameter**
|
||||
|
||||
Locate `GameRowView` in the file (or it may be in a separate file). Add parameter:
|
||||
|
||||
```swift
|
||||
struct GameRowView: View {
|
||||
let game: RichGame
|
||||
var showDate: Bool = false
|
||||
var showLocation: Bool = false // Add this
|
||||
```
|
||||
|
||||
**Step 2: Add location display in the view body**
|
||||
|
||||
After the existing content, add:
|
||||
|
||||
```swift
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
// ... existing matchup content
|
||||
|
||||
if showLocation {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.tertiary)
|
||||
Text("\(game.stadium.name), \(game.stadium.city)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Pass showLocation when groupBy is byGame**
|
||||
|
||||
Find where `GameRowView` is called and update:
|
||||
|
||||
```swift
|
||||
// When rendering games in "by game" mode:
|
||||
GameRowView(game: richGame, showDate: true, showLocation: groupBy == .byGame)
|
||||
```
|
||||
|
||||
**Step 4: Build and verify**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 5: Manual test**
|
||||
|
||||
Open Schedule tab, switch to "By Game" grouping. Each row should now show stadium name and city.
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Schedule/Views/ScheduleListView.swift
|
||||
git commit -m "feat: show location info in By Game schedule view
|
||||
|
||||
Adds stadium name and city to game rows when grouped by game."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Fix Game Selection Animation (Bug 5)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Trip/Views/TripCreationView.swift`
|
||||
|
||||
**Step 1: Find the game selection tap handler**
|
||||
|
||||
Look for the game selection list/grid and its tap action.
|
||||
|
||||
**Step 2: Wrap selection in transaction to disable animation**
|
||||
|
||||
```swift
|
||||
// Replace direct toggle:
|
||||
// viewModel.toggleGameSelection(game)
|
||||
|
||||
// With:
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
viewModel.toggleGameSelection(game)
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Alternative - add animation nil modifier**
|
||||
|
||||
If using a selection indicator (checkmark), add:
|
||||
|
||||
```swift
|
||||
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(isSelected ? .blue : .secondary)
|
||||
.animation(nil, value: isSelected)
|
||||
```
|
||||
|
||||
**Step 4: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 5: Manual test**
|
||||
|
||||
Go to trip creation, select "By Game" mode, tap games to select. Animation should be instant without weird morphing.
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Trip/Views/TripCreationView.swift
|
||||
git commit -m "fix: remove weird animation on game selection
|
||||
|
||||
Disables implicit animation when toggling game selection."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Fix Pace Capsule Animation (Bug 7)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Home/Views/SavedTripsListView.swift` (or similar)
|
||||
|
||||
**Step 1: Find the pace capsule component**
|
||||
|
||||
Search for "packed", "moderate", "relaxed" or pace-related text in the trips list view.
|
||||
|
||||
**Step 2: Add contentTransition and disable animation**
|
||||
|
||||
```swift
|
||||
Text(trip.paceLabel) // or whatever the pace text is
|
||||
.font(.caption2)
|
||||
.fontWeight(.medium)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(paceColor.opacity(0.2))
|
||||
.foregroundStyle(paceColor)
|
||||
.clipShape(Capsule())
|
||||
.contentTransition(.identity) // Prevents text morphing
|
||||
.animation(nil, value: trip.id) // No animation on data change
|
||||
```
|
||||
|
||||
**Step 3: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 4: Manual test**
|
||||
|
||||
View saved trips list. Pace capsules should appear without glitchy animation.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Home/Views/SavedTripsListView.swift
|
||||
git commit -m "fix: pace capsule animation glitch
|
||||
|
||||
Adds contentTransition(.identity) to prevent text morphing."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Remove "My Trips" Title (Bug 8)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Home/Views/HomeView.swift` (or main TabView file)
|
||||
|
||||
**Step 1: Find the My Trips tab**
|
||||
|
||||
Look for TabView and the "My Trips" tab item.
|
||||
|
||||
**Step 2: Remove or hide navigation title**
|
||||
|
||||
Option A - Remove `.navigationTitle()`:
|
||||
```swift
|
||||
NavigationStack {
|
||||
SavedTripsListView(trips: savedTrips)
|
||||
// Remove: .navigationTitle("My Trips")
|
||||
}
|
||||
.tabItem {
|
||||
Label("My Trips", systemImage: "suitcase")
|
||||
}
|
||||
```
|
||||
|
||||
Option B - Hide navigation bar:
|
||||
```swift
|
||||
NavigationStack {
|
||||
SavedTripsListView(trips: savedTrips)
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
}
|
||||
.tabItem {
|
||||
Label("My Trips", systemImage: "suitcase")
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 4: Manual test**
|
||||
|
||||
Switch to My Trips tab. No redundant title should appear above the content.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Home/Views/HomeView.swift
|
||||
git commit -m "fix: remove redundant My Trips title from tab view"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Add Stadium Timezone Support (Bug 6)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Core/Models/Domain/Stadium.swift`
|
||||
- Modify: `SportsTime/Core/Models/Domain/Game.swift`
|
||||
- Modify: `Scripts/sportstime_parser/normalizers/stadium_resolver.py`
|
||||
|
||||
**Step 1: Add timeZoneIdentifier to Stadium model**
|
||||
|
||||
```swift
|
||||
struct Stadium: Identifiable, Codable, Hashable {
|
||||
let id: String
|
||||
let name: String
|
||||
let city: String
|
||||
let state: String
|
||||
let coordinate: CLLocationCoordinate2D
|
||||
let sport: Sport
|
||||
let region: Region
|
||||
let timeZoneIdentifier: String? // Add this
|
||||
|
||||
var timeZone: TimeZone? {
|
||||
timeZoneIdentifier.flatMap { TimeZone(identifier: $0) }
|
||||
}
|
||||
|
||||
// Update init to include timeZoneIdentifier with default nil for backward compatibility
|
||||
init(
|
||||
id: String,
|
||||
name: String,
|
||||
city: String,
|
||||
state: String,
|
||||
coordinate: CLLocationCoordinate2D,
|
||||
sport: Sport,
|
||||
region: Region,
|
||||
timeZoneIdentifier: String? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.city = city
|
||||
self.state = state
|
||||
self.coordinate = coordinate
|
||||
self.sport = sport
|
||||
self.region = region
|
||||
self.timeZoneIdentifier = timeZoneIdentifier
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Add localGameTime to RichGame**
|
||||
|
||||
```swift
|
||||
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)
|
||||
}
|
||||
|
||||
var localGameTimeShort: String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "h:mm a"
|
||||
formatter.timeZone = stadium.timeZone ?? .current
|
||||
return formatter.string(from: game.dateTime)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Update Python scraper StadiumInfo**
|
||||
|
||||
In `stadium_resolver.py`, update the dataclass and all stadium entries:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class StadiumInfo:
|
||||
canonical_id: str
|
||||
name: str
|
||||
city: str
|
||||
state: str
|
||||
country: str
|
||||
sport: str
|
||||
latitude: float
|
||||
longitude: float
|
||||
timezone: str = "America/New_York" # Add with default
|
||||
|
||||
# Update entries, e.g.:
|
||||
"stadium_nba_chase_center": StadiumInfo("stadium_nba_chase_center", "Chase Center", "San Francisco", "CA", "USA", "nba", 37.7680, -122.3877, "America/Los_Angeles"),
|
||||
```
|
||||
|
||||
**Step 4: Build Swift code**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Core/Models/Domain/Stadium.swift SportsTime/Core/Models/Domain/Game.swift Scripts/sportstime_parser/normalizers/stadium_resolver.py
|
||||
git commit -m "feat: add timezone support for stadium-local game times
|
||||
|
||||
Adds timeZoneIdentifier to Stadium and localGameTime to RichGame.
|
||||
Game times can now display in venue local time."
|
||||
```
|
||||
|
||||
**Note:** Full timezone population for all stadiums is a separate data task.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Add Frost Bank Center Alias (Bug 9)
|
||||
|
||||
**Files:**
|
||||
- Modify: `Scripts/stadium_aliases.json`
|
||||
|
||||
**Step 1: Open stadium_aliases.json and add alias**
|
||||
|
||||
Add entry for AT&T Center (old name for Frost Bank Center):
|
||||
|
||||
```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 July 2024"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Verify JSON is valid**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
python3 -c "import json; json.load(open('Scripts/stadium_aliases.json'))"
|
||||
```
|
||||
|
||||
Expected: No output (valid JSON)
|
||||
|
||||
**Step 3: Test scraper resolution**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd Scripts && python3 -c "
|
||||
from sportstime_parser.normalizers.stadium_resolver import resolve_stadium
|
||||
result = resolve_stadium('nba', 'AT&T Center')
|
||||
print(f'Resolved: {result.canonical_id}, confidence: {result.confidence}')
|
||||
"
|
||||
```
|
||||
|
||||
Expected: `Resolved: stadium_nba_frost_bank_center, confidence: 95`
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add Scripts/stadium_aliases.json
|
||||
git commit -m "fix: add AT&T Center alias for Frost Bank Center
|
||||
|
||||
NBA scraper now resolves old stadium name to current Spurs arena."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Add Pagination to Schedule List (Bug 2)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Schedule/Views/ScheduleListView.swift`
|
||||
- Modify: `SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift`
|
||||
|
||||
**Step 1: Add pagination state to ViewModel**
|
||||
|
||||
```swift
|
||||
@Observable
|
||||
class ScheduleViewModel {
|
||||
// Existing properties...
|
||||
|
||||
// Pagination
|
||||
private let pageSize = 50
|
||||
var displayedGames: [RichGame] = []
|
||||
private var currentPage = 0
|
||||
private var allFilteredGames: [RichGame] = []
|
||||
|
||||
var hasMoreGames: Bool {
|
||||
displayedGames.count < allFilteredGames.count
|
||||
}
|
||||
|
||||
func loadInitialGames() {
|
||||
currentPage = 0
|
||||
displayedGames = Array(allFilteredGames.prefix(pageSize))
|
||||
}
|
||||
|
||||
func loadMoreGames() {
|
||||
guard hasMoreGames else { return }
|
||||
currentPage += 1
|
||||
let start = currentPage * pageSize
|
||||
let end = min(start + pageSize, allFilteredGames.count)
|
||||
displayedGames.append(contentsOf: allFilteredGames[start..<end])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Update filtering to populate allFilteredGames**
|
||||
|
||||
In the method that filters/fetches games:
|
||||
|
||||
```swift
|
||||
func applyFilters() async {
|
||||
// ... existing filter logic that produces games
|
||||
|
||||
allFilteredGames = filteredGames // Store full list
|
||||
loadInitialGames() // Display first page
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Update ScheduleListView to use displayedGames**
|
||||
|
||||
```swift
|
||||
struct ScheduleListView: View {
|
||||
@Bindable var viewModel: ScheduleViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.displayedGames) { game in
|
||||
GameRowView(game: game, showDate: true, showLocation: viewModel.groupBy == .byGame)
|
||||
.onAppear {
|
||||
if game.id == viewModel.displayedGames.last?.id {
|
||||
viewModel.loadMoreGames()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.hasMoreGames {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 5: Manual test**
|
||||
|
||||
Open Schedule tab with "By Game" grouping. List should load quickly with 50 games, then load more as you scroll.
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Schedule/Views/ScheduleListView.swift SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift
|
||||
git commit -m "perf: add pagination to schedule list
|
||||
|
||||
Loads 50 games at a time to fix lag with large datasets."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Fix Game Selection View Performance (Bug 4)
|
||||
|
||||
**Files:**
|
||||
- Modify: `SportsTime/Features/Trip/Views/TripCreationView.swift`
|
||||
- Modify: `SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift`
|
||||
|
||||
**Step 1: Add pagination to TripCreationViewModel**
|
||||
|
||||
Similar to Task 8, add pagination for the game selection:
|
||||
|
||||
```swift
|
||||
// In TripCreationViewModel
|
||||
private let gamePageSize = 50
|
||||
var displayedAvailableGames: [RichGame] = []
|
||||
private var currentGamePage = 0
|
||||
|
||||
var hasMoreAvailableGames: Bool {
|
||||
displayedAvailableGames.count < availableGames.count
|
||||
}
|
||||
|
||||
func loadInitialAvailableGames() {
|
||||
currentGamePage = 0
|
||||
displayedAvailableGames = Array(availableGames.prefix(gamePageSize))
|
||||
}
|
||||
|
||||
func loadMoreAvailableGames() {
|
||||
guard hasMoreAvailableGames else { return }
|
||||
currentGamePage += 1
|
||||
let start = currentGamePage * gamePageSize
|
||||
let end = min(start + gamePageSize, availableGames.count)
|
||||
displayedAvailableGames.append(contentsOf: availableGames[start..<end])
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Update game selection view to use displayedAvailableGames**
|
||||
|
||||
```swift
|
||||
// In game selection section of TripCreationView
|
||||
LazyVStack(spacing: 8) {
|
||||
ForEach(viewModel.displayedAvailableGames) { game in
|
||||
GameSelectionRow(game: game, isSelected: viewModel.selectedGameIds.contains(game.id))
|
||||
.onTapGesture {
|
||||
var transaction = Transaction()
|
||||
transaction.disablesAnimations = true
|
||||
withTransaction(transaction) {
|
||||
viewModel.toggleGameSelection(game)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if game.id == viewModel.displayedAvailableGames.last?.id {
|
||||
viewModel.loadMoreAvailableGames()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Fix color inconsistency**
|
||||
|
||||
Ensure all game rows use consistent background:
|
||||
|
||||
```swift
|
||||
struct GameSelectionRow: View {
|
||||
let game: RichGame
|
||||
let isSelected: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
// Game info...
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(isSelected ? .blue : .secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground)) // Consistent background
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Build and test**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' build
|
||||
```
|
||||
|
||||
**Step 5: Manual test**
|
||||
|
||||
Create a trip with "By Game" mode. Game selection should be smooth and colors consistent.
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add SportsTime/Features/Trip/Views/TripCreationView.swift SportsTime/Features/Trip/ViewModels/TripCreationViewModel.swift
|
||||
git commit -m "perf: add pagination to game selection view
|
||||
|
||||
Fixes lag and color inconsistency in trip creation game selection."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final Task: Run Full Test Suite
|
||||
|
||||
**Step 1: Run all tests**
|
||||
|
||||
```bash
|
||||
xcodebuild -project SportsTime.xcodeproj -scheme SportsTime -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.2' test
|
||||
```
|
||||
|
||||
**Step 2: Verify all pass**
|
||||
|
||||
Expected: All tests pass (existing 63 tests)
|
||||
|
||||
**Step 3: Manual smoke test**
|
||||
|
||||
- [ ] Launch app - UI responsive immediately
|
||||
- [ ] Home tab - suggested trips load in background
|
||||
- [ ] Schedule tab - "By Game" shows location, scrolls smoothly
|
||||
- [ ] Trip creation - game selection smooth, no weird animation
|
||||
- [ ] My Trips - no redundant title, pace capsules look good
|
||||
Reference in New Issue
Block a user