- Add 200ms delay between API calls to avoid 429 Too Many Requests
- Reduce chunk size from 50 to 20 (Tidal API limit for filter[id])
- Improve error messages to include response body for debugging
Cherry-pick and integrate playlist import from playlist-import-impl branch:
- Add playlist_import Rust module with providers (Spotify, Apple, Tidal, Deezer)
- Add track matching algorithm (ISRC first, then fuzzy matching)
- Add PlaylistImportModal.svelte with progress log UI
- Wire up import button in Sidebar
- Add modal state management in uiStore
- Register Tauri commands for preview and execute
Original implementation by Codex, integrated manually due to branch divergence.
- Validate device has supported output configs before using
- Fall back to default if saved device is invalid
- Reduces polling frequency to 10 seconds
UI:
- Cover art now has 2px margin vs song-card edges
- Quality badges have 2px margin on right side
Audio:
- Track consecutive sink failures to detect broken streams
- Stop playback gracefully after 3 failures
- Add AudioOutputBadges component showing DAC/Exclusive mode status
- Badges are reactive to actual player state, not just settings
- Add device tooltip on hover showing current output device
- Create audioDeviceNames helper for ALSA name to friendly name mapping
- Use pretty names in Settings dropdown while storing raw names
- Add get_audio_output_status backend command
- Store current device name in player SharedState
- Player::new() now accepts optional device name
- Uses OutputStream::try_from_device() for specific device
- AppState reads saved device from audio settings on startup
- SettingsView loads/saves audio settings to backend
- Changes take effect on app restart
Backend:
- Create config/audio_settings.rs with SQLite persistence
- Store output_device, exclusive_mode, dac_passthrough, sample_rate
- Add Tauri commands: get_audio_settings, set_audio_*
- Register state and commands in lib.rs
Note: Player modification to USE these settings is pending.
See HANDOFF.md for implementation details.
Backend (Rust):
- Create session_store module with SQLite database
- Store queue tracks, current index, position, volume, shuffle/repeat
- Add Tauri commands: save_session_state, load_session_state, etc.
- Use WAL mode for better concurrent access
Frontend (TypeScript):
- Create sessionService.ts with save/load functions
- Restore session after login (queue, track info, volume, modes)
- Save session on beforeunload and visibility change
- Clear session on logout
- Debounced position saving during playback
Users can now close the app and resume where they left off.
- Disable native window decorations in tauri.conf.json
- Create TitleBar component with minimize/maximize/close controls
- Add drag region for window movement
- Integrate TitleBar into main layout and LoginView
- Adjust content heights to account for 36px title bar
- Prepare for future mini player implementation
- Use Tauri opener plugin for all external links in About modal
- Convert anchor tags to buttons with openUrl() for proper browser opening
- Add GTK_USE_PORTAL=1 for native file dialogs on Linux (KDE Plasma)
- Add Tux.svg and mexico-flag.svg for About modal signature
- Create api_cache module with TTL-based expiration (24h default)
- Cache get_album, get_track, and get_artist responses
- Check cache before API calls, store results after fetch
- Reduces API calls and speeds up Recently Played loading
Instead of relying solely on pre-computed scores (trained on login),
now merge the 4 most recent plays with scored items. This ensures
newly played tracks appear at the top without waiting for retraining.
- Add album/getFeatured endpoint to API
- Create get_featured_albums Tauri command
- Add New Releases and Press Awards sections to HomeView
- Featured albums load first for faster perceived performance
- Add lyricsStore for frontend state management with LRC parsing
- Wire LyricsSidebar to main layout with toggle button in NowPlayingBar
- Fix Rust compile errors (add Clone to LrclibItem, remove unused import)
- Add lyrics active line tracking with binary search
- Auto-start/stop lyrics updates based on playback state
- Backend now emits playback:state events every 250ms when state changes
- PlayerStore uses listen() instead of setInterval for real-time updates
- Added Vitest testing framework with mocked Tauri APIs
- Added 53 unit tests for playerStore and queueStore
- 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
- 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
- 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
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
- 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
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