- Replace triple setTimeout pattern (1s, 1.4s, 6s) with single 1.5s debounced call
- Eliminates redundant API calls on session join (STATE-01)
- Uses useMemo to create stable debounced function
- Debounce delay of 1.5s covers mixer initialization window
- Cleanup via cancel() on unmount
- Add useRef to track previous category values
- Prevents circular updates by using ref instead of selectors
- Stores last dispatched values for comparison
- Add mixerArraysEqual helper to compare mixer arrays by ID
- Compares master.id + personal.id to create unique pair key
- Used to prevent unnecessary Redux dispatches when categorizing mixers
- Implements ref-based debounce pattern for stable timers
- Solves stale closure problem with debounced callbacks
- useRef stores latest callback, useMemo creates debounce once
- Cleanup on unmount via useEffect return
- Exports both named and default for flexibility
Phase 32: State Update Optimization
- 4 plan(s) in 2 wave(s)
- 3 parallel in wave 1, 1 sequential in wave 2
- Ready for execution
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 32: State Update Optimization
- Debounce patterns for track sync consolidation documented
- Ref-based closure pattern for stable debounce identified
- State colocation principles for button loading states
- Content comparison before Redux dispatch patterns
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>