diff --git a/.planning/phases/31-selector-optimization/31-01-PLAN.md b/.planning/phases/31-selector-optimization/31-01-PLAN.md
index c891d3330..0246fd853 100644
--- a/.planning/phases/31-selector-optimization/31-01-PLAN.md
+++ b/.planning/phases/31-selector-optimization/31-01-PLAN.md
@@ -12,8 +12,8 @@ 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"
+ - "useMixerHelper uses 6 composed selectors with shallowEqual"
+ - "Composed selectors use createSelector to memoize grouped state objects (grouping IS derived data - creates new object from multiple inputs)"
artifacts:
- path: "jam-ui/src/store/features/mixersSlice.js"
provides: "Composed memoized selectors for mixer state"
@@ -172,7 +172,7 @@ mixersSlice.js exports 6 composed selectors using createSelector:
Task 2: Refactor useMixerHelper to use composed selectors with shallowEqual
jam-ui/src/hooks/useMixerHelper.js
-Replace 18+ individual useSelector calls with 6 composed selector calls using shallowEqual:
+Replace 21 individual useSelector calls (lines 72-92) with 6 composed selector calls using shallowEqual:
1. Update imports at top of file:
@@ -209,19 +209,36 @@ import {
} from '../store/features/mixersSlice';
```
-2. Replace the 18+ individual useSelector calls (lines 72-92) with composed selector calls:
+2. Replace the 21 individual useSelector calls (lines 72-92) with composed selector calls:
-OLD (remove this block):
+OLD (remove these 21 lines - lines 72-92):
```javascript
const chatMixer = useSelector(selectChatMixer);
const broadcastMixer = useSelector(selectBroadcastMixer);
const recordingMixer = useSelector(selectRecordingMixer);
-// ... 15+ more individual selectors
+const recordingTrackMixers = useSelector(selectRecordingTrackMixers);
+const backingTrackMixers = useSelector(selectBackingTrackMixers);
+const jamTrackMixers = useSelector(selectJamTrackMixers);
+const metronomeTrackMixers = useSelector(selectMetronomeTrackMixers);
+const adhocTrackMixers = useSelector(selectAdhocTrackMixers);
+const masterMixers = useSelector(selectMasterMixers);
+const personalMixers = useSelector(selectPersonalMixers);
+const allMixers = useSelector(selectAllMixers);
+const mixersByResourceId = useSelector(selectMixersByResourceId);
+const mixersByTrackId = useSelector(selectMixersByTrackId);
+const metronome = useSelector(selectMetronome);
+const metronomeSettings = useSelector(selectMetronomeSettings);
+const mediaSummary = useSelector(selectMediaSummary);
+const noAudioUsers = useSelector(selectNoAudioUsers);
+const clientsWithAudioOverride = useSelector(selectClientsWithAudioOverride);
+const simulatedMusicCategoryMixers = useSelector(selectSimulatedMusicCategoryMixers);
+const simulatedChatCategoryMixers = useSelector(selectSimulatedChatCategoryMixers);
+const isReadyRedux = useSelector(selectMixersReady);
```
NEW (replace with):
```javascript
-// Composed selectors with shallowEqual - reduces 18+ subscriptions to 6
+// Composed selectors with shallowEqual - reduces 21 subscriptions to 6
const coreMixers = useSelector(selectCoreMixerState, shallowEqual);
const trackMixers = useSelector(selectTrackMixerState, shallowEqual);
const lookupTables = useSelector(selectMixerLookupTables, shallowEqual);
@@ -240,9 +257,9 @@ const { metronome, metronomeSettings, mediaSummary, noAudioUsers,
const { simulatedMusicCategoryMixers, simulatedChatCategoryMixers } = simulatedMixers;
```
-This preserves all existing variable names used throughout the hook while reducing selector subscriptions from 18+ to 6.
+This preserves all existing variable names used throughout the hook while reducing selector subscriptions from 21 to 6.
-IMPORTANT: Keep all other code in useMixerHelper.js unchanged - only modify imports and the selector calls section.
+IMPORTANT: Keep all other code in useMixerHelper.js unchanged - only modify imports and the selector calls section. The remaining useSelector calls (lines 95-97 for tracks, 100-101 for mix mode, 109-110 for session) stay as-is.
Run syntax check:
@@ -250,10 +267,10 @@ Run syntax check:
cd jam-ui && node -c src/hooks/useMixerHelper.js
```
-Verify shallowEqual usage:
+Verify shallowEqual usage (should appear 7 times: 1 import + 6 useSelector calls):
```bash
grep -c "shallowEqual" jam-ui/src/hooks/useMixerHelper.js
-# Should return 6 or more (imports + 6 useSelector calls)
+# Expected: 7
```
Verify composed selector imports:
@@ -263,22 +280,24 @@ grep "selectTrackMixerState" jam-ui/src/hooks/useMixerHelper.js
grep "selectMixerLookupTables" jam-ui/src/hooks/useMixerHelper.js
```
-Count useSelector calls (should be significantly reduced):
+Verify old individual selectors removed (lines 72-92 pattern should NOT exist):
```bash
-grep -c "useSelector" jam-ui/src/hooks/useMixerHelper.js
-# Should be around 9-12 (6 composed + a few remaining individual like selectMixMode)
+grep -E "useSelector\(select(ChatMixer|BroadcastMixer|RecordingMixer|RecordingTrackMixers|BackingTrackMixers|JamTrackMixers|MetronomeTrackMixers|AdhocTrackMixers|MasterMixers|PersonalMixers|AllMixers|MixersByResourceId|MixersByTrackId|Metronome[^S]|MetronomeSettings|MediaSummary|NoAudioUsers|ClientsWithAudioOverride|SimulatedMusicCategoryMixers|SimulatedChatCategoryMixers|MixersReady)\)" jam-ui/src/hooks/useMixerHelper.js
+# Expected: no matches (empty output)
+```
+
+Count total useSelector calls (should be exactly 13):
+```bash
+grep -c "= useSelector(" jam-ui/src/hooks/useMixerHelper.js
+# Expected: 13 (6 composed + 3 track selectors + 2 mix mode + 2 session selectors)
```
-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.
+useMixerHelper.js refactored:
+- 21 individual selectors (lines 72-92) removed
+- 6 composed selectors with shallowEqual added
+- Total useSelector calls reduced from 28 to 13
+- All existing variable names preserved via destructuring for backward compatibility
@@ -299,8 +318,8 @@ 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
+- useMixerHelper.js should have exactly 13 useSelector calls (6 composed + 7 other)
+- Old individual selector pattern (lines 72-92) should be completely removed
4. **Pattern verification:**
```bash
@@ -313,6 +332,8 @@ grep -E "createSelector|shallowEqual" jam-ui/src/store/features/mixersSlice.js j
- [ ] 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
+- [ ] Old individual selectors (lines 72-92) completely removed
+- [ ] Total useSelector calls in useMixerHelper.js equals exactly 13
- [ ] All existing variable names preserved via destructuring
- [ ] Syntax validation passes for both files
- [ ] Build completes without errors