# 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<Date>` 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<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 |
