---
phase: 27-backing-track-sync
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - jam-ui/src/components/client/JKSessionScreen.js
  - jam-ui/src/components/client/JKSessionBackingTrackPlayer.js
autonomous: true

must_haves:
  truths:
    - "Opening a backing track file shows the track in session screen (not just popup)"
    - "No 'state update on unmounted component' warnings when closing backing track quickly"
  artifacts:
    - path: "jam-ui/src/components/client/JKSessionScreen.js"
      provides: "handleBackingTrackSelected using openBackingTrack action"
      contains: "openBackingTrack(result.file)"
    - path: "jam-ui/src/components/client/JKSessionBackingTrackPlayer.js"
      provides: "Duration fetch useEffect with cleanup"
      contains: "let ignore = false"
  key_links:
    - from: "JKSessionScreen.js handleBackingTrackSelected"
      to: "useMediaActions openBackingTrack"
      via: "async await call"
      pattern: "await openBackingTrack\\(result\\.file\\)"
    - from: "JKSessionBackingTrackPlayer.js useEffect"
      to: "cleanup function"
      via: "return statement"
      pattern: "return.*ignore = true"
---

<objective>
Fix backing track sync to session screen and prevent unmounted component warnings.

Purpose: When users open a backing track, it should appear in the session screen (like JamTrack does), and closing the popup quickly should not produce React warnings.

Output: Two fixes in two files - use Redux action for track sync, add ignore flag for async cleanup.
</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/phases/27-backing-track-sync/27-RESEARCH.md

# Source files to modify
@jam-ui/src/components/client/JKSessionScreen.js
@jam-ui/src/components/client/JKSessionBackingTrackPlayer.js

# Reference for correct pattern (JamTrack uses similar flow)
@jam-ui/src/hooks/useMediaActions.js
</context>

<tasks>

<task type="auto">
  <name>Task 1: Use openBackingTrack action in handleBackingTrackSelected</name>
  <files>jam-ui/src/components/client/JKSessionScreen.js</files>
  <action>
In the `handleBackingTrackSelected` function (around line 1137), replace the direct `jamClient.SessionOpenBackingTrackFile()` call with the `openBackingTrack()` action from useMediaActions.

Current (incorrect):
```javascript
const handleBackingTrackSelected = async (result) => {
  try {
    await jamClient.SessionOpenBackingTrackFile(result.file, false);
    dispatch(setBackingTrackData({...}));
    dispatch(openModal('backingTrack'));
  } catch (error) {
    toast.error('Failed to open backing track');
  }
};
```

Change to (correct):
```javascript
const handleBackingTrackSelected = async (result) => {
  try {
    // Use the openBackingTrack action from useMediaActions (already imported at line 153)
    // This handles: jamClient call, Redux state update, and server sync
    await openBackingTrack(result.file);

    // Set popup data (same as before)
    dispatch(setBackingTrackData({
      backingTrack: result.file,
      session: currentSession,
      currentUser: currentUser
    }));
    dispatch(openModal('backingTrack'));
  } catch (error) {
    toast.error('Failed to open backing track');
  }
};
```

The `openBackingTrack` is already destructured from `useMediaActions()` at line 153. This change ensures:
1. jamClient.SessionOpenBackingTrackFile is called (via the thunk)
2. mediaSummary.backingTrackOpen is set to true
3. syncTracksToServer is called to sync track to session screen
  </action>
  <verify>
1. Grep for the change: `grep -n "openBackingTrack(result.file)" jam-ui/src/components/client/JKSessionScreen.js`
2. Verify jamClient.SessionOpenBackingTrackFile is NOT called directly in handleBackingTrackSelected
3. Run: `cd jam-ui && npm run build` - should complete without errors
  </verify>
  <done>
- handleBackingTrackSelected calls `await openBackingTrack(result.file)` instead of direct jamClient call
- Build succeeds
- No direct jamClient.SessionOpenBackingTrackFile call in handleBackingTrackSelected
  </done>
</task>

<task type="auto">
  <name>Task 2: Add ignore flag to duration fetch useEffect</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>
In the duration fetch useEffect (around line 90-143), add an ignore flag to prevent state updates after unmount.

Current structure (no cleanup):
```javascript
useEffect(() => {
  const shouldInitialize = (isOpen || isPopup) && backingTrack && jamClient;
  if (shouldInitialize) {
    setIsPlaying(false);
    setCurrentTime('0:00');
    setCurrentPositionMs(0);

    const fetchDuration = async () => {
      setIsLoadingDuration(true);
      try {
        // ... async jamClient calls ...
        setDurationMs(validDuration);
        setDuration(formatTime(validDuration));
        setIsLoadingDuration(false);
      } catch (error) {
        // ... state updates ...
      }
    };
    fetchDuration();
  }
}, [deps]);
```

Change to (with ignore flag):
```javascript
useEffect(() => {
  let ignore = false;

  const shouldInitialize = (isOpen || isPopup) && backingTrack && jamClient;
  if (shouldInitialize) {
    setIsPlaying(false);
    setCurrentTime('0:00');
    setCurrentPositionMs(0);

    const fetchDuration = async () => {
      if (ignore) return;
      setIsLoadingDuration(true);

      try {
        if (!jamClient) {
          if (!ignore) {
            handleError('general', 'Audio engine not available', null);
            setIsLoadingDuration(false);
          }
          return;
        }

        const durationInMs = await jamClient.SessionGetTracksPlayDurationMs();

        // Check ignore AFTER async operation
        if (ignore) return;

        const validDuration = parseInt(durationInMs, 10) || 0;
        if (validDuration === 0 || isNaN(validDuration)) {
          handleError('file', 'Failed to load track duration. The file may be invalid or unsupported.', null);
          setDuration('0:00');
          setDurationMs(0);
          setIsLoadingDuration(false);
          return;
        }

        setDurationMs(validDuration);
        setDuration(formatTime(validDuration));
        clearError();
        setIsLoadingDuration(false);
      } catch (error) {
        if (!ignore) {
          handleError('file', 'Failed to load track duration', error);
          setDuration('0:00');
          setDurationMs(0);
          setIsLoadingDuration(false);
        }
      }
    };

    fetchDuration();
  }

  return () => {
    ignore = true;
  };
}, [isOpen, isPopup, backingTrack, jamClient, handleError, formatTime, clearError]);
```

Key changes:
1. Add `let ignore = false;` at start of useEffect
2. Check `if (ignore) return;` before initial state updates in fetchDuration
3. Check `if (ignore) return;` after the await call (the critical point)
4. Wrap all state updates in catch block with `if (!ignore)`
5. Add cleanup function `return () => { ignore = true; };`
  </action>
  <verify>
1. Grep for the pattern: `grep -n "let ignore = false" jam-ui/src/components/client/JKSessionBackingTrackPlayer.js`
2. Grep for cleanup: `grep -n "ignore = true" jam-ui/src/components/client/JKSessionBackingTrackPlayer.js`
3. Run: `cd jam-ui && npm run build` - should complete without errors
  </verify>
  <done>
- useEffect has `let ignore = false;` declaration
- useEffect has cleanup function that sets `ignore = true`
- All state updates after async operations are guarded with `if (!ignore)` or `if (ignore) return;`
- Build succeeds
  </done>
</task>

</tasks>

<verification>
After both tasks complete:

1. **Build verification:**
   ```bash
   cd jam-ui && npm run build
   ```
   Should complete without errors.

2. **Code verification:**
   ```bash
   # BT-01: Verify openBackingTrack is used
   grep -n "openBackingTrack(result.file)" jam-ui/src/components/client/JKSessionScreen.js

   # BT-02: Verify ignore flag pattern
   grep -n "let ignore = false" jam-ui/src/components/client/JKSessionBackingTrackPlayer.js
   grep -n "ignore = true" jam-ui/src/components/client/JKSessionBackingTrackPlayer.js
   ```

3. **No regressions:**
   - Backing track popup should still open when selecting a file
   - Existing functionality preserved
</verification>

<success_criteria>
1. `handleBackingTrackSelected` uses `await openBackingTrack(result.file)` - enables session screen sync
2. Duration fetch useEffect has ignore flag with cleanup - prevents unmount warnings
3. `npm run build` succeeds with no errors
4. No removed functionality (popup still works, duration still displays)
</success_criteria>

<output>
After completion, create `.planning/phases/27-backing-track-sync/27-01-SUMMARY.md`
</output>
