- Add right-click context menus to search panel tracks (play, queue,
favorites, playlist, go to album/artist, track info) and albums
(open, favorite, go to artist)
- Add "Track info..." dialog showing metadata (title, performer,
composer, album, quality, hi-res status) — available everywhere:
playlists, albums, favorites, search results
- Split artist page shuffle into "Shuffle" (popular tracks) and
"Shuffle All" (deep shuffle across all releases)
- Remove magnifying glass emoji from welcome text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- Load artist portrait from images.portrait.hash via QNetworkAccessManager
- Fix race condition: fire getArtistReleases after setArtist() clears sections,
not before (from onArtistLoaded instead of onSearchArtistSelected)
- Apply uniform gold (#FFB232) play/shuffle button style matching album view
- Make biography scrollable (QTextEdit with max height + scroll on overflow)
- Extend track artist name parsing to handle top_tracks {display:...} format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of relying on the limited preview in artist/page, fire a
separate artist/getReleasesList request per release type (album,
epSingle, live, compilation) in parallel when loading an artist.
Each result updates its section independently as it arrives, so the
page populates progressively without a single large request.
Also fixes the artist name in the status bar (was reading wrong field).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adds extra=topTracks to artist/page API request
- Embeds a List::Tracks widget at the top of ArtistView showing
the artist's most popular tracks, with Play and Shuffle buttons
- Bubbles playTrackRequested through MainContent up to MainWindow
- Also adds the viz PCM ring buffer FFI infrastructure (for future
spectrum widget) to the Rust backend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch artist view to artist/page API (proper sections: Albums, Singles & EPs,
Live, Compilations; version in titles like "Deluxe")
- Fix artist sections categorization using releases[].type from artist/page
- Add getUser() backend call; fetch on session restore when userId=0 to fix
playlist ownership (Remove from playlist / Delete playlist were missing)
- Fix multi-disc double-click / Play Now queue start index (disc headers were
counted in row index but excluded from currentTracksJson)
- Hide redundant Album column when viewing an album
- Make artist name in context header clickable (navigates to artist page)
- Fix gap between title and artist name in context header (addStretch)
- Fix queue panel track titles to include version field
- Fix album list to show version in title column
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>
When gapless is off, the AudioOutput is dropped after each track ends
naturally, producing a real gap on the next play. When on, the output
stays alive so tracks transition seamlessly. Also re-adds URL prefetch
gating behind the same toggle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The player keeps the audio output alive between tracks unconditionally.
The toggle only controlled URL prefetching, not actual audio gaplessness.
Remove the setting and always prefetch the next track URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add TrackContextHeader widget: shows album art (fetched via NAM),
title, subtitle (artist/description), and metadata (year · tracks · duration)
above the track list when an album or playlist is opened
- Hide header for favorite tracks and search results
- Store user ID in AppSettings on login
- Only show "Delete playlist" for playlists the user owns
- "Add to playlist" submenu only lists owned playlists
- "Remove from this playlist" only appears when viewing an owned playlist
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add EV_PLAYLIST_TRACK_ADDED (22) — emitted when a track is
successfully added to a playlist
- If the currently-open playlist is the one modified, re-fetch it
so the track appears in the list immediately
- After creating a playlist, open it automatically so the user
lands in the new (empty) playlist view right away
- Sidebar already refreshes on create/delete; this ensures the
track list view also stays in sync
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>