# MixBoard — Copilot Session Log This file tracks changes made during Copilot-assisted development sessions. Future sessions: read this first for recent context. --- ## 2026-03-03 — Fix: SwiftData migration failure preventing playlist creation **Problem**: The app's SwiftData persistent store failed to load on launch. The entire `ModelContext` was broken — no playlists could be created, no data could be saved. The error: ``` CoreData error: entity=Playlist, attribute=groupTemplate, reason=Validation error missing attribute values on mandatory destination attribute ``` **Root cause**: `Playlist.groupTemplate` (and other `@Model` properties across all models) were declared without default values at the declaration site. SwiftData's lightweight migration couldn't fill in missing attributes for existing records in `~/Library/Application Support/default.store`. **Fix** (4 files): - `Sources/Models/Playlist.swift` — Added defaults to all stored properties on `Playlist` and `PlaylistEntry` - `Sources/Models/Track.swift` — Added defaults to all stored properties - `Sources/Models/CuePoint.swift` — Added defaults (note: enum defaults must be fully qualified, e.g. `CuePointType.marker` not `.marker`, due to `@Model` macro) - `Sources/Models/PlaylistFolder.swift` — Added defaults to all stored properties **Bonus fix**: `NewPlaylistSheet` in `Sources/Views/ContentView.swift` now takes a `@Binding var selectedPlaylist: Playlist?` so newly created playlists auto-select in the sidebar. **Tests**: 104 passed, 0 failures. **Key lesson**: When adding new stored properties to `@Model` classes, always provide a declaration-site default value. SwiftData lightweight migration requires this to populate existing rows. --- ## 2026-03-03 — Feature: Backspace to delete selected tracks from playlist **Change**: Added `.onKeyPress(.delete)` handler to `PlaylistEntryList` in `Sources/Views/PlaylistView.swift`. Pressing Backspace/Delete now removes all selected entries from the current playlist, matching the existing "Remove" button and context menu behavior. **Files changed**: - `Sources/Views/PlaylistView.swift` — Added `removeSelectedEntries()` method and `.onDeleteCommand` on the List **Tests**: 104 passed, 0 failures. --- ## 2026-03-03 — Fix: Folder import now sorts by full path (like iOS) **Problem**: When adding a folder to a playlist, `expandDirectories()` sorted files by **filename only** (`lastPathComponent`), which interleaved files from different subfolders alphabetically. For example, tracks from `Disc 1/01.mp3` and `Disc 2/01.mp3` would be shuffled together. **Fix**: Changed sort in `expandDirectories()` in `Sources/Views/PlaylistView.swift` to sort by **full path** with numeric + case-insensitive comparison (matching the iOS `FolderBrowserView.allTracksRecursive` behavior). Now `Disc 1/01.mp3` through `Disc 1/12.mp3` come before `Disc 2/01.mp3`. **Tests**: 104 passed, 0 failures. --- ## 2026-03-03 — Feature: {Date} tag uses album release year from metadata (matching iOS) **Problem**: The `{Year}` grouping tag used `track.dateAdded` (when the file was imported), not the album's release year. iOS uses `track.year` from metadata. **Changes** (6 files): - `Sources/Models/Track.swift` — Added `var year: Int?` (release year from metadata, with default for migration) - `Sources/Services/MetadataService.swift` — 3-tier year extraction from metadata (matching iOS): commonKeyCreationDate → format-specific tags (TDRC, TYER, ©day, DATE) → fallback regex scan - `Sources/Services/LibraryManager.swift` — Sets `track.year = metadata.year` after import - `Sources/Models/GroupTemplateResolver.swift` — Replaced `{Year}` with `{Date}`, resolves to `track.year`; preset changed from "Album (Year)" to "Album (Date)". Legacy `{Year}` still resolves for backward compat. - `Sources/Models/FileNameTemplate.swift` — `{year}` now resolves to `track.year` (release year) instead of `track.dateAdded` - `Tests/Unit/ModelTests.swift` — Replaced `testAlbumYearTemplate` with `testAlbumDateTemplate` + `testAlbumDateTemplateNoYear` **Tests**: 105 passed, 0 failures (1 new test). --- ## 2026-03-03 — Feature: Rescan Metadata (per-track and bulk) **Change**: Added ability to re-read metadata from audio files for existing tracks (to populate the new `year` field on tracks imported before the Date feature). **Files changed**: - `Sources/Services/LibraryManager.swift` — Added `rescanMetadata(_:)` (single track) and `rescanAllMetadata(tracks:)` (bulk) - `Sources/Views/PlaylistView.swift` — Added "Rescan Metadata" to track right-click context menu **Tests**: 105 passed, 0 failures. --- ## 2026-03-03 — Fix: Year backfill and validation **Problem**: Existing tracks had `NULL` year after the `{Date}` feature was added. The `importFile()` early-returned for existing tracks without backfilling year. Also, `readMetadata` lacked year range validation in the `commonKeyCreationDate` branch, causing bogus values like "105" to be stored. **Changes**: - `Sources/Services/LibraryManager.swift` — `importFile()` now backfills year on existing tracks; `rescanMetadata()` uses new lightweight `readYear()` method - `Sources/Services/MetadataService.swift` — Added `readYear(from:)` (metadata-only, no AVAudioFile); added `y > 1900 && y < 2100` validation to `commonKeyCreationDate` branch - `Sources/Views/ContentView.swift` — Auto-backfill on launch: fetches tracks with `nil` or invalid year, runs `rescanAllMetadata` **Result**: All 317 tracks in DB now have valid year values. --- ## 2026-03-03 — Feature: Spacebar play/pause **Change**: Pressing Space toggles play/pause when MixBoard is active. Implemented via `NSEvent.addLocalMonitorForEvents` in `MediaKeyHandler`. Skips interception when a text field is focused. **Files changed**: - `Sources/Services/MediaKeyHandler.swift` — Added `AppKit` import, `keyMonitor` property, local keyDown monitor for keyCode 49 (space) --- ## 2026-03-03 — Feature: Double-click to play track **Change**: Double-clicking a track in the playlist now plays it. Uses `.onTapGesture(count: 2)` before `.onTapGesture(count: 1)` on each row, with `.contentShape(Rectangle())` for full row hit-testing. **Files changed**: - `Sources/Views/PlaylistView.swift` — Added double-click and single-click tap gestures to `groupContent()` rows --- ## 2026-03-04 — Feature: Tooltips on all player bar buttons **Change**: Added `.help()` tooltips to all player bar buttons (Stop, Previous, Play/Pause, Next, Shuffle, Repeat, Cursor Mode, Volume, Settings, Now Playing). Tooltip delay set to 200ms via `NSInitialToolTipDelay`. **Files changed**: - `Sources/Views/PlayerView.swift` — Added `help` parameter to transport `btn()`, added `.help()` to volume control - `Sources/MixBoardApp.swift` — Set `NSInitialToolTipDelay` to 200 on launch **Tests**: 105 passed, 0 failures.