- Create uiStore.ts for overlay and modal visibility states
- Centralize queue panel, fullscreen, focus mode, cast picker states
- Add playlist modal state management with mode and track IDs
- Implement handleEscapeKey for prioritized overlay closing
- Update +page.svelte to use store subscription and functions
- Create toastStore.ts with centralized toast state management
- Add auto-hide functionality with configurable durations per type
- Update +page.svelte to use store subscription instead of local state
- Maintains existing toast behavior with improved separation of concerns
- Add Seek command to AudioCommand enum
- Store audio data in audio thread for re-seeking
- Use rodio's skip_duration to seek to desired position
- Preserve play/pause state after seeking
- Update playback timer to account for seek position
- Create AddToPlaylistModal component for selecting playlists
- Update PlaylistDetailView to merge local and Qobuz tracks
- Add visual indicator (HardDrive icon) for local tracks
- Show notice that local tracks are device-specific
- Add "Add to Playlist" option in Local Library track menu
- Connect local track play handlers for playlists
- Hide download button for local tracks in playlists
- Update CastPicker to show protocol tabs with device counts
- Register DLNA and AirPlay state and commands in lib.rs
- Add Deserialize derive to DlnaMetadata and AirPlayMetadata
- Fix capabilities file (remove unavailable fs permissions)
- Remove unused IpAddr import in media_server.rs
- Restore Last.fm session on app startup (not just when visiting Settings)
- Fix LocalLibrary artwork: use asset://localhost/ instead of file://
- Add fs:allow-read capability for asset protocol
- Enable assetProtocol in tauri.conf.json
- Add download functionality to FavoritesView tracks tab
- Add search input for each tab (tracks, albums, artists)
- Filter displayed items based on search query
- Show "no results" message when search has no matches
- Display filtered/total counts in toolbar
- Use buildDisplayTrack helper for cleaner code
- Add downloadStateVersion prop to trigger reactivity in child components
- Use version in each block keys to force re-evaluation when state changes
- Ensures download status persists whether track was downloaded from
album view or playlist view
- DownloadButton component with status indicators
- TrackRow now shows download button on hover
- downloadState store for managing download states
- Tauri event listeners for download progress
- Integration with AlbumDetailView
- SQLite-based index for tracking cached tracks
- Disk-based storage for audio files (~/.cache/qbz-nix/audio/)
- Download progress events via Tauri events
- LRU eviction with configurable cache size limit
- Integration with play_track for automatic local playback
- Open cache folder command for system file manager
1. Credential storage fallback - when keyring (KWallet) unavailable,
falls back to obfuscated file in config directory
2. Heart icon always visible - shows outlined when not favorite,
filled when favorite, with hover states
3. Player favorite state - checks favorite status when track plays
and updates the heart icon in NowPlayingBar
4. Song.link ISRC lookup - uses Spotify/Deezer search URLs with ISRC
since Qobuz isn't directly supported by Odesli
Odesli API doesn't support direct ISRC lookup. Changed to use
Qobuz track URL (https://open.qobuz.com/track/{id}) which Odesli
can properly resolve to song.link.
- Support both LAST_FM_* and LASTFM_* naming conventions
- Match .env.example variable names (LAST_FM_API_KEY, LAST_FM_API_SHARED_SECRET)
- Add detailed logging for auth.getSession to help troubleshoot failures
- Include raw response in error messages for debugging
- Add Cast icon from lucide-svelte
- Add onCast callback prop to both components
- Show "Casting coming soon" toast when clicked
- Backend cast module is disabled pending fixes
- Add isFavorite prop to TrackRow component
- Show filled heart icon for favorited tracks
- Fetch favorite tracks on mount in AlbumDetailView
- Fetch favorite tracks on mount in PlaylistDetailView
- Always show heart in FavoritesView tracks (already favorited)
- Create QualityBadge component with tier detection
- CD badge shows disc icon for 16-bit lossless
- HR badge for 24-bit Hi-Res audio
- MAX badge for 24-bit >96kHz Hi-Res+
- Update NowPlayingBar and FullScreenNowPlaying to use badges
- Pass bitDepth and samplingRate props through components
- Remove blue circle from fullscreen play button (match other controls)
- Add icons to TrackMenu items (Play, ListPlus, Heart, Share2, Disc3, User)
- Check album favorite status on mount in AlbumDetailView
- Improve control button styling with flexbox alignment
Import Chromecast, AirPlay, and DLNA casting scaffolding from separate
branches. The module is currently disabled due to:
- Thread safety issues: rust_cast uses Rc internally (not Send)
- API compatibility: rupnp methods have changed
The scaffolding includes:
- Chromecast: mDNS discovery, device connection, media server
- AirPlay: Discovery scaffolding (RAOP playback pending)
- DLNA/UPnP: SSDP discovery scaffolding (playback pending)
To enable casting in the future:
1. Fix rust_cast thread safety (or use spawn_blocking)
2. Update rupnp API calls for current version
3. Add Deserialize derive to metadata types
4. Uncomment cast module in lib.rs
5. Wire up commands in invoke_handler
- Add tauri-plugin-clipboard-manager for reliable clipboard access
- Replace browser clipboard API with Tauri plugin
- Improve error messages in share functions to be more specific
- Add loading indicator for Song.link fetch
- Remove blue circle from play/pause, now uses same style as other controls
- Add Popular Tracks section to ArtistDetailView with Play All
- Search tracks by artist name and filter results
- Connect onTrackPlay to play tracks from artist view
- Move Favorites to its own section outside Playlists
- Add collapsible chevron to Playlists section
- Fix play/pause button with explicit fill and color props
- Add onArtistClick prop to SearchView and FavoritesView
- Convert artist cards to buttons with click handlers
- Wire up handleArtistClick in main page
- Fix play button by removing fill props and using CSS to style svg
- Add custom dropdown arrow and dark theme styling for select element
- Add #[serde(default)] to many model fields to handle missing data
- Add Default implementations for Artist, PlaylistOwner, ImageSet
- Add explicit flex-direction: row and width: 100% to TrackRow
- Add missing .track-list and .tracks container styles in AlbumDetailView
- Add gap: 16px to table header to match TrackRow alignment
- Add box-sizing: border-box for consistent sizing
Playlist & Favorites:
- Fix playlist loading error (missing field `id` in JSON parsing)
- Add serde defaults for Track and PlaylistOwner optional fields
- Fix track favorites toggle to actually call backend API
- Add "Add to playlist" button on track rows in album view
Sidebar UI improvements:
- Increase sidebar width to 280px
- Reduce text size and margins throughout
- Normalize all icons to 14px (matching + and refresh buttons)
- Add text overflow with ellipsis and tooltip for playlist names
User card:
- Make user card more compact (smaller avatar, fonts, icons)
- Add collapsible toggle - when collapsed shows mini QBZ logo + username
Queue & Playback:
- Fix next_track spam when queue has only one song (queueEnded flag)
Other fixes:
- Fix TrackRow prop types (remove invalid hires, bitDepth, samplingRate props)
- Fix FavoritesView and PlaylistDetailView component prop types
- Add diagnostic logging to credential persistence
- Add keyring crate for secure credential storage (KWallet/GNOME Keyring)
- Create credentials module with save/load/clear functions
- Add auto_login command that uses saved credentials
- Add "Remember me" checkbox to login form (default: checked)
- Auto-login on app startup if credentials are saved
- Clear credentials from keyring on logout
- Show dynamic status during initialization (connecting, checking, logging in)
- Add Discogs API client for fetching missing album artwork
- Add UI button in Local Library settings to fetch missing artwork
- Load .env file at runtime using dotenvy for development
- Fix Last.fm to check runtime env vars as fallback to compile-time
- Use XDG Portal for dialog plugin (cross-desktop compatibility)
- Update README and .env.example with Discogs configuration
- Extract embedded artwork from audio files during scan
- Save extracted artwork to cache directory (~/.cache/qbz/artwork)
- Fallback to folder artwork (cover.jpg, folder.jpg, front.jpg, etc.)
- Add get_artwork_cache_dir helper function
- Artwork now displays in album cards and detail views
- Move Local Library section below Playlists in sidebar (Qobuz is main B.U.)
- Replace text input with native XDG folder picker dialog
- Add bit_depth and sample_rate to LocalAlbum model
- Display rich audio specs in album detail view (format, bit depth, sample rate, channels)
- Show format quality badge on album cards (e.g. "FLAC 24/96")
- Add Hi-Res visual indicator for 24-bit or >48kHz content
- Create PlaylistModal component for create/edit/add tracks
- Add "New Playlist" button to Sidebar
- Add refresh button for playlists in Sidebar
- Wire playlist creation modal into main page
- Support creating playlists and adding tracks to existing playlists
- Add create_playlist API method and command
- Add delete_playlist API method and command
- Add add_tracks_to_playlist API method and command
- Add remove_tracks_from_playlist API method and command
- Add update_playlist API method and command
- Register all new commands in invoke_handler
- Create LocalLibraryView component with albums, artists, tracks tabs
- Add folder management (add/remove library folders)
- Add library scan progress indicator
- Add album detail view with track list
- Integrate local track playback with queue system
- Track local vs Qobuz tracks for proper skip/advance behavior
- Update Sidebar with simplified Library navigation
- Add support for build-time credentials via LASTFM_API_KEY/LASTFM_API_SECRET env vars
- Add lastfm_has_embedded_credentials command to check for embedded keys
- Add lastfm_open_auth_url command to open browser via native opener
- Simplify UI flow: if embedded credentials exist, just click Connect
- Add cancel button during authorization flow
- Clean up UI when waiting for user to authorize in browser
- Add album id to AlbumDetail interface and conversion
- Add artist id to enable artist navigation from album view
- Wire heart button in AlbumDetailView to call add_favorite/remove_favorite
- Fix artist click to use actual artist id instead of hardcoded 0
Change audio thread from blocking recv to recv_timeout, periodically
checking if the sink is empty. When playback finishes naturally, set
is_playing to false so the frontend can trigger auto-advance to next.
- Add offset parameter to search_albums, search_tracks, search_artists
- Backend now properly passes offset to Qobuz API
- Fixes duplicate results when loading more search results
- Update MPRIS position every time get_playback_state is polled (~500ms)
- Update MPRIS position when seeking
- Add Default derive to PlaybackState for error fallback
- System media controls now show accurate playback progress
- Add Load More buttons for albums, tracks, and artists tabs
- Track pagination state with hasMore* derived values
- Accumulate results as users load more pages
- Style Load More button with hover and disabled states
- Create share module with SongLinkClient for Odesli API
- Support for tracks via ISRC and albums via UPC
- In-memory cache with 1-hour TTL
- Commands: share_track_songlink, share_album_songlink
- Qobuz URL generators for tracks, albums, artists
- Proper error handling for missing identifiers
Settings:
- Add language dropdown (Auto, English, Español, etc.) with persistence
Last.fm:
- Create lastfm module with API client for authentication and scrobbling
- Add commands for auth flow, now playing, and scrobbling
- Full OAuth-style authentication with token exchange
- UI for entering API credentials and authorizing
- Automatic scrobbling after 50% of track or 4 minutes
- Update 'now playing' when tracks start
- Session persistence in localStorage
- Add get_audio_devices command to list available output devices
- Show real audio devices in Settings dropdown
- Persist streaming quality preference in localStorage
- Persist 'prefer highest available' setting
- Account section now shows real user name and subscription
- Logout button connected to logout handler
- Theme switching with persistence (Dark/Light/OLED Black/Warm)
- Theme loads on app startup from localStorage
- Cache stats display (size, max, track count)
- Clear cache functionality with disabled state
- Add notify-rust dependency for desktop notifications
- Create notification.rs commands module with show_track_notification and show_notification
- Register notification commands in lib.rs
- Wire up notifications in frontend for all playback scenarios:
- Search results playback
- Album track playback
- Playlist track playback
- Queue navigation and auto-advance
- Create FavoritesView with tabs for tracks, albums, and artists
- Fetch favorites from backend API
- Support Play All and Add to Queue for tracks
- Display albums in grid with AlbumCard
- Display artists with circular avatars
- Wire up in +page.svelte
- Create PlaylistDetailView with header, artwork, and track list
- Fetch playlist data from backend on mount
- Support Play All, Shuffle, and Add to Queue actions
- Wire up track play handler in +page.svelte
- Display playlist owner, track count, and total duration
Backend:
- Add playlist.rs commands: get_user_playlists, get_playlist, search_playlists
- Add favorites.rs commands: get_favorites, add_favorite, remove_favorite
- Add API client methods: search_playlists, add_favorite, remove_favorite
Frontend:
- Remove Radio and New from sidebar (no API support)
- Sidebar now loads and displays user playlists dynamically
- Add playlist and favorites views (placeholder for now)
- Pass selectedPlaylistId and onPlaylistSelect to Sidebar
- Replace all Tauri icons with QBZ logo variants
- Update LoginView splash screen to use logo.png
- Add logo.png to static folder for web use
- Update favicon.png with QBZ icon
- Replaced 7 separate Mutex fields with single InternalState struct
- Prevents deadlock when multiple locks acquired in different orders
- Same pattern as AudioCache fix
- Fixed borrow checker issue in add_track method
- Add AudioCache module with LRU eviction (500MB default)
- Cache downloaded tracks in memory
- Prefetch next track in queue while current track plays
- Play from cache instantly when track is already cached
- Background prefetch task runs without blocking playback
- Add onPlayAll, onShuffleAll, onAddToQueue props to AlbumDetailView
- Implement handlePlayAllAlbum to play album from first track
- Implement handleShuffleAlbum to enable shuffle and play
- Implement handleAddAlbumToQueue to append album to existing queue
- Add QueueManager in Rust backend with shuffle, repeat modes, and history
- Create queue Tauri commands (add, remove, next, previous, play_index, etc.)
- Wire Next/Previous buttons to backend queue
- Auto-advance to next track when current track ends
- Sync queue state with frontend QueuePanel
- Wire shuffle and repeat toggles to backend
- Add queue population when playing from album detail view
Sidebar:
- Remove placeholder playlists (My Playlist 1/2/3)
- Keep only Favorites under Playlists section
NowPlayingBar:
- Remove hardcoded placeholder values (Irreplaceable, etc.)
- Add empty state when no track is playing
- Fix remaining time to be dynamic (duration - currentTime)
- Add artwork placeholder when image is missing
- Handle quality dots visibility properly
Backend (Rust):
- Add souvlaki crate for MPRIS support on Linux
- Create media_controls module with MediaControlsManager
- Track metadata (title, artist, album, duration, cover)
- Playback state (playing/paused/stopped)
- Wire pause/resume/stop commands to update MPRIS
- Add set_media_metadata Tauri command
Frontend (Svelte):
- Call set_media_metadata when track starts playing
- Send track info to MPRIS for system integration
Now shows track info in system media controls and
supports media key events (logged for now).
Backend (Rust):
- Add position tracking based on elapsed wall clock time
- SharedState now tracks playback start time and position at start
- get_state() returns real-time position instead of stale value
- Position automatically pauses/resumes with playback
Frontend (Svelte):
- Replace dumb setInterval timer with backend polling (500ms)
- Poll get_playback_state to sync position from Rust
- Add onSkipBack/onSkipForward handlers to NowPlayingBar
- Add onSkipBack/onSkipForward handlers to FullScreenNowPlaying
- Wire skip buttons to handlers (restart if >3s, otherwise previous)
- Add keyboard shortcuts: Ctrl+Left/Right for skip
The regex pattern for extracting info/extras from the Qobuz bundle
was incorrect. The pattern now:
- Dynamically builds timezone alternatives from extracted seeds
- Capitalizes timezone names to match bundle format (e.g., "Berlin")
- Correctly parses the name:"\w+/Timezone" format
- Converts back to lowercase for seed lookup
This enables successful FLAC streaming from Qobuz.
- Add track ID and duration to Track interface
- Update convertQobuzAlbum to include track metadata
- Add onTrackPlay prop to AlbumDetailView
- Create handleAlbumTrackPlay handler
- Improve error handling in playback (don't set isPlaying on error)