Date: 2026-03-18 12:17 Model: Claude Opus 4.6 Type: general Artifact: ## Cloud Download for Export — Draft Proposal ### Problem Cloud tracks cannot be included in DAW ex
Yes, but the problem statement undersells the complexity at the data layer. The proposal correctly identifies the user-facing gap (cloud tracks skipped in export), but the hardest question isn't the download mechanics — it's the model boundary between ChadTrack (transient Codable struct) and Track (SwiftData @Model).
The proposal's own "Open questions" section flags this, which is good. But this isn't an open question — it's the critical prerequisite that determines the entire shape of the solution. You cannot defer it. Let me explain why.
The user-provided context says:
Cloud tracks are currently transient (ChadTrack is Codable struct, not SwiftData) But Track @Model has cloud fields: isCloud, cloudStreamPath, cloudTrackId
This creates two possible states of the world:
When a user adds a cloud track to a playlist, a Track @Model is created with isCloud=true. If this is true, the export pipeline already has a Track to work with, and you just need to fill in the file. The proposal's architecture is roughly correct.
Cloud tracks in playlists are held as ChadTrack references that never become Track @Models. If this is true, the export pipeline will never encounter them — PlaylistEntry → Track won't yield them at all, and the guard track.hasLocalFile else { continue } isn't even the relevant skip point. They're invisible to export, not just skipped.
The proposal assumes scenario (1) without confirming it. If scenario (2) is the reality, the fix isn't "add a DownloadService" — it's "materialize ChadTrack into Track when adding to a playlist" plus download. That's a fundamentally different scope.
Action required: Before any implementation work, trace the actual code path for "user adds cloud track to playlist." Does a Track @Model get created? If not, the proposal needs a significant addition.
Both prior reviews flagged this. The proposal still doesn't answer it. This isn't optional — it's a UX contract:
Recommendation: Option B. DAW users are accustomed to "missing media" dialogs. Export should succeed with a manifest of missing tracks.
The proposal has download happening inside the export flow. Consider what this looks like:
This is confusing. The user didn't ask to download — they asked to export. Two things are happening under one progress indicator.
Better UX options:
The proposal asks whether to include this. Don't, for v1. It introduces UI surface, state management (what does a "downloaded" cloud track look like in the UI?), cache invalidation questions, and user expectations about persistence. Keep it export-scoped.
~/Library/Caches/MixBoard/CloudTracks/{trackId}.{ext}
Problem 1: File extension. Where does {ext} come from? The context says cloudStreamPath exists — if it's a URL path like /tracks/123/audio.wav, you can extract the extension. If it's /api/stream/123, you can't. The proposal doesn't address this. Prior reviews flagged it. You need to handle this explicitly:
cloudStreamPath path extension firstContent-Type / Content-Disposition from the HTTP responseProblem 2: Cache invalidation. ~/Library/Caches/ can be purged by macOS at any time. This is fine if you treat it as a true cache (re-download if missing). But if you store the path in Track.localCachePath (as proposed), you now have a stale reference. Don't persist the cache path on the model. Instead, have DownloadService check for the file at the deterministic path at call time — the path is derivable from trackId + extension, so you don't need to store it.
download(track: ChadTrack, apiClient: ChadMusicAPIClient) async throws -> URL
[CHECK THIS]: The parameter type is ChadTrack, but the export pipeline works with Track (@Model). If scenario (1) above is true, you'll need to either:
Track and extract cloud fields from it, orChadTrack-equivalent data from Track before callingThis is a direct consequence of the unresolved model boundary. Don't design the API until you know which type you're working with at the call site.
The proposal flags this as an open question. The answer is straightforward: MixExporter.copyAudioFiles needs to become async. If it's currently called from a Task or background queue, this is a small change. If it's called synchronously from a SwiftUI action, you need a Task { } wrapper. Check the current call site.
Both prior reviews flagged this. Add maxConcurrentDownloads (e.g., 3-4). Use TaskGroup with a semaphore pattern or a custom AsyncStream-based throttle. Unbounded concurrent downloads to the same server will either get rate-limited, OOM on large files, or both.
Auth headers on download requests. The context confirms ChadMusicAPIClient has authHeaders. The DownloadService must apply them to the URLRequest. The proposal doesn't mention this. Even if the server currently serves unauthenticated streams, always send auth — server policy can change.
Disk space check. Before downloading N tracks for export, estimate total size (if the API provides track file sizes) and compare to available disk space. A 2GB playlist download into a nearly-full disk is a bad time.
Cleanup policy. When are cached files deleted? Options: after export completes, after N days, never (let macOS manage ~/Library/Caches/). The proposal is silent on this. For MVP, "let macOS manage it" is fine, but document it.
Cancellation. User should be able to cancel the download/export. With async/await and Task, this means checking Task.isCancelled and supporting task.cancel(). The proposal doesn't mention it.
MixExporter.copyAudioFiles — referenced in user context ✓ChadMusicAPIClient.streamURL(for:) — referenced in user context ✓ChadMusicAPIClient.authHeaders — referenced in user context ✓~/Library/Caches/MixBoard/CloudTracks/ — proposed path, reasonable for macOS convention ✓ChadTrack as Codable struct — referenced in user context ✓Track as SwiftData @Model — referenced in user context ✓UploadService as existing pattern — referenced in user context ✓No hallucinations detected. All entity names trace back to user-provided context.
The proposal has the right instinct but is premature. The model boundary question (ChadTrack vs Track in playlists) must be resolved first — it determines whether this is a 1-service addition or a 2-layer refactor. The download mechanics are relatively straightforward once you know what type you're downloading for.