Commit Graph
161 Commits
Author SHA1 Message Date
vicrodh e288e4aaee feat(slint): DSD playback mode setting, DSD badges and local gapless
Settings > Audio gains a 'DSD playback' dropdown (Convert to PCM /
DoP / Native DSD, ALSA-direct only, permanent warning copy since DoP
and native support cannot be detected). Local .dsf/.dff files route to
the player's DSD path with toasts on unsupported files. Quality badges
read DSD64/128/256 from the stored bit rate on every surface including
the now-playing stamp. The gapless prefetch now pre-queues LOCAL
tracks too: DSD via the DoP/native append or an in-memory converted
WAV, other local formats via their raw bytes (fixes local FLAC
gapless); CUE virtual tracks are excluded. Six new locale strings.

cc
2026-07-03 17:56:48 -06:00
vicrodh a7f538b3de feat(slint): custom title bar and compact header navigation option
Custom title bar (Linux opt-in, macOS overlay default): frameless window
with drawn window controls (close/maximize/minimize, left or right
placement), header drag with once-per-press latch (Wayland move-grab
swallows the release; re-arming on stale pressed state re-grabbed the
window onto the cursor), double-click maximize, edge resize, 1px
hairline. Title bar minimize is always a WM minimize now - the old
minimize-to-tray branch made it visually identical to close-to-tray.

Compact header navigation (opt-in, Appearance): with nav out of the
sidebar, the header can always use the icon-only compact nav even while
the sidebar is open. Full text tabs and compact nav stay mutually
exclusive; the playlists flyout button keeps its closed-sidebar-only
behavior. Persisted as ui_prefs.nav_header_compact.

i18n: new strings in all six locales (includes two DSD toast strings
for the in-progress DSD support, riding along in the catalogs).

cc
2026-07-03 15:52:30 -06:00
vicrodh c504059c08 i18n: sync all locale catalogs against a fresh extractor POT
Zero missing msgids in all six locales (was 27 es / 36 de-fr-pt / 50
ru-ja): renderer tiers, shortcut modals, link resolver, Purchases, the
album-blacklist batch (de/fr/pt/ru/ja) and the recommendations + JACK
batch (ru/ja); plus es Collections leak fixes. msgfmt-validated.

cc
2026-07-03 10:01:05 -06:00
vicrodh 648f901776 docs(purchases): replace the format-picker anchor caveat with the verified fact
A standalone repro mirroring the exact chain (if-mounted view, Flickable,
layouts, if/for components, nested slot) measured absolute-position as
exact window coordinates at any scroll position in Slint 1.16.1 — the
double-count quirk is ListView-only and this chain has none. The anchor
math is correct as written; no code change.

cc
2026-07-03 10:01:05 -06:00
vicrodh 3143632516 feat(search): auto-close the live dropdown after 4.5s of inactivity
Idle timer at the Cortinilla root (a Timer inside the if subtree cannot
be restart()ed — 1.16 compiler panic): paused while the pointer is over
the panel (ancestor TouchArea hover covers the row/button children),
restarted by typing and arrow-selection moves, hover mirror reset on
open/close.

cc
2026-07-03 10:01:04 -06:00
vicrodh 12b1b31255 fix(search): stop the live dropdown resurrecting on navigation
The search inputs' changed has-focus handlers re-invoked live() whenever
focus returned with text present, and Slint hands focus back without user
intent (focus reassignment when the focused element unmounts on a page
change, window re-activation) — the dropdown popped open on navigation.
Gate the refresh on the dropdown being already open; typing still opens
it through edited. Also release the header search focus and reset the
typing guard on every view change, so single-key hotkeys work again after
typing a search, and a guard left stuck true by an unmounted input is
cleared.

cc
2026-07-03 10:01:04 -06:00
vicrodh c65bd9c2cb fix(ui): set the typing guard on the remaining text inputs
The single-key hotkey guard (UiFocusState.text-input-focused) was missing
on the three editable TextEdit description fields (create/edit playlist,
My QBZ edit) and on the read-only inputs (QConnect dev modal, log viewer
URL), so Space/q/? hotkeys fired while typing or selecting there.

cc
2026-07-03 10:01:04 -06:00
vicrodh d91042f880 fix(i18n): keep the es sidebar playlists title short
"LISTAS DE REPRODUCCION" overflowed the sidebar header and pushed its
buttons out; every other locale already uses "PLAYLISTS" there.

cc
2026-07-03 10:00:47 -06:00
vicrodh 8a734f6427 feat(login): narrate the browser OAuth flow on the login screen
The screen sat inert while the system-browser OAuth ran: no waiting or
authenticating feedback, and failures were log-only. New LoginState
global (phase + error) driven by an on_phase callback in
auth::login_via_system_browser (both call sites: login + recovery).
The blue button now reads "Sign in with your browser", disables while
an attempt is in flight, and the screen shows continue-in-browser text
(with a no-focus hint), a Cancel link that aborts the pending flow, an
authenticating state, and a sign-in-failed box. The redundant
"Use your system browser instead" link and its AppCommand are removed.
Five new msgids translated in all seven locale catalogs.

cc
2026-07-02 10:10:59 -06:00
vicrodh da566d2426 fix(ui): let settings buttons grow with translated labels
The six local SecondaryButton/ManageButton copies in the settings panels
and the two inline AudioSettings buttons had fixed widths sized for
English; Spanish/French/German labels overflowed the borders. Width is
now Math.max(label preferred-width + padding, old floor), so short
labels keep the 160/170/200px right-edge grid and long translations
grow leftward instead of overflowing.

cc
2026-07-02 10:10:59 -06:00
vicrodh b7549790a3 feat: interface-size (UI scale) presets
Webview-zoom-style presets: Extra small 0.8 / Small 0.9 / Default 1.0 /
Large 1.2 / Extra large 1.5, applied through SLINT_SCALE_FACTOR set at
the top of main() before any thread or the backend exists. The env var
overrides the compositor DPR, so the value is last observed DPR x
preset; the Default preset leaves the var unset (stock DPR handling,
including live monitor changes, stays intact). last_dpr is persisted
from the winit window (real compositor DPR) on the 5s renderer-sentinel
timer.

- const-bake guard: strip SLINT_SCALE_FACTOR in qbz-ui build.rs so the
  slint compiler never bakes the factor into the generated code
- new UiScale global + scale-divided window minimums keep the physical
  minimum constant across presets (XL fits a 1366x768 screen); the
  three window-geometry gates in main.rs use the process-active factor
- artwork decode targets scale with the preset: sharp covers at >=1.2,
  smaller decoded-cache footprint below 1.0
- Settings > Appearance "Interface size" row below Language (replaces
  the commented-out percentage placeholder and its orphan properties),
  restart-to-apply toast
- Typography.boost = 1.1 on Extra small only, so body text stays
  readable at the 0.8 factor; Small (0.9) needs no compensation
- i18n: new strings added to all 7 locale catalogs

Known accepted limitation: slight text softness at fractional scale
factors (owner-accepted; diagnostic notes live in
qbz-nix-docs/ui-scale-presets/).

cc
2026-07-02 08:36:40 -06:00
vicrodh d9b82e7b12 perf: reduce-motion on the GL/software renderer tiers
On weak GPUs every animation frame is a full-window femtovg repaint, so
continuously-animated loading indicators dominate transient CPU (opening
the search cortinilla spiked to ~40% on a 2014 Mac Mini). New
ShellState.reduce-motion (seeded true on non-wgpu tiers, like the shader
gate) switches those animations from display-rate animation-tick to a
shared ~8fps coarse clock advanced by ONE timer in AppShell — same phase
math, 60 -> 8 repaints per second:

- LoadingSpinner / LoadingDots (all mount sites inherit)
- Equalizer bars (TrackPlayCell + immersive track info)
- Fetch/download spinners (TrackPlayCell x2, TrackRow, offline manager)
- Both search-cortinilla skeleton pulses (the reported spike)
- Home skeleton: the 900ms opacity fade collapses to a snap (its phase
  timer becomes the only driver, ~1fps while mounted)

Also logs the window scale factor at startup (circle-AA investigation:
femtovg's fringe AA halves at fractional scale factors, dpi=ceil(scale)).

cc
2026-07-01 20:55:09 -06:00
vicrodh 23b572e5c9 perf: viewport windowing for the Local Library albums grid (phase 1)
Covers and card elements now scale with the viewport instead of the
library: the shared AlbumGrid gains opt-in windowing (row band = visible
rows +/- one viewport, computed in .slint, row-granular change events),
cards outside the band render as same-footprint placeholders (scroll
geometry unchanged), and the Rust side dispatches artwork only for the
band, evicting covers outside a retention span back to the placeholder.

- New id-keyed ArtworkTarget::LocalAlbumById (replaces the index-keyed
  LocalAlbumCard): immune to derive re-sorts between dispatch and apply;
  reloads still drop stale covers via the generation guard. The enum
  loses Copy (String variant).
- In-flight dedupe set freed on apply AND on fetch failure, cleared on
  generation bumps.
- Flat list view and grouped mode keep the pre-windowing full dispatch
  (they don't fire window events); switching grid->list re-dispatches
  covers the window may have evicted.
- Windowing is off by default in the shared grid: Discover, Labels and
  Favorites are untouched (phase 2).

A 2,000-album library drops from ~540 MB of resident cover RGBA to a
viewport-sized working set, and scrolling repaints ~40 mounted cards
instead of the whole collection.

cc
2026-07-01 19:46:14 -06:00
vicrodh a050e6394a fix: hide shader scenes on non-wgpu renderer tiers
The immersive shader scenes (Plasma/Tunnel/Aurora/Spectral Ribbon/Line
Bed) render through the wgpu underlay, which does not exist on the
femtovg-GL and software tiers - picking one there showed a black view.
Gate the five picker rows and the g-cycle behind a new
ImmersiveState.shader-scenes-available flag seeded from the renderer
selection; FOCUS views and splits (Slint-drawn) stay available.

cc
2026-07-01 19:46:14 -06:00
vicrodh fe111c3ff6 feat: renderer tier auto-detect with Settings override and auto-revert
Weak-GPU hardware (Raspberry Pi 4 / v3dv, Haswell / hasvk) exposes a
real Vulkan adapter, so the old binary wgpu-or-software detection handed
it the slowest possible path: femtovg-wgpu over an immature Vulkan
driver. Mesa's GL path is the fast one on that class of hardware.

- Three-tier chain: wgpu -> femtovg GL -> software. Auto-detect
  classifies adapters (weak-driver markers incl. hasvk, integrated-on-arm,
  tiny texture limits); adapters only reachable through wgpu's GL backend
  never select the wgpu tier (the renderer masks Backends::GL out, so
  detection must too). renderer-femtovg (GL) feature now compiled on
  Linux.
- wgpu tier requests PowerPreference::LowPower (WGPU_POWER_PREF still
  wins).
- Per-window swapchain alpha in the vendored femtovg renderer: only the
  miniplayer gets a transparent (blended) swapchain; the main window
  keeps an Opaque one, sparing a full-window compositor blend per frame.
- Settings > Appearance gains a Renderer row (Linux only): Auto
  (default) / GPU / GPU compatibility / Software, persisted in
  ui_prefs.json, applied on restart. QBZ_RENDERER env still overrides
  (now also accepts gl|gles|femtovg).
- Auto-revert sentinel: a non-auto choice is armed before backend init
  and disarmed after the window survives 5s; if the previous start died
  before that, the setting reverts to Auto with an explanatory toast, so
  a bad renderer pick can never lock the user out of Settings.
- Developer tools > Diagnostics now reports the active renderer, why it
  was chosen, and the enumerated GPU adapters (panel + markdown report).

cc
2026-07-01 18:05:28 -06:00
vicrodh e71f0ded46 perf: stop immersive atmosphere animation while paused
The atmosphere mount in ImmersiveView kept the default animated:true, so
the 4-layer warped background forced full-window redraws at display rate
even with playback paused. Gate it on NowPlayingState.playing (the other
three mount sites already pass animated:false).

cc
2026-07-01 18:05:14 -06:00
vicrodh bf20144622 fix(hotkeys): solid arrow glyphs + centered reset-all icon
The thin Unicode arrows (left/right/up/down) were nearly headless at keycap
size even after the font bump, so the arrow-key shortcuts use solid triangle
glyphs now. The footer Reset All to Defaults icon rode high next to its label
(a bare fixed-size icon in the row) — wrapped in a centered column.

cc
2026-06-30 15:26:37 -06:00
vicrodh fe7abd6e32 fix(header): wire the Documentation menu item to the project Wiki
The hamburger "Documentation" entry had no clicked handler, so it did
nothing. Point it at github.com/vicrodh/qbz/wiki via the generic
external-link handler.

cc
2026-06-30 14:51:00 -06:00
vicrodh 6e1ea24d63 feat(hotkeys): three-column customize editor + larger keycap glyphs
The editable Customize Shortcuts modal now lays its categories across three
columns (extracted KbEditGroup, reusing the cheatsheet's column models) so it
matches the cheatsheet and avoids a tall scroll; panel widened to fit. Keycap
text bumped to 15px in both modals so the arrow-key glyphs read clearly.

cc
2026-06-30 14:51:00 -06:00
vicrodh 7002e7773e fix(about): credit Rust, drop Kawarp, crisp logo, tighter disclaimer
- Acknowledgments now lead with Rust (the app's core, previously unmentioned)
  and drop Kawarp, which is no longer used (es/de/fr/pt updated).
- Logo renders from a 128px source instead of the 512px original scaled to
  56px, which aliased badly (femtovg has no mipmaps).
- Qobuz trademark notice dropped to 11px so it wraps to two lines, not three.

cc
2026-06-30 14:51:00 -06:00
vicrodh a7681945e9 fix(hotkeys): suppress single-key hotkeys while typing in LineEdit fields
The window-level hotkey handler only suppressed single-key shortcuts
(Space/q/?/1-5) when UiFocusState.text-input-focused was set. The ~24
std-widget LineEdit fields across 14 modals/settings never set it, so those
keys fired (and were swallowed) mid-typing. Each now mirrors its focus via
`changed has-focus`; import added where missing.

cc
2026-06-30 14:08:01 -06:00
vicrodh 1a9c132760 feat(hotkeys): lay the shortcuts cheatsheet out in three columns
The read-only shortcuts modal listed every category in one tall column that
scrolled. Split the groups round-robin into three column models (Rust) and
render them side by side; the panel widened to fit. The editable customize
editor still uses the full single-column list.

cc
2026-06-30 14:07:50 -06:00
vicrodh 9b9bffbf64 feat(album): Last.fm similar-albums carousel under suggestions
Second album-page row sourced from Last.fm: the album's primary artist ->
artist.getSimilar -> one top album per similar artist, resolved to Qobuz and
deduped against the Qobuz /album/suggest row (by artist|title and by id).
Gated on Last.fm being connected; resolved row cached per album for 30 days
(re-opens make zero API calls; only non-empty results cached so a transient
failure re-fetches). New build_similar_albums_seeded in qbz-external-reco.
reset_album now also clears this row so the previous album's recommendations
don't linger while the next loads.

cc
2026-06-30 13:13:50 -06:00
vicrodh 8c18287577 fix(album): single-click album context menus
The album header (⋯) and cover-art PopupWindows used the default
close-on-click policy, which races the row's TouchArea — the popup can
dismiss on the press before the item's clicked fires, so some rows (e.g.
Share Qobuz link) needed a second open+click. Switch both to
close-on-click-outside and close explicitly per row, matching the existing
Sidebar/QueueSidebar fix.

cc
2026-06-30 13:13:39 -06:00
vicrodh a67dd2c2df i18n: translate Recommendations/external-reco strings + JACK note (es/de/fr/pt)
The 11 Recommendations msgids (IntegrationsSettings RECOMMENDATIONS/toggle/
description, HomeView tab, ExternalRecoView row titles + empty state) had
zero catalog entries and fell back to English everywhere. Translate them in
es/de/fr/pt, plus the 2 new JACK-note strings. es is Mexican (register-
checked: no voseo/Castilian). ru/ja stay English-fallback pending a reviewed
pass.

cc
2026-06-30 09:27:07 -06:00
vicrodh 6f7ff9956c feat(audio): surface "JACK is not bit-perfect" in Audio settings
JACK routes through the JACK graph (resamples to the graph rate), so it
can't be bit-perfect — but nothing told the user. Add a `backend-is-jack`
flag (settings.rs computes it beside alsa/pipewire at both set-sites;
state.slint property) and a WarningBanner in AudioSettings, gated on it,
pointing at ALSA-direct / PipeWire-passthrough for bit-perfect. UI-only;
backend behavior unchanged.

Blind change (no build this session) — validates on the next qbz_ui build.

cc
2026-06-30 09:27:07 -06:00
vicrodh 67f5569af4 fix(ui): About modal density/height + splash disclaimer placement
- About: contributors 4->5 per row so the 10 handles fill the widened
  840px modal in 2 even rows instead of 3 sparse ones; modal height
  720->770px.
- Splash: move the legal disclaimer out of the card to the bottom of the
  screen (the main container, matching Tauri) and shrink it to 10px.

cc
2026-06-30 08:08:41 -06:00
vicrodh 66889af421 feat(blacklist): album-block context menu + manager Albums tab; albumview polish
Phase 2 (Slint UI + wiring) for album-level blocking, plus the album-view
enhancements that share these files.

Album blacklist UI:
- "Block this album" in all three album menus: the page-header menu as a
  Block/Unblock toggle (gated to Qobuz via AlbumState), and the AlbumCard +
  AlbumListRow grid menus (one-way, drop-on-reload; local/Plex excluded).
  Menu heights bumped for the extra item.
- Blacklist Manager gains an Artists | Albums tab bar (segmented, ADR-008);
  an AlbumRow with a cover thumbnail (blind-eye fallback) + title + artist +
  "Added {date}"; tab-aware controls (shared enable toggle, per-tab search /
  Clear-All / count) and confirm modal. Settings row retitled "Blacklist",
  status shows both counts.
- state.slint: BlacklistedAlbumItem + album-items/album-count/active-tab on
  BlacklistState; set-tab/album-select/remove-album/clear-all-albums/
  block-album on BlacklistActions; AlbumState.is-album-blocked.
- main.rs: ("album","block"/"unblock") arm (header, resolves display fields
  from AlbumState); 5 album BlacklistActions callbacks; seed_blacklist_status
  pushes album_count.
- blacklist_manager.rs: album build/push + set_tab/block_album/remove_album/
  clear_all_albums; covers resolve async via a new ArtworkTarget::BlacklistAlbum
  through spawn_loads.

Album-view enhancements (same files):
- E1: full credited-artist header line with localized roles, each name a
  clickable artist link (Album.artists drove this; was interpreter-only).
- E3: classical work-header composer name is now a clickable artist link
  (work title + composer id/name split through TrackData/TrackItem).

i18n: es (Mexican) for all new strings; de/fr/pt/ru/ja fall back to English
pending a translator pass.

cargo check -p qbz green (Slint codegen + bin).

cc
2026-06-30 00:06:05 -06:00
vicrodh be487dca4c feat(slint): work grouping, ru/ja locales, About + What's New + Discover reco modal
- Album view: classical "work" section grouping (PR #536 port) — new
  Track.work model field + run-length per-work headers in AlbumView.
- i18n: add Russian + Japanese locales (1365 msgids each); extend plural.rs
  (ru 3-form one/few/many, ja single-form) + tests; fix the language-selector
  reseed (main.rs set_languages + ui_prefs index maps). Seven languages total.
- About modal: native port of the Tauri About (version, links, build info,
  one-paragraph acknowledgments, inline contributors with GitHub avatars,
  signature). Contributors credit hoyon (PR #536) and mxnix (PR #517, ru).
- What's New modal: renders the GitHub release-tag description (markdown
  sections + TOC); release fetch + build.rs emits build date/commit.
- Discover Recommendations: move Cache window + Refresh now into the Customize
  modal, gated to the reco tab; hide the section-config UI there; add an
  explainer paragraph. New strings translated to all six non-English locales.
- About modal width +120px; splash screen gains the legal disclaimer footer.

cc
2026-06-29 22:55:17 -06:00
vicrodh e0e6ca81a9 fix(slint): queue sleep-timer flyout closed on any click
The sleep-timer PopupWindow used the default close-policy (close-on-click),
so any click inside it — including on the preset picker — closed it before
the value could change. Set close-policy: close-on-click-outside. Also
replaced the QbzSelect (itself a PopupWindow, which Slint cannot open nested
inside another PopupWindow) with inline preset rows, so the presets are
actually pickable.

cc
2026-06-29 16:58:16 -06:00
vicrodh 6f6f2d5aec feat(slint): whole-row selection in multi-select mode
In multi-select mode the entire TrackRow body is a selection target (Tauri
whole-row selectable), not just the 13px checkbox. The body click routes the
same media-action("track", id, "toggle-select") the checkbox does, so the
central toggle arm reads the modifier — Shift+Click range and Ctrl+Click
work on the body too. The "..." context-menu button + the checkbox keep
their own handlers (the menu stays reachable); the other in-row controls
(artist/album link, favorite heart, offline button) are disabled in select
mode so the click passes through to the row. Double-click no longer plays in
select mode. Inert (blacklisted) rows never select.

cc
2026-06-29 16:58:16 -06:00
vicrodh acfd3d928d feat(slint): keyboard multi-select on the built-out surfaces (Mix, Label, LocalLibrary Albums)
Extend the keyboard multi-select feature to the surfaces that had no
working selection in the Slint port.

- Mix (DailyQ/WeeklyQ/FavQ/TopQ all render through one MixView): mode
  toggle + MultiSelectBar, per-row checkbox via the central toggle arm + a
  ContentView::Mix branch (model + recount), bulk play-next/queue/
  add-to-playlist. Shift-range, Ctrl+A and Escape inherited automatically.
- Label Popular Tracks: was a dead stub (checkboxes with no bar/recount/
  router arms). Converted the local mode flag to LabelState.multi-select,
  added a real MultiSelectBar + label.rs recount/select_all/clear/selected,
  and the missing select-toggle/bulk router arms.
- LocalLibrary Albums grid (a previously-missing surface): AlbumCardItem
  gains a `selected` field; a `select-mode` prop is threaded through the
  shared AlbumCard / AlbumListRow / AlbumGrid / AlbumCollectionView (default
  false, so Discover/Label/Favorites are untouched). Albums toolbar toggle
  + MultiSelectBar; local_library.rs album select fns (flat + grouped,
  id-resilient Shift-range over AlbumCardItem); album->tracks bulk resolve
  via fetch_album_tracks_blocking on spawn_blocking for queue/play-next/
  add-to-playlist/add-to-mixtape; Ctrl+A and Escape routed by active-tab.

cargo check -p qbz green (qbz_ui compiles clean).

cc
2026-06-29 14:53:31 -06:00
vicrodh 99da4c381b feat(slint): pre-launch dev panel, queue controls, appearance persistence, reco cache settings
Sleep timer (queue footer): tokio monotonic deadline + 1Hz countdown that
fires core().pause() at expiry; presets + custom minutes via a popover.

Stop after this song: 4 QbzCore passthroughs + playback consume-before-advance
seam and the load-bearing gapless-prefetch gate, plus the queue marker/menu so
the Queue persists when playback halts.

qbz-log crate: TeeLogger wrapping env_logger (stderr + 5000-line ring +
~/.local/share/qbz/logs/qbz.log) with write-time secret redaction (regex +
live-secret registry) and a diagnostics bundle builder.

Dev panel logs: in-app viewer that shows content the moment it opens, advanced
controls behind a discreet [+], "Copy all" as the sole footer button, a copy
button on the upload URL, and a redacted public paste upload.

Diagnostics: qbz-app::diagnostics (extracted, src-tauri untouched) feeding a
7-section panel with the live active output device + sample rate/format (pactl)
and a complete redacted report for the upload.

Report an Issue: wires the header menu item to a 1:1 Tauri modal that opens the
GitHub bug-report template.

Appearance persistence: 6 toggles that were live-only now persist via ui_prefs
+ handler arm + startup seed (window-title-show, show-volume-steppers,
sidebar-playlist-collage, local-library-track-artwork, in-app-toasts,
theme-filter).

Recommendations cache (per-tab only): TTL select 24/36/48/72h + Refresh now in
ExternalRecoView; no change to the other Discover tabs.

Bump workspace version 0.1.0 -> 1.2.15 so app_version is correct in the
diagnostics report and the QConnect About string. i18n es/de/fr/pt for all new
strings.

cc
2026-06-29 13:19:06 -06:00
vicrodh b76e2b8137 feat(immersive): Liquid Spectrum visualizer POC (parked, disabled in menu)
Circular 'liquid' audio-spectrum shader scene (Motion-Array style): FFT bands wrapped around a ring, smoothed into an organic blob glowing in the album palette, with a rotating radial-rays backdrop and a beat-triggered burst.

Wired as shader-mode 6 (liquid_spectrum.wgsl + SHADER_SOURCES[4] + render_frame), but the first cut did not land visually, so the picker row is commented out and the g-cycle is left at 0..5 — the scene is unreachable but kept intact for a future pass.

cc
2026-06-29 03:22:53 -06:00
vicrodh 226c2118ef feat(immersive): three new visualizer scenes (Wave Bed, Spectral Ribbon, Line Bed) + polish
Wave Bed (focus mode 6): centered vertical-mirror waveform. Spectral Ribbon (shader-mode 4): paint-as-you-play GPU spectrogram with green axes + header overlay, bass-at-bottom orientation aligned to the axes, and a real-time ceiling line tracking the highest active frequency. Line Bed (shader-mode 5): wgpu line-strip waterfall with a leveled camera, B-spline-smoothed continuous curves, a primary->accent height gradient, a bottom cutoff above the player, 2x supersampling, and a sampled-texture heights feed (no vertex storage buffer, so it runs on Slint's downlevel wgpu device).

Also: Plasma reworked to be far more reactive (4 ink emitters, beat splats, faster turbulence, quicker decay); immersive song card no longer truncates the album/artist line; player heart reflects favorite state; player bar narrowed; sample-rate and bit-depth wired through for the spectral header.

cc
2026-06-28 23:55:17 -06:00
vicrodh 2a8d01e686 feat(immersive): GPU shader scenes with album-art palette, audio uniforms, and song card
Expand the shader Uniforms to 144 bytes (phase/beat/level/level_smooth + 8 log
bands + 5 energy bands + the album-art palette triad), feed them per frame from
the 30 fps visualizer drain (new FrameAudio + a forward-motion phase accumulator)
and push the palette on track change. Move plasma and aurora onto the new
uniforms and add a bottom-right ImmersiveSongCard shown over any shader scene.
Pairs with the faster tunnel from the previous commit.

cc
2026-06-28 15:36:07 -06:00
vicrodh 705b735393 feat(immersive): faster tunnel shader with winding path and vanishing-point speed-lines
Amplify the forward-flight clock (phase * 6, seam-safe at the 4096 wrap), add
radial speed-lines fanning out of the vanishing point (shimmer with treble and
the beat), and bend the corridor CENTERLINE as a function of depth so the path
winds while the rectangular cross-section stays undeformed. Stronger beat punch.
tunnel.wgsl only; plasma and aurora untouched.

cc
2026-06-28 15:32:51 -06:00
vicrodh 98d606f913 fix(playlist): follow/unfollow button reflects the true follow state
The detail toggle used sidebar::contains to decide "following", which is
true for owned playlists shown in the sidebar and false for followed ones
opened from Favorites > Followed (a followed playlist may live in a folder
or not be rendered in the sidebar at all). Being in the sidebar is a
consequence of following, not the determinant, so derive it authoritatively
from get_user_playlists (present but owner != me). Reflect the state in the
button: a check icon + "Unfollow on Qobuz" tooltip when following.

cc
2026-06-28 12:59:06 -06:00
vicrodh 1a4e3bd3d8 feat(sidebar): add search and import to the collapsed-sidebar playlists flyout
When the sidebar is fully collapsed, the header playlists flyout now has
a search box (sharing the sidebar's Rust-side filter, cleared on every
close so it never leaks into the expanded list) and an Import entry
(offline-gated, mirroring the sidebar "..." menu).

cc
2026-06-28 12:58:17 -06:00
vicrodh be97d899d9 fix(sidebar): open the playlists options flyout to the right
The "..." options menu opened leftward over the playlist list; anchor it
to the right of the button (overlaying the content area) instead.

cc
2026-06-28 12:58:17 -06:00
vicrodh e0c921070c fix(npb-classic): "+" opens the grouped add flyout like the other layouts
Classic NPB's "+" fired a one-click add-to-playlist; now it opens the same
grouped add menu (library / playlist / queue / play-next / mixtape / collection)
as New and Large. Classic keeps its inline favorite toggle next to it so the
favorite state stays visible.

cc
2026-06-28 11:00:36 -06:00
vicrodh 59409822ab fix(npb-large): lift the cover 2px off the window bottom
The Large now-playing cover sat flush against the window bottom. Lift it 2px and
take those 2px from the cover<->spectrum gap (10 -> 8) so the dock height is
unchanged; the bottom pad applies only while the spectrum is shown so the
off-state height still matches large-dock-height.

cc
2026-06-28 11:00:36 -06:00
vicrodh 6a17c48f59 feat(settings): appearance/integrations polish, functional window title, link modal
- Appearance: drop the red 'unsupported' tint on Language; hide UI Scale and
  the Title Bar / Window Controls / Immersive blocks (commented, not deleted).
- Window title: "Show track in window title" now works -- the OS window title
  becomes "{track} - {artist} | qbz" via a reactive binding (editable template
  row hidden; format is fixed).
- Plex and Scrobblers collapse carets enlarged (28/18) and moved to row level so
  they are vertically centered with the master toggle.
- Hamburger: "Open Qobuz Link" -> "Open Music Link".
- Link modal: a long pasted URL no longer grows the input/modal (min-width 0 on
  the clip container caps content-width propagation).

cc
2026-06-27 23:15:45 -06:00
vicrodh 0f34a4280d fix(playlist): reserve back-to-top FAB clearance at the content end
The floating back-to-top button overlapped the last track rows / the docked
Suggested Songs. Reserve the FAB footprint as a bottom footer so content
scrolls clear of it. Constant (not scroll-gated) to avoid a layout binding
loop (footer height -> track-list height -> visible-height -> FAB condition).

cc
2026-06-27 23:15:45 -06:00
vicrodh b5fd478b7b fix(foryou): radio excludes local/Plex, Plex/local artwork, radio nav buttons
- Radio Stations seed only Qobuz-sourced recent albums; local/Plex albums
  carry ids the Qobuz /radio/album endpoint can't resolve, so they no longer
  generate radio tiles (they still appear in Recently Played Albums).
- Recently Played albums/tracks route artwork by scheme (spawn_search_loads):
  http -> Qobuz CDN, /library/ or /photo/ -> Plex thumb, else local file. The
  plain HTTP loader left Plex/local covers blank.
- Radio Stations carousel extracted to a RadioCarousel component with prev/next
  nav buttons (drag preserved), mirroring SlimCarousel.

cc
2026-06-27 23:15:45 -06:00
vicrodh c3b921efff fix(reco): Weekly rows resolve fast, cache per-week, show per-row skeletons
Discover > Recommendations Weekly Exploration/Jams kept vanishing and loaded
slowly. Three root causes:

- Weekly track resolution ran every track through MusicBrainz's serial
  1.1s/req rate limiter (~110s for 100 tracks), so the rows never painted in
  practice (Fresh Releases, album-based with no MusicBrainz, returned fine).
  Resolve weekly tracks via fuzzy Qobuz search instead (skip_mb) -- fast and
  reliable for these mainstream playlists.
- The external-reco cache was only initialized on the offline entry path;
  the two online login paths (auth.rs) never bound it, so nothing cached
  online (0-byte db). Initialize it there too.
- Weeklies were bundled in the shared 48h results blob keyed only on
  auth-presence. Give them their own per-week cache (keyed by the
  ListenBrainz playlist mbid, 9d TTL, written only when non-empty, with a
  stale-fallback to the last good week) and rebuild them independently of the
  blob, so a transient empty build cannot hide them. Skip the 7d negative
  cache for weekly tracks so a throttle does not lock a row empty.

Per-row skeletons: the progressive paint now marks each expected row pending
at spawn and clears it as its builder resolves, so a slow row shows its own
skeleton instead of silently missing. Adds [reco] diagnostics.

cc
2026-06-27 21:05:57 -06:00
vicrodh 1458214a91 fix(artist): add ArtistState.is-following for the follow heart
The header heart binding + the Rust set_is_following calls reference an
ArtistState.is-following that did not exist (that property lived on other
header globals). Add it so the .slint compiles and the heart reflects the
follow state. Caught by cargo check -p qbz.

cc
2026-06-27 15:44:30 -06:00
vicrodh c1b189363a i18n: translate the slint-polish batch UI strings
Add the 10 new @tr labels (radio menu items, playlist Follow/Copy,
MusicBrainz setting) plus the new toasts to all five locale catalogs
(en/es/de/fr/pt); es is canonical Mexican, de/fr/pt machine-drafted and
flagged for review. Convert the new user-facing toasts in
enqueue_track_ids + the playlist-copy arm from raw strings to
qbz_i18n::t / qbz_i18n::tf so they localize like the rest.

cc
2026-06-27 15:34:22 -06:00
vicrodh d906e55647 feat(artist): make the follow heart + artist-card follow a working toggle
The ArtistView header heart dispatched a dead ("artist","","favorite")
with an empty id. Wire it to the artist follow (Qobuz favorite = follow)
as a real toggle that reflects state: bind the heart to
ArtistState.is-following, seed it on artist open, and toggle add/remove
via a new in-memory artist fav cache (fav_cache::is_artist_favorite /
set_all_artists / set_artist) seeded by search + the artist page. The
("artist","follow") arm becomes a toggle (was add-only), so the
search/discovery artist-card Follow chips can unfollow too. Optimistic
flip on the cache + every visible surface, revert on failure.

cc
2026-06-27 15:21:42 -06:00
vicrodh 2d7232d4e7 feat(discover): add "queue" + "create playlist" buttons to Weekly rows
Add two title-adjacent icon buttons to the external-reco Weekly
Exploration and Weekly Jams rows: add the whole backing track list to the
queue, or create a playlist from it (reusing the add-to-playlist picker
from the queue save-as-playlist work, P6). SlimCarousel gains an opt-in
show-list-actions + list-action callback (default off, so the shared
recent/popular rows are unchanged); external_reco::list_track_ids reads
the row's backing ids; playback::enqueue_track_ids resolves + enqueues
them; a new ("ext-reco-list", _) media-action routes both.

cc
2026-06-27 14:22:01 -06:00