# Critical Bug Fix: jamClient Undefined Error

**Date:** 2026-01-21
**Status:** ✅ FIXED
**Tests:** ✅ All 13 unit tests passing

---

## 🐛 The Bug

### Error Message
```
Unhandled Rejection (TypeError): Cannot destructure property 'jamClient' of 'state.jamClient' as it is undefined.
src/services/trackSyncService.js:64
> 64 |   const { jamClient } = state.jamClient;
```

### Root Cause
The `trackSyncService.js` was incorrectly trying to access `jamClient` from Redux state:
```javascript
// ❌ WRONG - jamClient is not in Redux!
const { jamClient } = state.jamClient;
```

However, `jamClient` is accessed via **Context API** (`useJamServerContext()`), not Redux.

**Evidence from JKSessionScreen.js:**
```javascript
// Line 14 - jamClient comes from Context
import { useJamServerContext } from '../../context/JamServerContext.js';

// Lines 112-121 - jamClient destructured from Context hook
const {
  isConnected,
  connectionStatus,
  jamClient,  // <-- From Context API, NOT Redux!
  server,
  // ...
} = useJamServerContext();
```

---

## ✅ The Fix

### Refactored Architecture

**Before (Broken):**
```javascript
// Service tried to get jamClient from Redux (doesn't exist there)
export const syncTracksToServer = (sessionId) => async (dispatch, getState) => {
  const state = getState();
  const { jamClient } = state.jamClient;  // ❌ ERROR: undefined
  // ...
};
```

**After (Fixed):**
```javascript
// Service accepts jamClient as parameter from components
export const syncTracksToServer = (sessionId, jamClient) => async (dispatch, getState) => {
  const state = getState();
  // jamClient is now passed as parameter ✅
  const clientId = jamClient?.clientID || jamClient?.GetClientID?.();
  // ...
};
```

### Files Modified

#### 1. **src/services/trackSyncService.js**
**Changes:**
- `buildTrackSyncPayload(state, sessionId)` → `buildTrackSyncPayload(state, sessionId, jamClient)`
- `syncTracksToServer(sessionId)` → `syncTracksToServer(sessionId, jamClient)`
- Removed: `const { jamClient } = state.jamClient;` (lines 20, 64)
- Updated: All internal calls to pass jamClient

#### 2. **src/components/client/JKSessionScreen.js**
**Changes:**
```javascript
// Before
dispatch(syncTracksToServer(sessionId));

// After
dispatch(syncTracksToServer(sessionId, jamClient));
```
- Updated all 3 timer-based sync calls (lines 412, 418, 424)
- Added jamClient to useEffect dependencies (line 433)
- Added jamClient guard: `if (!hasJoined || !sessionId || !jamClient)`

#### 3. **src/components/client/JKSessionMyTrack.js**
**Changes:**
```javascript
// Before
if (sessionId) {
  dispatch(syncTracksToServer(sessionId));
}

// After
if (sessionId && jamClient) {
  dispatch(syncTracksToServer(sessionId, jamClient));
}
```
- Updated instrument save handler (line 127)

#### 4. **src/hooks/useMediaActions.js**
**Changes:** Updated 4 functions to pass jamClient:
```javascript
// Before
if (sessionId) {
  dispatch(syncTracksToServer(sessionId));
}

// After
if (sessionId && jamClient) {
  dispatch(syncTracksToServer(sessionId, jamClient));
}
```
- `openBackingTrack()` - line 54
- `openMetronome()` - line 107
- `closeMetronome()` - line 133
- `loadJamTrack()` - line 158

#### 5. **src/services/__tests__/trackSyncService.test.js**
**Changes:** Refactored all 13 tests
- Created `mockJamClient` object at top of test suite
- Removed `state.jamClient.jamClient` structures
- Updated all `buildTrackSyncPayload()` calls to pass mockJamClient as 3rd parameter
- Updated all `syncTracksToServer()` calls to pass mockJamClient as 2nd parameter

---

## 🧪 Test Results

### Unit Tests: ✅ All Passing (13/13)
```
PASS src/services/__tests__/trackSyncService.test.js
  trackSyncService
    buildTrackSyncPayload
      ✓ builds correct payload from Redux state with user tracks
      ✓ builds payload with mono sound when track is not stereo
      ✓ builds payload with backing tracks
      ✓ builds payload with metronome open
      ✓ handles missing metronome state gracefully
      ✓ handles empty tracks array
      ✓ maps multiple instruments correctly
    syncTracksToServer
      ✓ calls API with correct payload
      ✓ skips sync when clientId is missing
      ✓ skips sync when sessionId is missing
      ✓ skips sync when session not joined
      ✓ handles API error gracefully
      ✓ dispatches Redux actions on success

Test Suites: 1 passed, 1 total
Tests:       13 passed, 13 total
Time:        1.003s
```

**Command to run:**
```bash
npm run test:unit -- src/services/__tests__/trackSyncService.test.js
```

---

## 🎯 What This Fixes

### Before Fix
- ❌ Browser console error: "Cannot destructure property 'jamClient'"
- ❌ App crashed when joining session
- ❌ No track sync API calls visible in Network tab
- ❌ Integration tests failing (0 API calls detected)

### After Fix
- ✅ No runtime errors
- ✅ jamClient accessed from correct source (Context)
- ✅ Service can get clientId successfully
- ✅ Track sync should work when session joined
- ✅ Unit tests all passing

---

## 📋 Next Steps

### 1. Manual Browser Testing (Required)
**Test in browser to verify the fix:**

1. **Start dev server:**
   ```bash
   npm start
   ```

2. **Open browser DevTools:**
   - Network tab (filter: tracks)
   - Console tab (check for errors)

3. **Test Session Join:**
   - Join a session
   - Wait 8 seconds
   - **Expected:** See 3 × `PUT /api/sessions/{id}/tracks` calls
     - At ~1 second after join
     - At ~1.4 seconds after join
     - At ~6 seconds after join
   - **Expected:** No console errors about jamClient

4. **Test Instrument Change:**
   - Click on your track
   - Change instrument
   - **Expected:** See 1 × `PUT /api/sessions/{id}/tracks` call immediately

5. **Test Metronome:**
   - Open metronome
   - **Expected:** See 1 × `PUT /api/sessions/{id}/tracks` call with `metronome_open: true`
   - Close metronome
   - **Expected:** See 1 × `PUT /api/sessions/{id}/tracks` call with `metronome_open: false`

6. **Verify Payload Structure:**
   - Click on a track sync API call in Network tab
   - Check Request Payload:
     ```json
     {
       "client_id": "uuid",
       "tracks": [...],
       "backing_tracks": [],
       "metronome_open": false
     }
     ```

### 2. Integration Tests
After manual testing confirms it works:
```bash
npm test -- test/track-sync/track-configuration.spec.ts
```

**Expected:** All 7 tests should pass (currently 4 failing due to this bug)

### 3. REFACTOR Phase
Once GREEN phase is verified:
- Consider optimizing 3-call pattern to 1 call
- Add debouncing for rapid changes
- Extract timing constants
- Consider TypeScript types

---

## 🔍 Why This Happened

### Architecture Mismatch
- **jamClient** lives in Context API (for WebSocket/native client connection)
- **Redux** stores application state (session data, tracks, UI state)
- The service layer (Redux thunk) incorrectly assumed jamClient was in Redux

### Correct Pattern
Components should:
1. Get jamClient from Context: `useJamServerContext()`
2. Get sessionId from Redux: `useSelector(selectSessionId)`
3. Pass BOTH to the service: `dispatch(syncTracksToServer(sessionId, jamClient))`

This follows **separation of concerns**:
- Context manages external connections
- Redux manages application state
- Services coordinate between them

---

## 📊 Impact Summary

**Files Changed:** 5
- 1 service file
- 2 component files
- 1 hook file
- 1 test file

**Lines Changed:** ~30 lines (excluding tests)

**Breaking Changes:** None (internal refactor only)

**Backward Compatibility:** ✅ Maintained

---

**Generated:** 2026-01-21
**Status:** ✅ Bug Fixed | 🔄 Manual Testing Required | ⏳ Integration Tests Pending
