# JamTrack Loading Sequence and Freeze Issue - Research

**Project:** jam-cloud (JamKazam)
**Researched:** 2026-02-25
**Confidence:** HIGH

## Executive Summary

The JamTrack freeze issue is caused by **premature UI rendering** - stems appear in the session screen immediately upon selection, but the backend hasn't finished creating/downloading/syncing the track files. When the user clicks play before backend processes complete, the system enters a "checking sync status" state that blocks the UI.

**Root cause:** The current flow triggers UI updates (`setSelectedJamTrack`, `setJamTrackStems`) immediately when the JamTrack is selected, but the actual backend processing (packaging, downloading, keying) happens asynchronously during the first play attempt in `JKSessionJamTrackPlayer`.

**Correct sequence should be:** Select → Backend processing (hidden) → Show UI (stems + player) → Ready to play

## Problem Flow (Current - Broken)

```
1. User selects JamTrack from modal
   ↓
2. handleJamTrackSelect() in JKSessionScreen.js (line 1165-1190)
   - Fetches JamTrack data via REST API
   - IMMEDIATELY dispatches: setSelectedJamTrack(), setJamTrackStems(), setJamTrackData()
   ↓
3. UI updates IMMEDIATELY (line 1405-1431)
   - Stems appear in session screen
   - WindowPortal opens with player
   ↓
4. User clicks Play button
   ↓
5. JKSessionJamTrackPlayer handlePlay() (line 175-211)
   - Calls loadJamTrack thunk
   ↓
6. mediaSlice loadJamTrack() (line 17-49)
   - Checks if synchronized
   - If NOT synchronized → triggers downloadJamTrack()
   - Shows "checking sync status..." (line 602)
   ↓
7. mediaSlice downloadJamTrack() (line 51-266)
   - PACKAGING phase (line 123-223): enqueue mixdown, wait for server signing
   - DOWNLOADING phase (line 225-255): download via native client
   - KEYING phase (line 256-258): request decryption keys
   ↓
8. FREEZE occurs if:
   - Packaging takes too long (60s timeout)
   - Download fails
   - Keys not available
   - User interacts during process
```

## File Locations

### Core Components

| File | Purpose | Lines of Interest |
|------|---------|-------------------|
| `/jam-ui/src/components/client/JKSessionScreen.js` | Main session screen orchestrator | 1165-1190 (handleJamTrackSelect), 1405-1431 (stems rendering), 1746-1761 (WindowPortal) |
| `/jam-ui/src/components/client/JKSessionJamTrackPlayer.js` | JamTrack player component (WindowPortal content) | 69-160 (initialization), 175-211 (handlePlay), 602 ("checking sync status" text) |
| `/jam-ui/src/components/client/JKSessionJamTrackStems.js` | Renders stems in session screen | 5-49 (stem track mapping) |
| `/jam-ui/src/components/client/JKSessionJamTrackModal.js` | JamTrack selection modal | 44-52 (onSelect callback) |

### Redux State Management

| File | Purpose | Lines of Interest |
|------|---------|-------------------|
| `/jam-ui/src/store/features/mediaSlice.js` | Media operations (load, download, sync) | 17-49 (loadJamTrack), 51-266 (downloadJamTrack), 268-280 (checkJamTrackSync) |
| `/jam-ui/src/store/features/activeSessionSlice.js` | Session data (selected track, stems) | 172-178 (setSelectedJamTrack, setJamTrackStems), 211-217 (jamTrackData for player) |
| `/jam-ui/src/store/features/sessionUISlice.js` | UI state (modal visibility) | 51 (openJamTrack state) |

### Infrastructure

| File | Purpose | Lines of Interest |
|------|---------|-------------------|
| `/jam-ui/src/components/common/WindowPortal.js` | Popup window manager | 7 (windowFeatures for sizing) |
| `/jam-ui/src/helpers/rest.js` | API endpoints | 531-538 (getJamTrack), 935-945 (closeJamTrack) |

## Backend API Calls and Timing

### 1. Initial Selection (REST API - synchronous)

**Endpoint:** `GET /jamtracks/:id`
**Called by:** `handleJamTrackSelect()` in JKSessionScreen.js (line 1169)
**Purpose:** Fetch JamTrack metadata with stems/mixdowns
**Timing:** ~100-500ms
**Response includes:**
- JamTrack metadata (id, name, artist, etc.)
- Mixdowns array (master, custom-mix, stem types)
- Each mixdown has packages array (file_type, encrypt_type, sample_rate)
- Tracks array (stems with instruments)

### 2. Packaging Phase (REST + WebSocket - asynchronous)

**Endpoint:** `POST /jam_track_mixdowns/:id/enqueue`
**Called by:** `downloadJamTrack()` in mediaSlice.js (line 143-148)
**Purpose:** Request server-side package creation/signing
**Timing:** 5-30 seconds (can timeout at 60s)
**Process:**
1. Server queues mixdown for packaging
2. WebSocket subscription created for progress notifications (line 157-166)
3. Polling loop waits for `signing_state === 'SIGNED'` (line 179-221)
4. State updates shown as "Your JamTrack is currently being created" (line 603)

**Failure modes:**
- `SIGNING_TIMEOUT`, `QUEUED_TIMEOUT`, `QUIET_TIMEOUT` → error
- WebSocket not available → error (line 159-161)

### 3. Download Phase (Native Client - asynchronous)

**Method:** `jamClient.JamTrackDownload()`
**Called by:** `downloadJamTrack()` in mediaSlice.js (line 247-254)
**Purpose:** Download encrypted audio files to local storage
**Timing:** 10-60 seconds (depends on file size, network)
**Process:**
1. Native client initiates download with progress callbacks
2. Progress updates via `window.jamTrackDownloadProgress` (line 234-236)
3. Success triggers `window.jamTrackDownloadSuccess` (line 238-240)
4. Failure triggers `window.jamTrackDownloadFail` (line 242-244)

### 4. Keying Phase (Native Client - synchronous)

**Method:** `jamClient.JamTrackKeysRequest()`
**Called by:** `downloadJamTrack()` in mediaSlice.js (line 257)
**Purpose:** Request decryption keys for encrypted files
**Timing:** ~1-5 seconds
**State shown:** "Requesting decryption keys..." (line 605)

### 5. Play Phase (Native Client - synchronous)

**Method:** `jamClient.JamTrackPlay(fqId)`
**Called by:** `loadJamTrack()` in mediaSlice.js (line 39-42)
**Purpose:** Start audio playback
**Timing:** ~100-500ms
**Requires:** Files synchronized (trackDetail.key_state === 'AVAILABLE')

## Root Cause of Freeze

The freeze occurs because:

1. **Immediate UI update** (line 1175-1176 in JKSessionScreen.js):
   ```javascript
   dispatch(setSelectedJamTrack(jamTrackWithStems));
   dispatch(setJamTrackStems(jamTrackWithStems.tracks || []));
   ```
   This causes stems to render immediately at line 1405-1431.

2. **Conditional rendering check is wrong** (line 1406):
   ```javascript
   {selectedJamTrack && jamTrackStems.length > 0 &&
    (jamTrackDownloadState.state === 'synchronized' || jamTrackDownloadState.state === 'idle')
   ```
   The condition allows rendering when `state === 'idle'`, but "idle" means **not yet checked**, not "ready to play".

3. **Play button triggers long async process** (line 183-190):
   When user clicks play, if track isn't synchronized, it triggers packaging → downloading → keying, which can take 30-90 seconds. During this time:
   - UI shows "checking sync status..." (line 602)
   - User can't interact (buttons disabled via `isOperating` flag)
   - If process fails or times out, UI is stuck

4. **WindowPortal sizing issue**:
   Player window is hardcoded to `width=600,height=500` (line 1750), but player content width is `420px` (line 487 in JKSessionJamTrackPlayer.js), causing poor fit.

## Recommended Fix Approach

### Strategy: Backend-First Loading

**Goal:** Don't show UI until backend is ready.

### Phase 1: Defer UI Rendering

**Change `handleJamTrackSelect()` to:**
1. Fetch JamTrack metadata (as now)
2. Show loading indicator in modal/toast
3. **DON'T dispatch `setSelectedJamTrack` yet**
4. Trigger `loadJamTrack()` thunk (which includes sync check + download if needed)
5. Wait for `loadJamTrack()` to complete
6. **THEN** dispatch UI state: `setSelectedJamTrack`, `setJamTrackStems`, `setJamTrackData`
7. Close modal, show player + stems

**Code change location:** JKSessionScreen.js lines 1165-1190

```javascript
const handleJamTrackSelect = async (jamTrack) => {
  try {
    // 1. Fetch metadata
    const response = await getJamTrack({ id: jamTrack.id });
    const jamTrackWithStems = await response.json();

    // 2. Show loading
    toast.info('Preparing JamTrack...');

    // 3. Pre-load backend (sync check + download if needed)
    await dispatch(loadJamTrack({
      jamTrack: jamTrackWithStems,
      mixdownId: null, // Use default
      autoPlay: false, // Don't auto-play, just prepare
      jamClient,
      jamServer
    })).unwrap();

    // 4. NOW show UI (backend is ready)
    dispatch(setSelectedJamTrack(jamTrackWithStems));
    dispatch(setJamTrackStems(jamTrackWithStems.tracks || []));
    dispatch(setJamTrackData({
      jamTrack: jamTrackWithStems,
      session: currentSession,
      currentUser: currentUser
    }));

    toast.success(`Loaded JamTrack: ${jamTrackWithStems.name}`);
  } catch (error) {
    toast.error(`Failed to load JamTrack: ${error.message}`);
  }
};
```

### Phase 2: Fix Conditional Rendering

**Change line 1406 in JKSessionScreen.js:**

```javascript
// OLD (wrong):
{selectedJamTrack && jamTrackStems.length > 0 &&
  (jamTrackDownloadState.state === 'synchronized' || jamTrackDownloadState.state === 'idle')

// NEW (correct):
{selectedJamTrack && jamTrackStems.length > 0 &&
  jamTrackDownloadState.state === 'synchronized'
```

Remove the `|| jamTrackDownloadState.state === 'idle'` condition. UI should only show when explicitly synchronized.

### Phase 3: Fix WindowPortal Sizing

**Change line 1750 in JKSessionScreen.js:**

```javascript
// OLD:
windowFeatures="width=600,height=500,left=200,top=200,..."

// NEW (matches player content width):
windowFeatures="width=460,height=350,left=200,top=200,..."
```

Player content is 420px wide (line 487 in JKSessionJamTrackPlayer.js), so 460px window width provides proper padding.

### Phase 4: Progress Feedback During Prep

While backend is processing (packaging/downloading), show progress in the modal or as a toast:

**Option A: Modal shows progress**
- Keep modal open during prep
- Show packaging/downloading progress bars
- Close modal when ready

**Option B: Toast notifications**
- Close modal immediately
- Show toast: "Preparing JamTrack... (packaging)"
- Update toast: "Preparing JamTrack... (downloading 45%)"
- Final toast: "JamTrack ready!" → open player + stems

## "Create Custom Mix" Navigation

### Current Implementation

**Location:** JKSessionJamTrackPlayer.js line 860
**Current code:**
```javascript
<a onClick={() => console.log('TODO: Open custom mix creator')}>
  create custom mix
</a>
```

### Required Behavior

Should open `/jamtracks/{id}` in a new browser tab.

**Implementation:**

```javascript
<a
  onClick={() => {
    const url = `/jamtracks/${jamTrack.id}`;
    window.open(url, '_blank', 'noopener,noreferrer');
  }}
  style={{
    fontSize: '12px',
    color: '#007aff',
    textDecoration: 'none',
    cursor: 'pointer'
  }}
>
  create custom mix
</a>
```

**Route:** Already exists in routes.js line 36 (`/jamtracks`)

**Component:** Should navigate to JamTrack show page where user can create custom mixdowns (existing functionality).

## Testing Strategy

### Test 1: Fresh JamTrack (Not Synchronized)
1. Select JamTrack never downloaded before
2. **Expected:** Modal shows loading, then closes when ready
3. **Expected:** Stems + player appear simultaneously
4. **Expected:** Play button works immediately without "checking sync status"

### Test 2: Already Synchronized JamTrack
1. Select JamTrack already downloaded
2. **Expected:** Fast load (no packaging/downloading)
3. **Expected:** Stems + player appear quickly
4. **Expected:** Play button works immediately

### Test 3: Packaging Failure
1. Mock server error during packaging
2. **Expected:** Error toast shown
3. **Expected:** UI does NOT show stems/player (stays clean)
4. **Expected:** User can retry or select different track

### Test 4: WindowPortal Sizing
1. Open JamTrack player
2. **Expected:** Player fits properly in window (no excessive white space)
3. **Expected:** Controls are visible without scrolling

### Test 5: Create Custom Mix Link
1. Open JamTrack player
2. Click "create custom mix"
3. **Expected:** New tab opens to `/jamtracks/{id}`
4. **Expected:** User can create custom mixdown

## Related Issues

### WebSocket Subscription Management

**File:** mediaSlice.js lines 157-173
**Issue:** Subscription/unsubscription for packaging progress must be robust
**Current:** Uses polling + timeout
**Risk:** Memory leaks if unsubscribe not called on error/timeout

### Native Client Callback Management

**File:** mediaSlice.js lines 234-244
**Issue:** Global callbacks (`window.jamTrackDownloadProgress`) are fragile
**Risk:** Multiple JamTracks could conflict if callbacks overwrite each other

### Error Recovery

**Current:** User must close player and retry
**Better:** Provide "Retry" button in error state (already exists in player, line 363-379)

## Summary

**The freeze is NOT a native client bug.** It's a **UI state management issue** where the UI renders before the backend is ready.

**Fix priority:**
1. **HIGH:** Defer UI rendering until backend ready (Phase 1)
2. **HIGH:** Fix conditional rendering logic (Phase 2)
3. **MEDIUM:** Fix WindowPortal sizing (Phase 3)
4. **LOW:** Add "create custom mix" navigation (it's a TODO, not a bug)

**Estimated effort:**
- Phase 1: 2-3 hours (requires Redux thunk orchestration)
- Phase 2: 5 minutes (one line change)
- Phase 3: 5 minutes (one line change)
- Phase 4: 30 minutes (optional progress UI)
- Create custom mix: 5 minutes (one line change)

**Total:** 3-4 hours to fix freeze + sizing issues
