Auto-advance from a previous track could arrive after the user
switched to a new album/playlist, advancing the new queue's index
and skipping a track (observed: Sorceress track skipped).
Added queue epoch counter that increments on setQueue/clearQueue.
nextTrackGuarded() captures epoch before the invoke and discards
the result if the queue changed while in-flight. Applied to all
auto-advance paths (track-end, gapless transition) but not to
manual skip (user-initiated).
cc
setCurrentTrack during session restore was missing albumId and artistId,
causing Go to Album / Go to Artist clicks to silently fail until a new
track was played. Also restore isLocal, source, and parental_warning for
full parity with the normal playback path.
Closes#252
cc
- Send track metadata to MPRIS on session restore so playerctl
shows title/artist/album immediately after app restart
- Persist playback context (album, playlist, etc.) to localStorage
on session save and restore it on startup
- Add buffer_progress to frontend PlaybackEvent type
Closes#240
cc
Show a subtle buffer progress bar in the seekbar when streaming
tracks (before they are fully cached). The bar disappears once
the track is fully buffered.
Backend: expose buffer_progress from BufferedMediaSource through
SharedState -> PlaybackEvent -> playerStore.
Frontend: render .seekbar-buffer bar behind the playback fill.
cc
F11 now toggles window fullscreen from anywhere in the app, not just
inside the immersive player. ESC exits window fullscreen when no UI
overlays are open.
Closes#202
cc
Listen for 'quality-fallback-prompt' custom events dispatched by
playbackService, open the modal with track context, and handle
try-lower / skip / close actions.
cc
Two bugs:
1. mainContentEl teardown crash on logout — the $effect cleanup used
non-null assertion (!) on an element that becomes null when the DOM
is destroyed during logout. Changed to optional chaining (?.).
This TypeError was blocking the render, causing the spinner to hang
instead of showing the login form.
2. deactivate_session was clearing last_user_id, which broke offline
mode after logout. The user's data stays on disk — only factory
reset should clear the last_user_id marker.
cc
Three bugs prevented offline mode from working after V2 migration:
1. activate_offline_session used user_id=0 (empty profile) instead of
the last known user_id, so the user's library/settings were invisible
2. setManualOffline was called BEFORE activate_offline_session, but it
requires the offline store to be initialized (which happens during
session activation). Reordered: activate session first, then set
manual offline.
3. LoginView called setManualOffline directly instead of delegating to
the parent's handleStartOffline which has the correct order.
Also added "noPreviousSession" i18n message for users who never logged
in — they need to create a profile first before offline mode works.
cc
When ALSA Direct hw: is active but a remote device (phone, speaker) is
the active renderer via QConnect, volume changes go to the remote device
not the local DAC. The 100% lock should only apply to local playback.
cc
- Add macOS platform detection (html.macos class)
- Prevent pinch-to-zoom gestures in WKWebView
- Hide custom title bar on macOS (uses native overlay title bar)
- Add 32px top padding to sidebar for traffic light buttons
- Add drag region for window movement
QconnectBadge was passing { rendererId } instead of
{ request: { renderer_id: rendererId } } to v2_qconnect_set_active_renderer,
causing "missing required key request" error on every handoff attempt.
Also fixed TypeError when event.payload is a string ("TransportConnected")
instead of an object — the 'in' operator requires an object operand.
cc
With ALSA Direct hw: there is no software mixer — volume must be
controlled at the DAC/hardware level. Software volume attenuation
would reduce bit depth and defeat the purpose of bit-perfect playback.
Backend: v2_set_volume forces volume to 1.0 when backend=Alsa+plugin=Hw
Frontend: volume slider disabled (grayed out at 100%), mute/step buttons
hidden, handleVolumeChange and handleToggleMute return early.
Settings callbacks notify +page.svelte when backend/plugin changes to
update the lock state in real-time.
i18n: volumeLockedHw tooltip added to all 5 locales.
cc
Developer Mode: toggle to show/hide QConnect dev panel button in
the player bar (hidden by default, persisted in localStorage).
Integrations: editable device name input for Qobuz Connect with
default "Qbz - {hostname}". Backend commands v2_qconnect_get/set
_device_name and v2_get_hostname. Custom name is used during the
WS bootstrap handshake.
cc
Place QConnect badge as direct child of badges-group with 3px gap
matching DAC/EXC spacing. Two-row vertical layout: icon on top,
Qobuz/Connect text below. Wire props from +page.svelte to both
NowPlayingBar instances. Add qconnect i18n keys to de/fr/pt locales.
cc
New _if_remote backend commands for set_volume and mute_volume.
handleVolumeChange and handleToggleMute in both main view and miniplayer
now check for peer renderer before setting volume locally. Media
control set_volume also routed through the guard. When no peer is
active, volume is set locally and reported to QConnect server.
When a peer renderer is active, the MPRIS/media-key stop command now
sends SetPlayerState(STOPPED) to the QConnect server instead of stopping
local playback. Falls back to local stop on error or when no peer is
active.
Replace local isMuted/previousVolume state in PlayerControlsCompact and
MiniPlayerFooter with the global toggleMute from playerStore. Muted
state is now derived from volume === 0, matching NowPlayingBar behavior.
Pre-mute volume is persisted in localStorage by the store.
When a peer renderer is active, shuffle/repeat/queue-play/history-play
commands are now routed to the QConnect server instead of being applied
locally. Two new backend commands (toggle_shuffle_if_remote,
cycle_repeat_if_remote) follow the existing _if_remote pattern.
Guards added to both +page.svelte (main + immersive) and miniplayer.
navigateTo() was passing the tab id to the navigation store but never
setting the local homeTab state that HomeView reads. Editor's Picks
and For You always landed on Tab-Home.
When last_view pointed to a view with incomplete context (e.g.
album without selectedAlbum data), the Svelte CSS extraction
pipeline would corrupt and produce PostCSS "Unknown word" errors
that survived cache cleans and npm ci.
- Add isSessionRestoreSafeView() whitelist for context-free views
- Add getSessionFallbackView() for graceful degradation
- Centralize persist logic in getPersistedSessionViewState()
- Only persist views that have valid context data
- Add purchase-album to restore path
- Add dev:reset-last-view script to reset session.db
- Fix AlbumCreditsModal.svelte event handler typing
Add defensive fallback conditions for artist and playlist views
when activeView is set but data hasn't loaded yet (race condition
during session restore). Also add a catch-all fallback at the end
of the view chain so no combination of state can result in an
empty/black main content area.
When WebKitGTK's GnuTLS can't find system trust store (common in
AppImage on some distros), direct HTTPS image loads fail. Home page
worked because AlbumCard already used cachedSrc which proxies through
the Rust backend (reqwest with its own TLS).
Apply cachedSrc to all remaining artwork displays:
- PlaylistCollage (playlist grid covers)
- NowPlayingBar (current track artwork + hover preview)
- ImmersiveArtwork (fullscreen player)
- QueuePanel (sidebar + immersive queue/history)
- CoverflowPanel, HistoryPanel, FocusMode
Also resolve artwork URL in +page.svelte via getCachedImageUrl before
passing to NowPlayingBar/ImmersivePlayer, so all downstream immersive
panels receive pre-cached asset:// URLs.
The Discover dropdown items (Home, Editor's Picks, For You) now
correctly navigate to the three tabs of the Home/Discover view
using navigateTo('home', tabId) instead of navigating to unrelated
discover-* view pages.
Nav buttons get 8px padding on the edge side (matching folder spacing).
New opt-out toggle to hide window control buttons (min/max/close) for
window manager users who want the titlebar for nav and search only.
Opt-in setting (off by default) that moves Discover, Favorites, and
Local Library from the sidebar to the custom titlebar. Includes
dropdown menus for Discover and Favorites sub-items. Navigation
button position defaults to opposite of window controls but is
user-configurable. Items are hidden from sidebar when shown in
titlebar.