- Updated must_haves.truths to clarify that grouping state IS derived data
- Made Task 2 verification precise: exactly 13 useSelector calls expected
- Added verification that old individual selectors (lines 72-92) are removed
- Changed vague 'around 9-12' to exact count verification
- Add memo to React import
- Wrap component with memo(function JKSessionAudioInputs)
- Add displayName for React DevTools
- Prevents unnecessary re-renders when parent re-renders with same props
Phase 30: Component Memoization
- 1 plan in 1 wave
- Wrap JKSessionAudioInputs with React.memo
- Wrap JKSessionRemoteTracks with React.memo
- Ready for execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 30: Component Memoization
- React.memo API and patterns for React 16.13.1
- Prop stability requirements documented
- Integration with Phase 29 context memoization
- Common pitfalls: inline props, children, context
- Verification with React DevTools Profiler
- DisplayName for debugging best practices
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 29 verified and complete:
- MixersContext.Provider value memoized with useMemo
- VuContext separated from MixerConfigContext (Phase 28)
- 6 context consumers wrapped with React.memo
- useMixerHelper.getMixer stabilized with useCallback
Requirements satisfied: CTX-01, CTX-02, CTX-03
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tasks completed: 4/4
- Task 1a: Stabilize useMixerHelper function references
- Task 1b: Memoize MixersContext provider value
- Task 2: Memoize VuContext and GlobalContext provider values
- Task 3: Wrap context consumers with React.memo
SUMMARY: .planning/phases/29-context-optimization/29-01-SUMMARY.md
Accomplishments:
- Memoized all 3 context providers (MixersContext, VuContext, GlobalContext)
- Stabilized getMixer and dependent function references
- Wrapped 6 consumer components with React.memo
- Eliminated cascading re-renders from context value changes
Requirements completed:
- CTX-00: useMixerHelper.getMixer wrapped with useCallback
- CTX-01: MixersContext.Provider value is memoized
- CTX-03: Context consumers only re-render when data changes
Performance impact:
- Volume slider changes no longer re-render VU meters
- VU updates no longer re-render volume sliders
- Context consumers only re-render when their specific data changes
VuContext:
- Wrap combined value object with useMemo
- vuStore is stable module reference (not a dependency)
- Depends on vuHelpers
GlobalContext:
- Wrap provider value with useMemo
- Dependencies: all state and callback values
- useState setters are stable (not dependencies)
Both contexts now only update consumers when actual data changes,
preventing unnecessary re-renders across the component tree.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add useMemo wrapper around mixerHelper value
- Prevents new object creation on every render
- Depends on stable mixerHelper (from Task 1a)
- Context consumers now only re-render when data actually changes
This eliminates cascading re-renders when unrelated state updates occur.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Wrap getMixer with useCallback to ensure stable reference
- Prevents cascade of function recreations on every render
- Dependencies: mixMode (selector value)
- Uses allMixersRef.current (ref, not dependency)
This stabilization is prerequisite for effective context memoization.
All dependent functions (fillTrackVolumeObject, mute, faderChanged, etc.)
now automatically stabilize because their getMixer dependency is stable.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 28 verified and approved:
- VU data flows through external store (vuStore.js)
- VU components use refs for direct DOM updates
- React DevTools shows 0 re-renders from VU updates
- Session screen remains responsive
Requirements satisfied: VU-01, VU-02, VU-03
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tasks completed: 4/4
- Update useMixerStore to route VU data to external store
- Simplify useVuHelpers and VuContext
- Rewrite SessionTrackVU with direct DOM updates
- Human verification (approved)
Additional fix:
- getLevelSnapshot now checks pendingUpdates for immediate access
SUMMARY: .planning/phases/28-vu-meter-optimization/28-02-SUMMARY.md
The vuStore RAF loop only runs with subscribers, but SessionTrackVU
uses direct polling without subscribing. Changed getLevelSnapshot to
check pendingUpdates first for immediate access to latest VU data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove useVuContext - directly import vuStore
- Remove VuMeter component usage - render lights directly
- Add RAF loop polling vuStore.getLevelSnapshot at ~60fps
- Store light element refs for direct className assignment
- Wrap with React.memo to prevent parent re-renders
- Zero React re-renders for VU visual updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove useState from useVuHelpers (no React state for VU data)
- Remove updateVuState, removeVuState, updateVU3 functions
- Remove VuMeterComponent (replaced by direct DOM in SessionTrackVU)
- Expose vuStore methods via return object
- VuContext provides vuStore reference to consumers
- Keep legacy renderVU/updateVU for backward compatibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import vuStore in useMixerStore.js
- Replace mixerHelper.updateVU call with vuStore.updateLevel
- VU data now flows to external store instead of React state
- Native bridge still calls at 50-70/sec but updates are buffered
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tasks completed: 2/2
- Install useSyncExternalStore shim and create external VU store
- Create useVuStore React hooks
SUMMARY: .planning/phases/28-vu-meter-optimization/28-01-SUMMARY.md
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add useAllVuLevels hook for subscribing to all VU levels
- Add useVuLevel hook for efficient single-mixer subscription
- Use useSyncExternalStore shim for React 16 compatibility
- Memoize getSnapshot callback to avoid unnecessary resubscription
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Install use-sync-external-store shim for React 16 compatibility
- Create vuStore.js with external state management
- Implement RAF batching loop to limit updates to 60fps
- Add updateLevel, removeLevel, getSnapshot, getLevelSnapshot, subscribe, reset methods
- Store VU data outside React to avoid reconciliation overhead
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 28: VU Meter Optimization
- 2 plan(s) in 2 wave(s)
- 1 parallel (wave 1), 1 sequential (wave 2)
- Ready for execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 28: VU Meter Optimization
- Standard stack identified (use-sync-external-store shim, requestAnimationFrame)
- Architecture patterns documented (external store, direct DOM updates with refs)
- Pitfalls catalogued (getSnapshot object creation, rAF cleanup, layout thrashing)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Goals:
- Eliminate page freezes from excessive React re-renders
- Optimize VU meter updates (50-70/sec → batched RAF)
- Prevent cascading re-renders with memoization
- Apply React best practices for granular updates
Phases:
28. VU Meter Optimization
29. Context Optimization
30. Component Memoization
31. Selector Optimization
32. State Update Optimization
19 requirements across 5 phases.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed Phase 28 (Metronome Responsiveness) from v1.6 milestone.
Metronome responsiveness is satisfactory after Phase 26-27 improvements.
MET-01, MET-02 requirements moved to Out of Scope.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add media state tracking to JKSessionOpenMenu component
- Disable menu items when any media (JamTrack, Backing Track, Metronome) is open
- Show warning toast when user attempts to open another media type
- Update backing track and metronome track styling and icons
- Adjust close button styling in session screen media sections
- Handle cancelled file selection dialog for backing tracks
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The stylesheet copying code was accidentally removed in aa731c96d,
causing popup windows (metronome, backing track) to render without
CSS styles. This restores the code that copies all <link> and <style>
elements from the parent window to the popup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change play/stop icon colors from black (#000000) to blue (#2c7be5)
- Adjust JamTrack player dimensions to 420x220px
- Reduce backing track popup height from 400 to 250px
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Detect track end via position reset (>80% to <20%) since native client
auto-loops without updating isSessionTrackPlaying state
- Implement manual looping: Stop → Seek to 0 → Play when loop enabled
- Fix Close button to use onClose callback for proper WindowPortal cleanup
- Add boolean conversion for isSessionTrackPlaying (handles string/number)
- Clean up debug console.log statements
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Native client needs Stop -> Seek(0) -> Play sequence to reliably
start playback after track has finished. Previously we only did
this when "atEnd", but after track finishes and resets to 0, the
"atEnd" check is false.
Fix: Also do the full reset sequence when position is at the
beginning (< 100ms). This ensures native client is in the right
state whether starting fresh or restarting after track finished.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When track reaches end:
- UI was reset to position 0
- Native client was NOT seeked to position 0
Result: First play click failed because native client was still at end,
but UI showed position 0 so atEnd check was false and no seek was done.
Fix: Call SessionTrackSeekMs(0) when track ends, so native client
position matches UI state and next play works immediately.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Race condition: when opening backing track, the sequence was:
1. Local state set from native client (my previous fix)
2. Native callback triggers refreshCurrentSession
3. Server hasn't received sync yet, returns empty backing_tracks
4. dispatch(setBackingTracks([])) clears local data
5. Track disappears
Fix: Only dispatch setBackingTracks if server has data. When user
closes backing track, closeMedia action clears the state directly.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MediaTrackGroup is 6 (not 8) per globals.js ChannelGroupIds.
8 is StreamOutChatGroup, which is why no backing tracks were found.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After opening a backing track file via native client:
1. Call SessionGetAllControlState(true) to get track info
2. Extract backing track data (id, rid, filename, shortFilename)
3. Dispatch to both Redux slices:
- activeSessionSlice.addBackingTrack (for track sync)
- mediaSlice.setBackingTracks (for session screen UI)
Also fixes syncTracksToServer calls that incorrectly passed jamClient
object instead of server.clientId.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tasks completed: 2/2
- Use openBackingTrack action in handleBackingTrackSelected
- Add ignore flag to duration fetch useEffect
SUMMARY: .planning/phases/27-backing-track-sync/27-01-SUMMARY.md
- Add let ignore = false at start of duration fetch useEffect
- Check ignore flag after async jamClient call
- Add cleanup function that sets ignore = true
- Prevents 'state update on unmounted component' React warnings
when closing backing track popup quickly