diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 59a7a854e..317c40a54 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -49,10 +49,10 @@ Plans: 2. VuContext separated from MixerConfigContext 3. Volume slider change doesn't re-render VU meters 4. VU update doesn't re-render volume sliders -**Plans**: TBD +**Plans**: 1 plan Plans: -- [ ] 29-01: TBD +- [ ] 29-01-PLAN.md — Memoize context providers (MixersContext, VuContext, GlobalContext) and wrap consumers with React.memo ### Phase 30: Component Memoization **Goal**: Child components don't re-render when parent re-renders with same props @@ -104,7 +104,7 @@ Plans: | Phase | Milestone | Plans Complete | Status | Completed | |-------|-----------|----------------|--------|-----------| | 28. VU Meter Optimization | v1.7 | 2/2 | ✓ Complete | 2026-03-05 | -| 29. Context Optimization | v1.7 | 0/TBD | Not started | - | +| 29. Context Optimization | v1.7 | 0/1 | Planned | - | | 30. Component Memoization | v1.7 | 0/TBD | Not started | - | | 31. Selector Optimization | v1.7 | 0/TBD | Not started | - | | 32. State Update Optimization | v1.7 | 0/TBD | Not started | - | diff --git a/.planning/phases/29-context-optimization/29-01-PLAN.md b/.planning/phases/29-context-optimization/29-01-PLAN.md new file mode 100644 index 000000000..66420a1b5 --- /dev/null +++ b/.planning/phases/29-context-optimization/29-01-PLAN.md @@ -0,0 +1,258 @@ +--- +phase: 29-context-optimization +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - jam-ui/src/context/MixersContext.js + - jam-ui/src/context/VuContext.js + - jam-ui/src/context/GlobalContext.js + - jam-ui/src/components/client/SessionTrackGain.js + - jam-ui/src/components/client/JKSessionMyTrack.js + - jam-ui/src/components/client/JKSessionVolumeModal.js + - jam-ui/src/components/client/JKSessionPanModal.js + - jam-ui/src/components/client/JKSessionBackingTrack.js + - jam-ui/src/components/client/JKSessionMetronome.js +autonomous: true + +must_haves: + truths: + - "Volume slider change does not re-render VU meters" + - "VU update does not re-render volume sliders" + - "MixersContext.Provider value is memoized" + - "Context consumers only re-render when their specific data changes" + artifacts: + - path: "jam-ui/src/context/MixersContext.js" + provides: "Memoized context provider value" + contains: "useMemo" + - path: "jam-ui/src/context/VuContext.js" + provides: "Memoized context provider value" + contains: "useMemo" + - path: "jam-ui/src/context/GlobalContext.js" + provides: "Memoized context provider value" + contains: "useMemo" + - path: "jam-ui/src/components/client/SessionTrackGain.js" + provides: "Memoized context consumer" + contains: "memo" + key_links: + - from: "MixersContext.Provider" + to: "useMixerHelper" + via: "useMemo wrapper" + pattern: "useMemo\\(\\(\\) => mixerHelper" + - from: "SessionTrackGain" + to: "MixersContext" + via: "memo-wrapped consumer" + pattern: "memo\\(function SessionTrackGain" +--- + + +Optimize React context providers and consumers to prevent unnecessary re-renders when context values change. + +Purpose: MixersContext creates a new value object on every render (line 10), causing all consumers to re-render even when underlying data hasn't changed. VuContext and GlobalContext have the same issue. This phase applies useMemo to provider values and React.memo to consumer components, so volume slider changes don't re-render VU meters and vice versa. + +Output: Memoized context providers (MixersContext, VuContext, GlobalContext) and memo-wrapped consumer components (SessionTrackGain, JKSessionMyTrack, etc.) with verified stable function references. + + + +@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md +@/Users/nuwan/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/29-context-optimization/29-RESEARCH.md + + + + + + Task 1: Memoize MixersContext provider value + + jam-ui/src/context/MixersContext.js + + +Memoize the MixersContext.Provider value to prevent new object creation on every render. + +1. Import `useMemo` from 'react' +2. Wrap the `mixerHelper` value with `useMemo`: + ```javascript + const value = useMemo(() => mixerHelper, [mixerHelper]); + ``` +3. Pass `value` to `MixersContext.Provider` instead of `mixerHelper` directly + +The useMixerHelper hook already returns stable references (verified: all functions use useCallback, myTracks uses useMemo). The memoization ensures the context value object itself doesn't recreate unless mixerHelper changes. + +Do NOT change useMixerHelper.js - the returned object from useMixerHelper already has stable function references via useCallback (faderChanged, findMixerForTrack, updateMixerData, etc.) and memoized values (myTracks via useMemo). + + +1. Run `cd /Users/nuwan/Code/jam-cloud/jam-ui && npm run build` - should compile without errors +2. Check MixersContext.js contains: `import { useMemo }` and `useMemo(() => mixerHelper` + + +- MixersContext.Provider value is wrapped with useMemo +- Build passes without errors + + + + + Task 2: Memoize VuContext and GlobalContext provider values + + jam-ui/src/context/VuContext.js + jam-ui/src/context/GlobalContext.js + + +Apply the same memoization pattern to VuContext and GlobalContext. + +**VuContext.js:** +1. Import `useMemo` from 'react' +2. Memoize the combined value object: + ```javascript + const value = useMemo(() => ({ + ...vuHelpers, + vuStore, + }), [vuHelpers]); + ``` + Note: vuStore is a stable module-level reference, doesn't need to be a dependency + +**GlobalContext.js:** +1. Import `useMemo` from 'react' +2. Memoize the provider value using all state/callback dependencies: + ```javascript + const value = useMemo(() => ({ + trackVolumeObject, + setTrackVolumeObject, + globalObject, + setGlobalObject, + metronomeState, + updateMetronomeState, + openMetronome, + closeMetronome, + resetMetronome, + }), [ + trackVolumeObject, + globalObject, + metronomeState, + updateMetronomeState, + openMetronome, + closeMetronome, + resetMetronome + ]); + ``` + Note: useState setters (setTrackVolumeObject, setGlobalObject) are stable and don't need to be dependencies + +3. Pass memoized `value` to provider instead of inline object + + +1. Run `cd /Users/nuwan/Code/jam-cloud/jam-ui && npm run build` - should compile without errors +2. Check VuContext.js contains: `useMemo(() => ({` +3. Check GlobalContext.js contains: `useMemo(() => ({` + + +- VuContext.Provider value is wrapped with useMemo +- GlobalContext.Provider value is wrapped with useMemo +- Build passes without errors + + + + + Task 3: Wrap context consumers with React.memo + + jam-ui/src/components/client/SessionTrackGain.js + jam-ui/src/components/client/JKSessionMyTrack.js + jam-ui/src/components/client/JKSessionVolumeModal.js + jam-ui/src/components/client/JKSessionPanModal.js + jam-ui/src/components/client/JKSessionBackingTrack.js + jam-ui/src/components/client/JKSessionMetronome.js + + +Wrap each context consumer component with React.memo to prevent re-renders when props haven't changed. + +**Pattern for each file:** +1. Import `memo` from 'react' (add to existing import) +2. Wrap the component function definition with memo: + ```javascript + const SessionTrackGain = memo(function SessionTrackGain({ mixers, gainType, ... }) { + // existing implementation + }); + ``` +3. Add displayName for debugging (optional but helpful): + ```javascript + SessionTrackGain.displayName = 'SessionTrackGain'; + ``` + +**Files to update:** +- `SessionTrackGain.js` - volume slider, consumes MixersContext +- `JKSessionMyTrack.js` - local track display, consumes MixersContext +- `JKSessionVolumeModal.js` - volume popup, consumes MixersContext +- `JKSessionPanModal.js` - pan control popup, consumes MixersContext +- `JKSessionBackingTrack.js` - backing track display, consumes MixersContext +- `JKSessionMetronome.js` - metronome controls, consumes MixersContext + +Do NOT wrap JKSessionScreen.js (the top-level container) - only wrap the specific consumer components that are frequently re-rendered. + +For each component: +1. Check if already wrapped with memo - skip if so +2. Convert `const ComponentName = (props) => {` to `const ComponentName = memo(function ComponentName(props) {` +3. Ensure PropTypes remain attached: `ComponentName.propTypes = {...}` + + +1. Run `cd /Users/nuwan/Code/jam-cloud/jam-ui && npm run build` - should compile without errors +2. Grep for memo usage: `grep -l "memo(function" jam-ui/src/components/client/SessionTrackGain.js jam-ui/src/components/client/JKSessionMyTrack.js` + + +- All 6 consumer components wrapped with React.memo +- Build passes without errors +- Components have displayName for debugging + + + + + + +After all tasks complete: + +1. **Build verification:** + ```bash + cd /Users/nuwan/Code/jam-cloud/jam-ui && npm run build + ``` + Should complete without errors or warnings related to memo/useMemo. + +2. **Code verification:** + ```bash + # Check MixersContext memoization + grep -n "useMemo" jam-ui/src/context/MixersContext.js + + # Check VuContext memoization + grep -n "useMemo" jam-ui/src/context/VuContext.js + + # Check GlobalContext memoization + grep -n "useMemo" jam-ui/src/context/GlobalContext.js + + # Check memo wrappers + grep -l "memo(function" jam-ui/src/components/client/*.js + ``` + +3. **Functional verification (manual):** + - Open session in browser with React DevTools Profiler + - Move volume slider + - Verify VU meters do NOT show in Profiler re-render list + - Verify only SessionTrackGain re-renders (due to local state) + + + +- CTX-01: MixersContext.Provider value is memoized (useMemo wraps mixerHelper) +- CTX-02: VuContext separated from MixerConfigContext (already done in Phase 28, now memoized) +- CTX-03: Context consumers only subscribe to data they actually use (React.memo prevents prop-unchanged re-renders) +- Volume slider change doesn't re-render VU meters +- VU update doesn't re-render volume sliders +- All 3 context providers use useMemo +- All 6 consumer components wrapped with React.memo +- Build passes without errors + + + +After completion, create `.planning/phases/29-context-optimization/29-01-SUMMARY.md` using the summary template. +