- 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
- Deterministic track selection with artist spacing
- SQLite-backed session persistence
- Pool construction from Qobuz public API
- 100% test coverage on core logic
- Add shouldHidePlaylistFeatures() helper function
- Hide 'Add to playlist' button in TrackMenu when offline
- Add informational banner in Local Library view
- Users can still play tracks in offline mode
- Implements Option 2 from OFFLINE_PLAYLIST_FEATURE_OPTIONS.md
Problem: When creating a playlist in offline mode, the modal would
create it in Qobuz instead of as a pending playlist because it read
the offline state only once at modal open time.
Solution: Subscribe to offline state changes reactively using $effect
and update a reactive offlineMode variable that the modal uses instead
of calling isOffline() directly.
This ensures the modal always has the current offline state, even if
the user toggles offline mode while the modal is open.
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.
- Populate localTrackCounts map with pending playlist local track counts
- Set tracks_count to only Qobuz tracks for correct filtering logic
- Store pending playlists metadata in pendingPlaylistsMap
- Fixes all_local vs some_local detection for pending playlists
- Playlists with only local tracks now correctly show as fully available offline
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
- Add detailed logging for each local track being added
- Catch and re-throw errors when adding local tracks fails
- Prevent marking playlist as synced if any operation fails
- Show error details in toast notifications
- Ensures playlists will retry sync on next online transition if failed
- Fixes playlists being marked synced without local tracks added
- Detect offline mode in loadUserPlaylists
- Load pending playlists instead of calling get_user_playlists API
- Convert PendingPlaylist to Playlist format for UI compatibility
- Use negative IDs to distinguish pending playlists
- Add to reload playlists when offline status changes
- Fixes playlists not appearing when offline real (cable disconnected)
- Update createPendingPlaylist to accept localTrackIds parameter
- Modify PlaylistModal to separate Qobuz and local tracks in offline mode
- Implement syncPendingPlaylists function to sync playlists when back online
- Register sync callback in bootstrap to auto-sync on online transition
- Properly adds both Qobuz tracks and local tracks to synced playlists
- Shows toast notifications for successful/failed syncs
- Add local_track_ids column to pending_playlist_sync table
- Update PendingPlaylist struct to include localTrackIds field
- Modify create_pending_playlist to accept and store local track IDs
- Update get_pending_playlists to read local track IDs
- Allows creating playlists with both Qobuz and local tracks in offline mode
- Detect offline mode in handleCreate and handleCreateAndAdd
- Use createPendingPlaylist() to queue playlist creation for sync
- Show toast notification that playlist will sync when back online
- Prevent creating playlists with local tracks in offline mode
- Fixes network error when trying to create playlist while offline
- Use library_get_folders instead of library_get_folders_with_metadata when offline
- Prevents is_network_path blocking DB when cable disconnected
- Mark network folders as inaccessible, local folders as accessible
- Allows viewing folder list in settings panel while offline
- Add debug logging to loadArtists and loadTracks
- Only execute loadFolders when not offline
- Prevents is_network_path blocking DB when cable disconnected
- Make onMount await loadLibraryData before starting loadFolders
- Add 5-second timeout to loadFolders using Promise.race
- Make folder loading non-critical to prevent UI blocking
- Prevents database lock deadlock when is_network_path blocks offline
Added detailed console logs to track execution flow:
- shouldExcludeNetworkFolders: logs offline state, reason, settings
- loadLibraryData: logs each step including invoke calls and responses
- Error handling with try/catch in shouldExcludeNetworkFolders
This will help identify where the infinite spinner occurs in offline mode.
User reports DB may be blocking in offline - investigating.
- Changed search to work for all tabs (Albums, Artists, Tracks) in sticky nav
- Search input dynamically changes placeholder based on active tab
- Removed duplicate search inputs from Albums and Artists sections
- Fixed gap between page top and sticky nav by removing padding-top from .library-view
- Search opens/closes with toggle, clears on close, focuses on open
- Each tab maintains its own search state (albumSearch, artistSearch, trackSearch)
Search behavior:
- Albums tab: searches albums and artists
- Artists tab: searches artists only
- Tracks tab: searches tracks, albums, artists with debounce
UI now clean with single search location in sticky nav for all tabs.
- Fixed gap between page top and sticky nav by adding negative margin-top
Changed margin from '0 -24px 16px' to '-24px -24px 16px'
- Removed tab counters (Albums (12), Artists (5), Tracks (252)) as they
are redundant with the counters already displayed in page content
Sticky nav now sits flush at the top with no gap when scrolling.
- Changed tab buttons to text links with underline style (matching ArtistDetailView)
- Made navigation bar sticky with proper z-index and negative margin
- Integrated track search into sticky nav bar (closed by default)
- Search opens with toggle button, closes with X or Escape key
- Removed duplicate search input from tracks section
- Added slideInFromRight animation for search appearance
Navigation now:
- Sticks to top when scrolling (position: sticky; top: 0; z-index: 4)
- Uses minimal text links instead of button-style tabs
- Active tab shown with accent-colored underline
- Track search accessible from any tab via sticky nav
Changed the fallback case (Tracks view without search) to calculate from
filtered albums instead of using stats.track_count.
Now when both filters are active (network folders + downloads hidden):
- Shows 0 tracks correctly instead of total count
- Counter always reflects actual filtered content across all tabs
Removed the length check conditions for albums and artists views so the
counter always calculates from the filtered data, even when the result is 0.
Now when network folders or downloads are hidden:
- Albums view: Shows 0 tracks if no albums are visible
- Artists view: Shows 0 tracks if no artists are visible
- Tracks view: Shows actual filtered track count
This ensures the counter is always reactive to filter changes.
Changed currentView to activeTab in the reactive counter logic. The variable
currentView doesn't exist - the correct variable is activeTab which tracks
the current selected tab (albums/artists/tracks).
This was causing LocalLibraryView to fail to mount/render.
Changed from $derived(() => {}) to $derived.by(() => {}) which is the correct
syntax for complex derived values in Svelte 5. The previous syntax caused the
function to be rendered as text in the UI instead of being evaluated.
Fixes UI displaying function code instead of track count number.
- Added source and qobuz_track_id fields to LocalTrack model for proper filtering
- Updated all database row mapping functions to include new fields
- Implemented network folder and download filtering for library_get_artists command
- Implemented network folder and download filtering for library_search (Tracks) command
- Made tab counters (Albums, Artists, Tracks) reactive to filtered content
- Counters now update based on current filtered data instead of total stats
Fixes offline mode filtering for Artists and Tracks tabs. When offline or when
downloads are hidden, Artists/Tracks now properly filter out inaccessible content,
matching the behavior already implemented for Albums.
Problem: Repair was failing for 42 tracks because it only searched
by qobuz_track_id, which was lost when scanner replaced downloads.
Root cause: Scanner's INSERT OR REPLACE doesn't include qobuz_track_id
in its column list, so it gets set to NULL during replacement. Previous
repair logic couldn't find tracks without qobuz_track_id.
Solution: Multi-strategy repair approach:
1. Search by qobuz_track_id first (intact downloads)
2. If not found, search by file_path (scanner-damaged downloads)
3. When found by path, restore BOTH qobuz_track_id AND source
New database functions:
- repair_qobuz_download_by_path(): Restores both ID and source
- track_exists_by_path(): Check existence by file path
Updated backfill logic:
- Try match by ID (for intact downloads with lost source)
- Try match by path (for scanner-damaged downloads)
- Insert as new only if not found by either method
This should successfully repair all 42 damaged downloads by matching
them via file_path from download_cache.
Files changed:
- src-tauri/src/library/database.rs
- src-tauri/src/library/commands.rs
Implemented configurable network folder visibility when in manual offline mode.
Problem:
- Previously, ALL offline modes hid network folders
- Users in manual offline mode (privacy/metered internet) may still
have local network access and want to see network-mounted music
Solution:
- Added showNetworkFoldersInManualOffline setting (already existed in backend)
- Implemented smart filtering logic in LocalLibraryView:
- Offline real (no_network): Always hide network folders
- Offline manual: Hide ONLY if user disabled the setting
- Online: Show everything
- Added toggle in Settings > Offline Mode section
Use cases:
1. Privacy: Manual offline mode but keep local network music visible
2. Metered internet: Don't stream from Qobuz but access NAS/network shares
3. Real offline: No network at all, hide everything network-based
Settings location: Settings > Offline Mode > "Show Network Folders"
Description: "Display music from network folders when in manual
offline mode (privacy/metered connection)"
Files changed:
- src/lib/components/views/LocalLibraryView.svelte
- src/lib/components/views/SettingsView.svelte
Added UI for library_backfill_downloads command in Settings > Downloads section.
Features:
- "Repair Downloads" button with description
- Shows repair report with counts (added/repaired/skipped/failed)
- Triggers library reload after repair to reflect changes
- Disabled when no downloads exist or repair is in progress
Location: Between "Show in Local Library" toggle and "Clear Downloads" button
This allows users to easily restore source markers for Qobuz downloads
that were damaged during library scans.
Files changed:
- src/lib/components/views/SettingsView.svelte
Problem: Qobuz downloads that were already scanned lost their
source = 'qobuz_download' marker. The previous fix only prevents
future damage, but doesn't repair existing tracks.
Solution:
- Added repair_qobuz_download_source() to restore source marker
- Modified library_backfill_downloads to repair existing tracks:
- When a track exists, try to repair its source marker
- Report shows: added, repaired, and skipped counts
- This fixes downloads that lost their marker during scans
User can now run the backfill command to:
1. Restore source markers for damaged downloads
2. Make "Show in Local Library" setting work correctly
3. Separate Qobuz downloads from scanned music again
Files changed:
- src-tauri/src/library/database.rs
- src-tauri/src/library/commands.rs
Problem 1: "Show in Local Library" setting was being ignored
- Qobuz downloads appeared in library even when setting was disabled
- Root cause: Scanner was overwriting Qobuz downloads with INSERT OR REPLACE
- This lost the 'source = qobuz_download' marker needed for filtering
Problem 2: Network music visible in offline mode
- Music from network folders remained visible when offline
- User expected network content to be completely hidden offline
- Only local disk music should appear
Solution 1: Protect Qobuz downloads from scanner
- Added is_qobuz_download_by_path() to check if file is already a download
- Modified insert_track() to skip files with source = 'qobuz_download'
- Now downloads and scanned music coexist as separate "joins" in DB
Solution 2: Filter network folders when offline
- Added get_network_folder_paths() to get list of network folder paths
- Modified library_get_albums to accept excludeNetworkFolders parameter
- When offline, filter out albums whose tracks are in network folders
- Frontend passes excludeNetworkFolders: isOffline to backend
- Added reactive effect to reload library when offline state changes
This implements the "join between tables" approach - Qobuz downloads
don't interfere with scanned music, and network content is properly
hidden when offline without being deleted.
Files changed:
- src-tauri/src/library/database.rs
- src-tauri/src/library/commands.rs
- src/lib/components/views/LocalLibraryView.svelte
Problem: When app is in offline mode, LocalLibrary gets stuck in
infinite loading screen and becomes unusable.
Root cause: loadFolders() was calling checkFolderAccessibility()
for each network folder. The backend command library_check_folder_accessible
uses blocking std::fs::read_dir which can hang for 20-30 seconds per
unreachable network path.
Solution:
Frontend (LocalLibraryView.svelte):
- Skip network folder accessibility checks when offline
- Assume network folders are inaccessible in offline mode
- Only check accessibility when online
Backend (library/commands.rs):
- Add 2-second timeout to library_check_folder_accessible
- Use tokio::task::spawn_blocking to avoid blocking async runtime
- Log timeout warnings for unreachable network paths
This allows LocalLibrary to load instantly when offline, showing
only local disk content as intended.
Files changed:
- src/lib/components/views/LocalLibraryView.svelte
- src-tauri/src/library/commands.rs
Problem: When user toggled the "Show Qobuz Downloads in Local Library"
setting in Settings, LocalLibraryView didn't refresh, so downloaded
tracks continued to appear even when the setting was disabled.
Root cause: LocalLibraryView only loaded data onMount and had no
mechanism to detect when download settings changed.
Solution:
- Created downloadSettingsStore with version tracking
- SettingsView notifies store when setting changes
- LocalLibraryView subscribes via $effect() and reloads data
Backend was already correctly filtering by the setting - this was
purely a frontend reactivity issue.
Files changed:
- src/lib/stores/downloadSettingsStore.ts (new)
- src/lib/components/views/SettingsView.svelte
- src/lib/components/views/LocalLibraryView.svelte