Add ListenBrainz scrobbling support with:
- Client module for ListenBrainz API
- Token-based authentication (user provides token from listenbrainz.org)
- Now-playing submission support
- Scrobble submission with MusicBrainz enrichment
- SQLite-based offline queue with TTL
- Credential persistence across sessions
- Settings UI for connection management
API integration uses Stage 1 MusicBrainz data to enrich scrobbles with
recording MBID, release MBID, and artist MBIDs when available.
cc
- Add MusicBrainz client with rate limiting (1 req/sec)
- Add SQLite cache with TTL for recordings, artists, releases
- Add Tauri commands for entity resolution
- Support ISRC, UPC/barcode, and name-based lookups
- Include artist relationship extraction (Stage 3 prep)
cc
- Use custom info SVG icon without circle outline
- All tracks collapsed by default (removed auto-expand)
- Add description field to Album model for editorial reviews
- Implement Review tab with album description content
- Tab switches between Credits (track list) and Review (description)
- Review tab disabled when no description available
- Add get_album_credits Tauri command with TrackCredits, AlbumCredits,
and AlbumInfo structs for consolidated album/track credits view
- Create AlbumCreditsModal component with two-column layout:
album info (artwork, metadata, quality badges) and collapsible
track list with performers and copyright
- Add Info button to AlbumDetailView header actions
- Integrate modal in +page.svelte with track play support
Phase 2 of track-info feature:
- Add performers, composer, copyright fields to Track model
- Add get_track_info command with parsed performers
- Create TrackInfoModal component to display track details and credits
- Add "Track info" option to TrackMenu context menu
cc
- Hide "Copy to My Library" button after playlist has been copied
- Persist copied playlist IDs in localStorage for subsequent visits
- Add attribution note to description: "Originally curated by X on Qobuz"
When viewing a playlist not owned by the current user, a new "Copy to
My Library" button appears between the favorite and context menu buttons.
Clicking it creates a copy of the playlist with all its tracks in the
user's library.
- Add get_current_user_id command to check playlist ownership
- Add subscribe_playlist command to copy playlist with all tracks
- Add ListPlus button in PlaylistDetailView for non-owned playlists
- Button only shows for playlists owned by others (e.g., Qobuz curated)
The index was being created during schema execution, but for existing
databases the column doesn't exist until the migration runs. Now the
index is created after all migrations have completed.
- Add UPC field to Album models (Rust and TypeScript)
- Enhance share_album_songlink command to use iTunes lookup:
- First tries UPC lookup via iTunes API
- Falls back to Odesli API with UPC
- Then tries album title/artist search on iTunes
- Generates direct album.link/i/{id} URLs like tracks do
- Update frontend to use the backend command instead of
generating non-functional URL-based links
This mirrors the track sharing approach that uses ISRC/iTunes
lookup to generate working song.link URLs.
Add tray icon configuration options:
- Enable/disable tray icon (requires restart)
- Minimize to tray: hide window when clicking minimize
- Close to tray: hide window instead of quitting
Rust backend: TraySettingsState with SQLite persistence
Frontend: SettingsView tray subsection with toggles
Related to #15 (title bar settings)
Add 50ms delay after stopping the previous playback engine to allow
the rodio Sink to fully release its reference to the OutputStreamHandle.
This prevents "resource busy" errors when rapidly switching tracks,
especially when selecting from search while radio is playing.
Ensure album_id and artist_id are passed through all queue track creation paths:
- trackActions.ts build functions (Qobuz, album, playlist, local tracks)
- Session persistence (both frontend and Rust backend)
- buildAlbumQueueTrack wrapper function
- Session restoration
Radio improvements:
- Increase initial tracks from 15 to 50
- Add refill_radio_queue command (generates 20 more when queue low)
- Add get_queue_remaining command to check queue size
- Add create_infinite_radio command for "Keep Playing" feature
- Add InfiniteRadio autoplay mode based on last 5 tracks
Backend infrastructure ready for:
- Auto-refill when queue drops below 10 tracks
- Infinite playback when normal queue ends
Add album_id and artist_id fields to QueueTrack struct so that
radio tracks have proper navigation metadata. This enables
clicking artist/album in the main player to navigate correctly
when playing tracks from artist or track radio.
Changes:
- Add album_id/artist_id to Rust QueueTrack struct
- Update track_to_queue_track to extract IDs from Qobuz Track
- Add fields to frontend BackendQueueTrack interface
- Pass IDs in ArtistDetailView radio playback
- Pass IDs in playQueueTrack to playTrack service
- Backend now supports limit=0 meaning "no limit"
- Frontend passes limit=0 to fetch all tracks
- Search now works across entire library regardless of size
- Virtualization handles rendering efficiently
Set GTK_APPLICATION_ID for proper WM_CLASS and add desktop template
with StartupWMClass for correct process identification on Linux.
Also add window label for Tauri window configuration.
- Add canonical_name column to artist_images table (migration)
- Store canonical names from Qobuz when fetching artist images
- Display canonical names (properly capitalized) in artist views
- Group and sort artists by canonical name when available
- Source priority: Qobuz -> Discogs -> Metadata
This ensures "Alice in chains" displays as "Alice In Chains" based on
Qobuz's authoritative naming.
- Fix asset protocol scope for thumbnails in tauri.conf.json
- Homologate artist view controls to match album controls pattern
- Make artist grouping optional with dropdown (Off, A-Z)
- Change grid/list to single toggle button for artists
- Allow 3 lines for artist names in grid view
- Fix album card width to prevent title overflow
- Add throttling to artist image fetch to avoid rate limiting
Extend SQL-based filtering to artists and search:
- Add get_artists_with_filter() method that filters source and
network folders directly in SQL
- Replace library_get_artists N+1 loop that was calling
db.search(artist.name, 10000) for EACH artist
- Add search_with_filter() method for optimized track search
- Replace library_search post-query filtering with SQL predicates
These changes eliminate thousands of redundant database queries
when browsing large libraries with network folders or Qobuz downloads.
Part of #13 - LocalLibrary performance optimization
- Add Comment field to all .desktop files for app launcher descriptions
- Change category from Music to AudioVideo for better categorization
- Add Keywords for improved searchability (audiophile, hifi, streaming)
- Enhance metainfo.xml with detailed feature list and links
- Configure AppImage bundle settings in tauri.conf.json
- Update workflows to generate improved .desktop files
AppImage now includes embedded icon via Tauri icon configuration.
App will now appear in Multimedia category with proper description in all Linux app launchers.
Artist radio: Generate 20 tracks, find first from seed artist (distance 0), move to position 0
Track radio: Generate 20 tracks, find seed track itself, move to position 0
This ensures radio always starts with a relevant track
- Increase weight for seed artist tracks from 3 to 6 (doubles probability)
- Reduce similar artists pool: 12 -> 8 artists, 40 -> 30 tracks per artist
- Force first track in artist radio to always be from the seed artist
- Force first track in track radio to always be the seed track itself
- Add Clone impl for QobuzClient to enable thread-safe usage
- Use spawn_blocking for RadioDb operations to avoid Send trait issues
- Fix track.hires type mismatch (bool vs Option<bool>)
- Compilation now succeeds with no errors
- Update version to 1.1.6-rc for release candidate testing
- Add Linux ARM64/aarch64 build workflow
- Uses ubuntu-24.04-arm GitHub runner (free for public repos)
- Builds .deb, .rpm, .AppImage, and AUR tarball for ARM64
- Workflow isolated with continue-on-error to not block x86_64 builds
credentials/mod.rs:
- Remove email from log messages (privacy/security best practice)
- Logs still indicate success without exposing PII
tests/qobuz_live_smoke.rs:
- Add ignored live smoke test for Qobuz API
- Requires explicit opt-in via QBZ_RUN_QOBUZ_LIVE_TESTS=1
- Uses local credentials from keyring (no hardcoded secrets)
- Basic test: login + get_favorites endpoint
- Safe for repository (no credentials exposed)
Problem: Pending playlists created offline used auto-increment track IDs,
which change on library re-scans, causing FOREIGN KEY errors and data loss.
Solution: Store file_path instead of IDs for local tracks. Paths are stable
identifiers that only change if files are moved/renamed.
Backend changes:
- Add local_track_paths TEXT column to pending_playlist_sync table
- Update PendingPlaylist struct to include local_track_paths field
- Modify create/add functions to accept paths instead of IDs
- Add get_track_by_path command to resolve paths to current IDs
- Keep local_track_ids for backward compatibility
Frontend changes:
- Update PendingPlaylist interface with localTrackPaths field
- Fetch file paths from track IDs before creating pending playlists
- Implement path-to-ID resolution in sync function
- Gracefully handle missing files (log warning and skip)
- Backward compatible with old ID-based pending playlists
Testing:
- Simulated playlist creation with 3 local tracks (IDs 197-199)
- Simulated re-scan by changing IDs to 1500-1502
- Verified path resolution finds tracks with new IDs correctly
- Verified missing files are handled gracefully
This makes the offline playlist system robust and production-ready.
Backend:
- Add add_tracks_to_pending_playlist method to OfflineStore
- Reads current track lists, appends new tracks, updates DB
- Add corresponding Tauri command and register in lib.rs
Frontend:
- Detect pending playlists by negative ID in handleAddToPlaylist
- Call add_tracks_to_pending_playlist for pending playlists
- Properly separate Qobuz and local tracks before adding
- Fixes error trying to call playlist_add_local_track with negative ID