---
phase: 26-jamtrack-polish
plan: 02
type: execute
wave: 2
depends_on: ["26-01"]
files_modified:
  - jam-ui/src/store/features/mediaSlice.js
  - jam-ui/src/components/client/JKSessionJamTrackPlayer.js
autonomous: true

must_haves:
  truths:
    - "No console warnings about leaked callbacks when closing JamTrack"
    - "User sees loading indicator while backend processes track (not premature controls)"
  artifacts:
    - path: "jam-ui/src/store/features/mediaSlice.js"
      provides: "Callback cleanup function for jamTrackDownload* globals"
      contains: "cleanupJamTrackCallbacks"
    - path: "jam-ui/src/components/client/JKSessionJamTrackPlayer.js"
      provides: "Callback cleanup on unmount and deferred controls rendering"
      contains: "cleanupJamTrackCallbacks"
  key_links:
    - from: "JKSessionJamTrackPlayer.js"
      to: "mediaSlice.js"
      via: "dispatch(cleanupJamTrackCallbacks()) in useEffect cleanup"
      pattern: "cleanupJamTrackCallbacks"
    - from: "JKSessionJamTrackPlayer.js render"
      to: "downloadState.state"
      via: "conditional rendering of controls"
      pattern: "downloadState\\.state.*synchronized"
---

<objective>
Add callback cleanup for JamTrack download globals and defer player controls rendering until track is ready.

Purpose: Prevent memory leaks from orphaned window callbacks when closing JamTrack, and fix the UX issue where player controls appear before the track is actually ready to play.
Output: Clean callback lifecycle and proper loading state management for JamTrack player.
</objective>

<execution_context>
@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/nuwan/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/26-jamtrack-polish/26-01-SUMMARY.md
@jam-ui/src/store/features/mediaSlice.js
@jam-ui/src/components/client/JKSessionJamTrackPlayer.js
@jam-ui/src/hooks/useRecordingHelpers.js (lines 465-498 for cleanup pattern)
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add callback cleanup action to mediaSlice</name>
  <files>jam-ui/src/store/features/mediaSlice.js</files>
  <action>
The downloadJamTrack thunk (lines 234-244) sets window globals but never cleans them up:
- window.jamTrackDownloadProgress
- window.jamTrackDownloadSuccess
- window.jamTrackDownloadFail

Add a new action to clean up these callbacks. Add this after the existing slice actions (around line 400-450 where other actions are defined):

```javascript
// Add to slice actions or as a standalone function
export const cleanupJamTrackCallbacks = () => {
  // Clean up global callbacks set during download
  if (typeof window !== 'undefined') {
    delete window.jamTrackDownloadProgress;
    delete window.jamTrackDownloadSuccess;
    delete window.jamTrackDownloadFail;
  }
};
```

This follows the same pattern used in useRecordingHelpers.js (lines 488-496) for cleaning up window.JK callbacks.

Note: This is a simple function, not a thunk, since it doesn't need dispatch or getState.
  </action>
  <verify>
Grep for the cleanup function:
```bash
grep -n "cleanupJamTrackCallbacks" jam-ui/src/store/features/mediaSlice.js
```
Should find the export and implementation.
  </verify>
  <done>mediaSlice exports cleanupJamTrackCallbacks function that removes window.jamTrackDownload* globals</done>
</task>

<task type="auto">
  <name>Task 2: Call cleanup on unmount and defer controls rendering</name>
  <files>jam-ui/src/components/client/JKSessionJamTrackPlayer.js</files>
  <action>
Two changes needed:

**1. Import and call cleanup on unmount:**

Add to imports (around line 4-9):
```javascript
import {
  loadJamTrack,
  checkJamTrackSync,
  closeJamTrack,
  setJamTrackState,
  setDownloadState,
  cleanupJamTrackCallbacks  // Add this
} from '../../store/features/mediaSlice';
```

Update the cleanup useEffect (around lines 163-172) to also call cleanup:
```javascript
useEffect(() => {
  return () => {
    mountedRef.current = false;

    if (fqIdRef.current && jamClient) {
      dispatch(closeJamTrack({ fqId: fqIdRef.current, jamClient }));
      dispatch(clearOpenJamTrack());
    }

    // Clean up download callbacks
    cleanupJamTrackCallbacks();
  };
}, [dispatch, jamClient]);
```

**2. Defer controls rendering until track is ready:**

Find the Controls Section (around line 720) and wrap it in a condition that checks if the track is ready:

```jsx
{/* Controls Section - only show when not in loading/download states */}
{(downloadState.state === 'idle' || downloadState.state === 'synchronized') && !isLoadingSync && (
  <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
    {/* ... existing controls ... */}
  </div>
)}
```

This ensures the playback controls only appear when:
- downloadState is 'idle' (local file already synced) OR 'synchronized' (download complete)
- AND not in initial loading state

The download progress UI (lines 593-711) already shows during download states, so this creates proper UX flow: loading -> download progress -> controls appear.
  </action>
  <verify>
```bash
# Check cleanup is imported and called
grep -n "cleanupJamTrackCallbacks" jam-ui/src/components/client/JKSessionJamTrackPlayer.js

# Check conditional rendering is in place
grep -n "downloadState.state.*synchronized" jam-ui/src/components/client/JKSessionJamTrackPlayer.js
```
Both should return matches.
  </verify>
  <done>JKSessionJamTrackPlayer calls cleanupJamTrackCallbacks on unmount and only renders controls when track is ready</done>
</task>

<task type="auto">
  <name>Task 3: Build verification and manual test notes</name>
  <files>None (verification only)</files>
  <action>
Run build to verify no syntax errors:
```bash
cd jam-ui && npm run build
```

Document manual test steps for verification:
1. Open a JamTrack that needs downloading
2. Observe: Loading indicator shown, controls hidden
3. Wait for download to complete
4. Observe: Controls appear after download
5. Close JamTrack window
6. Open browser console, verify no warnings about leaked callbacks
7. Verify no "state update on unmounted component" warnings
  </action>
  <verify>
Build command exits with code 0 and no TypeScript/compilation errors.
  </verify>
  <done>Build passes with no errors, manual test steps documented</done>
</task>

</tasks>

<verification>
1. Build check: `cd jam-ui && npm run build` completes without errors
2. Code verification:
   - mediaSlice.js exports cleanupJamTrackCallbacks
   - JKSessionJamTrackPlayer.js imports and calls cleanupJamTrackCallbacks in cleanup
   - JKSessionJamTrackPlayer.js conditionally renders controls based on downloadState
3. No eslint errors in modified files
</verification>

<success_criteria>
- cleanupJamTrackCallbacks function exists and is exported from mediaSlice
- JKSessionJamTrackPlayer calls cleanup function on unmount
- Player controls only render when downloadState is 'idle' or 'synchronized'
- Build passes with no errors
- No console warnings about leaked callbacks or unmounted component updates
</success_criteria>

<output>
After completion, create `.planning/phases/26-jamtrack-polish/26-02-SUMMARY.md`
</output>
