- Lock sidebar width (setFixedWidth) so it doesn't jump between views
- Remove back/forward navigation buttons and all NavPage history code
- Uniform column layout on artist page: hide Artist column from both
Popular Tracks and release sections, set matching fixed column widths
so columns align vertically across all sections
- Deep shuffle: new Rust FFI endpoint fetches tracks from all albums
in parallel, combines them, and returns via EV_DEEP_SHUFFLE_OK
- Auto-paginate artist releases in Rust (loop until has_more=false)
so all releases load at once sorted newest-first
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**Section toggles left-aligned**
- Replace QToolButton with flat QPushButton for all section headers;
QPushButton properly respects text-align: left in stylesheets
**Pagination via "Load More" button**
- QTreeWidget expands to fit all items so the scrollbar-based infinite
scroll never triggered; replaced with an explicit "Load more…" button
that appears when has_more is true and emits loadMoreRequested
**Favourite button reflects actual state**
- MainWindow preloads fav artist IDs on session restore (getFavArtists)
and caches them in m_favArtistIds
- ArtistView receives the full set via setFavArtistIds() and checks
it on every setArtist() call so the button starts gold if already faved
- Toggling updates the local cache immediately for back/forward nav
**Playback error → queue advances**
- player_loop now sets track_finished on Err (was only set on Ok(None)),
so the toolbar's onTrackFinished handler advances to the next track
instead of stalling on an unplayable track
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
**Artist portrait**
- Fix CDN URL: images.portrait.{hash,format} →
https://static.qobuz.com/images/artists/covers/large/{hash}.{format}
**Section alignment**
- Qt::ToolButtonTextOnly on all section toggles so text is truly left-aligned
**Auth 401 race condition**
- qobuz_backend_set_token now uses blocking_lock() instead of spawning an
async task, guaranteeing the token is set before any subsequent API call
**Pagination (infinite scroll)**
- Release sections load 50 at a time (was 500)
- ArtistSection tracks has_more + loaded count; scrolling to the bottom
emits loadMoreRequested → ArtistView calls getArtistReleases(offset=N)
- AlbumListView gains addAlbums() for append; setReleases routes to
setAlbums (offset=0) or appendAlbums (offset>0)
**Back/Forward navigation**
- MainToolBar exposes Back/Forward QActions (go-previous/go-next icons)
- MainWindow keeps a NavPage vector + index; pushNav() on every album/artist
navigation; goBack/goForward re-navigate without pushing history
**Context menu on now-playing label**
- Right-click on track label in toolbar → "Go to Album" / "Go to Artist"
- MainToolBar stores current track; emits albumRequested/artistRequested
signals wired to MainWindow's existing handlers
**Artist favourites button**
- ♡ Favourite / ♥ Favourited toggle in artist header
- Calls new addFavArtist / removeFavArtist (Rust + Qt backend wiring)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Artist profile: collapsible Albums / EPs & Singles / Other sections
keyed on release_type; fetches up to 200 albums per artist
- Favorites: starred icon on favorited tracks, context menu shows
Add or Remove (not both); IDs cached when fav tracks are loaded
- Shuffle button: one-time shuffle via shuffleNow() without touching
global shuffle flag, so double-click still plays in order
- Now-playing art: replaced setFixedHeight hack with ArtWidget that
overrides hasHeightForWidth() — scales smoothly up and down, no min-size
- Volume popup: replaced QMenu (laggy, broken drag) with Qt::Popup QFrame;
appears below button; fixed size locked at 100% label width
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Playlist management:
- Add/remove tracks from playlists via right-click context menu
- Create new playlists (right-click Playlists sidebar header)
- Delete playlists with confirmation dialog (right-click playlist item)
- Playlist view removes track immediately on delete (optimistic)
- Deleting currently-open playlist clears the track view
Gapless playback:
- Single long-running audio thread owns AudioOutput; CPAL stream stays
open between tracks eliminating device teardown/startup gap
- Decode runs inline on the audio thread; command channel polled via
try_recv() so Pause/Resume/Seek/Stop/Play all work without spawning
- New Play command arriving mid-decode is handled immediately,
reusing the same audio output for zero-gap transition
- Position timer reduced from 500 ms to 50 ms for faster track-end detection
- URL/metadata prefetch: when gapless is enabled Qt pre-fetches the next
track while the current one is still playing
ReplayGain:
- Toggled in Settings → Playback
- replaygain_track_gain (dB) from track audio_info converted to linear
gain factor and applied per-sample alongside volume
Qobuz dark theme:
- Background #191919, base #141414, accent #FFB232 (yellow-orange)
- Selection highlight, slider fill, scrollbar hover all use #FFB232
- Links use Qobuz blue #46B3EE
- Hi-res H badges updated to #FFB232 (from #FFD700)
- Now-playing row uses #FFB232 (was Spotify green)
- QSS stylesheet for scrollbars, menus, inputs, buttons, groups
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Queue panel:
- Double-clicking an upcoming track skips to it immediately: drops all
tracks before it from the queue and starts playback (skipToUpcoming)
- Items can be dragged to reorder; rowsMoved rebuilds the queue via
setUpcomingOrder()
- Track JSON stored per-item so order survives drag operations
- New PlayQueue methods: skipToUpcoming(), setUpcomingOrder()
- New QueuePanel signal: skipToTrackRequested(qint64) wired to MainWindow
Remove visualizer:
- Drop VisualizerWidget, Qt6::OpenGLWidgets, projectM CMake detection
- Remove qobuz_backend_read_pcm FFI (Rust + C header + Qt wrapper)
- Remove pcm_visualizer from PlayerStatus and PCM tap from AudioOutput
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seeking:
- Rust player: seek_requested/seek_target_secs atomics on PlayerStatus
- Decoder loop checks for seek each iteration, calls format.seek() and resets decoder
- New qobuz_backend_seek C FFI + QobuzBackend::seek(quint64)
- Progress slider onProgressReleased now seeks to the dragged position
Last.fm:
- LastFmScrobbler: now-playing + scrobble (50% or 240s threshold, min 30s)
- API signature follows Last.fm spec (sorted params, md5)
- Settings dialog: API key/secret, username/password, Connect button with status
- AppSettings: lastfm/enabled, api_key, api_secret, session_key
- Scrobbler wired to trackChanged, positionChanged, trackFinished in MainWindow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fav albums: now shows a sortable list (title/artist/year/tracks);
double-click opens the album
- Fav artists: now shows a sortable list; double-click opens the artist
- Artist detail page: name, biography summary, and their album list
- Rust ArtistDto gains albums field; get_artist fixed to extra=albums only
- Volume popup minimum width set so "100%" label is never clipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lightweight Qt6 desktop client for Qobuz with a Rust audio backend
(Symphonia/CPAL via staticlib FFI). Mirrors the spotify-qt layout:
toolbar with playback controls, library/context docks on the left,
tabbed search side panel on the right, queue panel, now-playing dock.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>