diff --git a/jam-ui/docs/PHASE1_MIGRATION_SUMMARY.md b/jam-ui/docs/PHASE1_MIGRATION_SUMMARY.md
new file mode 100644
index 000000000..92fafc8eb
--- /dev/null
+++ b/jam-ui/docs/PHASE1_MIGRATION_SUMMARY.md
@@ -0,0 +1,176 @@
+# Phase 1 Modal Migration - Completed
+
+## Summary
+
+Successfully migrated all modal state from `useState` hooks to Redux in **JKSessionScreen.js**.
+
+## Changes Made
+
+### 1. Updated Redux Slice (`sessionUISlice.js`)
+Added `mediaControls` modal to the initialState:
+```javascript
+modals: {
+ settings: false,
+ invite: false,
+ volume: false,
+ recording: false,
+ leave: false,
+ jamTrack: false,
+ backingTrack: false,
+ mediaControls: false, // NEW
+ metronome: false,
+ videoSettings: false
+}
+```
+
+### 2. Updated JKSessionScreen Component
+
+#### Imports Added:
+```javascript
+import { useDispatch, useSelector } from 'react-redux';
+import { openModal, closeModal, toggleModal, selectModal } from '../../store/features/sessionUISlice';
+```
+
+#### State Migration:
+
+**BEFORE (8 useState hooks for modals):**
+```javascript
+const [showSettingsModal, setShowSettingsModal] = useState(false);
+const [showInviteModal, setShowInviteModal] = useState(false);
+const [showVolumeModal, setShowVolumeModal] = useState(false);
+const [showRecordingModal, setShowRecordingModal] = useState(false);
+const [showLeaveModal, setShowLeaveModal] = useState(false);
+const [showJamTrackModal, setShowJamTrackModal] = useState(false);
+const [showBackingTrackPopup, setShowBackingTrackPopup] = useState(false);
+const [showMediaControlsPopup, setShowMediaControlsPopup] = useState(false);
+```
+
+**AFTER (Redux selectors):**
+```javascript
+const dispatch = useDispatch();
+const showSettingsModal = useSelector(selectModal('settings'));
+const showInviteModal = useSelector(selectModal('invite'));
+const showVolumeModal = useSelector(selectModal('volume'));
+const showRecordingModal = useSelector(selectModal('recording'));
+const showLeaveModal = useSelector(selectModal('leave'));
+const showJamTrackModal = useSelector(selectModal('jamTrack'));
+const showBackingTrackPopup = useSelector(selectModal('backingTrack'));
+const showMediaControlsPopup = useSelector(selectModal('mediaControls'));
+```
+
+#### Action Dispatch Updates:
+
+**Opening Modals:**
+- Toolbar buttons now use: `onClick={() => dispatch(openModal('settings'))}`
+- Previously: `onClick={() => setShowSettingsModal(true)}`
+
+**Closing Modals:**
+- Modal onClose handlers now use: `dispatch(closeModal('settings'))`
+- Previously: `setShowSettingsModal(false)`
+
+**Toggling Modals:**
+- Modal toggle handlers now use: `dispatch(toggleModal('settings'))`
+- Previously: `setShowSettingsModal(!showSettingsModal)`
+
+### 3. Updated Modal Components
+
+All modal components updated to use Redux actions:
+
+1. **JKSessionSettingsModal**
+ - toggle: `() => dispatch(toggleModal('settings'))`
+ - onSave: closes with `dispatch(closeModal('settings'))`
+
+2. **JKSessionInviteModal**
+ - onToggle: `() => dispatch(closeModal('invite'))`
+ - onSubmit: closes with `dispatch(closeModal('invite'))`
+
+3. **JKSessionVolumeModal**
+ - toggle: `() => dispatch(toggleModal('volume'))`
+
+4. **JKSessionRecordingModal**
+ - toggle: `() => dispatch(toggleModal('recording'))`
+
+5. **JKSessionLeaveModal**
+ - toggle: `() => dispatch(closeModal('leave'))`
+ - onSubmit: closes with `dispatch(closeModal('leave'))`
+
+6. **JKSessionJamTrackModal**
+ - toggle: `() => dispatch(toggleModal('jamTrack'))`
+
+7. **Backing Track Popup**
+ - onClose: `dispatch(closeModal('backingTrack'))`
+ - handleBackingTrackSelected: `dispatch(openModal('backingTrack'))`
+
+8. **Media Controls Popup**
+ - onClose: `dispatch(closeModal('mediaControls'))`
+
+## Benefits Achieved
+
+1. **Reduced Local State**: Removed 8 useState hooks from component
+2. **Centralized Modal State**: All modal visibility in one Redux slice
+3. **Predictable Updates**: All modal state changes tracked via Redux DevTools
+4. **Better Debugging**: Can inspect modal state in Redux DevTools
+5. **Consistent Pattern**: All modals follow same open/close pattern
+6. **Easier Testing**: Modal logic can be tested via Redux actions
+
+## Lines of Code Impact
+
+- **Removed**: ~16 lines of useState declarations
+- **Added**: ~8 lines of Redux selectors
+- **Net Reduction**: ~8 lines of boilerplate code
+
+## State Preserved
+
+The following non-modal state was **intentionally kept** as local useState:
+- `settingsLoading`, `inviteLoading`, `leaveLoading` - Loading states specific to modal operations
+- `friends`, `sessionInvitees` - Data fetched for modals
+- `volumeLevel`, `leaveRating`, `leaveComments` - Form field state within modals
+- `backingTrackData` - Data for backing track popup
+- `mediaControlsOpened`, `popupGuard` - Popup-specific guards
+
+These remain as local state because they are:
+1. Specific to modal/popup content, not visibility
+2. Don't need to be shared across components
+3. Reset when modal closes anyway
+
+## Testing Checklist
+
+To verify this migration works correctly:
+
+- [ ] Open Settings modal - verify it opens
+- [ ] Close Settings modal - verify it closes
+- [ ] Open Invite modal - verify it opens
+- [ ] Close Invite modal - verify it closes
+- [ ] Open Volume modal - verify it opens
+- [ ] Close Volume modal - verify it closes
+- [ ] Open Recording modal - verify it opens
+- [ ] Close Recording modal - verify it closes
+- [ ] Click Leave Session - verify modal opens
+- [ ] Close Leave modal - verify it closes
+- [ ] Open JamTrack modal - verify it opens
+- [ ] Close JamTrack modal - verify it closes
+- [ ] Select Backing Track - verify popup opens
+- [ ] Close Backing Track popup - verify it closes
+- [ ] Open Redux DevTools - verify modal state updates in `sessionUI.modals`
+- [ ] Verify no console errors
+
+## Next Steps (Phase 2)
+
+With modal state successfully migrated, you can now proceed to:
+
+1. **Phase 2**: Migrate session lifecycle state (`hasJoined`, `sessionGuardsPassed`, etc.) to `activeSessionSlice`
+2. **Phase 3**: Migrate entity collections (`userTracks`, `participants`, etc.) to Redux
+3. **Phase 4**: Integrate WebSocket callbacks with Redux using `useSessionWebSocket` hook
+
+## Files Modified
+
+1. `src/store/features/sessionUISlice.js` - Added mediaControls modal
+2. `src/components/client/JKSessionScreen.js` - Migrated all modal state to Redux
+
+## Rollback Instructions
+
+If you need to rollback this change:
+1. `git checkout HEAD -- src/components/client/JKSessionScreen.js`
+2. `git checkout HEAD -- src/store/features/sessionUISlice.js`
+
+This will restore the previous useState-based modal management.
diff --git a/jam-ui/docs/PHASE2_MIGRATION_SUMMARY.md b/jam-ui/docs/PHASE2_MIGRATION_SUMMARY.md
new file mode 100644
index 000000000..fb84ccb82
--- /dev/null
+++ b/jam-ui/docs/PHASE2_MIGRATION_SUMMARY.md
@@ -0,0 +1,368 @@
+# Phase 2 Session Lifecycle Migration - Completed
+
+## Summary
+
+Successfully migrated session lifecycle state from `useState` hooks to Redux in **JKSessionScreen.js**. This includes session join/leave operations, user tracks, connection status, and guard validation.
+
+## Changes Made
+
+### 1. Updated Imports
+
+Added Redux actions and selectors for active session management:
+
+```javascript
+import {
+ fetchActiveSession,
+ joinActiveSession,
+ leaveActiveSession,
+ setGuardsPassed,
+ setUserTracks,
+ setConnectionStatus,
+ clearSession,
+ selectActiveSession,
+ selectJoinStatus,
+ selectHasJoined,
+ selectGuardsPassed,
+ selectUserTracks,
+ selectShowConnectionAlert,
+ selectSessionId
+} from '../../store/features/activeSessionSlice';
+```
+
+### 2. State Migration
+
+#### BEFORE (6 useState hooks for lifecycle):
+```javascript
+const [userTracks, setUserTracks] = useState([]);
+const [showConnectionAlert, setShowConnectionAlert] = useState(false);
+const [hasJoined, setHasJoined] = useState(false);
+const [sessionGuardsPassed, setSessionGuardsPassed] = useState(false);
+// Plus session data states...
+```
+
+#### AFTER (Redux selectors):
+```javascript
+// Redux session lifecycle state
+const activeSession = useSelector(selectActiveSession);
+const joinStatus = useSelector(selectJoinStatus);
+const hasJoined = useSelector(selectHasJoined);
+const sessionGuardsPassed = useSelector(selectGuardsPassed);
+const userTracks = useSelector(selectUserTracks);
+const showConnectionAlert = useSelector(selectShowConnectionAlert);
+const reduxSessionId = useSelector(selectSessionId);
+```
+
+### 3. Session Fetch on Mount
+
+Added effect to fetch active session data when component mounts:
+
+```javascript
+// Fetch session data when sessionId changes
+useEffect(() => {
+ if (sessionId && sessionId !== reduxSessionId) {
+ console.log('Fetching active session:', sessionId);
+ dispatch(fetchActiveSession(sessionId));
+ }
+}, [sessionId, reduxSessionId, dispatch]);
+```
+
+### 4. Guard Operations Updated
+
+**Guard checks now dispatch Redux actions:**
+
+```javascript
+// In guardOnJoinSession function
+
+// For CHILD role
+dispatch(setUserTracks([]));
+
+// After getting tracks from sessionModel
+const tracks = await sessionModel.waitForSessionPageEnterDone();
+dispatch(setUserTracks(tracks));
+
+// After guards pass
+dispatch(setGuardsPassed(true));
+```
+
+### 5. Join Session Updated
+
+**Join operation now updates Redux state:**
+
+```javascript
+// In joinSession function, after successful join
+const data = await response.json();
+
+// Update Redux state - user has successfully joined
+dispatch(joinActiveSession.fulfilled(data, '', { sessionId, options: {} }));
+```
+
+**Note:** The join logic remains in the component for now (hybrid approach) because it involves complex native client calls and message callback registrations. A future phase can refactor this to use the Redux thunk more fully.
+
+### 6. Connection Status Monitoring
+
+**Connection status changes now dispatch Redux actions:**
+
+```javascript
+// Monitor connection status changes
+useEffect(() => {
+ if (connectionStatus === ConnectionStatus.DISCONNECTED ||
+ connectionStatus === ConnectionStatus.ERROR) {
+ dispatch(setConnectionStatus('disconnected'));
+ } else if (connectionStatus === ConnectionStatus.CONNECTED) {
+ dispatch(setConnectionStatus('connected'));
+ } else if (connectionStatus === ConnectionStatus.RECONNECTING) {
+ dispatch(setConnectionStatus('reconnecting'));
+ }
+}, [connectionStatus, dispatch]);
+```
+
+### 7. Leave Session Updated
+
+**Leave operation now clears Redux state:**
+
+```javascript
+const handleLeaveSubmit = async (feedbackData) => {
+ try {
+ // ... existing leave logic ...
+
+ await sessionModel.handleLeaveSession();
+
+ // Clear Redux session state
+ dispatch(clearSession());
+
+ dispatch(closeModal('leave'));
+ history.push('/sessions');
+ } catch (error) {
+ // ... error handling ...
+ }
+};
+```
+
+### 8. Cleanup on Unmount
+
+**Component unmount now clears Redux state:**
+
+```javascript
+useEffect(() => {
+ return () => {
+ unregisterMessageCallbacks();
+
+ if (metronomeState.isOpen) {
+ resetMetronome();
+ }
+
+ // Clear Redux session state on unmount
+ dispatch(clearSession());
+ };
+}, [metronomeState.isOpen, resetMetronome, dispatch]);
+```
+
+## Benefits Achieved
+
+1. **Centralized Session State**: All session lifecycle state in Redux
+2. **Predictable State Flow**: Clear action → reducer → state flow
+3. **Better Debugging**: Can inspect session state and lifecycle in Redux DevTools
+4. **State Persistence**: Session state survives component re-renders
+5. **Easier Testing**: Session logic testable via Redux actions
+6. **Reduced Component Complexity**: Removed 6 useState hooks
+
+## State Architecture
+
+### Redux State Structure (activeSession):
+```javascript
+{
+ sessionId: null,
+ sessionData: null,
+ joinStatus: 'idle', // 'idle' | 'joining' | 'joined' | 'leaving'
+ guardsPassed: false,
+ hasJoined: false,
+ userTracks: [],
+ connectionStatus: 'disconnected',
+ showConnectionAlert: false,
+ // ... other fields
+}
+```
+
+### State Flow:
+
+1. **Mount**:
+ - `fetchActiveSession(sessionId)` → Fetches session data from API
+
+2. **Guard Validation**:
+ - User tracks retrieved → `dispatch(setUserTracks(tracks))`
+ - Guards pass → `dispatch(setGuardsPassed(true))`
+
+3. **Join Session**:
+ - Guards passed + tracks ready → Join API called
+ - Success → `dispatch(joinActiveSession.fulfilled(data))`
+ - `hasJoined` becomes `true`
+
+4. **Connection Monitoring**:
+ - Connection changes → `dispatch(setConnectionStatus(status))`
+ - Alert shown/hidden based on connection state
+
+5. **Leave Session**:
+ - User clicks Leave → Modal opens
+ - User confirms → `sessionModel.handleLeaveSession()`
+ - Redux state cleared → `dispatch(clearSession())`
+ - Navigate to `/sessions`
+
+6. **Unmount**:
+ - Component unmounts → `dispatch(clearSession())`
+
+## Non-Migrated State
+
+The following state was **intentionally kept** as local useState:
+- `requestingSessionRefresh` - Refresh request state
+- `pendingSessionRefresh` - Pending refresh state
+- `sessionRules` - Session rules (may migrate later)
+- `subscriptionRules` - Subscription rules (may migrate later)
+- `currentOrLastSession` - Local session cache (may migrate later)
+
+These can be migrated in a future phase if needed.
+
+## Lines of Code Impact
+
+- **Removed**: ~12 lines of useState declarations
+- **Added**: ~7 lines of Redux selectors + ~20 lines of dispatch calls
+- **Net Addition**: ~15 lines (worth it for centralized state management)
+
+## Testing Checklist
+
+To verify Phase 2 migration:
+
+### Session Lifecycle:
+- [ ] Navigate to session screen with valid session ID
+- [ ] Verify session data fetches from API
+- [ ] Verify user tracks are retrieved during guards
+- [ ] Verify guards pass and `sessionGuardsPassed` becomes true
+- [ ] Verify session join succeeds and `hasJoined` becomes true
+- [ ] Open Redux DevTools → Check `activeSession` state updates
+
+### Connection Status:
+- [ ] Disconnect from network
+- [ ] Verify connection alert shows
+- [ ] Verify Redux state: `connectionStatus: 'disconnected'`
+- [ ] Reconnect to network
+- [ ] Verify connection alert hides
+- [ ] Verify Redux state: `connectionStatus: 'connected'`
+
+### Leave Session:
+- [ ] Click "Leave Session" button
+- [ ] Confirm leave in modal
+- [ ] Verify session leaves successfully
+- [ ] Verify Redux state cleared (activeSession back to initial state)
+- [ ] Verify navigation to `/sessions`
+
+### Cleanup:
+- [ ] Join a session
+- [ ] Navigate away from session screen
+- [ ] Verify Redux state cleared on unmount
+
+### Redux DevTools:
+- [ ] Watch state changes during session lifecycle
+- [ ] Verify actions dispatched:
+ - `activeSession/fetch/pending`
+ - `activeSession/fetch/fulfilled`
+ - `activeSession/setUserTracks`
+ - `activeSession/setGuardsPassed`
+ - `activeSession/join/fulfilled`
+ - `activeSession/setConnectionStatus`
+ - `activeSession/clearSession`
+
+## Known Limitations
+
+1. **Hybrid Join Approach**: The `joinSession` function still lives in the component with complex side effects (native client calls, callback registrations). A future refactor can move more of this logic to Redux middleware or sagas.
+
+2. **Context Dependencies**: Still depends on `CurrentSessionContext` for `setCurrentSessionId` and `setCurrentSession`. These can be migrated in Phase 3.
+
+3. **Session Model Integration**: Still uses `sessionModel` hook for some operations. Consider consolidating in future phases.
+
+## Next Steps (Phase 3)
+
+With lifecycle state successfully migrated, you can now proceed to:
+
+1. **Phase 3**: Migrate entity collections to Redux:
+ - Participants (`participants` array)
+ - Jam Tracks (`selectedJamTrack`, `jamTrackStems`)
+ - Backing Tracks
+ - Recording state
+
+2. **WebSocket Integration**: Use the `useSessionWebSocket` hook to integrate WebSocket callbacks with Redux actions automatically
+
+3. **Context Cleanup**: Replace `CurrentSessionContext` with Redux selectors completely
+
+## Files Modified
+
+1. `src/components/client/JKSessionScreen.js` - Migrated session lifecycle state to Redux
+
+## Rollback Instructions
+
+If you need to rollback this change:
+```bash
+git checkout HEAD -- src/components/client/JKSessionScreen.js
+```
+
+This will restore the previous useState-based lifecycle management.
+
+## Integration Points
+
+### Current Session Context:
+The component still uses `CurrentSessionContext` for:
+- `setCurrentSessionId()` - Setting session ID in ref
+- `setCurrentSession()` - Updating context session data
+- `inSession()` - Checking if in session
+- `currentSessionIdRef` - Ref to current session ID
+
+**Future**: These can be replaced with Redux actions/selectors in Phase 3.
+
+### Session Model Hook:
+The component uses `useSessionModel` for:
+- `waitForSessionPageEnterDone()` - Getting user tracks
+- `handleLeaveSession()` - Leave operations
+- `updateSessionInfo()` - Updating session info
+- `trackChanges` - Handling WebSocket messages
+
+**Future**: Consider consolidating session logic in Redux or creating a custom hook that wraps Redux.
+
+## Performance Considerations
+
+- **Memoized Selectors**: Redux selectors are automatically memoized by `useSelector`
+- **Selective Re-renders**: Component only re-renders when selected state changes
+- **No Prop Drilling**: Session state accessible anywhere via `useSelector`
+
+## Debugging Tips
+
+### Redux DevTools:
+
+1. **View State Tree**: See entire `activeSession` state
+2. **Action History**: Track all dispatched actions
+3. **Time Travel**: Jump to any previous state
+4. **State Diff**: See what changed between actions
+
+### Console Logging:
+
+Add logging to track state changes:
+```javascript
+useEffect(() => {
+ console.log('Session lifecycle state:', {
+ hasJoined,
+ sessionGuardsPassed,
+ userTracks: userTracks.length,
+ connectionStatus: showConnectionAlert ? 'disconnected' : 'connected'
+ });
+}, [hasJoined, sessionGuardsPassed, userTracks, showConnectionAlert]);
+```
+
+## Migration Pattern Summary
+
+This phase followed the pattern:
+
+1. ✅ Import Redux actions/selectors
+2. ✅ Replace useState with useSelector
+3. ✅ Replace state setters with dispatch calls
+4. ✅ Add fetch effect on mount
+5. ✅ Update lifecycle operations (join/leave)
+6. ✅ Add cleanup on unmount
+
+This same pattern can be applied to Phase 3 for entity collections.
diff --git a/jam-ui/docs/REDUX_MIGRATION_GUIDE.md b/jam-ui/docs/REDUX_MIGRATION_GUIDE.md
new file mode 100644
index 000000000..12fe72d88
--- /dev/null
+++ b/jam-ui/docs/REDUX_MIGRATION_GUIDE.md
@@ -0,0 +1,297 @@
+# Redux Migration Guide for JKSessionScreen
+
+## Files Created
+
+### 1. `src/store/features/activeSessionSlice.js`
+Redux slice for managing the active session state including:
+- Session data and lifecycle (joining, leaving)
+- Participants management
+- User tracks, jam tracks, and backing tracks
+- Recording state
+- Connection status
+
+**Key Actions:**
+- `fetchActiveSession(sessionId)` - Async thunk to fetch session data
+- `joinActiveSession({ sessionId, options })` - Async thunk to join session
+- `leaveActiveSession(sessionId)` - Async thunk to leave session
+- `addParticipant(participant)` - Add a participant to the session
+- `removeParticipant(participantId)` - Remove a participant
+- `addUserTrack(track)` - Add a user track
+- `setSelectedJamTrack(jamTrack)` - Set the selected jam track
+- `startRecording(recordingId)` - Start recording
+- `stopRecording()` - Stop recording
+- `setConnectionStatus(status)` - Update connection status
+- `clearSession()` - Clear all session state
+
+**Key Selectors:**
+- `selectActiveSession(state)` - Get session data
+- `selectJoinStatus(state)` - Get join status
+- `selectHasJoined(state)` - Check if user has joined
+- `selectParticipants(state)` - Get participants list
+- `selectUserTracks(state)` - Get user tracks
+- `selectIsRecording(state)` - Check if recording is active
+- `selectConnectionStatus(state)` - Get connection status
+
+### 2. `src/store/features/sessionUISlice.js`
+Redux slice for managing session UI state including:
+- Modal visibility (settings, invite, volume, recording, leave, etc.)
+- Panel visibility (chat, mixer, participants, tracks)
+- View preferences (layout, video visibility, sidebar state)
+
+**Key Actions:**
+- `openModal(modalName)` - Open a specific modal
+- `closeModal(modalName)` - Close a specific modal
+- `toggleModal(modalName)` - Toggle a modal
+- `closeAllModals()` - Close all modals
+- `togglePanel(panelName)` - Toggle a panel
+- `setParticipantLayout(layout)` - Set participant layout ('grid' | 'list' | 'compact')
+- `toggleMixer()` - Toggle mixer visibility
+- `resetUI()` - Reset all UI state
+
+**Key Selectors:**
+- `selectModal(modalName)(state)` - Check if a modal is open
+- `selectIsAnyModalOpen(state)` - Check if any modal is open
+- `selectPanel(panelName)(state)` - Check if a panel is open
+- `selectShowMixer(state)` - Check if mixer is visible
+- `selectParticipantLayout(state)` - Get participant layout
+
+### 3. `src/hooks/useSessionWebSocket.js`
+Custom hook that integrates WebSocket messages with Redux actions. This hook:
+- Listens to WebSocket events from `jamServer`
+- Dispatches appropriate Redux actions when events occur
+- Handles cleanup on unmount
+
+**Usage:**
+```javascript
+import { useSessionWebSocket } from '../hooks/useSessionWebSocket';
+
+function JKSessionScreen() {
+ const { sessionId } = useParams();
+
+ // This hook will automatically dispatch Redux actions
+ // when WebSocket events are received
+ useSessionWebSocket(sessionId);
+
+ // ... rest of component
+}
+```
+
+### 4. Updated `src/store/store.js`
+Added the new reducers to the Redux store:
+- `activeSession` - Active session state
+- `sessionUI` - Session UI state
+
+## Migration Strategy
+
+### Phase 1: Quick Wins - Migrate Modal State (Start Here)
+
+Replace `useState` for modals with Redux:
+
+**Before:**
+```javascript
+const [showSettingsModal, setShowSettingsModal] = useState(false);
+const [showInviteModal, setShowInviteModal] = useState(false);
+
+// In JSX
+
+{showSettingsModal && setShowSettingsModal(false)} />}
+```
+
+**After:**
+```javascript
+import { useDispatch, useSelector } from 'react-redux';
+import { openModal, closeModal, selectModal } from '../../store/features/sessionUISlice';
+
+const dispatch = useDispatch();
+const showSettingsModal = useSelector(selectModal('settings'));
+
+// In JSX
+
+{showSettingsModal && dispatch(closeModal('settings'))} />}
+```
+
+**Benefits:**
+- Removes 5-8 useState hooks immediately
+- Easy to test
+- Good practice for team
+
+### Phase 2: Migrate Session Lifecycle
+
+Replace session join/leave logic with Redux thunks:
+
+**Before:**
+```javascript
+const [hasJoined, setHasJoined] = useState(false);
+const [sessionGuardsPassed, setSessionGuardsPassed] = useState(false);
+
+const handleJoinSession = async () => {
+ // Guard checks...
+ setSessionGuardsPassed(true);
+ const response = await joinSession(sessionId, options);
+ setHasJoined(true);
+};
+```
+
+**After:**
+```javascript
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ joinActiveSession,
+ selectJoinStatus,
+ selectHasJoined
+} from '../../store/features/activeSessionSlice';
+
+const dispatch = useDispatch();
+const joinStatus = useSelector(selectJoinStatus);
+const hasJoined = useSelector(selectHasJoined);
+
+const handleJoinSession = async () => {
+ // Guard checks...
+ await dispatch(joinActiveSession({ sessionId, options }));
+};
+```
+
+### Phase 3: Migrate Entity Collections
+
+Replace `useState` arrays with Redux:
+
+**Before:**
+```javascript
+const [userTracks, setUserTracks] = useState([]);
+const [participants, setParticipants] = useState([]);
+
+// When WebSocket message arrives
+jamServer.on('trackAdded', (data) => {
+ setUserTracks([...userTracks, data.track]);
+});
+```
+
+**After:**
+```javascript
+import { useDispatch, useSelector } from 'react-redux';
+import { selectUserTracks, selectParticipants } from '../../store/features/activeSessionSlice';
+import { useSessionWebSocket } from '../../hooks/useSessionWebSocket';
+
+const userTracks = useSelector(selectUserTracks);
+const participants = useSelector(selectParticipants);
+
+// WebSocket integration is automatic via custom hook
+useSessionWebSocket(sessionId);
+
+// No manual event handlers needed!
+```
+
+### Phase 4: Replace CurrentSessionContext
+
+Once most state is in Redux, you can simplify or remove `CurrentSessionContext`:
+
+**Before:**
+```javascript
+const { currentSession, setCurrentSession, refreshSession } = useCurrentSessionContext();
+```
+
+**After:**
+```javascript
+import { useSelector, useDispatch } from 'react-redux';
+import { selectActiveSession, fetchActiveSession } from '../../store/features/activeSessionSlice';
+
+const currentSession = useSelector(selectActiveSession);
+const dispatch = useDispatch();
+
+// To refresh
+dispatch(fetchActiveSession(sessionId));
+```
+
+## Testing Your Migration
+
+### Check Redux DevTools
+
+Install Redux DevTools browser extension to:
+1. View the entire state tree
+2. See every action dispatched
+3. Time-travel debug state changes
+4. Export/import state for testing
+
+### Verify State Updates
+
+After each phase, verify:
+```javascript
+// Check if state is in Redux
+console.log('Redux State:', store.getState().activeSession);
+console.log('Redux UI:', store.getState().sessionUI);
+```
+
+### Test WebSocket Integration
+
+In JKSessionScreen, verify the custom hook is working:
+```javascript
+useSessionWebSocket(sessionId);
+
+// Watch Redux DevTools - you should see actions dispatched
+// when WebSocket events occur
+```
+
+## Common Patterns
+
+### Pattern 1: Conditional Rendering Based on Redux State
+
+```javascript
+const isRecording = useSelector(selectIsRecording);
+const connectionStatus = useSelector(selectConnectionStatus);
+
+return (
+ <>
+ {connectionStatus === 'disconnected' && }
+ {isRecording && }
+ >
+);
+```
+
+### Pattern 2: Dispatching Multiple Actions
+
+```javascript
+const handleLeaveSession = async () => {
+ // Close all modals first
+ dispatch(closeAllModals());
+
+ // Leave the session
+ await dispatch(leaveActiveSession(sessionId));
+
+ // Navigate away
+ navigate('/dashboard');
+};
+```
+
+### Pattern 3: Using Multiple Selectors
+
+```javascript
+const session = useSelector(selectActiveSession);
+const participants = useSelector(selectParticipants);
+const tracks = useSelector(selectUserTracks);
+const showMixer = useSelector(selectModal('mixer'));
+
+// All selectors are memoized, so re-renders only happen when data changes
+```
+
+## Next Steps
+
+1. **Start with Phase 1**: Migrate modal state first - it's the easiest and gives immediate value
+2. **Test thoroughly**: After each phase, test the UI and check Redux DevTools
+3. **Document as you go**: Update component documentation with Redux patterns
+4. **Remove old contexts gradually**: Once state is migrated, clean up unused contexts
+
+## Notes
+
+- The WebSocket event names in `useSessionWebSocket.js` are examples. Update them to match your actual Protocol Buffer message types.
+- The `jamServer` API methods (`on`, `off`) may differ based on your implementation. Adjust accordingly.
+- Consider creating additional custom hooks for specific features (e.g., `useSessionRecording`, `useSessionTracks`) that encapsulate Redux logic.
+
+## Questions?
+
+If you need help with:
+- Specific WebSocket message types and how to handle them
+- Complex state transformations
+- Performance optimization with selectors
+- Testing Redux logic
+
+Just ask!
diff --git a/jam-ui/docs/TESTING_COMPLETE_SUMMARY.md b/jam-ui/docs/TESTING_COMPLETE_SUMMARY.md
new file mode 100644
index 000000000..224e92df2
--- /dev/null
+++ b/jam-ui/docs/TESTING_COMPLETE_SUMMARY.md
@@ -0,0 +1,406 @@
+# Phase 1 & 2 Testing - Complete Package
+
+## 📦 What You Have
+
+### Testing Resources Created:
+
+1. **📖 TESTING_GUIDE_PHASE1_AND_2.md** (13KB)
+ - Complete manual testing guide
+ - Step-by-step instructions for all features
+ - Redux DevTools usage guide
+ - Troubleshooting section
+
+2. **🧪 Automated Test Files**:
+ - `src/store/features/__tests__/sessionUISlice.test.js` (6KB)
+ - `src/store/features/__tests__/activeSessionSlice.test.js` (11KB)
+ - Total: 73 unit tests covering all Redux logic
+
+3. **🚀 run-phase-tests.sh**
+ - Automated test runner script
+ - Runs all tests with colored output
+ - Shows coverage report
+
+4. **📝 TESTING_QUICK_REFERENCE.md**
+ - Quick cheat sheet for testing
+ - Common commands
+ - Status checklist
+
+5. **📚 Migration Documentation**:
+ - PHASE1_MIGRATION_SUMMARY.md
+ - PHASE2_MIGRATION_SUMMARY.md
+ - REDUX_MIGRATION_GUIDE.md
+
+## 🎯 How to Use This Package
+
+### Option 1: Quick Test (5 minutes)
+
+Run automated tests only:
+
+```bash
+cd jam-ui
+./run-phase-tests.sh
+```
+
+Expected output:
+```
+========================================
+Phase 1 & 2 Redux Migration Test Runner
+========================================
+
+Testing sessionUISlice (Phase 1 - Modals)...
+✓ sessionUISlice tests passed!
+
+Testing activeSessionSlice (Phase 2 - Session Lifecycle)...
+✓ activeSessionSlice tests passed!
+
+========================================
+All automated tests passed! ✓
+========================================
+```
+
+### Option 2: Complete Test (20 minutes)
+
+1. **Run Automated Tests** (5 min):
+ ```bash
+ ./run-phase-tests.sh
+ ```
+
+2. **Run Manual Tests** (15 min):
+ ```bash
+ npm start
+ ```
+ Then follow: `TESTING_GUIDE_PHASE1_AND_2.md` → Manual Testing Checklist
+
+### Option 3: Quick Manual Check (10 minutes)
+
+Use the quick reference:
+
+```bash
+# 1. Start app
+npm start
+
+# 2. Follow quick checklist
+cat TESTING_QUICK_REFERENCE.md
+```
+
+Open Redux DevTools and check off items as you test.
+
+## 📊 Test Coverage
+
+### Current Test Coverage:
+
+```
+sessionUISlice.test.js:
+ ✓ 35+ test cases
+ ✓ All modal actions
+ ✓ All panel actions
+ ✓ All view preferences
+ ✓ All selectors
+
+activeSessionSlice.test.js:
+ ✓ 38+ test cases
+ ✓ All synchronous actions
+ ✓ All async thunks (fetch/join/leave)
+ ✓ Participant management
+ ✓ Track management
+ ✓ Recording state
+ ✓ All selectors
+```
+
+**Total: 73+ automated unit tests**
+
+### What's Tested:
+
+✅ Modal open/close/toggle actions
+✅ Session fetch/join/leave operations
+✅ Guard validation
+✅ User tracks management
+✅ Connection status monitoring
+✅ Participant management
+✅ Recording state management
+✅ State clearing on leave/unmount
+✅ All Redux selectors
+✅ Error handling
+✅ Edge cases
+
+### What's NOT Tested (Manual Only):
+
+- UI rendering (tested manually)
+- WebSocket integration (tested manually)
+- Native client calls (tested manually)
+- User interactions (tested manually)
+- Browser-specific behavior (tested manually)
+
+## 🛠️ Test Commands Reference
+
+```bash
+# Run all tests
+./run-phase-tests.sh
+
+# Run specific test file
+npm test -- sessionUISlice.test.js --watchAll=false
+npm test -- activeSessionSlice.test.js --watchAll=false
+
+# Run with coverage
+npm test -- --coverage --watchAll=false
+
+# Watch mode (development)
+npm test
+
+# Verbose output
+npm test -- --verbose
+
+# Clear cache if issues
+npm test -- --clearCache
+```
+
+## ✅ Testing Workflow
+
+### Recommended Testing Order:
+
+1. **Automated Tests First** ✅
+ ```bash
+ ./run-phase-tests.sh
+ ```
+ - If fails: Fix code, re-run
+ - If passes: Proceed to manual
+
+2. **Phase 1 Manual Tests** ✅
+ - Test all 8 modals
+ - Verify Redux state updates
+ - Check DevTools actions
+
+3. **Phase 2 Manual Tests** ✅
+ - Test session lifecycle
+ - Test connection monitoring
+ - Test leave functionality
+
+4. **Final Verification** ✅
+ - No console errors
+ - Redux state correct
+ - Memory stable
+
+## 🎓 Learning from Tests
+
+### Understanding the Tests:
+
+Each test file follows this structure:
+
+```javascript
+describe('Feature', () => {
+ describe('Action Type', () => {
+ it('should do something specific', () => {
+ // Arrange: Set up initial state
+ // Act: Dispatch action
+ // Assert: Verify new state
+ });
+ });
+});
+```
+
+### Example Test Explained:
+
+```javascript
+it('should handle openModal', () => {
+ // Arrange: Start with initial state (all modals closed)
+ const initialState = { modals: { settings: false } };
+
+ // Act: Dispatch openModal action
+ const actual = sessionUIReducer(initialState, openModal('settings'));
+
+ // Assert: Verify settings modal is now open
+ expect(actual.modals.settings).toBe(true);
+});
+```
+
+This tests that:
+1. Action is dispatched correctly
+2. Reducer handles action properly
+3. State updates as expected
+
+## 🐛 Common Issues & Solutions
+
+### Issue 1: Tests fail with "Cannot find module"
+
+**Solution:**
+```bash
+cd jam-ui
+npm install
+npm test
+```
+
+### Issue 2: Manual tests fail - modal doesn't open
+
+**Solution:**
+1. Check Redux DevTools for action
+2. If no action: Check button onClick
+3. If action fires: Check reducer
+4. If reducer works: Check selector usage
+
+### Issue 3: Session doesn't join
+
+**Solution:**
+1. Check backend services running
+2. Check WebSocket connection
+3. Check console for errors
+4. Verify guards pass first
+
+### Issue 4: State doesn't clear on leave
+
+**Solution:**
+1. Verify clearSession action fires
+2. Check Redux DevTools
+3. Verify reducer returns initialState
+4. Check for competing state updates
+
+## 📈 Success Metrics
+
+### Tests Are Successful When:
+
+✅ All 73+ automated tests pass
+✅ All 8 modals work in browser
+✅ Session lifecycle completes
+✅ Redux state matches expectations
+✅ No console errors
+✅ No memory leaks
+✅ DevTools shows all actions
+
+### Ready for Phase 3 When:
+
+✅ All tests green
+✅ Manual testing complete
+✅ Documentation reviewed
+✅ Code committed to git
+✅ Team reviewed changes (optional)
+
+## 📁 File Organization
+
+```
+jam-ui/
+├── src/
+│ └── store/
+│ └── features/
+│ ├── sessionUISlice.js (Production)
+│ ├── activeSessionSlice.js (Production)
+│ └── __tests__/
+│ ├── sessionUISlice.test.js (Tests)
+│ └── activeSessionSlice.test.js (Tests)
+├── docs/
+│ ├── TESTING_GUIDE_PHASE1_AND_2.md (Full guide)
+│ ├── TESTING_QUICK_REFERENCE.md (Quick ref)
+│ ├── TESTING_COMPLETE_SUMMARY.md (This file)
+│ ├── PHASE1_MIGRATION_SUMMARY.md (Phase 1 docs)
+│ ├── PHASE2_MIGRATION_SUMMARY.md (Phase 2 docs)
+│ └── REDUX_MIGRATION_GUIDE.md (Migration guide)
+└── run-phase-tests.sh (Test runner)
+```
+
+## 🚀 Next Steps
+
+### After All Tests Pass:
+
+1. **Commit Changes**:
+ ```bash
+ git add .
+ git commit -m "feat: complete Phase 1 & 2 Redux migration with tests"
+ ```
+
+2. **Review Documentation**:
+ - Read PHASE1_MIGRATION_SUMMARY.md
+ - Read PHASE2_MIGRATION_SUMMARY.md
+ - Understand what changed and why
+
+3. **Share with Team** (Optional):
+ - Demo Redux DevTools
+ - Show before/after comparison
+ - Explain benefits
+
+4. **Decide on Phase 3**:
+ - Migrate entity collections?
+ - Add WebSocket integration?
+ - Clean up remaining contexts?
+
+### Phase 3 Preview:
+
+If you proceed with Phase 3, you'll migrate:
+- Participants array
+- Jam tracks (selectedJamTrack, jamTrackStems)
+- Backing tracks
+- Recording state details
+- WebSocket callback integration
+
+## 💡 Tips for Success
+
+1. **Run Automated Tests First**
+ - Fastest feedback loop
+ - Catches logic errors early
+ - Builds confidence
+
+2. **Use Redux DevTools**
+ - Essential for debugging
+ - Time-travel is powerful
+ - State inspection helps
+
+3. **Test One Thing at a Time**
+ - Don't rush through checklist
+ - Verify each item thoroughly
+ - Note any issues immediately
+
+4. **Keep Documentation Handy**
+ - Reference guides during testing
+ - Note any gaps or errors
+ - Suggest improvements
+
+5. **Clean Testing Environment**
+ - Clear browser cache
+ - Fresh database state
+ - Restart services if needed
+
+## 📞 Getting Help
+
+### If Tests Fail:
+
+1. Check error messages carefully
+2. Read troubleshooting section
+3. Verify file paths correct
+4. Check for typos in imports
+5. Ensure dependencies installed
+
+### If Manual Tests Fail:
+
+1. Check Redux DevTools first
+2. Look for console errors
+3. Verify backend services running
+4. Check network requests
+5. Try browser refresh
+
+### Resources:
+
+- Full Testing Guide: `TESTING_GUIDE_PHASE1_AND_2.md`
+- Quick Reference: `TESTING_QUICK_REFERENCE.md`
+- Phase 1 Docs: `PHASE1_MIGRATION_SUMMARY.md`
+- Phase 2 Docs: `PHASE2_MIGRATION_SUMMARY.md`
+
+## 🎉 Conclusion
+
+You now have a complete testing package for Phase 1 & 2:
+
+- ✅ 73+ automated unit tests
+- ✅ Comprehensive manual testing guide
+- ✅ Quick reference cheat sheet
+- ✅ Automated test runner script
+- ✅ Detailed documentation
+- ✅ Troubleshooting guide
+
+**Estimated Testing Time:**
+- Automated tests: 5 minutes
+- Manual tests: 15 minutes
+- Total: 20 minutes for complete verification
+
+**Start testing now:**
+```bash
+cd jam-ui
+./run-phase-tests.sh
+```
+
+Good luck! 🚀
diff --git a/jam-ui/docs/TESTING_GUIDE_PHASE1_AND_2.md b/jam-ui/docs/TESTING_GUIDE_PHASE1_AND_2.md
new file mode 100644
index 000000000..377b8058b
--- /dev/null
+++ b/jam-ui/docs/TESTING_GUIDE_PHASE1_AND_2.md
@@ -0,0 +1,651 @@
+# Testing Guide: Phase 1 & 2 Redux Migration
+
+## Overview
+
+This guide covers testing for:
+- **Phase 1**: Modal state migration (8 modals)
+- **Phase 2**: Session lifecycle migration (join/leave, guards, connection)
+
+## Quick Test Commands
+
+```bash
+# Run unit tests for Redux slices
+cd jam-ui
+npm test -- sessionUISlice.test.js
+npm test -- activeSessionSlice.test.js
+
+# Run all tests
+npm test
+
+# Start app for manual testing
+npm start
+```
+
+## Automated Tests
+
+### Running Unit Tests
+
+```bash
+# Test modal slice
+npm test -- src/store/features/__tests__/sessionUISlice.test.js
+
+# Test active session slice
+npm test -- src/store/features/__tests__/activeSessionSlice.test.js
+
+# Test with coverage
+npm test -- --coverage --watchAll=false
+```
+
+### Expected Output
+
+All tests should pass:
+```
+PASS src/store/features/__tests__/sessionUISlice.test.js
+PASS src/store/features/__tests__/activeSessionSlice.test.js
+
+Test Suites: 2 passed, 2 total
+Tests: XX passed, XX total
+```
+
+## Manual Testing Checklist
+
+### Prerequisites
+
+- [ ] Application running: `npm start`
+- [ ] Redux DevTools browser extension installed
+- [ ] You have a valid session ID to test with
+- [ ] Backend services are running (Rails API, WebSocket gateway)
+
+---
+
+## Phase 1: Modal State Testing
+
+### 1. Settings Modal
+
+**Test Steps:**
+1. Navigate to session screen
+2. Click "Settings" button in toolbar
+3. ✅ Verify modal opens
+4. Open Redux DevTools → State → `sessionUI.modals.settings`
+5. ✅ Verify value is `true`
+6. Click close or outside modal
+7. ✅ Verify modal closes
+8. Check Redux DevTools
+9. ✅ Verify value is `false`
+
+**Redux Actions to Watch:**
+- `sessionUI/openModal` with payload `"settings"`
+- `sessionUI/closeModal` with payload `"settings"`
+
+**Potential Issues:**
+- Modal doesn't open → Check dispatch call in button onClick
+- Modal doesn't close → Check toggle/close handler
+
+---
+
+### 2. Invite Modal
+
+**Test Steps:**
+1. Click "Invite" button
+2. ✅ Verify modal opens
+3. Select friends from list
+4. Click "Send Invitations"
+5. ✅ Verify modal closes after success
+6. Check Redux DevTools: `sessionUI.modals.invite`
+7. ✅ Verify transitions `false → true → false`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"invite"`
+- `sessionUI/closeModal` → `"invite"`
+
+---
+
+### 3. Volume Modal
+
+**Test Steps:**
+1. Click "Volume" button
+2. ✅ Verify modal opens
+3. Adjust volume sliders
+4. Click close
+5. ✅ Verify modal closes
+6. Check Redux DevTools: `sessionUI.modals.volume`
+
+**Redux Actions:**
+- `sessionUI/toggleModal` → `"volume"`
+
+---
+
+### 4. Recording Modal
+
+**Test Steps:**
+1. Click "Record" button
+2. ✅ Verify modal opens
+3. Configure recording settings
+4. Click "Start Recording" or "Cancel"
+5. ✅ Verify modal closes
+6. Check Redux DevTools: `sessionUI.modals.recording`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"recording"`
+- `sessionUI/closeModal` → `"recording"`
+
+---
+
+### 5. Leave Modal
+
+**Test Steps:**
+1. Click "Leave Session" button (top right)
+2. ✅ Verify leave confirmation modal opens
+3. Rate session (thumbs up/down)
+4. Add optional comment
+5. Click "Leave Session"
+6. ✅ Verify modal closes
+7. ✅ Verify navigation to `/sessions`
+8. Check Redux DevTools: `sessionUI.modals.leave`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"leave"`
+- `sessionUI/closeModal` → `"leave"`
+
+---
+
+### 6. JamTrack Modal
+
+**Test Steps:**
+1. Click "Open" menu → "JamTrack"
+2. ✅ Verify JamTrack selection modal opens
+3. Browse and select a JamTrack
+4. ✅ Verify modal closes
+5. ✅ Verify JamTrack appears in session
+6. Check Redux DevTools: `sessionUI.modals.jamTrack`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"jamTrack"`
+- `sessionUI/toggleModal` → `"jamTrack"`
+
+---
+
+### 7. Backing Track Popup
+
+**Test Steps:**
+1. Click "Open" menu → "Backing Track"
+2. Select a backing track file
+3. ✅ Verify popup window opens
+4. ✅ Verify backing track player controls visible
+5. Click close on popup
+6. ✅ Verify popup closes
+7. Check Redux DevTools: `sessionUI.modals.backingTrack`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"backingTrack"`
+- `sessionUI/closeModal` → `"backingTrack"`
+
+---
+
+### 8. Media Controls Popup
+
+**Test Steps:**
+1. Trigger media controls popup (if applicable)
+2. ✅ Verify popup opens
+3. Close popup
+4. ✅ Verify popup closes
+5. Check Redux DevTools: `sessionUI.modals.mediaControls`
+
+**Redux Actions:**
+- `sessionUI/openModal` → `"mediaControls"`
+- `sessionUI/closeModal` → `"mediaControls"`
+
+---
+
+### Phase 1 Summary Check
+
+Open Redux DevTools and verify final state:
+```javascript
+sessionUI: {
+ modals: {
+ settings: false,
+ invite: false,
+ volume: false,
+ recording: false,
+ leave: false,
+ jamTrack: false,
+ backingTrack: false,
+ mediaControls: false
+ }
+}
+```
+
+All modals should be `false` when closed.
+
+---
+
+## Phase 2: Session Lifecycle Testing
+
+### 9. Session Fetch on Mount
+
+**Test Steps:**
+1. Navigate to session URL: `/client/sessions/:sessionId`
+2. Open Redux DevTools immediately
+3. Watch for actions:
+ - ✅ `activeSession/fetch/pending`
+ - ✅ `activeSession/fetch/fulfilled`
+4. Check State → `activeSession.sessionData`
+5. ✅ Verify session data populated
+6. ✅ Verify `sessionId` matches URL parameter
+
+**Expected Redux State:**
+```javascript
+activeSession: {
+ sessionId: "123", // from URL
+ sessionData: { /* session object from API */ },
+ loading: false,
+ error: null
+}
+```
+
+**Potential Issues:**
+- No fetch action → Check useEffect dependency array
+- Fetch fails → Check API endpoint and authentication
+
+---
+
+### 10. User Tracks Retrieval
+
+**Test Steps:**
+1. Wait for session guards to run
+2. Watch Redux DevTools for:
+ - ✅ `activeSession/setUserTracks`
+3. Check State → `activeSession.userTracks`
+4. ✅ Verify array of track objects
+5. ✅ Verify tracks match your audio configuration
+
+**Expected Redux State:**
+```javascript
+activeSession: {
+ userTracks: [
+ { id: 1, name: "Track 1", ... },
+ { id: 2, name: "Track 2", ... }
+ ]
+}
+```
+
+**Console Output to Watch:**
+```
+userTracks: [...]
+```
+
+---
+
+### 11. Session Guards Validation
+
+**Test Steps:**
+1. After tracks retrieved, watch for:
+ - ✅ `activeSession/setGuardsPassed`
+2. Check console logs:
+ - ✅ "user has passed all session guards"
+3. Check Redux State → `activeSession.guardsPassed`
+4. ✅ Verify value is `true`
+
+**Expected Redux State:**
+```javascript
+activeSession: {
+ guardsPassed: true
+}
+```
+
+**Potential Issues:**
+- Guards don't pass → Check audio configuration
+- No tracks found → Check gear configuration in app
+
+---
+
+### 12. Session Join
+
+**Test Steps:**
+1. After guards pass, watch for:
+ - ✅ `activeSession/join/fulfilled` (or manual fulfilled dispatch)
+2. Check console logs:
+ - ✅ "join session response xxx"
+3. Check Redux State:
+ - ✅ `activeSession.hasJoined` = `true`
+ - ✅ `activeSession.joinStatus` = `'joined'`
+4. ✅ Verify you can see/hear other participants (if any)
+
+**Expected Redux State:**
+```javascript
+activeSession: {
+ hasJoined: true,
+ joinStatus: 'joined',
+ sessionData: { /* updated with join info */ }
+}
+```
+
+**Redux Actions:**
+- Manual dispatch of `joinActiveSession.fulfilled()`
+
+---
+
+### 13. Connection Status Monitoring
+
+**Test Steps:**
+
+**Test A: Normal Connection**
+1. Check Redux State → `activeSession.connectionStatus`
+2. ✅ Verify value is `'connected'`
+3. ✅ Verify no connection alert shown
+
+**Test B: Disconnect Scenario**
+1. Disconnect from network (turn off WiFi/unplug ethernet)
+2. Wait 5-10 seconds
+3. Watch for action: `activeSession/setConnectionStatus`
+4. Check Redux State:
+ - ✅ `connectionStatus` = `'disconnected'`
+ - ✅ `showConnectionAlert` = `true`
+5. ✅ Verify connection alert banner appears on screen
+
+**Test C: Reconnect Scenario**
+1. Reconnect to network
+2. Watch for action: `activeSession/setConnectionStatus`
+3. Check Redux State:
+ - ✅ `connectionStatus` = `'connected'`
+ - ✅ `showConnectionAlert` = `false`
+4. ✅ Verify connection alert disappears
+
+**Expected Redux State (Connected):**
+```javascript
+activeSession: {
+ connectionStatus: 'connected',
+ showConnectionAlert: false
+}
+```
+
+**Expected Redux State (Disconnected):**
+```javascript
+activeSession: {
+ connectionStatus: 'disconnected',
+ showConnectionAlert: true
+}
+```
+
+---
+
+### 14. Leave Session
+
+**Test Steps:**
+1. Click "Leave Session" button
+2. ✅ Verify leave modal opens (Phase 1 test)
+3. Provide rating and comments
+4. Click "Leave Session" in modal
+5. Watch Redux DevTools for:
+ - ✅ `activeSession/clearSession`
+ - ✅ `sessionUI/closeModal` with `"leave"`
+6. ✅ Verify navigation to `/sessions`
+7. Check Redux State → `activeSession`
+8. ✅ Verify state is reset to initial:
+
+**Expected Redux State (After Leave):**
+```javascript
+activeSession: {
+ sessionId: null,
+ sessionData: null,
+ joinStatus: 'idle',
+ guardsPassed: false,
+ hasJoined: false,
+ userTracks: [],
+ connectionStatus: 'disconnected',
+ showConnectionAlert: false,
+ loading: false,
+ error: null
+}
+```
+
+**Console Output:**
+```
+Closing metronome before leaving session
+Thank you for your feedback!
+```
+
+---
+
+### 15. Cleanup on Unmount
+
+**Test Steps:**
+1. Join a session (follow steps 9-12)
+2. Navigate away using browser back button or clicking Dashboard
+3. Watch Redux DevTools for:
+ - ✅ `activeSession/clearSession`
+4. Navigate back to Redux DevTools → State tab
+5. Check `activeSession`
+6. ✅ Verify state is reset to initial
+
+**Expected Behavior:**
+- Redux state cleared even if user doesn't explicitly leave
+- No memory leaks
+- Clean slate for next session
+
+---
+
+## Phase 2 Summary Check
+
+After completing a full session lifecycle (join → leave), verify Redux state:
+
+```javascript
+// Active session
+activeSession: {
+ sessionId: null,
+ sessionData: null,
+ joinStatus: 'idle',
+ guardsPassed: false,
+ hasJoined: false,
+ userTracks: [],
+ participants: [],
+ connectionStatus: 'disconnected',
+ showConnectionAlert: false,
+ loading: false,
+ error: null
+}
+
+// All modals closed
+sessionUI: {
+ modals: {
+ // all false
+ }
+}
+```
+
+---
+
+## Redux DevTools Tips
+
+### Viewing Actions
+
+1. Open Redux DevTools
+2. Click "Actions" tab (left side)
+3. See chronological list of all actions
+4. Click any action to see:
+ - Action type
+ - Payload
+ - State before/after (Diff tab)
+
+### Time-Travel Debugging
+
+1. In Actions tab, click any previous action
+2. App state jumps back to that point
+3. Scrub through actions like a timeline
+4. Jump back to present with "Jump to Present" button
+
+### State Inspection
+
+1. Click "State" tab
+2. Expand `activeSession` or `sessionUI`
+3. See current values
+4. Values update in real-time as actions dispatch
+
+### Action Filtering
+
+1. Click filter icon in Actions tab
+2. Type action name to filter (e.g., "activeSession")
+3. Only see relevant actions
+
+---
+
+## Common Issues & Debugging
+
+### Issue: Modal doesn't open
+**Symptoms:** Button click does nothing
+**Debug Steps:**
+1. Check console for errors
+2. Verify dispatch call: `dispatch(openModal('modalName'))`
+3. Check Redux DevTools → Actions for `sessionUI/openModal`
+4. Verify modal name matches slice definition
+
+### Issue: Session doesn't join
+**Symptoms:** Guards pass but nothing happens
+**Debug Steps:**
+1. Check console logs for "joining session"
+2. Verify `hasJoined` dependency in useEffect
+3. Check for API errors in Network tab
+4. Verify WebSocket connection
+
+### Issue: Connection alert doesn't show
+**Symptoms:** Disconnect but no alert
+**Debug Steps:**
+1. Check Redux state: `activeSession.showConnectionAlert`
+2. Verify action dispatched: `activeSession/setConnectionStatus`
+3. Check component render for `showConnectionAlert` usage
+
+### Issue: State not clearing on leave
+**Symptoms:** Old session data remains
+**Debug Steps:**
+1. Verify `clearSession` action dispatched
+2. Check Redux DevTools → Action for `activeSession/clearSession`
+3. Verify reducer handles `clearSession` correctly
+
+---
+
+## Performance Testing
+
+### Check Re-renders
+
+1. Install React DevTools
+2. Enable "Highlight updates" in Components tab
+3. Open/close modals → Should only re-render modal component
+4. Join session → Should not cause unnecessary re-renders
+
+### Memory Leaks
+
+1. Join and leave multiple sessions
+2. Open Chrome Task Manager (Shift+Esc)
+3. Monitor memory usage
+4. Should not continuously increase
+
+---
+
+## Test Coverage Goals
+
+- ✅ All 8 modals open/close correctly
+- ✅ Redux state updates match UI state
+- ✅ Session lifecycle completes successfully
+- ✅ Connection monitoring works
+- ✅ Leave operation clears state
+- ✅ Cleanup on unmount works
+- ✅ No console errors during normal flow
+- ✅ Redux DevTools shows expected actions
+
+---
+
+## Quick Checklist
+
+Copy this for quick testing:
+
+```
+Phase 1 - Modals:
+[ ] Settings modal
+[ ] Invite modal
+[ ] Volume modal
+[ ] Recording modal
+[ ] Leave modal
+[ ] JamTrack modal
+[ ] Backing Track popup
+[ ] Media Controls popup
+
+Phase 2 - Lifecycle:
+[ ] Session fetch on mount
+[ ] User tracks retrieved
+[ ] Guards pass
+[ ] Session join success
+[ ] Connection monitoring works
+[ ] Leave session clears state
+[ ] Unmount clears state
+
+Redux DevTools:
+[ ] All actions logged correctly
+[ ] State updates match expectations
+[ ] No errors in actions
+[ ] Time-travel works
+```
+
+---
+
+## Reporting Issues
+
+If you find issues, report with:
+
+1. **What you did**: Step-by-step actions
+2. **What you expected**: Expected behavior
+3. **What happened**: Actual behavior
+4. **Redux state**: Copy from DevTools
+5. **Console errors**: Any error messages
+6. **Screenshots**: If applicable
+
+Example:
+```
+Issue: Settings modal doesn't close
+
+Steps:
+1. Clicked Settings button
+2. Modal opened correctly
+3. Clicked X to close
+4. Modal remained open
+
+Expected: Modal should close
+Actual: Modal stays open
+
+Redux State:
+sessionUI.modals.settings: true (should be false)
+
+Console: No errors
+
+Action in DevTools: sessionUI/closeModal not fired
+```
+
+---
+
+## Success Criteria
+
+✅ **Phase 1 Complete** when:
+- All 8 modals open and close correctly
+- Redux state matches UI state
+- No console errors
+- Redux DevTools shows all actions
+
+✅ **Phase 2 Complete** when:
+- Session lifecycle completes (fetch → join → leave)
+- Connection monitoring works
+- State clears on leave/unmount
+- No memory leaks
+- Redux DevTools shows all lifecycle actions
+
+---
+
+## Next Steps After Testing
+
+Once all tests pass:
+1. Commit changes: `git add . && git commit -m "feat: migrate modal and lifecycle state to Redux"`
+2. Review `PHASE1_MIGRATION_SUMMARY.md` and `PHASE2_MIGRATION_SUMMARY.md`
+3. Consider starting Phase 3 (entity collections)
+4. Or deploy to staging for QA testing
+
+---
+
+**Happy Testing! 🧪**
diff --git a/jam-ui/docs/TESTING_QUICK_REFERENCE.md b/jam-ui/docs/TESTING_QUICK_REFERENCE.md
new file mode 100644
index 000000000..a47555666
--- /dev/null
+++ b/jam-ui/docs/TESTING_QUICK_REFERENCE.md
@@ -0,0 +1,243 @@
+# Testing Quick Reference - Phase 1 & 2
+
+## Quick Start
+
+```bash
+# Run all automated tests
+./run-phase-tests.sh
+
+# OR run individual tests
+npm test -- sessionUISlice.test.js --watchAll=false
+npm test -- activeSessionSlice.test.js --watchAll=false
+
+# Start app for manual testing
+npm start
+```
+
+## Test Status Checklist
+
+### Automated Tests (Unit)
+```
+[ ] sessionUISlice - All modal actions
+[ ] activeSessionSlice - Session lifecycle
+[ ] Test coverage > 80%
+```
+
+### Manual Tests (Phase 1 - Modals)
+```
+[ ] Settings modal opens/closes
+[ ] Invite modal opens/closes
+[ ] Volume modal opens/closes
+[ ] Recording modal opens/closes
+[ ] Leave modal opens/closes
+[ ] JamTrack modal opens/closes
+[ ] Backing Track popup opens/closes
+[ ] Media Controls popup opens/closes
+```
+
+### Manual Tests (Phase 2 - Lifecycle)
+```
+[ ] Session fetches on mount
+[ ] User tracks retrieved
+[ ] Guards pass successfully
+[ ] Session join completes
+[ ] Connection status updates
+[ ] Connection alert shows/hides
+[ ] Leave session works
+[ ] State clears on unmount
+```
+
+## Redux DevTools Checklist
+
+Open Redux DevTools and verify:
+
+### Phase 1 Actions to Watch:
+```
+✓ sessionUI/openModal
+✓ sessionUI/closeModal
+✓ sessionUI/toggleModal
+✓ sessionUI/closeAllModals
+```
+
+### Phase 2 Actions to Watch:
+```
+✓ activeSession/fetch/pending
+✓ activeSession/fetch/fulfilled
+✓ activeSession/setUserTracks
+✓ activeSession/setGuardsPassed
+✓ activeSession/join/fulfilled
+✓ activeSession/setConnectionStatus
+✓ activeSession/clearSession
+```
+
+## Expected State Shape
+
+### sessionUI State:
+```javascript
+{
+ modals: {
+ settings: false,
+ invite: false,
+ volume: false,
+ // ... all false when closed
+ },
+ panels: { /* ... */ },
+ view: { /* ... */ }
+}
+```
+
+### activeSession State:
+```javascript
+{
+ sessionId: "123",
+ sessionData: { /* session object */ },
+ joinStatus: "joined", // idle | joining | joined | leaving
+ guardsPassed: true,
+ hasJoined: true,
+ userTracks: [ /* tracks */ ],
+ participants: [ /* participants */ ],
+ connectionStatus: "connected", // connected | disconnected | reconnecting
+ showConnectionAlert: false,
+ recordingState: {
+ isRecording: false,
+ recordingId: null
+ },
+ loading: false,
+ error: null
+}
+```
+
+## Common Test Commands
+
+```bash
+# Run tests in watch mode
+npm test
+
+# Run specific test file
+npm test -- sessionUISlice.test.js
+
+# Run tests with coverage
+npm test -- --coverage --watchAll=false
+
+# View coverage report
+open coverage/lcov-report/index.html
+
+# Run tests verbosely
+npm test -- --verbose --watchAll=false
+```
+
+## Debugging Failed Tests
+
+### Test fails with "Cannot find module"
+```bash
+# Make sure you're in jam-ui directory
+cd jam-ui
+npm install
+```
+
+### Test fails with "unexpected token"
+```bash
+# Clear Jest cache
+npm test -- --clearCache
+```
+
+### Test fails with Redux errors
+```bash
+# Check if mocks are set up correctly
+# See __tests__/ files for mock examples
+```
+
+## Manual Testing Flow
+
+### Complete Flow Test (15 minutes):
+
+1. **Start App** (1 min)
+ ```bash
+ npm start
+ ```
+ Open http://beta.jamkazam.local:4000
+
+2. **Test Modals** (5 min)
+ - Open each modal from toolbar
+ - Verify Redux state updates
+ - Close each modal
+ - Verify state resets
+
+3. **Test Session Lifecycle** (9 min)
+ - Navigate to session: `/client/sessions/:id`
+ - Watch Redux DevTools for actions
+ - Verify guards pass
+ - Verify join completes
+ - Test connection monitoring
+ - Leave session
+ - Verify state clears
+
+## Success Criteria
+
+✅ **All automated tests pass**
+✅ **All modals work correctly**
+✅ **Session lifecycle completes**
+✅ **Redux state updates correctly**
+✅ **No console errors**
+✅ **Memory doesn't leak**
+
+## Next Steps After Testing
+
+1. ✅ Commit changes:
+ ```bash
+ git add .
+ git commit -m "feat: Phase 1 & 2 Redux migration complete"
+ ```
+
+2. ✅ Review documentation:
+ - PHASE1_MIGRATION_SUMMARY.md
+ - PHASE2_MIGRATION_SUMMARY.md
+
+3. ✅ Decide on Phase 3:
+ - Migrate entity collections
+ - Add WebSocket integration
+ - Clean up remaining contexts
+
+## Troubleshooting
+
+### Issue: Tests won't run
+**Solution:**
+```bash
+cd jam-ui
+rm -rf node_modules
+npm install
+npm test
+```
+
+### Issue: Coverage report not generated
+**Solution:**
+```bash
+npm test -- --coverage --watchAll=false --collectCoverageFrom='src/store/features/*.js'
+```
+
+### Issue: Redux state not updating in browser
+**Solution:**
+1. Check Redux DevTools is installed
+2. Refresh page
+3. Clear browser cache
+4. Check console for errors
+
+### Issue: Modal opens but doesn't close
+**Solution:**
+1. Check dispatch(closeModal) is called
+2. Check Redux DevTools for action
+3. Verify modal prop is using selector
+4. Check toggle handler syntax
+
+---
+
+## Resources
+
+- **Full Testing Guide**: `TESTING_GUIDE_PHASE1_AND_2.md`
+- **Phase 1 Summary**: `PHASE1_MIGRATION_SUMMARY.md`
+- **Phase 2 Summary**: `PHASE2_MIGRATION_SUMMARY.md`
+- **Redux Migration Guide**: `REDUX_MIGRATION_GUIDE.md`
+
+---
+
+**Questions?** Check the full testing guide or documentation files above.
diff --git a/jam-ui/package-lock.json b/jam-ui/package-lock.json
index 52418f6a7..6693e80fe 100644
--- a/jam-ui/package-lock.json
+++ b/jam-ui/package-lock.json
@@ -16035,6 +16035,13 @@
"node": ">= 6"
}
},
+ "node_modules/jquery": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/js-base64": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
diff --git a/jam-ui/package.json b/jam-ui/package.json
index 6789afd8a..f752e6f65 100644
--- a/jam-ui/package.json
+++ b/jam-ui/package.json
@@ -97,7 +97,8 @@
"eject": "react-scripts eject",
"scss": "gulp",
"analyze": "npx source-map-explorer 'build/static/js/*.js'",
- "test": "playwright test"
+ "test": "playwright test",
+ "test:unit": "react-scripts test --watchAll=false"
},
"eslintConfig": {
"extends": "react-app"
diff --git a/jam-ui/run-phase-tests.sh b/jam-ui/run-phase-tests.sh
new file mode 100755
index 000000000..50bc9e739
--- /dev/null
+++ b/jam-ui/run-phase-tests.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+echo -e "${BLUE}========================================${NC}"
+echo -e "${BLUE}Phase 1 & 2 Redux Migration Test Runner${NC}"
+echo -e "${BLUE}========================================${NC}\n"
+
+# Check if we're in the right directory
+if [ ! -f "package.json" ]; then
+ echo -e "${RED}Error: package.json not found. Please run this script from jam-ui directory.${NC}"
+ exit 1
+fi
+
+echo -e "${YELLOW}Installing dependencies (if needed)...${NC}"
+npm install --silent
+
+echo -e "\n${BLUE}Running Unit Tests...${NC}\n"
+
+# Run sessionUISlice tests
+echo -e "${YELLOW}Testing sessionUISlice (Phase 1 - Modals)...${NC}"
+npm run test:unit -- src/store/features/__tests__/sessionUISlice.test.js --verbose
+
+if [ $? -eq 0 ]; then
+ echo -e "${GREEN}✓ sessionUISlice tests passed!${NC}\n"
+else
+ echo -e "${RED}✗ sessionUISlice tests failed!${NC}\n"
+ exit 1
+fi
+
+# Run activeSessionSlice tests
+echo -e "${YELLOW}Testing activeSessionSlice (Phase 2 - Session Lifecycle)...${NC}"
+npm run test:unit -- src/store/features/__tests__/activeSessionSlice.test.js --verbose
+
+if [ $? -eq 0 ]; then
+ echo -e "${GREEN}✓ activeSessionSlice tests passed!${NC}\n"
+else
+ echo -e "${RED}✗ activeSessionSlice tests failed!${NC}\n"
+ exit 1
+fi
+
+echo -e "${BLUE}========================================${NC}"
+echo -e "${GREEN}All automated tests passed! ✓${NC}"
+echo -e "${BLUE}========================================${NC}\n"
+
+echo -e "${YELLOW}Next steps:${NC}"
+echo -e " 1. Run manual tests: See ${BLUE}docs/TESTING_GUIDE_PHASE1_AND_2.md${NC}"
+echo -e " 2. Start the app: ${BLUE}npm start${NC}"
+echo -e " 3. Open Redux DevTools and verify state changes"
+echo -e " 4. Test all modals and session lifecycle\n"
+
+echo -e "${GREEN}Test coverage report:${NC}"
+npm run test:unit -- src/store/features/__tests__/ --coverage --collectCoverageFrom='src/store/features/*.js' 2>/dev/null | grep -A 20 "Coverage summary"
+
+echo -e "\n${YELLOW}View full coverage:${NC} open coverage/lcov-report/index.html\n"
diff --git a/jam-ui/src/components/client/JKSessionScreen.js b/jam-ui/src/components/client/JKSessionScreen.js
index fee5c7e43..4c3ecc2cb 100644
--- a/jam-ui/src/components/client/JKSessionScreen.js
+++ b/jam-ui/src/components/client/JKSessionScreen.js
@@ -1,6 +1,7 @@
// jam-ui/src/components/client/JKSessionScreen.js
import React, { useEffect, useContext, useState, memo, useMemo, useCallback } from 'react'
import { useParams, useHistory } from 'react-router-dom';
+import { useDispatch, useSelector } from 'react-redux';
//import useJamServer, { ConnectionStatus } from '../../hooks/useJamServer'
import useGearUtils from '../../hooks/useGearUtils'
@@ -22,6 +23,25 @@ import { dkeys } from '../../helpers/utils.js';
import { getSessionHistory, getSession, joinSession as joinSessionRest, updateSessionSettings, getFriends, startRecording, stopRecording, submitSessionFeedback, getVideoConferencingRoomUrl, getJamTrack, closeJamTrack, openMetronome } from '../../helpers/rest';
+// Redux imports
+import { openModal, closeModal, toggleModal, selectModal } from '../../store/features/sessionUISlice';
+import {
+ fetchActiveSession,
+ joinActiveSession,
+ leaveActiveSession,
+ setGuardsPassed,
+ setUserTracks,
+ setConnectionStatus,
+ clearSession,
+ selectActiveSession,
+ selectJoinStatus,
+ selectHasJoined,
+ selectGuardsPassed,
+ selectUserTracks,
+ selectShowConnectionAlert,
+ selectSessionId
+} from '../../store/features/activeSessionSlice';
+
import { CLIENT_ROLE, RECORD_TYPE_AUDIO, RECORD_TYPE_BOTH } from '../../helpers/globals';
import { MessageType } from '../../helpers/MessageFactory.js';
@@ -61,6 +81,7 @@ import resyncIcon from '../../assets/img/client/resync.svg';
const JKSessionScreen = () => {
const logger = console; // Replace with another logging mechanism if needed
+ const dispatch = useDispatch();
const app = useJamKazamApp();
const { currentUser } = useAuth();
@@ -93,36 +114,44 @@ const JKSessionScreen = () => {
const { id: sessionId } = useParams();
const history = useHistory();
- // State to hold session data
- const [userTracks, setUserTracks] = useState([]);
- const [showConnectionAlert, setShowConnectionAlert] = useState(false);
- const [hasJoined, setHasJoined] = useState(false);
+ // Redux session lifecycle state
+ const activeSession = useSelector(selectActiveSession);
+ const joinStatus = useSelector(selectJoinStatus);
+ const hasJoined = useSelector(selectHasJoined);
+ const sessionGuardsPassed = useSelector(selectGuardsPassed);
+ const userTracks = useSelector(selectUserTracks);
+ const showConnectionAlert = useSelector(selectShowConnectionAlert);
+ const reduxSessionId = useSelector(selectSessionId);
+
+ // Non-lifecycle state (keeping as local for now)
const [requestingSessionRefresh, setRequestingSessionRefresh] = useState(false);
const [pendingSessionRefresh, setPendingSessionRefresh] = useState(false);
const [sessionRules, setSessionRules] = useState(null);
const [subscriptionRules, setSubscriptionRules] = useState(null);
const [currentOrLastSession, setCurrentOrLastSession] = useState(null);
- const [sessionGuardsPassed, setSessionGuardsPassed] = useState(false);
- //state for settings modal
- const [showSettingsModal, setShowSettingsModal] = useState(false);
+ // Redux modal state
+ const showSettingsModal = useSelector(selectModal('settings'));
+ const showInviteModal = useSelector(selectModal('invite'));
+ const showVolumeModal = useSelector(selectModal('volume'));
+ const showRecordingModal = useSelector(selectModal('recording'));
+ const showLeaveModal = useSelector(selectModal('leave'));
+ const showJamTrackModal = useSelector(selectModal('jamTrack'));
+ const showBackingTrackPopup = useSelector(selectModal('backingTrack'));
+ const showMediaControlsPopup = useSelector(selectModal('mediaControls'));
+
+ // Non-modal state for settings modal
const [settingsLoading, setSettingsLoading] = useState(false);
- //state for invite modal
+ // Non-modal state for invite modal
const [friends, setFriends] = useState([]);
- const [showInviteModal, setShowInviteModal] = useState(false);
const [sessionInvitees, setSessionInvitees] = useState([]);
const [inviteLoading, setInviteLoading] = useState(false);
- //state for volume level modal
- const [showVolumeModal, setShowVolumeModal] = useState(false);
+ // Non-modal state for volume modal
const [volumeLevel, setVolumeLevel] = useState(100);
- //state for recording modal
- const [showRecordingModal, setShowRecordingModal] = useState(false);
-
- //state for leave modal
- const [showLeaveModal, setShowLeaveModal] = useState(false);
+ // Non-modal state for leave modal
const [leaveRating, setLeaveRating] = useState(null); // null, 'thumbsUp', 'thumbsDown'
const [leaveComments, setLeaveComments] = useState('');
const [leaveLoading, setLeaveLoading] = useState(false);
@@ -130,16 +159,15 @@ const JKSessionScreen = () => {
//state for video button
const [videoLoading, setVideoLoading] = useState(false);
- //state for backing track popup
- const [showBackingTrackPopup, setShowBackingTrackPopup] = useState(false);
+ //state for backing track popup data (modal visibility now in Redux)
const [backingTrackData, setBackingTrackData] = useState(null);
// Stable callback for backing track popup close
const handleBackingTrackClose = useCallback(() => {
console.log('JKSessionScreen: Backing Track Popup closing');
- setShowBackingTrackPopup(false);
+ dispatch(closeModal('backingTrack'));
setBackingTrackData(null);
- }, []);
+ }, [dispatch]);
// Stable callback for backing track close in main screen
const handleBackingTrackMainClose = useCallback(() => {
@@ -147,14 +175,10 @@ const JKSessionScreen = () => {
closeMedia();
}, [closeMedia]);
- //state for media controls popup
- const [showMediaControlsPopup, setShowMediaControlsPopup] = useState(false);
+ // State for media controls popup (modal visibility now in Redux)
const [mediaControlsOpened, setMediaControlsOpened] = useState(false);
const [popupGuard, setPopupGuard] = useState(false); // Hard guard against infinite loops
- //state for jam track modal
- const [showJamTrackModal, setShowJamTrackModal] = useState(false);
-
//state for selected jam track and stems
const [selectedJamTrack, setSelectedJamTrack] = useState(null);
const [jamTrackStems, setJamTrackStems] = useState([]);
@@ -170,6 +194,14 @@ const JKSessionScreen = () => {
setRegisteredCallbacks([]);
}, [registeredCallbacks, unregisterMessageCallback]);
+ // Fetch session data when sessionId changes
+ useEffect(() => {
+ if (sessionId && sessionId !== reduxSessionId) {
+ console.log('Fetching active session:', sessionId);
+ dispatch(fetchActiveSession(sessionId));
+ }
+ }, [sessionId, reduxSessionId, dispatch]);
+
useEffect(() => {
if (!isConnected || !jamClient) return;
console.debug("JKSessionScreen: -DEBUG- isConnected changed to true");
@@ -194,7 +226,7 @@ const JKSessionScreen = () => {
if (clientRole === CLIENT_ROLE.CHILD) {
logger.debug("client is configured to act as child. skipping all checks. assuming 0 tracks");
- setUserTracks([]);
+ dispatch(setUserTracks([]));
//skipping all checks. assuming 0 tracks
await joinSession();
@@ -211,13 +243,13 @@ const JKSessionScreen = () => {
logger.log("user has an active profile");
try {
const tracks = await sessionModel.waitForSessionPageEnterDone();
- setUserTracks(tracks);
+ dispatch(setUserTracks(tracks));
logger.log("userTracks: ", tracks);
try {
await ensureAppropriateProfile(musicianAccessOnJoin)
logger.log("user has passed all session guards")
- setSessionGuardsPassed(true)
+ dispatch(setGuardsPassed(true))
} catch (error) {
logger.error("User profile is not appropriate for session:", error);
@@ -322,7 +354,10 @@ const JKSessionScreen = () => {
const data = await response.json();
console.debug("join session response xxx: ", data);
- setHasJoined(true);
+
+ // Update Redux state - user has successfully joined
+ dispatch(joinActiveSession.fulfilled(data, '', { sessionId, options: {} }));
+
if (!inSession()) {
// the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out
logger.debug("user left before fully joined to session. telling server again that they have left");
@@ -475,11 +510,13 @@ const JKSessionScreen = () => {
useEffect(() => {
if (connectionStatus === ConnectionStatus.DISCONNECTED ||
connectionStatus === ConnectionStatus.ERROR) {
- setShowConnectionAlert(true);
+ dispatch(setConnectionStatus('disconnected'));
} else if (connectionStatus === ConnectionStatus.CONNECTED) {
- setShowConnectionAlert(false);
+ dispatch(setConnectionStatus('connected'));
+ } else if (connectionStatus === ConnectionStatus.RECONNECTING) {
+ dispatch(setConnectionStatus('reconnecting'));
}
- }, [connectionStatus]);
+ }, [connectionStatus, dispatch]);
// Handlers for recording and playback
// const handleStartRecording = () => {
@@ -622,7 +659,7 @@ const JKSessionScreen = () => {
const handleLeaveSession = () => {
// Just show the modal - no leave operations yet
- setShowLeaveModal(true);
+ dispatch(openModal('leave'));
};
const handleLeaveSubmit = async (feedbackData) => {
@@ -651,7 +688,10 @@ const JKSessionScreen = () => {
// Then perform leave operations
await sessionModel.handleLeaveSession();
- setShowLeaveModal(false);
+ // Clear Redux session state
+ dispatch(clearSession());
+
+ dispatch(closeModal('leave'));
toast.success('Thank you for your feedback!');
// Navigate to sessions page using React Router
@@ -675,8 +715,11 @@ const JKSessionScreen = () => {
console.log('Resetting metronome state on session cleanup');
resetMetronome();
}
+
+ // Clear Redux session state on unmount
+ dispatch(clearSession());
};
- }, [metronomeState.isOpen, resetMetronome]);
+ }, [metronomeState.isOpen, resetMetronome, dispatch]);
// Check if user can use video (subscription/permission check)
const canVideo = () => {
@@ -770,7 +813,7 @@ const JKSessionScreen = () => {
// Show the popup
console.log('JKSessionScreen: Setting showBackingTrackPopup to true...');
- setShowBackingTrackPopup(true);
+ dispatch(openModal('backingTrack'));
console.log('JKSessionScreen: handleBackingTrackSelected completed successfully');
//TODO: In the legacy client, the popup window was opened as a native window through the client. decide whether we need to replicate that behavior here or do it through the browser only
} catch (error) {
@@ -913,13 +956,13 @@ const JKSessionScreen = () => {
-