build(qt): gate on a fifth audit — every icon name must have a baked svg

`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
This commit is contained in:
vicrodh committed 2026-08-20 21:15:10 -06:00
1 parent 85ece130e7
commit ffc2d7d664
1 file changed
+7 -3
+7 -3
View File
@@ -29,7 +29,7 @@
# ─── THE QML AUDITS RUN FIRST, BY DESIGN ────────────────────────────────────
# `cargo check` sees NOTHING of the QML: a missing component or a call to a
# bridge member that does not exist compiles clean and throws when the user
# opens that view. The two static audits are ~1s and catch exactly that, so they
# opens that view. The five static audits are ~1s and catch exactly that, so they
# run BEFORE the build — failing in one second beats failing after two minutes,
# or worse, at the owner's smoke. NO_AUDIT=1 skips them.
#
@@ -38,7 +38,7 @@
# NORUN=1 ./scripts/qt-run.sh # build only, don't exec
# TEST=1 ./scripts/qt-run.sh # also run `cargo test -p qbz-qt`
# SMOKE=1 ./scripts/qt-run.sh # offscreen gate instead of the GUI
# NO_AUDIT=1 ./scripts/qt-run.sh # skip the two QML audits
# NO_AUDIT=1 ./scripts/qt-run.sh # skip the five QML audits
# JOBS=4 ./scripts/qt-run.sh # cargo build jobs (default: auto)
# MOLD=1 ./scripts/qt-run.sh # link with mold (forces a full rebuild)
# FORCE=1 ./scripts/qt-run.sh # build even if another cargo/rustc is up
@@ -146,7 +146,11 @@ if [[ "${NO_AUDIT:-0}" != 1 ]]; then
# missing from build.rs is not in the QML MODULE, so it resolves fine on
# disk, compiles nothing, and blanks a whole page at runtime with
# "<Component> is not a type" (2026-08-20, the media-server panel).
for a in qml_resolution_audit.py qml_singleton_xref.py qml_eager_tab_audit.py qml_module_registration_audit.py; do
# qml_icon_bake_audit.py joined on 2026-08-20: QbzIcon draws PRE-BAKED
# per-tint svgs, so an icon name with no bake renders NOTHING — no error,
# no failing test, no complaint from the other four. Three menu rows
# shipped iconless through a green build before anyone looked at the menu.
for a in qml_resolution_audit.py qml_singleton_xref.py qml_eager_tab_audit.py qml_module_registration_audit.py qml_icon_bake_audit.py; do
[[ -r "${AUDIT_DIR}/${a}" ]] || { warn "audit ${a} not found — skipped"; continue; }
if ! python3 "${AUDIT_DIR}/${a}" "${audit_abs}"; then
die "${a} FAILED — fix it before burning a build (QML resolves lazily; this is what cargo cannot tell you)."