From c7f64801378e76b359b37bfe7ac3c56475df89e8 Mon Sep 17 00:00:00 2001 From: Nuwan Date: Thu, 26 Feb 2026 16:55:35 +0530 Subject: [PATCH] docs(27): create phase plan for backing track sync Phase 27: Backing Track Sync - 1 plan in 1 wave - 2 tasks (both auto) - Ready for execution Co-Authored-By: Claude Opus 4.5 --- .planning/ROADMAP.md | 6 +- .../27-backing-track-sync/27-01-PLAN.md | 266 ++++++++++++++++++ 2 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 .planning/phases/27-backing-track-sync/27-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 8848b2aaa..80632d739 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -46,10 +46,10 @@ Plans: **Success Criteria** (what must be TRUE): 1. Opening a backing track file shows the track in session screen (not just popup) 2. No "state update on unmounted component" warnings when closing backing track quickly -**Plans**: TBD +**Plans**: 1 plan Plans: -- [ ] 27-01: TBD +- [ ] 27-01-PLAN.md - Use openBackingTrack action and add async cleanup ### Phase 28: Metronome Responsiveness **Goal**: Metronome controls respond smoothly to user input @@ -71,7 +71,7 @@ Plans: | Phase | Milestone | Plans Complete | Status | Completed | |-------|-----------|----------------|--------|-----------| | 26. JamTrack Polish | v1.6 | 4/4 | Complete | 2026-02-25 | -| 27. Backing Track Sync | v1.6 | 0/TBD | Not started | - | +| 27. Backing Track Sync | v1.6 | 0/1 | Planned | - | | 28. Metronome Responsiveness | v1.6 | 0/TBD | Not started | - | --- diff --git a/.planning/phases/27-backing-track-sync/27-01-PLAN.md b/.planning/phases/27-backing-track-sync/27-01-PLAN.md new file mode 100644 index 000000000..30d6cece2 --- /dev/null +++ b/.planning/phases/27-backing-track-sync/27-01-PLAN.md @@ -0,0 +1,266 @@ +--- +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" +--- + + +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. + + + +@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md +@/Users/nuwan/.claude/get-shit-done/templates/summary.md + + + +@.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 + + + + + + Task 1: Use openBackingTrack action in handleBackingTrackSelected + jam-ui/src/components/client/JKSessionScreen.js + +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 + + +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 + + +- handleBackingTrackSelected calls `await openBackingTrack(result.file)` instead of direct jamClient call +- Build succeeds +- No direct jamClient.SessionOpenBackingTrackFile call in handleBackingTrackSelected + + + + + Task 2: Add ignore flag to duration fetch useEffect + jam-ui/src/components/client/JKSessionBackingTrackPlayer.js + +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; };` + + +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 + + +- 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 + + + + + + +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 + + + +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) + + + +After completion, create `.planning/phases/27-backing-track-sync/27-01-SUMMARY.md` +