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:
parent
bb0019c941
commit
249cf75ec6
|
|
@ -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 | - |
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue