- downloaded_purchases table in library DB (permanent, per-user)
- V2 commands: mark_downloaded, remove_downloaded, get_downloaded_track_ids
- Frontend enriches purchase data with download status from backend
- Grid overlay for unavailable albums centered on artwork
- Quality filter expanded: All / Hi-Res / CD / Lossy (radio group)
- Album download status derived from track-level registry
- Toolbar: FavoritesView pattern (control-btn, dropdown-menu, dropdown-item)
- Albums: grid/list toggle, group by (Off/A-Z/Artist), sort with direction
- Filter panel: LocalLibraryView pattern (checkbox for hide unavailable, Hi-Res)
- Tracks: group by (Off/Artist/Album/Name A-Z)
- Format selector: standard Dropdown component instead of custom pill
- Download All: global action-btn-circle instead of custom button
- Quality in track rows: plain text (12/192) instead of QualityBadge images
- QualityBadge compact only in album header and list mode
- Add filter.availability and filter.hideUnavailable i18n keys (4 locales)
- Add 'purchases' and 'purchase-album' to ViewType
- Add conditional ShoppingBag nav item in Sidebar (between Favorites and Playlists)
- Wire PurchasesView and PurchaseAlbumDetailView in +page.svelte
- Add Show Purchases toggle in Settings > Appearance
- Toggle persisted via purchasesStore, hidden by default
PurchasesView: albums grid and tracks list with search, filter
(all/hires), sort (date/artist/album/quality), and download buttons.
PurchaseAlbumDetailView: album header, format picker, folder picker
(mock mode text input), per-track download buttons, progress bar,
multi-disc grouping, and post-download Add to Library button.
- PurchaseResponse, PurchasedAlbum, PurchasedTrack, progress types
- Service layer with mock/real toggle via VITE_MOCK_PURCHASES env var
- purchasesStore for show/hide toggle persistence via userStorage
Lightweight mock API serving Qobuz-shaped purchase data with CORS
enabled for localhost:1420. Includes 8 albums (varied genres, one
multi-disc, one with downloadable:false), 5 individual tracks,
format options, search, and simulated download progress via SSE.
- add Spectral Ribbon analyzer/render path and immersive panel
- tune ribbon density/interpolation and bars edge-bin rendering
- apply artist blacklist filtering to discover index home containers
- include supporting i18n/settings/player wiring changes
cx
The global downloadStateVersion counter was embedded in {#each} key
expressions, causing Svelte to destroy and recreate all DOM nodes
whenever the counter incremented during downloads. Track IDs alone
are stable and unique keys; download status reactivity is already
handled by props passed to TrackRow.
Also skip redundant notifyListeners() calls in setOfflineCacheState
when status and progress haven't actually changed, preventing
unnecessary downloadStateVersion++ cascades.
Add dedicated Plex source filter in albums view, header sync button when Plex is configured, and one-time auto-sync per session when Plex cache is empty.
cx
Add a second LibraryDatabase connection to OfflineCacheState dedicated
to download post-processing writes. SQLite WAL mode natively handles
concurrent reads (UI via main connection) and writes (downloads via
this separate connection), eliminating app-level mutex contention that
caused progressive UI lag during bulk downloads.
Init after library.init_at() in both activate_session and
activate_offline_session. Teardown before main offline_cache teardown.
Add v2_cache_tracks_batch_for_offline command that inserts all tracks
in a single SQLite transaction and spawns download tasks in one IPC
call. Extract spawn_track_cache_download helper shared by both single
and batch commands.
Replace sequential for-of loops with batch calls in PlaylistDetailView
and +page.svelte (downloadAlbumById, handleDownloadAlbum,
handleReDownloadAlbum, reDownloadAlbumById). Single-track handlers
remain unchanged.
Reduces 75 IPC round-trips to 1 for bulk downloads.
Add a minimum 200ms interval between progress emissions per track,
on top of the existing 2% change filter. Always emits at 100%.
Limits events to max 5/sec/track, reducing IPC pressure during
concurrent downloads.
Coalesce all offline cache state notifications within a single
animation frame (~16ms) into one notification cycle. This caps
UI re-renders from hundreds/sec down to max 60/sec during bulk
downloads, preventing progressive UI lag from event storms.
These two databases were missing PRAGMA journal_mode=WAL and
PRAGMA synchronous=NORMAL, which could cause UI lag from lock
contention during concurrent reads/writes.
Shrink player bar min-width 700→670px. Move track info block to
bottom: 24px (same level as player). Album art now sits to the right
of the text block (title/album/artist/badge). The info block is part
of the panel so it stays visible when player controls auto-hide.
All 4 canvas panels (Oscilloscope, Energy, Pulse, X/Y) now share:
- Bottom-left: 72px album art thumbnail with rounded corners
- Bottom-right: 4 rows (title, album, artist, compact badge), right-aligned,
max-width 400px for longer text
- Energy: removed 85% height constraint, rings use full canvas
Convert 6 visualizer tabs into a single dropdown menu in the header.
Remove album art and full quality badge from Oscilloscope, Energy,
Transient Pulse, and Lissajous panels. Replace with bottom-right
compact track info (title, artist/album, text-only quality badge).
Dramatically increase AlbumReactive sensitivity for heavy music
(faster attack/decay, 3x scale/glow multipliers).
Energy Bands + Oscilloscope:
- Constrain drawing area to 85% of screen height, vertically centered
Oscilloscope:
- Change from bottom-left info to centered artwork layout (like Bars)
Transient Pulse:
- Lower backend threshold 0.15 -> 0.04, cooldown 6 -> 3 frames
- Use raw (un-smoothed) bass-weighted RMS for sharper detection
- Add frontend-side supplementary beat detection from energy data
Album Reactive:
- Fix glow layer centering (was anchored to top-left, now inset: 0)
- Double scale effect (0.04 -> 0.08) and glow spread (40 -> 80)
- Fast attack / medium decay smoothing for visible reactivity
Backend:
- Emit viz:energy with 5-band RMS (sub-bass, bass, mids, presence, air)
- Emit viz:transient with attack detection (threshold + cooldown)
Frontend panels:
- EnergyBandsPanel: concentric glowing rings around centered artwork
- LissajousPanel: stereo X/Y phase plot with trail (watermark layout)
- TransientPulsePanel: expanding rings from center on beat detection
- AlbumReactivePanel: album art with breathing scale/glow from energy
All panels extract colors from album art, throttle at 30fps, and follow
the existing visualizer lifecycle (enable/disable backend on mount).
Focus tabs now: Coverflow, Static, Bars, Scope, Energy, X/Y, Pulse,
Reactive, Lyrics, Queue (keys 1-0).
New OscilloscopePanel renders dual L/R waveforms from viz:waveform
event data. ALBUM_SECONDARY layout with small artwork bottom-left.
Colors extracted from album art with green tint (L) and blue tint (R).
Registered as focus tab with AudioWaveform icon and keyboard shortcut 4.
Renumbered focus tab shortcuts (1-6) to match new tab order.
Add viz:waveform event emission alongside existing viz:data FFT output.
De-interleaves L/R channels from the raw sample buffer and downsamples
to 256 points per channel (512 floats = 2048 bytes). Pre-allocates the
waveform buffer outside the hot loop to avoid per-frame allocations.
The wizard step 6 was writing per-app bit-perfect rules to
pipewire-pulse.conf.d/pulse.rules, but QBZ uses pipewire-alsa (via
rodio/cpal), so those rules were never applied.
Changed to pipewire.conf.d/stream.rules which applies to ALL PipeWire
clients regardless of API (ALSA, PulseAudio, JACK). Also updated the
step indicator from "Pulse" to "Apps" in all 4 locale files.
Fixes#53 (wizard bug)
Integrations section now shows a toggle to register/deregister QBZ as
the default handler for qobuzapp:// links. Checks current status on
mount, calls v2_register/v2_deregister commands on toggle.
Import LinkResolverModal, add handleResolvedLink() that dispatches to
handleAlbumClick/handleArtistClick/selectPlaylist. Listen for
link:resolved event from OS scheme handler. Render modal in template.
Svelte 5 modal with input field for pasting Qobuz URLs. Calls
v2_resolve_qobuz_link and forwards the resolved link to the parent.
Enter key submits, error displayed inline.
Added to all 4 locales (en, es, de, fr):
- settings.integrations.qobuzLinkHandler* keys for settings toggle
- linkResolver.* keys for the modal (title, placeholder, go, resolving, invalidLink)
Single-instance plugin now inspects args for qobuzapp:// or
play.qobuz.com URLs and emits link:resolved event. Launch args
are also checked on first startup with a 1.5s delay for frontend
readiness.
v2_check_qobuzapp_handler, v2_register_qobuzapp_handler,
v2_deregister_qobuzapp_handler — use xdg-mime for scheme handler
registration and mimeapps.list editing for deregistration.
- Add v2_save_session_state and v2_load_session_state commands
- Update favoritesStore to use V2 cache commands
- Update albumFavoritesStore to use V2 cache commands
- Update artistFavoritesStore to use V2 cache commands
- Update sessionService to use V2 session commands
- Update playbackContextStore to use V2 context commands
Previously, V2 queue commands had a dual-queue bug:
- Writes (v2_set_queue, v2_add_to_queue, etc.) went to AppState.queue (legacy)
- Reads (v2_get_queue_state) came from CoreBridge/QbzCore queue
- Result: frontend sets queue, syncs, gets empty/stale data
This commit fixes the architecture:
- All V2 queue commands now use CoreBridge methods
- Added queue methods to QbzCore: add_track, add_tracks, add_track_next,
set_queue, remove_track, remove_upcoming_track, move_track, play_index,
next_track, previous_track, peek_upcoming
- Added corresponding methods to CoreBridge
- Fixed v2_set_shuffle to use CoreBridge (was using legacy AppState)
- Fixed runtime requirements: v2_toggle_shuffle and v2_clear_queue now
use RequiresUserSession like other queue commands (consistency)
- Prefetch now gets upcoming tracks from CoreBridge, not legacy queue
- Updated tests to match V2 behavior (v2_toggle_shuffle, available field)
- HomeView: fix OfflineCacheStatus type for getTrackOfflineCacheStatus prop
- PlaylistDetailView: fix trackToReplace null access in console.log
- SearchView: add id and isViewMore to Album/Artist interfaces
- Toast: add 'warning' type support
- playbackContextStore: add 'search' to ContextType union
- queueStore: add trackId to QueueTrack interface
- trackActions: add queueDisplayTrackNext/Later and buildQueueTrackFromDisplayTrack
- +page.svelte: fix numerous type issues including:
- selectedAlbum closure captures for TypeScript narrowing
- handleDisplayTrackDownload signature for DisplayTrack
- handleSearchTrackDownload for SearchView's Track type
- ImportSummary callback signature
- QobuzTrack hires_streamable property usage
- album.tracks.items instead of .data
npm run check now passes with 0 errors (138 warnings remain, mostly a11y)
- SearchView.svelte: Fix async onMount pattern to properly return cleanup
- vite.config.js: Add ts-nocheck to suppress Node.js global errors
Reduces npm run check errors from 42 to 40.
ListenBrainzV2State::clear_credentials() now resets internal client to
fresh state, not just wrapper fields.
LastFmV2State:
- Added clear_session() method that clears session_key AND pending_token
- init_with_session(None) now calls clear_session() instead of no-op
Ensures no credentials leak across sessions on logout/deactivate.
Closes Must-Fix #2 from MUST_FIX_BEFORE_RELEASE_RUNTIME_V2.md
Added rollback_auth_state() helper that clears legacy_auth, corebridge_auth,
and emits AuthChanged event on partial login failures.
Applied to all error paths in v2_auto_login, v2_manual_login, and v2_login:
- CoreBridge auth failure now rolls back
- Session activation failure now rolls back
Ensures runtime_get_status never reports half-authenticated state.
Closes Must-Fix #1 from MUST_FIX_BEFORE_RELEASE_RUNTIME_V2.md