---
phase: 05-jamtrack-implementation
plan: 01
type: execute
---

<objective>
Establish Redux infrastructure for JamTrack with critical bug fix and state structure extensions.

Purpose: Fix existing fqId bug that blocks all JamTrack functionality, then add Redux state structure needed for download/sync machine, mixdown selection, and UI preferences.
Output: Redux slices extended with JamTrack state, bug fixed, ready for async thunks.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-phase.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-jamtrack-implementation/05-CONTEXT.md
@.planning/phases/04-jamtrack-research-design/04-02-SUMMARY.md
@.planning/phases/04-jamtrack-research-design/REDUX_DESIGN.md
@.planning/codebase/CONVENTIONS.md
@.planning/codebase/JAMTRACK_API.md
@jam-ui/src/store/features/mediaSlice.js
@jam-ui/src/store/features/sessionUISlice.js

**Tech stack available:**
- React 16.13.1
- Redux Toolkit 1.6.1
- jamClient (C++ native client proxy via QWebChannel)
- WebSocket Protocol Buffer messaging

**Established patterns from Phase 3 & 4:**
- jamClient passed as prop (non-serializable, never in Redux)
- jamClient returns Promises (always async/await)
- jamClient returns strings for position/duration (always parseInt)
- fqId format mandatory: `{jamTrackId}-{sampleRate}` for all JamTrack calls
- Cleanup on unmount required (prevent stale state)

**Constraining decisions:**
- Phase 4: Redux state split: jamTrackState (playback), downloadState (sync machine), availableMixdowns (mixdown cache)
- Phase 4: 6 async thunks needed: loadJamTrack (enhanced), downloadJamTrack, checkJamTrackSync, loadJMEP, seekJamTrack, closeJamTrack
- Phase 4: Enhanced loadJamTrack fixes existing bug: uses fqId format instead of jamTrack.id
- Phase 5 Context: All three pillars equally important (download UX, playback reliability, mixdown selection)
- Phase 5 Context: Match Backing Track player feel and quality

**Critical bug to fix:**
- mediaSlice.js line 29: `JamTrackPlay(jamTrack.id)` should be `JamTrackPlay(fqId)`
- This bug blocks ALL JamTrack playback - must fix in Task 1
</context>

<tasks>

<task type="auto">
  <name>Task 1: Fix loadJamTrack fqId bug and extend mediaSlice</name>
  <files>jam-ui/src/store/features/mediaSlice.js</files>
  <action>
**CRITICAL BUG FIX (line 29):**
Change `await jamClient.JamTrackPlay(jamTrack.id)` to `await jamClient.JamTrackPlay(fqId)` where fqId is already built on line 24.

**Then extend mediaSlice initialState:**
1. Replace `jamTrackState: {}` with full structure:
```javascript
jamTrackState: {
  isPlaying: false,
  isPaused: false,
  currentPositionMs: 0,
  durationMs: 0,
  selectedMixdownId: null,
  playbackMode: null, // 'master' | 'custom-mix' | 'stem'
  lastUpdate: null
}
```

2. Replace `downloadingJamTrack: false` with full downloadState:
```javascript
downloadState: {
  jamTrackId: null,
  mixdownId: null,
  fqId: null,
  state: 'idle', // 'idle' | 'checking' | 'downloading' | 'keying' | 'synchronized' | 'error'
  progress: 0,
  currentStep: 0,
  totalSteps: 0,
  error: null
}
```

**Add new reducers:**
- setJamTrackState(state, action) - Sets full jamTrackState from action.payload
- setDownloadState(state, action) - Sets full downloadState from action.payload
- clearDownloadState(state) - Resets downloadState to initial values

**Keep existing:**
- updateJamTrackState (for partial updates from WebSocket)
- clearJamTrackState (for cleanup)

**Do NOT add thunks yet** - that's Plan 2. Only fix bug and add state structure.
  </action>
  <verify>
1. grep "JamTrackPlay(fqId)" jam-ui/src/store/features/mediaSlice.js (not jamTrack.id)
2. grep "state: 'idle'" jam-ui/src/store/features/mediaSlice.js (downloadState exists)
3. grep "setDownloadState" jam-ui/src/store/features/mediaSlice.js (new reducer exists)
  </verify>
  <done>
- loadJamTrack bug fixed (uses fqId not jamTrack.id)
- jamTrackState extended with 7 fields (isPlaying, isPaused, currentPositionMs, durationMs, selectedMixdownId, playbackMode, lastUpdate)
- downloadState added with 8 fields (jamTrackId, mixdownId, fqId, state, progress, currentStep, totalSteps, error)
- 3 new reducers added (setJamTrackState, setDownloadState, clearDownloadState)
- No TypeScript/linter errors introduced
  </done>
</task>

<task type="auto">
  <name>Task 2: Extend activeSessionSlice and sessionUISlice</name>
  <files>jam-ui/src/store/features/activeSessionSlice.js, jam-ui/src/store/features/sessionUISlice.js</files>
  <action>
**In activeSessionSlice.js:**

Add to initialState:
```javascript
availableMixdowns: [], // Array of mixdown objects: { id, type: 'master'|'custom-mix'|'stem', name, jamTrackId, packageId }
activeMixdown: null, // Currently selected mixdown object
mixdownCache: {} // Map of packageId -> { metadata, sampleRate, fileType, encryptType }
```

Add reducers:
- setAvailableMixdowns(state, action) - Sets availableMixdowns array
- setActiveMixdown(state, action) - Sets activeMixdown object
- cacheMixdownPackage(state, action) - Adds/updates entry in mixdownCache: `state.mixdownCache[action.payload.packageId] = action.payload.metadata`
- clearMixdowns(state) - Resets all three to initial values

**In sessionUISlice.js:**

Add to initialState:
```javascript
openJamTrack: null, // Currently open JamTrack ID or null
jamTrackUI: {
  lastUsedMixdownId: null, // User's last selected mixdown for this session
  volume: 100 // Last used volume (0-100)
}
```

Add reducers:
- setOpenJamTrack(state, action) - Sets openJamTrack ID
- updateJamTrackUI(state, action) - Merges action.payload into jamTrackUI: `state.jamTrackUI = { ...state.jamTrackUI, ...action.payload }`
- clearOpenJamTrack(state) - Sets openJamTrack to null

**Do NOT add thunks** - only state structure and reducers.
  </action>
  <verify>
1. grep "availableMixdowns" jam-ui/src/store/features/activeSessionSlice.js (exists in state and reducer)
2. grep "openJamTrack" jam-ui/src/store/features/sessionUISlice.js (exists in state and reducer)
3. grep "setActiveMixdown" jam-ui/src/store/features/activeSessionSlice.js (reducer exists)
  </verify>
  <done>
- activeSessionSlice extended with availableMixdowns, activeMixdown, mixdownCache state + 4 reducers
- sessionUISlice extended with openJamTrack, jamTrackUI state + 3 reducers
- All reducers follow Redux Toolkit patterns (immutable updates)
- No TypeScript/linter errors introduced
  </done>
</task>

</tasks>

<verification>
Before declaring plan complete:
- [ ] Bug fix verified: loadJamTrack uses fqId not jamTrack.id
- [ ] All state structures added with correct field names and types
- [ ] All reducers added and follow Redux Toolkit patterns
- [ ] No errors when importing slices
</verification>

<success_criteria>

- loadJamTrack fqId bug fixed (critical blocker removed)
- mediaSlice extended: jamTrackState (7 fields), downloadState (8 fields), 3 new reducers
- activeSessionSlice extended: availableMixdowns, activeMixdown, mixdownCache, 4 new reducers
- sessionUISlice extended: openJamTrack, jamTrackUI, 3 new reducers
- All code follows existing patterns and conventions
- Redux state foundation ready for async thunks (Plan 2)
</success_criteria>

<output>
After completion, create `.planning/phases/05-jamtrack-implementation/05-01-SUMMARY.md`:

# Phase 5 Plan 1: Redux Infrastructure & Bug Fixes Summary

**[Substantive one-liner - what shipped]**

## Accomplishments

- Fixed critical loadJamTrack fqId bug
- Extended mediaSlice with jamTrackState and downloadState
- Extended activeSessionSlice with mixdown management state
- Extended sessionUISlice with JamTrack UI preferences

## Files Created/Modified

- `jam-ui/src/store/features/mediaSlice.js` - Bug fix + state extensions
- `jam-ui/src/store/features/activeSessionSlice.js` - Mixdown state
- `jam-ui/src/store/features/sessionUISlice.js` - UI preferences

## Decisions Made

[Key decisions and rationale]

## Issues Encountered

[Problems and resolutions, or "None"]

## Next Step

Ready for Plan 2: Async Thunks & Component Core
</output>
