Commit Graph
4534 Commits
Author SHA1 Message Date
vicrodh 917b2a8be8 fix(ui): avoid t shadow in mixtape detail
cx
2026-05-14 18:22:46 -06:00
vicrodh dac1b3a762 refactor(tauri): use qbz-app settings stores
cx
2026-05-14 17:08:57 -06:00
vicrodh 800447d33c refactor(qbz-app): add settings stores
cx
2026-05-14 17:08:45 -06:00
vicrodh dc74db6401 test(qbz-app): cover runtime state transitions 2026-05-14 11:54:16 -06:00
vicrodh 0877f4a5a2 refactor(tauri): use qbz-app runtime types 2026-05-14 11:52:43 -06:00
vicrodh d16ef003a5 refactor(qbz-app): move runtime core 2026-05-14 11:45:59 -06:00
vicrodh 4f008cd219 refactor(qbz-app): scaffold app crate 2026-05-14 11:40:19 -06:00
vicrodh 1bb8d53dd0 feat(graphics): NVIDIA Wayland compat toggle + watchdog fallback to CPU
Two-pronged improvement to the graphics safety net for users hitting
the NVIDIA + Wayland + WebKit GBM issue:

1) New opt-in toggle 'NVIDIA Wayland compatibility (experimental)' in
   Settings > Graphics. When ON:
   - Sets __NV_DISABLE_EXPLICIT_SYNC=1 to bypass libnvidia-egl-gbm's
     buggy implicit-sync paths that produce "Failed to create GBM
     buffer of size NxN: Invalid argument" on driver 550+.
   - Lifts the defensive compositing-mode shutdown that QBZ applies
     to NVIDIA-only Wayland sessions — the user is signaling the real
     fix is now in place, so the safety net is no longer needed.
   - Persisted in graphics_settings.db column nvidia_compat_mode (new,
     default off). Migrated via ALTER TABLE so existing installs don't
     pick up a behaviour change.

2) Reset-graphics CLI affordance, surfaced only when the toggle is on:
   inline copyable input + Copy button rendering `qbz --reset-graphics`
   (or `flatpak run com.blitzfc.qbz --reset-graphics` based on detected
   install method). Works around the global select-disable elsewhere
   in SettingsView via user-select: all + onclick={e.select()} + the
   explicit button. The boot watchdog still auto-reverts on crash, but
   this is the manual escape hatch when the user wants to recover
   without waiting for the watchdog to fire.

3) Watchdog recovery now lands in full CPU mode after ANY revert.
   The single-knob revert (hw_accel / dmabuf / preferred_gpu) still
   narrows the cause for the UI banner, but the safest post-crash
   state is full CPU mode (HW accel off + DMA-BUF off + preferred_gpu
   reset to auto). A partial revert can still hit the next crash if
   the failure was stack-wide (driver bug, missing extension); going
   all-the-way-CPU guarantees the user always reaches a paintable
   window after a recovery — they can selectively re-enable from the
   Settings banner.

4) preferred_gpu='software' now implies hardware_acceleration=off at
   env-var application time. Forcing Mesa llvmpipe while keeping
   WebKit GPU compositing on would just round-trip software pixels
   through a "GPU" path that has no GPU — nonsensical configuration
   that the watchdog had to clean up after. Now it's prevented at
   source.

i18n keys (en/es/de/fr/pt): settings.graphics.nvidiaCompatMode,
nvidiaCompatModeDesc, resetCmdTitle, resetCmdDesc;
actions.copiedToClipboard.

cc
2026-05-14 09:18:56 -06:00
vicrodh db45051c45 chore(security): patch file-type 16.5.4 ASF infinite-loop (GHSA, alert 56)
GitHub Security advisory for file-type < 21.3.1: malformed ASF input
with a zero-size sub-header triggers an infinite loop in the ASF parser
(medium severity). Our exposure is transitive — node-vibrant → jimp →
file-type — and only reached when extracting palette colors from album
artwork. Trusted Qobuz CDN + user-local library limit the practical
attack surface to near zero, but the alert is real and easy to close.

The upstream fix is a 6-line position-advance guard in core.js
(commit 319abf87). Cleanly backports to our 16.5.4 with identical
semantics. Applied via patch-package so the change survives every
`npm install` and is visible in the repo.

Direct upgrade to 21.3.1 wasn't viable: file-type went ESM-only at
17.0.0, and our jimp 0.22.x chain still uses CommonJS `require()` —
an override would have broken vibrant at runtime. Patch-package is
the right tool when upstream's only fix lives behind a breaking
module-system change.

Added `postinstall: patch-package` to package.json so the patch
applies automatically on fresh installs.

cc
2026-05-14 09:18:02 -06:00
Victor RH 2832213dac Merge pull request #424 from vicrodh/dependabot/npm_and_yarn/pre-release/types/node-25.7.0
chore(deps-dev): bump @types/node from 22.19.18 to 25.7.0
2026-05-14 09:02:29 -06:00
vicrodh 8b176a1848 chore(deps): bump discord-rich-presence 0.2.5 → 1.1.0 with API adjustment
DiscordIpcClient::new became infallible in 1.0 — it now returns Self
directly instead of Result<Self>. Failures surface at connect() time
where the catch already lives. Removed the now-spurious `if let Ok()`
wrap around the constructor.

Closes #428 (applied manually because the bump needed a code change
that Dependabot can't author).

cc
2026-05-14 08:59:02 -06:00
Victor RH 47c9c61894 Merge pull request #432 from vicrodh/dependabot/cargo/src-tauri/pre-release/tower-http-0.6.8
chore(deps): bump tower-http from 0.5.2 to 0.6.8 in /src-tauri
2026-05-14 08:46:48 -06:00
dependabot[bot] 875ad45268 chore(deps-dev): bump @types/node from 22.19.18 to 25.7.0
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.18 to 25.7.0.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.7.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 14:38:11 +00:00
Victor RH 7ee5359935 Merge pull request #429 from vicrodh/dependabot/npm_and_yarn/pre-release/vite-8.0.12
chore(deps-dev): bump vite from 8.0.10 to 8.0.12
2026-05-14 08:36:23 -06:00
Victor RH b165ddb073 Merge pull request #430 from vicrodh/dependabot/npm_and_yarn/pre-release/sveltejs/vite-plugin-svelte-7.1.2
chore(deps-dev): bump @sveltejs/vite-plugin-svelte from 7.0.0 to 7.1.2
2026-05-14 08:36:20 -06:00
Victor RH 69fbb1dff4 Merge pull request #426 from vicrodh/dependabot/npm_and_yarn/pre-release/tauri-apps/cli-2.11.1
chore(deps-dev): bump @tauri-apps/cli from 2.10.1 to 2.11.1
2026-05-14 08:36:16 -06:00
Victor RH 11854a9992 Merge pull request #427 from vicrodh/dependabot/npm_and_yarn/pre-release/tauri-apps/plugin-opener-2.5.4
chore(deps): bump @tauri-apps/plugin-opener from 2.5.3 to 2.5.4
2026-05-14 08:36:13 -06:00
Victor RH 0eae6a5609 Merge pull request #425 from vicrodh/dependabot/cargo/src-tauri/pre-release/tokio-1.52.3
chore(deps): bump tokio from 1.52.2 to 1.52.3 in /src-tauri
2026-05-14 08:35:07 -06:00
Victor RH dc6725b1da Merge pull request #431 from vicrodh/dependabot/cargo/src-tauri/pre-release/filetime-0.2.29
chore(deps): bump filetime from 0.2.27 to 0.2.29 in /src-tauri
2026-05-14 08:35:03 -06:00
Victor RH 91bfb05668 Merge pull request #433 from vicrodh/dependabot/cargo/src-tauri/pre-release/tauri-plugin-single-instance-2.4.2
chore(deps): bump tauri-plugin-single-instance from 2.4.1 to 2.4.2 in /src-tauri
2026-05-14 08:34:59 -06:00
vicrodh 921f8e1a49 fix(session): resume Qobuz streams at saved position without losing seek (#315)
Three bugs were stacking up to make session resume drop the saved offset
for streamed Qobuz tracks:

1. Position save was debounced, not throttled. Each playback tick reset
   the 5s timer, so the save never fired while a track played
   continuously — the persisted position froze at whatever value got
   written during the first quiet window after track start (typically
   ~2s). Switch to throttle: fire the first call immediately, then once
   per 5s window, with a tail call to flush the latest value when the
   window ends.

2. togglePlay tried to apply the restore offset via a separate cmdSeek
   right after cmdPlayTrack. For Qobuz streaming the audio thread's
   Seek handler bails when current_audio_data is unset — streaming
   uses current_streaming_source instead — so the seek was silently
   dropped and the track started at 0:00. Pass start_position_secs
   directly to v2_play_track. The streaming backend (legacy + V2
   player) waits for enough buffer to cover the offset plus 8s
   headroom, then eagerly decode-and-discards decoder output up to
   the offset before engaging the audio engine — no race, no
   first-pull underrun. Plex and local tracks keep the old play+seek
   path; they load full audio_data so Seek works as-is.

3. The seekbar showed 0:00 from app launch until the user hit play,
   even when a saved position was queued. setPendingSessionRestore
   now pre-positions currentTime when positionSecs > 0 so the UI
   reflects "paused at X" the moment the app opens.

Other plumbing:
- AudioCommand::PlayStreaming carries start_position_secs and
  content_length (used to estimate bytes-per-second for the resume
  buffer target).
- Player::play_streaming{,_dynamic} grow a start_position_secs
  parameter; QConnect and api_server callers pass 0 (remote-driven
  playback always starts fresh).
- cmdPlayTrack forwards startPositionSecs as a 5th optional arg.

cc
2026-05-14 02:18:59 -06:00
vicrodh b2960e8029 feat(graphics): smart GPU preference with boot watchdog and crash recovery
Solves the long-running performance complaints (especially Optimus
laptops where the dGPU silently hijacks WebKit). The end-to-end shape:

- graphics_detection enumerates physical GPUs from /sys/class/drm and
  cross-references EGL vendor JSONs under /usr/share/glvnd/egl_vendor.d
  to expose only stacks the running process can actually use. Works
  inside Flatpak/Snap because /sys is still mounted read-only and the
  sandbox-visible EGL vendor JSONs are the right thing to read.
- preferred_gpu column in graphics_settings.db (string: auto, integrated,
  discrete, software, or a specific PCI slot id). Migrated with
  ALTER TABLE ... DEFAULT 'auto'.
- env_vars_for_preferred_gpu returns Set/Unset actions applied at startup
  before WebKit init. Integrated/AMD path explicitly unsets
  __NV_PRIME_RENDER_OFFLOAD so KDE/Optimus sessions that pre-set it to 1
  no longer override the EGL pin and yank rendering back onto NVIDIA.
- Auto resolver: on hybrid systems (iGPU + dGPU both usable), Auto pins
  to the iGPU. Single-GPU and no-GPU systems remain no-op. The dropdown
  surfaces what Auto resolved to ("Auto (Intel Graphics)") so the user
  sees the decision without picking explicitly.
- boot_watchdog writes a pending.json before WebKit init listing the
  risky knobs in effect. Frontend calls v2_mark_boot_succeeded after
  first paint to clear it and promote to last-good.json. If the next
  launch sees pending without a success ack, it blames matching fields,
  reverts them in-DB, and increments per-knob failure counters; two
  consecutive failures lock the knob until the user clears it.
- Settings > Graphics gets a Rendering GPU dropdown (between HW accel
  and DMA-BUF) and a crash-recovery banner that lets the user re-enable
  any auto-disabled knob. Restart-required affordance shared with the
  other risky toggles.
- i18n keys for the dropdown, banner copy, and autoWithTarget label
  across all five locales.

cc
2026-05-14 00:36:52 -06:00
vicrodh 77754e168a feat(discovery): redesign Radio Station Lite cards to match iOS reference
180x180 card with a 114x114 artwork inset, palette extraction for the
background gradient and mix-blend-mode multiply on the PNG bezel so the
station artwork sits in front of a cohesive colored frame. RADIO label
uses padding-left compensation to align with the visible bezel edge,
not the raw container. Hover overlay blurs the artwork and surfaces a
play button — same pattern as the iOS reference shipped in c8cee326.

cc
2026-05-14 00:36:33 -06:00
vicrodh cd035a34a4 feat(graphics): detect hybrid NVIDIA+iGPU and surface GPU + DE in Diagnostics
autoconfig_graphics gains a hybrid Intel/AMD + NVIDIA branch that keeps
GSK renderer on Auto and DMA-BUF off so the iGPU handles compositing
without the dGPU getting dragged in. detect_gpu_name is exposed and
labels hybrid setups explicitly (e.g. "NVIDIA (driver 580) + Intel
(i915)"). RuntimeDiagnostics gets runtime_gpu_name and
runtime_desktop_environment so the Diagnostics panel can show what the
app sees at runtime, which is the first thing useful to triage
performance reports from log uploads.

cc
2026-05-14 00:36:27 -06:00
dependabot[bot] 5880744822 chore(deps): bump tauri-plugin-single-instance in /src-tauri
Bumps [tauri-plugin-single-instance](https://github.com/tauri-apps/plugins-workspace) from 2.4.1 to 2.4.2.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/fs-v2.4.1...fs-v2.4.2)

---
updated-dependencies:
- dependency-name: tauri-plugin-single-instance
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:38:24 +00:00
dependabot[bot] 7ce112bae1 chore(deps): bump tower-http from 0.5.2 to 0.6.8 in /src-tauri
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.5.2 to 0.6.8.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.5.2...tower-http-0.6.8)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-version: 0.6.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:38:07 +00:00
dependabot[bot] 620a393b2d chore(deps): bump filetime from 0.2.27 to 0.2.29 in /src-tauri
Bumps [filetime](https://github.com/alexcrichton/filetime) from 0.2.27 to 0.2.29.
- [Commits](https://github.com/alexcrichton/filetime/compare/0.2.27...0.2.29)

---
updated-dependencies:
- dependency-name: filetime
  dependency-version: 0.2.29
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:51 +00:00
dependabot[bot] b33ce39f63 chore(deps-dev): bump @sveltejs/vite-plugin-svelte from 7.0.0 to 7.1.2
Bumps [@sveltejs/vite-plugin-svelte](https://github.com/sveltejs/vite-plugin-svelte/tree/HEAD/packages/vite-plugin-svelte) from 7.0.0 to 7.1.2.
- [Release notes](https://github.com/sveltejs/vite-plugin-svelte/releases)
- [Changelog](https://github.com/sveltejs/vite-plugin-svelte/blob/main/packages/vite-plugin-svelte/CHANGELOG.md)
- [Commits](https://github.com/sveltejs/vite-plugin-svelte/commits/@sveltejs/vite-plugin-svelte@7.1.2/packages/vite-plugin-svelte)

---
updated-dependencies:
- dependency-name: "@sveltejs/vite-plugin-svelte"
  dependency-version: 7.1.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:50 +00:00
dependabot[bot] 9c83e1163e chore(deps-dev): bump vite from 8.0.10 to 8.0.12
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.10 to 8.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:44 +00:00
dependabot[bot] d53844c243 chore(deps): bump @tauri-apps/plugin-opener from 2.5.3 to 2.5.4
Bumps [@tauri-apps/plugin-opener](https://github.com/tauri-apps/plugins-workspace) from 2.5.3 to 2.5.4.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](https://github.com/tauri-apps/plugins-workspace/compare/http-v2.5.3...http-v2.5.4)

---
updated-dependencies:
- dependency-name: "@tauri-apps/plugin-opener"
  dependency-version: 2.5.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:27 +00:00
dependabot[bot] 7e7f3e057d chore(deps-dev): bump @tauri-apps/cli from 2.10.1 to 2.11.1
Bumps [@tauri-apps/cli](https://github.com/tauri-apps/tauri) from 2.10.1 to 2.11.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v2.10.1...@tauri-apps/cli-v2.11.1)

---
updated-dependencies:
- dependency-name: "@tauri-apps/cli"
  dependency-version: 2.11.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:20 +00:00
dependabot[bot] 060766e127 chore(deps): bump tokio from 1.52.2 to 1.52.3 in /src-tauri
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.2 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.2...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 05:37:18 +00:00
vicrodh b06580afc1 feat(local-library): unify album quality badge + add source icon across surfaces
The Library Albums tab carried three different quality-badge designs
across three rendering paths (grid card, list row, album detail
header), each rendered as raw text — visually disconnected from the
QualityBadgeStatic that Mixtape and Collection lists already use, and
the Plex source icon was inconsistent (present on grid cards, missing
on list rows and the album detail header).

Three coordinated swaps so all surfaces speak the same visual
language:

  - AlbumCardLibraryLite (Library grid + Artist Detail tile). The
    custom .quality-pill text badge is replaced by
    <QualityBadgeStatic iconOnly>, matching the Discovery V2 layout —
    icon-only square sitting to the right of a two-line title/artist
    stack. Adds format / bitDepth / samplingRate props so the badge
    can render its real tooltip ("FLAC: 16-bit / 44.1 kHz") instead
    of falling back to the parsed quality string.

  - VirtualizedAlbumList list mode. The plain-text .quality-badge
    span is replaced by <QualityBadgeStatic> full mode (the
    Mixtape/Collection badge), plus an inline <SourceBadge> when
    showSourceBadge is true so Plex / Qobuz Purchase / Qobuz Download
    surfaces are recognizable in the row. resolveSourceBadge maps
    the LocalAlbum.source string onto the narrow
    LibrarySourceBadge enum the card prop expects.

  - LocalLibraryView album detail header. The audio-specs strip now
    ends with a <SourceBadge> tied to selectedAlbum.source. Pairs
    naturally with the FLAC / 16-bit / 44.1 kHz pills already there
    and finally lets the user spot at a glance whether the album
    they're looking at came from a network Plex server or local
    disk.

Call sites in LocalLibraryView (the renderMetadataAlbumCell snippet
and the Artist Detail tile loop) pass album.bit_depth and
album.sample_rate / 1000 alongside the existing quality string so
the badge tooltips read accurate numbers. VirtualizedAlbumList
forwards the same trio to its inner AlbumCard.

cc
2026-05-13 22:23:25 -06:00
vicrodh b5918e1cbb fix(library): recover Plex album format + bit-depth when codec is missing
The Plex aggregation CTE used MAX(codec)/MAX(bit_depth) directly,
which produced two bugs that surfaced in the Albums grid for many
Plex-only libraries:

  - format = "Unknown". Plex frequently omits the stream-level codec
    when its analyzer hasn't seen the file yet. MAX(NULL) collapsed
    the row's format to NULL, which the frontend parser mapped to
    AudioFormat::Unknown — the Library grid then showed UNKNOWN even
    though the file's container was clearly "flac" / "mp3".
  - bit_depth = NULL. Plex commonly omits bitDepth on older releases
    entirely; the album row inherited the gap and the UI rendered
    "--/44.1" next to an otherwise valid quality string.

Two fallbacks applied to the plex_aggregated CTE only — the local
CTE is untouched because local indexing always writes a non-null
format string and a real bit depth via Symphonia:

  - format: COALESCE(MAX(codec), MAX(container)). The `container`
    column is populated for the same media and typically carries the
    same value ("flac", "mp3"), so it's a safe fallback when codec
    is fully NULL across the album.
  - bit_depth: COALESCE(MAX(bit_depth), CASE WHEN <lossless> AND
    sample_rate <= 48000 THEN 16 ELSE NULL END). Lossless formats
    at CD-range rates are virtually always 16-bit redbook, so we
    default to 16 in that narrow window. Higher rates stay NULL so
    we don't lie about 24 vs 32. The per-track view shows the real
    value when the user clicks in (lazy hydration via
    plex_get_track_metadata).

cc
2026-05-13 22:22:57 -06:00
vicrodh 0f5bff3acf refactor(immersive): extract shared ImmersiveSongCard from 9 panel copies
The bottom-right song card (artwork + title + album + artist + quality
badge) was duplicated inline in nine visualizer panels — LineBed,
CometFlow, NeonFlow, EnergyBands, Oscilloscope, Lissajous,
SpectralRibbon, TransientPulse, and TunnelFlow. Each carried its own
HTML structure and ~90 lines of identical CSS for the song-card
chrome. Any visual tweak (badge swap, layout shift, album/artist
inline join, artwork sizing) had to be replayed nine times and was
easy to miss one — the recent LineBed-only inline-album·artist change
exposed exactly this trap.

Extracts the song card into src/lib/components/immersive/Immersive
SongCard.svelte, which owns its own absolute positioning, the
two-line text stack with the new "album · artist" subline, the
QualityBadgeStatic mount, the 72px artwork thumb, the explicit-badge
overlay, and the responsive breakpoint. Each consumer panel now drops
in `<ImmersiveSongCard {artwork} {trackTitle} {artist} {album}
{explicit} {quality} {bitDepth} {samplingRate} {format} />` instead
of carrying its own copy.

Net effect: 268 insertions across all ten files vs 948 deletions of
duplicated markup and CSS — and the next visual change touches one
file, not ten. SuggestionsPanel uses a totally different layout
(queue list) and is intentionally NOT migrated.

cc
2026-05-13 22:22:32 -06:00
vicrodh 5bc5122d58 fix(ephemeral): replace 1 << 48 with 2 ** 48 for ephemeral track id floor
JavaScript bitwise shift operates on 32-bit signed integers, so
`1 << 48` is `1 << (48 % 32)` = `1 << 16` = 65536. Every Qobuz track
id sits above that, so the existing `id >= EPHEMERAL_ID_FLOOR` checks
were classifying every Qobuz track as ephemeral and disabling the
favorite / playlist / mixtape / collection actions for them — most
visibly in the immersive player favorite button, but also silently
stripping Qobuz tracks from the persisted session queue.

Five places shared the same broken constant, all now use `2 ** 48`
(281474976710656) — the actual ephemeral id floor defined in
src-tauri/src/ephemeral_library/mod.rs:

  - src/routes/+page.svelte (two metadataActionsDisabled checks for
    the immersive player and now-playing bar, plus the session-save
    EPHEMERAL_ID_FLOOR filter).
  - src/lib/components/QueuePanel.svelte (isEphemeralQueueTrack guard
    that hides favorite / playlist / track-info actions for queue
    rows).
  - src/lib/stores/playerStore.ts (isEphemeralTransition flag in the
    gapless carve-out and CUE virtual-track position translation).
  - src/lib/components/views/LocalLibraryView.svelte (close-folder
    cleanup check — only ephemeral folders should wipe the queue +
    now-playing slot when closed).

Each call site keeps a short comment naming the trap so future edits
don't reach for the same bitwise expression.

cc
2026-05-13 22:22:09 -06:00
vicrodh 7bb1047283 fix(linebed): size artwork off the live measured column height
CSS-only flex stretch + aspect-ratio collapsed instead of sizing the
thumb against the track-meta column — the previous two attempts
landed with no visible change (the thumb stayed near its old fixed
72px or fell back to image-intrinsic). aspect-ratio interacts poorly
with align-self: stretch when the item has no natural width to drive
the stretched dimension off.

Drops the CSS-only attempt. Binds clientHeight on .track-meta with
Svelte's bind:clientHeight, derives 98% of that into artworkSize, and
applies the result as inline width/height on the thumb. Reactive —
when the badge or text lines re-flow, the column height changes and
the thumb resizes with it. No fallback fixed size needed because the
{#if artwork && artworkSize > 0} guard avoids the first-paint flash
before the bind resolves.

cc
2026-05-13 19:19:58 -06:00
vicrodh c715475d10 fix(linebed): use align-self: stretch on artwork instead of percentage height
The previous attempt wrapped the thumb in a flex container with the
inner thumb at height: 98% — percentage height didn't resolve
because the wrapper's own height was content-determined, leaving the
inner thumb at near-zero and falling back to natural width. Visual
result: artwork stayed near the old fixed size, much smaller than
the column.

Drops the wrapper, sets align-self: stretch directly on the thumb so
it inherits the column height through the parent's align-items:
stretch chain, and uses aspect-ratio: 1 with auto width so the thumb
stays square. The 2% breathing room comes from margin-block: 1px,
which subtracts 2px from the stretched height in a way percentage
sizing couldn't reach.

cc
2026-05-13 19:02:40 -06:00
vicrodh 783f7e41ba fix(linebed): scale artwork to match track-meta column height
The bottom-info song card on LineBed pairs a four-row column (title +
album + artist + quality badge) with a square artwork thumb. With the
quality badge added the column grew to ~100px while the thumb stayed
at its old 72px fixed size, breaking the visual weight balance — the
column dominated the card and the thumb read as an afterthought.

Wraps the thumb in a flex container that stretches to the column
height, then sizes the thumb to 98% of that height with aspect-ratio
1 so it stays square. The 2% breathing room reads as a deliberate
gutter rather than a flush match, and the thumb sits vertically
centered against the column instead of pinned to either edge.

The fixed 72/56 dimensions and the mobile media-query override drop
out as a side effect — the thumb now scales with the actual content
height, so the breakpoint clamp isn't needed.

cc
2026-05-13 18:59:00 -06:00
vicrodh cb21653ac2 fix(quality-badge): tighten framed-badge right gutter from 8px to 4px
Visual check on real cards showed 8px was overcorrecting — the framed
icon ended further left than the visible Hi-Res mark instead of at
the same X. Halving the gutter lands closer to where the SVG's
transparent right space actually ends.

cc
2026-05-13 18:50:35 -06:00
vicrodh 72a1cbab75 fix(quality-badge): match framed-badge right gutter to Hi-Res SVG transparent space
The Hi-Res SVG canvas is 48.4x48.4 square but the visible "Hi-Res
AUDIO" mark occupies only ~65% of the width — there's permanent
transparent gutter to the right that we can't strip without editing
the SVG asset. That made the framed (CD/MP3) variant look like it
sat further right than the Hi-Res variant when both ended their
containers at the same X.

Two tweaks so both badges terminate at the same visual X:

  - Hi-Res shrinks 1px in height (proportional): 46x30 -> 44x29.
    Closer to the visual weight of the framed icon once both share
    the same baseline.
  - Framed gains margin-right: 8px, matching the SVG's transparent
    right gutter so the visible icon ends at the same horizontal
    position as the visible Hi-Res mark.

cc
2026-05-13 18:49:23 -06:00
vicrodh 978d5d681d feat(discovery): unified quality badge on album cards, Qobuz-style layout
The Discovery V2 cards rendered quality as a custom yellow gradient
pill underneath the title — visually disconnected from the badges used
in Mixtape and Collection lists, ugly at small sizes (the Artist
Spotlight section was the worst offender), and not honoring the same
hover tooltip pattern users are used to from the rest of the catalog
surfaces.

QualityBadgeStatic gains an `iconOnly` mode that mirrors Qobuz's web
client: for Hi-Res tier the badge renders the brand SVG bare (no
surrounding chrome — the logo carries its own); for CD / MP3 the
format icon sits centered in a small framed square the same height
as the Hi-Res mark. Hover surfaces the same tier + bitrate tooltip
as the full badge.

AlbumCardLite restructures the post-cover area into a single meta-row:
a two-line text stack (title + artist) on the left with truncation,
the icon-only badge on the right vertically centered across both
lines. The card's parent gap (4px) applies once between the cover and
this row, so the badge inherits the same separation from the album
art that the text already had.

The data adapters in discovery-v2/data.ts now propagate bitDepth and
samplingRate alongside the existing quality string / isHiRes flag so
the badge tooltip reads "Hi-Res: 24-bit / 96 kHz" with real numbers
instead of defaults parsed back out of the quality label.

Popular Albums uses the AlbumRowLite row layout and explicitly drops
the badge — the row already carries the play action and rank cell,
adding a quality mark crowded the line without earning its space.
AlbumRowLite loses its quality / isHiRes props as a cleanup since the
single consumer (DiscoveryView's mostStreamed snippet) no longer
passes them.

Sizing: Hi-Res 46x30, framed 30x30 — both square-ish, both 30px tall,
so they line up cleanly under each other when grids interleave both.

cc
2026-05-13 18:41:59 -06:00
vicrodh d0349119d0 feat(settings): add dismiss + manual re-detect to the graphics recommendation banner
Two interactions missing from the original banner landing:

  - Dismiss. The banner was always-on while the recommendation differed
    from persisted state — fine the first time the user sees it, but
    annoying if they read it, decided to keep their tuning, and didn't
    want the banner returning every time they opened the Graphics tab.
    Adds a Dismiss button that writes the recommendation's rationale
    string (joined) to a localStorage key as the dismissed signature.
    The banner stays hidden as long as the currently computed
    recommendation produces the same signature; if the environment
    changes enough to switch matrix branches (different GPU vendor,
    X11<->Wayland flip, VM detected), the signature mismatches and
    the banner re-surfaces so the user can re-evaluate.

  - Manual re-detect. Always-visible "Detect recommended settings"
    link above the banner area runs detection on demand and clears
    the dismiss flag. If the recommendation already matches the
    persisted state (no diff) the user gets a success toast confirming
    so — otherwise it would look like the button did nothing.

Apply now also clears the dismiss flag as a hygiene step — after Apply
the diff goes to false and the banner naturally hides, but the stale
signature would otherwise sit in localStorage waiting to suppress a
future legitimately-different recommendation.

i18n: 3 new keys per locale (dismiss / redetect / noChanges) across
en/es/de/fr/pt.

cc
2026-05-13 18:12:33 -06:00
vicrodh 700d5ef2c6 feat(settings): surface graphics recommendation banner in the Graphics tab
The detection + decision matrix in autoconfig_graphics.rs already knew
the right combination for every (GPU vendor x display server x desktop)
case, but the only way to consume it was the qbz --autoconfig-graphics
CLI tool which most users never run. The recommendation sat invisible
while users hunted toggles by hand.

Lifts the same logic into the Settings UI as a persistent banner at
the top of the new Graphics tab:

  - autoconfig_graphics: Environment and Recommendation structs become
    public with Serialize, the three pieces (detect_environment,
    compute_recommendation, write_recommendation) become pub so they
    can be re-used. The existing apply_recommendation CLI helper now
    delegates writes to the shared write_recommendation, keeping the
    behaviour identical (and inverting the DMA-BUF mapping so that
    rec.disable_dmabuf=false now writes force_dmabuf=true, matching
    the 1.2.13 opt-in semantics).

  - commands_v2/graphics_recommendation.rs (new): two thin Tauri
    commands — v2_get_graphics_recommendation returns the environment
    plus the computed recommendation so the frontend can compare it
    to persisted settings; v2_apply_graphics_recommendation writes
    the whole bundle in one call.

  - Settings UI: the Graphics tab loads the recommendation on mount.
    A $derived computes whether at least one recommended value
    diverges from the four persisted settings (HW accel / force_x11 /
    gsk_renderer / force_dmabuf with the inverted DMA-BUF axis).
    When it does, a violet-accent banner appears above the profile
    grid showing the detected environment, the rationale lines from
    the matrix, and an Apply button. Persistent (no dismiss) — vanishes
    the moment the persisted state matches the recommendation, no
    second copy of state to keep in sync.

  - i18n: 4 new keys per locale (title / detected / apply / applied)
    across en/es/de/fr/pt. The rationale strings stay in English
    because they're hardcoded in autoconfig_graphics.rs and reword
    them per scenario would mean a structured i18n key per case —
    not worth it for diagnostic copy that's read once and dismissed.

cc
2026-05-13 16:46:29 -06:00
vicrodh 11721594f4 refactor(settings): split Graphics out of Appearance into its own section
Appearance was getting kilometric — themes, layout, immersive panels,
immersive FPS, and an embedded collapsible "Composition" subsection
(GDK / GSK / X11 / Wayland / DMA-BUF / env-var reference) all under one
tab. The Composition piece is its own business unit (GPU infrastructure
that affects whether the app runs, not how it looks), and burying it
inside a collapsible inside an unrelated tab made the recommendation
work in the next commit awkward to surface.

Moves the entire Linux-only Composition block to a new top-level
Graphics section. Flattens the collapsible wrapper since the whole page
is the section now — the collapse-to-summary affordance only made
sense when it was a sub-block. The Graphics nav entry is injected
dynamically after Appearance and only on Linux (matches the existing
gating; macOS/Windows have no GDK/GSK/DMA-BUF surface to expose).

What stays in Appearance: themes, fonts, layout density, immersive
background mode, immersive panels per-panel toggles, immersive FPS
preset. Those control what the user sees, not the GPU infrastructure.

Locale subtrees move with the section — settings.appearance.composition
becomes settings.graphics across en/es/de/fr/pt. Title and summary
strings rewritten for the new "Graphics" framing in each locale; the
~30 leaf keys (hardwareAcceleration, forceDmabuf, profiles, env-vars
reference, etc.) keep their existing translations and just change path.

The unused compositionCollapsed state var drops out as a side effect.

cc
2026-05-13 16:27:25 -06:00
vicrodh dc96c5ac89 feat(graphics): flip DMA-BUF default to opt-in
The DMA-BUF renderer was on by default for AMD/Intel users with
specific NVIDIA carve-outs to disable it. The asymmetry was load-
bearing on a fragile feature: when DMA-BUF breaks on Linux the
failure mode is catastrophic (black window, missing textures, full
viewport corruption) rather than degraded, and the auto-detection
couldn't anticipate hybrid Intel+NVIDIA edge cases that surface only
on specific Mesa/NVIDIA driver pairings. The perf gain when it works
is marginal — ~5-10% memory bandwidth on heavy compositing, not
material for a music UI.

Flips the policy:
  - DMA-BUF off by default for everyone.
  - Users opt in via the existing Settings > Developer >
    force_dmabuf toggle (already persisted; just inverts the
    semantic from "force even though heuristics say off" to "turn
    it on at all").
  - QBZ_HARDWARE_ACCEL=1 explicit env override still bypasses
    everything as before (power-user escape hatch).
  - QBZ_DISABLE_DMABUF=1 still respected for explicit logging.

Removes the NVIDIA-Wayland / NVIDIA-X11 carve-outs because they're
strict subsets of "always off" now. Keeps the Wayland+NVIDIA-only
compositing disable — that's a separate axis (EGL protocol errors
corrupt the whole window, not just texture sharing) and still has
to fire on that specific stack.

User-visible startup log changes from "Using default WebKit
renderer (full hardware acceleration)" to "DMA-BUF renderer off by
default (opt-in via Settings > Developer)" for the common path.

cc
2026-05-13 16:18:17 -06:00
vicrodh 3061de2163 chore(scripts): add dev-igpu-mode.sh for iGPU-only paint path testing
Sister script to dev-cpu-mode.sh. Where the CPU script forces pure
software rendering across every layer that can sneak the GPU back in,
this one pins WebKit / GTK to the integrated GPU (Intel/AMD) so we can
test the "modest HW accel" tier the current detector ignores.

Specifically pins __EGL_VENDOR_LIBRARY_FILENAMES to the Mesa EGL JSON
so the NVIDIA EGL ICD doesn't win (it has the lower 10_* priority
prefix and WebKitGTK prefers EGL). Leaves WEBKIT_DISABLE_COMPOSITING_
MODE, WEBKIT_DISABLE_DMABUF_RENDERER, GSK_RENDERER, and LIBGL_ALWAYS_
SOFTWARE unset — the inverse of dev-cpu-mode — so real iGPU paint
paths exercise.

Header documents verification commands (nvidia-smi must not list
QBZ; intel_gpu_top / nvtop should show activity on Intel only).

Purpose is to inform a future refinement of graphicsState's detector:
binary hardware_accel_enabled doesn't differentiate "iGPU is fine for
balanced visuals" from "no GPU at all, force lite". Manual harness
first; detector smarts second once we know what actually works on
real iGPU paint.

cc
2026-05-13 15:04:35 -06:00
vicrodh a2ab509f86 fix(toast): keep error text selectable for debug copy-paste
Follow-up to #423. The new app-wide user-select: none rule covers
toasts too, which is correct for ephemeral info toasts but loses
copy-paste from error toasts — those carry track ids, file paths,
and backend error strings that we routinely copy into issues and
diagnostics.

Adds .selectable to the toast container so all toast types stay
copyable. Trade-off accepted: a stray drag-to-select gesture on a
success toast won't initiate a window drag, but error visibility
matters more than that edge case.

cc
2026-05-13 14:42:56 -06:00
Victor RH 48b83d1403 Merge pull request #423 from afonsojramos/rework-titlebar-drag-region
refactor: rework window drag regions and text selection model
2026-05-13 14:41:06 -06:00
Afonso Jorge Ramos 032021de44 feat(lyrics): drag region on sidebar header and selectable lyrics panel 2026-05-13 20:42:27 +01:00