- Group albums by category: Discography, EPs & Singles, Live Albums
- Exclude tributes and compilations (others) from display
- Each section has its own header that scrolls with content
- Updated footer: "To view compilations and other content, go to..."
- All content scrolls together (sections + footer)
- Add no-outer-scroll class when sidepanel mode is active
- Set up proper flex container hierarchy for full height:
- .favorites-view becomes flex column container
- .content gets flex: 1 with min-height: 0
- ViewTransition wrapper gets flex: 1 via :global selector
- Fix negative margins to match parent padding (was -32px, now -8px/-18px)
- Reduce artist column width to 240px (matches LocalLibrary)
- Fix album column right padding to 8px
- Add artist count header like LocalLibrary
- Remove sticky positioning from group headers (caused overlap)
- Restructure albums area with scroll wrapper containing both grid and footer
- Footer now scrolls with album grid (visible at bottom after scrolling)
- Match LocalLibrary CSS patterns for proper independent scrolling
- Fix toggle button to use control-btn icon-only style (matches Library)
- Add proper background/hover states to the toggle button
- Fix two-column layout for independent scrolling:
- Artist list scrolls independently from albums grid
- Added scroll optimization properties (overscroll-behavior, contain)
- Adjusted padding and margins for edge-to-edge layout
- Increased album card grid size to 180px (matches LocalLibrary)
Implements a two-column sidepanel view for Favorites > Artists:
- Left column shows artists grouped A-Z with sticky headers
- Right column shows selected artist's Discography albums
- Albums are filtered to show only studio albums (not EPs, Singles, Live)
- Proper AlbumCard with artwork, favorite button, and quality badge
- Footer link to navigate to full artist page for other album types
- Alpha index in toolbar for quick navigation
- Toggle button to switch between grid and browse views
- View mode persists to localStorage
Also exports categorizeAlbum from qobuzAdapters for reuse.
- Add Kurosaki light theme (cream/gold color palette)
- Add theme type filter button (dark/light/all) to cycle through themes
- Make Offline Library and Storage sections collapsible with descriptions
- Compact Account section with inline layout and subscription badge
- Navigation items now use i18n translation keys for reactive labels
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)
- Add fade/slide transitions when tracks are added or dismissed
- Show success state (checkmark + green highlight) before fade out
- Add track to local state without page reload
- Update sidebar counts via callbacks
- Disable buttons during adding animation
- Add extractAdaptiveArtists() for dynamic seed artist count based on playlist size
- Mix 60% top artists (coherence) + 40% random (discovery) for variety
- Filter duplicates by title+artist, not just track ID
- Catches same song from compilations, live albums, remasters, etc.
- Deduplicate within suggestion pool itself
- Add compact prop to QualityBadge for inline usage in track lists
- Compact mode shows simple text (24bit/96kHz or CD Quality)
- Use golden gradient for hires badges in compact mode
- Update ArtistDetailView to use QualityBadge compact
- Update FavoritesView to use QualityBadge compact
- Update SearchView to use QualityBadge compact
- Remove duplicate CSS and getQualityLabel functions
- LocalLibrary views keep custom format showing codec (FLAC 24/96)
- Use golden gradient (#fbbf24 to #b8860b) with dark text for hires badges
- Increase min-width to 90px for consistent sizing across all badges
- Rename track-row favorite button to avoid CSS conflict with header button
- Header favorite button now properly circular like radio and network buttons
1. Search: Re-clicking Search button in sidebar scrolls to top and focuses input
- Added triggerSearchFocus mechanism in searchState store
- SearchView subscribes to focus trigger for scroll + focus
2. ArtistDetailView: Added quality badge and favorite button to Popular Tracks
- Added getQualityLabel function with bit depth/sample rate
- Added quality-badge element with hires styling
- Added favorite button in track actions
3. FavoritesView: Fixed getAlbumQualityLabel to show detailed format
- Now shows "24bit/96kHz" instead of generic "Hi-Res"
- Change overflow-y to scroll to always reserve scrollbar space
- Fix sticky header top position to -24px to eliminate gap with window edge
- Add proper padding-top compensation when scrolled
- Add scrollbar-gutter: stable to prevent layout shift when content scrolls
- Create sticky header that activates on scroll with:
- Compact search input (bottom border only, muted accent, smaller height)
- Tabs and filters remain visible
- Title hidden when scrolled to save space
- Box-shadow for visual separation
- Increased menu min-width to 200px with width: max-content
to prevent word wrap on options
- Added padding: 4px 0 to menu (matches context menus)
- Changed box-shadow to match context menu (0 8px 24px)
- Reduced option padding to 10px 16px (was 12px 16px)
- Changed font-size to 13px (was 14px) to match context menus
- Added color transition on hover to match context menus
- Removed overflow/text-overflow from options (no wrap needed)
- Added smooth scroll CSS optimizations to artist list:
- webkit-overflow-scrolling: touch
- scroll-behavior: smooth
- overscroll-behavior: contain
- will-change: scroll-position
- contain: strict
- Added contain: layout style to artist cards for better rendering
- Added same optimizations to albums grid
- Memory: selectedArtistAlbums is a derived value that auto-updates
when switching artists, old arrays are garbage collected
- Note: VirtualizedArtistList exists for future use with large libraries
- Removed early return that blocked Various Artists clicks
(album header already protected via HTML condition)
- Added special case in selectedArtistAlbums: when user selects
"Various Artists", show all albums with that artist name
- Remove background from artist column, integrate with main background
- Add vertical border line separating artist list from albums view
- Wrap artists tab content in flex container for proper height
- Artist list now fills full available height (no gaps at top/bottom)
- Adjusted padding and margins for better visual integration
- Adjusted artist two-column layout height to account for NowPlayingBar
(was calc(100vh - 320px), now calc(100vh - 424px))
- Added 100ms delay before scrolling to artist card to ensure content
is fully rendered after tab switch
- Artist name in album header now clickable (except for Various Artists)
- Clicking artist navigates to Artists tab, scrolls to artist card,
and shows their albums
- Track artist links (when different from album artist) work the same
- Added IDs to artist cards for scroll targeting
- Navigation history updated so back button returns to album view
- Handle undefined/null values in cache stats with nullish coalescing
- Show "No cached artwork" when both counts are 0
- Use outlined style for Clear All button (consistent with others)
- Simplified description text
1. Play button fix: After resuming on cast device, update playerStore.isPlaying
so the UI shows the correct playing state immediately.
2. Disconnect handoff: When disconnecting from Chromecast/DLNA while playing:
- Captures current track and position from cast state
- Shows confirm dialog asking if user wants to continue locally
- If yes, starts local playback and seeks to saved position
- If no, just resets player state
This provides seamless transitions in both directions:
- Local → Cast: auto-resumes with position
- Cast → Local: asks user, then resumes with position
When user connects to a Chromecast/DLNA device while local playback is
active, the local stream was conflicting with the cast stream.
Now connectToDevice():
1. Captures current track info and position
2. Stops local playback completely
3. Establishes the cast connection
4. Automatically resumes the track on the cast device
5. Seeks to the saved position (if > 5 seconds)
This provides seamless handoff from local to cast playback.
- Added artwork thumbnails cache stats and clear button
- Shows legacy artwork files count if old cache exists
- Added "Clear All Caches" button that clears all cached data at once
- Danger button styling for destructive action
- metadata.rs now creates 500px thumbnails instead of copying full images
- Added cache management commands: library_get_cache_stats,
library_clear_artwork_cache, library_clear_thumbnails_cache
- Thumbnails stored in ~/.local/share/qbz/thumbnails/ (was ~/.cache/qbz/artwork)
- Reduces cache size from GB to MB for large libraries
Quality was hardcoded to UltraHiRes in backend. Now:
- Frontend passes quality from localStorage to play_track command
- Backend parses quality string to Quality enum
- All prefetch and stream URL calls use the user's preferred quality
- Add all_artists field to LocalAlbum for accurate artist matching
- Consolidate duplicate artists with case differences (e.g., "Alice in Chains" / "Alice In Chains")
- Fix album count calculation to avoid double-counting
- Show Various Artists albums under contributing artists
- Handle multi-artist names (e.g., "A, B, C") with union matching
- Add cleanup missing files feature in settings maintenance section
- Improve settings UI with side-by-side Maintenance/Danger Zone layout
- Add status indicator during cleanup operations
- Remove background/border from Qobuz badge, show icon at full 24px
- Keep background only for local badge (HardDrive icon)
- Add console logging when selecting artist to debug mismatch:
- Shows selected artist name (original and normalized)
- Shows which albums match
- Shows artist data from the artists array
- Add Local and Offline cache filters to the filter panel
- Icons: HardDrive for local, Qobuz logo for offline cache
- Remove CSS filter from Qobuz badge icon (display as-is with proper size)
- Fix alphabetical index scroll to work within the artist list container
- Add scroll reference binding to artist-list-scroll
- Fix group header ID (was duplicating prefix)
- Include multi-artist albums when selecting an artist:
- Exact match on album artist
- Partial match (artist name appears in album artist string)
- Split by common separators (,&feat.ft.featuring, etc.)
- Move alphabetical index from vertical (right) to horizontal (top)
- Replace full-width grid with two-column layout:
- Left: Single-column artist list with compact cards and scroll
- Right: Selected artist's albums in a grid
- Hide group dropdown and view mode toggle (kept in code for future use)
- Click artist to select and show their albums (instead of navigating to Qobuz)
- Defaults to alphabetical grouping and grid view
Albums now show a small badge indicating their source:
- HardDrive icon for local files (source='user')
- Qobuz logo for offline cached content (source='qobuz_download')
Badge appears in bottom-right corner of album artwork.
Flatpak uses the <releases> section in metainfo.xml to display the
application version. Added a placeholder release entry and updated
the workflow to inject the actual version and date during build.
When deleting a cached track (individually, via clear all, or via LRU
eviction), the audio file was deleted but cover.jpg remained orphaned.
Now the system removes the entire album folder if no audio files remain,
and recursively removes the artist folder if it also becomes empty.
- Add getTrackOfflineCacheStatus prop to HomeView
- Pass downloadStatus and downloadProgress to TrackRow
- Use reactive cache status function that depends on downloadStateVersion
Tracks in Continue Listening now correctly show their cached state.
Include downloadStateVersion in the each loop key to force re-render
when offline cache state changes, fixing tracks showing gray icons
even when cached.