Add Radio button to album detail header (next to favorite button)
that calls v2_create_album_radio, populates the queue with Qobuz
API recommended tracks, and starts playback of the first track.
Add i18n keys for album radio in all 4 locale files (en/es/de/fr).
Add /radio/album Qobuz API endpoint support:
- RadioResponse struct in models.rs with lenient deserialization
- get_radio_album() client method
- v2_create_album_radio command that calls the API directly (no
RadioPoolBuilder/RadioDb), filters blacklisted artists, populates
queue, and sets Radio playback context
- Register command in lib.rs invoke handler
Two fixes for session not persisting:
1. Changed v2_get_all_queue_tracks to return a struct instead of a
tuple — Tauri IPC serialization of tuples is unreliable.
2. Added proactive session saves instead of relying on async
beforeunload (which races with window close):
- Debounced full save on track change (2s after new track starts)
- Debounced full save on pause
- Reduced periodic save from 60s to 15s during playback
- Close handler remains as best-effort backup
The save was broken in two ways:
1. Only current_track + upcoming (capped at 20) were saved, dropping
all history tracks. Queue was reconstructed incomplete on restore.
2. Track metadata (hires, bit_depth, sample_rate, source, etc.) was
not being persisted, only the basic 6 fields.
Add v2_get_all_queue_tracks command that returns the full uncapped
track list with correct current_index. Use it in saveSessionBeforeClose
instead of the capped v2_get_queue_state. Map all metadata fields.
Re-enable queue/track restore on startup (visual only, paused at 0:00).
Update saveSessionBeforeClose to persist current view with context IDs.
Add "Startup page" dropdown in Settings > Appearance (Home / Where you
left off) that controls whether the last view is restored on launch.
i18n keys added to all 4 locale files.
Update PersistedSession interface with last_view fields. Add lastView,
viewContextId, viewContextType params to saveSessionState(). Add
restoreView(), setRestoredPlaylistId(), setRestoredLocalAlbumId() to
navigationStore for session restore without triggering data fetches.
Add last_view, view_context_id, view_context_type to player_state
table via migration. Update PersistedSession struct, save/load/clear,
and v2_save_session_state with 3 new optional parameters.
- Strip Shift modifier for symbol keys in eventToShortcut() so ? maps correctly
- Remove duplicate Space handler in ImmersivePlayer that caused double-fire
- Add bindingsVersion dependency to KeybindingsSettings for instant visual updates
- Add missing uiOpenLink i18n key and use getActionLabel() fallback in modal
- Add full keybindings translations to de.json and fr.json
- Add 4 bundled fonts: Montserrat, Noto Sans, Source Sans 3, plus
existing LINE Seed JP (default) and System fallback
- Font selector dropdown in Settings > Appearance, after Language
- Persisted in localStorage, applied on app bootstrap via data-font
attribute on <html> which overrides --font-sans CSS variable
- @font-face declarations for variable font files in static/fonts/
- i18n key "fontFamily" added to all 4 locales
- Add persistent drag region (32px strip) at top of immersive overlay
that remains active even when the header auto-hides after 4 seconds
- Add app-region: drag CSS to ImmersiveHeader's left spacer for proper
drag support when header is visible
- Add app-region: no-drag to tabs and window controls to keep them
clickable
- Double-click on drag strip toggles maximize
- Rename "Full Screen" to "Immersive Mode" in all 4 locales (en/es/de/fr)
- Deezer/Spotify/Tidal: fetch metadata directly from platform APIs
(bypasses Odesli ~300ms vs ~2-3s) with Odesli as fallback
- Smart Qobuz search with 3 progressive attempts:
1. Full "title artist" query
2. Cleaned title (strip parenthetical/bracket suffixes like Remastered)
3. Artist-only search with fuzzy title matching
- Fixes albums not found when platform titles differ (e.g. Iron Maiden
"Senjutsu (2021 Remaster)" vs Qobuz "Senjutsu")
The Odesli API can fail with connection errors on rapid successive
requests (connection pool staleness). Add a single retry with 500ms
delay to handle transient network failures gracefully.
Also improve song.link URL handling to hint track vs album detection.
Odesli's API returns entity IDs as integers for some platforms (e.g.
Bandcamp) while others return strings. The Entity.id field was typed as
String which caused serde deserialization to fail on numeric values.
Add a custom deserializer that accepts both strings and numbers.
Also show actual backend error in LinkResolverModal for easier debugging.
Odesli does NOT include Qobuz in its linksByPlatform response, so the
previous approach of extracting platforms["qobuz"] would always fail.
New strategy: use Odesli to identify the content (title/artist), then
search the Qobuz catalog via CoreBridge to find the matching album or
track. Falls back from track search to album search if needed.
Ctrl+L now accepts URLs from Spotify, Apple Music, Tidal, Deezer, and
song.link/album.link in addition to Qobuz. Non-Qobuz track/album URLs
are resolved to their Qobuz equivalent via the existing Odesli API
integration. Playlist URLs redirect to the Playlist Importer.
- Add MusicResource/MusicProvider enums and detect_music_resource()
- Add detect_resource() to each provider (spotify, apple, tidal, deezer)
- Add v2_resolve_music_link command with Odesli-based cross-resolution
- Upgrade LinkResolverModal with platform icon detection and playlist redirect
- Update i18n keys in all 4 locales (en, es, de, fr)
NowPlayingBar z-index raised from 100 to 2001 (above sidebar's 2000)
so the artwork preview floats over the sidebar instead of behind it.
Safe because the bar and sidebar occupy non-overlapping vertical space.
Fixes#96
Library scan now automatically removes tracks whose files no longer
exist on disk, both for full scans and single-folder scans. Previously,
deleted files would remain in the library until the user manually ran
"Cleanup missing files" from Settings.
Fixes#93
- Verbose capture toggle now persists to localStorage across sessions
- PurchasesView dropdown menus now have mutual exclusion (opening one closes others)
- Add to Library button wired to v2_library_add_folder with download destination tracking
- Added i18n keys for add-to-library success/error toasts (all 4 locales)
- Build Info grid now uses 2-column layout (Version|License, Platform|Build)
- Build field shows date + short commit hash (6 chars) injected at
build time via VITE_BUILD_COMMIT
The context icon showed correct tooltip on hover but clicking did
nothing for daily_q and weekly_q types. Add missing switch cases
to navigate to the respective views.
Add folder/flat toggle button that switches between showing playlists
organized in folders vs a flat list of all playlists. Add tree view as
a third view mode (grid -> list -> tree cycle) when folder mode is on.
- folderMode state persisted to localStorage
- Tree view groups playlists by folder with expand/collapse
- Flat mode shows all playlists regardless of folder assignment
- Auto-switch from tree to grid when folder mode turns off
- i18n keys added to all 4 locale files (en, es, de, fr)
Wire onPlaylistPlay, onPlaylistPlayNext, onPlaylistPlayLater,
onPlaylistCopyToLibrary, onPlaylistShareQobuz through LabelView Props
to QobuzPlaylistCard. Increase section spacing from 48px to 64px.
- Single state update for all artist images instead of N updates
- Prefer thumbnail/small images for carousel performance
- Increase section spacing to 48px between sections
- Fetch artist images via v2_get_artist for artists without images
from the label page data (parallel fetches with fallback)
- Add 20px extra spacing between sections
- Add scroll-margin-top to section anchors so jump-nav doesn't
cover section titles when clicking nav links
The /label/page endpoint releases containers were unreliable.
Now fetches 20 albums from v2_get_label (same as old LabelView)
for the releases carousel. Also loads description as fallback.
Critics' picks still parsed from /label/page containers.
DailyQ and WeeklyQ were incorrectly using 'home_list' context type.
LabelTop was defined in frontend but missing from Rust backend.
- Add LabelTop, DailyQ, WeeklyQ variants to Rust ContextType enum
- Add match arms in commands_v2.rs and display_info()
- Update frontend ContextType union and display strings
- DynamicSuggestView now uses 'daily_q' context type
- WeeklySuggestView now uses 'weekly_q' context type
- Switch from SearchPlaylistCard to QobuzPlaylistCard (matches HomeView)
- Add getPlaylistImage() handling rectangle/covers/images300 formats
- Improve getArtistImageUrl() with picture field and medium size fallback
- Fix jump-nav gap: set padding-top:0 to match ArtistDetailView
- Add debug logging for raw playlist/artist data
1. Move jump-nav below header (was above), match ArtistDetailView margins
2. Wrap handleTrackPlay context/queue in try-catch so onTrackPlay always fires
3. Add description fallback: fetch from v2_get_label if label/page omits it
4. Add detailed console logging for track play debugging
- Add back button with ArrowLeft icon
- Make header label image circular (border-radius: 50%)
- Remove play pill button from header (redundant with player bar)
- Replace artist circles with Search-style cards (160x220, bg-secondary)
- Fix artist image extraction (prioritize image.large over portrait hash)
- Replace QobuzPlaylistCard with SearchPlaylistCard for proper collage images
- Add missing action-btn-circle CSS for Popular Tracks controls
- Fix duplicate color property in label card placeholder
Replace the flat album grid LabelView with a rich detail page featuring:
- Header with label image, name, description, and play button
- Popular Tracks section (5/10/all expand, same UX as ArtistDetailView)
- Releases horizontal scroll row with "See all" → LabelReleasesView
- Critics' Picks section from second releases container
- Playlists, Artists, and More Labels sections
Backend: add /label/page and /label/explore API endpoints with V2
commands (v2_get_label_page, v2_get_label_explore). Models use
serde_json::Value for flexible section parsing.
Old LabelView renamed to LabelReleasesView for the "See all" flow.
i18n keys added to all 4 locales (en, es, de, fr).
Append a subfolder named after the selected format quality
(e.g. "FLAC 24-bit - 96 kHz") to the user-chosen destination.
Different quality downloads of the same album no longer overwrite
each other.
Move download state from component-local $state() to a persistent
module-level Svelte store (purchaseDownloadStore). Downloads continue
in the background when navigating away from album detail, and
progress bars are restored when returning.
Closes#90
Clicking the download icon on a track row also triggered row playback
due to event bubbling. Add e.stopPropagation() to both the normal
download and failed-retry download buttons.
The download button in the tracks tab had no onclick handler, so
clicking it triggered the row's playback handler instead. Now it
opens the native folder picker, downloads with the best available
format, and shows download status (spinner/checkmark). Artist link
also uses stopPropagation to avoid triggering playback. Fixes#91.
- Add back navigation button to both DailyQ and WeeklyQ views
- Replace subtitle with home card descriptions
- Remove cache/live source text, add total playback duration
- Remove refresh button from WeeklyQ (regenerates weekly on Fridays)
- Fix weekly cache to use Friday-based key instead of daily
- Add algorithm info icon with tooltip on both views
- Fix playback context: align queue index with blacklist-filtered tracks
- Change header label from "Playlists" to "Your mixes"
- Add algorithmInfo i18n key to all 4 locales
Add parent-driven track playback with queue setup, playback context,
context menus, offline cache status, and artist blacklist filtering.
Both views now behave like normal playlist views.
Replaced background-position animation with a ::before pseudo-element
(inset -40%) animated via transform (translate + rotate + scale).
GPU-composited, zero repaints. Each card has its own keyframes
(silk-daily 30s, silk-weekly 34s) with different movement patterns
so they never look synchronized.
Increased duration to 25s with 7 irregularly spaced keyframe stops
to make the loop less predictable. WeeklyQ offset at -11s for
desync. Same CPU cost — still just background-position interpolation.
Two narrow linear-gradient bands (3% spread each) layered on top:
a bright highlight streak and a dark shadow streak. They shift with
the silk animation creating visible contrast lines across the surface.
Slow background-position animation on 200% oversized gradients
creates a subtle flowing silk effect. 10s cycle, alternate
direction. Applied to Home cards and detail view artworks.