Audio, Playback, Appearance, Navigation, Local Library, Offline,
Integrations and Updates stay in the column; Blacklist, Storage,
Import / Export, Developer (and Orbit while enabled) move under an ADVANCED
disclosure row with a chevron. The fold is not persisted: it opens itself
whenever the current section lives inside it, and reads as active while
closed over the selected page. Appearance takes lucide's
paintbrush-vertical, Integrations unplug and Storage database — baked into
every tint and registered as masters. The kiosk selector follows the same
order, flattened. "Share logs" stays pinned at the bottom.
cc
Settings > Offline loses its "Clear cache" row: it ran the same
QbzOffline.clearAll() as the trash glyph in the Offline Cache Manager's
stats bar, so the whole cache had two entry points and one misleading text
("your downloaded albums are kept" — what is kept are the purchased
downloads in the music folder). The glyph now carries a hover tooltip (the
manager mounts the shared QbzTooltip overlay; the limit and folder glyphs
get one too) and its confirmation says exactly what goes — the live track
count and size — and what stays. The Tauri-era string leaves the eight
catalogs; the new ones enter them.
cc
Settings > Storage keeps only the caches that have no section of their own:
the artwork cache, the lyrics cache and the Plex mirror. The offline
downloads group goes back to Offline and the playback cache (memory profile,
usage, storage folder) back to Playback, since both configure the feature
they sit next to rather than disk in general. Rows and bridge verbs are
unchanged; the playback cache QML test instantiates PlaybackSettings again
(16/16) and the .po location comments follow the artwork rows to Storage.
cc
Offline downloads, the artwork cache, the lyrics cache, the Plex mirror and
the playback cache (memory profile + storage folder) move to a new Storage
section, each row keeping the bridge verb it had. Offline keeps the mode
rows, Playback keeps the behaviour rows, Plex keeps Disconnect.
cc
The boot trim alone let a long session grow the folder past the budget
again (an early release reached 1 GB). A trim now also runs on the
blocking pool every 16 MB of new originals, the budget comes from
ui_prefs image_cache_max_mb (100/200/500/1000 MB, 200 default), .img files
with no row older than an hour are swept at boot, and the settings
document carries the stats, the size options and a clear action.
cc
With featured performers shown, the line is one elided StyledText whose
names are separate links: the main artist opens its page as before; a
featured name opens its page when the album credits carry its id and the
search for that name otherwise. Rows send the names with ids resolved.
cc
Mark A, mark B, loop between them until cleared; local playback only,
never persisted, cleared on track change or a seek outside the section.
A 100 ms watcher reads the millisecond position and reissues the local
seek; the seekbar shows the section and its two marks.
cc
Both were rendered from a hardcoded English month table in Rust, invisible
to i18n. Rust now hands QML the raw timestamp / civil date and QML formats
it with Qt.formatDate(..., Locale.LongFormat) like the purchase views
already do.
cc
Volume restore (desktop session and qbzd driver), a playback-thread panic
at stop, and a credentials decrypt failure (now naming the file and the
likely cause, without the inert legacy retry).
cc
get_tracks_for_eviction had no caller; the resolve-path comment claimed
check_cache_limit evicts by last_accessed_at, but it only refuses new
downloads — downloaded albums are kept by design.
cc
The public README never mentioned that the control API binds 0.0.0.0 by
default nor how to restrict it; the boot line also told users who had set
[server] token that anyone could control playback.
cc
plan() matched same-name twins but never looked for the copy name, so a
run interrupted between create_playlist and the ledger save produced a
duplicate copy on the next run.
cc
The streaming sources unwrap NonZero::new on these values on the audio
thread; the decoded path already guarded both, the codec-params path did
not.
cc
Both requests built bare reqwest clients with no timeout; a stalled
connection parked the fast path forever and the Odesli fallback, which only
runs afterwards, never got its turn. One OnceLock client with the same
10 s / 5 s budget as odesli.rs.
cc
classify_reqwest formatted the raw reqwest error, whose Display embeds the
request URL with its hmac= token; the string is logged on every retry and
in the player's CMAF-failed warning. It now uses the URL-free helper the
legacy stream path already had, moved to qbz-qobuz::net_diag so both paths
share it. The log redaction layer additionally scrubs hmac=, Signature=,
Policy= and Key-Pair-Id= values.
cc
open_log_file_at renamed the live file to .prev on every open, so a second
qbz launch (the #749 two-line qbz.log) or a qbzd in the same directory took
the running process's log away. Writers now hold a shared advisory lock on
<name>.lock; an opener that cannot take it exclusively appends instead of
rotating. The daemon gets its own qbzd.log.
cc
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
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
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
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
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
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
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
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
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
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
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
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