Commit Graph
96 Commits
Author SHA1 Message Date
vicrodh 64ec3e2bc8 fix(updater): restore Qt release checks and signed installation
cx
2026-09-12 15:20:23 -06:00
vicrodh 74e1610433 feat(orbit): add shared library foundations and gated host tools
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
2026-09-12 13:02:29 -06:00
vicrodh 743d68fda4 fix(ci): give the early-exit smoke fixtures a window a slow runner cannot miss
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
2026-09-11 14:43:01 -06:00
vicrodh 1ff6df5bac merge: DLNA service-URL tolerance and fatal-signal diagnostics (#745)
cc
2026-09-09 22:48:14 -06:00
vicrodh 2181b379bb merge: restore macOS Exclusive mode row and cascade (#748)
cc
2026-09-09 22:44:16 -06:00
vicrodh e58ec85eb2 fix(qt): restore the Exclusive mode row on macOS (#748)
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
2026-09-09 22:44:16 -06:00
vicrodh 2fc03d325d fix(qt): restore window lifecycle and improve grid artwork
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
2026-09-09 21:58:25 -06:00
vicrodh 75b8eee1c6 feat(media): add Jellyfin Quick Connect and harden remote connections
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
2026-09-09 17:12:45 -06:00
vicrodh 17e08e93de fix(library): restore offline visibility and unify filters and sorting
Recover missing offline library entries, recognize cached-only libraries, and refresh availability after cache changes. Unify per-tab filters, compact the toolbar, and add reversible sort criteria with complete available purchase metadata.

cx
2026-09-09 17:08:33 -06:00
vicrodh 55ddc2c768 fix(qt): make mouse wheel scrolling predictable
Accumulate wheel distance with short easing instead of kinetic acceleration. Preserve rapid and fractional wheel input, immediate direction changes, nested scroll chaining and scroll-memory takeover.

Validated with 38 Qt Quick tests and desktop smoke testing.

cx
2026-09-08 22:19:32 -06:00
vicrodh 0e76c1f486 fix(qt): preserve navigation shortcuts and release text input focus
Keep search, settings and link shortcuts usable while editing, dismiss focus on outside clicks and navigation, and guard deferred focus against hidden inputs. Add Qt Quick regression coverage.

Refs #753

cx
2026-09-08 21:40:28 -06:00
vicrodh 0b6b111c74 fix(qt): correct playback commands and library browsing
Handle explicit MPRIS Play/Pause without toggling paused playback, remove the 10,000-favorite cutoff, and sort albums by original release dates. Isolate Library filters by tab, include purchased release dates, and support server-ranked artist popularity with safe pagination.

Validated with 766 Qt tests, QML checks, release-sort regressions, and a release build. Refs #750, #751.

cx
2026-09-08 18:27:51 -06:00
vicrodh 3aed91fa0a fix(cast): localize UPnP URL compatibility and harden crash diagnostics
Keep http 1.5 and tolerate empty or relative service URLs in a local rupnp patch. Cover standard DLNA SOAP requests and malformed descriptions.

Avoid logger locks in fatal handlers, flush emitted log batches, and preserve CI symbols after smoke failures.

cx
2026-09-08 12:08:53 -06:00
vicrodh a7629dc962 feat(log): report fatal signals instead of dying as a bare status
The packaged AppDir has an intermittent SIGSEGV during startup, in the
window between QGuiApplication construction and the QML engine coming up,
with the tokio workers, the audio thread, the loudness analyzer, the
visualizer producer and connectivity all running beside it. It hit twice
on 2026-09-08 on unrelated commits: the v2.1.0 tag's own release-flatpak
attempt 1, which then passed on a re-run of the identical commit, and the
#745 bugfix branch. Both left the same evidence - "status -11" and a log
that stops mid-startup - which is nothing to act on, and a user who hits
this has no re-run to reach for.

The process now reports its own death. A handler for SIGSEGV, SIGBUS,
SIGILL, SIGFPE and SIGABRT writes the signal, the faulting address, the
thread and a backtrace to stderr and flushes the file sink, so the next
occurrence names a frame instead of costing another round trip. It runs
on a dedicated signal stack so a stack-overflow fault can still report,
latches against re-entry, and arms alarm() BEFORE capturing anything:
taking a backtrace allocates and takes loader locks, so a fault inside
malloc could otherwise deadlock the handler and turn a crash into a hang.

The signal is re-raised with the default disposition restored, so the
wait status stays the signal and CI, the smoke harness and any core dump
still see a crash. The integration test asserts exactly that pair on a
child segfaulted on purpose, and it is a named gate in test-crates and
its local mirror.

qbzd installs it too: a headless daemon that dies of a signal leaves even
less behind than the GUI.

cc
2026-09-08 09:51:12 -06:00
vicrodh a995025f4e fix(cast): pin http to 1.4.0 so DLNA renderers stop vanishing
rupnp parses a device description's SCPDURL / controlURL / eventSubURL as
http::uri::PathAndQuery. http 1.5.0 rewrote scan_path_and_query and now
rejects two shapes 1.4.0 accepted: an empty element (ErrorKind::Empty,
printed as "empty string") and any relative URL without a leading slash
(PathDoesNotStartWithSlash). Both are common in old UPnP firmware, so the
transitive bump made rupnp drop those renderers during discovery.

Measured in the lockfiles: v2.0.2 resolved http 1.4.0, v2.1.0 resolved
1.5.0, and nothing in qbz-cast changed between them. A KEF LSX gen 1 that
worked in 2.0.2 reported "Visible Renderers: 0" in 2.1.0 with
"Invalid response: empty string", which is exactly
InvalidResponse(InvalidUri(Empty)) reproduced by the new test.

qbz-cast takes http as a direct dependency at "=1.4.0" so cargo unifies
the workspace on it, and device.rs now uses that crate instead of the
rupnp re-export. The new integration test serves two renderer
descriptions over loopback, one with empty and one with relative service
sub-URLs, and requires both to parse: it is red on 1.5.0 and green on
1.4.0, so it fails loudly if the pin is ever lifted. test-crates and its
local mirror gain the named gate.

Closes-when-confirmed: #745

cc
2026-09-08 08:37:20 -06:00
vicrodh 06770cfd4c fix(build): qt-cargo.py runs cargo via subprocess, not os.execvpe
os.exec* is emulated on Windows (spawn + terminate the caller) and crashed
the release-windows build: it died with "Segmentation fault ... exit code
139" immediately after the wrapper printed the SDK fingerprint, before cargo
produced any output. The POSIX Linux and macOS builds using the same wrapper
were unaffected, which localised it to the Windows exec emulation.

Spawn cargo with subprocess.run and propagate its exit code. Portable across
all three targets; cargo output still streams through the inherited stdio and
CXXFLAGS/QMAKE plumbing is unchanged. Verified locally: the wrapper reaches
cargo and returns its status.

cc
2026-09-07 15:58:21 -06:00
vicrodh 31da9c57bb fix(qt): kiosk feedback round — chrome, search, album, artists, lyrics
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
2026-09-07 12:33:28 -06:00
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
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
2026-09-07 08:23:11 -06:00
vicrodh 21e81a4b42 fix(ci): reject early exits in packaged Qt startup smokes
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
2026-09-06 19:24:15 -06:00
vicrodh 8f2050d703 fix(sacd): support images with 2064-byte physical sectors
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
2026-09-06 17:29:06 -06:00
vicrodh 0ce4e1fa04 fix(local): publish ephemeral sessions before requesting navigation
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
2026-09-06 15:21:26 -06:00
vicrodh f9578d042c fix(navigation): include local library tabs in history
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
2026-09-06 14:34:41 -06:00
vicrodh 9c9424bd30 fix(startup): keep Qt from retrying an unresponsive session bus
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
2026-09-06 12:13:34 -06:00
vicrodh 9fd9978be2 fix(windows): bundle and audit app-local MSVC runtime
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
2026-09-06 04:54:40 -06:00
vicrodh 6794e15257 fix(build): invalidate native objects when the Qt SDK changes
Fingerprint Qt SDK headers for C++ cache inputs and share bounded debug/release startup gates between local tooling and CI.

cx
2026-09-06 03:53:05 -06:00
vicrodh 39ba3282f5 fix(ci): eliminate test-crates build warnings
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
2026-09-05 20:31:05 -06:00
vicrodh 9b3b52cf9c fix(qconnect): preserve controller state across renderer handoffs
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
2026-09-05 18:37:07 -06:00
vicrodh 4bb4982312 feat(sacd): support raw and DST-compressed ISO playback
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
2026-09-05 10:00:24 -06:00
vicrodh 0d1cdb5e89 feat(packaging): complete Linux release assets
cx
2026-09-04 09:06:02 -06:00
vicrodh 56059e6814 feat(packaging): add complete AUR package set
cx
2026-09-04 08:27:17 -06:00
vicrodh f833a5a401 fix(tooling): audit the active Qt tree by default
cx
2026-09-03 09:34:31 -06:00
vicrodh e235cd3e23 test: require the account-migration and portable-blacklist suites in the gate
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
2026-09-02 08:25:09 -06:00
vicrodh 1cb988954e fix(qt): rename on-prefixed QML properties and gate the pattern
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
2026-09-01 23:40:33 -06:00
vicrodh 860f1ad67f ci(linux): the AppDir check no longer expects .DirIcon (appimagetool writes it)
cc
2026-08-28 21:10:49 -06:00
vicrodh 8f35809df7 ci(release): desktop-entry icon gate; Icon Composer app icon on macOS
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
2026-08-28 20:45:55 -06:00
vicrodh c17f932d6c test: require the listen_log suite in the gate
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
2026-08-28 20:30:43 -06:00
vicrodh bf710fb7eb ci(qml): share qmllint gate across Linux and Windows
cx
2026-08-28 09:44:25 -06:00
vicrodh bc4e8e6d7a fix(ci): make Windows preflight executable
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
2026-08-28 09:44:25 -06:00
vicrodh a712284306 ci: shader gate bakes with qsb >= 6.9 and mirrors build.rs's -b rule
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
2026-08-27 11:11:50 -06:00
vicrodh 811f6e978c ci: test-crates uses all cores (jobs=2 was a Slint-era memory tier)
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
2026-08-26 20:22:17 -06:00
vicrodh f53543d1a4 ci: isolate the offscreen boot (private bus, private XDG dirs)
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
2026-08-26 20:14:23 -06:00
vicrodh ce57551a71 ci: test-crates as a living gate with cargo-test.sh as its exact mirror
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
2026-08-26 19:11:58 -06:00
vicrodh af8d373d2c ci: shader bake gate; audits find Qt's qmltypes; builds never re-bake
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
2026-08-26 19:11:01 -06:00
vicrodh dd6527043b ci: only test-crates runs from pre-release; drop the Slint excludes
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
2026-08-26 19:11:01 -06:00
vicrodh 7bd5cb1ec6 scripts: aarch64 build scripts for the Qt binary
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
2026-08-26 19:11:01 -06:00
vicrodh 077e5fd97e ci: qt gate in test-crates; ship the QML audits in the repo
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
2026-08-26 19:11:01 -06:00
vicrodh ffc2d7d664 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
2026-08-20 21:15:10 -06:00
vicrodh 3cfce8a769 feat(qt): the media-server settings panel, driven and verified in the window
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
2026-08-20 10:57:28 -06:00
vicrodh 931cb06d38 feat(qt): ease the visualizers on the pulse, and make Plasma react
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
2026-08-19 13:53:57 -06:00
vicrodh a6b373cb5a build(qt): fail the build on a tab body that is built while hidden
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
2026-08-17 17:49:22 -06:00