A row or checkbox click toggled one folder. Shift now adds the run over the
rows the filter shows, Ctrl adds one, and the ids stay numbers for the
removal action.
cc
QbzLineEdit re-seeds its box only when `text` CHANGES and only while the
box is not focused; QbzTextArea only while unfocused. Modals that reset
their drafts on the close edge (the box still holds focus for that
frame) or reopen with the seed they closed on produced neither, so the
box kept the previous input while the draft said otherwise. Both
controls gain `reset(value)`, which writes the box directly, and every
modal form calls it from its open handler: playlist edit and create,
MyQBZ create / rename / description, folder create and edit, the link
resolver, the rip wizard and the library folder editor. The MyQBZ
create name now resets on the open edge, and its edit panel re-seeds on
every open, also for the same collection.
Renaming a Qobuz playlist also patches the playlist manager's warm cache,
which the editor seeds from before any fetch: reopening the editor right
after Save showed the pre-save name until the background reload landed,
and forever while offline.
cc
"Dynamic background" gains "Wallpaper" and "Wallpaper, blurred". The
picture is the desktop's wallpaper — Plasma's appletsrc (the containment
on screen 0, image plugin only; a wallpaper package resolves to its
largest bitmap, a slideshow folder to its first image), GNOME through
gsettings (the dark variant under a dark scheme), Windows' current
TranscodedWallpaper, macOS through System Events — or an image the user
picks (Settings > Appearance > Background image), which outranks it. The
resolved path and its mtime are memoised, so the frequent refreshes
(session entry, a mode switch, the window coming to the front) republish
nothing while the file holds.
"Wallpaper, blurred" sends the picture through the same atmosphere pass
Blurred art gives the cover, held still. "Wallpaper" shows the part of
the wallpaper that lies behind the window, lightly blurred under the
half-alpha chrome, at most a screen-sized texture: the window's place on
the screen comes from Qt where the platform tells it (X11, macOS,
Windows); Wayland hides window positions, so there the crop is centred
until the Plasma window-management protocol is wired. Neither mode waits
for a playing track. Six new strings in eight catalogs.
cc
Buttons that rest on surfaceElevated used surfaceHover for their hover
fill: a 6% white wash meant for rows that rest transparent. Over an
elevated button it lands DARKER than the rest fill — on a modal's dark
ground the Cancel/Save/footer buttons went near-black under the pointer.
QbzTheme gains surfaceElevatedHover (a lift of surfaceElevated, derived
so every palette has it), its ambient half-alpha twin, and the
elevatedFill / elevatedHoverFill pair an ambient-aware control paints.
Every site with the old pairing (65 across modals, toolbars, selects,
circle actions, chips and cards) now hovers to the lift.
cc
The compact icon form is now a fit decision, agnostic of the sidebar state:
text tabs while every label at the 11 px floor plus the tab chrome and
whatever trails the tabs (the playlists button a closed sidebar adds) fits
left of the search box, icons only otherwise — or always with "Compact
header navigation" on. A closed sidebar used to force the compact form by
itself; it now only adds one button to the measure, so a window with room
keeps its text tabs. The room is measured from the third sacred button,
not from the hidden row (a Row leaves a hidden child's x where it was).
The downloads ring and the separator + playlists flyout button move out of
the compact row into a trailing group shared by both forms, so a closed
sidebar with room keeps its playlists entry; the group hides outright when
it has nothing to show.
Compact section buttons now call NavFlyout.sectionClicked like the text
tabs and the sidebar rows do, so under the click-navigates opt-in a click
on an icon lands on the section's first entry instead of only opening the
flyout — Purchases was the one compact item that navigated.
Settings > Navigation describes the new rule in all eight catalogs (en
never carried the old entry). The Purchases navigation gate models
headerNavOn and the fit bit: 128 x 3 combinations, still exactly one
reachable Purchases entry.
cc
The separate "Purchases in title bar" placement toggle is gone: with Show
Purchases on, the entry sits wherever the other sections are — in the
sidebar while the navigation lives there, in the header beside the full
tabs or the compact buttons otherwise — so it is never the one entry left
behind. The nav_tb_purchases pref and its bridge key are retired; the
Show Purchases description says where the entry goes.
cc
The bars' audio flyout keeps its switch; Settings > Audio gains the same
key with its two companions: a Target loudness select (-11 / -14 default /
-16 / -18 / -23 LUFS, a value set elsewhere shows as a display-only
"Custom (N LUFS)" entry instead of being re-mapped) and the Prevent
clipping toggle. Eight catalogs updated.
cc
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
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
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
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
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
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
Claiming the official client's `qobuzapp://` scheme was a side effect
of installing. On Linux that displaces nobody; on macOS and Windows the
official client registers the same scheme, and on Windows — where QBZ
is only starting to be adopted — taking the browser's links over at
install time is not a reasonable default. `qbz://` (our own scheme)
stays registered unconditionally.
Settings > Integrations > "Open Qobuz links in QBZ" (link_handler_qt,
pref `qobuz_links_enabled` beside the other Integrations opt-ins),
default on for Linux/macOS and OFF on Windows. The registration moves
only when the toggle is flipped; the one startup reconciliation is
conservative — a claim pointing at this executable while the setting is
off (the 2.0.x MSI wrote one) is dropped, nothing is ever claimed on its
own.
Windows writes/removes HKCU\Software\Classes\qobuzapp (deleting only a
tree whose command names this exe); macOS uses Launch Services and hands
the default back to the first other handler on off; Linux runs xdg-mime
on. The MSI no longer registers qobuzapp and the workflow's protocol
checks cover qbz only. The Windows and macOS arms build on their
platform gates, not on this box.
cc
Preserve the remaining queue when shuffle is disabled, expose clear transport state tooltips, retain Library source filters for the session, and add independent track-artwork preferences for Queue and Library.
cx
Add random access for DSF, DFF, and SACD demuxers, replace live DoP/native sources without releasing ALSA, and expose seek through Qt, MPRIS, and the daemon API.
cx
Apply interface scaling through Qt at startup, complete keyboard navigation, move account migration into setup and progress modals, and defer Subsonic sync while the server scans.
cx
A new top-level Settings section, Import / Export, gathers everything that
moves data in or out of this install:
- SETTINGS PORTABILITY: the settings bundle export moves here from
Developer, with a note on what the bundle does not hold (favorites,
playlists, blacklist, purchases).
- BLACKLIST: export the blocked artists and albums as a JSON one user can
hand to another; import merges additively and never touches the enabled
flag (qbz_app::settings::blacklist_portable, frontend-agnostic).
- ACCOUNT MIGRATION: create a snapshot while signed in with the old
account, then migrate it into the account signed in now, with the local
profile copy and its three opt-in toggles (media servers, scrobblers,
listening history, all on). The disclaimer states that subscriptions and
purchases do not move. Progress and results are status lines in the
settings document; the favorites cache re-warms and the sidebar reloads
after a run, the local half asks for a restart.
Also: the header shows a translated "Member" for accounts without a
subscription, and the login screen says what a member can and cannot do.
50 new strings in all eight catalogs.
cc
Preserve local playback when it is authoritative, make renderer conflicts explicit and persist the selected policy. Filter unsupported queue rows, restore portable Qobuz identities after disconnect, and fence takeover commands to avoid stale queue and playback loops.
cx
Plain-Item overlays get no modal grab, so a MouseArea that swallows
presses still lets the wheel scroll the page underneath (the rule and
its wording live in DiscoverConfigModal.qml). Every scrim and panel
swallow across the modal set now accepts the wheel; the modal Popups
(TrackInfo, CastPicker, AlbumInfo, QconnectDev, LocalMediaInfo) already
had the Overlay grab, and CoverLightbox — a non-modal Popup — gets its
own.
cc
Add capability-aware enablement, exact control selection, diagnostics, live volume synchronization, and translated unsupported-state feedback to the Qt settings surface.
cx
Settings > Integrations > PRIVACY: a Listening history toggle (default
ON; the state lives in the per-user listen_meta, so it follows the
account) and a confirmed Clear (DELETE + VACUUM). Strings in all eight
catalogs; README names where the data lives.
ScrobblerSettings::history_read_allowed(service) = *_active(): the
scrobbling switch now governs reading a service's history for
Recommendations as well as sending to it, and its Settings text says
so. recommendations_qt uses it instead of *_is_authed().
cc
The third sibling of the Linux ksni item and the macOS NSStatusItem, wired into
the same `tray_qt` seams and dispatchers, so a tray action behaves identically
on all three. Close-to-tray needs no QML change: `Main.qml:786` already keys on
`trayLive`, which both arms of `init` publish.
The menu is the SAME menu. The plan's C ABI carried no labels, which would have
shipped an English-only menu in an app with eight locales; Linux and macOS
already share five `qbz_i18n` keys, so the translated strings are passed in
from Rust and no new string is introduced.
Win32 details that are load-bearing rather than incidental:
- NIF_SHOWTIP. Version 4 SUPPRESSES the standard tooltip unless asked, so
without it the icon appears, every track sets the tooltip, and nothing ever
shows on hover.
- The NIM_SETVERSION result is checked and creation fails if it is refused.
The window procedure decodes callbacks as version 4 and nothing else, so a
silent negotiation failure would leave a live icon whose clicks and menu do
nothing -- worse than no icon, because close-to-tray would then hide the
window behind it.
- WS_EX_TOOLWINDOW, not HWND_MESSAGE. A message-only window never receives the
broadcast TaskbarCreated, so the icon would never come back after an Explorer
restart. The window is on the GUI thread, so Qt's own loop dispatches it.
- LoadImageW without LR_SHARED returns an icon this process OWNS; it is
destroyed on teardown, while the LoadIconW system fallback is not.
- Failed creation gives back the window and the icon instead of parking them
for the process lifetime, and create is idempotent.
- NIM_SETFOCUS after the menu closes, which the Shell asks for and matters most
when it was dismissed with Escape.
Every mutation is marshalled through `tray_bridge::ui`, exactly as the macOS
theme arm is. The publishers run on whatever thread playback pushed from -- the
1 Hz poll lives in `crate::spawn` -- while this tray keeps process-global C++
state and talks to a GUI-thread window; calling straight through was a data
race, not merely a documentation mismatch. Linux needs no hop because ksni's
handle is a thread-safe channel.
Strings are truncated on the Rust side by UTF-16 CODE UNITS, not by `chars()`.
The buffers are sized in `wchar_t`, an astral character costs two, and
`wcsncpy_s` with `_TRUNCATE` would happily keep a high surrogate whose partner
was cut -- malformed UTF-16 handed to the shell.
The tray-icon-variant row is hidden on Windows: the notification area draws the
executable's own icon and Windows owns light/dark, so the four choices would
render, persist and change nothing.
cc
Three settings and menu entries offered Windows something no code behind them
could do.
DSD playback was opened to WASAPI in fc0d35e29, written before the phase B
spike. The spike then measured this DAC refusing 176400, the DSD64 DoP
carrier, so DoP over exclusive mode lost its target; and both non-convert
modes are cfg(target_os = "linux") in the audio thread and answer "DoP
playback is Linux-only" with a stream error. The row could only offer two ways
to fail. Back to ALSA-only. DSD still plays on Windows, converted to PCM.
Exclusive mode became visible the moment phase B published backendIsWasapi,
but the selection arm keys on backend_type == WasapiExclusive and never reads
exclusive_mode. A live toggle that changes nothing, directly under the
BIT-PERFECT header and directly above the thing being measured.
Open audio CD is dropped on Windows: the CDDA backend is mod unsupported there
and answers NoDrive to everything. Open SACD image stays on every platform --
it opens a file, not a drive, and hiding it would have been a bug.
cc
Adds isWindows, traySupported, systemNotificationsSupported and
rendererSelectable to the settings document, so Settings QML keeps reading
ONE document the way it already does for isMacos. The two any(...) lists
flip to include Windows when plan A-2 T3/T5 land; they point at that line.
Then the Audio tab, measured on the box with the tab open:
HiFi Wizard was live on Windows. qbz-dac-wizard-core generates a
PipeWire/WirePlumber config and knows no other stack, so the wizard cannot
finish anywhere but Linux. Hidden, not disabled - a disabled row invites a
bug report. This is not the reference's backend-is-pipewire gate returning:
inside Linux every backend still sees it, per the 2026-08-11 ruling in that
file's own comment.
Reserve DAC is D-Bus org.freedesktop.ReserveDevice1, Linux only. On Windows
exclusive mode IS the reservation; on macOS it is Hog Mode.
Exclusive mode was visible with no backend able to honour it. It now follows
the backend: Linux unchanged (always visible, enabled only on ALSA), hidden
on macOS, and on Windows it appears when Phase B publishes backendIsWasapi.
QML reads an absent doc property as undefined and undefined === true is
false, so this is correct today and correct again the day the field lands.
The DSD row follows the same pair.
Main.qml no longer boots the wizard off Linux, where its row is hidden and
the audio-stack probe would reach nothing.
Linux sees no change: every gate is isLinux-true or an added || on a field
that is undefined there. Five QML audits pass.
cc
Port the Tauri immediate and accumulated offline policies into the active Qt fire path, retain independent scrobbler sessions across Qobuz logout behind a default-on opt-out, and flush queued Last.fm/ListenBrainz plays only when the effective policy permits it.
cx
Add the chained genre, artist, and album browser with persistent layout and tab ordering. Reuse the shared album semantics for grouped versions and disc-aware details, enforce active source filters, and bound foreground detail work so chained searches stay responsive.
cx
Hide the waveform controls and force every now-playing bar back to the classic seekbar while preserving the implementation for a later release. Keep full-screen timed lyrics centered, slightly larger and wrapped without changing the approved typography or fill.
cx
Analyze audible tracks into a progressive 512-bin RMS document, cache complete results, and preserve gapless track boundaries. Render the waveform through a scenegraph item in both player bars behind a default-off preference.\n\ncx
It shipped doing nothing. Measured on the built binary: picking a face
persisted the pref and changed ZERO of 600600 pixels in the settings
label column. The row was a control that lied.
Two measurements say why, and both were run against this Qt build rather
than reasoned about:
- `ApplicationWindow.font.family` propagates through Qt Quick CONTROLS
only. In a standalone scene with a family set on the window, a
`Label` resolved to it and a sibling `Text` stayed on the
APPLICATION font. This tree is 1082 plain `Text` items against 13
that name a family, so the app's typeface IS the application font.
- The application font is read when an item is CONSTRUCTED. Driving
PySide6 against the same Qt, `QGuiApplication::setFont` updated
`QGuiApplication::font()` and left an already-built `Text` on its old
family.
Together those close the door on changing it live, so the ordering in
main() is the feature: FontPreload.qml registers the bundled faces (a
family does not exist until its file is loaded, and `load()` returns
with its FontLoaders already in the database), the application font is
set from the persisted choice, and only then does Main.qml build the UI.
Swap any two of those and the setting silently does nothing again.
The row now says "(requires restart)", the same wording Interface size
two rows above already uses, in all eight catalogues.
"System" resolves to "" and takes no branch, so anyone who never opens
the row gets exactly the font this app rendered before — verified by
capturing the window on both settings: 0 log lines and an untouched
frame for System, 27065 changed pixels for Montserrat.
NOT ADDRESSED, and worth its own decision: those 1082 Text items have
never used the bundled Inter either. `Main.qml` has always named it, but
that only ever reached the Controls, so the app renders in whatever Qt
picks. Making "System" mean Inter is a one-line change and a visible
one, so it is left alone here.
cc
The three media servers sit one under the other and Plex was the odd one
out: a small-caps "PLEX" eyebrow with no brand mark in a 64px band,
against the 44px SourceIcon + 14px DemiBold title the other two use.
SourceIcon already draws the full-colour Plex mark untinted, so this is
the same header, the same metrics and the same glyph pipeline.
NOT folded into MediaServerSettings.qml: that component is one form
twice — address, account, password, connect, sweep — and Plex's body is
a different shape, a PIN pairing flow with a server picker and a
per-library table. Only the header is the same.
One affordance the other two have is deliberately not copied: the
"· <server name>" suffix. PlexFields carries no name — pairing stores a
url and a token — so the row would be permanently invisible. An empty
binding is not parity, it is a dead branch.
cc