Root cause: server pause/resume commands send None for position and
tracks, which the renderer state blindly overwrote. Then renderer
reports sent null position to the server, making controllers show the
track as finished.
Three-part fix:
1. renderer.rs: only overwrite position/tracks when server provides
a non-None value (retain last known values through pause/resume)
2. app.rs + qconnect_service.rs: update renderer state position from
frontend periodic reports so the internal state always has the
real playback position
3. +page.svelte: increase position report frequency from 5s to 2s
for smoother seekbar updates on controllers
Root cause: inline BackendQueueTrack constructions in +page.svelte were
missing the 'source' field, causing resolveQconnectTrackOrigin to return
'external_unknown' which the admission system blocks.
Fix adds source:'qobuz' and streamable to all three inline track
constructions (album play next, playlist play next, album detail play
next).
Also restructures queueTrackNext and queueTrackLater to use a
useLocalFallback flag when admission rejects with
handoff_intent='continue_locally', so the track actually gets queued
locally instead of silently failing.
Logs confirm the QConnect protocol uses milliseconds for current_position
and duration fields. The 'seconds' fix was incorrect - the position
display issue was already resolved by the spam fix in 807b775a.
QConnect protocol uses seconds for current_position and duration fields.
Frontend was sending milliseconds (currentTime * 1000), causing controllers
to show track position at the end instead of actual progress.
Two bugs caused "Current track not found in queue nor autoplay" errors:
1. prevTrackId only updated when allowLocalSessionPersistence was true,
but during QConnect mode this is always false. This made every 1s
playback tick fire a state report (trackId != null always != prevTrackId
which was stuck at null).
2. Frontend sent currentQueueItemId: null in all reports because it
doesn't know about QConnect queue_item_ids. Server rejected every
report.
Fixes:
- Update prevTrackId unconditionally (not gated by session persistence)
- Backend auto-fills queue_item_ids from renderer state when frontend
passes null
- Track next_queue_item_id in sync state alongside current
- Add throttled 5s periodic position report for controller progress
The gapless transition detection was intercepting QConnect track changes
before the "Track changed externally" handler could process them. Since
the gapless callback returned early (QConnect guard), the event was
silently swallowed and the UI never updated.
Added remoteControlMode flag to playerStore that disables:
- Gapless transition interception (the root cause)
- Fallback end-of-track detection
- Auto-advance on track end
This allows the "Track changed externally" handler to run and sync
the song card, seek bar, and volume with the QConnect-controlled track.
When QConnect is connected as renderer, the server/controller manages
track advancement. The frontend must not auto-advance, pre-queue gapless
tracks, or resume from stop on its own, as this fights the remote controller
and causes rapid queue scrolling without actual playback.
- Add session card showing active renderer and renderer list
- Add controller transport buttons (play/pause/stop) via v2_qconnect_set_player_state
- Add volume slider and mute toggle via v2_qconnect_set_volume/mute_volume
- Add renderer selector with set-active-renderer button
- Pass sessionSnapshot prop from +page.svelte to QconnectPanel
- Add i18n keys for all new controller UI elements (en + es)
When QBZ is acting as a QConnect renderer, local user interactions
(volume change, play/pause) are now reported back to the server via
v2_qconnect_report_volume and v2_qconnect_report_playback_state.
This keeps the controller in sync with QBZ's actual state.
Thread onCreateQbzRadio/onCreateQobuzRadio through TrackRow,
VirtualizedTrackList, PlaylistDetailView, and FavoritesView so
track context menus in playlists and favorites show the radio
submenu with QBZ Radio and Qobuz Radio options.
FavQ shuffles up to 50 tracks from the user's Qobuz favorites.
TopQ seeds the Qobuz suggest API from the user's most-played playlists.
Both have same-day cache, refresh button, red/blue gradient artwork,
and a "qbz" badge (vs "qobuz" on DailyQ/WeeklyQ).
Add Radio button to album detail header (next to favorite button)
that calls v2_create_album_radio, populates the queue with Qobuz
API recommended tracks, and starts playback of the first track.
Add i18n keys for album radio in all 4 locale files (en/es/de/fr).
Two fixes for session not persisting:
1. Changed v2_get_all_queue_tracks to return a struct instead of a
tuple — Tauri IPC serialization of tuples is unreliable.
2. Added proactive session saves instead of relying on async
beforeunload (which races with window close):
- Debounced full save on track change (2s after new track starts)
- Debounced full save on pause
- Reduced periodic save from 60s to 15s during playback
- Close handler remains as best-effort backup
The save was broken in two ways:
1. Only current_track + upcoming (capped at 20) were saved, dropping
all history tracks. Queue was reconstructed incomplete on restore.
2. Track metadata (hires, bit_depth, sample_rate, source, etc.) was
not being persisted, only the basic 6 fields.
Add v2_get_all_queue_tracks command that returns the full uncapped
track list with correct current_index. Use it in saveSessionBeforeClose
instead of the capped v2_get_queue_state. Map all metadata fields.
Re-enable queue/track restore on startup (visual only, paused at 0:00).
Update saveSessionBeforeClose to persist current view with context IDs.
Add "Startup page" dropdown in Settings > Appearance (Home / Where you
left off) that controls whether the last view is restored on launch.
i18n keys added to all 4 locale files.
Ctrl+L now accepts URLs from Spotify, Apple Music, Tidal, Deezer, and
song.link/album.link in addition to Qobuz. Non-Qobuz track/album URLs
are resolved to their Qobuz equivalent via the existing Odesli API
integration. Playlist URLs redirect to the Playlist Importer.
- Add MusicResource/MusicProvider enums and detect_music_resource()
- Add detect_resource() to each provider (spotify, apple, tidal, deezer)
- Add v2_resolve_music_link command with Odesli-based cross-resolution
- Upgrade LinkResolverModal with platform icon detection and playlist redirect
- Update i18n keys in all 4 locales (en, es, de, fr)
The context icon showed correct tooltip on hover but clicking did
nothing for daily_q and weekly_q types. Add missing switch cases
to navigate to the respective views.
Wire onPlaylistPlay, onPlaylistPlayNext, onPlaylistPlayLater,
onPlaylistCopyToLibrary, onPlaylistShareQobuz through LabelView Props
to QobuzPlaylistCard. Increase section spacing from 48px to 64px.
Replace the flat album grid LabelView with a rich detail page featuring:
- Header with label image, name, description, and play button
- Popular Tracks section (5/10/all expand, same UX as ArtistDetailView)
- Releases horizontal scroll row with "See all" → LabelReleasesView
- Critics' Picks section from second releases container
- Playlists, Artists, and More Labels sections
Backend: add /label/page and /label/explore API endpoints with V2
commands (v2_get_label_page, v2_get_label_explore). Models use
serde_json::Value for flexible section parsing.
Old LabelView renamed to LabelReleasesView for the "See all" flow.
i18n keys added to all 4 locales (en, es, de, fr).
- Add back navigation button to both DailyQ and WeeklyQ views
- Replace subtitle with home card descriptions
- Remove cache/live source text, add total playback duration
- Remove refresh button from WeeklyQ (regenerates weekly on Fridays)
- Fix weekly cache to use Friday-based key instead of daily
- Add algorithm info icon with tooltip on both views
- Fix playback context: align queue index with blacklist-filtered tracks
- Change header label from "Playlists" to "Your mixes"
- Add algorithmInfo i18n key to all 4 locales
Add parent-driven track playback with queue setup, playback context,
context menus, offline cache status, and artist blacklist filtering.
Both views now behave like normal playlist views.
Static section (no API call) with video-background cards that navigate
to DynamicSuggestView and WeeklySuggestView. Positioned after Qobuz
Playlists in default Home order, toggleable via Home Settings.
Purchased tracks are streamable via normal Qobuz API. Reuse existing
handleDisplayTrackPlay/playAlbumById infrastructure to enable click-to-play
on track rows and a Play button on album detail header. Active track
highlights with accent color and animated playing bars.
Queue now pushes main content instead of overlaying it, fixing the
search header z-index conflict. Lyrics sidebar height fixed to 100%
to fill parent container correctly. Queue and lyrics are now mutually
exclusive - opening one closes the other.
- 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
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.
Import LinkResolverModal, add handleResolvedLink() that dispatches to
handleAlbumClick/handleArtistClick/selectPlaylist. Listen for
link:resolved event from OS scheme handler. Render modal in template.
- 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)
Backend:
- v2_auto_login/v2_manual_login now FAIL if activate_session fails
(was: log and continue with success:true, causing "logged in but not ready")
- Session activation is now REQUIRED, not optional
Frontend:
- Remove duplicate activate_user_session call from handleLoginSuccess
(V2 login already activates session internally)
- Remove duplicate deactivate_user_session call from handleLogout
(v2_logout already deactivates session internally)
- LoginView now uses runtime_bootstrap as SINGLE SOURCE OF TRUTH
(was: v2_init_client + legacy is_logged_in/get_user_info checks)
- PlaylistDetailView uses runtime_get_status for user_id
(was: legacy get_current_user_id)
This eliminates the "double-brain" where frontend used both legacy
and V2 paths, causing state divergence and intermittent failures.
Addresses multiple review findings for V2 architecture:
1. Create session_lifecycle.rs module:
- activate_session(): Full session activation callable from anywhere
- deactivate_session(): Full teardown with proper runtime state cleanup
- activate_offline_session(): Minimal session for offline/local-only use
2. Make runtime_bootstrap autocontenido:
- Now activates session after successful auth (Step 4)
- No longer delegates to frontend for session activation
3. Fix v2_login to be canonical:
- Performs full flow: legacy auth -> corebridge auth -> session activation
- Updates all runtime state flags
4. Fix v2_logout to be canonical:
- Calls deactivate_session (teardown stores + clear runtime state)
- Proper cleanup of legacy and corebridge auth
5. Fix deactivate_session:
- Now clears session_activated flag (was missing)
- Now clears corebridge_auth flag (was missing)
- Emits UserSessionDeactivated event
6. Add v2_set_repeat_mode runtime check:
- Now requires RequiresUserSession like other queue mutations
7. Fix LoginView early session activation:
- Removed premature activate_user_session call before auto-login confirmed
- ToS check uses localStorage fallback if no session
8. Fix offline mode:
- Add v2_activate_offline_session command
- Offline mode now activates minimal session so queue commands work