diff --git a/.planning/codebase/JAMTRACK_REACT_PATTERNS.md b/.planning/codebase/JAMTRACK_REACT_PATTERNS.md new file mode 100644 index 000000000..2b435ee8d --- /dev/null +++ b/.planning/codebase/JAMTRACK_REACT_PATTERNS.md @@ -0,0 +1,557 @@ +# JamTrack React Integration Patterns + +This document captures existing React patterns in jam-ui that will be extended for JamTrack implementation, building on lessons learned from the Backing Track player (Phase 3). + +## 1. Component Architecture + +### Existing Components + +**JKSessionJamTrackModal** (`jam-ui/src/components/client/JKSessionJamTrackModal.js`) +- **Purpose:** JamTrack selection modal with search/autocomplete (replaces jQuery dialog) +- **Current state:** Functional selection UI with REST API integration +- **Props:** `{isOpen, toggle, onJamTrackSelect}` +- **Features:** + - React autocomplete component (JKJamTracksAutoComplete) + - Pagination (10 items per page, matches legacy) + - Search by artist or song name + - REST API: `getPurchasedJamTracks(options)` +- **Usage:** Already integrated, ready for enhanced JamTrack loading workflow + +**JKSessionJamTrackStems** (`jam-ui/src/components/client/JKSessionJamTrackStems.js`) +- **Purpose:** Stem mixer view showing individual JamTrack channels +- **Current state:** Exists (needs verification of completeness) +- **Expected features:** Individual stem volume/pan/mute controls + +**JKJamTrackPlayer** (`jam-ui/src/components/jamtracks/JKJamTrackPlayer.js`) +- **Purpose:** Public JamTrack player with master/stems dropdown +- **Current state:** Exists (public-facing, not session player) +- **Note:** Different from session player needs + +### Missing Components (To Be Created in Phase 5) + +**JKSessionJamTrackPlayer** (NEW - primary work for Phase 5) +- **Purpose:** Session JamTrack player with playback controls +- **Similar to:** `JKSessionBackingTrackPlayer.js` (Phase 3 implementation) +- **Expected features:** + - Play/pause/stop buttons + - Seek bar with drag-to-position + - Duration and current time display (MM:SS format) + - Volume control (per Phase 3 patterns) + - Mixdown selection dropdown (master vs stems) + - Error handling with typed errors + - Loading states during sync/download + +**JKSessionJamTrackSync** (NEW - if separate component chosen) +- **Purpose:** Download/sync progress widget (replaces CoffeeScript DownloadJamTrack) +- **Alternative:** Could be integrated into JKSessionJamTrackPlayer +- **Features:** State machine visualization, progress bar, error handling + +## 2. Redux State Structure + +### Current State (mediaSlice.js) + +```javascript +// jam-ui/src/store/features/mediaSlice.js + +const initialState = { + // Media arrays (resolved/enriched data) + backingTracks: [], // Backing track list + jamTracks: [], // JamTrack stem list (after load) + recordedTracks: [], // Recorded track list + + // JamTrack real-time state (from WebSocket JAM_TRACK_CHANGES) + jamTrackState: {}, // Current: minimal state + downloadingJamTrack: false, + + // Loading states for async operations + loading: { + backingTrack: false, + jamTrack: false, + closing: false + }, + + error: null +}; +``` + +**Async thunks:** +- `openBackingTrack({ file, jamClient })` - Open backing track file +- `loadJamTrack({ jamTrack, jamClient })` - Load JamTrack (lines 16-40) + - Handles JMEP loading if present (tempo/pitch modifications) + - Calls `JamTrackPlay(jamTrack.id)` - **NOTE: Missing fqId format!** + - Returns `{jamTrack, result}` +- `closeMedia({ force, jamClient })` - Close all media + +**Identified gaps for Phase 5:** +- `jamTrackState` needs expansion for player state (playing, position, duration, error, isLoading) +- Missing sync/download state tracking (packaging, downloading, keying, synchronized) +- Missing selected mixdown state +- `loadJamTrack` thunk uses incorrect ID format (should be fqId, not just jamTrack.id) + +### Current State (activeSessionSlice.js) + +```javascript +// jam-ui/src/store/features/activeSessionSlice.js + +const initialState = { + selectedJamTrack: null, // Currently selected JamTrack for display + jamTrackStems: [], // Individual stem tracks from JamTrackGetTracks() + backingTrackData: null, // Backing track metadata + // ... other session state +}; +``` + +**Actions:** +- `setSelectedJamTrack(jamTrack)` - Set active JamTrack +- `setJamTrackStems(stems)` - Update stem list after load + +### Current State (sessionUISlice.js) + +```javascript +// jam-ui/src/store/features/sessionUISlice.js + +const initialState = { + // Modal visibility + showBackingTrackModal: false, + showJamTrackModal: false, // JamTrack selection modal + + // Mixdown/mix UI state + showMyMixes: false, // Show user's custom mixes + showCustomMixes: false, // Show custom mix editor + editingMixdownId: null, // Currently editing mixdown ID + creatingMixdown: false, // Creating new mixdown flag + createMixdownErrors: null, // Mixdown creation errors + + // Backing track player popup state + openBackingTrack: null, // {path, mode: 'popup'|'modal'} + // ... other UI state +}; +``` + +**Actions:** +- `setShowJamTrackModal(boolean)` - Toggle JamTrack selection modal +- `setShowMyMixes(boolean)` - Show/hide user mixes +- `setEditingMixdownId(id)` - Set mixdown being edited +- `setCreatingMixdown(boolean)` - Toggle mixdown creation mode + +**Identified gaps for Phase 5:** +- Missing `openJamTrack` equivalent to `openBackingTrack` for popup/modal mode +- Need JamTrack player modal visibility state +- Need download/sync progress modal state + +## 3. Hook Patterns + +### useMediaActions (jam-ui/src/hooks/useMediaActions.js) + +**Purpose:** Redux-based media action wrappers (replaces MediaContext) + +**Current implementation:** + +```javascript +const useMediaActions = () => { + const dispatch = useDispatch(); + const { jamClient } = useJamServerContext(); + + const openBackingTrack = useCallback(async (file) => { + await dispatch(openBackingTrackThunk({ file, jamClient })).unwrap(); + dispatch(updateMediaSummary({ backingTrackOpen: true, ... })); + }, [dispatch, jamClient]); + + const closeMedia = useCallback(async (force = false) => { + await dispatch(closeMediaThunk({ force, jamClient })).unwrap(); + dispatch(updateMediaSummary({ mediaOpen: false, ... })); + }, [dispatch, jamClient]); + + const openMetronome = useCallback(async (...) => { ... }, [...]); + + return { + openBackingTrack, + closeMedia, + openMetronome, + // ... other actions + }; +}; +``` + +**Pattern to follow for JamTrack:** +```javascript +const loadJamTrack = useCallback(async (jamTrack) => { + // 1. Dispatch async thunk + await dispatch(loadJamTrackThunk({ jamTrack, jamClient })).unwrap(); + + // 2. Update media summary + dispatch(updateMediaSummary({ + jamTrackOpen: true, + userNeedsMediaControls: true + })); +}, [dispatch, jamClient]); +``` + +### useJamTrack (Expected - To Be Created) + +**Purpose:** JamTrack-specific Redux selectors and operations + +**Expected shape:** +```javascript +const useJamTrack = () => { + const dispatch = useDispatch(); + const jamTrackState = useSelector(state => state.media.jamTrackState); + const selectedJamTrack = useSelector(state => state.activeSession.selectedJamTrack); + const jamTrackStems = useSelector(state => state.activeSession.jamTrackStems); + + // Mixdown operations + const selectMixdown = useCallback((mixdownId) => { ... }, [dispatch]); + const createMixdown = useCallback((mixdownData) => { ... }, [dispatch]); + const deleteMixdown = useCallback((mixdownId) => { ... }, [dispatch]); + + return { + jamTrackState, + selectedJamTrack, + jamTrackStems, + selectMixdown, + createMixdown, + deleteMixdown + }; +}; +``` + +### useSessionWebSocket (jam-ui/src/hooks/useSessionWebSocket.js) + +**Purpose:** WebSocket message handling with Redux dispatchers + +**Current pattern:** +```javascript +const useSessionWebSocket = () => { + // Handles MIXER_CHANGES WebSocket messages + // Dispatches to mixersSlice actions + + // Expected addition for JamTrack: + // JAM_TRACK_CHANGES messages → dispatch(updateJamTrackState(payload)) +}; +``` + +**WebSocket messages to handle:** +- `JAM_TRACK_CHANGES`: Real-time JamTrack state updates (playing, position) +- `MIXDOWN_CHANGES`: Mixdown packaging progress (signing_state, current_packaging_step) + +## 4. Data Flow + +### Complete JamTrack Load Flow + +``` +1. User Action → Modal Selection + ├─ User clicks "Open JamTrack" menu item + ├─ dispatch(setShowJamTrackModal(true)) + └─ JKSessionJamTrackModal renders + +2. Modal Selection → JamTrack Choice + ├─ User searches/selects JamTrack from purchased list + ├─ REST API: getPurchasedJamTracks(options) + └─ onJamTrackSelect(jamTrack) callback + +3. Redux Dispatch → Async Thunk + ├─ dispatch(loadJamTrackThunk({ jamTrack, jamClient })) + ├─ Thunk checks sync state via JamTrackGetTrackDetail(fqId) + └─ If not synchronized, initiate download/sync flow + +4. Download/Sync Flow (if needed) + ├─ State: packaging → downloading → keying → synchronized + ├─ JamTrackDownload(jamTrackId, mixdownId, userId, callbacks) + ├─ WebSocket MIXDOWN_CHANGES → UI progress updates + └─ JamTrackKeysRequest() → fetch encryption keys + +5. JamClient Call → Load JamTrack + ├─ sampleRate = jamClient.GetSampleRate() // 44 or 48 + ├─ fqId = `${jamTrack.id}-${sampleRateStr}` + ├─ if (jamTrack.jmep) jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep) + └─ result = jamClient.JamTrackPlay(fqId) + +6. WebSocket Update → State Sync + ├─ JAM_TRACK_CHANGES message received + ├─ dispatch(updateJamTrackState(payload)) + └─ dispatch(setJamTrackStems(stems)) + +7. UI Update → Player Renders + ├─ useSelector reads jamTrackState from Redux + ├─ JKSessionJamTrackPlayer re-renders with new state + └─ Playback controls enabled +``` + +**Flowchart:** +``` +[Open Menu] → [JamTrack Modal] → [Select JamTrack] + ↓ + [Check Sync State via jamClient] + ↓ + ┌───────────────────┴───────────────────┐ + ↓ ↓ + [Synchronized] [Not Synchronized] + ↓ ↓ + [Load JamTrack] [Download/Sync Widget] → [Progress UI] + ↓ ↓ + [JamTrackPlay(fqId)] [JamTrackDownload(...)] + ↓ ↓ + [WebSocket JAM_TRACK_CHANGES] [WebSocket MIXDOWN_CHANGES] + ↓ ↓ + [Redux State Update] [Packaging → Downloading → Keying] + ↓ ↓ + [Player UI Renders] [JamTrackKeysRequest()] + ↓ ↓ + [Playback Controls Active] [Synchronized] → [Load Flow] +``` + +## 5. Error Handling Strategy + +### Error Types (from Backing Track Phase 3) + +Apply same error categorization to JamTrack: + +| Type | Color | Retry | Use Cases | +|------|-------|-------|-----------| +| `file` | Red | Yes | Failed to fetch JamTrack details, invalid fqId | +| `network` | Red | Yes | Lost connection during download, WebSocket disconnect | +| `playback` | Yellow | No | Failed to start playback, seek error | +| `sync` | Yellow | Yes | Download timeout, keying timeout, packaging error | +| `general` | Yellow | No | jamClient not available, unknown error | + +### Error UI Pattern + +```jsx +// Banner/alert above player controls +{error && ( +