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
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
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
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
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
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
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
- 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
- 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
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
- 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
- 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
Publish the open sequence after the active session and its document on the Qt thread. Keep boot restore and metadata updates from requesting navigation.
cx
Retain artist IDs in new history snapshots and enable artist links in the carousel and View all. Route local sources to Local Library Artists and resolve older Qobuz snapshots through the existing album lookup.
cx
Record the outgoing tab state and scroll before switching, restore same-view history entries, and route sidebar tab requests through the same path without duplicating the initial landing. Add shared CI and local regressions for source filtering and tab transitions.
cx
Filter physical album variants in the compatibility detail view and cancel stale native queries and unfiltered retries. Load album rows when switching out of the native model.
cx
Carry transport failure into Qt startup with a process-local fail-fast bus address. Preserve a responsive bus when remote forwarding fails, and retry the original session on the next launch. Add the silent-bus xcb release regression to the shared CI/local runtime gate.
cx
Classify one-bit queue metadata as DSD before PCM formatting. Display older misclassified snapshots as DSD64, DSD128 and higher multiples without rewriting the stored history, and pass the detail to both carousel and full-page cards.
Verify native local-track to queue metadata and old snapshots in the passing 741-test Qt suite. Audio behavior is unchanged.
cx
Use one mapper for the carousel and full history page. Carry the stored routing source and normalized badge vocabulary through both surfaces, including existing rows, downloads and media servers.
Cover existing snapshots and catalog aliases in the Qt suite; all 741 tests pass with temporary XDG data.
cx
Save the local album identifier and active filter in the account navigation file at clean shutdown. Restore it on remembered startup, preserve launcher link and crash recovery precedence, and fall back to Local Library when the album is unavailable.
Cover profile isolation, a later Home exit, invalid context and startup policy with persistence fixtures. All 738 Qt tests pass with a temporary XDG profile.
cx
Carry the physical source list through Discover pins and Library feeds, including logical albums with multiple origins. Reuse the Local Library badge vocabulary and shared AlbumCard rendering so local origins remain visible in Discover, Library and MyQBZ.
Validated with 738 Qt tests and the six QML audits.
cx
Store browser searches and facet selections per profile with atomic writes. Prefer navigation history when available and restore saved choices before QML can publish construction defaults. Keep the startup crash guard and unrelated preferences intact.
Validate with six QML audits and two passing profile-isolation and persistence regressions.
cx
Probe Qt's default backend in a disposable graphics-only child before runtime and the main window. After proven failure, allow one software presentation attempt without persisting a renderer preference. Preserve explicit backend policy and saved-GPU validation.
Bound and reap children, keep deep links in the parent, and avoid blocked diagnostic pipes. Cover healthy/failing decisions, overrides, missing executables, paths with spaces and watchdog cleanup in the passing Qt suite.
cx
Apply a request-local 15-second deadline without limiting shared streaming requests or canceling store activation. Preserve saved tokens on network failures and release the splash into the existing login/offline route with the error visible.
Verify header stalls, partial and trickling bodies, cancellation, late responses and account retries with local HTTP fixtures plus boot-controller tests. All 103 Qobuz and 733 Qt tests pass.
cx
Use one two-second deadline across native Unix connect, D-Bus authentication, name ownership and URL/window forwarding. Own and shut down the transport on cancellation, preserve unconfirmed links, and keep the primary server alive.
Cover stalled and delayed phases, filesystem/abstract buses, ownership release and early UI handoff with isolated fixtures. All 733 Qt tests pass; async-io features remain unchanged.
cx
Correct dead platform declarations, stale rustdoc and test warnings; validate complete gettext headers. Install LLD in the Qt job and explicitly validate committed shader packs without rebaking them. Keep the independent shader bake gate and add three build-policy tests.
The full local gate passed 2398 workspace tests and 715 Qt tests, QML audits, shader baking and the isolated offscreen smoke without compiler, rustdoc or gettext warnings.
cx