fix(docking): make workspace and lid transitions atomic
All checks were successful
Check / eval (push) Successful in 3m19s

Restore the old flake's external-only workspace handoff, protect closed-lid cable removal with a verified low-level logind inhibitor, and use monitor-added as the settled audio reprobe boundary.

Verified: V2 — nix flake check --no-build; docking-ux, option-docs, and template-sot checks; KVM tools/monitor-fallback.nix lifecycle test.

V3 pending: closed-lid BenQ round 4 in agent/HARDWARE-QUEUE.md.
This commit is contained in:
2026-07-13 13:56:54 +01:00
parent cffe432912
commit 2a34c7398b
12 changed files with 654 additions and 292 deletions

View File

@@ -402,14 +402,15 @@ Design/decision records and a running log of shipped work (items marked
built-in. Rules in `modules/nixos/dock-audio-rules.nix`;
`checks.dock-audio` pure + conf-file VM. Real hotplug V3 in
HARDWARE-QUEUE.
✓ **Dock audio round 2 — auto-follow watcher (2026-07-12):** the T14s
✓ **Dock audio round 2 — auto-follow watcher (2026-07-12, superseded by
#100 below):** the T14s
dock test showed priority rules alone can't deliver "just works":
WirePlumber's *stored* default (default-nodes state, written by any
past explicit device pick — every real machine has one) outranks
`priority.session`, so the default never moves on plug. Fix:
`nomarchy-dock-audio` (modules/home/dock-audio.nix,
`nomarchy.dockAudio.enable`, default on) — a `pactl subscribe` watcher
that `set-default-sink`s new dock-class sinks (regex data imported
`nomarchy.dockAudio.enable`, default on) — initially a `pactl subscribe`
watcher that selected new/card-changed dock-class sinks (regex data imported
from dock-audio-rules.nix, single source) + toasts; startup sweep for
login-while-docked (only when the default isn't already dock-class, so
restarts don't override a manual pick). EasyEffects needs nothing: EE 8
@@ -437,7 +438,7 @@ Design/decision records and a running log of shipped work (items marked
DRM is session-to-greeter on the re-lit panel, not a black brick).
Also fixed in the rescue: `grep -c .` exits 1 at count 0 — an `||`-guard on that
pipeline silently disabled the rescue exactly when it mattered.
✓ **Docking hardware-QA correction (2026-07-13):** real clamshell QA
✓ **Docking hardware-QA correction (2026-07-13, superseded by #100):** real clamshell QA
confirmed every warned residual: Rofi's default `monitor=-5` followed
the mouse pointer onto the hidden laptop workspace; the profile watcher
(and blackout rescue) was not launched with default
@@ -445,17 +446,47 @@ Design/decision records and a running log of shipped work (items marked
it, with application loss consistent with the known zero-output crash;
audio did not arrive as a new sink; and the keyboard picker was disabled
by an empty optional-candidate list. Correction: Rofi targets the focused
output (`monitor=-1`); interactive **Dock mode** explicitly moves eDP
workspaces/focus to the external but keeps eDP logically active as the
unplug fallback; the reconnecting display watcher runs unconditionally
output (`monitor=-1`); the first correction's interactive **Dock mode**
moved eDP workspaces/focus but kept eDP logically active as a fallback;
the reconnecting display watcher runs unconditionally
and watches through the racy removal snapshot; keyboard hotplug runs by
default, searches the pinned XKB catalog without D-Bus, persists
per-device, and has a manual System Keyboard path; dock audio handles new sinks and
card/profile/port-availability changes while ignoring volume/default
changes so manual picks stick; and monitor-added re-applies the current
per-device, and has a manual System Keyboard path; that correction's dock
audio reacted to new sinks and card/profile/port changes (replaced by
#100's monitor-event boundary); and monitor-added re-applies the current
awww wallpaper after the output settles (new outputs do not inherit the
existing image). `checks.docking-ux` guards the generated default-session
artifacts; the corrected real sequence remains V3.
✓ **Dock transition recovery (#100, 2026-07-13):** the second T14s pass
exposed why keeping eDP active was not safe: Hyprland requires an active
workspace on every active output, so one workspace necessarily dangled.
Cable removal with the lid closed was independently proven in the previous
boot journal to make logind enter s2idle, explaining the apparent crash.
`nomarchy-display-transition` now follows the old working flake's ordering:
dock is one batch (enable external, move all internal workspaces, focus
external, disable internal); undock enables internal first, waits boundedly,
then moves every workspace back. Named profiles that disable the internal
panel use the same transition when they have an external target.
`nomarchy-display-profile-watch` owns a block-mode, low-level
`handle-lid-switch` inhibitor from external detection through internal
restoration and physical lid-open; it uses process substitution so state
stays in the parent shell, verifies acquisition through logind, kills a
private process group on cleanup, and validates/cleans a stale runtime PID
after an unclean exit. Normal undocked lid-close suspend resumes after
release. Audio now uses `monitoradded` as the fresh-plug intent boundary:
after a debounce and two-second hardware settle it restarts PipeWire,
PipeWire Pulse, and WirePlumber, then explicitly selects the highest-priority
available sink using the original WirePlumber regex data. Generic
`change:sink`/card events never select, so a manual speaker choice sticks;
the subscription loop reconnects after graph restarts. Journal lines record
the trigger and selected/no-candidate result. `checks.docking-ux` syntax- and
invariant-checks generated artifacts; `tools/monitor-fallback.nix` is now a
bounded KVM test for workspace/output/inhibitor transitions and passed V2.
QEMU's headless backend aborts Hyprland if its final active synthetic output
is deleted while the DRM output is disabled, so the harness explicitly runs
the internal-first recovery before deletion and leaves real cable-removal
timing to V3. V3 is queued as round 4; no speculative audio power policy was
added.
✓ **Parallel fingerprint-or-password (2026-07-12, Bernardo promoted
from PROPOSED):** `nomarchy.hardware.fingerprint.parallel` (default
**true** — the better UX is what opting into fingerprint PAM gets you;