# 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):** ```swift 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: ```json // 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: ```swift 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: ```swift 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` in ViewModel - [ ] **Task 2.1.3:** Implement `updateDateRangeForSelectedGames()`: ```swift 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: ```swift var gameDates: Set // 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 |