Replace heuristic-based most popular detection with Qobuz's actual
most_popular field from the catalog/search endpoint.
- Add CATALOG_SEARCH endpoint path
- Make app_id() and get_http() public on QobuzClient
- Add MostPopularItem enum with tagged serialization (artists/albums/tracks)
- Update search_all to use catalog/search and parse most_popular field
- Update SearchView template to use allResults.most_popular with new types
- Add MostPopularItem type to searchState store
The Most Popular result in search now intelligently determines
the best match based on query:
- Exact name match: artist > album > track
- Starts with match: artist > album > track
- Fallback: first artist, then album, then track
Added track card display for when a track is the most popular result.
- Update "Streaming Only" description to be less technical:
"Disables temporary cache. Not recommended without a fast connection.
Offline library tracks always play first."
- Fix account card vertical centering with explicit align-self on
avatar, info container, and logout button
Adds UI toggle for streaming_only mode that skips L1/L2 cache writes.
When enabled, tracks are streamed fresh each time but offline cache
still works for downloaded tracks (local-first principle).
Add streaming_only setting that skips L1+L2 cache writes while still
respecting offline cache (local-first). When enabled:
- download_and_stream() skips final cache.insert()
- Standard download path skips cache.insert()
- Offline cache continues to work normally
This reduces storage usage for users who prefer pure streaming.
- Clear both L1 memory and L2 disk caches when quality setting changes
This ensures cached tracks at wrong quality are removed
- Fix device name not showing in popup on first hover
Now refreshes status on each hover instead of only when null
- Add quality downgrade indicator when stream quality is lower than
track's original quality (shows yellow arrow with tooltip)
- Store original track quality values for comparison with actual stream
Issue #34 - Display actual stream quality, not track metadata
Backend changes:
- Add sample_rate and bit_depth to PlaybackEvent
- Add stream quality tracking to SharedState
- Extract bit_depth from FLAC/M4A headers in streaming path
- Player sets stream quality when decoding audio
Frontend changes:
- Update PlaybackEvent interface with new fields
- Update currentTrack with actual stream quality from backend
This allows the UI to show the actual quality being played (e.g., 44.1kHz
when streaming a 192kHz track at CD Quality) instead of the track's
maximum advertised quality.
Issue #34 - Max quality setting not respected
Changes:
- Remove 'Prefer Highest Available' toggle (was dead code, conflicted with quality dropdown)
- Clear playback cache (L1+L2) when streaming quality changes
- Show toast notification when cache is cleared
- Add description to streaming quality setting
- Update i18n for new messages (en/es)
This ensures the quality setting takes effect immediately, important for:
- Users with metered internet connections
- Users with hardware sample rate limitations (e.g., DACs limited to 96kHz)
The Qobuz API respects format_id and downsamples server-side, so this
change guarantees the correct quality is fetched for new streams.
- Log requested vs returned format_id and sample_rate in get_stream_url_with_fallback
- Log quality_str, parsed quality, and format_id in play_track and prefetch_track commands
- Add console.log in getStreamingQuality for frontend debugging
This will help diagnose why 192kHz is played despite lower quality settings.
- Add branch workflow section explaining pre-release integration
- Clarify that all PRs must target pre-release, not main
- Update maintainer procedure for new workflow
- Add release procedure section
- Add TrackReplacementModal to search for replacement tracks
- Add onFindReplacement action to TrackMenu context menu
- Show replacement option only for owned playlists with unavailable tracks
- Replacement searches by track title + artist
- Shows quality info (bit depth, sample rate) to help user choose
- Supports preview playback before confirming
- Replace old track with new one in playlist
Also fix tooltip for unavailable tracks to show a complete message
instead of "is no longer available" (now shows "Track no longer
available in Qobuz library")
- Add isUnavailable prop to TrackRow to show AlertCircle instead of track number
- Show unavailable icon only for user's own playlists (not Qobuz official playlists)
- Gray out unavailable tracks in both owned and non-owned playlists
- Remove all interactions for unavailable tracks in non-owned playlists
- Keep only "remove from playlist" option for unavailable tracks in owned playlists
- Create unavailableTracksStore for persisting unavailable track IDs to localStorage
- Load unavailable tracks on app startup in bootstrap.ts
- Auto-mark tracks as unavailable when playback fails with TrackUnavailable error
- Add streamable field to PlaylistTrack and DisplayTrack interfaces
- Add isTrackRemovedFromQobuz() check for tracks with streamable: false
- Show red AlertCircle icon for unavailable tracks
- Gray out and apply grayscale filter to unavailable track rows
- Limit menu actions to only 'Remove from Playlist' for removed tracks
- Hide favorite and download buttons for removed tracks
- Keep wrapper interactive for context menu access
Backend:
- Add TrackUnavailable error type for tracks removed from Qobuz
- Validate stream URL response - detect missing URL with restrictions
- Add SampleRestrictedByRightHolders to restriction check
- Return specific error when track is unavailable vs quality unavailable
Frontend:
- Detect track unavailable errors in playTrack()
- Show friendly toast: Track title is no longer available
- Auto-skip to next track in queue after 500ms delay
- Add i18n translations (en/es) for trackUnavailable message
- Add validation to check if URL is empty before returning StreamUrl
- Add debug logging for JSON response keys
- Return proper error instead of silently using empty URL
- This prevents 'builder error' from HEAD request to empty URL
- Use lazy_static to create a single reusable HTTP client instead of
creating a new client for each stream info request
- Add retry logic (3 attempts with 100ms delay) to handle transient
network/TLS initialization failures
- This fixes 'Failed HEAD request: builder error' toasts that required
pressing play button twice
- Adjust badge icons: QBZ 17px (was 22px), Qobuz 20px (was 18px)
QBZ fills its viewBox completely while Qobuz has whitespace,
so QBZ needs to be smaller to appear visually equal
- Adjust glyph icons: Playlist 32px (was 28px), Radio 26px (was 24px)
Playlist SVG only fills ~77% of its 20x20 viewBox, needs larger
pixel size to match Lucide Radio which fills its box completely
Backend:
- Add get_artist_tracks command for fetching artist's popular tracks
Frontend:
- Fallback to artist's popular tracks when tracks_appears_on is sparse
- Remove card hover movement, only image moves up on hover
- Add hover overlay with (add to queue) (play) (play next) buttons
- Use playlist.svg icon for playlist cards and glyph
- Radio glyph shown by default, replaced by action buttons on hover
- Fix icon sizes: QBZ (22px) vs Qobuz (18px) for visual balance
- Track playlist.svg asset
- Change from aggressive title normalization to exact title match only
- Keeps different versions (Remaster, Live, etc.) as valid options
- Add debug logging for sparse recommendation data
- This should fix the "only one recommendation" issue for some artists
- Fix $derived to $derived.by for complex logic (was causing CPU spike)
- Remove badge backgrounds from QBZ and Qobuz icons
- Move info icon (i) to top-left corner of radio card
- Add play button to radio card
- Increase badge icon sizes for visibility
- Dedupe recommended tracks by normalized title (filters remasters/editions)
- Use actual QBZ logo SVG instead of text "Q"
- Add playlist icon and "Playlist" label for clarity
- Add diamond-style collage layout for Song Radio card
- Pass currentArtwork for radio collage images
- Clean up unused error-text CSS in LyricsPanel
Layout:
- Top section: Artist curated playlists (from Qobuz) + Song Radio card (QBZ)
- Bottom section: Recommended tracks from artist's discography
Features:
- Artist playlists fetched via get_artist_detail (includes curated playlists)
- Song Radio card with experimental badge and tooltip
- Recommended tracks shuffled from artist's tracks_appears_on
- Single API call per artist (efficient loading)
- Loading state with spinner, empty state handling
- Play buttons with hover effects
Technical:
- Uses existing create_track_radio command for radio generation
- Tracks loadedArtistId to prevent duplicate requests
- Added translations for songRadio, radioExperimental, recommendedTracks
- Track loadedTrackId to avoid re-fetching same track
- Only request when trackId actually changes from loaded one
- Clear state only when track ends (trackId becomes null)
- Remove broken common.loading translation (spinner is enough)
- Create TrackInfoPanel with full track metadata and credits
- Display: duration, quality, ISRC, label, and all performer credits
- Uses same backend as TrackInfoModal (get_track_info command)
- Rename tab from 'Credits' to 'Track Info' (player.trackInfo)
- Update keyboard shortcut from 'C' to 'T' for Track Info tab
- Fix actions.clearQueue -> player.clearQueue translation key
- Add i18n translations for trackInfo in EN/ES
- Delete old placeholder CreditsPanel
- Increase canvas size from 8x8 to 64x64 to reduce visible pixelation
- Add small CSS blur(40px) to smooth the result
- Key: blur(40px) on 64x64 is much cheaper than blur(120px) on full image
- Adjusted brightness slightly darker (0.55x) for better text contrast
Performance problem:
- WebKitWebProcess consuming 97% CPU on one core
- Cause: CSS filter: blur(120px) on large image is extremely expensive
in WebKit/GTK which cannot GPU-accelerate this operation
Solution:
- Replace CSS blur with Canvas-based tiny thumbnail approach
- Draw image to 8x8 canvas, apply color adjustments, scale up via CSS
- Natural pixelation creates blur-like effect with ZERO CPU cost
- Remove backdrop-filter: blur() from header elements (use solid bg)
Also fixed:
- Change Mic icon to Mic2 to match rest of app
- Add missing i18n translations for player.credits and player.suggestions
- Remove track info (artwork, title, artist) from ImmersiveControls bottom bar
- Add track title and artist below album artwork in split view mode
- Change Lyrics Focus icon from MessageSquareText to Mic (microphone)
- Remove unused compact mode from ImmersiveControls
- Simplify props and cleanup CSS