Commit Graph
1561 Commits
Author SHA1 Message Date
vicrodh d819d94ab5 feat(qbzd): add radio (seed-and-go) + resolve (CONSOLE)
- api/radio.rs: POST /api/radio {artist_id|track_id|album_id} — qobuz mode
  (get_radio_artist/track/album, zero local state). Reuses the new shared
  play::start_resolved for the queue-replace + cold-start ritual (protected
  audio untouched). Smart local-pool mode deferred (needs RadioDb::open_at
  plumbing through QbzCore — a shared-core change).
- api/play.rs: extract pub(crate) start_resolved (shared by play + radio).
- cli/radio.rs: `qbzd radio artist:ID | track:ID | album:ID`.
- cli/resolve.rs: `qbzd resolve <URL>` -> kind:ID token; pure client, no
  daemon and no network (qbz_qobuz::link_resolver).
- api/mod.rs: P1_ROUTES 6->7 (+POST /api/radio); route + test. main.rs:
  Radio + Resolve subcommands.

Verified: cargo test -p qbzd --bin qbzd -> 264 passed, 0 failed.

cc
2026-07-19 15:06:38 -06:00
vicrodh ba2f626208 feat(qbzd): add browse reads — album/artist/similar/suggest (CONSOLE P2)
qbzd can now BROWSE the catalog from the CLI: open an album, an artist page /
top-tracks / releases grid, find similar artists or albums, and pull For-You
suggestions — reads that feed the composition pipeline (`--ids | queue add -`).

- api/browse.rs: GET /api/album (get_album + optional get_album_suggest);
  GET /api/artist?view=page|top|albums (get_artist_page / get_artist_tracks /
  get_releases_grid); GET /api/similar?artist=|album= (get_similar_artists /
  get_album_suggest); GET /api/suggest?seed= (get_dynamic_suggest — seeds from
  the current queue when omitted). Typed serde shapes verbatim; auth-gated;
  blacklist fail-open.
- cli/browse.rs: `album`, `artist`, `similar`, `suggest`; a generic items/tracks
  walk drives the human list and --ids; `--seed -` reads ids from stdin.
- api/mod.rs: P1_ROUTES 2->6 (+album/artist/similar/suggest); routes + test.
  main.rs: four subcommands.

Verified: cargo test -p qbzd --bin qbzd -> 262 passed, 0 failed.

cc
2026-07-19 15:02:19 -06:00
vicrodh f2f65b0fbf feat(qbzd): add play <content> + POST /api/play (CONSOLE P1 row 23)
qbzd can now START playback of a track / album / playlist / artist / Qobuz
URL from the CLI — the receiver becomes a source. Server-side materialization
and the shipped cold-start ritual; the protected audio path is untouched.

- api/play.rs: resolve selector (a URL via link_resolver wins over id fields),
  fetch tracks (get_album / get_playlist / get_artist_tracks / get_tracks_batch),
  map via the now-shared queue::track_to_queue_track, stamp album/artist/
  playlist provenance, then set_queue -> play_track_resolved -> save_session_now
  (never a bare cursor move). Auth-gated; 404 on unknown id / empty resolution;
  503 on start failure.
- cli/play.rs: `qbzd play [album:ID|track:ID|artist:ID|playlist:ID|URL|N]`;
  bare `play` delegates to transport::play (one resume implementation). Local
  exit 2 on a malformed selector.
- api/queue.rs: track_to_queue_track -> pub(crate); api/playback.rs:
  resolve_quality -> pub(crate) (both now shared with play).
- api/mod.rs: P1_ROUTES += POST /api/play; route + test. main.rs: Play gains
  an optional content arg (bare = resume, unchanged).

Verified: cargo test -p qbzd --bin qbzd -> 256 passed, 0 failed.

cc
2026-07-19 14:54:56 -06:00
vicrodh 23d6cc71ec feat(qbzd): add search verb + GET /api/search (CONSOLE P1 row 19)
First slice of Proposal A (CONSOLE): qbzd can originate discovery — search
Qobuz by name from the CLI, not only receive via QConnect or take track
ids by hand.

- api/search.rs: GET /api/search?q=&type=&limit=&offset=. Typed searches
  return SearchResultsPage<T> verbatim under a per-category key; type=all
  fans out to the four typed searches. Auth-gated (needs_auth); blacklist
  fail-open by design (documented GUI-parity delta).
- api/mod.rs: new P1_ROUTES table + p1_route_table test — the shipped-caller
  HARD RULE (§3.1.4) applied to the content-verb door; route registered.
- cli/search.rs: `qbzd search <q> [--type all|albums|tracks|artists|
  playlists] [--limit N] [--offset N] [--ids] [--json]`. --ids emits ids
  one-per-line (pipes into `queue add -`); frozen exit-code table.

Verified: `cargo check -p qbzd` clean (no warnings); `cargo test -p qbzd
--bin qbzd` → 248 passed, 0 failed (10 new + 238 existing). New files are
standard-rustfmt; existing files carry only the minimal logical edits.

cc
2026-07-19 14:20:25 -06:00
vicrodh efc2db6dbb feat(renderer): femtovg partial-render scaffold (stage 1)
Inert scaffold for #617 partial rendering in the vendored femtovg-wgpu
renderer. Mirrors the Skia renderer: add an ItemTransform to the render
State (logical space), accumulate it in translate/rotate/scale
(pre_translate/pre_rotate/pre_scale), implement current_transform() (the
core ItemRenderer trait default is todo!()), and add
impl ItemRendererFeatures { SUPPORTS_TRANSFORMATIONS = true }.

Nothing consumes current_transform() yet — the core PartialRenderer proxy
is wired in a later stage — so this is behavior-neutral. Not yet compiled;
validates at the stage-2 build.

cc
2026-07-19 13:39:35 -06:00
vicrodh 075582920a fix(qobuz): 403 circuit breaker + prefetch backoff; header tooltips
Issue #637: a Qobuz outage + forced re-login left a user's account
transiently 403'd, and the no-backoff prefetch retry storm escalated it
into a per-IP edge/WAF block (the "error decoding response body" lines are
that non-JSON WAF body hitting a bare .json()). The bundle bump b019->b034
is exonerated: extraction yields byte-identical app_id/secret/private_key.

- ForbiddenBreaker: after repeated 403s the hot streaming/favorites paths
  short-circuit without touching the network, with an exponential cooldown
  and a half-open probe; success resets it.
- get_stream_url: typed 401/403 + body preview; abort the quality fallback
  loop on 403/401/breaker-open instead of firing all five qualities.
- CMAF session/start + file/url: feed the breaker, map 403.
- get_favorites + qws/createToken: check status before .json() so a 403
  surfaces as a 403, not a misleading "error decoding response body".
- Prefetch: per-track failure cooldown (negative cache) so a failed track
  is not re-hammered every queue tick.
- Audible path: a 403/back-off no longer counts as an "unavailable" skip;
  stop cleanly with a toast.
- QBZ_FORCE_403 test hook to smoke the back-off path on a healthy account.
- Header action-button tooltips for AlbumView/ArtistView/PlaylistView,
  translated across all 8 locales.

cc
2026-07-19 10:01:37 -06:00
vicrodh e81ed911f8 feat(library): ownership-aware playlist cards, Library All genre/sort, live fav/follow sweep
PlaylistCard now reflects ownership: owned playlists show favorite only;
foreign Qobuz playlists show follow/unfollow on the overlay plus follow +
copy in the context menu. Added is-owned/is-following/is-copied to
SearchPlaylistItem, seeded from the favorites owned/followed split and
owner.id == current_user_id elsewhere. Wired the actions id-scoped across
both routing families via shared helpers (copy/follow/favorite), so a card
toggles ITS playlist instead of the open detail.

Library "All" toolbar: genre filter (own "library-all" context over the
mixed feed; albums + tracks carry a genre, non-genre kinds hide when a genre
is selected) and PlaylistView-style sort menu (re-select the active field to
flip asc/desc). Tooltips on the four source toggles.

Live favorite/follow sweep across every card surface that was missed:
album favorite now reaches the Pinned carousel, the Recently Played "view
all" page and the artist "In library" grid; artist follow reaches Home Top
Artists, label-page artists and Pinned; new set_playlist_row_following flips
the follow chip on every playlist card (incl. Pinned) after subscribe.

cc
2026-07-19 02:44:43 -06:00
vicrodh 89d08c27b8 feat(playback): artist-card play — Popular tracks with studio-discography fallback
The artist grid/overlay play button emitted media-action("artist", id,
"play") but no Rust arm handled it, so it was a no-op. Add play_artist:
play the artist's Popular (top) tracks, and when the artist has none, fall
back to playing their studio discography — the "album" + EP/single buckets
in the page's section order, deduped by album id, excluding compilations,
live, and "other". Fetches the artist page once and decides from it; a
single unavailable album is skipped without a toast or aborting the rest.

cc
2026-07-18 23:38:20 -06:00
vicrodh e9d235f33e fix(renderer): reuse one wgpu instance/device across Wayland surface recreations
On Wayland every tray hide->show destroys the winit window and re-runs
the femtovg-wgpu set_surface, which with WGPUConfiguration::Automatic
created a brand-new VkInstance + VkDevice on each restore. The NVIDIA
driver segfaults on that churn: a null call inside libnvidia-glcore,
reached via libvulkan from set_surface before the first render
(reproduced under gdb, close-to-tray -> restore with playback starting
mid-restore; it crashed even while paused, ruling out the shader
drain path).

Create the wgpu instance/adapter/device/queue ONCE at startup and hand
it to Slint as WGPUConfiguration::Manual (Linux+Wayland only), so a
restore only re-creates the surface/swapchain on the SAME device. The
Automatic configuration stays as the fallback on other platforms and
on creation failure.

km
2026-07-18 20:43:06 -06:00
vicrodh 5ebe418805 fix(kiosk): dismiss on-screen keyboard on accept and tap-outside
The OSK opened on kiosk search/inputs but never closed: focus stayed on
the search field. Clear focus in the field's `accepted` handler and add a
full-bleed background TouchArea that clears focus on any tap outside the
input, so the on-screen keyboard dismisses.

cc
2026-07-18 19:50:27 -06:00
vicrodh a08d91eeef fix(kiosk): touch-scroll Settings via raw Flickable + ListScrollbar
The std ScrollView does not intercept a touch-drag that starts over the
dense SettingRow controls, so Settings did not finger-scroll on the Pi
kiosk. Replace it with a raw Flickable (intercepts the drag after a few
px) plus a ListScrollbar overlay for the desktop bar; scroll-position
restore now keys on the Flickable's own height.

cc
2026-07-18 19:50:27 -06:00
vicrodh 3b7b5a43a4 fix(library+tray): tray-restore crash via hide-clear; richer/cleaner All list rows
- Tray restore no longer crashes (RefCell panic on close / segfault on restore):
  clear ImmersiveState.shader-texture in tray::hide_window BEFORE the surface is
  destroyed (UI thread, no rendering-notifier RefCell borrow), instead of a
  deferred clear in the RenderingTeardown notifier that ran too late when the
  drain was parked (paused). The 30 fps drain repopulates it after restore.
- All list view: right-click opens the context menu AT THE CURSOR (window
  pointer minus the row's absolute position); the whole row is right-click
  sensitive (per-cell handlers), while the ⋯ button keeps anchoring the menu to
  itself. Hide the source column + grid source badges when show-local is off
  (all-Qobuz feed → no per-row Qobuz mark).
- Remove the now-unused FavArtistCard / FavLabelCard components (the Favorites
  Artists/Labels grids use ArtistGridCard / LabelCard).

cc
2026-07-18 17:41:39 -06:00
vicrodh 80986a02b4 fix(kiosk): immersive-exit returns to the pre-immersive shell; native-touch diagnostics
- Immersive exit now returns to the shell (kiosk or desktop) the user came
  from, and only drops fullscreen when it was NOT fullscreen before opening —
  so the kiosk appliance (which boots fullscreen outside immersive) is no longer
  dumped back to the windowed desktop shell on exit. Captured on the open edge
  in the fullscreen guard.
- Add a WindowEvent::Touch arm: a one-shot log confirming whether the compositor
  delivers native touch (RaspiOS/labwc mouse-emulation withholds wl_touch, which
  is why swipe/drag never works — a compositor-side fix, not an app bug), plus
  touch-point mirroring to ShellState so hover-revealed chrome reacts under
  native touch.

cc
2026-07-18 15:38:14 -06:00
vicrodh e1bdec5094 fix(library): proper All list rows, NVIDIA seam, close-to-tray crash, Go-to-album
- Rework the "All" list view to the aligned column layout of the mixtape detail
  list: artwork + hover play, title + clickable-artist subtitle, a TYPE column,
  a dedicated SOURCE column (SourceGlyph), the row QualityBadgeFull in a Quality
  column (was the uneven grid badge), favorite/follow indicator, and the per-kind
  context menu — no longer bare and out of tune.
- Fix the NVIDIA "wallpaper join" background seam: replace the float hash2 in
  ambient.wgsl + plasma.wgsl with a bit-exact integer (u32) lattice hash so the
  same cell corner hashes identically from adjacent cells on NVIDIA.
- Fix the close-to-tray "RefCell already mutably borrowed" panic introduced by
  the teardown texture-clear: defer the set via upgrade_in_event_loop so it runs
  after the window adapter releases its borrow.
- Add "Go to album" to the TrackCard context menu and the list-row menu (adds
  album-id to the feed model).

cc
2026-07-18 15:11:15 -06:00
vicrodh f484cc481f fix(library): tray-restore crash, local art/badges, LabelCard, live pins, rich list rows
- Fix the tray-restore wgpu crash (pre-existing, amplified by the app-wide
  background): clear ImmersiveState.shader-texture on RenderingTeardown so the
  first restored frame never hands a cross-instance stale texture to the new
  femtovg canvas.
- Local/Plex feed covers now decode: route the "All" artwork jobs through
  spawn_search_loads (http / local fs / Plex thumb) at all four dispatch sites;
  show the source badge on the feed's album/track cards (AlbumCard maps plain
  "qobuz" to the Qobuz logo).
- New card-sized LabelCard (200x246), used by the feed + the Favorites Labels
  tab (replaces the round FavLabelCard).
- Artist pin badge updates live in the Favorites Artists grid: add is-pinned to
  FavoriteArtistItem, stamp it, bind it in the ArtistGridCard, and flip the
  favorites artist models in set_artist_row_pinned.
- Rich "All" list view: type-aware subtitle, quality badge (album/track),
  source icon (local/Plex), favorite/follow indicator, and a per-kind context
  menu (⋯ button + right-click).

cc
2026-07-18 11:27:28 -06:00
vicrodh 8e0802768f fix(library): smoke-test fixes for All feed + local favorites + artist toggle
- All feed clicks were dead: the Albums/Artists/Playlists/Labels branch didn't
  exclude the "all" tab, so an empty Flickable overlaid the grid and swallowed
  every press. Exclude "all" from that branch.
- Local favorites never bound on the online session paths: init_for_user was
  only called in the offline shell. Bind it in login + restore, teardown in
  logout — so the heart flips and local items reach the "All" feed.
- Add the show-local source switch + the grid/list ViewToggle to the All
  toolbar; implement the compact list view.
- ArtistPage "In library" rows rendered blank: add ArtistLibraryTrack/Album
  artwork targets + dispatch cover jobs in navigate_artist.
- Move the catalog/library toggle into the artist header, floated right.
- Glass under the dynamic background: ToggleButton / ExpandableSearch /
  SegmentedTabBar go translucent when app-background-active.
- Library > Artists grid now uses the ArtistGridCard (flat + A-Z grouped).

cc
2026-07-18 02:59:12 -06:00
vicrodh 83df91439c feat(library): All mixed feed, TrackCard, artist playlists + library toggle, local favorites
- Library "All": mixed feed (tracks/albums/artists/playlists/labels) sorted by
  date added, reusing the existing cards; new TrackCard; toolbar (search + source
  switches + sort); "All" added to the Library nav surfaces.
- ArtistPage: restored the curated Playlists carousel (parsed but dropped in the
  Slint port); added the catalog/library toggle backed by a per-session
  per-artist index (favorites).
- Local-library favorites layer: new SQLite store (mirrors the pinned service),
  album-level; excludes the Qobuz offline cache at write time. Feeds "All" behind
  the show-local switch.
- i18n: 3 new strings across all 8 locales.

cc
2026-07-18 01:33:23 -06:00
vicrodh e84d1035f8 fix(cast): seek by fraction of the cast track duration
The seekbar derived the absolute position from the local core's playback
duration, which is 0/stale while casting (the local backend is stopped),
so fraction * duration collapsed to ~0 and every drag restarted the
track. Resolve the real duration from the cast track's catalog metadata
instead (new seek_fraction_if_cast). Fixes seek on both Chromecast and
DLNA.

cc
2026-07-17 21:22:36 -06:00
vicrodh 142142e317 fix(cast): clear residual DLNA transport state on connect
A renderer left mid-playback by a previous session re-requests its
orphaned CurrentURI against the fresh media server (new path token ->
403 loop) and reports a stale PLAYING position. That poisons
cast_max_position and trips the premature-STOPPED end-detection guard
into auto-advancing the first cast track. Issue a best-effort AVTransport
Stop right after connect so the transport starts clean.

cc
2026-07-17 21:22:35 -06:00
vicrodh 1638da05dc chore(deps): bump getrandom to 0.4 in qbz-cast
getrandom::getrandom() was renamed to getrandom::fill() in 0.3; adopt fill() at the cast token call site. Consolidates onto the getrandom 0.4.2 that tempfile and rand already pull in.

cc
2026-07-17 20:20:51 -06:00
vicrodh b8ee89b85c chore(deps): bump tempfile to 3.27.0
cc
2026-07-17 20:19:31 -06:00
vicrodh 1f608cddc9 fix(qobuz): back off between bundle-extraction retries
The 3 bundle-extraction attempts fired back-to-back, so a brief network hiccup
(DNS blip, dropped connection, captive-portal redirect) failed all of them in a
few ms and the retries were effectively useless — surfacing as "Failed to
extract bundle tokens: Network error" at sign-in. Add a short growing delay
(600ms, 1200ms) between attempts so a transient failure has time to clear.

cc
2026-07-17 19:36:49 -06:00
vicrodh 3b0d8e7641 polish(shader): soften ambient metaball edges
The metaball iso-surface + a strong high-frequency domain-warp octave produced
hard, clashing edges between the color lobes (visible over the translucent UI).
Gentle the high-freq warp (lower freq + amplitude), widen the iso-surface
smoothstep so lobes melt into the base with a long gradient instead of a hard
rim, ease the bright/dark contrast, and add a touch more dither against banding
— the flowing motion stays, the ugly edges go.

cc
2026-07-17 19:36:49 -06:00
vicrodh 1ee2a6c963 fix(label)+ui: label top-track artist/album from real endpoint + album column
- label.rs parse_top_track: the /label/page top_tracks carry the main artist in
  a track-level `artists` array (roles=main-artist), not performer/artist (null
  here), and the album title in `album.title`. Read both (with legacy fallbacks)
  so label Popular Tracks show the artist name + album (discussion #631).
  TopTrack gains an `album` field, threaded to the row.
- Artist + Label Popular Tracks: show-album: true so the album-title column
  renders (owner: album required in all PopularTracks lists).

Note: "Add to Mixtape" is already present in the artist/label Popular-Tracks
multi-select bar (cassette icon, tooltip) and the per-track "..." menu
(mixtape-action = qobuz-row) — no code change needed there.

Base: 6c41f58f.

cc
2026-07-17 16:28:24 -06:00
vicrodh 6c41f58f44 fix(shader)+feat(ui): nvidia shader tiling fix + scrollbar/mixtape/track-listing batch
NVIDIA shader fix (root-caused via diagnostic bisection on the discrete GPU):
- ambient.wgsl + plasma.wgsl: replace the sin-based hash `fract(sin(dot(p,k))*N)`
  with the sin-free Dave Hoskins hash. On NVIDIA, as noise coords grow with
  time, sin's argument gets huge and range-reduction precision diverges from
  Intel's, degenerating the noise into big blocks. Confirmed via a solid-color
  clear (clean) then a uniform-passthrough (clean) — isolating it to the shader
  hash, not the texture handoff.
- ImmersiveView: the 3 immersive-exit paths (X button, in-menu exit, ESC) kept
  the drain alive only for the Large NPB dock, freezing the shader texture on
  the last immersive frame when the app-wide Ambient background was on. Now they
  also keep it alive for Ambient so it renders the ambient scene after exit.

UI batch:
- Auto-hide scrollbar (Discover's ListScrollbar auto-hide + pointer hit-test)
  on AlbumView, ArtistView, PlaylistView, PlaylistManagerView.
- #631: artist Popular Tracks now show the album cover thumbnail (thread
  artwork_url through TrackData -> track_data_to_item + a new
  ArtworkTarget::ArtistTopTrack decode job, mirroring LabelTopTrack); label
  Popular Tracks fall back to the album's main artist for the name+id. Quality
  badge already rendered by TrackRow — reused, not reinvented.
- #446: "Add to Mixtape/Collection" bulk button in the Favorites/Artist/Label/
  Mix selection bars, mirroring the existing add-to-mixtape path (track items
  auto-restrict the picker to mixtapes, not collections).

slintcheck + Opus Rust review green (no compile blockers). Base: 8c2f84b7.

cc
2026-07-17 14:51:18 -06:00
vicrodh 8c2f84b798 feat(shell): per-GPU selector, ambient scene in immersive, glass carousels/viewall
Smoke round 5 (owner feedback). Checkpoint.

- Preferred GPU selector (Settings > Renderer, Linux/wgpu): lists each detected
  GPU by name ("Auto" + e.g. "NVIDIA … (discrete)" / "Intel … (integrated)"),
  persisted (ui_prefs.gpu_power by device name), applied at startup via the wgpu
  adapter PowerPreference (WGPU_POWER_PREF env still wins). Fixes the iGPU running
  hot under the shader background on hybrid laptops — pick the discrete GPU.
  New gpu_adapters() enumerates+dedups adapters; gpu_power_from_prefs /
  _options / _seed_index / _value_for_index.
- Ambient metaball added to the IMMERSIVE scene picker (mode 7, wgpu-only opt-in);
  the non-shader atmosphere stays the fallback for GL/software tiers.
- Glass treatment on the surfaces that were still opaque: carousel < > nav
  buttons (6 carousels), "Filter by category" pill, "Filter by genre" buttons,
  and the PlaylistBrowseView (View-all) header band + category radio row.
- i18n: "Preferred GPU" + description + discrete/integrated + restart toast (8
  locales).

Base: 76ea5a82. Prior checkpoint: a0f5cf2b.

cc
2026-07-17 10:54:45 -06:00
vicrodh a0f5cf2bf5 feat(shell): metaball ambient + translucent bars/panels/controls, drop carousel fades
Smoke rounds 3-4 (owner feedback). Checkpoint during iteration.

Ambient (the big one): rewrote ambient.wgsl from translating gaussian blobs to a
METABALL field (r²/d² summed + smoothstep iso-surface) with 2-octave domain warp
— lobes fuse/stretch/split like an amoeba / lava lamp (the Cider motion that was
missing), faster clock, more contrast.

Surfaces made background-aware (translucent when the dynamic background is on):
- Search results view (root + toolbar), artist Network panel + its tab-row
  header (transparent to avoid double-translucent compounding), JUMP TO bar
  (JumpNavBar bar-bg passed in — can't import ShellState without a cycle).
- Discover toolbars + tab pill + BrowseHeaderTools; SearchResultsView toolbar.
- CircleAction (shared: playlist/album/artist/etc. action buttons) on-surface
  secondary fill goes glass; playlist Sort dropdown; QbzSelect closed control.
- New app-background-bar-alpha knob (0.3, env QBZ_BG_BAR_ALPHA): thin bars go
  MORE transparent than panels (surface-alpha 0.5).
- Content well: frosted tint (0.22) + 1px hairline border → distinct panel.
- Album/Artist header gradient auto-suppressed under the dynamic background
  (they clashed); header text stays light on the dynamic backdrop.
- Discover carousel edge-fade "shadows" hidden when active (Carousel /
  PlaylistCarousel / ArtistCarousel / PinnedCarousel).

Base: 76ea5a82. Prior checkpoint: 4e405338.

cc
2026-07-17 08:08:33 -06:00
vicrodh 4e40533870 feat(shell): dynamic background shows through content area + livelier ambient
Smoke round 2 (owner feedback: content area was an opaque black slab, motion
imperceptible, ambient≈blurred).

- Content-area sweep: 27 content-view roots go transparent when the app
  background is active (was opaque Theme.surface-main covering the background) —
  Home/Discover/Album/Artist/Search-adjacent/Favorites/Playlist/LocalLibrary/
  MyQBZ/managers/etc. Content well (AppShell) transparent too.
- SongCard (NPB classic), search input box, cortinilla panel go translucent so
  they stop reading as opaque slabs over the background.
- ambient.wgsl: much faster + bigger blob travel + stronger domain warp +
  brighter/more saturated so the flow is obviously moving and clearly distinct
  from the blurred-art mode.
- Defaults: dim 0.35, surface-alpha 0.5.

Checkpoint during smoke iteration (owner-requested). Base: 76ea5a82.

cc
2026-07-17 02:42:59 -06:00
vicrodh 76ea5a82bb feat(shell): app-wide dynamic album-art background ("modo Cider")
Opt-in album-art background composited behind the whole shell, wgpu-tier
only. Appearance mode picker: Off | Ambient (GPU shader) | Blurred art.

- ambient.wgsl: new calm, low-energy album-colored scene (mode 7), drifting
  mesh-gradient of the primary/secondary/accent triad, no beat/transient.
  Registered in shader_underlay SHADER_SOURCES[5] + render_frame idx math.
- state.slint: AppearanceState app-background {modes,mode-index,available,
  dim,surface-alpha}; ImmersiveState.app-shader-mode (derived 1->7);
  ShellState.app-background-active (mode>0 && wgpu && has-track).
- visualizer.rs: drain renders shader-mode when immersive OPEN, else
  app-shader-mode (7=ambient) into the same shader-texture sink.
- AppShell: background layer at root z-bottom (Ambient=shader texture,
  Blurred=ImmersiveAtmosphere) + tunable dark scrim; shell chrome surfaces
  go translucent when active; viz-should-run extended to drive the drain.
- Whole-shell translucency: Sidebar/HeaderBar/PlayerBar(Small)/QueueSidebar/
  LyricsSidebar/SidebarNowPlayingDock + AppShell content well/frame/column.
- ui_prefs: persisted `app_background` (off|ambient|blurred), String-key
  pattern; appearance-select("app-background") write-back.
- Live-tunable knobs QBZ_BG_DIM / QBZ_BG_SURFACE_ALPHA (seeded at startup)
  so the look is dialed in one smoke session without a rebuild.
- i18n: 8 locales for the picker + warning; reduce-motion / D4 (no-track ->
  opaque theme) handled via derived active flag.

Design + signed decisions: qbz-nix-docs/specs/2026-07-16-app-wide-dynamic-background.md

cc
2026-07-16 23:50:40 -06:00
vicrodh d8127356d3 fix(playback): recover stuck-load watchdog and seed transport state on restore
Two playback fixes:
- The 45s loading watchdog only cleared the spinner, so a load that hung
  on a network stall (not errored) left the track dead at 0:00 with no
  recovery — the legacy fallback only reacts to a setup *error*, never a
  hang. Re-start the stuck track up to twice, then skip to the next
  playable track. The counter is keyed per-track and resets the moment
  real audio plays.
- NowPlayingState shuffle/repeat were only set on a manual toggle, so the
  NPB Large shuffle indicator stayed dark after a session restore. Seed
  both from persisted state in refresh_now_playing_meta.

cc
2026-07-16 20:38:04 -06:00
vicrodh c4ddca0223 fix(input): gate hotkeys while text inputs are focused
A use-site `changed has-focus` on a std-widgets LineEdit/TextEdit never
fires — has-focus is an alias into the inner TextInput (slint#7747) — so
typing a letter that doubles as a shortcut ran the action instead of
inserting the character. Sweep every text-input surface (search, import,
create/edit playlist & folder, tag editor, colour picker, settings
fields, QConnect dev modal, kiosk shell) and drive
UiFocusState.text-input-focused from a `guard-focused` probe property
that the change tracker does observe.

cc
2026-07-16 20:37:57 -06:00
vicrodh 134eddc1ae fix(share): resolve Song.link/Album.link via Deezer ISRC/UPC (#514)
The 2.0 port kept only the Odesli-by-URL path, which rejects Qobuz URLs
(400 could_not_resolve_entity), so album.link/Song.link never resolved
while the raw Qobuz link still copied fine. Restore the Tauri behaviour:
look the track up on Deezer by ISRC (album by UPC, with a leading-zero
retry) and build song.link/d/{id} / album.link/d/{id} from the match.

cc
2026-07-16 20:37:51 -06:00
vicrodh 8e57864bee fix(shell): #618 window identity/geometry + single-instance present; Plex refresh seed; wire discover/pinned state
Persist the Wayland app_id (set_xdg_app_id) and window geometry/maximized across
tray-hide and miniplayer round-trips so the pinned taskbar icon keeps grouping
and the size/position survive (#618); route the single-instance raise and MPRIS
Raise through a miniplayer-aware present() plus a dedicated D-Bus Present method.
Seed the Local Library Plex refresh button after login (not before, when the
per-user store is unbound). Also the shared wiring these features need in
main.rs / state.slint / app.slint / home.rs / artwork.rs (pinned globals + store
seed + mixed-artwork target, playlist-browse nav, sub-genre reload) and the
bundled i18n strings.

cc
2026-07-16 16:31:51 -06:00
vicrodh 288e982b44 fix(ui): sidebar dock margins, queue count footer, macOS NPB-small corner
Homologate the large-dock cover's side margins (16px each vs the visible content
bezel) and add a 4px bottom gap by trimming the FFT band 2px top/bottom. Move the
queue count out of the "Up Next" header into a fixed footer ("Showing X of Y
tracks", dropping the redundant "left"). On macOS, inset the flush-left NPB-small
art so the native rounded window corner stops clipping it.

cc
2026-07-16 16:31:39 -06:00
vicrodh f26d3e5e1d feat(settings): searchable theme dropdown
Enable the searchable QbzSelect on the theme picker (36 themes) — the search box
the migration deferred. Rides the #616 theme-filter (icon swap + list narrowing,
already on pre-release); the search filters within the active All/Dark/Light
facet.

cc
2026-07-16 16:31:39 -06:00
vicrodh 7138dafbdb feat(home): Pinned section — per-user store, mixed carousel, pin affordances
Users pin albums/artists/playlists to a configurable "Pinned" section on Home
and For You. New per-user SQLite pinned-items store (headless in qbz-app +
lifecycle wrapper), a mixed PinnedCarousel dispatching Album/ArtistGrid/Playlist
cards by kind, a rotated pin glyph (hover-only) on those cards, and Pin/Unpin
from the playlist header and the album/artist context menus. Local/Plex albums
resolve to their Local Library page (id-format routing) and render their real
covers via the mixed-source artwork dispatcher.

cc
2026-07-16 16:30:30 -06:00
vicrodh d87ae8a65f feat(discover): ArtistGridCard for mixed carousels, used app-wide
New 200x246 artist card (framed round portrait, follow/play/more hover overlay,
centered wrap-capable name with an optional reco subtitle) matching the album/
playlist card footprint. ArtistCarousel renders it by default across the app
(search "Most popular" hero included); the legacy 160x220 ArtistCard stays for
the label/award carousels whose artist actions don't apply.

cc
2026-07-16 16:30:30 -06:00
vicrodh 64920a76e4 feat(discover): Qobuz Playlists "View all" page + shared header tools
Port the Tauri playlist "View all": a new PlaylistBrowseView with the category
tag bar, search filter, genre filter and grid/list toggle, extracted into a
shared BrowseHeaderTools and a reusable PlaylistListRow. Restores the "Popular
albums" see-all and aligns PlaylistCarousel's "View all" affordance with the
standard carousels.

cc
2026-07-16 16:30:12 -06:00
vicrodh 6aa314b8ef fix(discover): sub-genre filter sends raw ids to the server (Tauri parity)
/discover/index honors sub-genre ids server-side, so send the raw selection
(parent or sub-genre, exactly as toggled) instead of mapping to the top-level
ancestor and narrowing client-side by genre.path — the previous hybrid widened
sub-genre selections back to their parent and dropped albums tagged only at top
level. Removes the client-side narrowing and its now-dead helpers; keeps a
diagnostic log of the outgoing genre_ids.

cc
2026-07-16 16:30:12 -06:00
vicrodh f778874fdd fix(audio): #508 ALSA-exclusive + streaming regression; flatpak ReserveDevice1 own-name
Store the CMAF duration at command-accept so the seek bar and progress can't
freeze at 0:00, backfill it on the Resume rebuild, and clear + surface the
zombie stream-create-failure path. Degrade every non-priority device-reservation
contention error (only a higher-priority holder stays fatal) so a sandboxed
D-Bus RequestName denial can't block bit-perfect playback. Honor the
stream_buffer_seconds floor in the dynamic CMAF path and wire the network
throttle (record_segment_bandwidth / record_underrun / prefetch cap) to tame the
#591 underrun loop. Grant --own-name=org.freedesktop.ReserveDevice1.* in the
repo flatpak manifest so WirePlumber releases the card cleanly.

cc
2026-07-16 16:29:58 -06:00
vicrodh b4bff581ae feat(qbzd): wide sidebar, wrapped descriptions and fixed control column in setup TUI
cc
2026-07-15 12:15:32 -06:00
vicrodh e6cb5114a9 fix(qbzd): posture voice on the TUI network screen and pinned boot note
cc
2026-07-15 11:41:29 -06:00
vicrodh e6cc082669 feat(qbzd): default control-plane and login binds to 0.0.0.0 for LAN-first posture
cc
2026-07-15 11:32:44 -06:00
vicrodh 2e9e6a35ac fix(qbzd): honest OSC 52 copy semantics, copy-all file artifact and wizard polish
cc
2026-07-15 11:09:17 -06:00
vicrodh 90412328be feat(qbzd): hifi wizard section with copyable config blocks in setup TUI
cc
2026-07-15 10:49:38 -06:00
vicrodh ff1dbf7425 feat(qbzd): frames layout for setup TUI — sidebar nav, content frame, header and breadcrumb
cc
2026-07-15 10:10:09 -06:00
vicrodh 927b5aa762 fix(qbzd): footer state mapping test, layout parity and help-tint precision in themed TUI
cc
2026-07-15 08:47:15 -06:00
vicrodh 74dcc60b18 feat(qbzd): themed setup TUI with section borders and accent palette
cc
2026-07-15 08:37:56 -06:00
vicrodh 6b444f9748 fix(qbzd): family-aware bind fallback, URL-first output and IP-validated callback host
cc
2026-07-15 08:17:39 -06:00
vicrodh 7799017c69 feat(qbzd): auto-detect LAN callback host from SSH sessions for login
cc
2026-07-15 08:08:49 -06:00