Commit Graph
2264 Commits
Author SHA1 Message Date
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
vicrodh 7a83bfa0b9 fix(lyrics): retry LRCLIB once on network error before fallback
Distinguish not-found (Ok(None)) from network error (Err) in
fetch_lrclib. On network error, retry the full LRCLIB flow once
before falling back to lyrics.ovh. Not-found skips directly to
the fallback with no retry.
2026-02-09 15:06:09 -06:00
vicrodh d1fd6e045f fix(lyrics): fallback to lyrics.ovh when LRCLIB fails
Network errors in LRCLIB were propagated as hard Err via ? operator,
preventing the lyrics.ovh fallback from ever being reached. Changed
provider functions to return Option instead of Result so errors are
treated as soft failures. Added 10s HTTP timeout. Fixed internal
LRCLIB fallback (GET failure now falls through to search). Replaced
hardcoded UI strings with i18n keys.
2026-02-09 15:02:57 -06:00
vicrodh 344579c849 chore: clean unused CSS selectors and add $state() to DOM refs
Remove 22 unused CSS selector blocks and wrap 28 bind:this variables
with $state() to suppress Svelte 5 dev-mode warnings.
2026-02-09 14:49:25 -06:00
vicrodh fcafac095c fix(window): always restart on system titlebar toggle
Runtime decoration toggling doesn't work reliably in either direction
on Linux/Wayland. Always restart the app so the window is recreated
with the correct decoration state.
2026-02-09 14:40:31 -06:00
vicrodh cb6c86324c fix(window): use dark background instead of hidden window
Revert visible:false approach which broke WM buttons on Wayland.
Instead, set html/body background to #0f0f0f (dark theme default)
so the brief frame before webview renders shows a dark surface
instead of a white flash.
2026-02-09 14:36:44 -06:00
vicrodh f284ac92bc fix(window): hide window until webview renders to avoid empty frame
Create the window with visible:false so the empty frame + title bar
don't flash while the webview loads. The frontend calls
show_main_window from onMount (after first DOM paint) to reveal the
window with content already rendered.
2026-02-09 14:34:24 -06:00
Joel C 512a411292 Add confirmation imodal when adding duplicate tracks to a playlist
Use similar copy as official iOS client
2026-02-09 21:26:40 +01:00
vicrodh 68b227f46c fix(window): only restart when enabling system titlebar
Disabling system titlebar works at runtime (decorations:true -> false
is fine on Linux/Wayland). Only enabling requires a restart because
the window must be born with decorations:true for WM buttons to work.

When disabling: remove OS decorations via setDecorations(false) and
restore the custom title bar immediately via notifyListeners().
2026-02-09 13:38:12 -06:00
vicrodh c88974c468 feat(window): restart app on system titlebar toggle
Since the window must be created with the correct decoration state
(runtime toggling doesn't work on Linux/Wayland), persist the setting
to the Rust backend and restart the app immediately.

Add restart_app Tauri command using AppHandle::restart(). Update i18n
descriptions to inform the user the app will restart.
2026-02-09 13:36:57 -06:00
vicrodh dec620ee4f fix(window): create main window programmatically for correct decorations
On Linux/Wayland, windows created with decorations:false cannot
reliably switch to decorations:true at runtime - the WM buttons
render but don't function until a maximize/restore cycle.

Fix by removing the declarative window from tauri.conf.json and
creating it programmatically in the setup hook with the correct
decorations value from the start. The WM sees the right state when
the window is first mapped, so buttons work immediately.

Also fixes the empty frame flash from visible:false + early show().
2026-02-09 13:30:26 -06:00
vicrodh 50569b61b5 fix(window): sync localStorage value to Rust backend on init
When the frontend initializes and finds useSystemTitleBar=true in
localStorage, sync it to the Rust SQLite store. This handles migration
from the localStorage-only era and ensures the Rust backend has the
correct value for next startup.
2026-02-09 13:25:05 -06:00
vicrodh 32a7f2c807 fix(window): set system decorations before window show
Move decoration setting to Rust side so the WM sees correct state when
the window is first mapped. Window starts hidden (visible: false in
tauri.conf.json), Rust reads the persisted setting from SQLite, applies
decorations, then shows the window.

Frontend now persists the setting via Tauri invoke (in addition to
localStorage) so Rust can read it at startup. Inline script in app.html
prevents flash of custom title bar before Svelte mounts.
2026-02-09 13:18:50 -06:00
vicrodh 7b5bd166d4 feat(settings): add system title bar toggle
Add option to use OS native window decorations instead of the custom
QBZ title bar. Uses Tauri setDecorations() API to toggle at runtime.
Disables "Hide title bar" toggle when system bar is active.
2026-02-09 13:03:56 -06:00
vicrodh be5a80a381 fix(svelte): eliminate t-shadow regressions after merge
cx
2026-02-09 13:01:00 -06:00
vicrodh c402c75010 fix(settings): restore Plex metadata write toggle
The toggle was removed from the template in a previous redesign but all
backing logic (state, handler, localStorage key, i18n) remained intact.
Re-add the setting-row with Toggle before the disconnect/clear actions.
2026-02-09 11:51:53 -06:00
vicrodh d9d03fcc2d fix(plex): offline mode, background quality hydration, single-file detection
- Disable Plex in isPlexLibraryEnabled() when offline reason is
  no_network (manual_override keeps Plex active for local servers)
- Add background hydration after album list load: fetch quality for
  tracks with NULL sampling_rate_hz/bit_depth, persist to cache, then
  refresh album list so quality badges update without opening each album
- Add likely_single_file_album detection to LocalAlbum/PlexCachedAlbum
  interfaces and mapPlexAlbum mapping
- Show info notice in album detail view for single-file albums
- Add i18n keys for single-file album notice (en + es)
2026-02-09 11:43:57 -06:00
vicrodh 6e7f5940c1 fix(plex): preserve hydrated quality during re-sync, add hydration query
- Save existing quality data before DELETE in plex_cache_save_tracks,
  restore hydrated container/sampling_rate_hz/bit_depth after re-insert
  so bulk sync no longer overwrites per-track quality from hydration
- Add plex_cache_get_tracks_needing_hydration command returning
  rating_keys where sampling_rate_hz or bit_depth is NULL (limit param)
- Add likely_single_file_album field to PlexCachedAlbum struct, set true
  when track_count == 1 and total_duration_secs > 600
2026-02-09 11:40:48 -06:00
vicrodh fdb6c520cb fix: restore isBlacklisted prop in AlbumDetailView TrackRow
Was incorrectly removed during merge conflict resolution of
feature/ui-fixes. Without it, blacklisted tracks lack visual
feedback (opacity, Ban icon, cursor) in album detail view.
2026-02-09 11:02:15 -06:00
vicrodh aaaf74e3c2 Merge branch 'feature/home-resolved' into pre-release 2026-02-09 10:59:12 -06:00
vicrodh e08677cf6a Merge branch 'feature/artistpage-refactor' into pre-release 2026-02-09 10:59:09 -06:00
vicrodh 2fe573ec84 Merge branch 'feature/ui-fixes' into pre-release
# Conflicts:
#	src/lib/components/AudioOutputBadges.svelte
#	src/lib/components/views/AlbumDetailView.svelte
#	src/lib/components/views/PlaylistDetailView.svelte
2026-02-09 10:59:04 -06:00
vicrodh a91c955ee4 feat: replace ~50 IPC calls with single reco_get_home_resolved invoke
HomeView ML sections now use a single IPC call that returns fully
resolved card data from Rust. The 3-tier cache (reco meta → API cache
→ Qobuz API) handles all resolution server-side, eliminating the
per-item fetch loop on the frontend.

Genre filtering and section visibility still applied client-side.
Existing fetchAlbums/fetchTracks/fetchArtists kept for other uses.
2026-02-09 10:44:42 -06:00
vicrodh ee8cd286f3 feat: add reco_get_home_resolved Rust command with 3-tier metadata cache
Add persistent metadata cache tables (reco_album_meta, reco_track_meta,
reco_artist_meta) with no TTL for stable played-item metadata.

Implement reco_get_home_resolved command that returns fully-resolved
card data in a single IPC call using 3-tier resolution:
1. Reco meta cache (persistent, sub-ms)
2. API cache (24h TTL, sub-ms)
3. Qobuz API (parallel HTTP fallback)

Also refactors reco_get_home_ml to use shared get_home_seeds_internal
to avoid seed-gathering duplication.
2026-02-09 10:41:55 -06:00
vicrodh 84a54cc017 fix: correct artist portrait image URL construction
The Qobuz CDN pattern for artist portraits is
/images/artists/covers/medium/{hash}.{format}, not
/images/artists/covers/{hash}_600.{format}.
2026-02-09 09:53:42 -06:00
vicrodh 7072fbf63d chore: remove unused imports of convertQobuzArtist and appendArtistAlbums
These functions are no longer called from +page.svelte after switching
to convertPageArtist and appendPageReleases. The function definitions
remain in qobuzAdapters.ts as categorizeAlbum (used by FavoritesView)
depends on the same helper functions.
2026-02-09 09:29:22 -06:00
vicrodh faad669e4c feat: per-category releases pagination via get_releases_grid
Replace monolithic loadMoreArtistAlbums (get_artist_albums) with
per-category loadMoreArtistReleases using get_releases_grid endpoint.
Each section (albums, EPs, live, others) gets its own "load more"
button driven by has_more flags from the API response.
2026-02-09 09:26:47 -06:00
vicrodh 85ec6f3ab2 feat: integrate artist/page endpoint into navigation
Replace get_artist_detail with get_artist_page in handleArtistClick()
and fetchAlbumArtistAlbums(). Pass pre-loaded top_tracks and
similar_artists as props to ArtistDetailView, eliminating 2 extra
API calls (search_tracks text search + get_similar_artists).
2026-02-09 09:22:51 -06:00
vicrodh bbdbdb7e44 feat: add TypeScript types and adapter for artist/page endpoint
Add PageArtist* interfaces matching the Rust types. Add
convertPageArtist() adapter that maps API release_type categories
instead of heuristic regex, filters foreign discs, and builds
the existing ArtistDetail UI model from a single API response.
2026-02-09 09:19:43 -06:00
vicrodh b6202ebb66 feat: add Rust backend for artist/page and releases/grid endpoints
Add PageArtistResponse and ReleasesGridResponse types with all
supporting structs for the /artist/page and /artist/getReleasesGrid
Qobuz API endpoints. Add client methods and Tauri commands.
2026-02-09 09:17:44 -06:00
vicrodh ee7ea4f164 feat: SWR cache for Sidebar playlists
Sidebar now uses stale-while-revalidate for playlists, settings,
and local track counts. On mount, cached data is shown immediately
while fresh data loads in the background.

- Create sidebarDataCache.ts with sessionStorage persistence
- Sidebar restores from cache on mount (fresh/stale), loads in background
- Cache cleared only on real playlist changes (create/delete/track changes)
- Sort order, play counts, and UI state do NOT invalidate the cache
- Coordinated loadSidebarData() replaces three independent load calls
2026-02-09 08:33:15 -06:00
vicrodh 4aa32d388b feat: SWR cache for Home with stale-while-revalidate
homeDataCache now has three states:
- fresh (<5 min): use cache, no revalidation
- stale (5-60 min): show cache immediately, revalidate in background
- empty (>60 min / genre mismatch / no data): full skeleton load

Changes:
- Replace isHomeCacheValid() with getHomeCacheStatus() -> fresh/stale/empty
- Add sessionStorage persistence for cross-navigation survival
- Stop destroying cache data on staleness (was nullifying at 5 min)
- HomeView onMount uses three-state logic
- loadHome() accepts showSkeletons option for silent revalidation
- Extract restoreFromCache() helper for DRY cache restoration
2026-02-09 08:29:12 -06:00
vicrodh 1aa23dae9b refactor: consolidate Home API calls from 6 to 1 via discover/index
The discover/index endpoint already returns all editorial content
(new_releases, press_awards, most_streamed, qobuzissims, album_of_the_week)
in a single response. Previously we made 5 separate get_featured_albums
calls plus 1 discover call.

- Add missing containers to Rust DiscoverContainers struct
- Add missing containers to TypeScript DiscoverContainers interface
- Replace fetchFeaturedAlbums x5 + fetchDiscoverData with single
  fetchAllDiscoverData that extracts all sections from one API call
- Add discoverAlbumToCardData() converter for DiscoverAlbum -> AlbumCardData
- Genre filtering now handled by discover/index genre_ids parameter
2026-02-09 08:27:14 -06:00
Victor RH af36b08af1 Merge branch 'pre-release' into fixes/1.1.10/debug 2026-02-09 07:54:28 -06:00