docs(04-01): document React integration patterns for JamTrack
- Mapped existing components (JKSessionJamTrackModal, stems, player) - Documented Redux state structure (mediaSlice, activeSessionSlice, sessionUISlice) - Captured hook patterns (useMediaActions, useSessionWebSocket) - Illustrated complete data flow from modal → download → playback - Compared JamTrack vs Backing Track patterns with gap analysis - Applied Phase 3 performance patterns (visibility-aware polling, useCallback) - Identified 20+ gaps for Phase 5 implementation
This commit is contained in:
parent
d3ff1f1477
commit
d402b22fc6
|
|
@ -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 && (
|
||||
<div className={`error-banner error-${error.type}`}>
|
||||
<span className="error-icon">⚠️</span>
|
||||
<span className="error-message">{error.message}</span>
|
||||
<div className="error-actions">
|
||||
<button onClick={dismissError}>Dismiss</button>
|
||||
{error.canRetry && <button onClick={retryOperation}>Retry</button>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
### Async Error Handling (Thunks)
|
||||
|
||||
```javascript
|
||||
export const loadJamTrack = createAsyncThunk(
|
||||
'media/loadJamTrack',
|
||||
async ({ jamTrack, jamClient }, { rejectWithValue }) => {
|
||||
try {
|
||||
// ... jamClient operations
|
||||
return { jamTrack, result };
|
||||
} catch (error) {
|
||||
return rejectWithValue({
|
||||
type: 'playback', // Error type categorization
|
||||
message: error.message,
|
||||
canRetry: false
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## 6. Performance Patterns (from Backing Track Phase 3)
|
||||
|
||||
### Visibility-Aware Polling
|
||||
|
||||
```javascript
|
||||
// Adjust polling frequency based on tab visibility
|
||||
useEffect(() => {
|
||||
const pollInterval = document.hidden ? 2000 : 500; // 500ms visible, 2s hidden
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
if (!jamClient.JamTrackIsPlaying()) return;
|
||||
|
||||
const position = parseInt(await jamClient.SessionCurrrentJamTrackPlayPosMs());
|
||||
const duration = parseInt(await jamClient.SessionGetJamTracksPlayDurationMs());
|
||||
|
||||
// Only update if values changed (lazy state updates)
|
||||
if (position !== currentPosition) {
|
||||
setCurrentPosition(position);
|
||||
}
|
||||
}, pollInterval);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, [document.hidden, currentPosition]);
|
||||
```
|
||||
|
||||
### useCallback for Handlers
|
||||
|
||||
```javascript
|
||||
// Prevent re-renders by memoizing all handler functions
|
||||
const handlePlay = useCallback(async () => {
|
||||
await jamClient.SessionStartPlay();
|
||||
setIsPlaying(true);
|
||||
}, [jamClient]);
|
||||
|
||||
const handlePause = useCallback(async () => {
|
||||
await jamClient.SessionPausePlay();
|
||||
setIsPlaying(false);
|
||||
}, [jamClient]);
|
||||
|
||||
const handleSeek = useCallback(async (positionMs) => {
|
||||
await jamClient.SessionJamTrackSeekMs(positionMs);
|
||||
}, [jamClient]);
|
||||
```
|
||||
|
||||
### Conditional State Updates
|
||||
|
||||
```javascript
|
||||
// Only update state if value actually changed
|
||||
const newPosition = parseInt(await jamClient.SessionCurrrentJamTrackPlayPosMs());
|
||||
if (newPosition !== currentPosition) {
|
||||
setCurrentPosition(newPosition);
|
||||
}
|
||||
```
|
||||
|
||||
### Consecutive Error Tracking
|
||||
|
||||
```javascript
|
||||
// Track consecutive errors with useRef (not state)
|
||||
const consecutiveErrorsRef = useRef(0);
|
||||
|
||||
const handlePollingError = () => {
|
||||
consecutiveErrorsRef.current += 1;
|
||||
|
||||
if (consecutiveErrorsRef.current >= 3) {
|
||||
setError({ type: 'network', message: 'Lost connection to jamClient' });
|
||||
stopPolling();
|
||||
}
|
||||
};
|
||||
|
||||
const handlePollingSuccess = () => {
|
||||
consecutiveErrorsRef.current = 0; // Reset on success
|
||||
};
|
||||
```
|
||||
|
||||
## 7. Established Patterns from Phase 3
|
||||
|
||||
### jamClient Integration
|
||||
|
||||
**Critical rules:**
|
||||
1. **jamClient returns Promises** - Always use `async/await`
|
||||
2. **jamClient returns strings** - Always `parseInt()` before math operations
|
||||
3. **Non-serializable objects** - Pass `jamClient` as prop, NOT stored in Redux
|
||||
4. **Cleanup on unmount** - Stop playback to prevent stale state
|
||||
|
||||
```javascript
|
||||
// WRONG - storing jamClient in Redux
|
||||
const initialState = {
|
||||
jamClient: null, // ❌ Redux requires serializable state
|
||||
};
|
||||
|
||||
// RIGHT - passing jamClient as prop
|
||||
const JKSessionJamTrackPlayer = ({ jamClient, jamTrack }) => {
|
||||
// Use jamClient directly, don't store in state
|
||||
};
|
||||
```
|
||||
|
||||
### Async Pattern
|
||||
|
||||
```javascript
|
||||
// jamClient calls are always async
|
||||
const duration = parseInt(await jamClient.SessionGetJamTracksPlayDurationMs());
|
||||
const position = parseInt(await jamClient.SessionCurrrentJamTrackPlayPosMs());
|
||||
const isPlaying = await jamClient.JamTrackIsPlaying();
|
||||
```
|
||||
|
||||
### Cleanup Pattern
|
||||
|
||||
```javascript
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
// Cleanup on unmount
|
||||
if (jamClient) {
|
||||
jamClient.JamTrackStopPlay();
|
||||
}
|
||||
};
|
||||
}, [jamClient]);
|
||||
```
|
||||
|
||||
## 8. Comparison to Backing Track Patterns
|
||||
|
||||
| Feature | Backing Track | JamTrack | Notes |
|
||||
|---------|---------------|----------|-------|
|
||||
| **File selection** | Native dialog (`ShowSelectBackingTrackDialog`) | React modal (`JKSessionJamTrackModal`) | JamTrack: existing modal ready |
|
||||
| **Loading** | Immediate (`SessionOpenBackingTrackFile`) | Async sync (`JamTrackDownload` → `JamTrackKeysRequest`) | JamTrack: requires download/sync widget |
|
||||
| **Playback API** | `SessionOpenBackingTrackFile(path)` | `JamTrackPlay(fqId)` | JamTrack: fqId format critical |
|
||||
| **Volume control** | `SessionSetTrackVolumeData(mixer.id, ...)` | Same (uses mixer system) | Same pattern |
|
||||
| **Loop control** | `SessionSetBackingTrackFileLoop(path, bool)` | TBD (needs investigation) | JamTrack: API unclear |
|
||||
| **Seek/position** | `SessionTrackSeekMs(ms)`, `SessionCurrentPlayPosMs()` | `SessionJamTrackSeekMs(ms)`, `SessionCurrrentJamTrackPlayPosMs()` | Similar APIs |
|
||||
| **Duration** | `SessionGetTracksPlayDurationMs()` | `SessionGetJamTracksPlayDurationMs()` | Both plural "Tracks" |
|
||||
| **Modal/popup mode** | Supported (Phase 3 fixed popup mode bugs) | Expected similar pattern | Apply Phase 3 fixes |
|
||||
| **Error handling** | 4 error types (file, network, playback, general) | 5 error types (+ sync) | JamTrack: add sync errors |
|
||||
| **Performance** | Visibility-aware polling, useCallback, lazy updates | Same patterns | Apply Phase 3 optimizations |
|
||||
|
||||
## 9. Gaps Identified for Phase 5 Implementation
|
||||
|
||||
### Redux State Gaps
|
||||
|
||||
- [ ] Expand `mediaSlice.jamTrackState` for player state (position, duration, isPlaying, error, isLoading)
|
||||
- [ ] Add download/sync state tracking (packaging, downloading, keying, synchronized)
|
||||
- [ ] Add selected mixdown state to `activeSessionSlice`
|
||||
- [ ] Fix `loadJamTrack` thunk to use correct fqId format (not just jamTrack.id)
|
||||
- [ ] Add `openJamTrack` to `sessionUISlice` for popup/modal mode support
|
||||
|
||||
### Component Gaps
|
||||
|
||||
- [ ] Create `JKSessionJamTrackPlayer` component (main player with playback controls)
|
||||
- [ ] Create or integrate download/sync progress widget (state machine visualization)
|
||||
- [ ] Verify `JKSessionJamTrackStems` completeness for stem mixer controls
|
||||
- [ ] Add mixdown selection dropdown to player UI
|
||||
|
||||
### Hook Gaps
|
||||
|
||||
- [ ] Create `useJamTrack` hook for selectors and mixdown operations
|
||||
- [ ] Add JAM_TRACK_CHANGES handling to `useSessionWebSocket`
|
||||
- [ ] Add MIXDOWN_CHANGES handling for packaging progress
|
||||
- [ ] Enhance `useMediaActions` with JamTrack-specific operations
|
||||
|
||||
### API Integration Gaps
|
||||
|
||||
- [ ] Implement download/sync flow with callbacks (JamTrackDownload)
|
||||
- [ ] Implement keying flow (JamTrackKeysRequest)
|
||||
- [ ] Handle WebSocket subscriptions for mixdown packaging (from legacy patterns)
|
||||
- [ ] Implement mixdown CRUD operations (create, edit, delete)
|
||||
- [ ] Verify loop control API for JamTracks (unclear from legacy code)
|
||||
|
||||
### Error Handling Gaps
|
||||
|
||||
- [ ] Add sync error type for download/packaging/keying failures
|
||||
- [ ] Implement timeout handling for packaging (state machine max times)
|
||||
- [ ] Add retry logic for network failures during download
|
||||
- [ ] Handle SIGNING_TIMEOUT, QUEUED_TIMEOUT, QUIET_TIMEOUT error states
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**React patterns established in jam-ui:**
|
||||
- Redux Toolkit with async thunks for jamClient integration
|
||||
- useMediaActions hook pattern for action wrappers
|
||||
- Modal components for file/track selection
|
||||
- WebSocket integration via useSessionWebSocket
|
||||
- Error handling with typed errors and user feedback
|
||||
- Performance optimizations from Phase 3
|
||||
|
||||
**Ready to use:**
|
||||
- `JKSessionJamTrackModal` - Selection UI exists and works
|
||||
- `useMediaActions` - Pattern established, needs JamTrack methods
|
||||
- Redux infrastructure - Slices exist, need expansion
|
||||
- Error patterns - Backing Track patterns apply directly
|
||||
- Performance patterns - Apply Phase 3 optimizations
|
||||
|
||||
**Primary work for Phase 5:**
|
||||
1. Create `JKSessionJamTrackPlayer` component (similar to Backing Track)
|
||||
2. Implement download/sync state machine in React
|
||||
3. Expand Redux state for JamTrack player and sync tracking
|
||||
4. Add WebSocket handlers for JAM_TRACK_CHANGES and MIXDOWN_CHANGES
|
||||
5. Implement mixdown selection and CRUD operations
|
||||
6. Fix `loadJamTrack` thunk to use correct fqId format
|
||||
|
||||
**Complexity factors vs. Backing Track:**
|
||||
- Download/sync flow (8-state machine vs. instant file open)
|
||||
- Mixdown selection (user chooses stem configuration)
|
||||
- WebSocket packaging progress (real-time server-side mixdown creation)
|
||||
- Encryption key management (separate API call after download)
|
||||
- JMEP support (tempo/pitch modifications)
|
||||
Loading…
Reference in New Issue