Files
Nomarchy/agent/HARDWARE-QUEUE.md
Bernardo Magri bec826baf0
All checks were successful
Check / eval (push) Successful in 3m10s
fix(docking): the undock keyword is inert, not raced; escalate to reload
Round 7 FAILED: 5-6 consecutive unplugs on the dev box, the panel never came
back. Round 6's retry could not have worked, because round 6's diagnosis was
wrong — and so was the "phantom re-add" theory I brought to this session.

With ZERO enabled outputs — panel disabled by the dock, external gone —
Hyprland 0.55.4 accepts `hyprctl keyword monitor eDP-1,preferred,auto,1`,
prints `ok`, exits 0, and never flushes it: the rule waits for a DRM event
that is not coming. It is inert, not raced. Retrying it 25x/poll for 6 polls
bought 30s of black screen and nothing else.

What made this survive two rounds is worth more than the fix: every
`transition=undock result=ok` in the round-6 journal was Bernardo plugging
the cable back in because the screen was black. His hotplug flushed the
queued rule and the next poll took the credit. Success was indistinguishable
from the user working around the failure, so the logs confirmed whichever
story we brought to them. Ten minutes of probing on hardware killed both.

Probed live 2026-07-14 (dev box, cable out): keyword inert across 4s and 5s
in two runs; `dispatch forcerendererreload` inert too; only `hyprctl reload`
escapes — 99ms and 289ms. So: issue the keyword (enough whenever another
output is still enabled, e.g. the menu's Dock mode), and escalate to reload
only when `monitors` proves it inert. After a reload, re-assert the rule so a
config that parks the panel off cannot undo the undock, and restore per-device
keyboard layouts — a reload re-reads the config and drops the runtime
`device[<name>]:kb_layout` an external board depends on. The menu's `enable`
now proves itself against `monitors` too, instead of cheering for an exit code.

Verified V3 (partial): the fixed transition driven through a real unplug —
panel on, workspaces 1-3 home, 1.8s, `keyword=inert escalate=reload` ->
`enable=via-reload` -> `result=ok`. V2: nix flake check --no-build; docking-ux
+ dock-audio; monitor-fallback.nix; shellcheck clean on display-transition.

NOT proven, and queued as HARDWARE-QUEUE round 8: the watcher-driven path
(exec-once + baked store path = relogin required, the round-5 trap) and
repetition. The keyboard-restore path is untested — the dev box's keyboard
hangs off the dock's hub, so it leaves with the cable; round 8 adds a check
with a keyboard in the laptop. The VM harness still cannot reach any of this:
QEMU aborts Hyprland if the last active output is deleted while the DRM output
is disabled, which is the same zero-output degeneracy from the other side.

New BACKLOG #114 (PROPOSED): tuigreet ignores per-device keyboard layouts —
a VT has one keymap, so the greeter cannot honour `device[]:kb_layout`.
Bernardo hit it logging out while docked; not a regression, a design gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:28:06 +01:00

28 KiB
Raw Blame History

Hardware queue — V3 checks only a human can run

Everything shipped at V1/V2 whose final verification needs real hardware. Agents append (newest at the bottom of a section) with exact steps; Bernardo runs them and either checks off ([x] + date + verdict) or files the failure as a NOW bug in BACKLOG.md. Checked-off entries are pruned on the next sync sweep (LOOP.md §5) — outcomes live in the journal/ROADMAP; git history is the archive. Machines: the AMD dev box (Ryzen AI laptop: AMD + fingerprint + NPU), the Latitude 5310/5410 (Intel QA), the T14s (webcam case).

Any machine (dev box is fine)

  • #96 battery-limit row on threshold-less firmware (Acer M5-481T) — on the Acer (live or installed session with the commit carrying this entry): open System (SUPER+CTRL+I). Pass: a "Battery limit" row IS listed (battery present); picking it shows the notification explaining the firmware exposes no charge-stop control (not a raw error, and the row never silently disappears). On the T14s the row must still open the working preset picker.

  • #101 charge-limit service survives USB-C power-event bursts (round 2) — round 1 FAILED on TuringMachine 2026-07-13 18:16: a spaced storm (each run finished before the next AC event) landed 5 successful starts in 10s → start-limit-hit, unit marked failed, doctor badge — although every run succeeded. Fixed by exempting the unit from start rate limiting (StartLimitIntervalSec=0) + a spaced-storm guard in checks.battery-charge-limit (the commit carrying this entry). Re-check: after pulling/rebuilding this commit, sudo systemctl reset-failed nomarchy-battery-charge-limit .service, then plug/unplug the powered USB-C dock several times in quick succession. Pass: systemctl is-failed nomarchy-battery-charge-limit.service stays false, Result=success, BAT0's charge_control_end_threshold still matches the limit, and the journal has no new start-limit-hit/Failed with result lines.

  • Docking recovery round 8 (undock panel restore, 2026-07-14) — round 7 FAILED on the AMD dev box (Bernardo, 56 consecutive unplugs, none recovered). Its whole diagnosis was wrong, and the way it was wrong is the lesson: round 6 read result=enable-timeout ×2 + result=ok ×1 as an intermittent race and retried the keyword. But the keyword is inert, not raced — with ZERO enabled outputs (panel disabled by the dock, external gone) Hyprland 0.55.4 accepts keyword monitor (prints ok, exits 0) and never flushes it until a DRM event arrives. Retrying an inert command 25×/poll for 6 polls just bought 30s of black screen. Every result=ok in that journal was Bernardo plugging the cable back in — his hotplug flushed the queued rule and the next poll took the credit. Probed live 2026-07-14: keyword inert across 4s and 5s in two runs, dispatch forcerendererreload also inert, and only hyprctl reload worked — 99ms and 289ms, cable out. The transition now escalates to reload when the keyword proves inert, re-asserts the rule, and restores per-device keyboard layouts (a reload drops runtime device[…]:kb_layout keywords). Already proven on hardware: one real unplug, invoking the fixed nomarchy-display-transition undock eDP-1 directly with the watcher paused — panel on and workspaces 13 home in 1.8s, journal keyword=inert escalate=reloadenable=via-reloadresult=ok. What is NOT proven, and is this round: the watcher-driven path (the running watcher calls the transition by baked store path, so it only picks the fix up at relogin — the round-5 trap), and repetition. Re-check: relogin (mandatory — see below), dock, then unplug the cable at least five times, plus once with the lid shut and once while an external keyboard is plugged into the laptop (not the dock's hub) — that last one is the only check on the keyboard-restore path. Pass: the panel comes back every time with workspaces intact, the external keyboard keeps its remembered layout, and journalctl --user -t nomarchy-display-transition -t nomarchy-display-watch --since '-10 min' shows result=ok for each undock with no result=enable-failed. keyword=inert escalate=reload on every undock is EXPECTED — that is the fix firing, not a fault. A result=ok without a preceding keyword=inert line means the keyword flushed on its own and the reload was never needed: interesting, worth reporting, still a pass.

  • Headphone jack-follow (last open count from rounds 46, 2026-07-14) — rounds 46 opened five counts on the AMD dev box; four are now confirmed fixed on hardware by round 6 (Bernardo): wallpaper on the external, automatic dock mode, audio reaching the monitor, and the external keyboard prompting exactly once (was four times). The undock panel is round 7 above. This is the one count never tested: plugging headphones had stopped moving audio to them, because a pinned default.configured.audio.sink outranks the priority rules, which kills jack-follow on UCM cards where the headphones are their own sink (fixed in ce480f3's lineage, unverified). Re-check: docked and undocked, plug and unplug headphones — audio must move to them and back each time, and journalctl --user -t nomarchy-dock-audio must show trigger=jack selected=… on each plug. Worth folding into the round 7 session: the dock is already in hand and the two don't interfere.

    **Historical detail for the four settled counts** (kept only until
    round 7 closes; the fixes themselves are in the log):
    — round 4 was run on the AMD dev box and **failed on four counts**
    (Bernardo): no wallpaper on the external monitor, no automatic dock
    mode (System  Display had to be used by hand), audio staying on the
    laptop speakers while docked, and one dock + one external keyboard
    asking for a layout **four times**. Separately, plugging headphones had
    stopped moving audio to them. Causes and fixes in the commit carrying
    this entry: the display watcher's `socat -T 1` closed Hyprland's IPC
    socket after a second of idle, so a dock plugged into an idle desktop
    lost `monitoradded` in the reconnect gap (wallpaper + dock mode + audio
    all hang off that one event); Hyprland lists every key-capable evdev
    node as a "keyboard"; and a pinned `default.configured.audio.sink`
    outranks the priority rules, which kills jack-follow on UCM cards where
    the headphones are their own sink. **Re-run round 4 below in full**,
    plus: (a) leave the desktop untouched for ~30s, then plug the dock —
    wallpaper, dock mode, and audio must all follow with no manual step;
    (b) the external keyboard must prompt **exactly once**, and never for
    the monitor or the lid/power buttons; (c) docked and undocked, plug and
    unplug headphones — audio must move to them and back each time.
    `journalctl --user -t nomarchy-display-watch` should show
    `outputs-changed added=…`, and `-t nomarchy-dock-audio`
    `trigger=jack selected=…` on a headphone plug.
    
    **Round 5 was itself run and failed (2026-07-14); this entry now covers
    round 6.** Two of the four reports were never actually testing the fix:
    both watchers are Hyprland `exec-once`, so `nomarchy-home` swaps the
    config but leaves the *old* processes running — only `nomarchy-dock-audio`
    (a user service) had picked up new code. **Relogin is mandatory before
    testing either watcher**; confirm with `tr '\0' '\n' < /proc/$(pgrep -f
    bin/nomarchy-display-profile-watch)/cmdline | sed -n 2p` and grep the
    script for `outputs-changed`. The other two were real and are fixed in
    the commit carrying this entry: automatic dock mode did not exist at all
    (the watcher auto-*undocked* but only ever matched display profiles on
    plug, and this box has none saved with `displayProfileAuto` unset, so
    nothing happened — `Dock mode` was interactive-only), and the BenQ's
    audio was unreachable because WirePlumber had `alsa_card.pci-0000_c3_00.1`
    pinned to `pro-audio`, whose raw `pro-output-N` sinks have no ports and
    no routing. The BenQ's own USB card is input-only (all profiles
    `sinks: 0`), so DisplayPort is the only path to its speakers.
    **Also check:** no display profile is saved, yet plugging the BenQ must
    now dock by itself; and `journalctl --user -t nomarchy-dock-audio` shows
    `repaired-card=alsa_card.pci-0000_c3_00.1 profile=HiFi` on the first
    plug, after which audio reaches the monitor's speakers. A saved+matching
    display profile must still win over auto-dock.
    
  • Docking recovery round 4 (#100, closed-lid BenQ sequence) — after updating to the commit carrying this entry, run nomarchy-home and relogin. With the lid open, put visible windows on at least workspaces 1, 2, and 3 and start audio; plug the BenQ GW2790QT. Pass on plug: wallpaper appears, audio toasts and moves to a BenQ/GPU HDMI sink, and journalctl --user -t nomarchy-dock-audio -n 10 shows trigger=monitoradded plus selected=… (not only a card event). Open System Display Dock mode. Pass on dock: every workspace and window is on the BenQ, focus follows, eDP is disabled in hyprctl monitors all -j, and systemd-inhibit --list shows Nomarchy holding handle-lid-switch for “Safe dock/undock display transition”; there is no dangling laptop workspace and no application exits. Close the lid; audio/session must stay awake. While still docked, manually choose the speakers and change volume/mute: it must stay on speakers. Unplug the BenQ with the lid still closed, wait 10 seconds, then open it. Pass on undock: no suspend/crash/hard reset, eDP is already active, workspaces 1/2/3 and their windows are intact on it, and the inhibitor disappears only after lid-open. journalctl -b --since '-5 min' must contain no cable-removal systemd-logind: Suspending.... Finally close the undocked lid normally: the laptop must suspend, proving policy was restored. Wake it, replug the BenQ: that fresh plug must move audio back to HDMI (manual speakers do not survive a physical replug). If any step fails, capture journalctl --user -t nomarchy-display-watch -t nomarchy-display-transition -t nomarchy-dock-audio --since '-10 min', systemd-inhibit --list, hyprctl monitors all -j, hyprctl workspaces -j, and pactl --format=json list sinks before changing state again.

  • Parallel fingerprint-or-password on the real reader (AMD dev box, 2026-07-12) — with a finger enrolled and fingerprint.pam = true (parallel is the default): sudo -k true must show ONE prompt ("Enter Password or Place finger…"); typing the password immediately works, touching the sensor instead works, wrong-finger ×3 falls back to password, and password keeps working with fprintd stopped (systemctl stop fprintd). Also check hyprlock and the greeter accept both factors and don't wedge on a leftover prompt after a fingerprint win (known cosmetic quirk of the hack — pthread_cancel'd prompt). fingerprint.parallel = false must restore the old sequential behavior. VM already asserts the PAM stack shape + password-only lockout safety with no reader (checks.hardware-toggles).

  • #55 fingerprint enroll on real reader — with nomarchy.hardware.fingerprint.enable and a physical reader: System Fingerprint Enroll a finger; List shows it; Verify succeeds; optional Use for login (on) → nomarchy-rebuild → login/sudo accept fingerprint. Menu surface is V1/V2 without a reader (self-gate + dry paths).

  • #60 non-BAT battery name (if available)* — on a machine whose system battery is not named BAT* (e.g. CMB0): confirm charge-limit oneshot writes the threshold, System Battery limit and Power profile rows appear, Waybar power-profile module shows, doctor charge-limit section runs. On BAT0-only machines this is a no-op (charge-limit re-apply passed on BAT0 — Latitude 2026-07-10).

  • btop theme fidelity (#52 residual) — softGL theme-shot cannot open Ghostty, so the hand btop.theme assets were only guest-file asserted (main_bg / inactive_fg keys) + desktop/bar rendered for rose-pine, everforest, summer-night, vantablack, catppuccin. On a real session: nomarchy-theme-sync apply <slug> then btop and confirm backgrounds/text match the theme (esp. rose-pine Dawn light bg #faf4ed, vantablack near-black #0d0d0d, catppuccin Mocha #1E1E2E). One pass cycling those five is enough.

  • SSH_AUTH_SOCK for GUI clients — after relogin, launch a GUI git client (or rofi-launched terminal-less app) and confirm it reaches gpg-agent's SSH socket without an interactive shell parent.

  • Notification inhibited-state glyph (item 28 color-only sweep, iteration #69) — have an app hold a notification inhibitor (e.g. run a fullscreen video, or busctl --user call org.freedesktop.Notifications … Inhibit); the Waybar bell must switch to the muted bell-off 󰂛 (same glyph as DND), NOT the normal bell 󰂜/󰂚. Then release it → the bell returns. Repeat on a whole-swap bar (summer-day/night use their own bell-off glyph, executive-slate/boreal use 󰂛). The glyph itself already renders (DND uses it); this only confirms swaync emits the inhibited-* class and the bar routes it.

  • Keyboard layout cycle bind — with a comma layout (e.g. nomarchy.keyboard.layout = "us,de"), SUPER+SHIFT+K cycles the focused keyboard's layout, the Waybar 󰌌 indicator follows, and the row shows in the SUPER+? cheatsheet. — 2026-07-04 attempt: no comma layout was configured, so the bind wasn't rendered (the gate working as designed, not a failure) — retest after setting a comma layout + rebuild + relogin. The SUPER+? no-op found en route was a real bug → BACKLOG item 26 (fix shipped; SUPER+? cheatsheet PASS on Latitude 2026-07-10).

  • swaync readable on summer-day (item 25 fix) — on summer-day after home-update: notify-send "title" "body text" shows readable body text; open the control centre (SUPER+N), hover a notification row and check the Clear button — all text legible (body/buttons now @text on tinted chips, not subtext/surface).

  • Bar + rofi legible on flexoki-light (item 27 fix) — switch to flexoki-light: every Waybar module readable (window title, tray row, dimmed inactive workspaces / muted volume visible-but-dim), rofi inputbar/alternate rows show text. Spot-check one dark theme (e.g. tokyo-night) for no visual regression in the same spots.

  • Stub-bar themes got the generated bar (item 28, 90a5104) — switch to catppuccin, lumon, nord and retro-82: each now shows the generated styled bar in its own palette (workspace pill, padded right cluster) instead of the old raw default Waybar. Their rofi menus should look unchanged.

  • Back-audit spot-check (item 24) — the keybinds cheatsheet (SUPER+? / root menu Keybindings) now ends in ↩ Back and returns to the root picker; spot a couple of submenus (Display, VPN Tailscale) still Back correctly after the audit pass.

  • Per-device keyboard menu + hotplug restore (re-verify after in-flake graduation) — update to the current main, run nomarchy-home, and relogin with an external keyboard already connected. Open System Keyboard: both the built-in and external boards must be listed with their current layouts. Pick a different layout for the external board; it must apply only there and persist in settings.keyboard.devices. Unplug/replug it: the saved layout must restore without another prompt and graduate into a device{} block on the next rebuild. Finally plug a previously unseen keyboard in after login: the automatic picker must appear once and remember that choice too.

  • Snapshots restore + rollback exercise (residual — the GUI/polkit half passed on hardware 2026-07-04, a47aa3a): in sudo nomarchy-snapshots, restore a single file (undochange) and walk a root-config rollback up to (or through) the typed-yes gate.

  • Rollback menu (item 9b) — after a couple of theme changes, menu System Rollback: recent desktop generations listed (newest marked current); picking an older one opens a terminal, activates it, and the theme visibly reverts; picking the newer one again rolls forward. The two System rows: Snapshots opens the snapshot flow, "boot an older generation (how)" fires an instruction notification.

  • Open-a-file smoke (viewers + mime defaults, item 8) — after nomarchy-home: from yazi/Thunar, open a PDF (→ zathura, themed to the palette), an image (→ imv, NOT GIMP), a video (→ mpv); xdg-open . on a directory → Thunar; a link → Chromium (chromium-browser.desktop — template default as of 2026-07-10).

  • Update awareness — with nomarchy.updates.enable, let the timer fire (or start the unit): indicator appears only when inputs are behind, notification only on count growth, click opens the upgrade flow.

  • VPN menu live paths — import a real WireGuard .conf and an .ovpn via System → VPN, toggle up/down (● / ○ state), and the Tailscale block: up/down + exit-node without sudo (operator grant). Exit-node rows now show "hostname — Country City" (2026-07-11, header-offset parsing): confirm Mullvad-style nodes carry their location, a locationless self-hosted node stays a bare hostname, and picking either still sets the node (tailscale status shows it).

  • Printer menu — with nomarchy.services.printing, System → Printers opens system-config-printer; add a printer, test page.

  • GRUB UEFI ISO theme render — boot the ISO on UEFI hardware: composed splash background, palette menu in the lower third, accent timeout bar.

  • Visual theme pass — live ISO: all six identity themes (bars) + the four authored rofi .rasi (nord/retro-82/lumon/kanagawa) look right, not just parse.

  • Display profiles, slice a (item 15) — declare two nomarchy.displayProfiles (e.g. docked disables eDP-1 + arranges the externals; undocked re-enables it), home-update, then: System Display Profiles lists them (● marks active); applying is instant (no rebuild) and survives a relogin (the baked overlay); "Base layout" restores; a menu resolution pick made earlier must NOT re-enable a profile-disabled panel after the next rebuild.

  • Display profiles workspace pins, slice c (item 15) — give the docked profile workspaces = { "1" = "<ext>"; "9" = "eDP-1"; }: applying the profile moves open workspaces 1/9 to those outputs immediately, new visits land there too, and the pins survive a relogin (baked workspace rules). Switching to a profile without pins leaves workspaces where they are (stale session pins are expected until reload/rebuild — noted in the applier).

  • Display profiles auto-switch, slice b (item 15) — same setup, then menu Profiles Auto-switch on: plugging the dock/monitor applies the matching profile within ~3s (toast names it); unplugging switches back; Auto-switch off stops that; with two profiles naming the same outputs, no flapping (ties do nothing).

  • P2 retunes eyeball (item 28b) — spot-check on top of the P1 entry below: gruvbox/nord/lumon/everforest/retro-82/white dimmed text (inactive workspaces, muted volume) now visible-but-dim; latte + rose-pine(dawn) + summer-day warn/battery tint reads on the light bases; flexoki-light statuses are the deeper canonical 600s; latte selected-row/alt-accent pink darker. Anything that lost its "dim" feel → reopen 28b.

  • Retuned palettes eyeball (item 28b P1) — switch through summer-day, flexoki-light, kanagawa, miasma: chips/menus/toasts now draw on a raised surface (summer-day's was slate-on-cream, kanagawa's near-black, miasma's pure black), secondary text (tooltips, fastfetch labels) is visible on summer-day + flexoki-light, kanagawa floats are lighter than the bg (upstream sumiInk4). Anything that reads worse than before → reopen 28b.

  • Doctor bar tripwire (LATER item) — with everything healthy the bar shows nothing; systemctl --user start doomed-style induced failure → within ~5 min a red appears (tooltip lists the ✖ lines), clicking opens the sheet in a terminal; reset-failed → it disappears on the next poll. Also visible on both summer bars.

  • OCR region (LATER item) — Tools Capture "OCR region → clipboard": select a region with visible text → toast reports the word count and wl-paste yields the text; Esc in slurp cancels silently; a text-free region toasts "No text recognized" without clobbering the clipboard.

  • Look & Feel submenu (item 19) — root menu: "Look & Feel" (replacing Theme, root still six rows) → Theme grid opens, Next wallpaper cycles instantly, Night light toggles (and is GONE from System). SUPER+T / SUPER+SHIFT+T direct binds unchanged.

  • Launch-or-focus (item 17) — uncomment the template's nomarchy.launchOrFocus firefox example (with firefox installed), home-update + reload: SUPER+B launches firefox when closed, FOCUSES the existing window when open (also from another workspace); with firefox removed the bind fires a "not installed" toast; the row shows under SUPER+?.

  • Themed greeter + console (item 16) — after sys-rebuild + logout: tuigreet renders in theme colors (dark container, accent border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the theme's ANSI palette; on a LUKS machine the passphrase prompt follows too. Then switch theme + sys-rebuild + logout → the greeter follows the new palette. (tuigreet can't run under the VM harness — see MEMORY.md — so rendering is hardware-tier.)

  • Color picker (item 13, final slice) — SUPER+CTRL+P (or Tools Color picker): hyprpicker's zoom loupe appears; click a pixel → toast shows the hex and wl-paste yields it; Esc cancels with no toast. Row present in the SUPER+? cheatsheet.

  • Low-battery toasts (item 13 slice) — on battery, drain past 25%: one "Battery low" toast; past 10%: a critical "Battery critical" toast that stays up until dismissed (swaync); plug in, drain again → it re-notifies; no repeat toasts while it just keeps draining. (Crossing logic VM-verified — this checks the real swaync rendering in a session.)

  • Audio opens in Amberol (iteration #60, item 37) — after nomarchy-home: double-click an mp3/flac/ogg (or xdg-open song.mp3) → it opens in Amberol, not mpv; video files still open in mpv. (xdg-mime query default audio/mpeg → io.bassi.Amberol.desktop.)

  • rofi menu polish (iteration #58, item 39 + #56/#57 34/40) — after nomarchy-home: (a) menu System Power profile shows a colored icon per profile (performance/balanced/power-saver), not just text — icons render (not blank); (b) every submenu's ↩ Back shows a single arrow, no double; (c) on this single-monitor box, System Display → pick a resolution → Back returns to System (not back into the same resolution list).

  • Screenshot annotation (satty) (iteration #73) — after nomarchy-home: hit SUPER+SHIFT+Print (or Tools ▸ Capture ▸ Annotate region) → a region select (slurp) appears, then the satty UI opens in fullscreen with the screenshot loaded. Check that the UI draws on the current theme palette (tools colored properly) and hitting save places the screenshot in ~/Pictures/Screenshots/ while hitting copy places it in the clipboard.

  • #76 no-swap Hibernate notify (#76 itself is closed — the hibernate→resume power-cycle PASSED on TuringMachine 2026-07-12: Bernardo ran the full hibernate → power off → single LUKS unlock → session-restored cycle "flawlessly"; this notify check is the one remaining leftover) — on a Nomarchy machine installed with swap = 0 (or temporarily sudo swapoff -a on one): nomarchy-menu → Power → Hibernate must surface a desktop notification ("Couldn't hibernate — likely no swap is configured. See docs/MIGRATION.md → Enabling hibernation."), not a silent no-op. (swapon after, if you swapoff'd for the test.)

  • Night-light geo mode (2026-07-11) — in home.nix set nomarchy.nightlight = { enable = true; latitude = "<lat>"; longitude = "<long>"; } (your real coordinates), nomarchy-home, relogin. Then: systemctl --user status wlsunset is running (and hyprsunset is NOT); after local sunset the screen visibly warms (or test by setting coordinates where it's currently night); the Waybar moon + menu toggle still flip it instantly and an off survives relogin (the ExecCondition gate on the swapped unit — the eval check proves the wiring, not the runtime gate). Pass = warm shift at the location's night + toggle/persistence intact.

AMD dev box only

  • AMD runtime bits — VA-API (vainfo → radeonsi), amd-pstate EPP active and PPD switching governors; opt-ins: ROCm (rocminfo, a GPU PyTorch/Ollama smoke) and the XDNA NPU driver loading.
  • Fingerprintfprintd-enroll + (opt-in PAM) login/sudo.
  • System ▸ Firmware menu on real LVFS hardware (item #43, nomarchy-menu firmware) — on a machine whose firmware/SSD/dock is on LVFS: open Menu ▸ System ▸ Firmware; confirm the terminal runs fwupdmgr refresh → lists real get-updates → the y/N confirm gates correctly → fwupdmgr update applies and prints the reboot note when a capsule needs one. Verify no flash happens on "N"/cancel. (VM only proves the menu row + flow renders; a real capsule write is hardware-only.)

Latitude 5310 / 5410 only

  • v1 QA batch on-hardware pass (583708d batch was QEMU-verified) — general smoke before the next main → v1 promotion (broader than the 2026-07-10 session; include theme/ISO/greeter leftovers).

T14s only

  • Webcam IR-hide end-to-end on Nomarchy — installer detects the RGB+IR pair, bakes hardware.camera.hideIrSensor; wpctl status shows one colour source; an app picker lists one camera; Howdy-style direct /dev/video2 reads still work.
  • Portal/Flatpak libcamera IR (b)/(c) — after #71 docs: on hardware, confirm a Flatpak/portal picker still lists the internal IR node; only then investigate (b) WirePlumber libcamera GREY-only monitor rule or (c) libcamera/udev-layer hide. Do not ship either without a live dual-sensor check (see HARDWARE.md §7 / ROADMAP § Webcam).