fix(display): #148 — enforce dock intent without the auto-profiles toggle
All checks were successful
Check / eval (push) Successful in 3m37s

Every nomarchy-home re-lit eDP-1 inside the shut lid: HM activation fires
hyprctl reload, the catch-all monitor rule re-enables the panel, and the
tick invariant built for exactly this (#142, enforce_dock_intent) never
fired — it was gated on settings.displayProfileAuto, the *named-profile*
auto-switch toggle, which is unrelated to dock intent and off unless
opted into. Confirmed live on the incident machine: dockMode=true,
displayProfileAuto unset, watcher journal empty — every tick returned at
that guard.

Fix: drop the auto_on gate. settings.display.dockMode alone is the
user's recorded intent (written by the menu and the dock transition);
the profile-active check remains as the real guard (a named profile
outranks the dock heuristic). No fight with the #142 undock hammer:
undock clears the intent before its rescue reload, so the enforcer
stands down by ordering.

tools/monitor-fallback.nix grows the reload-mid-dock scenario: fake lid
closed, hyprctl reload, panel re-disabled within ~4s, action=re-dock
logged — under displayProfileAuto's default false, i.e. the exact
condition the bug shipped under.

Verification: V2 — nix flake check --no-build green, checks.docking-ux
green, monitor-fallback harness green three times (implementer twice,
reviewer once, ~71s VM). V3 pending: relogin + nomarchy-home while
docked-clamshell (HARDWARE-QUEUE; the watcher is exec-once, the fix
arms at next login). Implementation by a Sonnet subagent; diagnosis,
design and review on Fable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 07:48:05 +01:00
parent e324876eb7
commit 5479ade702
6 changed files with 78 additions and 34 deletions

View File

@@ -124,6 +124,28 @@ pkgs.testers.runNixOSTest {
assert mons()[0].get("focused") is True, mons()
print("dock transition: both workspaces on DP-1, internal disabled")
# #148: every home-manager activation fires `hyprctl reload`, which
# re-reads the generated config's catch-all `monitor = ,preferred,auto,1`
# and would re-light Virtual-1 even though the session is still docked
# with the (simulated) lid shut. `enforce_dock_intent` the tick-driven
# invariant in the watcher is what is supposed to put it back off.
# Before the #148 fix it also required `settings.displayProfileAuto`
# (the *named-profile* auto-switch toggle, unrelated to dock intent), and
# this harness never sets that it stays at its schema default `false`
# which is exactly the condition the bug shipped under: the invariant
# silently never fired for any session that had not separately opted
# into auto profiles. Re-assert the lid is shut (it already is, from the
# tmpfiles seed, but do so explicitly since this scenario depends on it),
# reload, and prove the watcher re-disables the panel and logs the
# re-dock action.
machine.succeed("printf 'state: closed\\n' > /run/nomarchy-test-lid-state")
machine.succeed(hy + "reload'")
machine.wait_until_succeeds(
hy + "-j monitors' | jq -e 'all(.[]; .name != \"Virtual-1\")'", timeout=15
)
machine.succeed("journalctl -t nomarchy-display-watch | grep -q 'action=re-dock'")
print("reload-mid-dock: panel re-disabled by enforce_dock_intent; re-dock logged (#148)")
# QEMU's synthetic headless backend aborts Hyprland if its last active
# output is deleted while the DRM output is disabled. Exercise the exact
# production recovery primitive first, then remove DP-1: the real