---
phase: 13-file-upload-infrastructure
plan: 02
type: tdd
wave: 1
depends_on: []
files_modified:
  - jam-ui/src/store/features/sessionChatSlice.js
  - jam-ui/src/store/features/__tests__/sessionChatSlice.test.js
  - jam-ui/src/helpers/rest.js
autonomous: true

must_haves:
  truths:
    - "User clicking Attach on valid file sees 'Uploading...' state reflected in UI immediately"
    - "User sees upload complete and state returns to normal when backend responds successfully"
    - "User sees clear error message when upload fails (network error, server error)"
    - "User uploading file >10 MB rejected by backend sees 'File too large' error message"
    - "User can dismiss upload error and try again with a different file"
  artifacts:
    - path: "jam-ui/src/store/features/sessionChatSlice.js"
      provides: "Upload state management and uploadAttachment thunk"
      exports: ["uploadAttachment", "setUploadStatus", "clearUploadError", "selectUploadStatus", "selectUploadError", "selectIsUploading"]
    - path: "jam-ui/src/helpers/rest.js"
      provides: "REST API helpers for file upload"
      exports: ["uploadMusicNotation", "getMusicNotationUrl"]
    - path: "jam-ui/src/store/features/__tests__/sessionChatSlice.test.js"
      provides: "Unit tests for upload reducers and thunk"
  key_links:
    - from: "jam-ui/src/store/features/sessionChatSlice.js"
      to: "jam-ui/src/helpers/rest.js"
      via: "uploadAttachment calls uploadMusicNotation"
      pattern: "uploadMusicNotation\\(formData\\)"
    - from: "uploadMusicNotation"
      to: "/api/music_notations"
      via: "native fetch with FormData (NOT apiFetch)"
      pattern: "fetch.*music_notations"
---

<objective>
Extend sessionChatSlice with upload state management and create REST helpers for file upload

Purpose: Track upload progress in Redux and provide the API layer for uploading files to S3 via backend. Uses native fetch (NOT apiFetch) because FormData requires browser-generated Content-Type with multipart boundary.

Output: Extended sessionChatSlice with upload state/thunk and rest.js helpers for upload/download
</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/12-attachment-research-&-backend-validation/docs/REACT_INTEGRATION_DESIGN.md
@jam-ui/src/store/features/sessionChatSlice.js
@jam-ui/src/helpers/rest.js

# Critical pattern from research:
# - Use native fetch() for FormData, NOT apiFetch
# - Do NOT set Content-Type header - browser must set it with multipart boundary
# - FormData fields: files[] (File), session_id (string), attachment_type ('notation'|'audio')
</context>

<feature>
  <name>Redux Upload State and REST Helpers</name>
  <files>
    jam-ui/src/store/features/sessionChatSlice.js
    jam-ui/src/store/features/__tests__/sessionChatSlice.test.js
    jam-ui/src/helpers/rest.js
  </files>
  <behavior>
    ## initialState.uploadState
    - status: 'idle' | 'uploading' | 'error'
    - progress: 0-100 (future enhancement, start at 0)
    - error: string | null
    - fileName: string | null

    ## Reducers
    - setUploadStatus({ status, progress?, error?, fileName? }) -> updates uploadState fields
    - clearUploadError() -> resets error to null, status to 'idle'

    ## uploadAttachment Thunk
    - Input: { file: File, sessionId: string, clientId?: string }
    - Builds FormData with files[], session_id, attachment_type
    - Calls uploadMusicNotation(formData)
    - On success: returns response data
    - On 413: rejects with "File too large - maximum 10 MB"
    - On 422: rejects with "Invalid file type or format"
    - On other error: rejects with error.message or "Upload failed"

    ## Selectors
    - selectUploadStatus(state) -> 'idle' | 'uploading' | 'error'
    - selectUploadError(state) -> string | null
    - selectUploadProgress(state) -> number
    - selectUploadFileName(state) -> string | null
    - selectIsUploading(state) -> boolean

    ## uploadMusicNotation(formData) in rest.js
    - Uses native fetch (NOT apiFetch)
    - POST to /api/music_notations
    - credentials: 'include' (session cookie)
    - NO Content-Type header (browser sets it)
    - Returns Promise<response.json()>
    - Throws error with status property on failure

    ## getMusicNotationUrl(id) in rest.js
    - Uses apiFetch (JSON response)
    - GET /music_notations/:id
    - Returns Promise<{ url: string }>
  </behavior>
  <implementation>
    Follow REACT_INTEGRATION_DESIGN.md Sections 3 and 4.

    TDD Cycle:
    1. RED: Write test for uploadState initial state
    2. GREEN: Add uploadState to initialState
    3. RED: Write test for setUploadStatus reducer
    4. GREEN: Implement setUploadStatus
    5. RED: Write test for clearUploadError reducer
    6. GREEN: Implement clearUploadError
    7. RED: Write test for uploadAttachment.pending
    8. GREEN: Add extraReducer for pending
    9. (continue for fulfilled/rejected)
    10. Write REST helper tests (mock fetch)
    11. Implement REST helpers

    CRITICAL: uploadMusicNotation must use native fetch, not apiFetch.
    Reason: apiFetch sets Content-Type: application/json, which breaks multipart FormData.
    Browser must auto-generate Content-Type with boundary parameter.
  </implementation>
</feature>

<verification>
```bash
# Run sessionChatSlice tests
cd /Users/nuwan/Code/jam-cloud/jam-ui && npm run test:unit -- sessionChatSlice

# Expected: All existing tests pass + 15+ new upload tests
```
</verification>

<success_criteria>
1. uploadState added to sessionChatSlice initial state
2. setUploadStatus and clearUploadError reducers working
3. uploadAttachment async thunk implemented with extraReducers
4. 5 selectors exported for upload state
5. uploadMusicNotation uses native fetch (verify no Content-Type header)
6. getMusicNotationUrl uses apiFetch
7. All existing sessionChatSlice tests still pass
8. New tests for upload functionality pass
</success_criteria>

<output>
After completion, create `.planning/phases/13-file-upload-infrastructure/13-02-SUMMARY.md`
</output>
