---
phase: 21-chat-window-fixes
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - jam-ui/src/store/features/sessionChatSlice.js
  - jam-ui/src/components/client/JKSessionScreen.js
autonomous: true

must_haves:
  truths:
    - "Chat messages are limited to MAX_MESSAGES (500) per channel"
    - "Oldest messages are removed when new messages exceed the limit"
    - "Chat state is cleared when user leaves session"
    - "Memory usage does not grow unbounded with chat activity"
  artifacts:
    - path: "jam-ui/src/store/features/sessionChatSlice.js"
      provides: "MAX_MESSAGES constant and clearAllMessages action"
      contains: "MAX_MESSAGES"
    - path: "jam-ui/src/components/client/JKSessionScreen.js"
      provides: "Chat cleanup on session leave"
      contains: "clearAllMessages"
  key_links:
    - from: "sessionChatSlice.js addMessageFromWebSocket"
      to: "slice(-MAX_MESSAGES)"
      via: "array limit after push"
      pattern: "slice.*-MAX_MESSAGES"
    - from: "JKSessionScreen.js leave handler"
      to: "clearAllMessages action"
      via: "dispatch on session leave"
      pattern: "dispatch.*clearAllMessages"
---

<objective>
Implement bounded chat message storage and cleanup to prevent unbounded memory growth.

Purpose: Fix CHAT-02 (unbounded message growth) and CHAT-03 (no cleanup on session leave) identified in Phase 19 audit. Note: CHAT-01 (WebSocket listener audit) was verified as properly implemented in Phase 19 audit. Without these fixes, the messagesByChannel Redux state grows indefinitely during active chat sessions, contributing to memory degradation over time.

Output: sessionChatSlice.js with MAX_MESSAGES limit enforced on all message additions, and JKSessionScreen.js dispatching clearAllMessages when leaving sessions.
</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/REQUIREMENTS.md
@.planning/phases/19-audit-and-discovery/19-AUDIT.md

# Key source files
@jam-ui/src/store/features/sessionChatSlice.js
@jam-ui/src/components/client/JKSessionScreen.js
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add MAX_MESSAGES limit to sessionChatSlice.js</name>
  <files>jam-ui/src/store/features/sessionChatSlice.js</files>
  <action>
Add bounded message storage to prevent unbounded memory growth:

1. Add constant at top of file (after imports, before initialState):
   ```javascript
   /**
    * Maximum messages retained per channel to prevent unbounded memory growth
    * Oldest messages are removed when this limit is exceeded
    */
   const MAX_MESSAGES = 500;
   ```

2. In `addMessageFromWebSocket` reducer (around line 202), after pushing and sorting:
   - Add slice to limit array: `state.messagesByChannel[channel] = state.messagesByChannel[channel].slice(-MAX_MESSAGES);`
   - This keeps only the last MAX_MESSAGES (most recent)

3. In `fetchChatHistory.fulfilled` reducer (around line 366), after merging and sorting:
   - Add same slice: `state.messagesByChannel[channel] = state.messagesByChannel[channel].slice(-MAX_MESSAGES);`

4. In `uploadAttachment.fulfilled` reducer (around line 517), after pushing and sorting:
   - Add same slice: `state.messagesByChannel[channel] = state.messagesByChannel[channel].slice(-MAX_MESSAGES);`

Why slice(-MAX_MESSAGES): Negative slice keeps last N elements, so oldest messages are dropped first (FIFO queue behavior).
  </action>
  <verify>
1. Grep for MAX_MESSAGES in file: should appear 4 times (1 definition + 3 usages)
2. Grep for "slice(-MAX_MESSAGES)" in file: should appear 3 times
3. Run ESLint: `cd jam-ui && npm run lint -- --quiet src/store/features/sessionChatSlice.js`
  </verify>
  <done>
MAX_MESSAGES constant defined (500), and slice(-MAX_MESSAGES) applied in addMessageFromWebSocket, fetchChatHistory.fulfilled, and uploadAttachment.fulfilled reducers. Message arrays are bounded to 500 per channel.
  </done>
</task>

<task type="auto">
  <name>Task 2: Add clearAllMessages action and dispatch on session leave</name>
  <files>jam-ui/src/store/features/sessionChatSlice.js, jam-ui/src/components/client/JKSessionScreen.js</files>
  <action>
Add chat cleanup when leaving sessions:

**In sessionChatSlice.js:**

1. Add new reducer `clearAllMessages` in the reducers object (after `clearUploadError`, around line 305):
   ```javascript
   /**
    * Clear all messages when leaving session
    * Called from JKSessionScreen when user leaves to prevent stale data
    */
   clearAllMessages: (state) => {
     state.messagesByChannel = {};
     state.unreadCounts = {};
     state.fetchStatus = {};
     state.fetchError = {};
     state.nextCursors = {};
     // Preserve lastReadAt for persistence across sessions
     // Preserve isWindowOpen and windowPosition for UX continuity
   },
   ```

2. Export the action in the exports (around line 540):
   - Add `clearAllMessages` to the destructured exports

**In JKSessionScreen.js:**

1. Import clearAllMessages action (around line 58):
   - Add `clearAllMessages` to the imports from sessionChatSlice

2. In `handleLeaveSubmit` callback (around line 916 where clearSession is dispatched):
   - Add `dispatch(clearAllMessages());` BEFORE `dispatch(clearSession());`

3. In the cleanup useEffect (around line 944 where clearSession is dispatched):
   - Add `dispatch(clearAllMessages());` BEFORE `dispatch(clearSession());`

This ensures chat state is cleared both when:
- User explicitly leaves via feedback modal (handleLeaveSubmit)
- Component unmounts (navigation away, session end)
  </action>
  <verify>
1. Grep for clearAllMessages in sessionChatSlice.js: should appear 2 times (reducer + export)
2. Grep for clearAllMessages in JKSessionScreen.js: should appear 3 times (import + 2 dispatches)
3. Run ESLint on both files:
   - `cd jam-ui && npm run lint -- --quiet src/store/features/sessionChatSlice.js`
   - `cd jam-ui && npm run lint -- --quiet src/components/client/JKSessionScreen.js`
  </verify>
  <done>
clearAllMessages action added to sessionChatSlice.js, exported, and dispatched in both leave paths in JKSessionScreen.js (handleLeaveSubmit and unmount cleanup useEffect). Chat state is properly cleaned up when leaving sessions.
  </done>
</task>

</tasks>

<verification>
1. **Syntax verification:** Both files pass ESLint without errors
2. **Constant verification:** `grep -n "MAX_MESSAGES" sessionChatSlice.js` shows 4 occurrences
3. **Action verification:** `grep -n "clearAllMessages" sessionChatSlice.js JKSessionScreen.js` shows 5 occurrences total
4. **Build verification:** `cd jam-ui && npm run build` completes without errors (if environment supports)
</verification>

<success_criteria>
1. MAX_MESSAGES = 500 constant defined in sessionChatSlice.js
2. slice(-MAX_MESSAGES) applied after message push in 3 reducers
3. clearAllMessages action created and exported
4. clearAllMessages dispatched in both leave paths (handleLeaveSubmit, unmount cleanup)
5. Both files pass ESLint
6. CHAT-02 and CHAT-03 requirements addressed (CHAT-01 verified OK in Phase 19 audit)
</success_criteria>

<output>
After completion, create `.planning/phases/21-chat-window-fixes/21-01-SUMMARY.md`
</output>
