docs(31): create phase plan

Phase 31: Selector Optimization
- 1 plan in 1 wave
- Fully autonomous (no checkpoints)
- Ready for execution
This commit is contained in:
Nuwan 2026-03-05 18:56:14 +05:30
parent bb0019c941
commit 249cf75ec6
2 changed files with 326 additions and 3 deletions

View File

@ -77,10 +77,10 @@ Plans:
2. Derived data uses createSelector for memoization
3. Object selectors use shallowEqual comparison
4. Single mixer field change triggers 1-3 selector runs (not 18+)
**Plans**: TBD
**Plans**: 1 plan
Plans:
- [ ] 31-01: TBD
- [ ] 31-01-PLAN.md — Create composed selectors with createSelector and refactor useMixerHelper to use shallowEqual
### Phase 32: State Update Optimization
**Goal**: Redundant and cascading state updates eliminated
@ -106,7 +106,7 @@ Plans:
| 28. VU Meter Optimization | v1.7 | 2/2 | ✓ Complete | 2026-03-05 |
| 29. Context Optimization | v1.7 | 1/1 | ✓ Complete | 2026-03-05 |
| 30. Component Memoization | v1.7 | 1/1 | ✓ Complete | 2026-03-05 |
| 31. Selector Optimization | v1.7 | 0/TBD | Not started | - |
| 31. Selector Optimization | v1.7 | 0/1 | Planned | - |
| 32. State Update Optimization | v1.7 | 0/TBD | Not started | - |
---

View File

@ -0,0 +1,323 @@
---
phase: 31-selector-optimization
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- jam-ui/src/store/features/mixersSlice.js
- jam-ui/src/hooks/useMixerHelper.js
autonomous: true
must_haves:
truths:
- "Single mixer field change triggers 1-3 selector runs instead of 18+"
- "useMixerHelper uses 3-5 composed selectors with shallowEqual"
- "Derived data is memoized via createSelector"
artifacts:
- path: "jam-ui/src/store/features/mixersSlice.js"
provides: "Composed memoized selectors for mixer state"
exports: ["selectCoreMixerState", "selectTrackMixerState", "selectMixerLookupTables"]
contains: "createSelector"
- path: "jam-ui/src/hooks/useMixerHelper.js"
provides: "Optimized hook using composed selectors"
contains: "shallowEqual"
key_links:
- from: "jam-ui/src/hooks/useMixerHelper.js"
to: "jam-ui/src/store/features/mixersSlice.js"
via: "import composed selectors"
pattern: "import.*selectCoreMixerState.*from.*mixersSlice"
- from: "jam-ui/src/hooks/useMixerHelper.js"
to: "react-redux"
via: "shallowEqual comparison"
pattern: "useSelector\\(.*shallowEqual\\)"
---
<objective>
Consolidate 18+ individual Redux selectors in useMixerHelper into 3-5 composed memoized selectors using createSelector from Redux Toolkit.
Purpose: Reduce selector overhead from 18+ independent subscriptions and equality checks per Redux action to 3-5 memoized selectors that only recompute when their specific inputs change. This eliminates unnecessary re-renders when unrelated mixer fields change.
Output: Composed selectors in mixersSlice.js and refactored useMixerHelper.js using shallowEqual comparison for object returns.
</objective>
<execution_context>
@/Users/nuwan/.claude/get-shit-done/workflows/execute-plan.md
@/Users/nuwan/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/31-selector-optimization/31-RESEARCH.md
# Key source files
@jam-ui/src/store/features/mixersSlice.js
@jam-ui/src/hooks/useMixerHelper.js
</context>
<tasks>
<task type="auto">
<name>Task 1: Create composed selectors in mixersSlice.js</name>
<files>jam-ui/src/store/features/mixersSlice.js</files>
<action>
Add createSelector from Redux Toolkit and create three composed memoized selectors that group related mixer state:
1. Import createSelector at top:
```javascript
import { createSlice, createSelector } from '@reduxjs/toolkit';
```
2. Add composed selectors after the existing simple selectors (near end of file, before the parameterized selectors):
```javascript
// Composed memoized selectors for useMixerHelper optimization
// These group related data that's typically used together
export const selectCoreMixerState = createSelector(
[selectChatMixer, selectBroadcastMixer, selectRecordingMixer],
(chatMixer, broadcastMixer, recordingMixer) => ({
chatMixer,
broadcastMixer,
recordingMixer
})
);
export const selectTrackMixerState = createSelector(
[
selectRecordingTrackMixers,
selectBackingTrackMixers,
selectJamTrackMixers,
selectMetronomeTrackMixers,
selectAdhocTrackMixers
],
(recordingTrackMixers, backingTrackMixers, jamTrackMixers, metronomeTrackMixers, adhocTrackMixers) => ({
recordingTrackMixers,
backingTrackMixers,
jamTrackMixers,
metronomeTrackMixers,
adhocTrackMixers
})
);
export const selectMixerLookupTables = createSelector(
[selectAllMixers, selectMixersByResourceId, selectMixersByTrackId],
(allMixers, mixersByResourceId, mixersByTrackId) => ({
allMixers,
mixersByResourceId,
mixersByTrackId
})
);
export const selectMasterPersonalMixers = createSelector(
[selectMasterMixers, selectPersonalMixers],
(masterMixers, personalMixers) => ({
masterMixers,
personalMixers
})
);
export const selectMixerMetadata = createSelector(
[selectMetronome, selectMetronomeSettings, selectMediaSummary, selectNoAudioUsers, selectClientsWithAudioOverride, selectMixersReady],
(metronome, metronomeSettings, mediaSummary, noAudioUsers, clientsWithAudioOverride, isReady) => ({
metronome,
metronomeSettings,
mediaSummary,
noAudioUsers,
clientsWithAudioOverride,
isReady
})
);
export const selectSimulatedCategoryMixers = createSelector(
[selectSimulatedMusicCategoryMixers, selectSimulatedChatCategoryMixers],
(simulatedMusicCategoryMixers, simulatedChatCategoryMixers) => ({
simulatedMusicCategoryMixers,
simulatedChatCategoryMixers
})
);
```
These selectors use createSelector which:
- Memoizes the result based on input selectors
- Only recomputes when any input selector returns a different reference
- Returns the same object reference when inputs haven't changed
</action>
<verify>
Run syntax check:
```bash
cd jam-ui && node -c src/store/features/mixersSlice.js
```
Verify createSelector import and exports:
```bash
grep -E "import.*createSelector.*@reduxjs/toolkit" jam-ui/src/store/features/mixersSlice.js
grep -E "export const select(CoreMixerState|TrackMixerState|MixerLookupTables)" jam-ui/src/store/features/mixersSlice.js
```
</verify>
<done>
mixersSlice.js exports 6 composed selectors using createSelector:
- selectCoreMixerState (chatMixer, broadcastMixer, recordingMixer)
- selectTrackMixerState (5 track mixer arrays)
- selectMixerLookupTables (allMixers, mixersByResourceId, mixersByTrackId)
- selectMasterPersonalMixers (masterMixers, personalMixers)
- selectMixerMetadata (metronome, settings, mediaSummary, noAudioUsers, etc.)
- selectSimulatedCategoryMixers (simulatedMusic, simulatedChat)
</done>
</task>
<task type="auto">
<name>Task 2: Refactor useMixerHelper to use composed selectors with shallowEqual</name>
<files>jam-ui/src/hooks/useMixerHelper.js</files>
<action>
Replace 18+ individual useSelector calls with 6 composed selector calls using shallowEqual:
1. Update imports at top of file:
Replace the long list of individual selector imports with:
```javascript
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { selectActiveSession, selectInSession } from '../store/features/activeSessionSlice';
import {
// Composed selectors (use these instead of individual ones)
selectCoreMixerState,
selectTrackMixerState,
selectMixerLookupTables,
selectMasterPersonalMixers,
selectMixerMetadata,
selectSimulatedCategoryMixers,
// Actions (still needed)
setMasterMixers,
setPersonalMixers,
organizeMixers,
updateMixer,
setChatMixer,
setBackingTracks as setBackingTracksAction,
setJamTracks as setJamTracksAction,
setRecordedTracks as setRecordedTracksAction,
setMetronome as setMetronomeAction,
setMediaSummary,
setSimulatedMusicCategoryMixers as setSimulatedMusicAction,
setSimulatedChatCategoryMixers as setSimulatedChatAction,
setRecordingTrackMixers,
setBackingTrackMixers,
setJamTrackMixers,
setMetronomeTrackMixers,
setAdhocTrackMixers
} from '../store/features/mixersSlice';
```
2. Replace the 18+ individual useSelector calls (lines 72-92) with composed selector calls:
OLD (remove this block):
```javascript
const chatMixer = useSelector(selectChatMixer);
const broadcastMixer = useSelector(selectBroadcastMixer);
const recordingMixer = useSelector(selectRecordingMixer);
// ... 15+ more individual selectors
```
NEW (replace with):
```javascript
// Composed selectors with shallowEqual - reduces 18+ subscriptions to 6
const coreMixers = useSelector(selectCoreMixerState, shallowEqual);
const trackMixers = useSelector(selectTrackMixerState, shallowEqual);
const lookupTables = useSelector(selectMixerLookupTables, shallowEqual);
const masterPersonal = useSelector(selectMasterPersonalMixers, shallowEqual);
const metadata = useSelector(selectMixerMetadata, shallowEqual);
const simulatedMixers = useSelector(selectSimulatedCategoryMixers, shallowEqual);
// Destructure for backward compatibility with rest of hook
const { chatMixer, broadcastMixer, recordingMixer } = coreMixers;
const { recordingTrackMixers, backingTrackMixers, jamTrackMixers,
metronomeTrackMixers, adhocTrackMixers } = trackMixers;
const { allMixers, mixersByResourceId, mixersByTrackId } = lookupTables;
const { masterMixers, personalMixers } = masterPersonal;
const { metronome, metronomeSettings, mediaSummary, noAudioUsers,
clientsWithAudioOverride, isReady: isReadyRedux } = metadata;
const { simulatedMusicCategoryMixers, simulatedChatCategoryMixers } = simulatedMixers;
```
This preserves all existing variable names used throughout the hook while reducing selector subscriptions from 18+ to 6.
IMPORTANT: Keep all other code in useMixerHelper.js unchanged - only modify imports and the selector calls section.
</action>
<verify>
Run syntax check:
```bash
cd jam-ui && node -c src/hooks/useMixerHelper.js
```
Verify shallowEqual usage:
```bash
grep -c "shallowEqual" jam-ui/src/hooks/useMixerHelper.js
# Should return 6 or more (imports + 6 useSelector calls)
```
Verify composed selector imports:
```bash
grep "selectCoreMixerState" jam-ui/src/hooks/useMixerHelper.js
grep "selectTrackMixerState" jam-ui/src/hooks/useMixerHelper.js
grep "selectMixerLookupTables" jam-ui/src/hooks/useMixerHelper.js
```
Count useSelector calls (should be significantly reduced):
```bash
grep -c "useSelector" jam-ui/src/hooks/useMixerHelper.js
# Should be around 9-12 (6 composed + a few remaining individual like selectMixMode)
```
</verify>
<done>
useMixerHelper.js uses 6 composed selectors with shallowEqual instead of 18+ individual selectors:
- coreMixers via selectCoreMixerState
- trackMixers via selectTrackMixerState
- lookupTables via selectMixerLookupTables
- masterPersonal via selectMasterPersonalMixers
- metadata via selectMixerMetadata
- simulatedMixers via selectSimulatedCategoryMixers
All existing variable names preserved via destructuring for backward compatibility.
</done>
</task>
</tasks>
<verification>
After both tasks:
1. **Syntax validation:**
```bash
cd jam-ui && node -c src/store/features/mixersSlice.js && node -c src/hooks/useMixerHelper.js
```
2. **Build check:**
```bash
cd jam-ui && npm run build 2>&1 | head -50
```
3. **Selector count verification:**
- mixersSlice.js should export 6 new composed selectors
- useMixerHelper.js should have 6 useSelector calls with shallowEqual
- Total useSelector calls in useMixerHelper reduced from 18+ to ~10-12
4. **Pattern verification:**
```bash
grep -E "createSelector|shallowEqual" jam-ui/src/store/features/mixersSlice.js jam-ui/src/hooks/useMixerHelper.js
```
</verification>
<success_criteria>
- [ ] mixersSlice.js imports createSelector from @reduxjs/toolkit
- [ ] mixersSlice.js exports 6 composed selectors (selectCoreMixerState, selectTrackMixerState, selectMixerLookupTables, selectMasterPersonalMixers, selectMixerMetadata, selectSimulatedCategoryMixers)
- [ ] useMixerHelper.js imports shallowEqual from react-redux
- [ ] useMixerHelper.js uses 6 composed selectors with shallowEqual comparison
- [ ] All existing variable names preserved via destructuring
- [ ] Syntax validation passes for both files
- [ ] Build completes without errors
</success_criteria>
<output>
After completion, create `.planning/phases/31-selector-optimization/31-01-SUMMARY.md`
</output>