- Remove Scripts/ directory (scraper no longer needed) - Add themed background documentation to CLAUDE.md - Add .gitignore for marketing-videos to prevent node_modules tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
Customer Feedback Implementation Plan
Created: January 20, 2026 Updated: January 20, 2026 Status: In Progress (3 of 4 implemented) Priority: High (User-reported issues)
Executive Summary
This document outlines the implementation plan for four customer-reported issues:
| # | Issue | Type | Priority | Status |
|---|---|---|---|---|
| 1 | Game row selection area too small | UX Bug | P1 | ✅ DONE |
| 2 | NWSL team names duplicated | Data Bug | P1 | ⏸️ Deferred (per user request) |
| 3 | "By Games" mode needs date selection UI | Feature | P2 | ✅ DONE |
| 4 | Schedule view missing NBA games | Investigation | P1 | ✅ DONE (debugging added) |
Phase 1: Critical Bug Fixes (P1)
1.1 Game Row Selection Area
Problem: Users report they cannot click anywhere on the row to select a game.
Investigation Findings:
- File:
Features/Trip/Views/Wizard/Steps/GamePickerStep.swift - Lines 397-512:
GamesPickerSheet - Current implementation wraps content in
Buttonwith.buttonStyle(.plain) - The entire row SHOULD be tappable
Root Cause Hypothesis:
The issue may be in a DIFFERENT view than GamesPickerSheet. Need to verify which screen users are referring to. Possible locations:
ScheduleListView.swift- GameRowView is display-only (no selection)- Game selection in other wizard steps
- Possibly hitting the checkbox icon area feels required
Tasks:
- Task 1.1.1: Reproduce the issue - identify EXACT screen where selection fails
- Task 1.1.2: If
GamesPickerSheet- Add.contentShape(Rectangle())to ensure full row hit testing - Task 1.1.3: If
ScheduleListView- This is intentionally display-only; clarify requirements - Task 1.1.4: Add visual tap feedback (highlight on press) for better UX
- Task 1.1.5: Write regression test for row selection
Proposed Fix (if GamesPickerSheet):
Button {
// toggle selection
} label: {
HStack {
// ... content
}
.padding(.vertical, Theme.Spacing.xs)
.contentShape(Rectangle()) // ← ADD THIS
}
.buttonStyle(.plain)
Files to Modify:
SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swift
Estimated Effort: 1-2 hours (including investigation)
1.2 NWSL Team Name Duplication
Problem: NWSL teams display as "Houston Houston Dash" instead of "Houston Dash".
Investigation Findings:
- File:
Core/Models/Domain/Team.swift:59 - The
fullNamecomputed property:city.isEmpty ? name : "\(city) \(name)" - NWSL teams in JSON have:
city: "Houston"name: "Houston Dash"(already includes city)
- Result: "Houston" + " " + "Houston Dash" = "Houston Houston Dash"
Affected Teams:
| Team | city | name | Current fullName |
|---|---|---|---|
| Houston Dash | Houston | Houston Dash | Houston Houston Dash |
| Portland Thorns | Portland | Portland Thorns | Portland Portland Thorns |
| Seattle Reign | Seattle | Seattle Reign | Seattle Seattle Reign |
| Orlando Pride | Orlando | Orlando Pride | Orlando Orlando Pride |
| Kansas City Current | Kansas City | Kansas City Current | Kansas City Kansas City Current |
| North Carolina Courage | North Carolina | North Carolina Courage | North Carolina North Carolina Courage |
| + other NWSL teams... |
Solution Options:
Option A: Fix Data (Recommended)
Update teams_canonical.json and regenerate from scripts:
// BEFORE
{ "city": "Houston", "name": "Houston Dash" }
// AFTER
{ "city": "Houston", "name": "Dash" }
Pros:
- Clean separation of city and nickname
- Consistent with MLB/NFL/NHL conventions
fullNamelogic remains simple
Cons:
- Requires data regeneration
- Need to verify all NWSL teams
- Must update CloudKit canonical data
Option B: Fix Code
Detect city prefix in fullName and avoid duplication:
var fullName: String {
if city.isEmpty { return name }
if name.hasPrefix(city) { return name } // ← ADD THIS
return "\(city) \(name)"
}
Pros:
- No data migration needed
- Handles edge cases automatically
Cons:
- Hides data inconsistency
- Fragile (what about "New York City FC" vs "NYC"?)
- Adds runtime overhead
Selected Approach: Option A (Data Fix)
Tasks:
- Task 1.2.1: Update
Scripts/output/teams_nwsl.json- changenameto nickname only - Task 1.2.2: Run data pipeline to regenerate
teams_canonical.json - Task 1.2.3: Verify all NWSL teams display correctly
- Task 1.2.4: Update CloudKit canonical data via admin tool
- Task 1.2.5: Write unit test to prevent regression:
test_NWSLTeamNames_DoNotDuplicateCity
Files to Modify:
Scripts/output/teams_nwsl.jsonSportsTime/Resources/teams_canonical.json(regenerated)- CloudKit
CanonicalTeamrecords
Estimated Effort: 2-3 hours
1.3 Schedule View Missing Games Investigation
Problem: User reports NBA San Antonio @ Houston game (tonight) not showing in schedule.
Investigation Findings:
Date Filter Analysis (TODAY = Jan 20, 2026):
- Default range:
Date()toDate() + 14 days - If "tonight" means Jan 20, the game SHOULD show
- Database contains:
game_nba_2025_20260120_sas_houdated 2026-01-20 06:00 UTC
Potential Causes:
| Cause | Likelihood | How to Verify |
|---|---|---|
| Timezone mismatch | High | Game stored as UTC, filter uses local time |
| Data not loaded | Medium | Check AppDataProvider.shared.allGames(for: [.nba]) count |
| Sport filter unchecked | Medium | Verify NBA is in selectedSports |
| Team/Stadium lookup fail | Low | Check teamsById["team_nba_sas"] exists |
Most Likely Issue: Timezone Bug
Game time: 2026-01-20T06:00:00Z (6 AM UTC = midnight CST on Jan 20)
If user's device is in CST (UTC-6) and filter is:
game.dateTime >= Date() // Using local time
At 7 PM CST on Jan 19, Date() = 2026-01-20T01:00:00Z
Game is at 2026-01-20T06:00:00Z
→ Game IS >= Date() → Should show
But if filter comparison isn't timezone-aware, edge cases occur.
Tasks:
- Task 1.3.1: Add debug logging to
filterGames()- log date range and game count - Task 1.3.2: Verify timezone handling in
CanonicalGame.dateTimevsDate() - Task 1.3.3: Check if games are loading - add game count to Schedule header temporarily
- Task 1.3.4: Verify NBA games exist in local SwiftData store
- Task 1.3.5: If data issue - check
CanonicalSyncServicelast sync status - Task 1.3.6: If client issue - fix timezone/filter logic
- Task 1.3.7: Add diagnostic screen in Settings showing:
- Total games per sport in database
- Last sync timestamp
- Date range being used for filtering
Files to Investigate:
SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swiftSportsTime/Core/Services/DataProvider.swiftSportsTime/Core/Services/CanonicalSyncService.swift
Estimated Effort: 2-4 hours (investigation + fix)
Phase 2: Feature Enhancement (P2)
2.1 "By Games" Mode - Date Selection with Auto-Scroll
Problem: When selecting games in "By Games" routing mode, users want:
- A visible date selection calendar
- When selecting a game, calendar auto-scrolls to that game's date
- Selected game should be in the middle of a 7-day span (position 4 of 7)
- Multiple games should show range from earliest to latest
Current Behavior:
- No calendar shown in
gameFirstmode - Dates are auto-derived AFTER user finishes selecting games
- Date range calculated:
min(gameDates)tomax(gameDates) + 1 day
Proposed UX:
┌─────────────────────────────────────────────────┐
│ BY GAMES MODE - STEP 2 │
├─────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ JANUARY 2026 │ │
│ │ Su Mo Tu We Th Fr Sa │ │
│ │ 1 2 3 4 │ │
│ │ 5 6 7 8 9 10 11 │ │
│ │ 12 13 14 15 16 17 18 │ │
│ │ 19 20 [21][22][23][24][25] │ │ ← 7-day span
│ │ 26 27 28 29 30 31 │ │ centered on
│ └─────────────────────────────────────────┘ │ selected game
│ │
│ SELECTED GAMES (2): │
│ ┌─────────────────────────────────────────┐ │
│ │ 🏀 SAS @ HOU • Jan 22 • 7:00 PM ✕│ │ ← Game #1 (anchor)
│ │ ⚾ STL @ HOU • Jan 25 • 1:10 PM ✕│ │ ← Game #2
│ └─────────────────────────────────────────────┘ │
│ │
│ [+ Add More Games] │
│ │
└─────────────────────────────────────────────────┘
Behavior Rules:
-
Single Game Selected:
- Calendar centers on game date (position 4 of 7)
- Date range:
gameDate - 3 daystogameDate + 3 days - Visual: 7 consecutive days highlighted
-
Multiple Games Selected:
- Date range:
min(gameDates) - 1 daytomax(gameDates) + 1 day - All game dates marked with sport-colored dots
- Calendar shows earliest month containing games
- Date range:
-
User Manually Adjusts Range:
- Allow expanding beyond auto-calculated range
- Prevent shrinking below game dates (show error)
Implementation Tasks:
- Task 2.1.1: Add
DateRangePickertoGamePickerStep.swift(below game summary) - Task 2.1.2: Create
@State var dateRange: ClosedRange<Date>in ViewModel - Task 2.1.3: Implement
updateDateRangeForSelectedGames():func updateDateRangeForSelectedGames() { guard !selectedGameIds.isEmpty else { dateRange = nil // Clear range when no games return } let gameDates = selectedGames.map { $0.dateTime }.sorted() if gameDates.count == 1 { // Single game: 7-day span centered on game let gameDate = gameDates[0] let start = Calendar.current.date(byAdding: .day, value: -3, to: gameDate)! let end = Calendar.current.date(byAdding: .day, value: 3, to: gameDate)! dateRange = start...end } else { // Multiple games: span from first to last with 1-day buffer let start = Calendar.current.date(byAdding: .day, value: -1, to: gameDates.first!)! let end = Calendar.current.date(byAdding: .day, value: 1, to: gameDates.last!)! dateRange = start...end } } - Task 2.1.4: Modify
DateRangePickerto accept game dates for marking:var gameDates: Set<Date> // Dates with selected games - Task 2.1.5: Add game date markers to calendar (sport-colored dots)
- Task 2.1.6: Implement calendar auto-scroll to earliest game month
- Task 2.1.7: Add
.onChange(of: selectedGameIds)to triggerupdateDateRangeForSelectedGames() - Task 2.1.8: Update
TripWizardView.planTrip()to use the visible date range - Task 2.1.9: Write UI tests for auto-scroll behavior
Files to Modify:
SportsTime/Features/Trip/Views/Wizard/Steps/GamePickerStep.swiftSportsTime/Features/Trip/Views/Wizard/Steps/DateRangePicker.swiftSportsTime/Features/Trip/ViewModels/TripWizardViewModel.swift
Estimated Effort: 6-8 hours
Implementation Schedule
Week 1: Bug Fixes
| Day | Task | Owner |
|---|---|---|
| Day 1 | Task 1.3.1-1.3.4: Investigate schedule missing games | TBD |
| Day 1 | Task 1.2.1-1.2.2: Fix NWSL team data | TBD |
| Day 2 | Task 1.3.5-1.3.7: Complete schedule fix | TBD |
| Day 2 | Task 1.2.3-1.2.5: Verify NWSL fix + tests | TBD |
| Day 3 | Task 1.1.1-1.1.5: Fix row selection | TBD |
| Day 3 | Integration testing all Phase 1 fixes | TBD |
Week 2: Feature Enhancement
| Day | Task | Owner |
|---|---|---|
| Day 4-5 | Tasks 2.1.1-2.1.4: Date picker integration | TBD |
| Day 6 | Tasks 2.1.5-2.1.7: Calendar markers + auto-scroll | TBD |
| Day 7 | Tasks 2.1.8-2.1.9: Integration + UI tests | TBD |
Testing Checklist
Phase 1 Tests
- 1.1: Tap anywhere on game row → row selected
- 1.1: Selected row shows checkmark, deselected shows empty circle
- 1.2: Houston Dash displays as "Houston Dash" (not "Houston Houston Dash")
- 1.2: All NWSL teams display correctly
- 1.3: NBA games for today show in Schedule view
- 1.3: Games from different timezones show on correct local date
Phase 2 Tests
- 2.1: Select single game → calendar shows 7-day range centered on game
- 2.1: Select multiple games → calendar spans earliest to latest
- 2.1: Game dates have colored markers on calendar
- 2.1: Selecting game auto-scrolls calendar to that month
- 2.1: Cannot shrink date range to exclude selected games
- 2.1: Trip planning uses visible date range
Risk Assessment
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| NWSL data fix breaks team lookups | Low | High | Test with full data load before deploy |
| Schedule timezone fix affects other sports | Medium | Medium | Test with games from multiple timezones |
| Date picker state conflicts with game selection | Low | Medium | Clear state isolation between components |
| CloudKit sync overwrites data fix | Medium | High | Update CloudKit admin data simultaneously |
Success Criteria
- Zero instances of duplicated team names
- All today's NBA games visible in Schedule (when NBA filter enabled)
- 100% row area tappable for game selection
- Automatic calendar updates when games selected in "By Games" mode
Implementation Notes (January 20, 2026)
Issue 1: Row Selection - COMPLETED
Fix Applied: Added .contentShape(Rectangle()) to all picker sheet rows in GamePickerStep.swift:
GamesPickerSheet(line 468)SportsPickerSheet(line 268)TeamsPickerSheet(line 366)
This ensures the entire row area is tappable, not just the visible content.
Issue 2: NWSL Names - DEFERRED
Per user request, this fix is deferred for later investigation.
Issue 3: By Games Date Selection - COMPLETED
Implementation:
- Added
startDateandendDatebindings toGamePickerStep - Added
dateRangeSectionview that appears when games are selected - Added
updateDateRangeForSelectedGames()function with auto-scroll logic:- Single game: 7-day span centered on game (position 4 of 7)
- Multiple games: Range from earliest to latest with 1-day buffer
- Added game date markers legend showing selected games by date
- Updated
TripWizardViewto pass date bindings
Files Modified:
GamePickerStep.swift(new date bindings, date section, auto-update logic)TripWizardView.swift(pass date bindings to GamePickerStep)
Issue 4: Missing NBA Games - DEBUGGING ADDED
Implementation:
- Added
os.loglogging toScheduleViewModel.loadGames()with:- Query date range
- Sports filter
- Teams/stadiums loaded count
- Games returned count by sport
- Added
ScheduleDiagnosticsstruct to capture query details - Added
ScheduleDiagnosticsSheetaccessible via toolbar menu in Schedule view - Shows: date range used, sports queried, data loaded counts, games by sport, troubleshooting steps
Files Modified:
ScheduleViewModel.swift(logger, diagnostics tracking)ScheduleListView.swift(diagnostics menu item and sheet)
How to Use:
- Open Schedule tab
- Tap filter menu (three lines icon)
- Tap "Diagnostics"
- Review date range, loaded data counts, and games by sport
Appendix: File Reference
| File | Purpose |
|---|---|
Features/Trip/Views/Wizard/Steps/GamePickerStep.swift |
Game selection UI |
Features/Trip/Views/Wizard/Steps/DateRangePicker.swift |
Calendar component |
Features/Trip/ViewModels/TripWizardViewModel.swift |
Trip wizard state |
Features/Schedule/Views/ScheduleListView.swift |
Schedule display |
Features/Schedule/ViewModels/ScheduleViewModel.swift |
Schedule filtering |
Core/Models/Domain/Team.swift |
Team model with fullName |
Core/Services/DataProvider.swift |
Data loading and filtering |
Scripts/output/teams_nwsl.json |
NWSL team source data |
Resources/teams_canonical.json |
Bundled team data |