Commit Graph
100 Commits
Author SHA1 Message Date
vicrodh ea644f5e05 feat(qbz-qt): phase 19 — full Settings subnav, Appearance + Integrations, live theming
Theme system as data: qbz_theme registry (36 themes + auto/custom)
resolved from the shared ui_prefs slug into a themeJson document;
QbzTheme rebound to it so the whole app repaints live (OLED->Light
probe-verified). Full subnav (8 sections + Share logs footer);
Appearance 1:1 (theme dropdown with dark/light filter, ambient/titlebar
rows in their real home, typography, library visuals, window chrome,
tray via tray_settings.db, renderer); Integrations 1:1 (MusicBrainz,
Last.fm 2-step browser connect + authed state, ListenBrainz token
validate, Discord toggle -> live DiscordRpc.set_enabled). Offline/Local
Library/Blacklist/Developer render honest placeholders.

km
2026-07-28 06:13:04 -06:00
vicrodh c59c883ac1 feat(qbz-qt): phase 18 — all four NPB modes + ViewMode flyout
PlayerBar.qml covers New/Classic/Large (full-width seekbar with times,
responsive 3-column layout, glass Classic SongCard variant, right
cluster), NowPlayingBar seam Loader picks per mode; Small unchanged.
ViewMode flyout wired (New/Classic/Small/Large live, Miniplayer/
Immersive/Kiosk inert replicas), mode persisted to the shared
ui_prefs npb_mode key, Large forces the sidebar open and docks the
208px cover (SidebarNowPlayingDock equivalent); AppShell bar height
mode-aware (42/112). Empty and playing states verified per mode;
user's persisted small pref restored after probes.

km
2026-07-28 05:02:11 -06:00
vicrodh 8c1ff34205 feat(qbz-qt): phase 17 — PlaylistView 1:1 + drag & drop as Slint
Full playlist surface: header with owner/foreign gating, play/shuffle,
favorite/follow optimistic, copy-to-library, pin, rename, delete,
per-row remove for owners, search filter, full sort set, custom-order
drag reorder persisted per user. Sidebar and every playlist card
navigate; card overlay/menu actions un-stubbed. Drag & drop per the
Slint gesture: row-body press-drag (>6px) from album/playlist/search/
library rows, window-top ghost pill, sidebar playlist rows drop-hot
highlight, drop -> add_tracks_to_playlist + reload reconcile; in-list
release reorders. Round-trip proven end-to-end on a throwaway playlist
(create -> drag -> drop -> refetch shows track -> delete).

km
2026-07-28 03:39:35 -06:00
vicrodh 22e65e939b feat(qbz-qt): phase 16 — card overlays everywhere + window-centered modals
Overlay opacity bindings referenced a nonexistent parent chain — all
card overlays evaluated invisible. Fixed per family: LibTrackCard
(scrim + fav/play/more row, full Slint menu), LibArtistCard rewrite
(circle-clipped scrim, play + more; play_artist with Popular-tracks
first and studio-discography fallback), LibPlaylistCard overlay fixed,
LibLabelCard verified overlay-less like the Slint. Modals: shared
AppShell-level layer (full-window scrim, window-centered panel)
replacing the view-local content-frame-centered ones.

km
2026-07-28 02:26:24 -06:00
vicrodh 9eb46d5e25 feat(qbz-qt): phase 15 — search 1:1 (cortinilla + full page with filters)
Intelligent search engine reused from qbz_app (SWR cache, interaction
recording, rank_within, top_for_query; app-menu kill switch on the
shared pref). Header field fully live: 220ms debounce, keyboard
semantics (arrows/Enter/Esc), clear, idle-close. Cortinilla as a plain
overlay (pointer stays on the field — Slint's exact reason): top
result, spec-ordered sections with View more, skeleton, row activation
dispatches (album/artist views, track play). Search page: 5 searchType
filter radios (Main Artist/Performer/Composer/Label/Release Name),
5 scope tabs, Most-popular hero, carousels, per-type grids/lists with
paged Load more. Perf: cortinilla 245ms cached / 1.1s cold. Fixed an
artwork attach bug the screenshots exposed (url/path slot mixup).
18/18 tests.

km
2026-07-28 00:49:53 -06:00
vicrodh 9f86a40f52 feat(qbz-qt): phase 14 — app-wide ambient background (modo Cider)
Port of the spec's palette derivation (16x16 downscale, coverage-
weighted 24-bin hue histogram, genuine secondary cluster or deeper
same-hue, +180 accent, B/W defaults) driven by the now-playing artwork
on track change. QPainter AmbientField with the WGSL scene's exact
four blob orbits, dim scrim, 30fps cap paused when inactive; chrome
surfaces translucent per the AppShell alpha tokens (content frame
surface-main @ 0.22 frosted). Same shared app_background pref (owner
has it 'ambient'), app-menu toggle applying live, QBZ_BG_* env knobs.
4 new unit tests (14/14 total).

km
2026-07-27 23:37:13 -06:00
vicrodh cc71337293 feat(qbz-qt): phase 13 — correct card families in Library + Discover tabs
Library audit: the phase-7 conversion never actually mounted the shared
AlbumCard — ALL kinds used the invented feed card. Now per-kind 1:1:
LibTrackCard (discover/TrackCard replica with wired next/later/queue
enqueue), shared AlbumCard for albums, LibArtistCard (ArtistGridCard
circle), LibPlaylistCard (collage, follow-aware), LibLabelCard
(gradient + contain-fit logo), list rows rewritten to the real
FavoritesView inline row, Tracks tab on a TrackRow replica. Discover:
Editor's Picks and For You tabs rendered from the same discover-index
fetch, prefs-ordered per DiscoverPrefsStore (incl. mostStreamed#album
variant, Qobuz Mixes gradient tiles, Pinned in the user's own position);
Recommendations tab gated on the user's pref with an explicit engine
placeholder. No refetch on tab switches.

km
2026-07-27 22:35:21 -06:00
vicrodh 71d41b927c fix(qbz-qt): phase 12 — card rounding, menu anchoring, opaque window, titlebar toggle
Rounding: QML clip is rectangular (radius+clip does NOT round-clip) —
new shared RoundedImage (software Canvas raster clip, GL-independent)
applied at 16 artwork sites incl. true circular artist crops. Menus:
new QbzContextMenu (cursor-anchored / below-trigger, window-clamped)
converted at 7 sites, replicating TrackRow.slint positioning. Window:
main window is OPAQUE in Slint (transparency is miniplayer-only;
rounded corners are KWin's) — dropped the translucent frameless
misread, squared app corners, opaque root. Titlebar: wired the EXISTING
shared ui_prefs use_system_title_bar key (app-menu checkable item,
restart semantics like Slint).

km
2026-07-27 21:43:53 -06:00
vicrodh da36d86ccc feat(qbz-qt): phase 11 — final polish vs owner's Slint reference shots
Fixed an ALSA enumeration zip bug that silently dropped the last device
(the stored DAC now shows with its BP badge). Home rails now follow the
persisted DiscoverPrefsStore (order + disabled sections) like the Slint
shell; Pinned rail built from pinned_items.db (albums/artists/playlists,
ArtistGridCard circle); View-all header links on endpoint rails; pin
badge hover-gated like the Slint overlay buttons. Restore path now
binds the pinned + local-favorites stores (was login-only). Settings
panels verified row-for-row against the 3440px references.

km
2026-07-27 20:01:02 -06:00
vicrodh d57cec974f feat(qbz-qt): phase 10 — Settings > Audio and Playback 1:1
settings_qt.rs controller over the shared stores (AudioSettingsState,
PlaybackPreferencesState, ui_prefs.json, qconnect_settings.db) with the
upstream cascades (dac-passthrough->skip-sink-switch, streaming-only->
gapless, backend switch resets routing) and live apply through public
player APIs (reload_settings/reinit_device/clear_audio_cache). ALSA
device regrouping (Defaults/Bit-perfect/Plugin/Other + BP badges),
searchable QbzSelect, QbzToggle/QbzSlider/SettingRow replicas, exact
row order and gating. Persistence probe verified (gapless +
streaming-quality round-trip, original values restored).

km
2026-07-27 19:12:06 -06:00
vicrodh b10ff98620 feat(qbz-qt): phase 9 — Queue homologation + Lyrics side panel
Queue panel 1:1: History tab (click replays per queue.rs semantics),
#442 'Next in queue'/'Next up' section markers from manual_next_count,
40-row pagination + live search + page clamp, row actions (play,
remove, remove-all-after, heart), press-drag reorder via move_track,
clear queue, plural-aware footer count, exact empty states. New Lyrics
panel: qbz-lyrics service + per-user cache wired, Qobuz-first chain
with LRCLIB fallback, synced line view with line-level highlight +
centered auto-follow + click-to-seek, exact status bodies. Both panels
stack Feishin-style in the right column; NPB lyrics button wired.

km
2026-07-27 18:22:22 -06:00
vicrodh 86de977f3c feat(qbz-qt): phase 8 — AlbumView and ArtistView 1:1
AlbumPageView replica: 224px cover, credits, meta, description modal,
action row, quality badge + track search, exact tracklist (playing-row
pill, disc/work headers, live hearts, row menus), label/awards
sidebar, same-artist + suggestions carousels. ArtistPageView replica:
circular portrait, bio modal, From catalog/In library toggle (both
arms live), JUMP TO tabs with jump-scroll, Popular Tracks with Load
more, server-bucketed release sections paged per section, Appears On,
Network sidebar (labels, similar artists). Card clicks navigate
everywhere via the nav stack; new playback paths (play from track,
shuffled album, track lists). Owner data live: album 654ms, artist
670ms loads.

km
2026-07-27 17:49:08 -06:00
vicrodh 4d196223cd feat(qbz-qt): phase 7 — shared AlbumCard, rounded frameless window, sidebar playlist tree
One AlbumCard.qml replicating the Slint shared card (pin badge with
PinnedItemsService wired, heart, hover play overlay, exact context
menu with Play next/Add to queue wired) used by BOTH Home rails and
the Library All grid. Frameless translucent window with 12px rounded
corners, replicated WindowControls, header drag, 8 resize grips.
Sidebar playlist tree: 56 playlists / 12 folders live, expand state,
5-option sort with direction caret, recursive search filter, collage
covers, create-playlist wired. Fixed a std-Mutex self-deadlock in the
sidebar loader (two lock temporaries in one log statement).

km
2026-07-27 16:45:44 -06:00
vicrodh c06297c915 feat(qbz-qt): phase 5.5 — visual polish pass audited against the Slint sources
Screen-by-screen audit with before/after screenshots: rounded
surface-main content panel with 8px bezel (the Slack-style frame),
real splash card, QbzPrimaryButton/QbzCheckbox/QbzTextLink replicas,
accent arc spinner replacing BusyIndicator, custom thin auto-hide
scrollbars (Home + Library grid/list), carousel edge fades, toolbar
pill offset, QbzSlider/SeekBar metrics, alpha-format re-audit of all
8-digit hex literals. Pure look — zero functionality changes.

km
2026-07-27 15:19:24 -06:00
vicrodh 1b81b289d1 feat(qbz-qt): phase 5 — Library view 1:1 with All grid/list and perf marks
Six live tabs (All/Tracks/Albums/Artists/Playlists/Labels) with real
count badges, paged favorites/playlists/purchases loaders, library_all
merge semantics, heart routing (Qobuz API vs local store —
LocalFavoritesService now initialized at session entry), sort popup
with direction carets, grid/list toggle with natively windowed
GridView/ListView delegates, id-keyed artwork via signal with hand-
rolled eviction. Perf marks on the owner's account: 344 items, fetch
wall 2.7-3.4s (97% network, sequential), map+merge ~11ms, JSON
serialize 10ms, QML parse 7ms (~20us/item => ~140ms at the 2200-item
reference scale), RSS +29MB after publish.

km
2026-07-27 14:57:29 -06:00
vicrodh b405755c1a merge: pull smoke-2026-07-25 backend for the Library All POC phase
Brings the windowed Library All backend (qbz-library) and the latest
Slint reference implementation into the POC base. POC branch only;
pre-release untouched.

km
2026-07-27 14:05:08 -06:00
vicrodh 80dcd85a91 feat(qbz-qt): phase 4 — live playback, queue, and transport via QbzCore
Album-card click plays the album: get_album -> QueueTrack vec ->
set_queue -> play_track_resolved (tier walk stays in the core). 1 Hz
poll publishes position/meta/quality into the NowPlaying model; end-of-
track edge auto-advances with meta refresh (traced live: 24-bit/48kHz
CMAF stream, position advancing, track 2/3 transitions). Transport
invokables rewired to real core calls; queue panel shows NOW PLAYING +
numbered UP NEXT from get_queue_state. Public QbzCore API proved
sufficient — protected audio/player crates untouched. Named gaps:
no mute API, no playback event stream, auto-advance/prefetch semantics
remain frontend glue (~150 lines every UI must port).

km
2026-07-27 14:04:58 -06:00
vicrodh 95df643a25 feat(qbz-qt): phase 3 — Discover > Home with live Qobuz data and artwork
Pure mapping port of home.rs/foryou.rs (quality tiers, award ribbons,
slim grids) into Slint-free rows; discover index + favorites +
release-watch + top artists fetched concurrently (12 sections, ~170
cards live-verified). Artwork via the shared qbz-cache ImageCacheService
(semaphore-16 downloads, file:// rows, native QML decode). HomeView.qml:
tab pill, five rail kinds, shimmer skeleton, error+Retry, offline
placeholder — exact msgids throughout. Sections travel as one JSON
document (cxx-qt-lib cannot nest QVariant containers — documented).

km
2026-07-27 13:36:01 -06:00
vicrodh 744bce3819 feat(qbz-qt): phase 2 — app shell with 1:1 Slint visual parity
HeaderBar (42px, sidebar cycle, back/forward, centered search replica,
offline badge + flyout, app menu), three-state Sidebar (open 240 / mini
64 / closed 0, 160ms animation, nav rows with exact msgids), NowPlaying
bar SMALL (3px three-color seekbar, art+title+time left, transport
center, quality/volume/queue-toggle right, exact empty state), Queue
panel (300px animated column, Queue/History tabs, exact empty states).
Nav history stack + NowPlayingModel with empty-state defaults behind
new bridge properties/invokables (transport log-and-noop until
playback lands). Icons as pre-baked tinted SVG variants (ColorOverlay
is broken on the software path); Inter fonts bundled; full dark theme
tokens ported from the Slint foundation files.

km
2026-07-27 12:56:38 -06:00
vicrodh df7eb3588e feat(qbz-qt): phase 1 — login screen with full offline guardrails
QbzBridge QML singleton (12 properties, 8 invokables) driven by tokio
tasks via CxxQtThread. Port of auth.rs (browser OAuth loopback listener
with qbzd-style nonce, 180s timeout, cancel via JoinHandle abort,
scoped offline-gate lift for cold bundle init, D1 token-keep on network
failure / clear on explicit rejection, D4 subscription grace marks).
OfflineModeEngine + ConnectivityActor started at boot with live watch
forwarding (connectivity/captive portal/recovery banner/has-previous-
session). Boot: splash -> silent token restore -> shell | login.
Start-offline enters the guest profile. LoginScreen.qml is a faithful
port with exact msgids routed through qbz-i18n. 5 unit tests green.

km
2026-07-27 12:09:31 -06:00
vicrodh 469f83781d feat(qbz-qt): phase 0 — crate skeleton + cxx-qt toolchain gate
New qbz-qt bin crate (cxx-qt 0.7 + QML) as the only default workspace
member in this worktree, so plain cargo build produces just the Qt
binary. Gate verified against system Qt 6.11.1 (codegen, moc,
qmltyperegistrar, qrc load, offscreen run all green).

km
2026-07-27 11:36:18 -06:00
vicrodh ae519279f6 fix(cast): error-tolerant DLNA discovery loop + per-device logging (#667)
The window loop broke on the FIRST device whose description fetch/parse
failed, silently dropping every other responder in that window — and
every subsequent window, forever, with zero log lines. One malformed
UPnP device on the LAN (router, TV, printer) could starve the whole
DLNA list. Now each error is logged and skipped, and every discovered
renderer is logged at info level (name / ip / capability flags), so the
next field report is self-diagnosing.

km
2026-07-27 09:57:57 -06:00
vicrodh a69491a49e feat(player): re-wire the low-memory profile into qbz/qbzd (#660)
The system_capabilities profiler (Normal vs LowMemory, built for #331)
lost all its consumers in the Tauri removal; 2.0.2 shipped it as dead
code and qbzd got OOM-killed (exit 137) on 1 GB Pi hosts at track
transitions. Phase A wiring:

- Frontends read memory_profile() at startup and push it down via the
  new player memory_tuning (initial-buffer cap, L1 budget, low-memory
  gate) before the Player is created.
- L1 audio cache sized from the profile (400 MB Normal / 50 MB
  LowMemory; was hardcoded 400 MB).
- Oversized tracks (>L1/4 = 12.5 MB on LowMemory) skip the promotion
  clone and the parallel CMAF RAM accumulator entirely; the finished
  track is streamed to the L2 disk cache in 1 MB chunks straight from
  the playback buffer — no second in-RAM copy. Gapless degrades to a
  small gap for those tracks (a gap beats an OOM kill).
- Stream buffer preallocates total_size up front (try_reserve_exact,
  1 GiB cap, dynamic fallback) — no more ~8 doubling reallocs + memcpy
  per track (weak-CPU relief).
- New memory-pressure watchdog (15 s): critical pressure evicts the
  L1 (L2 disk kept) and halts prefetch; low pressure pauses HiRes
  prefetch (downgrades to Lossless). Spawned by both frontends; qbzd
  aborts+joins it for the #521 shutdown ordering.
- Prefetch honors the profile (depth cap, HiRes->Lossless downgrade).

Normal-profile behavior is byte-identical (every behavioral change is
gated on the LowMemory class; profile values equal the old hardcodes).

Phase B (follow-ups): tempfile spill of the live stream buffer for
giant tracks, POLLERR flood rate-limit, desktop kick_prefetch halt
check, L2-read on the play_track cache-hit path.

km
2026-07-26 23:56:39 -06:00
vicrodh 96acc4386a test(qbz-app): install the rustls CryptoProvider in shell tests
Follow-up to #663: with reqwest no longer pulling a rustls provider,
QbzCore::new (MusicBrainzClient) panicked "No provider set" in 6
shell tests. The test ctor now calls ensure_crypto_provider(), exactly
like the real binaries do at startup.

km
2026-07-26 23:56:38 -06:00
vicrodh ad6c98ff7d feat(queue): Play later everywhere — every context menu and bulk bar (#442)
Extends Play later (end of the manual block) from track rows to every
surface that already offered Play next / Add to queue:

- playback.rs: enqueue_album_later, enqueue_playlist_later (mixed
  sidecar interleave), enqueue_artist_top_later, enqueue_tracks_later,
  enqueue_local_tracks_later — all NATURAL order (each add_track_later
  appends, no reverse), QConnect controller routing via
  play_later_batch_on_peer_if_active, and post-add queue sync.
- local_playlist::enqueue_later_by_id (source-aware rows, offline-only
  stamp preserved), myqbz_play RowMode::PlayLater + BulkEnqueueMode.
- 19 dispatch arms in main.rs (album/playlist/artist/label/mix/
  favorites/local-library/album-page/disc/myqbz dialects).
- 12 context menus + 10 bulk bars in .slint: list-plus icon between
  Play next and Add to queue, popup heights bumped.

The immersive tap-action dropdown is deliberately untouched (it is a
configurable single-tap behavior setting, not a context menu).

km
2026-07-26 21:08:12 -06:00
vicrodh 09380d4634 Merge branch 'pre-release' into chore/internal/smoke-2026-07-25 2026-07-26 16:45:03 -06:00
Victor RH fe2be63f0f Merge pull request #663 from fengalin/fix/external/qbzd-add-tls-provider
fix(qbzd): add tls provider
2026-07-26 16:42:35 -06:00
vicrodh 6ca5813fb1 feat(mpris): expose xesam:url with the public Qobuz track link (#658)
playerctl scripts/widgets can now identify the exact song:
https://open.qobuz.com/track/<id> on every Qobuz catalog row (offline
qobuz_download keeps the real id, so it qualifies too). Local/Plex rows
omit the field — QueueTrack carries no file path to map to file:// and
a synthetic local id would mint a bogus Qobuz link. Wired in both the
app (refresh_now_playing_meta) and qbzd (track_meta).

km
2026-07-26 16:38:38 -06:00
vicrodh def93528bc fix(audio): open ALSA mixer on the card ctl id, not the PCM alias (#659)
set_hardware_volume attached the mixer to the stream's raw PCM id
(iec958:/hdmi:/front:/DEV-qualified hw:) — snd_mixer_attach needs the
per-card ctl device, so hardware volume silently no-oped on plugin-alias
devices (HiFiBerry Digi as iec958:CARD=sndrpihifiberry,DEV=0). Derive
hw:CARD=<name> / hw:<n> first. With this, qbzd's 'Hardware volume'
toggle reaches the DAC's mixer control (e.g. 'Digital') as intended;
with the toggle off, ALSA Direct stays fixed-volume by design
(bit-perfect, no software gain).

km
2026-07-26 16:32:30 -06:00
vicrodh 4d2ddec66e fix(ui): heart-crack icon for Remove from Library/favorites actions
Replaces the trash can (Favorites bulk bars, tracks + albums) and the
plain heart (Local Library bulk bar) with Lucide heart-crack — trash
reads as file deletion, not as un-favorite.

NOTE: touches .slint, so the next build recompiles qbz-ui (long path).

km
2026-07-26 16:26:58 -06:00
vicrodh 64fe89cf52 feat(qconnect): play-later positional insert on peer + queue sync on add
Controller mode: play-later now routes to the peer's manual-block tail
(insert_after steered via a new ControllerManualBlock tracker that
confirms echo positions) instead of degrading to play-next; play-next
inserts also feed the tracker. Single + batch variants.

Local renderer mode: every queue ADD (enqueue/play-next/play-later,
album/playlist/artist/favorites/local batches) now pushes the queue to
the session immediately via sync_local_queue_if_changed, so controllers
see new items at their position instead of waiting for the next track
transition. Adds containing local/plex rows skip the push (all-or-
nothing admission would refuse the whole queue anyway).

km
2026-07-26 15:45:00 -06:00
vicrodh 449f2470f6 fix(ui): mixtape row menu play-next/add-to-queue icons (list-start/list-end)
km
2026-07-26 15:45:00 -06:00
vicrodh 3f1a7d074a fix(build): move the bulk-unfav toast closure (E0373)
km
2026-07-26 15:00:50 -06:00
vicrodh 5dc4d553dc fix(favorites): bulk un-favorite without the UI freeze, with feedback
- Local Library bulk un-favorite was O(selected x visible) on the UI
  thread and froze on big libraries (owner: 'se ciclo'). Now: one model
  pass (hearts hollow instantly), store mutations on a blocking thread,
  'Removing…' info toast up front and a success toast at the end.
- Favorites > Tracks bulk remove: 150 ms courtesy delay between Qobuz
  API calls (no burst) + the same progress toasts.

km
2026-07-26 14:59:29 -06:00
vicrodh 8c1750a78f fix(ux+audio): #661 pause-during-load, anti-alias ring, menu icons, bulk unfav
- #661: a pause issued during the load window fell into the toggle's
  resume branch and got swallowed — the track started anyway and the
  sleep inhibitor stayed held. stop() now cancels the in-flight play
  (PENDING_PLAY_ID), so nothing plays and no inhibitor is acquired.
- Accent ring on the playing row: 1px -> 1.5px border (femtovg 1px
  stroked circles alias; CircleAction's header rings use 1.5px).
- Menu position hints (#N) removed from the track context menu (owner).
- Icon mapping across menus: list-start = Play next, list-end = Add to
  queue, list-plus = Play later; new list-start.svg +
  list-indent-increase.svg (queue manual-block section marker).
- Local Library > Tracks multi-select gains bulk Remove from favorites
  (local-favorites store, live heart flip, toast).

km
2026-07-26 13:22:53 -06:00
vicrodh 51b5f3bec7 fix(build): compile errors from the round
- with_db closures return the raw Result (not Option) in the all-local
  feed loader.
- build_tree takes sort_asc (its own scope, not rebuild's).
- reconcile_sidebar_after_import captures first/single before the
  closures move ids.

km
2026-07-25 20:45:03 -06:00
vicrodh 83b0ada9b9 Merge branch 'bugfix/internal/local-library-tag-fallback' into chore/internal/smoke-2026-07-25
# Conflicts:
#	crates/qbz-library/src/database.rs
2026-07-25 12:07:56 -06:00
vicrodh cb1ae7db6f i18n: strings for the round (8 locales)
Library-All scope selector + settings rows, play-indicator/swipe
toggles, sidebar/manager sort carets, Play later + queue sections +
menu hints.

km
2026-07-25 12:05:56 -06:00
vicrodh efa486ba4f feat(packaging+log): standalone qbzd packages, IP masking in logs
- nfpm-qbzd.yaml (deb/rpm, libasound-only dep) + release workflows build
  the standalone qbzd packages per arch; dual dispatch to the future
  qbz-rpm repo (continue-on-error until token scope lands).
- Gentoo overlay templates: qbzd (source build) + qbzd-bin (tarball
  repack) with systemd user unit + completions.
- Log redaction layer 3: IPv4 -> *.*.*.<last>, IPv6 -> *:*:<last>, with
  version/timestamp false-positive guards (qbz-log 10/10 tests).

km
2026-07-25 12:05:56 -06:00
vicrodh a8919cbadf feat(app): the round's wiring — gamescope, watchdog, geometry, hearts
- Steam PR1/PR3/PR4 wiring: apply_gamescope_quirks at the top of main(),
  tray + miniplayer suppression, gilrs spawn, UiFocusState OSK hook.
- #558 watchdog arm at window-ready; #654 monitor-DPR geometry clamp;
  local-heart routing in the favorite arms; play-later media-action arm;
  sort-flip handlers with direction (#657/Library All pattern); gilrs +
  wayland-data-control deps (#514).

km
2026-07-25 12:05:56 -06:00
vicrodh 953ba39e77 feat(playlists+queue): sort directions (#657), import refresh, Play later (#442)
- Sidebar + playlist manager sorts gain both directions with a caret on
  the active row (re-select flips); 'recent' keeps the API order
  (newest-first — the old reverse was the #657 bug).
- Import now inserts the new playlist optimistically and bounded-retries
  until the API lists it (read-after-write lag forced a manual refresh).
- Play later: tracks land at the end of the manual block (after every
  play-next/later, before the source resumes); Play next and Add to
  queue are untouched. The queue sidebar shows the section divider
  (Next in queue / Next up) with drag-math compensation, and the context
  menu shows the landing position of each queue action.

km
2026-07-25 12:05:27 -06:00
vicrodh d99419f22d fix(renderer+ux): frame-liveness watchdog (#558), dup-add button (#655)
- #558: a backend can be alive yet wedged (zero presented frames forever
  on legacy stacks). Vendored femtovg counts presented frames; a watchdog
  8s after window-ready re-arms the renderer sentinel and restarts, so
  the existing ladder degrades wgpu -> alt adapter -> GL -> software on
  its own instead of stranding the user on a blank window.
- #655: 'Add only new tracks' stays enabled with all-dupes (adds zero
  and closes) instead of disappearing as the primary action.

km
2026-07-25 12:05:27 -06:00
vicrodh f1eeaf9126 feat(steam-deck): game mode support (plan PR1-PR5)
- PR1: gamescope detection at boot — X11 backend, scale 1, kiosk
  fullscreen; tray + miniplayer suppressed (click-offset fix).
- PR2: full keyboard navigation for the kiosk (3 zones, focus ring,
  scroll-into-view; typing never navigates).
- PR3: native gamepad via gilrs — D-pad/stick/A/B to synthetic Slint
  keys with hysteresis, repeat and debounce; B suppresses Escape while
  a text field is focused (closes the Steam OSK).
- PR4: Steam OSK via the steam://open/keyboard deeplink on text focus.
- PR5: scripts/qbz-add-to-steam (stdlib-only, 19/19 unit tests) with
  artwork assets + pure-gamepad controller layout.

Spec: qbz-nix-docs/steam-deck/STEAM_DECK_GAME_MODE_PLAN.md

km
2026-07-25 12:04:58 -06:00
vicrodh 55caada4c4 feat(ui): play-indicator toggle, swipe inversion, accent-ring row mark
- Appearance > Play indicator animation (default OFF): static play glyph
  in an accent ring on the playing row, pause on hover, accent edge pill;
  the 8fps eq-bars clock no longer runs while playing with the pref off
  (CPU complaint on some machines).
- Appearance > Invert swipe navigation direction for the two-finger
  touchpad back/forward gesture (#653 follow-up).

km
2026-07-25 12:04:57 -06:00
vicrodh 54c2779eb5 feat(library-all): windowed feed, show-local default + scope, local hearts
- Viewport windowing for the All grid + list (AlbumGrid-class sampler);
  thousands of items no longer mount eagerly (owner's 2200-item library
  was unscrollable, even typing in search lagged).
- Artwork is id-keyed (kind:id) + generation-guarded + band eviction,
  ending wrong-cover races and per-keystroke image churn; covers persist
  on the full model across derives.
- show-local defaults ON (persisted) with the filter toggle kept in the
  view toolbar; Settings gains the scope selector (hearted items vs the
  whole local library, loaded with Local-Library query parity).
- Cards respect favorite/followed state; hearts of local/Plex items route
  to the local-favorites store instead of the Qobuz API.

km
2026-07-25 12:04:22 -06:00
vicrodh 6fff4aae31 docs(readme): note QBZ has no built-in EQ/DSP by design (use EasyEffects/JACK)
cc
2026-07-24 01:14:19 -06:00
vicrodh 70aca326fd docs(readme): add the "what QBZ is and isn't" statement to Why QBZ
Aligns the README with the wiki and website: QBZ exists to give Linux users the
Qobuz client they deserve, and is explicitly not a web wrapper and not a
download tool. Keeps the technical native-pipeline explanation below it.
2026-07-23 23:42:48 -06:00
vicrodh e7aaeb33f9 chore: bump flake to 2.0.2
cc
2026-07-23 16:05:37 -06:00
vicrodh 93ae499f38 ci(release): install jack headers for the build-qbzd jobs
qbzd pulls qbz-audio whose jack-sys build script requires jack.pc; the
desktop build job already installs libjack-jackd2-dev, the new qbzd jobs
did not.

cc
2026-07-23 14:00:23 -06:00
vicrodh 0328a38b2f docs(readme): count Dutch among the bundled languages
cc
2026-07-23 13:09:47 -06:00
vicrodh d8f43cc4a3 docs(readme): qbzd and kiosk sections, drop diagram and star history
Promote the headless daemon to its own section with a screenshot and the
wiki link, add a Kiosk Mode section for Raspberry screens and handheld
consoles, remove the mermaid architecture diagram and the star-history
chart.

cc
2026-07-23 13:07:06 -06:00
vicrodh 116adbdd3e docs: fix TAG_DETAILS formatting for 2.0.2
Split the intro highlights into separate paragraphs, restore the em-dash
bullet pattern in the Casting entry, add the missing section separator
before What Won't Be Developed.

cc
2026-07-23 12:15:33 -06:00
vicrodh f434ed02cf merge: 2.0.2 final round — lyrics translation, Discover reco fixes, gestures/share/sandbox
Three owner-smoked feature/bugfix lines, validated together on
chore/internal/smoke-lyrics-discover (one build):

- feature/internal/lyrics-translation (15): Qobuz v10 lyrics translation
  (language param, translation-only doc merge, per-track toggle,
  bilingual lines, floating ghost-square toggle, language setting,
  account country/language capture, i18n x8, About +TerminalTilt).
- bugfix/internal/discover-reco-follow-state (11): reco follow-state
  seed+warm, followed/blacklisted/dismissed filter + cross-rail dedup +
  overflow backfill, Not interested (reco-scoped store + Blacklist
  settings tab), XDG deep-link navigation (argv + D-Bus OpenUrl).
- bugfix/internal/touch-gestures-share-url-sandbox (5): two-finger
  touchpad back/forward (#653), share links to open.qobuz.com for
  track/album (#514), Sandbox ReserveDevice1 note covers #652
  (device=all already in the published manifest).

km
2026-07-23 11:54:14 -06:00
vicrodh a31d3abf13 Merge branch 'bugfix/internal/touch-gestures-share-url-sandbox' into chore/internal/smoke-lyrics-discover 2026-07-23 09:35:09 -06:00
vicrodh 5b525b9e89 fix(settings): correct the #652 Sandbox row — device access is already granted
The published Flathub manifest has shipped --device=all since 2026-05-30
(flathub/com.blitzfc.qbz@11d38546), so /dev/snd IS exposed in the sandbox;
the device-override row added earlier on this branch was diagnosed against
the stale pre-repack local manifest clone and would have users grant what
they already have. The likely #652 mechanism is the ReserveDevice1 name
(qbz-audio device_reservation): PipeWire holds the DAC and Flatpak can't
own org.freedesktop.ReserveDevice1.* (Flathub linter blocks it), which is
exactly what the pre-existing Sandbox row fixes. Drop the device=all row
and make that row's note name the #652 symptom explicitly.

km
2026-07-23 09:35:09 -06:00
vicrodh 9db8bfd762 Merge branch 'bugfix/internal/touch-gestures-share-url-sandbox' into chore/internal/smoke-lyrics-discover 2026-07-23 09:23:32 -06:00
vicrodh cfa88098ee i18n(settings): translate the Flatpak ALSA device-access row (#652)
Hand-added the new Sandbox settings msgids (title + description) to all
8 locales; en msgstr = msgid. msgfmt --check passes on each.

km
2026-07-23 09:20:49 -06:00
vicrodh 3dca29fc62 feat(settings): document the Flatpak ALSA device-access grant (#652)
The Flathub manifest grants wayland/x11/pulseaudio/dri only, so /dev/snd
is not visible inside the sandbox: opening an ALSA hw:X,0 output fails
and exclusive mode silently falls back to PipeWire (other apps keep
mixing through the DAC). Per owner decision the manifest stays as-is
(Flathub reviewer friction); instead the Sandbox settings page gains a
third Flatpak row with the manual one-time grant
`flatpak override --user --device=all com.blitzfc.qbz`, following the
existing copyable-command-row pattern, and notes the required restart.

km
2026-07-23 09:20:49 -06:00
vicrodh 97adf3d3cf fix(share): use open.qobuz.com for track/album share links (#514)
The copied track and album links now use the canonical open.qobuz.com
form. Artist, label and playlist links are unchanged.

The builders also feed the Odesli (Song.link/Album.link) URL fallback;
that fallback never resolves Qobuz URLs anyway (every qobuz.com form
answers 400 could_not_resolve_entity, verified in-tree 2026-07-16) and
the working path stays ISRC/UPC -> Deezer, so the resolution machinery
is unaffected. open.qobuz.com is already accepted inbound by
qbz-qobuz's link_resolver.

km
2026-07-23 09:13:25 -06:00
vicrodh d6ed93280c feat(nav): two-finger touchpad swipe for back/forward history (#653)
Extend the AppShell bottom-layer TouchArea (the one already catching the
mouse back/forward buttons) with a scroll-event handler that accumulates
horizontal touchpad deltas and fires one NavState.request-back/forward
per gesture — the same callbacks the header nav buttons drive, so entry
swap, scroll restore and nav flags all follow the existing path.

Delivery is front-to-back, so a horizontal carousel Flickable consumes
horizontal deltas before they reach this layer (carousels keep scrolling
and never trigger navigation); a vertical-only page scroller rejects
horizontal-only deltas as an orthogonal direction, letting them fall
through to the gesture handler. The handler always returns reject, so
scroll routing is untouched everywhere. One navigation per gesture via
an idle-timeout/direction-flip reset and a fired latch; 70px threshold;
fingers-right = back / fingers-left = forward under libinput natural
scrolling (commented in code).

km
2026-07-23 09:10:28 -06:00
vicrodh e61318b286 Merge branch 'feature/internal/lyrics-translation' into chore/internal/smoke-lyrics-discover 2026-07-23 08:31:44 -06:00
vicrodh 6a7784366d feat(ui): restyle the lyrics translate toggle as a ghost square
Owner 2026-07-23: drop the mobile-parity circle — square, no border, no
shadow; bg lights up on hover, matching the Discover configurator gear
(GearButton in HomeView.slint). Glyph swapped to the owner-picked
translate-svgrepo icon (translate.svg; the Lucide languages.svg added
earlier on this branch is removed). Accent tint remains the ON state.

km
2026-07-23 08:31:20 -06:00
vicrodh d7a5c83c67 feat(lyrics): make the translation toggle per-track
Owner decision 2026-07-23: enabling translation applies only to the song
it was toggled on, in that playback — not a session/album-wide switch.
Every track change now resets the translation session and fetches
original-only (was: track-change requests rode the resolved target so
the translation followed across tracks). enable_translation/disable and
the fail-soft toast paths are unchanged.

km
2026-07-23 08:31:20 -06:00
vicrodh 7e40ff71db fix(deep-link): import ComponentHandle for Weak::as_weak
Static-review miss (qbz crate can't be check-built without the 30 GB
qbz-ui unit): as_weak() is a ComponentHandle trait method, so the trait
must be in scope. Found by the owner smoke build (E0599, qbz/src/
deep_link.rs:153) — the sole error in the crate.

km
2026-07-23 00:33:38 -06:00
vicrodh 3eb93ec4cb Merge branch 'bugfix/internal/discover-reco-follow-state' into chore/internal/smoke-lyrics-discover
# Conflicts:
#	crates/qbz-ui/translations/de/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/en/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/es/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/fr/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ja/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/nl/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/pt/LC_MESSAGES/qbz-ui.po
#	crates/qbz-ui/translations/ru/LC_MESSAGES/qbz-ui.po
2026-07-22 23:05:16 -06:00
vicrodh 3a6e5960ac Merge branch 'feature/internal/lyrics-translation' into chore/internal/smoke-lyrics-discover 2026-07-22 23:03:15 -06:00
vicrodh 299a7c3237 chore(packaging): drop dead x-scheme-handler/qbz registration
qbz:// resolves nowhere in the link resolver, so the registration
advertised a scheme the app never handles. Keep
x-scheme-handler/qobuzapp (handled by the resolver) in all 7 desktop
files.

km
2026-07-22 22:56:14 -06:00
vicrodh 537cabbb99 fix(deep-link): forward Qobuz links to the running instance over D-Bus
A second launch carrying a deep link exited after a bare Present(),
taking the URL with it — focus worked, navigation didn't. Add
OpenUrl(url) to the SingleInstance D-Bus interface: the primary stashes
the URL, presents itself, and dispatches through the same pending/drain
path as a cold-start link (stays pending while no shell is up).
acquire_or_raise calls OpenUrl when argv carried a link and falls back
to bare Present() when the method is missing on an older primary
(version skew), then to MPRIS Raise as before; no-link launches keep
plain Present().

km
2026-07-22 22:56:00 -06:00
vicrodh fd8f8f4064 fix(deep-link): navigate Qobuz links passed via argv on cold start
xdg-open https://open.qobuz.com/album/<id> raised/focused QBZ but never
navigated: main() never read argv, so the desktop files' Exec=qbz %u URL
was silently dropped (Tauri parity gap — the old app scanned argv on
cold start and in its single-instance callback).

New deep_link module: capture_argv() at the top of main() stashes the
first Qobuz-link argv entry (legacy is_qobuz_link prefixes) BEFORE the
single-instance guard runs — a second launch must read its own argv
before deciding to raise+exit. enter_shell binds a shell context
(runtime, window weak, tokio handle, image cache; cleared on logout) and
drains the pending URL at its very end, after the startup-page/view
restore, so the restore can't re-root over the deep link. Session is
active and the AppWindow exists there, so no readiness sleep hack.
Dispatch is the existing Ctrl+L machinery (link_resolver::resolve ->
apply_resolved_link); failures surface as toasts. At the login screen or
in offline mode the URL stays pending for the next successful online
enter_shell.

km
2026-07-22 22:55:47 -06:00
vicrodh c8e37f937d refactor(discover): borrow-free upsert in the reco-dismiss store
position() + index instead of an iter_mut().find() borrow held across a
match arm — no NLL subtlety, and the lifecycle test passes against the
verbatim file (scratch-crate run, since the qbz crate itself is not
built here).

km
2026-07-22 22:31:00 -06:00
vicrodh 2b0cf33d2e i18n(discover): Not interested + Recommendations-tab strings (8 locales)
New msgids hand-added to all 8 qbz-ui catalogs: the tab description, the
empty-state heading + hint, and the dismiss/undo toasts. "Not
interested" and "Recommendations" reuse the pre-existing msgids
(PlaylistView / Discover). en msgstr = msgid; es/de/fr/pt/ru/ja/nl
translated. msgfmt --check clean on all 8.

km
2026-07-22 22:25:01 -06:00
vicrodh 6adb33fcef feat(settings): Recommendations tab in the Blacklist Manager
Manage the reco-scoped Not-interested dismissals: a third tab in
Settings > Blacklist lists dismissed artists (search-filtered like the
other axes, count badge, empty + no-results states) with a per-row undo
that removes the entry from the reco_dismiss store — the artist becomes
eligible for the Recommendations rails again on their next paint. Row
click reuses the existing artist-select navigation. No bulk Clear-All on
this tab (undo is per-row); the modal stays scoped to the blacklist axes.

km
2026-07-22 22:18:59 -06:00
vicrodh a181fb7aa9 feat(discover): Not interested menu action with live removal + backfill
ArtistGridCard's context menu gains a 4th item (Not interested,
thumbs-down icon; popup height bumped for the extra row) that emits
("artist", id, "not-interested") through the existing media-action
chain. The new central arm persists the dismissal to the reco_dismiss
store and, when the artist is in a reco rail, removes the card live and
backfills the freed slot from the rail's retained overflow (already
Qobuz-validated — no network; the pop re-checks live exclusions and
cross-rail visibility). Dismissed from a non-reco surface, the display
name is resolved via get_artist before persisting, so the management
list and toast still read well.

km
2026-07-22 22:11:58 -06:00
vicrodh 465f8627eb feat(discover): filter, cross-rail dedup, and overflow backfill for reco rails
The two Recommended-Artist rails showed artists the user already follows
or blacklisted, and the same artist could appear in both rails at once.

Builders now retain the full validated pool (rotated, not truncated at
DISPLAY_CAP); the pools ride the results blob as before, so old blobs
simply have no overflow until they expire. A new pure compose step in
qbz-external-reco (unit-tested: exclusion, within/cross-rail dedup with
common-wins ordering, visible/overflow split order) runs at the single
paint choke point in external_reco.rs — covering BOTH the fresh build
and the cached-blob paint. Exclusions fold followed (fav_cache) +
blacklisted + reco-dismissed ids, each source independently fail-open.
The past-cap survivors are retained as overflow for the Not-interested
live backfill (no extra network).

km
2026-07-22 22:08:38 -06:00
vicrodh 2c162b6925 feat(discover): per-user reco-dismissal store for Not interested
Reco-scoped dismissal (NOT the app-wide blacklist): a dismissed artist
only leaves the Recommendations rails and stays visible everywhere else.
One small JSON store per user (the light discovery_dismiss precedent)
bound at session activation next to fav_cache (login / restore /
offline) and dropped on logout. Fail-open: unbound or corrupt file reads
empty and mutations no-op. Unit-tested lifecycle (roundtrip, idempotent
dismiss with snapshot backfill, re-bind persistence, undo, junk/unknown
format tolerance).

km
2026-07-22 22:02:25 -06:00
vicrodh da3a5553e0 fix(discover): seed artist follow state in the Recommendations rails
slim_from_artist hardcoded following: false, so reco artist cards never
reflected the current follow state at build time (the pinned carousel
already seeded from fav_cache). Seed from fav_cache::is_artist_favorite
and extend the shell-entry favorite-artists warm walk to the three
ExternalRecoState artist models, in place (no model swap, no flicker).
Live toggles were already propagated by search::mark_artist_followed.

km
2026-07-22 21:59:26 -06:00
vicrodh accdef5a02 fix(qobuz): merge the translation-only v10 lyrics document with the original
Live wire (verified 2026-07-22, track 266725027 with language=es): when a
translation is requested and available, lyrics_url points at the TRANSLATED
document (<id>-<lang>.json) carrying ONLY 'translation' — no 'original' —
and 'translation_requested' is absent. get_lyrics rejected such documents
('without original content -> no lyrics'), so every translation toggle
ended in the unavailable toast. Now get_lyrics fetches the original-only
document in a second round trip and merges both into one bilingual doc
(merge_translation_into); requested-but-unavailable still 403s the CDN
URL -> typed miss (fail soft).

km
2026-07-22 21:34:39 -06:00
vicrodh 9cc7307616 feat(about): add TerminalTilt to the contributors list
Owner request; rides the lyrics-translation branch. Appended after the
existing Slint-era external-PR contributors.

km
2026-07-22 20:00:03 -06:00
vicrodh f86b0f7386 i18n: lyrics translation strings in all 8 locales
New msgids: Show/Hide lyrics translation (toggle tooltips), Translation
language + Auto (account language) (flyout row), and the fail-soft toast
"Translation is not available right now. Please try again later.".
Register-matched per catalog (de/nl informal, fr/pt formal); en follows
its msgstr = msgid convention. msgfmt --check clean on all eight.

km
2026-07-22 20:00:03 -06:00
vicrodh b4ead674d8 feat(qbz): wire the lyrics translation toggle end to end
The commit push now carries translation-available (doc translation_langs
non-empty) and per-line translation-text, aligned 1:1 with the rendered
lines (the core filters translation lines like the originals; a missing
entry degrades to empty). Toggle ON resolves the target (explicit setting,
else Auto: account language_code -> UI locale) and refetches the current
track with LyricsRequest.language set — show-translation flips only when
the response actually embeds a translation; any gap toasts the Android-
parity error string and reverts, never putting the lyrics view in an
error state. Toggle OFF is pure UI (no network). While ON, track-change
requests ride the resolved target so the translation follows across
tracks; prefetch stays original-only. A doc that already carries the
target's translation flips locally without a refetch. The miniplayer
mirrors show-translation so the mini lyrics surface follows the sidebar.

km
2026-07-22 19:59:47 -06:00
vicrodh 7b447fb7e4 feat(qobuz): expose the account language code on the client
Read-only accessor over the session lock — the lyrics translation "Auto"
resolution reads it through the shared client handle. None pre-login and
for pre-v10 persisted sessions.

km
2026-07-22 19:59:47 -06:00
vicrodh b1b64f2dfb feat(ui): translation language setting in the lyrics flyout
New QbzSelect row (Auto (account language) + en es fr de it pt nl ja ru,
the owner-approved list) after the Size row; language names are native
literals, the same convention as the Appearance dropdown. The options
array lives on LyricsState (reseeded from Rust on language change) and
the choice persists per-user in lyrics_prefs.json as translation_language
("auto" or an ISO 639-1 code), sanitized on load (unknown -> auto) and
round-tripped through apply_to_ui/persist_from_ui like the other prefs.
Flyout popup height grows by the new row (and the comment now counts the
lite-fill row it had missed).

km
2026-07-22 19:59:27 -06:00
vicrodh 2b4c8c5f57 feat(ui): floating translate toggle in the lyrics sidebar
Circular button anchored bottom-right of the lyrics body, overlaying it —
mobile parity (owner decision 2026-07-22: floating, NOT in the header,
NOT in the flyout). Mounted only while LyricsState.translation-available,
so a doc without translations renders the sidebar exactly as before.
Off = dim, on = accent; tooltips via the shared TooltipState channel.
The glyph is Lucide's languages icon, matching the bundled icon set.

km
2026-07-22 19:59:07 -06:00
vicrodh 499f652b71 feat(ui): carry lyrics translation state and render bilingual lines
LyricsLineItem gains translation-text (empty when the doc has none) and
LyricsState the translation props (translation-available, show-translation,
toggle-translation callback) per spec section C. LyricsLinesView renders
the translation under its original line while show-translation is on —
smaller (~72%) and dimmer, static text that follows the line; the karaoke
fill stays keyed on the original timings/text. Secondary surfaces share
the model, so the field flows through untouched.

km
2026-07-22 19:58:35 -06:00
vicrodh 619f6d5abc feat(qbz): thread the translation language through the lyrics glue
SharedClientProviders implements the new language-aware LyricsProviders
boundary; both track-change and prefetch requests pass None (the default
original-only flow per spec section B.1) — the refetch-with-language is
triggered by the UI layer in a follow-up task. The committed LyricsDoc
now carries the translation into CURRENT_DOC automatically, so the sync
engine and the future UI toggle read the same document. qbz-core's
headless get_lyrics wrapper keeps its signature and fetches
original-only.

km
2026-07-22 19:12:22 -06:00
vicrodh 378afb9c9a feat(lyrics): map and cache embedded translations
LyricsRequest carries an optional translation target that threads
through the LyricsProviders::qobuz boundary to the client; the external
fallback chain is untouched. LyricsDoc gains translation
(Option<Box<TranslatedLyrics>> with a LyricsKind dispatch mirroring the
Android v10 mapper: wsync->WordSynced, lsync->LineSynced, plain->Plain)
plus a client-derived has_translation (requested lang in
translation_langs, false when none requested). QobuzWsync stores the
translation verbatim in the existing cache JSON (serde default, no DB
migration) with its lang recorded: a synced cache hit only serves a
language request when the stored translation matches it, otherwise the
chain refetches; original-only requests keep today's behavior.

km
2026-07-22 19:12:07 -06:00
vicrodh 8c2dfd5727 feat(models): capture account country and language on login
parse_login_response now reads user.country_code and user.language_code
(snake_case wire names, verified against Qobuz's own embedded login
fixture) into two new serde-defaulted Option fields on UserSession, so
pre-v10 persisted sessions still load with both as None (translation
stays Auto-off). qbzd's test construction updated for the new fields.

km
2026-07-22 19:11:52 -06:00
vicrodh 072223078e feat(qobuz): send the language param on track/lyricsUrl
get_lyrics_url_raw/get_lyrics_url/get_lyrics take an optional ISO 639-1
language, appended to the signed param list only when Some (the existing
sign_request sort covers it). None keeps the original-only default flow.
When a translation was requested but the document does not embed one,
the envelope's translation_requested.lyrics_url is fetched as a fallback
translation source (spec section B.3); every gap degrades to
original-only. lyrics_probe gains --lang=<iso639-1> for parity.

km
2026-07-22 19:11:41 -06:00
vicrodh 9d7f9c6c17 feat(qobuz): parse the v10 embedded lyrics translation block
QobuzLyricsDocument gains the optional translation member (same content
union as original, new in API 10.0), the content DTOs become
Serialize+PartialEq so the lyrics cache can persist a translation
verbatim, and a lang() accessor exposes the content language. New
fixtures cover embedded translations of each type (wsync/lsync/plain),
the lyricsUrl envelope with translation_requested, empty
translation_langs (feature off), and 9.x documents (no translation).

km
2026-07-22 19:11:28 -06:00
vicrodh 26baeb0ffc docs(release): cover the post-bump commits in the 2.0.2 changelog
Integrates the 30 commits that landed after the version bump: Most Played
Albums, album-identity mode, multi-artist header, cross-tag scan (#447,
#507), local album play-next/queue (#636), Plex rating-key resolution,
ALSA unstick, gapless-late resume, DSD/hi-res DLNA (#646), media-server
token redaction, label web parity, macOS chrome/NPB work, qbzd/Connect
hardening, apt trigger (#648) and the flatpak twin talk-name drop. Also
includes the owner's latest TAG_DETAILS touch-ups.

km
2026-07-22 18:23:10 -06:00
vicrodh 6c6eaf2b36 merge: Most Played Albums (feature/most-played-albums)
Play-count album history store (album_play_history, WAL per ADR-002) hooked
into record_recent, Most Played Albums carousel on Home + For You (default
off), View-all page with search + per-card play count, i18n in all 8
locales.

Conflicts resolved against pre-release: kept the Slint-era Pinned section
alongside the new MostPlayedAlbums everywhere (DiscoverySectionId, prefs,
render kinds, nav, ContentView, HomeActions); AlbumCardItem carries both
is_pinned and plays (populated via card_to_item); .po files union-merged,
msgfmt clean; discover_prefs tests updated (home 15/8 ON, forYou 14/13 ON)
— 10/10 green. Release build green.

km
2026-07-22 17:46:09 -06:00
vicrodh cb10d42b53 chore(flatpak): drop the org.freedesktop.secrets talk-name from the twin
Parity with the published Flathub manifest (repack flow, PR #49), which
already ships without it. The Flathub repo manifest is untouched.

km
2026-07-22 16:53:54 -06:00
vicrodh 0d58e1d0c3 docs(release): revise 2.0.2 tag details intro and highlights
km
2026-07-22 16:49:24 -06:00
vicrodh 03ac0a7164 fix(cast): redact media path tokens in media server request logs
Session tokens in /audio/<token>/<id> must not appear in log bundles. Port
of the remaining delta of 9aab6233 (pr/605) onto current pre-release: add
redact_media_uri() to media_server.rs and redact the URL in the three
handle_request logs (request, 404 parse, 403 token mismatch). The device.rs
side and the CSPRNG tokens already landed via PR #586.

km
2026-07-22 16:49:23 -06:00
vicrodh c49b9b52a0 fix(macos): reach NSWindow through the content NSView for traffic lights
raw-window-handle 0.6 exposes only the content view; dereference the NSView
and get the NSWindow via -[NSView window] instead of reading a non-existent
ns_window handle.

km
2026-07-22 16:49:23 -06:00
vicrodh f9a8edcbeb fix(shell): macOS NPB-Small bottom clearance via Rust-set npb-small-extra
The 2px .slint literal proved invisible (owner ask 2026-07-21, iteration
2026-07-22). The extra clearance is now set from Rust at startup
(ShellState.npb-small-extra, default 6px, macOS-only, QBZ_NPB_SMALL_EXTRA
env override) so tuning costs a ~2-minute qbz-bin rebuild instead of a
~2 h qbz-ui one on the 8 GB Mac. Linux stays pixel-identical (extra = 0).

km
2026-07-22 16:49:23 -06:00
vicrodh 6c5996cc50 fix(library): single-album play-next/queue on the local albums surface (#636)
The AlbumCard context menu on local albums (LocalLibrary grids, folder or
Plex source) routed play-next/queue to LocalLibraryActions.album-action,
whose handler only implemented play / toggle-select / favorite — the two
queue actions fell into a catch-all that logged "queue slice pending" and
did nothing.

Resolve the album's tracks with the same source-aware resolver play uses
(local_library::fetch_album_tracks_blocking — local folders AND Plex) and
enqueue them via playback::enqueue_local_tracks, mirroring the already-
working albums bulk-action arm. Track-level play-next/queue was never
affected. Port of d0f67149.

km
2026-07-22 16:49:10 -06:00
vicrodh ee37d65f40 fix(library): read album/album-artist/date across all tags at scan (#447, #507)
Port of 0c3b38f8 (bugfix/internal/local-library-tag-fallback) onto current
pre-release. The scanner read metadata from a SINGLE lofty tag
(primary_tag().or(first_tag())) with no per-key fallback to the file's other
tags. Old, repeatedly-retagged collections routinely carry the album /
Album Artist / date only in a secondary tag (ID3v1/APE/Vorbis) while the
primary tag lacks them:

- #447: the missed album tag let the folder-derived backfill land in the
  album column and masquerade as metadata (folder-name albums, folder-level
  grouping, MIN(year) across mixed folders).
- #507: the missed Album Artist left album_artist NULL, degenerating every
  COALESCE(album_artist, artist) to the per-track artist (Various Artists).

extract() now falls back per key across tagged_file.tags() (primary first,
deterministic conflict policy) for title/artist/album/album_artist/genre/
catalog/track/disc/date. Folder backfills stay as-is for genuinely untagged
files. Tests adapted to the AlbumGroupMode signature; 75/75 pass. A full
library rescan picks the fix up (no migration).

km
2026-07-22 16:49:10 -06:00
vicrodh b5c1a76e81 fix(playback): resolve Plex rating key from queue id when the hint is the album key
The MyQBZ collections path stamps the per-item ALBUM boundary key
(plex:<hash>, for album-shuffle boundary detection) in source_item_id_hint,
but the Plex play path used that hint verbatim as the track rating key:
/library/metadata/plex:<hash> -> 404, the Plex track never started, and the
previous track kept playing while the UI showed the new one (continuous
playback Qobuz -> Plex broken, audio/UI desync).

Ignore any plex:-prefixed hint and fall back to the numeric queue id (=
rating key for the common numeric-key case). The LocalLibrary path (hint =
raw rating key, no prefix) is unchanged.

km
2026-07-22 16:48:56 -06:00
vicrodh e822c21c8b chore(scripts): add aarch64 qbzd cross-build script
Docker/cross build of the slint-free daemon for the Pi streamer
(~90 s incremental, dist/qbzd-aarch64-linux); native path included for
aarch64 hosts.

km
2026-07-22 09:04:23 -06:00