Remove unnecessary get() wrapper from svelte/store when calling the
i18n translate function. Direct $t() works correctly in regular
functions (only $derived is problematic per ADR-001). Removes unused
get import from GenreFilterButton, FavoritesView, PlaylistDetailView,
PurchaseAlbumDetailView, PurchasesView, and SettingsView.
Add dev-ghost-doctor.sh (t-shadow check + runtime clean + restart) and
dev-ultra-restore.sh (deep clean with --full/--no-start/--check flags).
Improve check-no-t-shadow.sh patterns for broader t-variable detection.
Clean-dev-runtime now runs guardrail check automatically. Remove --force
from vite dev. Add npm script shortcuts for all new tools.
- Allow dead_code on TokenValidationResponse.code (deserialized but not read)
- Remove unused MusicProvider import in commands_v2.rs
- Restore #[allow(non_snake_case)] on v2_plex_cache_get_tracks
- Replace drop(tokens) with let _ = tokens (drop on reference is no-op)
Mirror the album cover context menu options: right-clicking the artist
photo now shows Open in Browser and Save As below the existing
custom image options, separated by a divider.
Detect maximized state via Tauri window API. When the window is not
maximized (floating), apply 8px border-radius and a soft box-shadow.
Body/html backgrounds set to transparent so corners show through
the Tauri transparent window.
Remove isFavoriteOverride={true} that forced all hearts filled,
preventing the toggle from having any visible effect. Subscribe to
favoritesStore so unfavorited tracks animate out (slide+fade) and
are removed from the list after 300ms without requiring a refresh.
Shuffle favorite artists randomly so all get a fair chance (was
deterministic, first artist dominated). Cap at 2 albums per artist.
Shuffle final results for variety on each load.
Add #[allow(non_snake_case)] to V2 command functions and structs that
use camelCase parameter names for Tauri frontend compatibility.
Renaming to snake_case would break the frontend-backend contract.
Replace scroll-based section navigation with tab-based rendering where
only one section is visible at a time. Merge Offline Mode into Downloads
tab. Move Remote Control into Developer tab. Uncollapse previously
collapsed sections (Downloads, Storage, Developer). Add Developer to
nav tabs.
When the titlebar search is active, the search input inside SearchView
is redundant. Hide it so only the tabs and results remain. The query
still syncs from the titlebar via the shared searchState store.
Search input can now live in the titlebar instead of the sidebar,
controlled by a new toggle in Settings > Appearance. When the custom
titlebar is hidden or system titlebar is active, search stays in the
sidebar regardless of the toggle.
- New searchBarLocationStore with localStorage persistence
- TitleBar accepts search props and exposes focusSearch()
- Sidebar hides its search container when titlebar search is active
- Ctrl+F focuses the active search input in both modes
- Titlebar height increased from 32px to 36px to fit the search field
- Translations added to all 4 locale files (en, es, de, fr)
Rename t -> item/track in .map()/.filter() callbacks in
favoritesStore.ts and queueStore.ts to comply with ADR-001
and prevent intermittent PostCSS extraction failures.
cc
Expand the sidebar automatically when Ctrl+F is pressed while it's
collapsed. Also replace setTimeout with Svelte's tick() in focusSearch
to properly wait for DOM updates before focusing the input.
Closes#124
cc
Playlist search tab was invoking legacy 'search_playlists' command
which no longer exists. Updated both initial search and load-more
to use 'v2_search_playlists', matching the other search tabs.
Closes#129
cc
Previously, the section displayed the user's own favorite albums — the
same albums they had already marked as favorites. Now it resolves the
artists behind those favorite albums (and favorite tracks), fetches
their catalogs from the Qobuz API (parallel, max 8 artists, 25 albums
each), and filters out albums the user already has in favorites or
recently played. Results are cached in reco_album_meta for instant
future lookups.
Closes#122
After clicking Add to Local Library, the in-memory download state is
cleared so the progress bar and button disappear. Also triggers a
background scan of the added folder so the library indexes the new
files without requiring a manual refresh.
Download checkmarks now only show for the currently selected format.
In-memory store tracks formatId per album download, and the album
detail view checks track.downloaded_format_ids from backend against
the selected format dropdown. Switching quality hides checkmarks
from a different format's downloads.
Migrate downloaded_purchases to compound PK (track_id, format_id) so
the same track downloaded in MP3 and FLAC are tracked separately.
Album detail endpoint now returns downloaded_format_ids per track.
Album detail view now checks track.downloaded (from SQLite) in
addition to in-memory session status, so checkmarks survive restart.
Track numbers are no longer replaced by checkmarks -- the number
column always shows the track number (spinner only while downloading),
and the download column handles all status indicators.
Green checkmark on downloaded tracks now shows a download icon on
hover, allowing users to re-download in a different format or
location. Applied to both the Purchases track list and album detail.
When downloading a single track from the Purchases tab, if multiple
formats are available a popup lets the user choose before the folder
picker opens. Single-format tracks proceed directly as before.
Quality folder was being prepended to the destination path by the
frontend, resulting in Quality/Artist/Album/track. Now quality_dir
is passed as a separate parameter through the full stack and inserted
between album and filename by the backend path builder.
The metadata call used getPurchaseIds(1, 0) without a type filter.
With limit=1 the Qobuz API returns only the first type's total,
leaving the other at 0. Now fetches totals per type separately in
parallel so both counters are correct from mount.
Closes#112
Login responses were serialized as full objects (user_id, subscription,
tokens). Track play logs dumped the entire QobuzTrack/Track object which
may include signed CDN URLs. Now only log the minimum needed for
debugging (success booleans, track id + title).
This likely caused the 0x0.st 451 rejection when users tried to upload
their console logs for bug reports.
Both stores read getUserItem() at module load time when the userId is
still null, so they hit the unscoped localStorage key. After login,
writes go to the user-scoped key (qbz.{userId}.key) which is never
read back on the next restart.
Add rehydrate functions that re-read from localStorage after
setStorageUserId() + migrateLocalStorage() have run, matching the
existing resyncPersistedVolume() pattern.
Closes#113
The click-outside logic used `el && !el.contains(target)` which
short-circuits to falsy when submenu refs are null (not rendered),
preventing the menu from ever closing. Changed to `!el || !el.contains()`
so a null ref correctly counts as "outside". Also added missing
radioSubmenuEl to the check.
Closes#120
Right-click on album artwork shows context menu with:
- Add/Change/Remove custom album cover
- Open in browser
- Save as...
Also adds v2_save_image_url_to_file backend command.
cc
v2_library_set_custom_album_cover: resize, cache, store in db
v2_library_remove_custom_album_cover: delete files + db entry
v2_library_get_all_custom_album_covers: bulk load for frontend store
cc
- Add flac; keyword to all .desktop files (was only in deb template)
- Add StartupNotify=true to flatpak .desktop files (was missing)
- Add Keywords line to snap .desktop (was missing entirely)
- Unify shortDescription to match other .desktop Comment fields
cc
GDK_DPI_SCALE is a float multiplier that works on X11, XWayland, and
native Wayland. Previously it was only applied when force_x11 was
enabled, leaving native Wayland users with no way to compensate for
their DE scale factor from within the app.
- Move GDK_DPI_SCALE application out of the force_x11 block in main.rs
- Show the DPI Scale setting in the UI for all backends (not gated
behind the Force X11 toggle)
- GDK_SCALE remains XWayland-only (integer, not useful on Wayland)
- Setting is already persistent in graphics_settings.db
- Add window_width, window_height, is_maximized columns to window_settings DB
with automatic migration for existing installs
- Restore saved size on startup; restore maximized state after window creation
- Save non-maximized size on every Resize event
- Save maximized flag on CloseRequested
- Default remains 1280x800 for first launch
- Remove require_tos_accepted gate from all login commands
(v2_auto_login, v2_manual_login, v2_start_oauth_login, v2_login)
- Call accept_tos_best_effort after each successful session activation
so the value is persisted the moment login succeeds
- Re-initialize LegalSettingsStore in accept_tos_best_effort if torn
down (e.g. after factory reset that deletes legal_settings.db)
- Bootstrap ToS check now fails open (unwrap_or(true)) so saved
credentials still trigger auto-login after a factory reset
The frontend checkbox already gates the UI; the backend gate was
causing double-failures when the DB was unavailable post-factory-reset.
window.open() calls from social login providers are intercepted and opened
as related WebviewWindows (required on Linux/WebKitGTK). Both the main
window and all popups watch for play.qobuz.com?code_autorisation= so the
code is captured regardless of which window receives the final redirect.
Cleanup on timeout/cancel iterates all qobuz-oauth* windows.
- Save user_auth_token after successful OAuth login
- On bootstrap, restore session via saved token if no email/password creds
- If token is expired, clear it so user sees login screen normally
- Clear OAuth token on v2_clear_saved_credentials (logout)
- Add login_with_token() to api client (reuses OAuth /user/login step)
Also rename t->translateStore in NowPlayingBar.svelte (ADR-001 fix from Codex)
and improve clean-dev-runtime.sh to kill all stale processes and ports
Qobuz redirects to play.qobuz.com/discover?code_autorisation=XXX
not ?code=XXX. Both the navigation intercept and the /oauth/callback
API call were using the wrong parameter name, so the code was never
captured and the exchange request was malformed.
on_navigation returning false does not reliably block HTTP 302 redirects
on WebKitGTK (Linux). Closing the window from within the callback is
more reliable than calling close() from an async Tokio thread.
Changes:
- Always return true from on_navigation (never try to block)
- Close oauth window inside on_navigation callback after code capture
- Best-effort close via app.get_webview_window() after await (fallback)
- Add logging of every URL visited for debugging
- Idempotent: ignore duplicate code captures if callback fires twice
The subscribe handler in qobuzLegalStore persists acceptance fire-and-
forget. If the user checks the box and immediately clicks Login, the
async write can lose the race against require_tos_accepted() which reads
the DB synchronously. Force a synchronous persist in handleLogin() so the
backend gate always sees the correct value.
Previously all albums shared one scroll position key ('album'), so
opening any album would restore the scroll from the last album visited.
Now keyed as 'album:id', 'artist:id', 'playlist:id' so each item
tracks its own position independently. Back navigates to exact scroll,
fresh opens start at 0.
- Revert placement='top' to default bottom sticky across all views
- Content area already has padding-bottom: 100px in all views so no
track is ever hidden behind the bar
- AlbumDetailView/ArtistDetailView/WeeklySuggestView: bar now after tracks
- DynamicSuggestView/FavQView/TopQView: bar now after tracks (new views)
- LabelView: complete multi-select implementation with checkbox rows,
Select toggle button, and BulkActionBar after popular tracks list
AlbumDetailView:
- Select toggle button alongside AlbumMenu in action bar
- TrackRow selectable/selected/onToggleSelect props wired
- BulkActionBar with play next/later, add to playlist, add to favorites
- buildAlbumQueueTracks helper (album artwork and artist from album props)
ArtistDetailView (Popular Tracks section):
- Select button in section-header-actions alongside Play All
- Checkbox injected before track-number in custom track-row div
- click/keydown/contextmenu handlers respect multiSelectMode
- multi-selected highlight CSS
- BulkActionBar with add to favorites using toggleTrackFavorite
WeeklySuggestView:
- Select toggle button in track-controls (next to search)
- TrackRow selectable/selected/onToggleSelect props wired
- BulkActionBar with play next/later, add to playlist
- Reuses existing buildQueueTracks helper
- Add multi-select mode to PlaylistDetailView (independent of custom order mode):
Select toggle button in track-controls toolbar, multiSelectedKeys state,
checkboxes per row, multi-selected highlight, BulkActionBar with play
next/later, add to playlist, and bulk remove from playlist
- Bulk remove handles local tracks (one-by-one), Qobuz tracks with
playlistTrackId, and fallback trackId in a single invocation each
- Add onRemoveFavorites prop to BulkActionBar (HeartOff icon, danger style)
- Fix FavoritesView: replace Add to Favorites with Remove from Favorites
(tracks are already in favorites, remove makes sense there)
- Add BulkActionBar component (sticky-bottom bar with queue split button,
add to playlist, add to favorites, clear selection)
- Add selectable/selected/onToggleSelect props to TrackRow with checkbox UI
- Add .selected highlight and .track-checkbox CSS to TrackRow
- Extend VirtualizedTrackList with selectable/selectedIds/onToggleSelect props
- Wire FavoritesView tracks tab: Select toggle button in toolbar,
selectedTrackIds state, BulkActionBar with bulk queue/favorites/playlist handlers
- Add actions.select and actions.cancelSelection i18n keys to all 4 locales
- v2_bulk_add_to_queue: add multiple tracks to end of queue
- v2_bulk_add_to_queue_next: add multiple tracks as play next (reversed
to preserve selection order)
- v2_bulk_add_favorites: add multiple tracks to favorites via API then
update local cache atomically (lock not held across await points)
- v2_add_tracks_to_playlist and v2_remove_tracks_from_playlist already
accept Vec<u64> — no changes needed for playlist bulk actions
- Favorite tracks now display album art between track number and title,
matching the layout used in PlaylistDetailView (showArtwork=true)
- VirtualizedTrackList gains showArtwork and getArtworkUrl props
- AboutModal: add release codename 'La marrano de engorda' (1.1.15)
- README: remove stale KWin native title bar manual workaround section
After setting/removing custom artist images in ArtistDetailView
or LocalLibraryView, update the global store so all views
reflect the change immediately. Also fix LocalLibraryView to use
the resized thumbnail path from the backend result instead of
the original unresized file path.