SESSION_LOG.md 6.8 KB

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.swiftimportFile() 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.