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
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
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
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