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