- 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.