# JamTrack Legacy Implementation Reference

This document captures the legacy jQuery/CoffeeScript JamTrack implementation patterns discovered in the web project, serving as a reference for the React migration in jam-ui.

## 1. Legacy Dialog Patterns

### openJamTrackDialog.js

**Location:** `web/app/assets/javascripts/dialog/openJamTrackDialog.js`

**Purpose:** jQuery-based dialog for JamTrack selection with search and pagination

**Key Components:**
- **Search functionality** (lines 23-85):
  - User input via React JamTrackAutoComplete component embedded in jQuery dialog
  - Cookie persistence for search state (`jamtrack_session_search`)
  - Search types: 'user-input', autocomplete-driven
  - Shows search UI only if user has > 10 purchased JamTracks

- **Pagination** (lines 77-82):
  - 10 items per page (`perPage = 10`)
  - Uses `JK.Paginator.create()` utility
  - Reads total count from response header: `jqXHR.getResponseHeader('total-entries')`

- **Initialization pattern:**
  ```javascript
  context.JK.OpenJamTrackDialog = function(app) {
    // Dialog state variables
    var showing = false;
    var $dialog = null;
    var sampleRate = null; // Set from jamClient.GetSampleRate()

    // Template references
    var $templateOpenJamTrackRow = null;

    // Returns API with show/hide methods
    return {
      show: function() { /* ... */ },
      hide: function() { /* ... */ },
      search: function(searchType, searchData) { /* ... */ }
    };
  }
  ```

- **Data flow:**
  1. Dialog opens via `app.layout.showDialog('open-jam-track-dialog')`
  2. User selects JamTrack from list
  3. Dialog closes, fires `EVENTS.DIALOG_CLOSED` event
  4. Callback receives `{canceled: false, result: {jamTrack: selectedJamTrack}}`
  5. Calls `loadJamTrack(jamTrack)` (session.js:2716)

**REST API Integration:**
- Fetches purchased JamTracks with pagination
- Endpoint called via `getPurchasedJamTracks(offset)` helper
- Response headers include `total-entries` for pagination

## 2. Download/Sync State Machine

### download_jamtrack.js.coffee

**Location:** `web/app/assets/javascripts/download_jamtrack.js.coffee`

**Purpose:** 8-state CoffeeScript state machine managing JamTrack download, packaging, and synchronization

**State Machine (lines 59-68):**

| State | Name | Purpose | Leaf Node |
|-------|------|---------|-----------|
| `no_client` | 'no-client' | Browser mode (native client not available) | Yes |
| `synchronized` | 'synchronized' | JamTrack on disk, keys fetched, ready to play | Yes |
| `packaging` | 'packaging' | Server creating mixdown package | No |
| `downloading` | 'downloading' | Downloading package from server to disk | No |
| `keying` | 'keying' | Fetching encryption keys (max 10s timeout) | No |
| `initial` | 'initial' | Starting state, determining next action | No |
| `quiet` | 'quiet' | Hidden state before activation | No |
| `errored` | 'errored' | Error occurred, show error message | Yes |

**Key Operations:**

1. **Initialization** (lines 90-99):
   ```coffeescript
   init: () =>
     @active = true
     if !gon.isNativeClient
       this.transition(@states.no_client)  # Browser fallback
     else
       this.transition(@states.initial)    # Start state machine
   ```

2. **State transitions** (checkState method, not shown in excerpt):
   - Queries `jamClient.JamTrackGetTrackDetail(fqId)` for sync status
   - Returns: `{key_state: string, version: number}`
   - Decides next state based on client-reported status

3. **Download flow:**
   - `packaging` → Server preparing mixdown
   - `downloading` → `JamTrackDownload(jamTrackId, mixdownId, userId, progressCallback, successCallback, failCallback)`
   - `keying` → `JamTrackKeysRequest()` to fetch encryption keys
   - `synchronized` → Ready to play

4. **Progress tracking** (lines 45-52):
   - `@startTime`, `@attempts`, `@tracked` for metrics
   - `@currentPackagingStep` / `@totalSteps` for server packaging progress
   - Fires `EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED` on transitions

5. **Error handling:**
   - `@errorReason` and `@errorMessage` set before transitioning to `errored`
   - Timeout states: SIGNING_TIMEOUT, QUEUED_TIMEOUT, QUIET_TIMEOUT
   - Ajax abort methods for cleanup

**Integration with session.js:**
- Created in `loadJamTrack()` (session.js:2731): `new context.JK.DownloadJamTrack(app, jamTrack, 'large')`
- Listens for `JAMTRACK_DOWNLOADER_STATE_CHANGED` event
- When `synchronized` state reached, widget hidden and playback initiated

## 3. Store Patterns (Reflux)

### JamTrackStore.js.coffee

**Location:** `web/app/assets/javascripts/react-components/stores/JamTrackStore.js.coffee`

**Purpose:** Reflux store managing mixdown synchronization state

**Key Functionality:**

1. **pickMyPackage() logic** (lines 30-43):
   - Selects compatible mixdown package for current client
   - Criteria: `file_type == 'ogg' && encrypt_type == 'jkz' && sample_rate == @sampleRate`
   - `@sampleRate` comes from `jamClient.GetSampleRate()` (44 or 48 kHz)
   - Sets `mixdown.myPackage` property for each mixdown

2. **WebSocket subscription pattern** (lines 45-78):
   - Subscribes to mixdown packaging progress: `context.JK.SubscriptionUtils.subscribe('mixdown', mixdown_package.id)`
   - Listens for `SUBSCRIBE_NOTIFICATION` events
   - Updates `signing_state`, `packaging_steps`, `current_packaging_step` from WebSocket data
   - Unsubscribes when `signing_state == 'SIGNED'` (package ready)

3. **Mixdown state tracking:**
   - `signing_state` values: SIGNING_TIMEOUT, QUEUED_TIMEOUT, QUIET_TIMEOUT, ERROR, SIGNED
   - `packaging_steps` (total) and `current_packaging_step` for progress bar
   - Calls `@reportError(mixdown)` on timeout/error states

4. **Store lifecycle:**
   - `init()`: Registers with AppStore to get `@app` reference
   - `manageWatchedMixdowns()`: Subscribe to active, unsubscribe from complete
   - Clears all subscriptions when JamTrack changes or unmounts

### JamTrackMixdownStore (referenced but not shown)
- Handles mixdown CRUD operations
- Connected via `listenables: [JamTrackActions, JamTrackMixdownActions]`

## 4. REST API Endpoints

Based on route analysis and code references:

| Method | Endpoint | Purpose |
|--------|----------|---------|
| POST | `/api/sessions/{sessionId}/jam_tracks/{jamTrackId}/open` | Open JamTrack in session |
| POST | `/api/jamtracks/{id}/mixdowns/active` | Set active mixdown for JamTrack |
| POST | `/api/mixdowns/` | Create new mixdown |
| PUT | `/api/mixdowns/{id}` | Edit existing mixdown |
| DELETE | `/api/mixdowns/{id}` | Delete mixdown |
| POST | `/api/mixdowns/{id}/enqueue` | Enqueue mixdown for packaging |
| GET | `/api/jamtracks/autocomplete` | Autocomplete search for JamTracks |

**Response patterns:**
- Pagination: `total-entries` header on list responses
- Per-page: 10 items default
- Search: Cookie-persisted search state

## 5. Integration Points

### session.js loadJamTrack() function

**Location:** `web/app/assets/javascripts/session.js` (lines 2716-2778)

**Flow:**

1. **Recording conflict check** (lines 2693-2700):
   - Blocks JamTrack opening if actively recording
   - Shows notification: "You can't open a jam track while creating a recording"

2. **Dialog interaction** (lines 2702-2713):
   - Opens dialog: `app.layout.showDialog('open-jam-track-dialog')`
   - Listens for `EVENTS.DIALOG_CLOSED` event
   - Checks `!data.canceled && data.result.jamTrack`
   - Calls `loadJamTrack(data.result.jamTrack)` on selection

3. **DownloadJamTrack widget** (lines 2723-2777):
   - Cleans up previous widget if exists (lines 2723-2729)
   - Creates: `new context.JK.DownloadJamTrack(app, jamTrack, 'large')`
   - Listens for `JAMTRACK_DOWNLOADER_STATE_CHANGED` event
   - Appends widget to `$otherAudioContainer` in session UI

4. **Playback initiation on synchronized** (lines 2736-2770):
   ```javascript
   if(data.state == downloadJamTrack.states.synchronized) {
     // Hide download widget
     downloadJamTrack.root.remove();
     downloadJamTrack.destroy();

     // Stop any existing playback
     context.jamClient.JamTrackStopPlay();

     // Build fqId: {jamTrackId}-{sampleRate}
     var sampleRate = context.jamClient.GetSampleRate(); // 44 or 48
     var sampleRateForFilename = sampleRate == 48 ? '48' : '44';
     var fqId = jamTrack.id + '-' + sampleRateForFilename;

     // Load JMEP (Jam Enhancement Package) if present
     if(jamTrack.jmep) {
       context.jamClient.JamTrackLoadJmep(fqId, jamTrack.jmep);
     }

     // "JamTrackPlay" means "load" in this API
     var result = context.jamClient.JamTrackPlay(fqId);

     if(result) {
       playJamTrack(jamTrack.id); // Start actual playback
     } else {
       app.notify({ title: "JamTrack Can Not Open", ... });
     }
   }
   ```

5. **JMEP (Jam Enhancement Package):**
   - Tempo/pitch modifications
   - Loaded via `JamTrackLoadJmep(fqId, jmepData)` before playback
   - Optional feature, checked with `if(jamTrack.jmep)`

6. **fqId format:**
   - Fully-qualified ID: `{jamTrackId}-{sampleRate}`
   - Sample rate: '44' or '48' based on client audio config
   - Used for all jamClient JamTrack operations

**Event pattern:**
- `EVENTS.DIALOG_CLOSED`: Dialog selection callback
- `EVENTS.JAMTRACK_DOWNLOADER_STATE_CHANGED`: State machine transitions

---

## Summary for React Migration

**Key patterns to preserve:**
1. **fqId format:** Always use `{jamTrackId}-{sampleRate}` for jamClient calls
2. **Mixdown selection:** pickMyPackage() logic filters by ogg/jkz/sample_rate
3. **State machine:** Track download/sync states for UX feedback
4. **WebSocket updates:** Subscribe to mixdown packaging progress
5. **JMEP support:** Load tempo/pitch modifications before playback
6. **Recording conflicts:** Block JamTrack opening during active recording

**Differences from Backing Track:**
- **No native file dialog:** JamTrack uses jQuery dialog with purchased track list
- **Download process:** Multi-state sync (packaging → downloading → keying → synchronized)
- **Mixdown selection:** User chooses stem configuration (full track vs. specific mixes)
- **Encryption keys:** Requires separate `JamTrackKeysRequest()` call after download
- **Real-time packaging:** WebSocket updates during server-side mixdown creation

**Complexity factors:**
- 8-state synchronization machine vs. simple file open
- Server-side packaging with progress tracking
- Mixdown package selection based on client capabilities
- Encryption key management
- JMEP (tempo/pitch) modification support
