---
phase: 02-backing-track-seek-controls
type: execute
---

<objective>
Make the backing track seek bar functional by wiring it to real playback position and implementing drag-to-position capability using jamClient.SessionTrackSeekMs().

Purpose: Complete the player's seek functionality, matching the legacy web implementation's behavior while using modern React patterns. This builds directly on Phase 1's monitoring foundation.

Output: Backing track player with a fully functional seek slider that reflects current position and allows users to seek to any position by dragging.
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-backing-track-playback-monitoring/01-01-SUMMARY.md
@jam-ui/src/components/client/JKSessionBackingTrackPlayer.js

**Codebase conventions:**
- React functional components with hooks (React 16.13.1)
- camelCase for functions and variables
- Single quotes for strings (Prettier enforced)
- 2-space indentation

**Legacy implementation reference:**
- web/app/assets/javascripts/playbackControls.js: Slider calculation (lines 142-150, 403-410)
- jamClient method: SessionTrackSeekMs(positionMs) - Seeks to specific position in milliseconds
- Formula: `positionMs = (sliderValue / sliderMax) * durationMs`
- Slider updates during playback: `xPos = (timeMs / durationMs) * sliderWidth`

**Phase 1 foundation:**
- formatTime utility converts milliseconds to M:SS (lines 25-31)
- Polling useEffect updates currentTime and duration every 500ms (lines 50-83)
- Duration state available in milliseconds (before formatting)
- currentTime state available (formatted as M:SS)

**Current state:**
- Seek slider hardcoded: min="0" max="100" value="0" (lines 256-269 popup, 352-364 modal)
- handleSeek is placeholder: console.log('Seek to:', e.target.value) (line 147)
- Slider doesn't reflect current position
- Dragging slider has no effect

**Vision:**
- Slider value reflects current playback position (moves as track plays)
- Dragging slider seeks to that position immediately
- Slider range is 0 to duration in milliseconds
- Works during playback and when paused
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add state for position in milliseconds</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Add state to track current position in milliseconds (separate from formatted currentTime string). This allows the slider to use numeric values while still displaying formatted time.

Implementation:
- Add state after line 21: `const [currentPositionMs, setCurrentPositionMs] = useState(0);`
- Add state after line 21: `const [durationMs, setDurationMs] = useState(0);`
- Modify initialization useEffect (lines 33-48) to set both:
  ```javascript
  const durationInMs = jamClient.SessionGetTracksPlayDurationMs();
  setDurationMs(durationInMs);
  setDuration(formatTime(durationInMs));
  setCurrentPositionMs(0);
  ```
- Modify polling useEffect (lines 50-83) to set both:
  ```javascript
  const positionMs = jamClient.SessionCurrrentPlayPosMs();
  const durationInMs = jamClient.SessionGetTracksPlayDurationMs();
  setCurrentPositionMs(positionMs);
  setCurrentTime(formatTime(positionMs));
  setDurationMs(durationInMs);
  setDuration(formatTime(durationInMs));
  ```
- Modify handleStop (lines 101-109) to reset position: `setCurrentPositionMs(0);`

This separates concerns: formatted strings for display, milliseconds for slider calculations.</action>
  <verify>Check state initialization:
- Open backing track player
- Verify durationMs state is set (use React DevTools)
- Click play, verify currentPositionMs increments every 500ms
- Click stop, verify currentPositionMs resets to 0</verify>
  <done>State exists for currentPositionMs and durationMs, properly updated by initialization and polling useEffects</done>
</task>

<task type="auto">
  <name>Task 2: Wire slider to reflect current position</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Update the seek slider inputs to use real values from state instead of hardcoded values.

Popup version (lines 256-269):
```javascript
<input
  type="range"
  min="0"
  max={durationMs || 100}
  value={currentPositionMs}
  onChange={handleSeek}
  style={{ flex: 1 }}
  disabled={!backingTrack}
/>
```

Modal version (lines 352-364):
```javascript
<input
  type="range"
  min="0"
  max={durationMs || 100}
  value={currentPositionMs}
  onChange={handleSeek}
  style={{ flex: 1 }}
  disabled={!backingTrack}
/>
```

Key changes:
- max={durationMs || 100} - Use actual duration, fallback to 100 if not loaded
- value={currentPositionMs} - Reflects current position (updates via polling)
- Keep onChange={handleSeek} (will implement in Task 3)

This makes the slider "live" - it will automatically move as playback progresses due to the polling useEffect updating currentPositionMs.</action>
  <verify>Play backing track:
- Verify slider knob moves smoothly from left to right as track plays
- Slider should be at ~20% when currentTime shows ~1:00 of a 5:00 track
- Pause playback, verify slider stops moving
- Resume playback, verify slider continues moving
- Slider fills completely when track finishes</verify>
  <done>Slider visually reflects current playback position, moves during playback, accurate to within 500ms</done>
</task>

<task type="auto">
  <name>Task 3: Implement functional handleSeek</name>
  <files>jam-ui/src/components/client/JKSessionBackingTrackPlayer.js</files>
  <action>Replace the placeholder handleSeek function (line 147) with functional implementation that calls jamClient.SessionTrackSeekMs().

Implementation:
```javascript
const handleSeek = async (e) => {
  const seekPositionMs = parseInt(e.target.value);

  try {
    // Update local state immediately for responsive UI
    setCurrentPositionMs(seekPositionMs);
    setCurrentTime(formatTime(seekPositionMs));

    // Seek the native client to the new position
    await jamClient.SessionTrackSeekMs(seekPositionMs);
  } catch (error) {
    console.error('Error seeking:', error);
  }
};
```

Pattern explanation:
- Get seekPositionMs from slider value (already in milliseconds due to Task 2)
- Update state immediately (optimistic update for responsive UX)
- Call jamClient.SessionTrackSeekMs(seekPositionMs) to seek native player
- Error handling: log but don't crash

Note: The polling useEffect will sync the actual position within 500ms, correcting any drift.

IMPORTANT: Remove the placeholder console.log line entirely.</action>
  <verify>Test seek functionality:
- Open backing track, click play
- Drag slider to middle (e.g., 2:30 of 5:00 track)
- Verify playback jumps to that position (audio changes)
- Verify time display updates to match slider position
- Drag slider while paused - should work identically
- Drag to end, verify track near completion
- Drag to beginning, verify track restarts
- Check console for errors (should be none)</verify>
  <done>Seek slider fully functional, dragging seeks to position, works during playback and when paused, no console errors</done>
</task>

</tasks>

<verification>
Before declaring phase complete:
- [ ] Open backing track player, verify slider starts at far left (0:00)
- [ ] Click play, verify slider moves smoothly from left to right
- [ ] Verify slider position matches time display (e.g., ~40% at 2:00 of 5:00 track)
- [ ] Drag slider to middle while playing, verify playback jumps and audio changes
- [ ] Verify time display updates to match dragged position
- [ ] Pause playback, drag slider, verify seeking works when paused
- [ ] Drag to very end, verify track near completion (within 1 second)
- [ ] Drag to beginning (far left), verify track restarts from 0:00
- [ ] Check browser console for errors (should be none)
- [ ] Verify both modal and popup versions work identically
</verification>

<success_criteria>

- currentPositionMs and durationMs state exists and updates correctly
- Seek slider value reflects real playback position (live updates)
- Slider range is 0 to actual duration in milliseconds
- handleSeek calls jamClient.SessionTrackSeekMs() with correct position
- Seeking works during playback and when paused
- Audio playback position changes when slider is dragged
- Time display updates to match slider position
- No console errors during seek operations
- Functional parity with legacy web seek behavior
</success_criteria>

<output>
After completion, create `.planning/phases/02-backing-track-seek-controls/02-01-SUMMARY.md`:

# Phase 2 Plan 1: Backing Track Seek Controls Summary

**Functional seek slider with drag-to-position capability using jamClient.SessionTrackSeekMs()**

## Accomplishments

- Added currentPositionMs and durationMs state for numeric slider calculations
- Wired slider to reflect live playback position via polling updates
- Implemented handleSeek with jamClient.SessionTrackSeekMs() integration
- Slider now functional during both playback and pause

## Files Created/Modified

- `jam-ui/src/components/client/JKSessionBackingTrackPlayer.js` - Added position state, wired slider, implemented seek

## Decisions Made

- Separate state for milliseconds (slider calculations) vs formatted strings (display)
- Optimistic UI update before jamClient call for responsive UX
- Polling useEffect auto-corrects position drift within 500ms
- Works identically during playback and when paused (no special handling needed)

## Issues Encountered

(None expected - straightforward slider integration)

## Next Phase Readiness

Phase 3 (Backing Track Finalization) can now proceed. The player has complete core functionality (play/pause/stop, time display, seek). Phase 3 will add:
- Comprehensive error handling (jamClient failures, file not found, invalid formats)
- Edge cases (seek beyond duration, rapid seeks, seek during stop transition)
- Performance optimization (reduce polling frequency when not visible, cleanup on unmount)
- UI polish (loading states, disabled states during transitions)
</output>
