diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index b8cb865..549f596 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -197,36 +197,6 @@ high-ROI, etc.) live in the journal + ROADMAP — not here.* ### Product / day-2 -### 148. Every `nomarchy-home` re-lights the laptop panel inside a shut lid - -Bernardo, 2026-07-16, and he hit it twice in ten minutes: after `nomarchy-home` -the laptop panel "turns on again" while docked **lid closed**, and he had to -re-set docking by hand from the Display menu. Then an agent's `hyprctl reload` -silently undid his fix and re-lit it a second time — confirmed live: -`/proc/acpi/button/lid/*/state` = `closed` with `eDP-1` enabled and -`dpms_on=true`, i.e. a panel burning inside a shut clamshell with nobody -looking at it. - -**Cause is a known hammer with no follow-through.** `hyprctl reload` re-applies -every monitor rule and re-enables every output — the ROADMAP already calls this -"the #142 hammer", and the undock transition *relies* on it. But -`nomarchy-display-watch` only reacts to `monitoradded`/`monitorremoved`; a -reload emits neither, so nothing re-asserts the dock profile and the panel just -stays on. Any HM activation reloads Hyprland, so **every rebuild while -docked-clamshell lights the panel** and leaves it lit until the user notices. -Journal for the window is empty — display-watch never saw a thing. - -Cost: wasted power and panel life, and the live display state silently diverging -from the profile the user chose — the exact class of "you have to fix it by -hand" that the display work exists to remove. Fix is probably to re-assert the -dock/clamshell profile *after* a reload rather than only on hotplug: either -watch the lid switch as a trigger in its own right (logind already knows), or -have the transition/activation path re-run the dock decision once the reload -settles. Note the ordering trap — the reload is also the undock rescue, so -whatever re-asserts must not fight the rescue that deliberately re-enables eDP -when the lid is open. - - ### 146. `[watch]` hypridle hangs instead of exiting when its compositor vanishes **Rewritten 2026-07-16, hours after filing: the premise was disproven and almost diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index 557279b..d2c7ca3 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -28,6 +28,17 @@ Everything else below stays open; order is convenience, not a gate. ## Any machine (dev box is fine) +- [ ] **#148 dock-intent enforcement without auto-profiles (dev box, docked)** — + after pulling the #148 commit: **log out and back in first** — the + watcher is `exec-once`, so `nomarchy-home` alone leaves the old one + running. Then, docked clamshell (lid closed, panel off, dockMode set + from the Display menu or a prior auto-dock), run `nomarchy-home` or a + bare `hyprctl reload`. **Pass:** eDP-1 re-lights for at most ~1–2 s and + goes back off by itself; `journalctl -t nomarchy-display-watch + --since -5min` shows `dock-intent=true … action=re-dock`; a "Docked —" + toast appears. Same relogin answers #149's question (does the Waybar + unit still race Hyprland IPC on a cold relogin without linger?). + **Fail:** the panel stays lit inside the shut lid (the #148 symptom). - [ ] **#115 suspend-then-hibernate (laptop with hibernate/resume wired)** — after system rebuild: Preferences shows **Suspend then hibernate (on)** (hidden if `CanHibernate=no`). Confirm: diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index ff911e6..375c1ae 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,22 @@ Template: --- +## 2026-07-17 — #148: dock intent now enforced without the auto-profiles toggle +- **Task:** BACKLOG #148 (promoted from PROPOSED by Bernardo in-session). +- **Did:** one-line fix — `enforce_dock_intent` dropped its `auto_on || return` + guard (`settings.displayProfileAuto` gates named-profile auto-switching, not + dock intent; with it, the #142 invariant never fired for anyone not opted + into auto profiles — confirmed live on this box: dockMode=true, auto unset, + watcher journal empty). Comment rewritten to record the why. + `tools/monitor-fallback.nix` grew a reload-mid-dock scenario. Implementation + by a Sonnet agent from my design; diagnosis + review here. +- **Verified:** V2 — flake check green, checks.docking-ux green, monitor- + fallback harness green ×3 (coder ×2 + my independent re-run): lid closed → + reload → panel re-disabled ~4s, `action=re-dock` logged. +- **Pending:** V3 queued (relogin + nomarchy-home docked-clamshell; watcher is + exec-once so the fix arms at next login). Rides with #149's relogin check. +- **Next suggestion:** #127 rewrite from the boot -4 forensics; then #134. + ## 2026-07-17 — #147: doctor learns to see machine state the flake never set - **Task:** BACKLOG #147 (promoted from PROPOSED by Bernardo in-session). - **Did:** `nomarchy-doctor` gains a machine-state-drift check — linger as the diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 85b9254..629dd5e 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -493,6 +493,22 @@ Design/decision records and a running log of shipped work (items marked V2 by render, both heads + the password dialog, via the new `tools/plymouth-preview.sh`. V3 (a real docked boot/shutdown) is queued: the fake heads exist from the start, so the rig cannot resize a canvas. +- ✓ **Reload re-lit the panel inside a shut lid (#148, 2026-07-17):** every + `nomarchy-home` (HM activation → `hyprctl reload`) re-applied the catch-all + monitor rule and left eDP-1 burning in the closed clamshell. The #142 tick + invariant `enforce_dock_intent` was built for exactly this and never fired: + it was gated on `settings.displayProfileAuto` — the *named-profile* + auto-switch toggle, unrelated to dock intent and off for most users. + Confirmed on the incident machine: `dockMode=true`, `displayProfileAuto` + unset, watcher journal empty — every tick returned at that guard. Fix: + drop the gate; `dockMode` alone is the recorded intent, the profile-active + check stays as the real guard, and undock's ordering (intent cleared + *before* its rescue reload) keeps the enforcer from fighting the #142 + hammer. V2: `tools/monitor-fallback.nix` grew a reload-mid-dock scenario — + fake lid closed → `hyprctl reload` → panel re-disabled in ~4s, + `action=re-dock` logged. V3 queued: real relogin + `nomarchy-home` while + docked-clamshell (the running watcher is exec-once, so the fix arms at the + next login). - ✓ **Machine state the flake can't see (#147, 2026-07-17):** the QA box ran six months with `Linger=yes` from a pre-migration marker in `/var/lib/systemd/linger` — set by nothing in the repo, visible to no flake diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index fb4000e..b8d732f 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -600,12 +600,21 @@ ${lib.concatStringsSep "\n" (lib.mapAttrsToList # # `auto_dock` is already idempotent — it returns early unless the internal # is enabled — so this costs one `hyprctl monitors` per tick and does - # nothing in the overwhelmingly common case. Gated on the same display - # profile check as the event path: a profile is a deliberate, more specific - # layout and outranks the dock heuristic. + # nothing in the overwhelmingly common case. `settings.display.dockMode` + # is the user's recorded intent — written by the menu and by the dock + # transition itself (`set_dock_intent`, which `auto_dock` above reaches + # via `$TRANSITION dock`) — and that alone is what this is gated on. It + # used to also require `auto_on` (`settings.displayProfileAuto`), but that + # toggle only controls *named-profile* auto-switching; gating the dock + # invariant on it meant enforcement never fired for anyone who had not + # separately opted into auto profiles, which is most users. A + # `home-manager` activation's `hyprctl reload` re-applies the catch-all + # monitor rule regardless, so the panel inside a shut lid came back on + # every rebuild while docked (#148). The profile-active check right below + # is the real, narrower guard this needs: a profile is a deliberate, more + # specific layout and outranks the dock heuristic. enforce_dock_intent() { [ "$(${sync} get settings.display.dockMode 2>/dev/null)" = true ] || return - auto_on || return [ "$(nomarchy-display-profile active 2>/dev/null || echo none)" = "none" ] || return internal=$(internal_output) [ -n "$internal" ] || return diff --git a/tools/monitor-fallback.nix b/tools/monitor-fallback.nix index 4f424d6..99c7e90 100644 --- a/tools/monitor-fallback.nix +++ b/tools/monitor-fallback.nix @@ -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