# PM Decision Log --- ### 2026-03-18 — Cloud Upload v1 **Request**: Add ability to upload local audio files from MixBoard to chad-music cloud library **Decision**: Approved — Option C (immediate rescan + background beets cron) **Appetite**: Small-Medium (1 server endpoint + nginx config + cron, 1 client service + UI) **Scope**: Single file upload via raw PUT, synchronous rescan, background beets enrichment. No dedup, no multi-file, no drag-and-drop. **Handed to**: @builder (pending) **Key tradeoffs**: Dropped dedup (beets handles implicitly), dropped multi-file, chose raw PUT over multipart (Woo memory risk), chose background beets over synchronous (avoids subprocess blocking + sudo on upload path). Enikesha needs to review/merge server PR. **Deliberation**: 3 models — Codex (scope), Gemini (challenge), Claude (requirements). All agreed on cutting dedup and multi-file. Gemini proposed skipping beets entirely; compromise was Option C. --- ### 2026-03-18 — Cloud Download for Export v1 **Request**: Download cloud tracks so they can be included in DAW export sessions (Audition, Bitwig, REAPER etc.) **Decision**: Approved **Appetite**: Small (~3-4 files changed, 1 new service, no new dependencies, no model schema changes) **Scope**: Export-scoped download only. DownloadService downloads cloud tracks to temp dir during export, then cleans up. Pre-flight confirmation dialog, partial-failure policy (export what you can, report missing). No standalone download UI, no persistent cache, no offline mode. **Handed to**: @builder **Key tradeoffs**: Chose export-scoped temp dir over persistent ~/Library/Caches/ cache (Codex + Gemini agreed). Chose partial-failure over all-or-nothing export (Claude recommended). Deferred standalone download feature to separate brief. No Track model schema changes — cache path is deterministic, no need to persist it. **Deliberation**: 3 models — Codex (scope), Gemini (challenge), Claude (requirements). Claude flagged critical prerequisite: ChadTrack→Track materialization — verified in code that Track.fromCloud() creates SwiftData @Model when adding to playlist. All three agreed on bounded concurrency (3-4), auth headers always, and export-scoped cleanup. --- ### 2026-03-18 — Offline Download v1 **Request**: Download cloud tracks for offline use — at song, album, and playlist level (like Spotify/Apple Music) **Decision**: Approved **Appetite**: Medium (~5-6 files changed, new model fields, playback routing change, UI components) **Scope**: Persistent downloads to Application Support. Track/album/playlist level. New model fields (localCachePath + downloadState enum). Playback routing changes from isCloud to hasPlayableLocalFile. AudioEngine for downloaded files with AVPlayer fallback for unsupported formats. Inline download indicators, album/playlist header buttons, context menu download/remove actions. **Handed to**: @builder **Key tradeoffs**: Chose Option C (separate localCachePath) over changing hasLocalFile predicate (protects export pipeline + all existing consumers). Application Support over Caches (user expectation of persistence). Included album+playlist batch in v1 (low incremental cost — DownloadService.downloadBatch exists). Deferred auto-download toggles, resumable downloads, OGG transcoding, storage management UI. AVPlayer fallback for OGG instead of wiring stb_vorbis into AudioEngine. **Deliberation**: 3 models + Designer. All 3 models converged on Option C, Application Support, explicit downloadState enum. Designer chose Direction A (inline icon buttons). Codex wanted track-only v1 — overruled since batch infra exists. --- ### 2026-03-18 — Track State Badge Unification (fast-lane) **Request**: Track download state not visually clear — need distinct indicators for all 5 track states (local, cloud, downloading, downloaded, uploading) **Decision**: Approved (fast-lane, designer consulted) **Appetite**: Small (icon + color changes in 3 existing files) **Scope**: Changed downloaded icon from `checkmark.circle.fill` (.secondary) to `arrow.down.circle.fill` (.green). Cloud badge uses accent blue. Added orange `arrow.up.circle.fill` for uploading tracks (matched against UploadService state, no model changes). Local = no badge. **Handed to**: @builder (complete) **Key tradeoffs**: Kept download action in existing inline position (not artwork overlay). Upload badge is display-only check against UploadService filename — no new model field. --- ### 2026-03-18 — Album Covers for Cloud Tracks (backlog) **Request**: Fetch and display album artwork for cloud tracks — either from Chad Music server (cover field exists in API) or external source (MusicBrainz, etc.) **Decision**: Backlog — not shaped yet **Priority**: Low — cosmetic enhancement, cloud browsing works without it --- ### 2026-03-18 — Live Radio / Synchronized Playback (backlog) **Request**: Live radio / shared playlist mode — one person DJs (switches tracks), playback is synchronized for all listeners in real time. "Spinning mixes for everyone." (from Enikesha) **Decision**: Backlog — not shaped yet **Priority**: Low — cool feature but requires significant backend work (WebSocket/SSE sync, multi-client streaming). Phase 4+ territory.