---
phase: 07-chat-infrastructure
plan: 01
type: tdd
---

<objective>
Create sessionChatSlice with initial state structure and core reducers using TDD methodology.

Purpose: Build Redux foundation for chat feature with multi-channel state management, message deduplication, and unread tracking. All state transitions must be tested following CLAUDE.md TDD requirements.

Output: sessionChatSlice.js with 7 reducers and comprehensive unit tests.
</objective>

<execution_context>
@./.claude/get-shit-done/workflows/execute-phase.md
@./.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md

# Phase 6 design documents
@.planning/phases/06-session-chat-research-design/CHAT_REDUX_DESIGN.md
@.planning/phases/06-session-chat-research-design/IMPLEMENTATION_ROADMAP.md

# Existing Redux patterns
@jam-ui/src/store/features/mediaSlice.js
@jam-ui/src/store/features/activeSessionSlice.js

# Codebase conventions
@.planning/codebase/ARCHITECTURE.md
@.planning/codebase/CONVENTIONS.md
@CLAUDE.md

**Key context from Phase 6:**
- Multi-channel state (global, session, lesson) keyed by channel ID
- Message deduplication by msg_id (critical for WebSocket + REST)
- Unread tracking is NEW functionality (client-side with localStorage)
- State structure designed in CHAT_REDUX_DESIGN.md lines 0-150
- 7 reducers defined: addMessageFromWebSocket, setActiveChannel, openChatWindow, closeChatWindow, markAsRead, incrementUnreadCount, setWindowPosition

**TDD requirements from CLAUDE.md:**
- Write test FIRST (RED phase)
- Implement minimum code to pass (GREEN phase)
- Refactor while keeping tests green
- All reducers must have unit tests
- All state transitions must be validated
</context>

<tasks>

<task type="tdd">
  <name>Task 1: Create sessionChatSlice.js with initial state structure (TDD)</name>
  <files>jam-ui/src/store/features/sessionChatSlice.js, jam-ui/src/store/features/__tests__/sessionChatSlice.test.js</files>
  <action>
Using TDD methodology, create the Redux slice file and initial state:

**RED Phase - Write failing tests first:**

1. Create test file `jam-ui/src/store/features/__tests__/sessionChatSlice.test.js`
2. Write tests for initial state structure:
   ```javascript
   describe('sessionChatSlice initial state', () => {
     test('has empty messagesByChannel object', () => {
       const state = sessionChatReducer(undefined, { type: 'unknown' });
       expect(state.messagesByChannel).toEqual({});
     });

     test('has null activeChannel', () => {
       const state = sessionChatReducer(undefined, { type: 'unknown' });
       expect(state.activeChannel).toBeNull();
     });

     test('has empty unreadCounts object', () => {
       const state = sessionChatReducer(undefined, { type: 'unknown' });
       expect(state.unreadCounts).toEqual({});
     });

     // ... tests for all initial state fields
   });
   ```
3. Run tests - should FAIL (slice doesn't exist yet)

**GREEN Phase - Implement minimum code:**

4. Create `jam-ui/src/store/features/sessionChatSlice.js` with:
   ```javascript
   import { createSlice } from '@reduxjs/toolkit';

   const initialState = {
     messagesByChannel: {},
     activeChannel: null,
     channelType: null,
     unreadCounts: {},
     lastReadAt: {},
     fetchStatus: {},
     fetchError: {},
     sendStatus: 'idle',
     sendError: null,
     nextCursors: {},
     isWindowOpen: false,
     windowPosition: null
   };

   const sessionChatSlice = createSlice({
     name: 'sessionChat',
     initialState,
     reducers: {
       // Reducers added in Tasks 2-3
     }
   });

   export const {} = sessionChatSlice.actions;
   export default sessionChatSlice.reducer;
   ```
5. Add slice to store configuration in `jam-ui/src/store/index.js`
6. Run tests - should PASS

**REFACTOR Phase:**

7. Review state structure against CHAT_REDUX_DESIGN.md
8. Add TypeScript-style JSDoc comments for type hints
9. Ensure tests still pass

**Verification:**
- All initial state tests pass
- Slice exports reducer and actions
- Store configuration includes sessionChat slice
  </action>
  <verify>
sessionChatSlice.js exists with correct initial state. Unit tests pass. Slice registered in store. TDD methodology followed (RED-GREEN-REFACTOR).
  </verify>
  <done>
Redux slice foundation complete with validated initial state structure.
  </done>
</task>

<task type="tdd">
  <name>Task 2: Implement core reducers with message deduplication (TDD)</name>
  <files>jam-ui/src/store/features/sessionChatSlice.js, jam-ui/src/store/features/__tests__/sessionChatSlice.test.js</files>
  <action>
Using TDD, implement reducers: addMessageFromWebSocket, setActiveChannel, openChatWindow, closeChatWindow

**RED Phase - Write failing tests first:**

1. Write tests for `addMessageFromWebSocket`:
   ```javascript
   describe('addMessageFromWebSocket', () => {
     test('adds message to new channel', () => {
       const state = { messagesByChannel: {} };
       const action = {
         type: 'sessionChat/addMessageFromWebSocket',
         payload: {
           id: 'msg-1',
           senderId: 'user-1',
           message: 'Hello',
           channel: 'session',
           sessionId: 'session-abc',
           createdAt: '2026-01-26T12:00:00Z'
         }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.messagesByChannel['session-abc']).toHaveLength(1);
     });

     test('deduplicates messages by msg_id', () => {
       // Test adding same message twice (critical for WebSocket + REST)
       const state = {
         messagesByChannel: {
           'session-abc': [{ id: 'msg-1', message: 'Hello' }]
         }
       };
       const action = {
         type: 'sessionChat/addMessageFromWebSocket',
         payload: { id: 'msg-1', message: 'Hello', /* ... */ }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.messagesByChannel['session-abc']).toHaveLength(1);
     });

     test('increments unread count if window closed', () => {
       // Test unread tracking logic
     });
   });
   ```

2. Write tests for `setActiveChannel`:
   ```javascript
   describe('setActiveChannel', () => {
     test('sets active channel and type', () => {
       const state = { activeChannel: null, channelType: null };
       const action = {
         type: 'sessionChat/setActiveChannel',
         payload: { channel: 'session-abc', channelType: 'session' }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.activeChannel).toBe('session-abc');
       expect(newState.channelType).toBe('session');
     });
   });
   ```

3. Write tests for `openChatWindow` (resets unread count):
   ```javascript
   describe('openChatWindow', () => {
     test('opens window and resets unread count for active channel', () => {
       const state = {
         isWindowOpen: false,
         activeChannel: 'session-abc',
         unreadCounts: { 'session-abc': 5 },
         lastReadAt: {}
       };
       const action = { type: 'sessionChat/openChatWindow' };
       const newState = sessionChatReducer(state, action);
       expect(newState.isWindowOpen).toBe(true);
       expect(newState.unreadCounts['session-abc']).toBe(0);
       expect(newState.lastReadAt['session-abc']).toBeTruthy();
     });
   });
   ```

4. Write tests for `closeChatWindow`:
   ```javascript
   describe('closeChatWindow', () => {
     test('closes window without changing unread counts', () => {
       const state = {
         isWindowOpen: true,
         unreadCounts: { 'session-abc': 0 }
       };
       const action = { type: 'sessionChat/closeChatWindow' };
       const newState = sessionChatReducer(state, action);
       expect(newState.isWindowOpen).toBe(false);
       expect(newState.unreadCounts['session-abc']).toBe(0);
     });
   });
   ```

5. Run tests - should FAIL

**GREEN Phase - Implement reducers:**

6. Implement reducers in sessionChatSlice.js following CHAT_REDUX_DESIGN.md patterns
7. Key implementation details:
   - Channel key construction: session messages use 'session-{id}', global uses 'global'
   - Message deduplication: check `msg_id` exists before adding
   - Unread increment: only if window closed AND channel not active
   - lastReadAt: set to current ISO timestamp on window open
8. Run tests - should PASS

**REFACTOR Phase:**

9. Extract channel key construction to helper function
10. Add JSDoc comments
11. Ensure tests still pass

**Verification:**
- All reducer tests pass
- Message deduplication works correctly
- Unread tracking logic validated
  </action>
  <verify>
4 core reducers implemented with passing unit tests. Message deduplication logic validated. Unread tracking works correctly.
  </verify>
  <done>
Core message and window management reducers complete with TDD validation.
  </done>
</task>

<task type="tdd">
  <name>Task 3: Implement unread tracking reducers (TDD)</name>
  <files>jam-ui/src/store/features/sessionChatSlice.js, jam-ui/src/store/features/__tests__/sessionChatSlice.test.js</files>
  <action>
Using TDD, implement remaining reducers: markAsRead, incrementUnreadCount, setWindowPosition

**RED Phase - Write failing tests first:**

1. Write tests for `markAsRead`:
   ```javascript
   describe('markAsRead', () => {
     test('resets unread count for specified channel', () => {
       const state = {
         unreadCounts: { 'session-abc': 5, 'global': 2 },
         lastReadAt: {}
       };
       const action = {
         type: 'sessionChat/markAsRead',
         payload: { channel: 'session-abc' }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.unreadCounts['session-abc']).toBe(0);
       expect(newState.unreadCounts['global']).toBe(2); // Unchanged
       expect(newState.lastReadAt['session-abc']).toBeTruthy();
     });
   });
   ```

2. Write tests for `incrementUnreadCount`:
   ```javascript
   describe('incrementUnreadCount', () => {
     test('increments unread count for channel', () => {
       const state = { unreadCounts: { 'session-abc': 3 } };
       const action = {
         type: 'sessionChat/incrementUnreadCount',
         payload: { channel: 'session-abc' }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.unreadCounts['session-abc']).toBe(4);
     });

     test('initializes unread count if not exists', () => {
       const state = { unreadCounts: {} };
       const action = {
         type: 'sessionChat/incrementUnreadCount',
         payload: { channel: 'session-abc' }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.unreadCounts['session-abc']).toBe(1);
     });
   });
   ```

3. Write tests for `setWindowPosition`:
   ```javascript
   describe('setWindowPosition', () => {
     test('updates window position', () => {
       const state = { windowPosition: null };
       const action = {
         type: 'sessionChat/setWindowPosition',
         payload: { x: 300, y: 400 }
       };
       const newState = sessionChatReducer(state, action);
       expect(newState.windowPosition).toEqual({ x: 300, y: 400 });
     });
   });
   ```

4. Run tests - should FAIL

**GREEN Phase - Implement reducers:**

5. Implement the 3 remaining reducers in sessionChatSlice.js
6. Key implementation details:
   - markAsRead: reset count to 0, update lastReadAt timestamp
   - incrementUnreadCount: handle missing channel gracefully
   - setWindowPosition: used by WindowPortal for persistence
7. Run tests - should PASS

**REFACTOR Phase:**

8. Review all 7 reducers for consistency
9. Add JSDoc comments for all reducers
10. Export all actions
11. Ensure tests still pass

**Integration verification:**

12. Write a comprehensive integration test that uses multiple reducers:
    ```javascript
    describe('sessionChat integration', () => {
      test('complete message flow: receive → open window → mark read', () => {
        let state = initialState;

        // Receive message (window closed)
        state = sessionChatReducer(state, addMessageFromWebSocket({...}));
        expect(state.unreadCounts['session-abc']).toBe(1);

        // Set active channel
        state = sessionChatReducer(state, setActiveChannel({...}));
        expect(state.activeChannel).toBe('session-abc');

        // Open window (should mark as read)
        state = sessionChatReducer(state, openChatWindow());
        expect(state.unreadCounts['session-abc']).toBe(0);
      });
    });
    ```

**Verification:**
- All 7 reducer tests pass
- Integration test validates complete flow
- All actions exported
  </action>
  <verify>
All 7 reducers implemented with passing unit tests. Integration test validates complete message flow. Actions exported correctly.
  </verify>
  <done>
Complete sessionChatSlice with all reducers, comprehensive unit tests, and integration validation. TDD methodology followed throughout.
  </done>
</task>

</tasks>

<verification>
Before declaring plan complete:
- [ ] sessionChatSlice.js exists with complete initial state
- [ ] All 7 reducers implemented: addMessageFromWebSocket, setActiveChannel, openChatWindow, closeChatWindow, markAsRead, incrementUnreadCount, setWindowPosition
- [ ] Comprehensive unit tests written (TDD approach)
- [ ] Message deduplication logic tested and working
- [ ] Unread tracking logic tested and working
- [ ] Integration test validates complete message flow
- [ ] Slice registered in store configuration
- [ ] All tests pass (RED-GREEN-REFACTOR cycle followed)
- [ ] TDD requirements from CLAUDE.md satisfied
</verification>

<success_criteria>

- sessionChatSlice.js created with all 7 reducers
- Unit test coverage 100% for all reducers
- Message deduplication working correctly
- Unread tracking validated with tests
- Integration test shows complete flow works
- All tests passing
- TDD methodology strictly followed (test-first approach)
- Ready for Phase 7 Plan 2 (Async Thunks)
</success_criteria>

<output>
After completion, create `.planning/phases/07-chat-infrastructure/07-01-SUMMARY.md`:

# Phase 7 Plan 1: Redux Slice & Core Reducers Summary

**Created sessionChatSlice with all reducers using TDD methodology**

## Accomplishments

- Created sessionChatSlice.js with complete initial state structure
- Implemented 7 reducers with TDD approach (test-first)
- Comprehensive unit test suite with 100% coverage
- Message deduplication logic validated
- Unread tracking system tested and working
- Integration test validates complete message flow

## Files Created/Modified

- `jam-ui/src/store/features/sessionChatSlice.js` - Redux slice with 7 reducers
- `jam-ui/src/store/features/__tests__/sessionChatSlice.test.js` - Unit tests
- `jam-ui/src/store/index.js` - Store configuration (added sessionChat slice)

## Decisions Made

[Document any implementation decisions: Helper functions? State structure refinements? Edge cases discovered?]

## Issues Encountered

[Any challenges during TDD process, or "None"]

## Next Phase Readiness

Ready for Plan 2 (Async Thunks & API Integration)
</output>
