Commit Graph
4203 Commits
Author SHA1 Message Date
vicrodh 2b5366eb06 chore(deps): add discord-rich-presence 0.2.4
For Discord Rich Presence integration (#336).

cc
2026-05-07 01:20:27 -06:00
vicrodh 9bab69310b chore(deps): batch bump minor/patch versions
Manual batch instead of merging the 10 dependabot PRs individually so
Cargo.lock and package-lock.json each see a single coherent update.

Rust (src-tauri/Cargo.lock):
- tokio 1.52.1 -> 1.52.2 (#401)
- rustls 0.23.37 -> 0.23.40 (#400)
- tauri-plugin-opener 2.5.3 -> 2.5.4 (#399)
- tauri-plugin-deep-link 2.4.8 -> 2.4.9 (#397)
- notify-rust 4.16.1 -> 4.17.0 (#394)

npm (package.json + package-lock.json):
- @sveltejs/kit 2.58.0 -> 2.59.1 (#398)
- @tauri-apps/plugin-dialog 2.7.0 -> 2.7.1 (#396)
- jsdom 29.1.0 -> 29.1.1 (#395)
- @tauri-apps/api 2.10.1 -> 2.11.0 (#393)
- svelte-check 4.4.6 -> 4.4.8 (#392)

cargo check + npm run check both clean.

cc
2026-05-07 00:19:45 -06:00
vicrodh a9c17365e6 Merge feature/internal/artist-header-gradient into pre-release
Apply the album/artist detail backdrop overhaul:
- Blurred artwork background instead of a flat palette gradient
- Gradient extended to artist detail, sharing the existing toggle
- Image cache plumbed through extractPalette so artist images bypass
  Qobuz CDN's missing CORS headers and Vibrant can read pixels
- Per-view height tuning so each fade lands above its own toolbar
- Artist jump-nav transparent at rest, gains its background only when
  pinned (IntersectionObserver sentinel)
- Reactive text + action button overrides extended to artist view

cc
2026-05-07 00:13:55 -06:00
vicrodh 55a200a8da feat(detail-views): blurred artwork backdrop and artist-page gradient
Promote the album/artist detail header gradient from a flat
palette-color fade to a Qobuz/Feishin-style blurred backdrop, and
extend it to the artist page.

- artworkPalette: route the URL through getCachedImageUrl before
  handing it to Vibrant. Qobuz CDN sends Access-Control-Allow-Origin
  for album covers but not artist images, which tainted the canvas
  Vibrant uses and made pixel reads fail. The cache resolves to a
  local asset:// path and primes the disk store for next time.
- AlbumDetailView and ArtistDetailView: ::before now renders the
  artwork itself with filter:blur + saturate, masked to fade into the
  theme background; ::after carries the contrast scrim so the blur
  layer doesn't muddle it. The CSS variable --art-image-url carries
  the locally-resolved URL so the backdrop image is served from disk
  instead of being re-downloaded.
- Heights tuned per view: artist's header is taller (image + bio +
  actions) than album's, so artist gets 340 × scale(1.1) and album
  gets 280 × scale(1.15) so each fade lands above its respective
  toolbar / JUMP TO bar.
- ArtistDetailView jump-nav: now transparent at rest and gains its
  background only when stuck (IntersectionObserver against a 1px
  sentinel placed right above it). Stops the bar from cutting a
  flat-color stripe across the gradient when it's not pinned.
- Reactive text + action button overrides on .has-art-bg apply to
  the artist page too: back button, bio text, bio toggle, and the
  action-btn-circle family read in white-tinted variants over the
  colored backdrop.

cc
2026-05-07 00:12:58 -06:00
vicrodh eed2f4f636 refactor(playlist-detail): unify selection on multiSelectMode and remove visual flash
Custom sort had two parallel selection systems: an inherent per-row
checkbox column (selectedTrackKeys) used solely to enable Move Up/Down
in a separate batch toolbar, and the toolbar's multiSelectMode for
bulk actions. They had different stores, different widths, and one
disabled the other. Consolidate onto multiSelectMode; drag and the
existing per-row reorder controls cover ordering, and the wrapper
propagates dragTrackIds for group moves.

- Drop selectedTrackKeys, isSelectionMode, toggleTrackSelection,
  clearSelection, selectAllTracks, moveSelectedUp, moveSelectedDown
  and the associated batch-controls toolbar / col-checkbox header
  branch / inherent per-row checkbox.
- Move the multi-select checkbox out of TrackRow into the wrapper so
  the column order in custom sort reads [checkbox][reorder][TrackRow];
  TrackRow's internal track-checkbox is hidden via :global so its
  click + keyboard handlers stay wired through selectable=true.
- Reserve 24px for the row-checkbox and the header col-select-all at
  all times and animate opacity (180ms) instead of width. Toggling
  select no longer reflows the columns to its right; the checkbox
  fades inside a slot that was always there.
- Stable each-key for virtualized rows: use track identity
  (`<namespace>-<id>-<number>`) instead of `${visibleTrackRange.start
  + loopIdx}-${track.id}`. The old key mixed scroll state into the
  identity, so a re-render that bumped trackListScrollTop reused row
  instances against new data — rows showed mismatched track / # /
  album combinations until the next paint.

cc
2026-05-06 23:26:01 -06:00
vicrodh 8977e721c6 refactor(album-detail): align tracklist columns and smooth select-mode toggle
- Reduce table-header left padding to 4px and switch the # column to
  text-align: left so track numbers line up with the bare quality badge
  in the toolbar above. Track rows in the album-detail context get the
  same 4px left padding via a :global override on .track-list and the
  track-number column switches to flex-start so the row stays aligned.
- Match the select-all checkbox width to the track-row checkbox (24px)
  and shrink the checkbox itself to 15x15 so the header checkbox lands
  on the same x as the per-row ones.
- Always render the select-all and track-row checkbox containers and
  animate width + opacity in/out (180ms ease) so toggling select mode
  no longer pops the column into existence.
- Move the AlbumMenu (...) trigger to the end of the action row so the
  bulk-action triggers (CassetteTape, multi-select) sit before the
  overflow menu — same convention as other detail views.

cc
2026-05-06 22:21:51 -06:00
Victor RH ecb8f96fa1 Merge pull request #390 from afonsojramos/bugfix/internal/track-change-click
fix(audio): eliminate macOS track-change crackle
2026-05-06 22:09:41 -06:00
Victor RH 1fdf0bfcb2 Merge pull request #389 from vicrodh/dependabot/cargo/src-tauri/openssl-0.10.79
chore(deps): bump openssl from 0.10.78 to 0.10.79 in /src-tauri
2026-05-06 22:09:31 -06:00
vicrodh f4c9b71366 Merge feature/internal/album-header-gradient into pre-release
Album detail UX overhaul:
- Artwork-derived gradient backdrop on the album header (opt-out toggle in
  Settings > Appearance > Album header gradient).
- Bare mode for QualityBadgeStatic so quality reads inline without a
  pill container.
- Shrink label/awards sidebar by ~20%.
- Inline tracks count and duration with the metadata row, format duration
  as Xh Ym Zs.
- Plumb album description with sanitized HTML and click-to-expand.
- Tracklist toolbar with track search and inline quality badge.
- Featured artists and explicit (parental warning) badge in the artist
  line.
- Reactive divider between header and tracklist (visible only when the
  gradient is off), reactive action button colors, metadata centering
  when there is no description.

cc
2026-05-06 22:08:33 -06:00
vicrodh 5b05b6fae3 feat(album-detail): tracklist toolbar, featured artist line, explicit badge, header polish
Inline UX iteration for the album detail header and tracklist:

- Tracklist toolbar above the table: bare quality badge on the left,
  borderless search input on the right (filters tracks by title or
  artist), with min-height alignment so badge and input share a baseline.
- Artist line renders the explicit badge (when parental warning) before
  the main artist and appends featured artists separated by a dot, each
  clickable to its discography. Featured names use a quieter weight and
  size to keep the main artist primary.
- Reactive divider between header and tracklist: visible only when the
  artwork gradient is off; when on, the gradient itself provides the
  demarcation.
- Symmetric vertical breathing room around the toolbar (20px top to
  optically compensate for the 40px-tall table-header below) and the
  same offset on the album sidebar so the LABEL section aligns with the
  toolbar row.
- Action buttons in the header (play/shuffle/heart/etc.) react to the
  gradient backdrop with white-tinted strokes and a brighter hover.
- Metadata column vertically centers against the artwork when the
  album has no description.

i18n keys added in all five locales: tracklist.searchPlaceholder,
tracklist.noMatches.

cc
2026-05-06 22:08:06 -06:00
vicrodh e8d58196c5 feat(album): plumb featured artists and parental warning
Add Album.parental_warning and Album.artists (with the new AlbumArtist
struct) to qbz-models so the /album/get response surfaces both fields.
Mirror them in the QobuzAlbum / AlbumDetail TypeScript types and
populate featuredArtists (filtered to non-main contributors) and
parentalWarning in convertQobuzAlbum. Wire onFeaturedArtistClick from
the route so featured artist names route to their discography.

cc
2026-05-06 22:06:54 -06:00
vicrodh e1447159bb feat(album-detail): plumb album description with click-to-expand toggle
Pass Qobuz album.description through adapter and AlbumDetail prop, then
render below the metadata row with a 2-line clamp and a Read more / Show
less toggle. Description HTML (Qobuz CMS markup with paragraph and
emphasis tags) is sanitized through the existing allowlist before being
inserted as HTML so the formatting renders cleanly without exposing
data-* attributes.

cc
2026-05-06 21:01:59 -06:00
vicrodh 7e87de4352 refactor(album-detail): inline tracks/duration in metadata row
Folds the standalone .album-quality and .album-stats lines into the
existing date/label/genre row, since the quality string moves to the
tracklist toolbar in a follow-up. Plumbs durationSeconds through
AlbumDetail and the Qobuz adapter, plus bitDepth/samplingRate so the
inline QualityBadgeStatic can render the same tier label as Mixtape and
Collection rows. Total duration renders as Xh Ym Zs (no leading zeros).

cc
2026-05-06 20:52:15 -06:00
vicrodh 16e134fba1 refactor(album-detail): shrink label/awards sidebar by ~20%
The right-side sidebar was carrying too much weight versus the tracklist.
Drops the column from 280px to 224px and proportionally reduces avatar,
icon, and font sizes so the label/awards stack reads as supporting
metadata rather than competing with the track table.

cc
2026-05-06 20:49:00 -06:00
vicrodh a2866f8d96 feat(quality-badge): add bare prop for inline (no-container) usage
When bare={true}, the badge drops its background, border, padding,
min-width, and fixed height so it can render inline above a tracklist
toolbar without visually competing with the table header.

cc
2026-05-06 20:46:20 -06:00
vicrodh 0d784545eb feat(album-detail): album header gradient backdrop from artwork palette
Extracts the dominant color from album artwork (via node-vibrant) and
paints it as a soft top-down gradient behind the album header, with an
appearance toggle to opt out. Adds the .has-art-bg state class plus
contrast-lifted overrides for back button, info text, and artist link.

cc
2026-05-06 20:40:18 -06:00
vicrodh 19f33cb157 Merge feature/internal/local-library-followups into pre-release
Local Library follow-ups:
- fix tracks tab empty on initial load (active tab loader trigger)
- relocate edit-tabs button into folder management toolbar with sort icon
- give Albums tab the full Folders action bar (group/filter/sort/view/select)
- include Plex albums in metadata-grouped Albums view
- show Albums in Edit Library Tabs modal
- declare standard line-clamp alongside -webkit-line-clamp

cc
2026-05-06 18:56:36 -06:00
vicrodh 4bbff02c4b style: declare standard line-clamp alongside -webkit-line-clamp
svelte-check warned about missing standard property next to the
-webkit-line-clamp prefix in 9 spots across 8 components. Add the
standard line-clamp declaration with the same value to clear the
warnings without changing rendering.

cc
2026-05-06 18:56:18 -06:00
vicrodh ebe98ab4b8 fix(library): show Albums in Edit Library Tabs modal
The modal sanitizer filtered tab_order against availableTabs, which
omitted 'albums', so the metadata-grouped Albums tab never appeared
in the order/visibility editor. Add it to both availableTabs and
DEFAULT_TAB_ORDER to match the backend default and LocalLibraryView.

cc
2026-05-06 18:56:15 -06:00
vicrodh a1197983cf fix(library): include Plex albums in metadata-grouped Albums view
loadMetadataAlbums only queried the local SQLite library, so the new
Albums tab was missing all Plex-cached content while the Folders tab
showed it. Mirror the merge pattern from loadLibraryData: fetch Plex
cached albums in parallel and concatenate after mapping.

cc
2026-05-06 18:56:09 -06:00
Afonso Jorge Ramos e93fcaec99 fix(audio): defer host-stream drop on Stop to reuse device across track changes 2026-05-07 02:32:39 +02:00
vicrodh ea98a34f8e feat(library): give Albums tab the full Folders action bar (group/filter/sort/view/select)
Albums was a stripped-down VirtualizedAlbumList over metadataAlbums with
no controls. Folders had the full toolbar (group dropdown, quality/format
filter, sort, view toggle, multi-select with bulk actions, alpha index).
The user wants Albums to be 1:1 with Folders, no reinvention.

Implementation:
- Extracted the album-controls block into a Svelte 5 snippet
  `albumControls(alphaGroups)` declared at the top of the library-view
  template. The snippet body references the existing shared state
  (group/filter/sort/view/select); only `alphaGroups` differs per source.
- Extracted the body of `filteredAndGroupedAlbums` into a helper
  `buildFilteredAndGroupedAlbums(source)` and added a parallel
  `filteredAndGroupedMetadataAlbums` derived for the Albums tab.
- Generalized album-selection state: `currentAlbumsSource` switches
  between `albums` and `metadataAlbums` based on activeTab; Select All
  and the keyboard shortcut now operate on the active list. Bulk action
  handlers look up album IDs across both lists (the two ID spaces don't
  overlap) so playlists/queues/mixtapes work from either tab.
- Moved the album BulkActionBar out of the Folders branch so it renders
  once for either tab.
- handleTabChange clears in-flight album selection when crossing tabs.
- metadataAlbums state declarations moved up to live next to `albums`
  because the new selection derivations reference them earlier in scope.

cc
2026-05-06 18:10:04 -06:00
vicrodh 9130bdc913 refactor(library): relocate tab order editor into folder management toolbar
The gear icon mounted next to the tab buttons inside .jump-links was
visually noisy and overloaded a UX area meant for navigation. Moved it
into the folder-actions toolbar of the Library Settings panel, alongside
the existing folder management icon-btns (FolderPlus, PenLine, Trash2),
where ordering controls sit naturally. Swapped the icon from Settings
(generic) to ListOrdered (semantically a list-ordering action).

Removed the orphaned .jump-link.tab-edit-btn CSS rule.

cc
2026-05-06 18:01:06 -06:00
vicrodh fa38c8a552 fix(library): trigger active tab loader after preferences settle on mount
The visible-tabs rework moved tab order behind LibraryPreferences, so the
initial activeTab can be any of folders/albums/artists/tracks. Mount only
called loadLibraryData (folders + stats), leaving Tracks/Artists/Albums
empty until the user manually clicked a different tab and back.

Extracted the lazy-load switch from handleTabChange into
ensureActiveTabDataLoaded() and call it from both handleTabChange and
onMount once preferences (and therefore activeTab) have settled. Folders
needs no branch because loadLibraryData populates it.

cc
2026-05-06 17:59:20 -06:00
vicrodh 2b8e1a66e1 Merge branch 'feature/internal/local-library-tab-order' into pre-release
Includes Phase 2 (rename Albums to Folders + per-user tab order
preferences) and Phase 3 (new metadata-grouped Albums view + V2
commands + frontend wiring) of the Local Library design.

cc
2026-05-06 17:35:08 -06:00
vicrodh 8426783990 Merge branch 'bugfix/internal/local-library-artwork-per-track' into pre-release
cc
2026-05-06 17:35:08 -06:00
vicrodh 863e5dc86f feat(local-library): add metadata-grouped Albums tab
Wires the new Albums tab in LocalLibraryView via the metadata-grouped
V2 commands. Default tab order in LibraryPreferences now includes
'albums' between 'folders' and 'artists'; existing users get the new
slot via sanitizeLibraryPreferences's backfill of missing default tabs.

LocalAlbum interface gains the optional source_folders field exposed by
v2_library_get_albums_metadata. fetchAlbumTracks branches on the album
record's source_folders presence: metadata rows fetch via
v2_library_get_album_tracks_metadata, folder rows keep using
v2_library_get_album_tracks. No separate viewMode plumbing is needed
because the discriminator travels on the row itself.

The Albums tab renders through the same VirtualizedAlbumList component
as Folders (single ungrouped group, no controls in this initial pass),
loaded lazily on first visit via loadMetadataAlbums. Cache invalidates
on every loadLibraryData, post-scan, and library-clear so a re-scan
surfaces fresh metadata grouping.

cc
2026-05-06 17:27:40 -06:00
vicrodh bf02556837 feat(commands_v2): expose metadata-grouped Local Library album queries
v2_library_get_albums_metadata and v2_library_get_album_tracks_metadata
mirror the existing folder-grouped commands but call the new
get_albums_metadata_grouped and get_album_tracks_metadata. Frontend
wiring lands in the next commit.

cc
2026-05-06 17:16:28 -06:00
vicrodh 54074a5580 feat(library): add metadata-grouped album queries
New album_grouping module exports the SQL CASE-WHEN expression for the
metadata group key shared by both new queries. The expression has three
priorities: (1) usable album tag => album|artist key, (2) fallback to
existing folder-based album_group_key, (3) orphan bucket
'__unknown_album__'.

get_albums_metadata_grouped runs the grouped query and returns
LocalAlbum rows with directory_path empty and source_folders populated
with the comma-separated list of contributing folders. Pinned 'Unknown
Album' to the bottom via ORDER BY.

get_album_tracks_metadata fetches all tracks belonging to a metadata
group, ordered by disc/track number.

Adds idx_local_tracks_album_lookup index on (album, album_artist,
artist) to keep the GROUP BY index-backed at 16k+ track libraries.

LocalAlbum gains an optional source_folders field (None for folder-
grouped rows; Some(comma-separated) for metadata-grouped rows). The
existing folder-grouped query continues to populate it as None so no
downstream consumer breaks.

Hidden filter (album_settings.hidden) does not apply to metadata-
grouped rows in this iteration; revisiting requires extending
album_settings to support metadata keys.

cc
2026-05-06 17:10:51 -06:00
vicrodh 1a5c9b81ce feat(local-library): rename Albums tab to Folders, add tab order modal
Frontend half of Phase 2: rename the existing 'Albums' tab (which has
always shown folder-grouped data) to 'Folders' so the label matches
the behavior. NorinB filed issue #380 because of this discrepancy.

Wires LocalLibraryView to the new per-user library_preferences backend
(landed in the previous commit). LibraryEditModal is a clone of
FavoritesEditModal trimmed to the Library prefs scope (tab_order +
hidden_tabs only).

Tabs render in the user-defined order, hidden tabs are skipped. Active
tab on session start is the first visible tab. Default order matches
the current tab list with 'folders' in place of the old 'albums';
Phase 3 will add the new metadata-grouped 'albums' to the default.

i18n updated in 5 locales: new library.folders key + library.editTabs
block. Existing 'Library Folders' settings header moved to the new
library.folderListTitle key to free up library.folders for the tab
label.

cc
2026-05-06 16:57:38 -06:00
vicrodh a5ab9965d0 feat(library): add per-user Library tab preferences store
Mirrors the FavoritesPreferences pattern so the Local Library can have
a user-configurable tab order and hidden-tabs list, persisted per user
in library_preferences.db. Adds the LibraryPreferences struct, the
single-row SQLite table, the LibraryPreferencesState lifecycle, and
the V2 get/save commands.

Default order matches the current LocalLibraryView tab list with the
renamed 'folders' tab in place of the old 'albums' tab. Phase 3 will
add 'albums' (the new metadata-grouped view) to this default.

Frontend wiring lands in the next commit on this branch.

cc
2026-05-06 16:50:27 -06:00
vicrodh 523cce46fd fix(library): apply per-track artwork fix to second scan loop, suppress legitimate users
A code review of the previous commit surfaced a second scan loop in
legacy_compat.rs that had the same destructive update_album_group_artwork
pattern. Apply the same fix: per-folder cache + drop the destructive
call. Same intent and shape as the main loop fix.

The deprecated marker also surfaced three callers that are NOT bugs
and stay as-is, with #[allow(deprecated)] + a comment to make the
intent explicit:
- CUE ingestion: a CUE sheet is one physical album, so a single cover
  for all CUE tracks is semantically correct.
- v2_library_set_album_artwork: user-initiated set-cover-for-album, by
  design propagates to all tracks.
- Discogs backfill: only fires for albums with no existing artwork, so
  there is nothing per-track to destroy.

cc
2026-05-06 16:38:14 -06:00
vicrodh 7eba2749f5 fix(library): preserve per-track artwork during scan, cache folder lookups
The scan loop called update_album_group_artwork after every successful
track insert, which propagated one track's artwork to every track in
the same group. Tracks with unique embedded covers ended up sharing
whichever cover was processed last. Drop the call; per-track artwork
is resolved correctly by extract_artwork (embedded) + find_folder_artwork
(cover.jpg fallback) before insert_track.

Replace the propagation's caching effect with an in-memory per-folder
HashMap that memoizes find_folder_artwork for the duration of the scan
run, avoiding repeated folder walks for tracks without embedded art.

Mark update_album_group_artwork #[deprecated] so no future caller
reintroduces the bug. Keep it compilable.

Existing libraries already have collapsed artwork paths in the DB and
need a re-scan to recover per-track covers; called out in release notes.

cc
2026-05-06 16:32:27 -06:00
vicrodh 74289fb682 Merge branch 'feature/internal/qconnect-honor-peer-seek-on-local-renderer' into pre-release
cc
2026-05-06 14:21:56 -06:00
vicrodh 6afbc94171 fix(qconnect): honor peer-controller seeks when qbz is the active renderer
Fixes #387: seeks issued from the official Qobuz mobile app while qbz
acts as the active renderer were silently dropped — audio kept playing
from the original position while the controller's progress bar locked.

Two independent gates were swallowing the command between the
WebSocket and the audio thread; either alone is enough to break the
flow, so both move together.

1. Upstream filter (crates/qconnect-app/src/app.rs)

   The is_echo guard treated every SET_STATE with playing_state=None
   AND current_track=None as a pure server echo and skipped emitting
   RendererCommandApplied. iOS sends a real seek as exactly that
   shape (only current_position_ms set, the rest unchanged), so the
   command never reached the bridge handler.

   Add current_position_ms.is_none() to the guard. Pure echoes still
   match (server echoes carry only next_track); position-only seeks
   pass through.

2. Downstream gate (src-tauri/src/qconnect/corebridge.rs)

   The seek branch was conditioned on peer_renderer_active — i.e. it
   only acted when qbz was the controller observing a peer renderer,
   never when qbz was the renderer itself. Removed; the existing
   is_echo_reset and abs_diff > 2 checks already filter the redundant-
   seek case the gate was originally added to defend against (147bcbd7).
   Drop the now-unused is_peer_renderer_active import.

Verified manually: seeks from iPhone (renderer_id=3) targeting qbz
(renderer_id=2) at ~1/4 and ~5/6 of a 150s track now translate to
local audio jumps to the correct positions.

cc
2026-05-06 14:21:27 -06:00
vicrodh 86e22bf886 Merge branch 'feature/internal/seekbar-uniform-poll-cadence' into pre-release
cc
2026-05-06 12:51:32 -06:00
vicrodh 1e16ec52f5 fix(playback): poll player state at uniform 250ms cadence
The previous adaptive scheme dropped to 5s when idle and 1s when
paused-with-track. PLAYBACK_STATE_WAKEUP catches synchronous V2
mutations (pause/resume/stop/seek) instantly, but play_data() is
asynchronous: it queues an AudioCommand::Play over a channel and
returns. The audio thread sets is_playing/start_playback_timer only
after decoder + device init (500-2000ms), so the wakeup fires too
early — the loop reads stale state, decides 'idle' or 'paused', and
sleeps for the full long branch before catching the actual transition.

current_position() is wall-clock based (now - playback_start_millis),
so by the time the next idle tick arrives, position is already past
second 1 and the seekbar appears to jump straight to 2+.

Polling uniformly at 250ms catches the async transition within one
cadence and keeps the seekbar showing 0:00 -> 0:01 -> 0:02 from the
start. Cost is negligible (~10 atomic reads x 4 Hz), and should_emit
filters every no-change iteration so the frontend wire is unaffected.

cc
2026-05-06 12:34:01 -06:00
vicrodh b0716962e0 Merge branch 'feature/internal/seekbar-cold-start-latency' into pre-release
cc
2026-05-06 12:26:11 -06:00
dependabot[bot] 510db740a4 chore(deps): bump openssl from 0.10.78 to 0.10.79 in /src-tauri
Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.78 to 0.10.79.
- [Release notes](https://github.com/rust-openssl/rust-openssl/releases)
- [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.78...openssl-v0.10.79)

---
updated-dependencies:
- dependency-name: openssl
  dependency-version: 0.10.79
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-06 18:09:48 +00:00
vicrodh 8288350559 feat(playback): wake state polling loop on V2 playback mutations
The background task in lib.rs that emits playback:state to the frontend
sleeps adaptively: 250ms while playing, 1s when paused-with-track, and
5s when idle. The 5s idle branch leaves the seekbar blank for several
seconds on cold start because the loop only reads new state — and only
then emits — after the sleep ends, even though audio is already playing.

Add a tokio::sync::Notify (PLAYBACK_STATE_WAKEUP) shared via the
existing commands_v2::helpers module. Race the sleep against
PLAYBACK_STATE_WAKEUP.notified() with tokio::select. Each V2 command
that mutates playback state (play_track, pause, resume, stop, seek)
calls notify_one after the underlying bridge call succeeds, so the
loop wakes within milliseconds and emits the new state immediately.

notify_one stores up to one permit when no waiter is parked, so a
notification fired between iterations is delivered on the next select.

Make commands_v2::helpers a pub(crate) module so the static is
reachable from lib.rs and the V2 command modules.

cc
2026-05-06 11:42:21 -06:00
vicrodh 0aa923e32a Merge branch 'feature/internal/offline-cache-manager' into pre-release
cc
2026-05-06 10:49:52 -06:00
vicrodh 534c81016d feat(offline_cache_manager): cache size limit modal
Adds a pencil icon next to the usage bar in the manager's stats header.
Click opens a Modal (shared Modal.svelte) with a number input bound to
the current limit in GB plus Save / Cancel buttons. Validates >= 5 GB
client-side and surfaces a toast on success or error. Save calls
v2_set_offline_cache_limit which now persists across restarts. New
i18n keys land in all five locales (en/es/de/fr/pt).

cc
2026-05-06 10:24:55 -06:00
vicrodh 1b07036454 feat(offline_cache): enforce cache size limit on new track downloads
Pre-flight check_cache_limit reads the current total_size_bytes and
returns an informative error if the cache is already at or over the
configured limit. Wired into v2_cache_track_for_offline (single) and
v2_cache_tracks_batch_for_offline (batch). Re-download paths are not
gated since they target tracks already counted toward the total. The
check intentionally does not predict the new track's size; preventing
cache from growing further once at the limit is enough for v1.

cc
2026-05-06 10:23:27 -06:00
vicrodh 362f3154ac feat(offline_cache): persist cache size limit across restarts
Adds cache_limit_bytes column to offline_settings (idempotent ALTER
TABLE) plus get/set methods on the OfflineState store. v2_set_offline_cache_limit
now writes the value to disk in addition to the in-memory cache_state.
At session activation, OfflineCacheState::apply_persisted_limit reads
the stored value and seeds the in-memory limit so the user's chosen cap
survives app restarts. Legacy installs with NULL persisted value keep
the in-memory 5 GB default seeded by init_at.

cc
2026-05-06 10:23:18 -06:00
vicrodh 7ca515be7e feat(offline_cache): bump default cache size limit from 2 GB to 5 GB
Hi-res FLAC tracks at 24-bit/192 kHz routinely run 60-100 MB each, so a
2 GB cap held only ~25-30 album downloads before users had to clear
space. 5 GB is the new floor for the default; users can raise it from
the manager view and the value persists across restarts (next commits).

cc
2026-05-06 10:20:50 -06:00
vicrodh e71cd71f60 feat(i18n): rename Re-download album to Re-download all tracks
The old label implied the entire album would be downloaded, but the
action only re-fetches tracks that were previously cached. Updated in
all 5 locales (en/es/de/fr/pt). The translation key stays
offlineManager.row.actions.redownloadAlbum.

cc
2026-05-06 10:19:03 -06:00
vicrodh a1b29e4f72 feat(offline_cache_manager): live progress rings on album and track rows
Replaces the static '⟳' / '…' status glyphs with the same SVG progress
ring used by DownloadButton. Album-row ring shows the average progress
across currently-downloading tracks; track-row ring tracks per-track
progressPercent. Queued state renders an indeterminate spinner
(75%-fill rotating). Same pattern, no new dependencies.

cc
2026-05-06 10:13:30 -06:00
vicrodh f72f21838e feat(offline_cache_manager): show album cover artwork in rows
Backend cache rows do not store an artwork URL, so on each fresh load we
hydrate per album_id via v2_get_album in batches of 8 with parallel
Promise.all and an in-memory cache. Failures per album are tolerated and
recorded as null so the row falls back to the placeholder. Re-rollups
triggered by progress events and remove operations re-apply the cache so
covers do not flicker on state updates.

cc
2026-05-06 10:11:33 -06:00
vicrodh 695063916f feat(offline_cache_manager): expand albums by default
Auto-expand every album's track list on each rollup so the user sees
per-track status (size, badges, action menus) without needing to click
each row open. The set is sticky for the session — collapsing an album
manually persists until the next loadAll, and a fresh load re-expands.

cc
2026-05-06 10:08:43 -06:00
vicrodh 23995067a9 fix(offline_cache_manager): correct fully-cached batch response shape
v2_check_albums_fully_cached_batch returns a HashMap<String, bool> which
serializes as a JSON object, not an array of tuples. The previous typing
made new Map(result) silently throw with object-is-not-iterable, which
aborted loadAll before buildRollup ran and forced the empty state on
every load. Use Record<string, boolean> + Object.entries instead, and
log loadAll errors so future shape mismatches do not fail silently.

cc
2026-05-06 10:06:25 -06:00