# Cloud Download for Export v1 — Shaped Brief > Approved: 2026-03-18 > Deliberation: 3 models (Codex scope, Gemini challenge, Claude requirements) ## Problem Cloud tracks from Chad Music are skipped during DAW export. The export pipeline (`MixExporter.copyAudioFiles`) skips tracks where `hasLocalFile == false`. Users who mix local and cloud tracks in playlists get incomplete Audition/Bitwig/REAPER sessions. ## Goal When exporting a playlist that contains cloud tracks, MixBoard downloads those tracks and includes them in the export — same result as if the files were local. ## Non-goals (v1) - Standalone "Download" button / right-click → Download (separate feature) - Offline mode / persistent cache management - Resumable downloads - Background pre-caching when tracks are added to playlists - Download queue UI (download is part of the export flow, not a separate surface) ## Acceptance Criteria - [ ] Exporting a playlist with cloud tracks downloads them and includes them in the session file - [ ] Download progress is visible (separate from export progress) — e.g., "Downloading 3 cloud tracks... (2/3)" - [ ] Pre-flight dialog before export: "N cloud tracks need to be downloaded. Continue?" - [ ] If some downloads fail, export completes with available tracks and reports which ones are missing - [ ] User can cancel during the download phase - [ ] Downloaded files are cleaned up after export (temp directory, not persistent cache) - [ ] Auth headers are included on all download requests (even though file-server is currently unprotected) ## Appetite Small: - 1 new service file (`DownloadService.swift`, ~150 lines) — mirrors UploadService pattern - Modify `MixExporter.copyAudioFiles` to become async + call DownloadService for cloud tracks - Update `ExportSheet` UI for download progress / pre-flight dialog - ~3-4 files changed total, no new dependencies, no model schema changes ## Architecture ### Critical prerequisite — VERIFIED When cloud tracks are added to playlists, `Track.fromCloud()` creates a SwiftData `@Model` with `isCloud=true`, `cloudStreamPath` set, and `filePath=""`. The export pipeline already has `Track` objects — they're just skipped by `guard track.hasLocalFile`. No ChadTrack → Track boundary problem. ### DownloadService API ```swift func download(track: Track, apiClient: ChadMusicAPIClient, to directory: URL) async throws -> URL ``` - Accepts `Track` (not `ChadTrack`) since the export pipeline works with Track @Model - Downloads to an **export-scoped temp directory** — cleaned up after export - Uses `URLSession.download(for:)` with progress - File extension derived from `cloudStreamPath` (paths like `/music/Artist/Album/track.flac`) - Falls back to `Content-Type` response header if path extension is missing - Validates downloaded file exists and has non-zero size before returning ### Export pipeline changes - `MixExporter.copyAudioFiles` becomes async - Before copying, partition tracks into local vs cloud - Download cloud tracks with bounded concurrency (`TaskGroup`, max 3-4 simultaneous) - On download failure: skip track, collect into "missing tracks" report - After export: delete temp download directory ### Pre-flight UX - Export detects cloud tracks → shows confirmation dialog with count - Two-phase progress: download phase (track-by-track), then export phase ## Technical Constraints - `cloudStreamPath` stores URL paths like `/music/Artist/Album/track.flac` — extension is reliably present - Stream URLs are direct HTTP (Nginx, verified 206 Range support) — same GET as streaming - `ChadMusicAPIClient.streamURL(for:)` and `.authHeaders` already exist - No backend changes needed — server already serves full files at stream URLs ## Dependencies & Blockers None. All prerequisites exist. ## Risks - **Large playlists**: 50 cloud tracks × 50MB = 2.5GB download before export. Bounded concurrency helps but user needs patience. Pre-flight dialog makes this visible. - **Network interruption**: Partial failure handled by "export what you can" policy. - **Disk space**: No explicit check in v1 (URLSession will fail naturally if disk is full). - **OGG Vorbis**: Files might exist in cloud library. They'll download fine but may not be usable in all DAWs. Not our problem — include as-is. ## Not Now (Deferred) - Standalone download feature (right-click → Download, toolbar button) - Persistent local cache with cleanup policy - Track model `localCachePath` field for cached state - Pre-flight disk space estimation - Download without export (for offline listening)