The popover appeared behind other elements when the sidebar was
collapsed because it was inside the .content div which has
overflow:hidden. Moving it outside the aside tag resolves this.
- Use nav-item style with heart icon (like Home)
- Add chevron button next to label for expand/collapse
- Show indented sub-items (Artists, Albums, Tracks) when expanded
- Collapsed sidebar shows heart icon with popover menu
- Restructure Favorites to use same layout as Playlists section
- Use section header with collapse button on the right
- Use NavigationItem components for sub-items
- Match padding, fonts, and hover states
- Change nested button to span with role="button" for favorites chevron
- Add localStorage persistence for collapse state of:
- Favorites section (expanded/collapsed)
- Playlists section (collapsed/expanded)
- Local Library section (collapsed/expanded)
- Folder collapse state intentionally not persisted
- Add chevron button to expand/collapse Favorites sub-items
- Show Artists, Tracks, Albums as nested items when expanded
- Respect user's tab order from favorites preferences
- Add popover menu for collapsed sidebar mode
- Use translations for all labels
Adds streamable field throughout the stack to detect tracks that have
been removed from Qobuz but still appear in playlists/favorites:
Backend (Rust):
- QueueTrack: add streamable field with default true
- PersistedQueueTrack: add streamable for session persistence
- radio.rs: log warning when non-streamable track is encountered
Frontend:
- BackendQueueTrack: add streamable? field
- QobuzTrack, Track, PlaylistTrack types: add streamable
- qobuzAdapters.ts: include streamable in track conversions
- trackActions.ts: all queue builders now include streamable
- +page.svelte: debug logging includes streamable field
This prepares the infrastructure for graying out unavailable tracks
in the UI (to be implemented when we can reproduce the issue).
Refs: User report about "Failed to fetch audio" for removed tracks
When the last track in the queue finishes playing, now properly:
- Stop playback via backend
- Clear current track (removes "now playing" indicator)
- Reset player bar state
FavoritesView Albums:
- Change from combined sort modes to sortBy + sortDirection
- Click option to select, click again to toggle direction
- Show arrow indicator on selected option
PlaylistDetailView:
- Remove separate Ascending/Descending options
- Integrate direction toggle into each sortable option
- Default and Custom Order remain direction-insensitive
- Reduce filter panel text sizes from 13px to 12px for consistency
- Update artist alpha-index to use same styling as albums/tracks tabs
- Replace boxed buttons with clean text-only alpha letters
- Outer view slides down on initial mount (200ms, 12px)
- Each tab content (albums, artists, tracks) slides up on tab change
- Uses {#key activeTab} to re-trigger transition on tab switch
- Simple transition without spinner fadeout logic
When switching tabs, the loading spinner now animates with slide-down
transition (200ms, 12px) using {#key activeTab} to re-trigger on tab change.
This completes the animation flow: spinner slides down -> fadeout -> content slides up
Wrap entire FavoritesView in ViewTransition with direction="down" so the
initial view (header + spinner) slides down on first load.
Complete flow:
0. Initial view appears with slide-down transition (200ms, 12px)
1. Loading spinner shows while syncing
2. When sync completes, spinner fades out (200ms)
3. Content appears with slide-up transition (200ms, 12px)
- Add ViewTransition wrapper to AlbumDetailView (200ms, 12px slide up)
- Add ViewTransition to FavoritesView content sections (tracks, albums, artists, playlists)
- Add spinner fadeout animation (200ms) before content transition in FavoritesView
- Both loading spinner and playlists loading spinner now fade out smoothly
This ensures favorites sync properly across all views (album detail,
continue listening, etc.) by using local persistence as the source
of truth for UI, with API calls for actual operations.
Backend (Rust):
- Add favorites_cache.rs module with SQLite tables:
- favorite_tracks (track_id INTEGER PRIMARY KEY)
- favorite_albums (album_id TEXT PRIMARY KEY)
- favorite_artists (artist_id INTEGER PRIMARY KEY)
- Add Tauri commands for cache operations:
- get_cached_favorite_* - read from local cache
- cache_favorite_* / uncache_favorite_* - add/remove single items
- sync_cached_favorite_* - bulk sync from API data
- clear_favorites_cache - for logout
Frontend:
- Rewrite favoritesStore.ts to use persistence:
- loadFavorites(): Load from cache first, then sync from API
- toggleFavorite(): API call first → update cache on success
- No optimistic updates - UI shows loading state until confirmed
- Rewrite albumFavoritesStore.ts with same pattern
- Create artistFavoritesStore.ts for artist favorites
- Update +page.svelte to load all favorites on login
- Update FavoritesView to sync cache when loading from API
Sync strategy:
- On login: Fetch from Qobuz API → sync to local cache
- On toggle: API call → on success → update local cache → notify UI
- FavoritesView reads from API and syncs local cache
- All other views read from local cache (instant UI)
Issues fixed:
- playbackService was bypassing the favorites store, causing UI desync
- Album favorites weren't loaded on login (only track favorites were)
- No visual feedback while favorite API call was in progress
Changes:
- Add toggling state tracking to favoritesStore and albumFavoritesStore
- Add isTrackToggling() and isAlbumToggling() exports
- Prevent double-toggle while API call is in progress
- Update TrackRow and AlbumCard to show pulse animation while toggling
- Update playbackService to use store's toggle (centralized API calls)
- Load both track and album favorites on login
- Remove problematic $effect that ran loadPlaybackPreferences() on every
render, causing a state update loop and visual flicker
- The function is already called in onMount, so the duplicate was redundant
- Add ViewTransition wrapper for consistent subtle animation (200ms fade+slide)
- Only navigate to search page after 3 characters typed
- Sync still happens immediately from 1st character
- Remove auto-navigate on focus (let user type first)
- Add ViewTransition component for reusable subtle animations
- Apply 200ms fade+slide transition to SearchView
cc
- Replace search button with actual text input in sidebar
- Add X button to clear search when text is present
- Sync sidebar input 1:1 with SearchView input (bidirectional)
- Typing in sidebar navigates to search page and triggers search
- Clearing sidebar input resets search page to standby state
- Add subscribeSearchQuery and setSearchQuery to searchState store
cc
Redesigned dropdown architecture:
- Menu uses position:fixed instead of absolute
- Menu rendered outside dropdown container (sibling, not child)
- Position calculated in pixels before menu opens
- Completely removes menu from document flow
This eliminates the momentary scroll flash that occurred when
dropdowns opened and temporarily expanded the page.
cc
- Header now outside .track-sections.virtualized (like LocalLibraryView)
- Use calc() height for virtualized section to give explicit dimensions
- Removes complex flex/absolute positioning that was breaking layout
- Add track list header with columns: #, Title, Album, Duration,
Quality, Favorite icon, Download icon
- Album column is hidden when grouping by album (redundant)
- Add getTrackAlbum and showAlbum props to VirtualizedTrackList
- Style matches PlaylistDetailView for consistency
Previously, streaming tracks could not be resumed after pause timeout
because current_audio_data was never set for streaming playback.
Changes:
- Store BufferedMediaSource in current_streaming_source for streaming tracks
- On resume, check if streaming source has complete data and use it
- Clear streaming source on Stop and when switching to non-streaming play
- Log buffered bytes when streaming not complete yet
This allows users to pause streaming tracks and resume them after the
download completes, instead of getting "no audio data available" error.
- Add z-index: 1000 to dropdown-container to appear above sidebar
- Change dropdown alignment from right: 0 to left: 0 for natural
left-to-right opening
- Affects all tabs: Albums, Tracks, Artists
- Fix Favorites Albums group dropdown appearing behind sidebar
(z-index 10 -> 100)
- Persist volume level to localStorage between sessions
- Allow changing volume when nothing is playing (volume
applies when playback starts)