Observe process survival through the full smoke window, isolate profiles and the session bus, restore hidden SDKs and reap private process groups. Reuse the Qt log gate and retain smoke evidence on failures. Add subprocess regressions to the test-crates gate.
cx
Detect sector layout from Master TOC signatures and strip physical framing while preserving logical addressing, bounds and file mutation checks. Add reader and scanner regressions with synchronized CI presence guards.
Verified full decoding of 53 tracks across five real images, seek and previous-reader comparisons, isolated release UI opening and scanning, and disc, DSD, library and Qt tests.
cx
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
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
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
Deploy the official x64 redistributable DLLs into the shared MSI/ZIP payload and reject missing, debug, incompatible or shadowed CRT imports. Smoke both deployed products with isolated profiles and record loaded Qt/CRT paths.
Validate with 14 PE fixtures, PowerShell parsing, actionlint and shellcheck. Native clean-Windows acceptance remains pending.
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
Keep queue reads from starving controls without weakening real queue-write serialization. Restore official proto3 zero defaults and next/previous loop, shuffle and autoplay semantics.
Project controls from the active peer state and target exact upcoming queue occurrences without optimistic cursor changes or local playback fallback. Cover takeover, seek, mute, wire decoding and manual skip regressions in the CI gate.
cx
Parse resilient SACD images, decode DST, scan ISO releases into the local library, and keep explicit opens ephemeral.
Ship decoder notices across distribution formats and align DSD album filtering and version selection.
cx
Same shape as the listen-log presence check: the workspace run already
executes them; the counts keep a renamed module or a dropped cfg from
shrinking coverage without a trace. The workflow calls the script, so the
mirror stays exact; its header names the new check.
cc
A property named `on<Upper>` is filed as a signal handler as soon as a
sibling member `<upper>` exists: its initializer never binds, it sits at
the type default, and nothing warns. `PurchasesView.qml` declared
`onAlbums` next to `albums`, so the Albums tab bound `[]` once and never
re-evaluated, which blanked Purchases for every account with real
purchases. Reproduced standalone with the `qml` runtime: the identical
expression under another name reads `true`.
Rename it to `albumsTab` and the four other `on<Upper>` properties
(`collectionsTab`, `surfaceTint`, `accentFloor`, `litColor`) so the rule
can be exact, and add `qml_on_prefixed_property_audit.py` to the four
QML audit sites (qt-run, cargo-test, test-crates, qt-gate-windows).
cc
Linux: scripts/packaging/desktop_entry_check.sh asserts the MPRIS
DesktopEntry constant against the recipes (nfpm basename + Icon=,
StartupWMClass, reverse-DNS basenames) and against the built deb/rpm/
AppDir. The AppDir's desktop file now carries the reverse-DNS basename
so an integrated AppImage resolves the media-widget icon too. This
invariant regressed more than once; it is a gate now, not a rename.
macOS: compile packaging/icons/QBZ.icon (Icon Composer, issue #712) with
Xcode 26's actool into Assets.car + CFBundleIconName=QBZ; icon.icns stays
as CFBundleIconFile for older macOS. The Intel leg moves to macos-26-intel
so both legs carry actool.
cc
cargo-test.sh and test-crates.yml assert qbz-app::listen_log lists at
least 25 tests and passes, so a refactor that drops the module's tests
fails loudly instead of shrinking coverage. Bounded: the binary is
already built by the workspace run.
cc
Teach qmllint about statically registered native QML types and enforce metadata parity. Pin release builds to the MSVC 2022 runner and force Cargo to use the Visual Studio linker instead of Git for Windows link.exe.
cx
The GLSL 1.20 variant must stay in plasma/ambient's packs: Qt's OpenGL
RHI selects 120/130/140 on desktop GL <= 3.1 and the renderer tier does
not exclude that hardware, so dropping it (the previous attempt) was a
regression for old GL — caught in review. qsb 6.8 cannot lower those
shaders' uint hash constants for 1.20 while 6.9+ can, and the committed
.qsb were baked with 6.11: the bake toolchain is therefore pinned apart
from the 6.8 runtime floor (vars.QT_VERSION_SHADERTOOLS, default 6.9.3).
The gate also passes -b only to vertex shaders without the no-batch
marker, exactly like build.rs.
cc
Measured cold on the 4-vCPU CI VM: CARGO_BUILD_JOBS=4 finishes the
workspace test in 392 s against 589 s at jobs=2, with a 2.7 GB peak on
a 10 GB guest. The throttle protected a 30 GB rustc that no longer
exists in this graph. Leave the job count to cargo in the workflow and
in scripts/cargo-test.sh alike; --no-fail-fast and the 20-minute limit
stay.
cc
The single-instance lock is a D-Bus well-known name, so on a developer
box with QBZ open the debug binary presented the running instance and
exited with one log line, failing the gate for the wrong reason. The
smoke now runs under dbus-run-session with its own XDG dirs — never the
developer's config or session — in the script and the workflow alike.
cc
Per the 2026-08-26 rule: the workflow's test job runs scripts/cargo-test.sh
(CARGO_BUILD_JOBS=2, --no-fail-fast, 20-minute limit), the script
documents what --workspace covers now that the Slint crates are gone,
and QT=1 runs the Qt gates locally in the same order as the shader-gate
and qt-gate jobs: audits, shader bake, Slint-free graph, cargo test
-p qbz-qt, offscreen boot.
cc
qml_resolution_audit hard-coded /usr/lib64/qt6/qml, so on a runner it
saw zero Qt types and reported every Rectangle as unresolved; it now
takes QBZ_QT_QML_DIR (authoritative), then QT_ROOT_DIR, then the distro
layouts, and fails loudly when none has plugins.qmltypes. test-crates
runs the audits after the Qt install, pointed at it.
build.rs re-bakes a shader only when its .frag is newer than its .qsb,
which makes a fresh checkout nondeterministic whenever qsb is on PATH.
Release and test builds now install Qt without qtshadertools, so the
committed .qsb ship as-is, and a new shader-gate job bakes every shader
to a temp dir with the pinned qsb using build.rs's own target lists
(read from the file, so they cannot drift). It is red today at 6.8.3:
plasma.frag's uint literals do not bake for the GLSL 120 target with
qsb 6.8 (6.9+ accept them) — that is the finding, not a false positive.
cc
The Slint crates are gone from the workspace, so cargo-test.sh's four
--exclude flags were stale; qbz-qt is the only exclusion now (it is the
one crate that needs Qt, and the sole consumer of wayland-sys — the
reason the workspace test fails on a runner without libwayland-dev).
Release workflows write their caches from main only: nothing but
test-crates.yml is meant to run from pre-release.
cc
build-aarch64-linux.sh builds -p qbz-qt natively with the distro's Qt
(gated at >= 6.8, the tree's floor; Debian 13 / Ubuntu 25.04 / Fedora
41 qualify), installs rustup when absent, forces one rustc at a time on
<= 4 GB boxes, and runs the same offscreen smoke as CI. Its CROSS mode
is retired honestly: cxx-qt runs the target Qt's qmake during the
build, which cross-rs's Ubuntu 20.04 images cannot do. The qbzd script
and Cross.toml keep cross-compiling the daemon, with the Qt-free dep
set.
cc
The five static QML audits move from the docs checkout to
scripts/qml-audits so CI runs the same gate as scripts/qt-run.sh
(which now defaults to the in-repo copy). New qt-gate job: audits,
Slint-free graph check, cargo test -p qbz-qt, and an offscreen boot of
the debug binary that fails on any QML complaint. cargo-test.sh excludes
qbz-qt (it needs Qt) so the no-UI job stays cheap.
cc
`QbzIcon` does not draw an SVG by path: it picks a pre-baked, per-tint copy
out of `qml/assets/icons/<tint>/`. A name with no bake renders NOTHING. The
row keeps its 15px slot, the label sits where it always did, and no test,
audit or compiler says a word.
That is how three rows of the new Refresh menu shipped iconless through a
clean build, 434 green tests and four green audits: `"icon": "server"` looked
obvious and has no bake. It was caught by driving the app and LOOKING.
The audit checks literal `name:` / `"icon":` assignments against the union of
the tint directories, and it is deliberately a floor, not a ceiling: an icon
chosen by expression is out of reach for a static pass, and a first draft that
also flagged names absent from SOME tints produced ~90 findings, all noise —
amber/green/orange/favorite are status tints that hold a handful of glyphs by
design. An audit that cries wolf gets ignored, and then it protects nothing.
Proven in both directions before landing: with "server" reintroduced it fails
and names the file; without it, 101 names across 288 files pass.
The tool itself lives in qbz-nix-docs (planning/tooling stays out of the repo).
cc
ONE component instantiated twice, because it is one form twice. Every
difference between the protocols is a property set at the call site, so
they are visible in one place instead of drifting across two files:
"Test" proves the address for Jellyfin and only "a Subsonic server
answered" for Subsonic (which has no credential-free endpoint), the
password is discarded for Jellyfin and kept for Subsonic (no session,
md5(password+salt) per request), and the sync cost is stated up front —
45.8 s per 4924 tracks against 0.81 s per 6678 — so a long sweep does not
read as a hang. The credential never leaves Rust; the panel gets
`hasCredential`.
Assets: the colour marks for cards, the monochrome ones for rows. Both
mono files arrived as BLACK ink and are invisible on a dark theme
untinted, so they were normalised to the #ffffff master ink and joined
`icon_tint_qt::MASTERS` — the port's only shader-free way to recolour an
SVG per theme. The Jellyfin colour asset had a white rounded-rect plate
behind the mark; it was stripped, because every other brand mark here is
transparent and a white tile beside them reads as a rendering bug.
Rendered with Qt itself and looked at before any of it was wired.
CAVEAT, recorded in SourceIcon: the Subsonic mark is NAVIDROME's logo,
and `subsonic` covers Gonic, Airsonic, Astiga and Ampache too. `ping`
reports the server's own type and it is already persisted, so branching
later is cheap — a known wrongness, not an unnoticed one.
THREE BUGS THAT ONLY THE RUNNING WINDOW COULD SHOW, all found by
driving it under Xvfb rather than by reading the log:
1. `MediaServerSettings is not a type` — a .qml missing from build.rs is
not in the QML module. It resolves fine on disk, compiles nothing,
and blanks the WHOLE settings page at runtime. All three existing
audits passed. `qml_module_registration_audit.py` is the missing
check and is now in the gate.
2. The toggle would not move. The panel reads its state from the
settings DOCUMENT, unlike Plex's toggle which reads a bridge
property, so every mutation has to republish it — connect, enable,
disconnect and the end of a sweep now do.
3. Every Jellyfin album in the grid drew the LOCAL HARD-DRIVE badge:
`local_rows::badge_source` had never been taught the two words and
folded them into `_ => local`. Not cosmetic — that string is what the
source chips filter on.
`play_single_from_source` also grew the arm it needed: a media-server row
from the recently-played rail has no library.db row to mint a LocalTrack
from, so it resolves through `registry.tracks_of` instead.
END TO END, from the UI, against the real server: toggle on -> type the
address -> Connect -> "jellyfin sync: 500/4924 ... 4924 saved, 0 pruned,
4924 cached" -> Local Library shows 461 albums with their covers and the
Jellyfin badge. The cached quality distribution is identical to what the
server reported in the original protocol sweep.
428/428.
cc
TWO FINDINGS, both of which falsified something that was being assumed.
FLUIDITY IS NOT FRAME RATE. Driven at QBZ_PULSE_MS=10 the shell presented ~100
frames/s — qt-batches confirmed it, and the script now prints the pulse it ran
with so two logs cannot be confused — and the bars looked exactly as they had at
30. They would: every published frame was applied as a STEP, so the extra
presents repainted the same discrete values three times over. What the Slint
build does differently is not rate, it is `animate bar-h { 90ms }` — it moves
BETWEEN values.
So the interpolation is back, ON THE PULSE, and it costs nothing. The old
interpolator was expensive because of its private 16 ms Timer, not because it
interpolated: it presented at its own rate, unsynchronised with everything else.
Easing on the pulse presents exactly as often as stepping on the pulse did — one
repaint per period either way, only the value differs. Self-parking survives: a
converged ease writes nothing and schedules no repaint. The price is lag, and it
is the honest one — k = 0.45 is ~83% there in three pulses (~100 ms), matching
the reference's 90 ms, against 46 ms of FFT window and 77 ms of Rust smoothing
already in the chain.
The from/to/progress triple is gone with it, and its consumers moved to `cur`.
Note for the next reader: `SpectrumBand` drove all three of its modes from
`Connections` on those properties, and a Connections handler naming a property
that no longer exists is NOT a compile error — QML warns at runtime and simply
never fires. That is how the whole band went dead at once; grep for
`on<Name>Changed` when removing a property, not only for direct reads.
PLASMA WAS DRIVEN BY THE SLOWEST SIGNALS IN THE PIPELINE while the fast ones
travelled beside it unconnected. `transientAmp`, `bandsLo` and `bandsHi` were
published, parsed and stored, and never handed to the item — the shader declared
them and received zeros — so everything ran off the energy bands, which decay
x0.85 per frame (~473 ms) against the bars' x0.65 (~178 ms).
Worse, `beat` stops being a beat on dense material: it decays x0.88 per tick, so
above ~3 hits/s it never falls far enough to come back as a spike and settles
into a shallow ripple around 0.7. A splat multiplied by it becomes a steady
glow, and the busier the music the flatter the picture — the most frenetic input
produced the least motion. `beatAc` subtracts a slow density floor, so a hit
reads as a hit at any tempo.
The scene itself was rebuilt around the reference the owner filmed (Butterchurn
on a percussion-heavy track). Its recipe is three parts: one thin annulus
injected per frame that the feedback warp marches inward, so the concentric
rings ARE the last N frames of audio laid out in space; retention high enough
for that to stack (this held 0.85, where a trail died in ~15 frames and nothing
ever accumulated); and angular structure from reading the spectrum BY ANGLE.
But a CLOSED ring was wrong, and the owner named why: a perfect inner
circumference reads as a drawn shape, hard-edges the field, and at this
retention successive rings pack into one solid disc — which is what killed the
ambient feel. The annulus is gated by angle now, so a hit paints arcs the
conveyor carries inward, the circle never closes, and the gaps stay open for the
background. Noise bends, frays and thins those arcs so they are made of plasma
rather than of compass.
The worms are back at full strength with tighter cores and a contrast curve — a
raw gaussian is all shoulder and reads as haze — and their orbits now reach past
the arc band instead of being trapped inside it. Colour comes from ONE
album-derived triad, so everything used to be a shade of the same hue; a
Rodrigues hue turn splits it into worms, arcs and an ambient wash that sit apart
whatever album is playing. That wash is what stops the background being void for
the foreground to be a cut-out against. A soft limiter replaces the hard clamp:
at 0.945 retention a clamp pins the middle to white and the structure vanishes.
Release build clean, offscreen smoke green, 421/421.
cc
The class the previous commit fixed leaves no trace a reader would
notice: the view renders correctly, cargo is happy, both existing
audits pass, and the only symptom is that entering the section takes
seconds. So it needs a static gate or it comes back.
qml_eager_tab_audit.py reports a per-tab body gated only on `visible:`,
and reports it only when it is genuinely heavy — it must instantiate a
Repeater/ListView/GridView AND that view's model must be data rather
than an inline literal. Without the second condition it flagged the
sort popups in LibraryToolbar, which are Repeaters over four fixed
options, and three false positives would have buried the one real
finding.
cc
Accumulated qt-frontend work from the 2026-08-11/12 sessions (cc),
uncommitted until now: the app-wide dynamic background in both modes
(AmbientField with GPU/Canvas arms, AppShell layering, ambient palette
pipeline), the wave-1 clip/batch sweep across the tree (alpha batches
449 -> 327, reproducible), the rounded-cover and icon-tint arms, and
scripts/qt-batches.sh (batches + presents/s sampler).
The GPU behaviour of these pieces was finalized in the follow-up pulse
commits; see qbz-nix-docs/qt-frontend/2026-08-11-scenegraph-batches/
GPU-COST-INVESTIGATION.md for the full campaign.
km
The artifact is now `target/<profile>/qbz` (owner, 2026-08-04). The package
stays `qbz-qt`, so `cargo build -p qbz-qt` is unchanged.
⚠ `crates/qbz` — the Slint binary — declares the SAME bin name and shares
this workspace's target dir, so both write `target/<profile>/qbz` and the
last build wins. Survivable only because the Slint binary is never built in
this worktree (the qbz-ui 20-30 GB wall); if it ever is, a smoke against
"the wrong qbz" is silent. Written into Cargo.toml beside the name.
The MPRIS line exists because "Plasma sees nothing" has two very different
causes — the player never registered, or it registered and no metadata was
ever pushed — and the log could not tell them apart. It settled that
question the same day: the owner's log shows `[mpris] metadata pushed:
Holy Diver — Dio (350s)`, so the port's half works and what remains is
desktop-side.
cc
The release-section "Load more" fetched a page and appended it to a structure
no view ever re-read. The handler pushed IN PLACE into the object behind
`readonly property var artist: JSON.parse(QbzArtist.artistJson)`, and the
releaseSections projection returned that same array on the unfiltered arm, so
both Repeaters were re-handed the identical reference and nothing was dirty.
The tree's own rule, written in cards/PlaylistCollage.qml: rebind requires a
NEW object reference. A second bug sat on the same path - the artist document
republishes progressively (stories, then each MusicBrainz section as it
resolves) and every republish re-runs the parse, wiping anything appended, so
fixing only the first would have left a bug that looks random. Both
pre-existing; the in-flight placeholder added earlier is what made an
always-silent no-op visible.
Appended pages now live in an overlay keyed by release type. It survives a
republish, emits new arrays so the Repeaters rebuild, dedupes by album id,
carries the reference's 4-page cap per bucket (artist.rs:718-720, which hands
off to the discography page beyond it), and is guarded against a page
requested on one artist landing on another's grid - releaseSectionReady
carries no artist id and every artist has an "album" bucket.
"See discography" was a pointer-cursor MouseArea with no onClicked under a
POC-NOTE saying the page was out of scope. ArtistReleasesView.qml +
artist_releases_qt.rs port ArtistReleasesView.slint: infinite scroll rather
than a button, plus the loading, error-with-retry, empty and offline arms.
The album page's "More from <artist>" view-all is a second door to the same
page. Its paging offset counts what the SERVER returned rather than the
rendered row count the reference uses: blacklist filtering and id dedupe make
the rendered count smaller, so the reference re-requests seen rows and skips
the tail whenever the blacklist is non-empty.
The per-section sort was a dimmed Rectangle with no MouseArea, under a
comment asserting no seam existed. It is now a real QbzSelect with the
reference's five options and index mapping, persisted per release type in
artist_prefs. Choosing "Default" after a sort does not restore the server
order until the page reloads - that is the reference's behaviour
(album_map.rs:262 is a bare no-op arm and resort sorts the live model), kept
deliberately rather than diverged.
Also, two visual fixes from the same round that share these files: Popular
Tracks drew its cover at radius 8 where the shared rows/TrackRow.qml uses 4,
and the album page's rail mounts said `width: parent.width` inside a Column
with 32px padding - QML padding shifts children without narrowing
parent.width, so the rails hung 32px off the right and the right page chevron
landed outside the window.
Carries scripts/qt-diag.sh, which has been sitting untracked since the
rendering round: it answers "which backend is this actually running on" from
a real graphical session, refusing ssh and refusing offscreen because either
would answer the wrong question. That refusal is the point - the doctrine it
disproved ("shader effects render nothing on this port's software path") came
from an offscreen observation, and offscreen forces software by definition.
cc
Ports the three sections the tab was missing, against the Slint
implementation rather than invented: the section rails, the cards, and the
mix landing page with its navigation seam so back/forward behave.
The mixes reuse the shared crates for their requests -- the Slint app
already makes them, so nothing new talks to Qobuz here.
Also carries the qt-run.sh portability work needed for the Mac mini:
`free`, `nproc`, `pgrep` and `timeout` all differ or are simply absent on
BSD/macOS. Two of those bit during the first Mac build. `pgrep` exits 1
when it matches nothing -- the normal case -- and under `pipefail` that
killed the script before a single line of output, which reads exactly like
a build that did nothing. And `timeout` does not exist on macOS at all, so
the smoke produced a one-line log over which the grep cheerfully reported
"0 QML complaints": a test that never ran, rendering as a pass. The smoke
now fails outright on a log that short.
cc
The Qt-track sibling of slint-run.sh, and deliberately not a copy of it.
No memory tiering and no cgroup cap: qbz-qt does not depend on qbz-ui, so
the 20-30 GB compilation unit never enters this graph. That is the point
of the track.
No RUSTFLAGS and no +nightly either. This worktree's target dir was built
on stable with rustflags=[], and RUSTFLAGS is a fingerprint input, so
exporting mold would buy a from-scratch rebuild of the workspace to save
seconds on a two-minute incremental. MOLD=1 opts in and warns first.
The two QML audits run before the build, because cargo check sees nothing
of the QML: a missing component or a bridge member that does not exist
compiles clean and throws when the view opens. They take a second.
SMOKE=1 runs the track's offscreen gate; a guard refuses to start while
another cargo/rustc is alive, since one Slint build in a sibling worktree
is enough to freeze the box.
cc
- PR1: gamescope detection at boot — X11 backend, scale 1, kiosk
fullscreen; tray + miniplayer suppressed (click-offset fix).
- PR2: full keyboard navigation for the kiosk (3 zones, focus ring,
scroll-into-view; typing never navigates).
- PR3: native gamepad via gilrs — D-pad/stick/A/B to synthetic Slint
keys with hysteresis, repeat and debounce; B suppresses Escape while
a text field is focused (closes the Steam OSK).
- PR4: Steam OSK via the steam://open/keyboard deeplink on text focus.
- PR5: scripts/qbz-add-to-steam (stdlib-only, 19/19 unit tests) with
artwork assets + pure-gamepad controller layout.
Spec: qbz-nix-docs/steam-deck/STEAM_DECK_GAME_MODE_PLAN.md
km
Docker/cross build of the slint-free daemon for the Pi streamer
(~90 s incremental, dist/qbzd-aarch64-linux); native path included for
aarch64 hosts.
km
Learned per-tier ETA from the last successful build, a live elapsed/ETA/percent ticker while codegen is silent, and start/finish banners. Safe under zsh/oh-my-zsh (runs under bash, newline-terminated, TTY-gated colors). NO_TICKER=1 silences it.
cc
Keeps only the most-recently-modified incremental-compilation dir per crate under crates/target and prunes the rest (slint-run.sh's per-RAM-tier RUSTFLAGS spawn a new hash dir each time; Cargo never GCs them). Release builds do not use incremental, so unaffected.
cc
Run unit/doc tests for the shipping crates workspace on PRs without
compiling qbz-ui. Fix three green-blockers: local playlist schema for
tests, Track.work fixture, and a stale ListenBrainz doctest.
The file was an untracked local script predating this session; a
directory-level git add pulled it into the #549 commit unintentionally.
Back to untracked, file kept on disk.
cc
Users on CPUs without AES-NI (Nehalem i7-860, pre-2010 Intel Macs) or
without SSSE3 (AMD Phenom II) crashed at launch with SIGILL. The
global `-C target-feature=+aes,+ssse3` was kept after the #353/#343
x86-64-v3 incident on the belief that the aes crate's cpufeatures
runtime check would protect CPUs without AES-NI — but a compile-time
target feature makes cpufeatures compile its check to a constant, so
the AES-NI intrinsics ran unconditionally (credentials AES-GCM decrypt
at startup), and +ssse3 let LLVM autovectorize anywhere, breaking
Phenom II. Measured on the qbz-cmaf hot path (Ctr128BE<Aes128>
apply_keystream, 256 MB, release): 2145 MB/s compiled-in vs 2162 MB/s
runtime-dispatched, identical output — the flags bought nothing on
capable CPUs. Removed from .cargo/config.toml, all dev/build scripts
and the release/build workflows; old CPUs now get the software AES
fallback instead of a crash.
Fixes#549
cc