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 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-02-26 16:55:35 +05:30
parent 12ce12420f
commit c7f6480137
2 changed files with 269 additions and 3 deletions

View File

@ -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 | - |
---

View File

@ -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"
---
<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>