Files
qbz/.github/workflows/test-crates.yml
T
vicrodh b3aa992954 feat(qt): a keyed list model whose rows change instead of being replaced
Handing an item view a new JS array is a model swap: every delegate is
thrown away, the scroll offset resets and the page is built again.
QbzKeyedModel takes the same arrays and reconciles them by key into
removes, inserts, moves and in-place updates, so a view keeps its
delegates, its offset and its artwork, and a leaving row can fade out
while its neighbours slide into the gap.

Each model row carries only `rowKey` and `rowRev`; a delegate looks the
row up by key, so it reads the host's own JS object (no copy into a
ListModel map) and a rev bump hands it a changed row. New objects for an
existing key are compared field by field, so a republished document with
the same data touches no delegate. A reorder keeps the longest in-order
run steady and moves only the rest; past a cap, and on any scope change
(tab, sort, search), the rows are replaced without animation, laid out
synchronously and started from the top, like the swap they replace.

Three shared transitions (add, remove, displaced) give every adopter the
same motion. The remove transition restores opacity and scale as its last
step: the view pools a delegate the moment the transition ends and hands
it back as it was left, which measured 76 invisible rows after three
removals and a scroll.

tst_keyed_model covers order and identity under 400 random edits, the
one-move reorder, silent resets, a removal that keeps the viewport and
creates at most two delegates, the fade that keeps its content, and the
pooled rows; it runs in the runtime gate.

cc
2026-09-14 05:37:31 -06:00

285 lines
12 KiB
YAML

# CI for the crates/ workspace on the integration branch.
#
# Playback memory presets: shared persistence/migration, live budgets and growth;
# Qt profile descriptions/selection are covered by the shared QML runtime gate.
# Three jobs, three economies:
# test — validate all eight gettext catalogs, then `cargo test` on
# every crate that needs no Qt (scripts/cargo-test.sh:
# excludes the frozen Slint crates and qbz-qt), then the named
# listen-log suite check (qbz-app::listen_log must exist and be
# green — the per-user listen store's schema, accumulator, end
# reasons and pause/clear rules), and the account-migration +
# portable-blacklist suite check (Settings > Import / Export:
# snapshot/plan/apply/ledger/local copy, additive blacklist
# merge), plus QConnect controller handoff/read-vs-write,
# proto3-zero cursor, manual skip/loop/shuffle/autoplay and
# consecutive-log repetition regression checks, plus SACD
# 2048/2064-sector TOC/frame/seek equivalence and scanner import/
# failed-read preservation, plus the DLNA device-description
# tolerance suite (empty and relative SCPDURL/controlURL must
# still parse with current http — local rupnp patch, #745).
# Normal-renderer v1/v3 transport/volume SOAP is also gated.
# ~5 min cold.
# qt-gate — the Qt frontend: the five static QML audits, `cargo test -p
# qbz-qt`, the Slint-free dep-graph gate, and an OFFSCREEN BOOT of
# debug AND release binaries (including process liveness).
# Release also boots on Xvfb with a deliberately silent D-Bus.
# Node executes the actual Local Library filter/tab expressions.
# This is the thing `cargo check` cannot give:
# a missing QML component or a call to a bridge member that does
# not exist compiles clean and throws when the user opens the
# view. Qt comes from aqt — ubuntu-24.04's own Qt is 6.4.2 and has
# no QtQuick.Effects module.
#
# Rewritten 2026-08-25 for the Slint -> Qt frontend switch. Release packaging
# stays tag/dispatch-only; this workflow also tests optimized startup on
# pre-release
# integration so regressions land before a release cut.
# Shared cargo-test.sh also gates signed updater verification, version selection
# and preference preservation; qt-runtime-gate.sh covers the update UI.
# Shared cargo-test.sh also gates Orbit HTTP host isolation, library profiles
# and the SSE public-identity projection. No separate test command to drift.
# Shared cargo-test.sh also gates the 2026-09 static-review regressions: image-cache
# LRU eviction, loudness-cache degrade, live-writer log rotation lock, URL-free
# CDN fetch errors + signed-param redaction, and the link-resolver timeout.
# Loudness (2026-09): absolute-LUFS cache with ranked sources, full-track scan,
# one-shot live gain with a 400 ms glide, and the player's start-gain plan.
# Qt runtime gate includes Library source/count regressions and the Rust
# playlist-deletion/creation/offline-gapless and maintenance retry regressions.
# The shared library service gate preserves manual scan feedback while keeping
# background completions silent, including coalesced work and unavailable roots.
# Watcher read-feedback and no-op content invalidation are in cargo-test.sh;
# Library folders status/action layout and tooltips are in qt-runtime-gate.sh.
name: test-crates
# Shared playback-cache gate covers #774 sealed buffers, Hi-Res decode continuity,
# memory policy admission/pressure, persistence and qbzd settings round trips.
# Purchases navigation placement matrix; missing-root watcher retry/recovery.
# Disk-backed streaming, 24/192 seek, cache reader leases and cancelled prefetch.
on:
pull_request:
# pre-release is the real base; main is listed so a PR opened against main
# still runs once before redirect-pr-to-prerelease retargets it.
branches: [pre-release, main]
paths:
- "crates/**"
- "scripts/cargo-test.sh"
- "scripts/install.sh"
- "scripts/test-installer.py"
- "scripts/test_qt_library.mjs"
- "scripts/test_qt_purchases_navigation.mjs"
- "scripts/qt-*.py"
- "scripts/qt-*.sh"
- "scripts/test_qt_build_gates.py"
- "scripts/packaging/windows_crt.py"
- "scripts/packaging/test_windows_crt.py"
- "scripts/packaging/smoke_release.py"
- "scripts/packaging/test_smoke_release.py"
- "scripts/qml-audits/**"
- "scripts/qml-tests/**"
- "scripts/test-search-local-qml.sh"
- "scripts/test-orbit-qml.sh"
- "scripts/test-updates-qml.sh"
- "scripts/test-playback-cache-qml.sh"
- "scripts/test-text-edit-menu-qml.sh"
- "scripts/test-compact-seek-qml.sh"
- "scripts/test-library-folders-qml.sh"
- "scripts/test-array-model-qml.sh"
- "scripts/test-keyed-model-qml.sh"
- "scripts/test-card-menu-qml.sh"
- "scripts/test-orbit-daemon.py"
- ".github/workflows/test-crates.yml"
push:
# Integration branch only — never main (a push to main is a release merge).
branches: [pre-release]
paths:
- "crates/**"
- "scripts/cargo-test.sh"
- "scripts/install.sh"
- "scripts/test-installer.py"
- "scripts/test_qt_library.mjs"
- "scripts/test_qt_purchases_navigation.mjs"
- "scripts/qt-*.py"
- "scripts/qt-*.sh"
- "scripts/test_qt_build_gates.py"
- "scripts/packaging/windows_crt.py"
- "scripts/packaging/test_windows_crt.py"
- "scripts/packaging/smoke_release.py"
- "scripts/packaging/test_smoke_release.py"
- "scripts/qml-audits/**"
- "scripts/qml-tests/**"
- "scripts/test-search-local-qml.sh"
- "scripts/test-orbit-qml.sh"
- "scripts/test-updates-qml.sh"
- "scripts/test-playback-cache-qml.sh"
- "scripts/test-text-edit-menu-qml.sh"
- "scripts/test-compact-seek-qml.sh"
- "scripts/test-library-folders-qml.sh"
- "scripts/test-array-model-qml.sh"
- "scripts/test-keyed-model-qml.sh"
- "scripts/test-card-menu-qml.sh"
- "scripts/test-orbit-daemon.py"
- ".github/workflows/test-crates.yml"
workflow_dispatch:
concurrency:
group: test-crates-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Same pin as build-qt-linux.yml (the Linux floor). Bump both together.
QT_VERSION: ${{ vars.QT_VERSION_LINUX || '6.8.3' }}
jobs:
test:
name: cargo test (workspace, no UI)
runs-on: ubuntu-24.04
# The rule: --no-fail-fast, 20 minutes. All cores: measured cold on an
# equivalent 4-vCPU VM, 392 s (jobs=4) vs 589 s (jobs=2), 2.7 GB peak —
# the old jobs=2 was a Slint-era memory tier with nothing left to protect.
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install system dependencies
run: |
# Non-UI crates still link ALSA / JACK / D-Bus (qbz-audio) and
# OpenSSL (reqwest).
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential pkg-config gettext dbus-daemon desktop-file-utils \
libasound2-dev libjack-jackd2-dev \
libdbus-1-dev libssl-dev
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
shared-key: test-crates-linux
cache-on-failure: true
save-if: ${{ github.ref == 'refs/heads/pre-release' }}
- name: cargo test (scripts/cargo-test.sh — the local mirror)
env:
CARGO_TERM_COLOR: always
run: |
rustc -V; cargo -V
START=$(date +%s)
./scripts/cargo-test.sh
echo "cargo test wall time: $(( $(date +%s) - START ))s"
# Every shader must bake. Separate job so qsb is never on PATH while cargo
# runs (see qt-gate). The BAKE toolchain is pinned apart from the runtime
# floor: the committed .qsb need qsb >= 6.9 (plasma/ambient's uint hash
# constants vs the GLSL 1.20 variant, which desktop GL <= 3.1 selects at
# runtime, so it must stay in the pack) — see shader_bake_gate.sh.
shader-gate:
name: shader bake gate
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
QT_SHADERTOOLS_VERSION: ${{ vars.QT_VERSION_SHADERTOOLS || '6.9.3' }}
steps:
- uses: actions/checkout@v7
- name: Install Qt ${{ env.QT_SHADERTOOLS_VERSION }} + shader tools
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_SHADERTOOLS_VERSION }}
host: linux
target: desktop
arch: linux_gcc_64
modules: qtshadertools
cache: true
- name: Bake every shader to a temp dir
run: scripts/qml-audits/shader_bake_gate.sh "$QT_ROOT_DIR/bin/qsb"
qt-gate:
name: qt gate (audits, tests, offscreen boot)
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential pkg-config cmake clang libclang-dev lld nasm dbus \
libasound2-dev libjack-jackd2-dev libdbus-1-dev libssl-dev \
libgl1-mesa-dev libegl1-mesa-dev libxkbcommon-dev libwayland-dev \
xvfb nodejs libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 \
libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0
# cxx-qt prefers LLD when present; without it, Ubuntu falls back to
# the deprecated gold linker. Do not add workspace-wide RUSTFLAGS.
ld.lld --version
# No qtshadertools here ON PURPOSE: with qsb on PATH build.rs re-bakes
# shaders by mtime, which makes a fresh checkout nondeterministic. The
# build explicitly validates/uses committed .qsb; shader health is the
# shader-gate job, independent of tools accidentally present on PATH.
- name: Install Qt ${{ env.QT_VERSION }}
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: qmllint (QML type check)
run: scripts/qml-audits/qmllint_gate.sh "$QT_ROOT_DIR"
# The audits need Qt's own plugins.qmltypes to resolve QtQuick types —
# hence after the Qt install, pointed at it. ~1 s, and the class of
# mistake nothing else in CI can see (a missing component, a bridge
# member that does not exist).
- name: QML audits
env:
QBZ_QT_QML_DIR: ${{ env.QT_ROOT_DIR }}/qml
run: |
set -euo pipefail
for a in qml_resolution_audit qml_singleton_xref qml_eager_tab_audit qml_module_registration_audit qml_icon_bake_audit qml_on_prefixed_property_audit; do
echo "::group::$a"
python3 "scripts/qml-audits/$a.py" "$PWD/crates/qbz-qt"
echo "::endgroup::"
done
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
shared-key: qt-gate-${{ env.QT_VERSION }}
cache-on-failure: true
save-if: ${{ github.ref == 'refs/heads/pre-release' }}
# The exact QT=1 half of scripts/cargo-test.sh, minus its `test` job
# (already run above) and its shader gate (the shader-gate job, so qsb
# is never on PATH while cargo runs here): dep-graph gate, cargo test
# -p qbz-qt, Local Library/Kiosk artwork/navigation and federated-search
# view/activation regressions, debug/release
# offscreen boot. One shared runtime script.
- name: qt gates (scripts/cargo-test.sh QT=1, without the workspace test)
env:
CARGO_TERM_COLOR: always
QBZ_QT_QML_DIR: ${{ env.QT_ROOT_DIR }}/qml
QBZ_PREBUILT_SHADERS: '1'
run: |
set -euo pipefail
hits=$(cargo tree --manifest-path crates/Cargo.toml -p qbz-qt -e normal \
| grep -E '\b(slint|qbz-ui|qbz-slint-common|qbz-dac-wizard) v' || true)
[ -z "$hits" ] || { echo "::error::qbz-qt graph resolves Slint crates:"; echo "$hits"; exit 1; }
bash scripts/qt-runtime-gate.sh