---
phase: 25-memory-leak-audit
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - jam-ui/src/hooks/useRecordingHelpers.js
  - jam-ui/src/components/client/JKSessionRecordingModal.js
  - .planning/phases/25-memory-leak-audit/25-UAT.md
autonomous: false

must_haves:
  truths:
    - "Recording modal can remain open 15+ minutes without memory growth"
    - "No 'state update on unmounted component' console warnings from recording code"
    - "Callbacks do not accumulate in window.JK when modal is opened/closed repeatedly"
  artifacts:
    - path: "jam-ui/src/hooks/useRecordingHelpers.js"
      provides: "Timer and callback cleanup on unmount"
      contains: "return () =>"
    - path: "jam-ui/src/components/client/JKSessionRecordingModal.js"
      provides: "Async operation cleanup with ignore flag"
      contains: "let ignore = false"
    - path: ".planning/phases/25-memory-leak-audit/25-UAT.md"
      provides: "Manual memory verification checklist"
      min_lines: 100
  key_links:
    - from: "jam-ui/src/hooks/useRecordingHelpers.js"
      to: "window.JK.*"
      via: "useEffect cleanup function"
      pattern: "delete window\\.JK"
    - from: "jam-ui/src/hooks/useRecordingHelpers.js"
      to: "waitingOnStopTimer"
      via: "useEffect cleanup function"
      pattern: "clearTimeout\\(waitingOnStopTimer"
    - from: "jam-ui/src/components/client/JKSessionRecordingModal.js"
      to: "setAudioStoreType"
      via: "ignore flag guard"
      pattern: "if \\(!ignore\\)"
---

<objective>
Fix memory leaks in recording-related components and create UAT checklist for verification.

Purpose: Ensure recording functionality is stable for extended use (15+ minutes) without memory growth, matching the stability achieved in v1.4 for the session screen.

Output:
- Updated useRecordingHelpers.js with proper cleanup
- Updated JKSessionRecordingModal.js with async cleanup
- UAT checklist for manual memory verification
</objective>

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

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/25-memory-leak-audit/25-RESEARCH.md
@.planning/phases/22-session-screen-fixes/22-01-SUMMARY.md
@.planning/phases/23-verification/23-UAT.md
@jam-ui/src/hooks/useRecordingHelpers.js
@jam-ui/src/components/client/JKSessionRecordingModal.js
</context>

<tasks>

<task type="auto">
  <name>Task 1: Fix timer and callback cleanup in useRecordingHelpers</name>
  <files>jam-ui/src/hooks/useRecordingHelpers.js</files>
  <action>
Add cleanup for the `waitingOnStopTimer` and the global `window.JK.*` callbacks.

**Timer cleanup (new useEffect):**
Add a new useEffect with empty dependency array that cleans up `waitingOnStopTimer` on unmount:

```javascript
// Cleanup timer on unmount
useEffect(() => {
  return () => {
    if (waitingOnStopTimer.current) {
      clearTimeout(waitingOnStopTimer.current);
      waitingOnStopTimer.current = null;
    }
  };
}, []);
```

Place this after the `waitingOnStopTimer` useRef declaration (around line 32).

**Global callback cleanup (modify existing useEffect around line 459):**
The tricky part: Multiple hook instances (JKSessionScreen + JKSessionRecordingModal) share window.JK callbacks. Only delete if we still own the callback.

Add a `callbacksRef` to track what we registered, and use conditional cleanup:

```javascript
// Track our callbacks for conditional cleanup
const callbacksRef = useRef({});

// Existing useEffect - modify to add cleanup:
useEffect(() => {
  if (window) {
    window.JK = window.JK || {};

    // Store our callback references
    callbacksRef.current = {
      HandleRecordingStartResult: handleRecordingStartResult,
      HandleRecordingStopResult: handleRecordingStopResult,
      HandleRecordingStopped: handleRecordingStopped,
      HandleRecordingStarted: handleRecordingStarted,
      HandleRecordingAborted: handleRecordingAborted,
    };

    // Register callbacks
    Object.assign(window.JK, callbacksRef.current);
  }

  return () => {
    // Only delete if we still own the callback (another instance may have registered newer ones)
    if (window.JK && callbacksRef.current) {
      Object.keys(callbacksRef.current).forEach(key => {
        if (window.JK[key] === callbacksRef.current[key]) {
          delete window.JK[key];
        }
      });
    }
  };
}, [handleRecordingStartResult, handleRecordingStopResult, handleRecordingStopped, handleRecordingStarted, handleRecordingAborted]);
```

**Important:** Keep the existing comment explaining why cleanup is conditional (multiple hook instances sharing callbacks).
  </action>
  <verify>
1. `node --check jam-ui/src/hooks/useRecordingHelpers.js` passes (no syntax errors)
2. Grep for "return () =>" shows cleanup functions exist
3. Grep for "clearTimeout(waitingOnStopTimer" shows timer cleanup
4. Grep for "delete window.JK" shows callback cleanup
  </verify>
  <done>
- Timer cleanup useEffect added with clearTimeout on unmount
- Callback useEffect has cleanup function with conditional deletion
- No syntax errors
  </done>
</task>

<task type="auto">
  <name>Task 2: Fix async cleanup in JKSessionRecordingModal</name>
  <files>jam-ui/src/components/client/JKSessionRecordingModal.js</files>
  <action>
Fix both useEffect hooks that have async operations without cleanup.

**useEffect for audio preference (lines 54-71):**
Add ignore flag pattern to prevent state updates after unmount:

```javascript
useEffect(() => {
  let ignore = false;

  const fetchAudioStoreType = async () => {
    try {
      if (jamClient) {
        const pref = await jamClient.GetAudioRecordingPreference();
        if (!ignore) {
          if (AUDIO_STORE_TYPE_MIX_AND_STEMS['backendValues'].includes(pref)) {
            setAudioStoreType(AUDIO_STORE_TYPE_MIX_AND_STEMS['key']);
          } else if (AUDIO_STORE_TYPE_MIX_ONLY['backendValues'].includes(pref)) {
            setAudioStoreType(AUDIO_STORE_TYPE_MIX_ONLY['key']);
          }
        }
      }
    } catch (error) {
      if (!ignore) {
        console.error('Error fetching audio store type preference:', error);
      }
    }
  };

  fetchAudioStoreType();

  return () => {
    ignore = true;
  };
}, [jamClient]);
```

**useEffect for video sources (lines 74-78):**
Add ignore flag to `loadAvailableVideoSources` call:

```javascript
useEffect(() => {
  let ignore = false;

  const loadSources = async () => {
    if (isOpen && jamClient) {
      try {
        if (jamClient.getOpenVideoSources) {
          const openSources = await jamClient.getOpenVideoSources();
          if (!ignore) {
            console.log('Available video sources:', openSources);

            const sources = [];
            if (openSources?.webcam1) sources.push(1);
            if (openSources?.webcam2) sources.push(3);
            if (openSources?.screen_capture) sources.push(4);
            if (openSources?.session_video) sources.push(2);

            setAvailableVideoSources(sources);
          }
        }
      } catch (error) {
        if (!ignore) {
          console.error('Error loading video sources:', error);
        }
      }
    }
  };

  loadSources();

  return () => {
    ignore = true;
  };
}, [isOpen, jamClient]);
```

**Note:** The inline `loadAvailableVideoSources` function (lines 80-98) can be removed since the logic is now inside the useEffect.
  </action>
  <verify>
1. `node --check jam-ui/src/components/client/JKSessionRecordingModal.js` passes
2. Grep for "let ignore = false" shows ignore flags added
3. Grep for "if (!ignore)" shows state updates are guarded
4. Both useEffect hooks have "return () =>" cleanup
  </verify>
  <done>
- Audio preference useEffect has ignore flag and cleanup
- Video sources useEffect has ignore flag and cleanup
- No syntax errors
  </done>
</task>

<task type="auto">
  <name>Task 3: Create UAT checklist for memory verification</name>
  <files>.planning/phases/25-memory-leak-audit/25-UAT.md</files>
  <action>
Create a comprehensive UAT checklist following the Phase 23 pattern. Include:

**Frontmatter:**
```yaml
---
phase: 25-memory-leak-audit
type: uat
status: pending
verified-by:
verified-date:
---
```

**Sections to include:**

1. **Prerequisites** - Chrome DevTools, jam-ui running, native client connected

2. **Quick Verification (2-3 minutes)** - Open/close recording modal 5 times, check for console warnings

3. **Section 1: Timer Cleanup Verification**
   - Purpose: Verify waitingOnStopTimer is cleared on unmount
   - Test steps: Start recording, stop recording, leave session
   - Expected: No timer-related errors

4. **Section 2: Callback Cleanup Verification**
   - Purpose: Verify window.JK callbacks don't accumulate
   - Test steps: Open/close recording modal 20 times, check window.JK
   - Expected: Same callback names, no duplicates

5. **Section 3: Async Operation Cleanup Verification**
   - Purpose: Verify no "state update on unmounted component" warnings
   - Test steps: Open recording modal, quickly close before load completes
   - Expected: No console warnings

6. **Section 4: 15-Minute Idle Test**
   - Purpose: Verify modal can stay open without memory growth
   - Test steps: Open modal, leave open 15+ minutes, measure heap
   - Expected: < 50% growth

7. **Section 5: 15-Minute Active Recording Test**
   - Purpose: Verify active recording doesn't leak memory
   - Test steps: Start recording, continue 15+ minutes, measure heap
   - Expected: < 50% growth, no freezes

8. **Verification Results Summary** - Table with all sections

9. **Troubleshooting** - Common issues and how to investigate

Follow exact format from Phase 23 UAT.md including:
- Checkbox format for test steps
- Expected/Actual result tables
- Notes sections
- Footer with template version
  </action>
  <verify>
1. File exists at .planning/phases/25-memory-leak-audit/25-UAT.md
2. File has > 100 lines
3. Contains all 5 main test sections
4. Contains frontmatter with status: pending
5. Contains troubleshooting section
  </verify>
  <done>
- 25-UAT.md created with comprehensive verification checklist
- All test scenarios from CONTEXT.md covered (idle 15min, active recording 15min)
- Format matches Phase 23 UAT.md pattern
  </done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 4: Verify memory stability with UAT checklist</name>
  <what-built>
Memory leak fixes in recording-related components:
1. Timer cleanup on unmount in useRecordingHelpers.js
2. Global callback conditional cleanup in useRecordingHelpers.js
3. Async operation ignore flags in JKSessionRecordingModal.js
  </what-built>
  <how-to-verify>
**Quick Verification (2 minutes):**
1. Open Chrome DevTools > Console
2. Navigate to jam-ui session screen
3. Open recording modal
4. Close recording modal
5. Repeat open/close 5 times
6. Check console for "state update on unmounted component" warnings

**Expected:** No warnings about state updates on unmounted components

**15-Minute Stability Test:**
1. Open Chrome DevTools > Performance Monitor
2. Note initial JS Heap size: ______ MB
3. Open recording modal and leave it open
4. Wait 15+ minutes
5. Note final JS Heap size: ______ MB
6. Expected: Growth < 50%, no unbounded increase

**Repeated Open/Close Test:**
1. Open recording modal
2. Close recording modal
3. Repeat 20 times
4. Check window.JK in console (type `Object.keys(window.JK)`)
5. Expected: Same callback names, not duplicates/accumulated

**If all checks pass:** Type "approved"
**If issues found:** Describe the issue and observed behavior
  </how-to-verify>
  <resume-signal>Type "approved" or describe issues found</resume-signal>
</task>

</tasks>

<verification>
**Code Verification:**
- `node --check jam-ui/src/hooks/useRecordingHelpers.js` - No syntax errors
- `node --check jam-ui/src/components/client/JKSessionRecordingModal.js` - No syntax errors
- Existing tests pass: `cd jam-ui && npm test -- --testPathPattern="recording" --passWithNoTests`

**Pattern Verification:**
- useRecordingHelpers.js contains cleanup useEffect for timer
- useRecordingHelpers.js contains cleanup useEffect for callbacks
- JKSessionRecordingModal.js contains ignore flag pattern in both useEffect hooks

**UAT File Verification:**
- 25-UAT.md exists with 100+ lines
- Contains all required test sections

**Manual Verification (UAT):**
- Human verifies no console warnings during modal open/close
- Human verifies memory stability during 15+ minute test
- Human verifies callbacks don't accumulate
</verification>

<success_criteria>
1. MEM-01: All useEffect hooks in recording code have proper cleanup functions
2. MEM-02: Timer (waitingOnStopTimer) is cleared on unmount
3. MEM-03: Recording modal can remain open 15+ minutes without memory growth
4. No "state update on unmounted component" console warnings
5. Callbacks in window.JK don't accumulate across modal open/close cycles
6. 25-UAT.md created with comprehensive verification checklist
</success_criteria>

<output>
After completion, create `.planning/phases/25-memory-leak-audit/25-01-SUMMARY.md`
</output>
