Commit Graph
1997 Commits
Author SHA1 Message Date
Victor RH e202ab03fc Improves stability and adds new features
This release brings stability improvements, including fixes for UI freezes, EGL crashes, and playlist loading performance.

It introduces gapless playback, volume normalization, Plex integration, and enhanced discover browsing.
2026-02-10 03:28:40 -06:00
vicrodh 3356d76ade fix(snap): install SQLite 3.46 to fix sqlite3_error_offset link error
Core22 ships SQLite 3.37 but rusqlite needs 3.38+ for
sqlite3_error_offset. The RUSTFLAGS -L flags cause the linker
to find the system libsqlite3.a before the bundled one.
2026-02-10 03:18:37 -06:00
vicrodh 3c26880e56 feat: add aarch64 architecture support to snapcraft 2026-02-10 02:51:30 -06:00
vicrodh a7409bfdd4 chore: bump version to 1.1.10 2026-02-10 02:17:48 -06:00
vicrodh 051c9a3197 fix: remove z-index from artist page sticky nav to prevent sidebar dropdown overlap 2026-02-10 02:08:11 -06:00
vicrodh c071b86ae2 feat: preserve original Qobuz playlist image when copying to library 2026-02-10 02:05:17 -06:00
vicrodh bbc3b4a32e fix: use localized playlist tags and align tag selector below section title 2026-02-10 01:52:56 -06:00
vicrodh b76b369d9b feat: wire up discover playlists browse page with navigation and i18n 2026-02-10 01:51:31 -06:00
vicrodh 0f8bba5f89 feat: add playlist tags endpoint and DiscoverPlaylistsBrowseView component 2026-02-10 01:48:37 -06:00
vicrodh d912bd1284 feat: add genre_ids support to discover playlists endpoint 2026-02-10 01:43:49 -06:00
vicrodh d453bcd559 fix: remove z-index from favorites-nav and dropdown-container
Both were creating stacking contexts that trapped the sidebar playlist
dropdown behind them. Removing z-index entirely avoids creating stacking
contexts — sticky positioning handles scroll overlap natively, and the
dropdown-menu child manages its own z-index.
2026-02-10 01:25:17 -06:00
vicrodh 8857ef0597 fix: lower favorites-nav z-index to prevent sidebar dropdown overlap
The sticky .favorites-nav header had z-index: 4 which created a stacking
context that trapped the sidebar playlist dropdown menu behind it. Lower
to z-index: 1, matching the pattern used in ArtistDetailView and
LocalLibraryView.
2026-02-10 01:22:04 -06:00
vicrodh 0fe1174a0b fix: move See All link below section title instead of beside it
Wrap title + See All in a flex column group so the link sits directly
under the section heading, left-aligned. Remove margin-left: auto that
was pushing it to the right edge.
2026-02-10 01:13:30 -06:00
vicrodh 3e4f95d297 fix: replace pipe separator with arrow icon for See All links
Remove the pipe divider that looked bad. Replace ChevronRight with
ArrowRight for a cleaner See All > appearance. Revert HorizontalScrollRow
gap change that was only needed for the pipe.
2026-02-10 01:11:21 -06:00
vicrodh ba62057545 feat: add Qobuzissimes, Albums of the Week, Press Accolades browse pages
- Add 3 new Rust endpoint constants and command mappings for
  /discover/qobuzissims, /discover/albumOfTheWeek, /discover/pressAward
- Add 3 new navigation ViewType values and genre filter contexts
- Extend DiscoverBrowseView to handle the 3 new endpoint types
- Add See All links with chevron caret and pipe separator to all 6
  discover sections in HomeView (newReleases, mostStreamed,
  essentialDiscography, qobuzissimes, editorPicks, pressAwards)
- Wire up 3 new views in +page.svelte with full album action callbacks
- Rename Editor's Picks to Albums of the Week, Press Awards to Press
  Accolades across all 4 locale files (en, es, de, fr)
- Fix rank badge: top-right position, Impact font, 35px size
- See All styling: inline-flex with ChevronRight icon, pipe divider,
  15px gap before nav buttons, #666666 color matching nav buttons
2026-02-10 01:10:28 -06:00
vicrodh a575c0c805 fix: use existing genre filter, fix ranking display and See All positioning
- Replace custom genre <select> with GenreFilterButton component, each
  discover page gets independent persistence via separate contexts
- Rank badge in grid mode: 3x bigger (33px), positioned on right side
  of album card, no # symbol
- See All link: pushed to right edge (margin-left: auto), color matches
  nav buttons (#666666), hover transitions to text-primary
- Add proper page padding to DiscoverBrowseView (18px left, 8px right)
2026-02-10 00:57:57 -06:00
vicrodh c9bdcae855 feat: add discover browse pages for New Releases, Ideal Discography, Top Albums
Add full-page browsing views for the three discover sections that were
previously only shown as limited carousels on the Home page.

Backend: add get_discover_albums Tauri command with endpoints for
newReleases, idealDiscography, and mostStreamed. Uses paginated API
calls with genre_ids filtering.

Frontend: new DiscoverBrowseView component with virtualized grid/list,
genre filter dropdown, client-side search, and infinite scroll.
VirtualizedFavoritesAlbumGrid gains ranking support (showRanking),
infinite scroll (onLoadMore), and loading indicator.

HomeView sections now show "See All" links that navigate to the
dedicated browse pages. i18n keys added to all 4 locales.
2026-02-10 00:46:39 -06:00
vicrodh ed06a397ef fix(lyrics): re-fetch when cached entry lacks synced lyrics
Cached entries with plain-only lyrics were served forever, even after
the search-first strategy can now find synced versions. Treat plain-only
cache entries as a miss so the provider chain runs again. If synced
lyrics are found, the cache is updated via upsert.
2026-02-09 23:58:46 -06:00
vicrodh 8cf84b64e8 fix(lyrics): search-first strategy to prioritise synced lyrics from LRCLIB
GET /api/get often returns results without synced lyrics even when they
exist in the database. Switch to search-first: /api/search returns
multiple candidates so pick_best_match can select one with synced lyrics.
GET is now only a fallback when search yields nothing.

Also pass track_name + artist_name as separate search filters instead
of a combined q param for more precise results.
2026-02-09 23:35:25 -06:00
vicrodh dcdddf384c fix: remove inner scroll from Search tabs, use parent scroll container
Search tabs (albums, tracks, artists) had their own overflow-y scroll
containers, causing double scrollbars and preventing the global
back-to-top button and sticky search bar from working.

- Remove height, min-height, overflow from .virtual-scroll-container
- Remove scrollbar pseudo-element CSS for virtual containers
- Feed virtual scroll calculations from parent .search-view scroll
- Remove per-container onscroll handlers (unused)
- ResizeObservers now only track width for grid column calculations
- Container height comes from parent scroll viewport
2026-02-09 23:15:56 -06:00
vicrodh 7b5a7d963d feat: move back-to-top to global layout level
Replaced the per-view back-to-top in PlaylistDetailView with a
global one in +page.svelte. Uses capture-phase scroll listener
on .main-content to track scroll from any child view. Appears
after 800px scroll on any view. Resets on view change.
2026-02-09 23:08:40 -06:00
vicrodh 170b6408f7 fix(artist,library): bump jump-nav z-index above scrolling content
Content elements (track artwork z:1, play overlay z:2) were rendering
on top of the sticky jump-nav (also z:1). Bumped to z:10, still below
dropdown menus (z:50+) and context menus (z:100).
2026-02-09 23:05:21 -06:00
vicrodh cb3b518eb2 fix(playlist): add bottom spacer when suggestions section is absent
Prevents the back-to-top button from covering the last track's
action buttons on playlists without a suggestions section.
2026-02-09 23:01:01 -06:00
vicrodh cdc051d476 fix(playlist): position back-to-top 10px above the player bar 2026-02-09 22:58:50 -06:00
vicrodh 3cd9b99153 feat(playlist): add back-to-top button for large playlists
Floating button appears after scrolling 600px on playlists with
100+ tracks. Smooth scrolls back to top. Positioned bottom-right
with sticky positioning inside the scroll container.
2026-02-09 22:53:31 -06:00
vicrodh 8270de2e5e perf(api): replace Mutex with RwLock for QobuzClient
QobuzClient has no &mut self methods — all API calls are read-only.
The Mutex serialized all concurrent API requests (favorites sync,
sidebar playlists, playlist loading) causing 6+ second wait times
at startup.

RwLock allows concurrent reads. All 94 call sites across 20 files
changed from .lock() to .read(). ListenBrainz/LastFm clients keep
their own Mutex (different state, different lock).
2026-02-09 22:50:35 -06:00
vicrodh 8f1cc735a7 perf(playlist): use $state.raw for tracks to eliminate proxy bloat
$state() creates deep reactive proxies for every object in the array.
With 2000 tracks, each background batch reassignment triggered proxy
creation for all existing + new objects, growing quadratically.

$state.raw() only tracks the reference — no deep proxying. Combined
with a fast path in displayTracks that skips the full copy/interleave
when there are no local tracks, search, or custom sort.
2026-02-09 22:45:12 -06:00
vicrodh 33eb990a6c fix(playlist): eliminate placeholders and proxy bloat for large playlists
The black screen was caused by Svelte 5 creating deep reactive proxies
for 2000 objects (200 real + 1800 placeholders) synchronously, blocking
the main thread for ~27s.

Now: start with only loaded tracks (200), no placeholders. Background
batches append via single array reassignment per group (not N individual
mutations) and yield to the browser between groups. The displayTracks
derived no longer re-spreads 2000 objects on every mutation.
2026-02-09 22:39:14 -06:00
vicrodh e7ff1d647e fix(playlist): keep spinner until first viewport batch loads
For large playlists (>500 tracks), the spinner now stays visible
until the first group of tracks (up to 200) has real data. This
eliminates the placeholder flash — users see spinner then real
tracks. Remaining batches load in the background.
2026-02-09 22:27:23 -06:00
vicrodh de33a41aa4 feat(playlist): adaptive loading — single call for small playlists
Small playlists (≤500 tracks) now use get_playlist_track_ids for
metadata then get_playlist for full data in one shot — no placeholders,
no visible two-phase loading. Progressive batched loading only kicks
in for large playlists (>500 tracks) where it actually helps.
2026-02-09 22:20:26 -06:00
vicrodh 1c7a14d789 fix(api): use POST with JSON body for track/getList endpoint
The Qobuz track/getList endpoint requires a POST request with
JSON body {"tracks_id": [...]} — not GET with query params or
form-encoded POST. This matches the Qobuz web player behavior.
2026-02-09 22:15:18 -06:00
vicrodh 042a307c41 fix(api): add POST fallback and flexible response parsing for track/getList
The Qobuz track/getList endpoint may require POST instead of GET.
Try GET first, fall back to POST with form body if response is an error.
Also handle multiple response structures and log the actual response
keys for debugging.
2026-02-09 22:03:28 -06:00
vicrodh fa784df62e feat(playlist): progressive loading via track_ids + track/getList
Replace monolithic get_playlist call with two-phase approach:
- Phase 1: playlist/get?extra=track_ids returns metadata + all track
  IDs in a single lightweight call (~50KB vs ~5MB)
- Phase 2: track/getList fetches full details in batches of 50,
  4 concurrent groups, with tracks rendering progressively

Backend changes:
- Add TRACK_GET_LIST endpoint and PlaylistWithTrackIds model
- Add get_playlist_track_ids and get_tracks_batch API client methods
- Add corresponding Tauri commands
- Update remove_tracks_from_playlist to accept either
  playlist_track_ids or regular track_ids (resolves internally)

Frontend changes:
- Rewrite loadPlaylist() for two-phase progressive loading
- Show playlist header + placeholder tracks after Phase 1 (~0.5s)
- Fill in full track details as batches arrive
- Add placeholder track row CSS (gray bars, no shimmer)
- Use Qobuz pre-made collage images from playlist metadata
- Update track removal to handle missing playlist_track_id
2026-02-09 21:56:15 -06:00
vicrodh 6a56a1492d debug: add timing logs across entire playlist load pipeline 2026-02-09 21:02:44 -06:00
vicrodh c084e64756 perf(api): fetch playlist pages concurrently after first page
First page is fetched normally to get metadata and total track count.
Remaining pages are launched concurrently via futures::join_all and
reassembled in offset order. For a 2000-track playlist this means
3 concurrent requests instead of 3 sequential ones (~3x faster).
2026-02-09 20:56:04 -06:00
vicrodh 0717e1c478 fix(playlist): clear old tracks immediately on playlist switch to prevent stale recomputation 2026-02-09 20:51:04 -06:00
vicrodh 542eb9b9b5 fix(playlist): reset virtual scroll on playlist change, manual suggestions for all
- Reset trackListScrollTop and scrollContainer.scrollTop to 0 when
  playlistId changes. Fixes tracks not rendering until user scrolls
  (old scroll offset exceeded new playlist's track count).
- Recalculate trackListViewHeight after content mounts via rAF.
- Set SUGGESTIONS_AUTO_THRESHOLD to 0: all playlists require manual
  activation to rule out suggestions as unresponsiveness cause.
2026-02-09 20:45:39 -06:00
vicrodh c36f8580e6 debug: log playlist name on load for virtualization tracking 2026-02-09 20:39:58 -06:00
vicrodh 9ca74daba0 perf(playlist): virtualize track list and defer suggestions computation
- Virtualize playlist track list using absolute positioning and viewport
  culling. Only ~20 track rows are in the DOM at any time instead of all
  2000. Eliminates main-thread blocking when navigating between playlists.
- Gate extractAdaptiveArtists and excludeTrackIds computation: playlists
  >=2000 tracks never compute (Qobuz limit), >500 tracks defer until
  manual activation. The computation itself is skipped, not just the UI.
- Move manual launch button from PlaylistSuggestions to PlaylistDetailView
  so the component only mounts after activation (zero overhead when idle).
- Simplify PlaylistSuggestions by removing redundant manual launch logic.
2026-02-09 20:34:35 -06:00
vicrodh 02f08e5c8b fix(suggestions): skip at Qobuz limit, manual launch for large playlists
Playlists at the 2000-track Qobuz limit no longer mount the
suggestions component at all (nothing can be added).

Playlists with >500 tracks require a manual button press to launch
the suggestion engine instead of auto-loading on mount. This avoids
heavy background work that causes UI freezes on large playlists.
2026-02-09 20:18:10 -06:00
vicrodh 4bbf97a131 feat(playlist-import): multipart playlists for 2000+ track imports
Qobuz limits playlists to 2000 tracks. When matched tracks exceed
this limit, the importer now automatically splits into multiple
playlists named "Playlist (Part 1)", "Playlist (Part 2)", etc.

- models.rs: qobuz_playlist_id -> qobuz_playlist_ids Vec, add parts_created
- importer.rs: chunk matched tracks by 2000, create playlist per chunk
- Frontend: show parts count in summary, move all parts to folder
- i18n: add partsCreated key in en/es/de/fr
2026-02-09 18:43:46 -06:00
vicrodh 595d6d46f5 fix(spotify): add token retry, HTTP status checking, and diagnostics
The Spotify API path was failing silently (no visible logs) and falling
back to embed which caps at ~100 tracks. Root cause: HTTP error
responses (401/429) were parsed as JSON, then failed with the
misleading "missing items" error.

Changes:
- Add error_for_status() to all HTTP calls (proxy, metadata, tracks)
  so 401/429/5xx errors surface immediately with real status codes
- Retry token + fetch once before falling back to embed (handles
  transient proxy failures and token expiry)
- Descriptive error messages include which request failed
2026-02-09 18:13:49 -06:00
vicrodh ef5a7b6c0e fix(spotify): restore embed fallback when API fetch fails
The previous commit blocked the embed fallback when the API token was
obtained but the fetch failed, causing a hard error instead of
returning partial results. Restore the original fallback behavior
with logging to diagnose API failures.
2026-02-09 18:07:57 -06:00
vicrodh e2e8766775 fix(spotify): improve pagination reliability and stop silent embed fallback
The embed scraping fallback silently caps at ~100 tracks. When the API
token was obtained successfully but the fetch failed (transient error,
rate limit), the code fell through to embed without logging, returning
incomplete results.

Changes:
- Propagate API errors instead of silently falling through to embed
  when the token was successfully obtained
- Fix next-page detection: use is_null() instead of as_str() which
  could miss non-string truthy values
- Use response.total field as backup pagination signal
- Add safety checks for empty pages to prevent infinite loops
- Validate fetched count against expected total from metadata
- Add logging at every decision point for diagnosability
2026-02-09 18:00:18 -06:00
vicrodh 9fdd1dd0df feat(playlist-import): concurrent matching with progress reporting
Replace sequential track matching with buffer_unordered(8) for 8
concurrent Qobuz API calls, reducing import time ~8x. Add real-time
progress events (import:progress, import:phase) emitted from Rust
backend and consumed by frontend via Tauri event listeners.

Frontend shows progress bar, matching status text, and current track
name during import. Individual search failures are now logged and
skipped instead of aborting the entire import.
2026-02-09 17:53:04 -06:00
vicrodh a46a422a04 fix(playlist): paginate get_playlist to fetch all tracks
The Qobuz API get_playlist call was hardcoded with limit=500, silently
dropping tracks beyond that. Now paginates in 500-track pages using
the TracksContainer.total field until all tracks are loaded.
2026-02-09 17:33:51 -06:00
vicrodh bd5725f345 feat(playlist): remove total track limit from smart playlists
Remove max_total_tracks (was 100) cap and all truncation/early-break
logic from smart playlist generation. Playlists now include all
tracks found across related artists. Increase per-artist sampling
from 10 to 25 for richer results.
2026-02-09 17:27:35 -06:00
vicrodh 8cc0a8a6ae fix(i18n): replace hardcoded strings in PlaylistModal
Replace 5 hardcoded validation messages ('Please enter a playlist
name', 'Please select a playlist') with i18n keys playlist.enterName
and playlist.selectPlaylist across all 4 locales.
2026-02-09 17:23:12 -06:00
vicrodh f85d04fa68 fix(playlist): migrate duplicate modal to Svelte 5 and add i18n
Migrate PlaylistDuplicateConfirmModal from Svelte 4 (export let,
createEventDispatcher, on:click) to Svelte 5 ($props, callback
props, onclick). Replace hardcoded English strings with i18n keys
under playlist.duplicates for all 4 locales (en, es, de, fr).
2026-02-09 17:13:23 -06:00
vicrodh ae1a3533a8 Merge branch 'feature/external/warn-on-playlist-duplicates' into pre-release 2026-02-09 17:11:15 -06:00