From b7208d945c10dcec74bea964b8626883c750d07d Mon Sep 17 00:00:00 2001 From: Nuwan Date: Wed, 14 Jan 2026 21:19:21 +0530 Subject: [PATCH] docs(04-01): create legacy JamTrack implementation documentation - Documented jQuery dialog patterns with search/pagination - Captured 8-state download/sync state machine (CoffeeScript) - Explained Reflux store patterns and WebSocket subscriptions - Mapped REST API endpoints and integration points - Documented fqId format, JMEP support, mixdown selection logic --- .planning/codebase/JAMTRACK_LEGACY.md | 264 ++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 .planning/codebase/JAMTRACK_LEGACY.md diff --git a/.planning/codebase/JAMTRACK_LEGACY.md b/.planning/codebase/JAMTRACK_LEGACY.md new file mode 100644 index 000000000..ed0d4c8e7 --- /dev/null +++ b/.planning/codebase/JAMTRACK_LEGACY.md @@ -0,0 +1,264 @@ +# 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