Commit Graph
100 Commits
Author SHA1 Message Date
vicrodh e9b02cdedb fix(player): never panic the audio thread when the loudness cache cannot open
LoudnessCache::new failing (full disk, read-only profile, corrupt
loudness_cache.db) used to panic inside the audio thread, which is created
once in Player:🆕 playback was dead until restart while the log promised
the opposite. The cache now degrades to an in-memory session cache, then to
a disabled cache, and normalization keeps running.

cc
2026-09-13 03:36:50 -06:00
vicrodh c25ae457c1 fix(artwork): restore the shared image cache eviction lost with the Slint frontend
ImageCacheService::evict had no caller since 69ed61207 retired the Slint
app's spawn_evict, so ~/.cache/qbz/images grew without bound in 2.1.0 and
2.1.1. housekeeping() now trims it to the same 200 MB budget once per run,
in 64-row batches so the GUI thread is never parked behind the cache
mutex. evict is batched and covered by unit tests.

cc
2026-09-13 03:27:56 -06:00
vicrodh 5e739f658e merge: integrate 2.1.2 playback and library stabilization
cx
2026-09-13 02:57:34 -06:00
vicrodh e291eb3af4 fix: stabilize playback memory, local library sync and Qt settings
Add shared playback memory profiles, bounded disk-backed playback and storage
selection for desktop and daemon. Preserve gapless during streaming-only playback
and provide explicit cache eviction with playback confirmation.

Stop watcher read events and unchanged scans from rebuilding the local catalog.
Restore playlist/sidebar updates, folder availability feedback and player layout.
Add appearance presets and a radio-based cache settings panel with translated
trade-offs, plus consistent clipboard menus across visible Qt text editors.

Validate with Rust regressions, QML interaction tests, isolated release startup
and idle-library checks; extend the shared local and CI gates.

cx
2026-09-13 02:57:21 -06:00
vicrodh 3cf1f32177 merge: integrate updater recovery and installer into pre-release
cx
2026-09-12 16:25:14 -06:00
vicrodh 7a22fd6867 feat(updater): add native update channels and one-line installation
Support Flatpak portal updates and signed Windows MSI upgrades. Add the Linux/macOS installer for 2.1.2, release signing, translations and regression coverage.

cx
2026-09-12 16:25:08 -06:00
vicrodh 64ec3e2bc8 fix(updater): restore Qt release checks and signed installation
cx
2026-09-12 15:20:23 -06:00
vicrodh 6a02dcdd67 merge: integrate Orbit foundations into pre-release
cx
2026-09-12 13:02:38 -06:00
vicrodh 74e1610433 feat(orbit): add shared library foundations and gated host tools
Extract the HTTP host and catalog layer into qbz-control, preserve the
existing daemon API, and keep private session credentials out of SSE.

Scope library stores and scan workers to their host. Share bounded source
search and show Local results alongside Qobuz in the Qt search page.

Add the launch-gated Qt Orbit laboratory and persistent remote-context
banner. Let qbzd run --orbit own and administer its file library through
instance-scoped API and CLI operations. Full remote UI control remains
pending.

Validate library/control/daemon suites, Qt search and QML regressions,
two isolated daemon processes, Qt release build and final cargo check.

cx
2026-09-12 13:02:29 -06:00
vicrodh 6394ef176c chore: bump flake to 2.1.1
qbzVersion 2.1.1 and both tarball hashes from the published v2.1.1 assets
(nix-prefetch-url / sha256 at their final URLs).

cc
2026-09-11 17:19:05 -06:00
vicrodh 3a8c31339e Merge pre-release into main for 2.1.1
flake.nix: take pre-release's prebuilt-tarball flake (#744); main's
source-build variant with the 2.1.0 srcHash is superseded.

cc
2026-09-11 15:34:47 -06:00
vicrodh 456e12e114 docs(release-2.1.1): add the 2026-09-11 batch to the notes and metainfo
Resizable panels (#771), collapsible My QBZ sidebar, artist identity
matching (#768), album-card editions, queue ordering (#442), custom artwork
on cards, GNOME clipboard (#684) and dock icon, MPRIS after restore (#683),
and the prebuilt-tarball flake (#744). Release date moved to 2026-09-11.

cc
2026-09-11 15:34:08 -06:00
vicrodh f334fbcf45 Merge bugfix/internal/wayland-app-id-desktop-file into pre-release
cc
2026-09-11 15:30:07 -06:00
vicrodh 0a0322e18d fix(linux): publish the desktop-file name so Wayland windows match their launcher
GNOME Shell pairs a live window with its launcher through the Wayland app_id,
and Qt sends the executable's name (`qbz`) unless the application sets its
desktop-file name. Every Linux package installs the entry as
`com.blitzfc.qbz.desktop`, so the dock painted the generic icon while the app
grid, which reads the .desktop directly, showed the right one; the D-Bus
notification `desktop-entry` hint had the same gap. StartupWMClass covers
X11 only.

Set `QGuiApplication::desktopFileName` to `com.blitzfc.qbz` right after the
application exists (Linux only). Verified the mechanism on an Ubuntu
GNOME/Wayland VM: a `qbz.desktop` symlink made the dock icon appear.

cc
2026-09-11 15:30:07 -06:00
vicrodh 1032974358 Merge bugfix/internal/windows-gate-temp-dir-fixture into pre-release
cc
2026-09-11 14:49:31 -06:00
vicrodh e99a5e0fa1 fix(ci): compare editor directories against a fixture the Windows gate can match
Two tag_editor_qt tests compared editor_directory() — the parent() of a
file inside std::env::temp_dir() — against temp_dir() itself. On Windows
temp_dir() ends in a separator ("C:\...\Temp\") and Path::parent never
yields one, so the strings differ and the Windows Qt gate failed on every
run since 2026-08-28, the v2.1.0 tag push included. Linux never saw it.

Both tests now use a created subdirectory of temp, which normalises the
two sides on every platform.

cc
2026-09-11 14:49:31 -06:00
vicrodh 743d68fda4 fix(ci): give the early-exit smoke fixtures a window a slow runner cannot miss
test_core_then_signal_crash_is_rejected_without_core_dump flaked on the
2026-09-11 pre-flight: its fake child (a fresh Python interpreter that
prints the startup marker and SIGSEGVs itself) has to reach the crash
inside a 250 ms observation window. On a loaded runner it did not; the
deadline expired with the child alive, the shutdown SIGTERM raced the
self-inflicted SIGSEGV, and the smoke rejected the run as "unexpected
process exit status -11" instead of the "exited before" the test asserts.

The early-exit fakes now get a 5 s window — free, since observe() returns
the moment the child exits — while the four live-process fakes keep their
explicit 250 ms so the suite still runs in ~2 s.

cc
2026-09-11 14:43:01 -06:00
vicrodh 6e25a4ea45 Merge bugfix/internal/684-683-clipboard-mpris into pre-release
cc
2026-09-11 13:53:27 -06:00
vicrodh 8cad9c7ca7 fix(mpris): publish Paused after a session restore (#683)
A restored session runs `refresh_now_playing`, whose MPRIS push is the
optimistic `Playing` at position 0 — correct for the real play path, where
a stream is about to open and the poll loop's play/pause edge corrects it.
Nothing opens on restore, so `playerctl status` answered "Playing" from
boot until the first real play, and the sleep inhibitor was armed for a
paused app. Publish Paused at the resume position once the restore is done.

Verified: playerctl reports Paused on a restored, unplayed session.

cc
2026-09-11 13:53:27 -06:00
vicrodh 6b81cfa84d fix(share): copy links through QClipboard, not arboard (#684)
Copy link failed on GNOME + Flatpak: arboard's Wayland path needs the
wlr/ext-data-control protocol, which Mutter does not implement, so it fell
back to X11 — reachable natively through XWayland, unreachable inside the
sandbox ("clipboard unavailable: X11 server connection timed out"). KWin
has the protocol, which is why the failure never reproduced on Plasma.

Replace it with a C++ shim (cxx/clipboard.cpp) that queues the write onto
the GUI thread and hands it to QClipboard: the base wl_data_device protocol
every compositor implements, no extra sandbox permission, and the offer
lives as long as the application (the #514 lifetime rule, without the
long-lived arboard instance). arboard is dropped from the crate.

Verified on Plasma/Wayland and on an Ubuntu GNOME/Wayland VM with DISPLAY
unset (the sandbox condition), where the copy now lands.

cc
2026-09-11 13:53:27 -06:00
vicrodh 8b1941d9ad Merge feature/internal/771-resizable-panels into pre-release
cc
2026-09-11 13:09:47 -06:00
vicrodh dc158bd9b6 feat(shell): resizable playlist sidebar and queue/lyrics column (#771)
Both panels were fixed-width (240 / 300); playlist names elided on wide
monitors. Each panel's visible edge (the content pane's edge, 8px past the
panel — the bezel between them shares the panel colour) now carries a 5px
grab strip with a 2x28 notch, split cursor, and a one-shot accent pulse when
a drag lands on a snap point.

Rules live in `panel_resize.rs` (pure, unit-tested), the bridge applies them
live and persists on release (`ui_prefs` `sidebar_width` / `queue_panel_width`,
clamped on read):
- sidebar: 240..480 while open; below 200 it snaps to the mini rail, below 24
  it closes through the same state the header button uses (with the Large-NPB
  fallback); the mini rail reopens at 220 — a 20px hysteresis band so no
  pointer x can flip open<->mini on every move (the flapping seen in the
  2026-09-11 screencast). The stored width never drops below 240, so
  reopening restores the last open width. A state snap during a drag still
  animates: the bridge raises `sidebarSnapping` before the state change and
  Sidebar's width Behavior honours it while the drag otherwise disables it.
- column: 300..600; below 260 it closes queue and lyrics as their X would.
  With the queue in the column and the width parked at 600, the notch shows a
  steady larger glow toward the content; pushing 60px further opens the
  Listen List (queue-view) and the queue leaves the column, lyrics staying
  if open. A lyrics-only column never pushes through.

The Large-NPB dock is untouched: it is mounted with absolute x/width in
AppShell and reads nothing from the sidebar width.

Verified: panel_resize 9/9, qmllint + the six qt-run.sh audits, release
builds v1-v4, four owner smoke rounds.

cc
2026-09-11 13:09:47 -06:00
vicrodh f9795e3c18 Merge bugfix/internal/2.1.1-card-version-and-768-identity into pre-release
cc
2026-09-11 09:46:02 -06:00
vicrodh 7e30ad769d fix(musicbrainz): identity-based artist matching, never an unrelated first result (#768)
Swim Deep's Artist page showed Deep Purple's founding year and members. Two
defects combined: `search_artist` emitted `artist:Swim Deep` unquoted (the
second word leaks into the global query; Deep Purple ranks first at 100,
Swim Deep second at 88), and `resolve_artist` fell back unconditionally to
the first result when no exact name scored >= 90. Name-keyed caches then
made such guesses durable and shareable across homonyms.

New `qbz-integrations::musicbrainz::identity` — pure decisions plus
orchestration over injected fetcher/store/catalog boundaries:
- query `artist:"<lucene-escaped name>"`, URL-encoded once;
- containment rule: exactly one exact-name candidate with score >= 90, else
  None — the first-result fallback no longer exists;
- source direction (Qobuz artist id -> MBID): the page's own Popular-Track
  ISRCs (max 3, guests excluded) vote among the exact-name candidates using
  every credit of every recording that carries the ISRC; ties, conflicts and
  failures fail closed;
- scene direction (MBID + catalog scope -> Qobuz id): a unique exact-name
  Qobuz artist is provisional; several homonyms are split by their own
  top-track ISRCs (max 2 each); no evidence omits the row instead of taking
  max(albums_count);
- four `*_v2` cache tables keyed by the stable identifier + matcher version,
  carrying evidence kind, expiring at read time (verified 30 d, provisional
  7 d, search windows 7 d, ISRC credits 30 d); legacy name-keyed rows are
  neither read nor written; scene cache key bumped to v2 with the real
  account territory (no scope -> no cache), rows re-derived on replay.

Core: `catalog_scope()`, `musicbrainz_resolve_source_artist`, store/catalog
adapters; `musicbrainz_resolve_artist` and the musician resolver go through
the containment rule. Artist page passes {qobuz id, own ISRCs} to core and
keeps its generation guard; QML, playback, auth and reco crates untouched.

Tests: qbz-integrations 59 (query encoding, #768 ordering fixture, ISRC
voting incl. collaborations/dupes/conflicts, cache keys/TTL/version/legacy,
bounded fan-out with request counting), qbz-core 40, qbz-qt 782.

cc
2026-09-11 09:46:02 -06:00
vicrodh baed7bb785 fix(cards): carry the Qobuz album version in card titles + tooltip when elided
Every album card producer copied `title` raw although the model carries
`version` ("50th Anniversary", "Deluxe"); the album page, info modal and quick
view already used `format_album_title`. The Artist page listed two identical
"All Things Must Pass" cards for two different editions.

- Route the 8 card producers through `album_qt::format_album_title`
  (artist page buckets, album carousels, label grid, Home rails, Library ALL
  feed, Artist Scene); search_qt drops its private duplicate of the helper.
- AlbumCard.qml: the title is always the full string, elided at the card
  width; when Qt reports `truncated`, hovering shows the whole title in a
  Controls ToolTip (QualityBadge pattern, no character counting, no ticker).

Verified: qmllint clean, qbz-qt suite 782/782, release build.

cc
2026-09-11 09:45:47 -06:00
vicrodh cab68c9209 fix(queue): play-next/play-later ordering + Q-mix multiselect/queue/create-playlist (#442)
Split the manual insertion block into two tiers so "Play later" no longer
behaves identically to "Play next":
- add a `manual_later_count` to InternalState tracking the trailing
  "Play later" sub-block: [now][play-next block][play-later block][source].
- play-next inserts at the FRONT of the manual block; play-later appends at
  its END (after any earlier play-later tracks), in order.
- fix the idle + shuffle case: with no current track, both actions anchor at
  base 0 instead of the end of shuffle_order, so an idle-built queue plays
  play-next first and play-later right after, and does not collapse the rest
  of the queue on the first transport play.
- next()/remove_at()/jump decrement drain the play-next tier before the
  play-later tier; set_queue/clear/shuffle-enable dissolve both.
- the public QueueState.manual_next_count exposes mnc+mlc so both tiers render
  inside the single "Next in queue" visual container.

NorinB's shuffle-randomization report keeps a debug log in next() to capture
a repro if it recurs; the temporary INFO instrumentation is lowered to debug.

MixView Q-mixes reach transport parity with playlists: functional multiselect
(SelectionModel + multi-select bar), header queue menu (play next / later /
add to queue via bulkTracksAction), and create-playlist-from-selection.

Queue engine covered by 86 passing tests (play-later ordering non-shuffle,
under shuffle, and idle-shuffle cases added).

cc
2026-09-11 08:51:38 -06:00
vicrodh ef74cde3d8 test(myqbz): align tracks_text test with the #766 empty-column contract
The stale test still asserted the pre-#766 behavior (em-dash for an unknown
album count, "1" for a track). #766 made the tracks column empty for a track
and empty for an unknown album count (filled only once resolve_items backfills)
— the shipped, smoke-validated behavior. Update the test to match; the CI qt
gate was red on this alone.

cc
2026-09-10 23:20:38 -06:00
vicrodh 6047a9ee0d fix(artwork): show custom album cover / artist portrait on cards app-wide
A user-set custom cover (add_custom_cover) or artist portrait (add_custom_image)
showed only on the album/artist page, never on the grid cards. The card
builders emitted the source artwork URL and skipped the override map.

Library album and artist cards now prefer the override; Home (all tabs) and
recommendations pick it up at the shared attach_cached choke point — one store
read per pass, skipped entirely when a profile has no overrides — keying albums
by id and artist rails by display name; and Search artist rows prefer the
portrait too. Albums already routed through prefer_album_cover on the album,
artist, playlist and search-album surfaces.

cc
2026-09-10 20:55:08 -06:00
vicrodh c82e624188 release: bump workspace version to 2.1.1
The About modal reads CARGO_PKG_VERSION, so it showed 2.1.0. Bump the workspace
version (and the Cargo.lock members) to 2.1.1 and add the 2.1.1 metainfo release
entry.

cc
2026-09-10 20:33:15 -06:00
vicrodh dbb4d849d3 fix(flake): install the prebuilt release tarball instead of building from source (#744)
The source build failed under Nix at cxx-qt / qt-build-utils with "Could not
find qmltyperegistrar": the Qt host tools live across several outputs and the
sandbox PATH did not carry qtdeclarative's. Rather than chase that, the flake
now repacks the published bare-binary tarball (the same asset AUR-bin / Gentoo
/ Flathub consume) — matching the project's binary-repack packaging doctrine and
dropping the whole Rust/Qt toolchain from the closure.

autoPatchelfHook rewrites the binary against nixpkgs and wrapQtAppsHook wires
the Qt plugin/QML paths; qt6-base/declarative/svg/wayland mirror the qbz-bin
depends. Per-arch tarball hashes for x86_64 and aarch64, computed from the
published v2.1.0 assets. Verified: nix build succeeds and the wrapped binary
launches against nixpkgs Qt 6.10.2. Bump version + hashes post-tag.

cc
2026-09-10 20:33:15 -06:00
vicrodh 9e6992c066 merge: collapsible My QBZ sidebar (tree, per-element hide, header toolbar)
cc
2026-09-10 20:22:38 -06:00
vicrodh ae9f1c2679 docs(release-2.1.1): tag details and changelog
First 2.1 maintenance release: scroll response/perf in heavily populated
sections plus a broad round of bug fixes (#764, #766, #748, #745, #660).

cc
2026-09-10 20:20:26 -06:00
vicrodh d025e92160 chore(about): add Ko-fi supporter Olivier
Cumulative thank-you list update from the maintainer's Ko-fi records; no
GitHub changes this round.

cc
2026-09-10 20:20:26 -06:00
vicrodh e402fa250a feat(myqbz): collapsible My QBZ sidebar with per-element hide and toolbar
Opt-in collapsible My QBZ tree in the sidebar (Settings > Navigation): a
branding-titled parent over Mixtapes and Collections sections, each header a
link to its grid with a chevron that only collapses. Header text and icon on
the left, a Playlists-parity toolbar floating right (search, new, sort A-Z/Z-A,
refresh, collapse) with the chevron aligned to the playlist carets. The tree
feed rides QbzShell (QbzMyQbz is not reliably registered in the shell context)
and is published through publish_sidebar, so it lands reliably at startup.

Per-element visibility reuses the mixtape hidden flag (repo::set_hidden): a
row's right-click hides it from the sidebar, and the detail hero menu toggles
"Hide from sidebar" / "Show in sidebar". Collapse state for the playlist tree
and all three My QBZ sections is persisted. The collapsible tree and the flat
My QBZ entry are mutually exclusive — the classic row is suppressed whenever the
tree is on. Settings gains a Navigation section (Navigation + Search moved out
of Appearance). Detail view also gains a discreet, column-aligned sub-header
over the inline track list. Eight locales updated.

cc
2026-09-10 20:19:48 -06:00
vicrodh 66cd5fb1da merge: mixtape/collection detail fixes and importer reduction (#766)
Song-to-album routing, source-aware "Remove from Mixtape", per-source glyphs,
tracks count, "Add to mixtape" from the playlist header with live update, and
stop importing mixtapes/collections until their ids remap. Validated by owner
smoke.

cc
2026-09-10 16:11:57 -06:00
vicrodh ee66a21457 fix(migration): stop importing mixtapes and collections until ids remap
Mixtape/collection items reference playlist and album ids that change during an
account import, so copying mixtape_collections / mixtape_collection_items
verbatim left garbage rows pointing at ids that no longer exist. Drop them from
both copy paths (fast and remapped) and drop the collection view-state JSON,
with a note on how to re-enable once the importer remaps ids (import elements
first, note the new ids, then import + update the collections). Elements
(playlists, local albums) still import.

cc
2026-09-10 16:11:44 -06:00
vicrodh 0297dfcee9 feat(myqbz): add a playlist to a mixtape from its header, update live
- PlaylistView's header menu gains "Add to mixtape" (source-aware: local vs
  qobuz), matching the cards and the sidebar which already had it.
- Adding an item to a mixtape now re-derives and republishes the OPEN detail
  (reload_if_open), so a just-added playlist appears without reopening. (#766)

cc
2026-09-10 16:11:44 -06:00
vicrodh cc50b01dca fix(myqbz): mixtape/collection detail row corrections (#766)
- A song row opens the album it is on (its resolved, source-aware albumId)
  instead of a blank page; the album id is plumbed from the first resolved track
  through the resolve pass, and an unresolved click is ignored like the artist
  link. (#766 problem 1)
- The row menu says "Remove from Mixtape" on a mixtape, "Remove from collection"
  elsewhere. (#766 problem 2)
- A playlist inside a mixtape hides its quality cell (per-track quality is
  varied); collections keep it.
- The source column shows one glyph per DISTINCT source across the item's
  tracks, so a mixed-source playlist shows them all.
- The tracks column is empty only for a track; an album or playlist shows its
  resolved track count instead of a dash.

cc
2026-09-10 16:11:44 -06:00
vicrodh 95d5d532f3 chore(packaging): jack makedepends and Flathub runtime 6.11
- AUR qbz and qbzd: makedepends jack2 -> jack, so pipewire-jack can satisfy the
  build-time JACK dependency (both provide `jack`; only jack2 provides `jack2`).
- Flathub: bump runtime/SDK from the EOL org.kde.Platform 6.9 to 6.11 (verified
  available in the KDE flatpak-kde-runtime branches). The binary is built
  against Qt 6.8 and loads on 6.11 by forward ABI compatibility. (#755)

cc
2026-09-10 13:24:24 -06:00
vicrodh 435098b981 merge: MusicBrainz 503/transport resilience + scene loading UX
Retry 503s and transient transport hangs behind a shared per-process rate
limiter, cache resolved artists, drop the redundant scene backoff, and add a
scene loading heads-up + 5-minute retry timeout. Client tuning and scene
verified by owner smoke.

cc
2026-09-10 13:24:24 -06:00
vicrodh 89da9bbd13 feat(scene): loading heads-up and a 5-minute retry timeout
The ArtistScene discovery fans out many MusicBrainz requests, so a throttled MB
can keep it loading a while. After ~10s the loading screen shows a "this can
take a few minutes" note; after 5 minutes it swaps the spinner for a Retry (a
late success still swaps in the grid on its own). Both derive from the existing
pulse-driven clock, so no new Timer and the repaint law holds. Two new strings
across all eight locales.

cc
2026-09-10 13:23:53 -06:00
vicrodh 8e8efb00fa feat(mb): cache resolved artists in the core, drop the redundant scene backoff
- musicbrainz_resolve_artist is now cache-first + write-after via
  MusicBrainzCache::get_artist/put_artist (resolved_artists), the same shape as
  the metadata path, so reopening an ArtistPage no longer re-runs a live
  artist?query= search. Negatives are never cached; the existing cleanup(ttl)
  governs expiry.
- Remove the scene's one-shot Retry-After backoff: the client already retries
  503s, so stacking another wait could push the total past ~10s.

cc
2026-09-10 13:23:53 -06:00
vicrodh 857cb16e87 fix(mb): retry MusicBrainz 503s and transport failures behind a shared limiter
- Route every endpoint through a new send_with_retry: it waits on the rate
  limiter, GETs, logs the x-ratelimit budget, and on a 503/429 backs off the
  server's Retry-After (capped 8s) and retries, up to 3 attempts. get_artist_tags
  and get_recording_isrcs, which skipped status handling entirely, now go
  through it too.
- Also retry transient TRANSPORT failures (a hung connection to the request
  timeout, a drop or refusal). MusicBrainz under load hangs instead of returning
  503, which surfaced as "error sending request for url". Capped at 2 attempts
  with a 6s request timeout so a total failure is ~13s, not ~33s, while a hang
  that clears still recovers on the second try.
- Share ONE process-wide direct rate limiter across every client (core, the tag
  editor, remote metadata) so several clients on one public IP pace as one 1.1s
  stream instead of each pacing itself.
- Log MusicBrainz's x-ratelimit-limit/remaining/reset headers (debug on 2xx,
  warn on 429/503) as the only signal of whether the bucket was ours or MB's.

Tests: 503-then-200, a dropped connection, and shared-limiter serialization,
each red-to-green. Dev-only deps (tokio test features + rustls/aws-lc-rs to
install the crypto provider); no runtime dependency added.

cc
2026-09-10 13:23:53 -06:00
vicrodh 4216bdb6cc merge: 2.1.x UI polish, playlist row fixes (#764) and sponsor update
Bundles the header-select homologation, seekbar fluidity, per-platform app-menu
and keyboard shortcuts, per-tab Library filter persistence, the local track-info
routing, the playlist removal and remote-cover-art fixes, and a Ko-fi credit.
All verified by smoke test.

cc
2026-09-10 11:22:36 -06:00
vicrodh 4a52c66385 chore(about): add Ko-fi supporter Miguel
New named one-time Ko-fi supporter. Anonymous entries are not recorded.

cc
2026-09-10 11:22:08 -06:00
vicrodh d9d5cd0771 fix(qt): playlist row removal and remote-source cover art
- Remove from playlist did nothing for a Qobuz row. The membership-id split
  made a row's display id (the catalog id QML sends) differ from its
  playlist_track_id (the membership id), but remove_track still matched the
  open document and called playlist/deleteTracks by the catalog id, so the
  lookup found nothing and the endpoint got a non-membership id. Resolve the
  catalog id to the row's membership id, remove exactly that row, and delete by
  membership; same-catalog duplicates keep their other copy. The local and
  mixed-sidecar arms were already correct. Verified by smoke test. (#764)
- Media-server rows (Jellyfin, Subsonic/Navidrome and Plex) showed no cover in
  the playlist TrackRow while the queue and NowPlayingBar did. The row fed
  cached_path the raw artwork token, which classify cannot resolve, whereas the
  queue namespaces it through portable_artwork_ref. Build the row cover from
  that same ref, and warm uncached covers on the local-playlist load (the Qobuz
  loader already did) so the cover appears without playing the track.

cc
2026-09-10 11:22:08 -06:00
vicrodh 04f6ef44e4 feat(qt): 2.1.x UI polish round — header selects, seekbar, shortcuts, per-tab filters
- Homologate the three Library header selects to the ambient Group style; Sort
  gains an arrow-down-up glyph, a "Sort" placeholder and an explicit Default,
  and every select's border clears when it returns to its default value.
- Seekbar fluidity: a drag scrubs locally and commits a single seek on release;
  the time readout is a floating opaque tooltip reparented to the overlay so the
  content-frame shadow no longer bleeds noise over it.
- Route the NowPlayingBar info button and title to the LOCAL track-info modal
  for a local row, so a local track no longer opens an empty Qobuz modal.
- App menu shows each entry's shortcut (muted, smaller, right-aligned, resolved
  per platform — Cmd on macOS, Ctrl elsewhere) and gains a Quit entry; the
  keyboard-shortcuts modal adds Close Window and Quit under Navigation, moves
  Immersive below Interface, and tightens row heights. i18n for Quit/Close
  Window across all eight locales.
- Persist each Library tab's filter toggles to disk, independently per tab.

cc
2026-09-10 11:21:50 -06:00
vicrodh 35c3d368b9 merge: wedged CPAL stream recovery and POLLERR rate-limit (#660)
cc
2026-09-10 01:35:48 -06:00
vicrodh 9fdf53c3ea fix(audio): recover from a wedged CPAL output stream and rate-limit POLLERR (#660)
cpal 0.17's ALSA worker reports `alsa::poll() returned POLLERR` and polls
again immediately, without snd_pcm_recover: a PCM left in XRUN/SUSPENDED/
DISCONNECTED (CPU starvation at a track change on a Pi, a suspend, a pulled
cable) becomes a tight loop that never plays again until the stream is
dropped. rodio's default callback eprintln!s every one of them, which is the
POLLERR flood in the issue. The System default and PipeWire routes installed
no error callback at all, and nothing rebuilt a wedged stream.

- New qbz-audio::stream_health: classify (Wedged vs Transient), a flood
  limiter (one log line per 5s window with a suppressed count), a wedge
  latch, and one shared error_callback installed on every CPAL route
  (System default x4, PipeWire, ALSA) and the legacy player path.
- The audio thread checks the wedge latch at the TOP of its loop, every
  iteration, not in the recv_timeout Timeout arm: under QConnect the
  renderer-state command flow keeps recv returning Ok(...) and the Timeout
  arm barely runs, which left a wedged worker spinning a core for ~22s.
  On a wedge it drops the stream (joining cpal's worker), and when playing
  rebuilds and resumes at the current position; a second wedge within a 10s
  backoff releases the device and stops instead of looping.

Bit-perfect paths (ALSA Direct, DSD, WASAPI) are untouched; they never use
cpal's worker. The low-memory profile from 2.1.0 already addressed the exit
137 OOM; this is the separate flood/wedge half.

Verified on a Pi 4B + HiFiBerry DAC+ over the System default route with a
192 kHz 835 MB track and SIGSTOP-induced wedges: rebuild+resume in ~40 ms,
auto-recovery after the backoff, release+stop on a repeat wedge, and 7
POLLERR log lines instead of a flood. qbz-audio 125 and qbz-player 139 tests
pass; new stream_health and wedge_recovery tests included.

cc
2026-09-10 01:35:38 -06:00
vicrodh 1ff6df5bac merge: DLNA service-URL tolerance and fatal-signal diagnostics (#745)
cc
2026-09-09 22:48:14 -06:00
vicrodh 2181b379bb merge: restore macOS Exclusive mode row and cascade (#748)
cc
2026-09-09 22:44:16 -06:00
vicrodh 7f10782e79 fix(qbzd): keep exclusive_mode across a backend change on macOS
The TUI cascade cleared `exclusive_mode` on any non-ALSA backend. On macOS
System default honours it through CoreAudio Hog Mode, so mirror the Qt
shell's cascade (#748): the flag survives onto ALSA and, on macOS, System
default; every other backend still resets it. The platform is a parameter so
the macOS arm runs on the Linux gate.

cc
2026-09-09 22:44:16 -06:00
vicrodh e58ec85eb2 fix(qt): restore the Exclusive mode row on macOS (#748)
System default on macOS is the CoreAudio exclusive backend: with
`exclusive_mode` set, qbz-audio takes Hog Mode, pins the device and switches
its nominal rate to the track (PR #391, shipped in 1.2.11 and never removed).
The Tauri settings gated the toggle on `ALSA || (macos && SystemDefault)`;
the Slint port copied only the ALSA half, and fc0d35e29 then hid the row on
macOS on the premise that no backend there could honour it. So 2.0.x showed
a grey row and 2.1.0 none, while the DB flag still engaged Hog Mode.

- SettingsDoc publishes `backendIsCoreAudio` (macOS && System default); the
  row is visible and enabled on it. Linux and Windows are unchanged.
- The backend-switch cascade goes through one predicate
  (`output_labels::backend_honours_exclusive`), so re-selecting Auto or
  System default on macOS no longer clears the flag.
- The output LED reads EXCL on macOS System default + exclusive_mode instead
  of DEFAULT while the device is hogged.
- Node test `scripts/test_qt_exclusive_gate.mjs` evaluates the row's real
  gate expressions across platforms; added to qt-runtime-gate.sh. Rust tests
  cover the predicate, the LED and the serde name QML reads.

Verified by smoke on the Mac mini with a USB DAC: the row appears, Hog Mode
is acquired, and browser audio moves to the built-in speakers while QBZ
plays. qbz-qt suite 782/782 on Linux; 13/13 with cfg(macos) on the Mac.

cc
2026-09-09 22:44:16 -06:00
vicrodh 7c4310d8fb merge: integrate Qt window lifecycle and grid artwork improvements
cx
2026-09-09 21:58:39 -06:00
vicrodh 2fc03d325d fix(qt): restore window lifecycle and improve grid artwork
Restore tray-aware close and quit confirmation, circular label covers, and missing label artist portraits. Publish artwork progressively, preload two grid rows, and skip redundant motion reports while preserving data refreshes.

Include regression tests and isolated artwork benchmarks. Verified Linux build, QML checks, and scrolling smoke tests.

cx
2026-09-09 21:58:25 -06:00
vicrodh 5fab503eb7 feat(media): integrate validated Quick Connect and connection guards
cx
2026-09-09 17:12:46 -06:00
vicrodh 75b8eee1c6 feat(media): add Jellyfin Quick Connect and harden remote connections
Add cancellable Quick Connect pairing and shared connection guards for Jellyfin and Subsonic. Bound connection attempts, validate library access before saving credentials, and report per-provider connection and synchronization status.

cx
2026-09-09 17:12:45 -06:00
vicrodh a097b7bf76 fix(library): integrate validated library visibility and toolbar updates
cx
2026-09-09 17:08:42 -06:00
vicrodh 17e08e93de fix(library): restore offline visibility and unify filters and sorting
Recover missing offline library entries, recognize cached-only libraries, and refresh availability after cache changes. Unify per-tab filters, compact the toolbar, and add reversible sort criteria with complete available purchase metadata.

cx
2026-09-09 17:08:33 -06:00
vicrodh 9504139dec fix(qconnect): integrate validated playback and queue hardening
cx
2026-09-09 13:08:52 -06:00
vicrodh d82602e2c7 fix(qconnect): preserve playback state and queue occurrence identity
Keep queue edits from resetting the playback clock, preserve Play next and Play later batches, and report observed renderer state and owned output telemetry. Guard duplicate-track gapless transitions by queue occurrence.

cx
2026-09-09 13:08:39 -06:00
vicrodh 45b745a6d1 fix(qt): integrate validated natural wheel scrolling
cx
2026-09-08 22:19:32 -06:00
vicrodh 55ddc2c768 fix(qt): make mouse wheel scrolling predictable
Accumulate wheel distance with short easing instead of kinetic acceleration. Preserve rapid and fractional wheel input, immediate direction changes, nested scroll chaining and scroll-memory takeover.

Validated with 38 Qt Quick tests and desktop smoke testing.

cx
2026-09-08 22:19:32 -06:00
vicrodh 49247f2ee7 fix(qt): integrate validated input focus and shortcut fixes
cx
2026-09-08 21:40:28 -06:00
vicrodh 0e76c1f486 fix(qt): preserve navigation shortcuts and release text input focus
Keep search, settings and link shortcuts usable while editing, dismiss focus on outside clicks and navigation, and guard deferred focus against hidden inputs. Add Qt Quick regression coverage.

Refs #753

cx
2026-09-08 21:40:28 -06:00
vicrodh bc6a795980 Merge ALAC format and ALSA buffering fixes into pre-release
cx
2026-09-08 20:54:37 -06:00
vicrodh e0755fe2d6 fix(audio): apply rate-scaled ALSA callback buffering
Apply the period after Rodio resets stream configuration, respect device bounds, and timestamp stream errors.

cx
2026-09-08 20:54:37 -06:00
vicrodh b2ca4d7be6 fix(player): derive ALAC signal format from codec configuration
Keep device setup and streaming sources consistent with the decoded ALAC rate and bit depth. Verify real hi-res files and exact synthetic samples.

cx
2026-09-08 20:54:30 -06:00
vicrodh 5c0d62f108 fix(qt): integrate validated playback and library fixes
cx
2026-09-08 18:28:03 -06:00
vicrodh 0b6b111c74 fix(qt): correct playback commands and library browsing
Handle explicit MPRIS Play/Pause without toggling paused playback, remove the 10,000-favorite cutoff, and sort albums by original release dates. Isolate Library filters by tab, include purchased release dates, and support server-ranked artist popularity with safe pagination.

Validated with 766 Qt tests, QML checks, release-sort regressions, and a release build. Refs #750, #751.

cx
2026-09-08 18:27:51 -06:00
vicrodh 09d640138c fix(library): integrate remote album and SACD artwork fixes
Integrate the combined build containing the remote album ID correction and SACD artwork sidecars. The KEF DLNA fix remains on its separate branch pending reporter feedback.

cx
2026-09-08 15:26:15 -06:00
vicrodh 0ebcab6d3e fix(library): support SACD artwork fallback and image sidecars
Refresh missing artwork for unchanged SACD images when a folder cover is added after scanning. Enable image-specific metadata and cover sidecars in the Qt editor while preserving physical track numbers and never writing the ISO.

Validated with SACD regressions, the real Heathen image, 13 Qt editor tests, QML audits and the combined release build.

cx
2026-09-08 15:26:03 -06:00
vicrodh 357f2373fa fix(library): omit remote albums without a server album ID
Keep orphan Jellyfin and Subsonic tracks indexed without exposing internal grouping keys as server album IDs. Align album cards, artist counts and track navigation, and rebuild derived catalogs with schema 8.

Regression fails on the previous projection (three cards for one real album) and passes with the fix for both providers. Catalog suite: 45 passed, 2 ignored scale benchmarks.

cx
2026-09-08 14:38:36 -06:00
vicrodh 6b0561c5cd fix(library): merge validated remote album artwork fallback
cx
2026-09-08 13:39:51 -06:00
vicrodh ff49cbd2ab fix(library): retain album artwork fallback for remote catalogs
Use collection artwork when Jellyfin or Subsonic tracks have no image, preserving existing track artwork. Rebuild derived catalogs so existing libraries receive the correction.

cx
2026-09-08 13:39:50 -06:00
vicrodh 3aed91fa0a fix(cast): localize UPnP URL compatibility and harden crash diagnostics
Keep http 1.5 and tolerate empty or relative service URLs in a local rupnp patch. Cover standard DLNA SOAP requests and malformed descriptions.

Avoid logger locks in fatal handlers, flush emitted log batches, and preserve CI symbols after smoke failures.

cx
2026-09-08 12:08:53 -06:00
vicrodh a7629dc962 feat(log): report fatal signals instead of dying as a bare status
The packaged AppDir has an intermittent SIGSEGV during startup, in the
window between QGuiApplication construction and the QML engine coming up,
with the tokio workers, the audio thread, the loudness analyzer, the
visualizer producer and connectivity all running beside it. It hit twice
on 2026-09-08 on unrelated commits: the v2.1.0 tag's own release-flatpak
attempt 1, which then passed on a re-run of the identical commit, and the
#745 bugfix branch. Both left the same evidence - "status -11" and a log
that stops mid-startup - which is nothing to act on, and a user who hits
this has no re-run to reach for.

The process now reports its own death. A handler for SIGSEGV, SIGBUS,
SIGILL, SIGFPE and SIGABRT writes the signal, the faulting address, the
thread and a backtrace to stderr and flushes the file sink, so the next
occurrence names a frame instead of costing another round trip. It runs
on a dedicated signal stack so a stack-overflow fault can still report,
latches against re-entry, and arms alarm() BEFORE capturing anything:
taking a backtrace allocates and takes loader locks, so a fault inside
malloc could otherwise deadlock the handler and turn a crash into a hang.

The signal is re-raised with the default disposition restored, so the
wait status stays the signal and CI, the smoke harness and any core dump
still see a crash. The integration test asserts exactly that pair on a
child segfaulted on purpose, and it is a named gate in test-crates and
its local mirror.

qbzd installs it too: a headless daemon that dies of a signal leaves even
less behind than the GUI.

cc
2026-09-08 09:51:12 -06:00
vicrodh d1ef21225f fix(log): stop the preflight children from eating the on-disk log
qbz_log::install rotates qbz.log to qbz.log.prev and creates a fresh
file. The internal preflight children (QBZ_INTERNAL_AUTO_PREFLIGHT_CHILD,
QBZ_INTERNAL_GPU_PREFLIGHT_CHILD) re-enter main and ran it too, so a
child renamed the LIVE log out from under the parent, which kept writing
to the renamed inode. On Linux that happens on every launch: qbz.log
ended at the child's own first two lines while the whole session went to
qbz.log.prev, unread. Reported in #749, where the second of those two
lines ("explicit QT_SCALE_FACTOR preserved") was the child inheriting the
variable the parent had just set, not a user setting.

main now reads the child env markers before installing the logger and
takes install_without_file_sink, which keeps stderr and the ring. A child
loses nothing: the parent already captures its stdout proof line and logs
its output tail on failure. The in-app diagnostics export reads the ring
and was never affected.

Second half of the same problem: the file sink is an 8 KiB BufWriter that
only flushed on exit or on a repeat summary, so a process force-killed
while wedged - the state every hang report is filed from - lost the last
and most interesting lines. It now flushes on WARN or worse and otherwise
at most every 200 ms, which bounds the loss without turning a log burst
into one write syscall per record.

Tests cover the rotation contract (a second open renames the live file
away) and the flush policy (bounded window, urgent records never wait).

cc
2026-09-08 08:37:32 -06:00
vicrodh a995025f4e fix(cast): pin http to 1.4.0 so DLNA renderers stop vanishing
rupnp parses a device description's SCPDURL / controlURL / eventSubURL as
http::uri::PathAndQuery. http 1.5.0 rewrote scan_path_and_query and now
rejects two shapes 1.4.0 accepted: an empty element (ErrorKind::Empty,
printed as "empty string") and any relative URL without a leading slash
(PathDoesNotStartWithSlash). Both are common in old UPnP firmware, so the
transitive bump made rupnp drop those renderers during discovery.

Measured in the lockfiles: v2.0.2 resolved http 1.4.0, v2.1.0 resolved
1.5.0, and nothing in qbz-cast changed between them. A KEF LSX gen 1 that
worked in 2.0.2 reported "Visible Renderers: 0" in 2.1.0 with
"Invalid response: empty string", which is exactly
InvalidResponse(InvalidUri(Empty)) reproduced by the new test.

qbz-cast takes http as a direct dependency at "=1.4.0" so cargo unifies
the workspace on it, and device.rs now uses that crate instead of the
rupnp re-export. The new integration test serves two renderer
descriptions over loopback, one with empty and one with relative service
sub-URLs, and requires both to parse: it is red on 1.5.0 and green on
1.4.0, so it fails loudly if the pin is ever lifted. test-crates and its
local mirror gain the named gate.

Closes-when-confirmed: #745

cc
2026-09-08 08:37:20 -06:00
vicrodh 572ce4e16d fix(flake): set the real 2.1.0 srcHash
The flake shipped with srcHash = lib.fakeHash, so consuming it with
inputs.qbz.url = "github:vicrodh/qbz" failed to evaluate. Set the
published v2.1.0 tag hash (verified with nix-prefetch-url --unpack).

Closes #744.

cc
2026-09-08 00:33:21 -06:00
vicrodh 30a778f2f6 fix(flake): set the real 2.1.0 srcHash
The flake shipped with srcHash = lib.fakeHash, so consuming it with
inputs.qbz.url = "github:vicrodh/qbz" failed to evaluate. Set the
published v2.1.0 tag hash (verified with nix-prefetch-url --unpack).

Closes #744.

cc
2026-09-08 00:24:07 -06:00
vicrodh 04fdc186c9 packaging(aur): drop debuginfo from source PKGBUILDs
The qbz/qbzd source builds set only !lto; makepkg then injects -C
debuginfo=2 for its debug package, which inflates the final rustc link
enough to trip the OOM killer on the qbz-qt binary. Add !debug so the
source packages link within a normal builder's memory. Verified by
clean container builds of qbz and qbzd 2.1.0.

cc
2026-09-07 23:40:57 -06:00
vicrodh 61d58f3ead ci(release): fix aarch64 tarball naming and gentoo metadata validation
Two release-pipeline fixes surfaced by the 2.1.0 tag:

- release-linux-aarch64: the GUI and qbzd tarballs (and their internal dirs)
  were named with DEB_ARCH (arm64), but every source consumer — release-aur,
  the Gentoo binary ebuilds, the Flathub manifest — fetches them as aarch64,
  which matches the rpm/AppImage suffix. They 404'd on the tag and needed a
  manual re-upload. Name the tarballs and dirs with ARCH (aarch64); the .deb
  files keep DEB_ARCH (arm64), the Debian convention.
- release-gentoo: the metadata validation step calls xmllint, which is not on
  the runner by default; install libxml2-utils before using it.

cc
2026-09-07 23:40:57 -06:00
vicrodh 0668868a28 packaging(aur): drop debuginfo from source PKGBUILDs
The qbz/qbzd source builds set only !lto; makepkg then injects -C
debuginfo=2 for its debug package, which inflates the final rustc link
enough to trip the OOM killer on the qbz-qt binary. Add !debug so the
source packages link within a normal builder's memory. Verified by
clean container builds of qbz and qbzd 2.1.0.

cc
2026-09-07 23:40:18 -06:00
vicrodh 80a13d75c1 ci(release): fix aarch64 tarball naming and gentoo metadata validation
Two release-pipeline fixes surfaced by the 2.1.0 tag:

- release-linux-aarch64: the GUI and qbzd tarballs (and their internal dirs)
  were named with DEB_ARCH (arm64), but every source consumer — release-aur,
  the Gentoo binary ebuilds, the Flathub manifest — fetches them as aarch64,
  which matches the rpm/AppImage suffix. They 404'd on the tag and needed a
  manual re-upload. Name the tarballs and dirs with ARCH (aarch64); the .deb
  files keep DEB_ARCH (arm64), the Debian convention.
- release-gentoo: the metadata validation step calls xmllint, which is not on
  the runner by default; install libxml2-utils before using it.

cc
2026-09-07 21:40:34 -06:00
vicrodh a32cbb22ea release: 2.1.0 — Rebuild Q (You Can (Not) Redo)
Merge the Qt frontend release (pre-release) into main for 2.1.0.

Conflicts resolved by taking pre-release's versions, verified equivalent:
- .github/workflows/release-windows.yml: main's copy was a verbatim older
  snapshot ('No behavior change'); pre-release carries the current workflow.
- README.md: pre-release's Qt rewrite already covers main's Aug-31 additions
  (native/not-a-web-player, bit-perfect DAC, no EQ/DSP, TDL/SPTFY/DZR, offline
  locks) in its dedicated 'What QBZ is and what it is not' section.

cc

# Conflicts:
#	.github/workflows/release-windows.yml
#	README.md
2026-09-07 18:50:54 -06:00
vicrodh 9857c1992b docs(release): 2.1.0 feature inventory and tag details
Release copy for 2.1.0 "Rebuild Q (You Can (Not) Redo)": the feature inventory
that feeds the Discussion and the tag details rendered by the website and the
in-app About modal. The Discussion link points to discussions/743; the h1
codename matches the About modal literal (about_qt.rs CODENAME).

cc
2026-09-07 18:12:19 -06:00
vicrodh b56e4f9b74 fix(qt): remove an unavailable sidecar row from a mixed playlist
A mixed Qobuz playlist whose Plex (or Jellyfin/Subsonic) sidecar track no
longer resolves renders an "Unavailable track" row, and "Remove from playlist"
from the row menu did nothing:

  playlist remove: no plex key for row plex:47032
  playlist remove: non-numeric row id plex:47032

remove_sidecar_row resolved the ref ONLY through the live queue
(local_picker_ref_for_row), but an unavailable row has no queue entry, so the
lookup returned None, the plex arm bailed, and the fallthrough tried to parse
the "plex:<key>" display id as a Qobuz u64 and failed. Nothing was removed —
and it must not depend on Plex being reachable, since the removal is a local
SQLite delete.

Derive the removal target from the display id itself, which already encodes
"<source>:<reference>", falling back to it when the queue ref is absent. This
also closes the same gap for Jellyfin/Subsonic sidecars (previously unhandled),
via remove_remote_track_from_playlist. Pure helper `sidecar_removal` with unit
tests covering resolved, unavailable-plex, unavailable-remote, local and Qobuz
rows.

cc
2026-09-07 17:36:54 -06:00
vicrodh 06770cfd4c fix(build): qt-cargo.py runs cargo via subprocess, not os.execvpe
os.exec* is emulated on Windows (spawn + terminate the caller) and crashed
the release-windows build: it died with "Segmentation fault ... exit code
139" immediately after the wrapper printed the SDK fingerprint, before cargo
produced any output. The POSIX Linux and macOS builds using the same wrapper
were unaffected, which localised it to the Windows exec emulation.

Spawn cargo with subprocess.run and propagate its exit code. Portable across
all three targets; cargo output still streams through the inherited stdio and
CXXFLAGS/QMAKE plumbing is unchanged. Verified locally: the wrapper reaches
cargo and returns its status.

cc
2026-09-07 15:58:21 -06:00
vicrodh e4ad325744 fix(qt): unique id for the kiosk history queue delegate
The Up Next and History delegates in KioskNowPlaying both declared
`id: queueRow`. qmlcachegen and the runtime tolerate the collision, but
qmllint fails to resolve the inline QueueRowLite type in the second delegate
and the pre-release qt gate rejected it. Give the history delegate its own id.

cc
2026-09-07 12:46:58 -06:00
vicrodh db950139eb chore(about): merge Ko-fi supporter Jan into pre-release 2026-09-07 12:34:11 -06:00
vicrodh 58cd3b70e7 chore(about): add Ko-fi supporter Jan
cc
2026-09-07 12:33:45 -06:00
vicrodh 31da9c57bb fix(qt): kiosk feedback round — chrome, search, album, artists, lyrics
Address the owner's kiosk feedback on top of the release hardening:

- Chrome: the back bar is a drag surface and shrank to 44px with one shared
  button form for Back/Forward/Connect/Cast; Qobuz Connect and Cast are a
  permanent cluster in the bar; the dead Share-logs button left the kiosk
  Settings header.
- Transport bar: the hidden overflow menu is gone; Volume is always visible
  with a vertical sheet, and Desktop mode has its own button.
- Now Playing: one centred transport row (New-bar order); lyrics are centred
  (shared LyricsLinesView gains an opt-in centered, desktop default false).
- Local Library: the media funnel and an Open (ephemeral) entry point reach
  the kiosk; Artists is its own multi-column round-card grid (kiosk-only, the
  desktop LocalArtistsTab is untouched); the Artists drill-down query no
  longer hangs off a stale derived guard.
- Search: the All-tab dashboard shelves render again — they were mounted in a
  Loader Component that read the delegate id under reuse and bound an empty
  model; they are direct children now.
- Album detail: icon actions the height of the quality badge plus play next /
  later / queue, an inline floating quality badge, and zebra rows; both album
  headers show a quality badge.
- Immersive: a quiet auto-hiding kiosk exit moved to the bottom, clear of the
  window controls.

Regression coverage in scripts/test_qt_kiosk_feedback.mjs (wired into the
runtime gate). Verified on a private authenticated Xvfb; QML audits 6/6,
Node gate 5/5, release build 0 warnings.

cc
2026-09-07 12:33:28 -06:00
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
Integrate the Kiosk profile hardening round: 12 dedicated Kiosk views plus
22 shared opt-in hosts, shared navigation history with Back/Forward, Albums
as the default library tab, native paged Local Library (artists/tracks),
lightweight MyQBZ grid and detail, Ephemeral lifecycle, bounded lists and
artwork sized to the physical frame, static skeletons, 64px transport bar,
Settings touch reflow and a persistent Immersive exit.

Desktop keeps its prior behavior: every shared component defaults to the
desktop path and only opts into Kiosk behavior through explicit properties
(origin-relative ScrollMemory, viewport-bounded artwork requests).

Regression coverage: kiosk navigation and artwork Node tests added to the
runtime gate, scroll-origin fixtures for fixed and grouped rows, i18n
strings for all eight locales.

Verified by 750 release tests, six QML audits, Kiosk smokes at 800x480 and
1280x800 and a Desktop A/B with seven pixel-identical captures.

cc
2026-09-07 08:23:11 -06:00
vicrodh c1c463dc94 refactor(about): load sponsor credits from release data
cx
2026-09-06 23:44:22 -06:00
vicrodh 834dc7826d docs(community): clarify Windows and sponsor support
cx
2026-09-06 23:14:21 -06:00
vicrodh 7b90b30d38 docs(contributing): require workflow context in PRs
cx
2026-09-06 23:06:15 -06:00
vicrodh a6649f3bdc docs(contributing): keep model disclosure future-proof
cx
2026-09-06 22:58:52 -06:00
vicrodh 4eea00a61a chore(issue-templates): simplify contributor intake
cx
2026-09-06 22:56:59 -06:00
vicrodh 654ebc5722 docs(contributing): require model-specific LLM disclosure
cx
2026-09-06 22:56:54 -06:00