Files
Sportstime/docs/CUSTOMER_FEEDBACK_PLAN.md
Trey t dbb0099776 chore: remove scraper, add docs, add marketing-videos gitignore
- 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>
2026-01-26 18:13:12 -06:00

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 Button with .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:

  1. ScheduleListView.swift - GameRowView is display-only (no selection)
  2. Game selection in other wizard steps
  3. 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 fullName computed 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
  • fullName logic 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 - change name to 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.json
  • SportsTime/Resources/teams_canonical.json (regenerated)
  • CloudKit CanonicalTeam records

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() to Date() + 14 days
  • If "tonight" means Jan 20, the game SHOULD show
  • Database contains: game_nba_2025_20260120_sas_hou dated 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.dateTime vs Date()
  • 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 CanonicalSyncService last 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.swift
  • SportsTime/Core/Services/DataProvider.swift
  • SportsTime/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:

  1. A visible date selection calendar
  2. When selecting a game, calendar auto-scrolls to that game's date
  3. Selected game should be in the middle of a 7-day span (position 4 of 7)
  4. Multiple games should show range from earliest to latest

Current Behavior:

  • No calendar shown in gameFirst mode
  • Dates are auto-derived AFTER user finishes selecting games
  • Date range calculated: min(gameDates) to max(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:

  1. Single Game Selected:

    • Calendar centers on game date (position 4 of 7)
    • Date range: gameDate - 3 days to gameDate + 3 days
    • Visual: 7 consecutive days highlighted
  2. Multiple Games Selected:

    • Date range: min(gameDates) - 1 day to max(gameDates) + 1 day
    • All game dates marked with sport-colored dots
    • Calendar shows earliest month containing games
  3. User Manually Adjusts Range:

    • Allow expanding beyond auto-calculated range
    • Prevent shrinking below game dates (show error)

Implementation Tasks:

  • Task 2.1.1: Add DateRangePicker to GamePickerStep.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 DateRangePicker to 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 trigger updateDateRangeForSelectedGames()
  • 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.swift
  • SportsTime/Features/Trip/Views/Wizard/Steps/DateRangePicker.swift
  • SportsTime/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

  1. Zero instances of duplicated team names
  2. All today's NBA games visible in Schedule (when NBA filter enabled)
  3. 100% row area tappable for game selection
  4. 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 startDate and endDate bindings to GamePickerStep
  • Added dateRangeSection view 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 TripWizardView to 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.log logging to ScheduleViewModel.loadGames() with:
    • Query date range
    • Sports filter
    • Teams/stadiums loaded count
    • Games returned count by sport
  • Added ScheduleDiagnostics struct to capture query details
  • Added ScheduleDiagnosticsSheet accessible 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:

  1. Open Schedule tab
  2. Tap filter menu (three lines icon)
  3. Tap "Diagnostics"
  4. 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