docs(agent): Bernardo's calls — #136 settled, #130 decided, #143 waits, #120 deferred
All checks were successful
Check / eval (push) Successful in 3m49s

#136 CLOSED: he confirms the incident was clamshell (laptop closed), so the
gate already matched the evidence and only the words overclaimed. Swept:
idle.nix's comment + its journal line (`dpms-off skipped: docked` →
`clamshell (internal $m present but off)`), the ROADMAP entry, and #127's
own text. Docked-with-lid-open still blanks and is now explicitly untested
rather than implicitly covered.

#127 gains the exact protocol he asked for — two tests, separated by risk:
A) the mitigation holds (safe, unattended, journal line to grep); B) the
decisive one, whether DPMS-off was ever the trigger, run by hand because the
shipped mitigation would otherwise skip it. B self-recovers after 45s
(dpms on + hyprctl reload — #142's hammer re-lights eDP) because this box
has no sshd. All three outcomes are written down with what each implies,
including "input wakes it" → revert the mitigation and #135 never exists.

#130 decided: fix the contrast, we hold ourselves higher than GNOME —
matching upstream is no defence at 2.7:1, under even the large-text floor.
Recorded with the two candidate shapes and a preference for darkening our
label over the user's accent, plus the note that check-theme-contrast.py
covers palette pairings only, so it needs its own assertion or it rots
exactly like #98.

#143 decided: wait for rofi#2317, do not carry a patch — a patched rofi
means every machine compiles rofi from source, on every lock bump, to fix a
highlight. Now a watch, not work.

#120 deferred to PROPOSED intact: the measurements stand, and the no-binary-
cache gotcha probably makes cachix the real first step anyway.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 14:24:47 +01:00
parent 4a0b76cc97
commit d31bdf05cd
4 changed files with 199 additions and 116 deletions

View File

@@ -58,16 +58,21 @@ and never re-enables a disabled internal.
**Progress 2026-07-15:**
- Recovery tooling: undock ends with `dpms on`; `nomarchy-display-wake` /
`nomarchy-display-dump` (SUPER+SHIFT+D; TTY if it works).
- **Mitigation (shipped):** do **not** DPMS-off when docked — if any
- **Mitigation (shipped):** do **not** DPMS-off when **clamshell** — if any
laptop internal (eDP/LVDS/DSI) exists in `monitors all` but is not
enabled, the 10 min listener skips blanking. Lock at 5 min still runs;
undocked laptops still DPMS-off as before. Bernardo: Ctrl+Alt+F3 did
not yield a usable TTY on the original brick, so prevention > dump path.
- **Revisit later (LATER):** intentional DPMS-when-docked once wake is
- **Scope settled (#136, 2026-07-16):** the gate is *clamshell*, not
"docked" — and Bernardo confirms the incident was exactly that, **laptop
closed**. So the code matched the evidence and only the wording
overclaimed; that is fixed (idle.nix, ROADMAP, and the journal line, now
`dpms-off skipped: clamshell`). Docked with the lid **open** still blanks
and is untested — not exempted, not claimed safe.
- **Revisit later (LATER):** intentional DPMS-when-clamshell once wake is
proven trustworthy (monitor power-save without a brick). Tracked as
**#135** (state key + the surface to delete this by) and **#136** (the
gate is clamshell, not "docked" — lid-open docked still blanks). Both
are downstream of the hardware test below.
**#135** (state key + the surface to delete this by), downstream of the
hardware test below.
- **Caveat the test must settle:** the mitigation is a cure for an
**unconfirmed cause**. "Blanking the only live output caused the brick"
is inference from one incident, shipped at V1. If DPMS-off is not the
@@ -75,10 +80,67 @@ and never re-enables a disabled internal.
sleep) and the brick is still live — so confirming the cause outranks
#135/#136.
**Pass (current mitigation):** docked on AC, idle past 10+ min → external
stays lit under hyprlock (journal: `dpms-off skipped: docked`); undocked
still blanks at 10 min and wakes on input. Full original pass (wake after
DPMS-while-docked) deferred to the LATER revisit.
**Pass (current mitigation):** clamshell-docked on AC, idle past 10+ min →
external stays lit under hyprlock (journal: `dpms-off skipped: clamshell`);
undocked still blanks at 10 min and wakes on input. Full original pass (wake
after DPMS-while-clamshell) deferred to the LATER revisit.
---
### The two tests, exactly (2026-07-16)
**There is no SSH lifeline on this box** (`sshd` inactive), which is why test B
self-recovers instead of relying on one. If you would rather have the net, turn
`services.openssh.enable = true` on for the day and keep a phone SSH client
open — then a brick is a nuisance, not a reboot.
**Test A — does the mitigation hold? (safe, ~15 min, unattended)**
1. Docked, **lid closed**, on AC. Leave it completely alone for 12 minutes.
2. Expect: at 5 min the external shows hyprlock; at 10 min it **stays lit**.
3. Then: `journalctl --user -b -t nomarchy-idle --since '-15 min'`
→ one `dpms-off skipped: clamshell (internal eDP-1 present but off)`.
4. Now open the lid / undock, idle 11 min → the panel **should** blank, and
wake on a keypress. That half must still work; the mitigation is not
supposed to have disabled blanking everywhere.
**Test B — was DPMS-off actually the trigger? (the decisive one; bounded)**
This is the test that outranks everything else here, because the mitigation is
a cure for an *inferred* cause. Docked, **lid closed**, from a terminal on the
external:
```sh
( sleep 45
hyprctl dispatch dpms on
hyprctl reload # re-enables every output (the #142 hammer)
) >/tmp/dpms-test.log 2>&1 &
hyprctl dispatch dpms off
```
The screen goes black. Then, in order:
1. **Press keys / move the mouse** in the first ~40s. Does it come back?
(In the incident it did not — this is the exact step that failed.)
2. **Wait for the 45s auto-recovery.** Does the external return by itself?
3. If it is still dark, **open the lid** — the `reload` should have re-enabled
eDP even if the external stayed dead.
4. Afterwards:
`journalctl --user -b -t nomarchy-idle -t nomarchy-display-watch --since '-5 min'`
**What each outcome means — all three are useful:**
- **Input wakes it** → DPMS-off is **not** the trigger. The mitigation is a
permanent behaviour cost buying nothing, the brick is still live and
un-diagnosed → revert the skip, **#135 never exists**, and hunt the real
cause (hyprlock? hypridle's own resume path? the dock's DP link?).
- **Dark until the 45s recovery, then fine** → the compositor was alive and
`dpms on` works, so blanking-while-clamshell *is* the trigger and the
mitigation is right → **#135 becomes the way to opt back in**.
- **Dark even after recovery** (lid needed) → the original brick, reproduced
**deterministically and on demand**. That is the best outcome: it turns a
once-off incident into something bisectable (try it without hyprlock, with
the lid open, on the internal only …).
Note the deliberate omission: this runs `dpms off` **by hand** rather than
waiting for hypridle, precisely because the shipped mitigation would otherwise
skip it. Same command, same state, no rebuild.
**Fallback if the cause resists (Bernardo 2026-07-16):** diagnosing stays the
preference — DPMS when docked is the outcome we want. But the mitigation as
@@ -111,25 +173,6 @@ the key in the same commit.
trigger, the mitigation is reverted wholesale and this key never exists —
building it first risks shipping an option for a policy we withdraw.
### 136. `[human]` #127's gate is clamshell, not "docked"
The code skips DPMS-off when a laptop internal is present in `monitors all`
but absent from the enabled list — i.e. **clamshell**. Commit 060bf52, the
#127 entry and the journal all say "docked", which is broader: **docked with
the lid open** (internal + externals all enabled) falls straight through to
`hyprctl dispatch dpms off` and still blanks. So either
- the narrow scope is deliberate (the brick needs the internal disabled, and
lid-open docked is genuinely safe) → fix the wording in 060bf52's
descendants so the docs stop claiming more than the code does; or
- it is an accidental gap → the mitigation misses half the docked cases and
the brick can still fire with the lid open.
Bernardo's call, and the #127 hardware test is what settles it: if the brick
reproduces lid-open, this is a gap. Cheap either way — one `jq` predicate or
one docs sweep. Filed because a doc that overclaims its own fix is how the
next session mis-diagnoses the recurrence.
### 129. Permanent guard for the GTK button-colour trap (#98 follow-up)
The #98 regression (destructive-action at 1.03:1, invisible) was caught only
@@ -148,10 +191,18 @@ Fallout noted while fixing #98: in light palettes the suggested/destructive
labels are base00 (cream) on saturated accent/bad — summer-day measures
**2.72:1** (suggested) and **2.74:1** (destructive), under AA 4.5 and even
under the 3:1 large-text floor. Not a regression: upstream adw-gtk3 does the
same (`color: white` on the accent), so we currently match GNOME. Decide
whether Nomarchy holds itself higher — darker label, or a darker accent mix
for light palettes — and note `tools/check-theme-contrast.py` covers palette
pairings only, never GTK widget surfaces. `[human]` for the aesthetic call.
same (`color: white` on the accent), so we currently match GNOME.
**Decided 2026-07-16 (Bernardo): fix the contrast — we hold ourselves higher
than GNOME here.** Matching upstream is not a defence when upstream is under
even the large-text floor; "everyone ships it" is how 2.7:1 text becomes
normal. Two shapes to pick between while building: darken the *label* (the
label is ours; the accent is the user's palette and the thing they chose the
theme for), or darken the *accent mix* under the label in light palettes only.
Prefer the first unless it looks wrong — it leaves the palette alone.
Note `tools/check-theme-contrast.py` covers palette pairings only, never GTK
widget surfaces, so whatever lands needs its own assertion or it rots exactly
like #98 did.
### 143. Rofi keeps the selected *row number* when the search changes, so the highlight lands on an unrelated entry
@@ -199,17 +250,79 @@ sent: the scoping call is the maintainer's, since keeping the index across a
*reload* looks deliberate (#1064 asked for exactly that, and got it in 1.7.4).
No duplicate found before filing.
**Options.** (a) Wait for upstream — done, now their move. (b) Carry the patch
ourselves in an overlay — a few lines, but it means **building rofi from source
on every user's machine**: our derivations are in no binary cache (#120's
gotcha), so this trades a real install/rebuild cost for it. (c) Live with it
and say nothing — the status quo, and the reason Bernardo hit it cold.
Recommend (a) + (b) together if the patch stays small: file it upstream, carry
it until it lands, delete on the release that fixes it. `[human]` for whether
a source-built rofi is a price worth paying.
**Decided 2026-07-16 (Bernardo): wait for upstream. Do not carry a patch.** The
overlay option was real but priced badly — our derivations are in no binary
cache, so a patched rofi means **every machine compiles rofi from source**, on
every lock bump, to fix a highlight. That is a permanent, distro-wide install
cost for an annoyance with a free workaround (glance before you press Enter).
**This item is now a watch, not work:** the only thing to do is check
rofi#2317, and take the fix when a release carries it. If it is still open at
the next lock bump, that is fine — nothing here rots meanwhile.
## LATER
- **DPMS when docked (revisit #127):** today we **skip** display blanking
while any laptop internal is present-but-disabled (dock/clamshell), so
idle only locks. That avoids blanking the sole external and the brick
where wake/TTY failed (2026-07-15). Revisit when we want monitor
power-save on dock again: re-enable DPMS-off for that mode only if
`nomarchy-display-wake` (or better) reliably restores the external and
undock-while-black restores eDP on the AMD dock setup — without
depending on SSH. Until then, leave the skip in `modules/home/idle.nix`.
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
still slow after.
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
impermanence, BIOS/legacy boot.
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying.
## FUTURE (decided deferred — not the agent queue head)
Work we **intend** someday but explicitly **not** NEXT. Agents do not
pick these unless Bernardo promotes one into NEXT/NOW.
### 20. KVM runner → VM suite in CI `[human]`
**Status (2026-07-10):** keep **eval-only** CI on the current Gitea
stack (act_runner in docker-compose on the 4c/4GB IONOS VPS). Nested
KVM + RAM headroom on that host are a poor fit next to Gitea; full
`checks.*` VMs stay local / promotion-time until a **separate**
KVM-capable machine exists.
**When ready:** register a second runner (host-mode nix + `/dev/kvm`,
label `nix-kvm` — not the existing docker eval runner), then uncomment
the `vm-checks` job in `.gitea/workflows/check.yml` (`runs-on: nix-kvm`,
`nix flake check` + toplevel/HM builds). Do not enable the job until
that label is online (Gitea queues forever otherwise).
### Formatter — adopt later `[human]`
**Intent:** add a Nix formatter (likely `nixfmt-rfc-style`) in a dedicated
pass: reformat the tree once, document in CONVENTIONS, optional CI
check. **Not** the queue head — no drive-by reformats until that pass.
## PROPOSED (agent suggestions — await human triage)
*Agents: append here with a one-paragraph pitch (what/why/cost). Do not
implement. Bernardo moves accepted items into a tier.*
*Open work only. Shipped exam/AC items (#47#63, #14, #52 theme
high-ROI, etc.) live in the journal + ROADMAP — not here.*
### Product / day-2
### 120. A netinstall ISO, next to the fat offline one
**Deferred to PROPOSED 2026-07-16 (Bernardo): not now.** Nothing below is
stale — the measurements stand and the decision it needs is unchanged. It sits
here because it is the only genuinely large item left, and because the gotcha
at the bottom (no binary cache for our own derivations) probably makes a
cachix the real first step, not the ISO.
Bernardo 2026-07-14, after seeing the measured size: **keep the current ISO
exactly as it is** — the guaranteed offline install is the feature it buys —
and ship a **much lighter netinstall variant alongside it**. Two products, one
@@ -268,62 +381,6 @@ Pass = a second, documented ISO target that is *substantially* smaller (state
the measured number, both ISOs built from one tree), installs successfully with
a network in a QEMU run, says clearly at boot that it needs one, and leaves the
offline ISO's behaviour untouched (`checks.*` for the offline path stay green).
## LATER
- **DPMS when docked (revisit #127):** today we **skip** display blanking
while any laptop internal is present-but-disabled (dock/clamshell), so
idle only locks. That avoids blanking the sole external and the brick
where wake/TTY failed (2026-07-15). Revisit when we want monitor
power-save on dock again: re-enable DPMS-off for that mode only if
`nomarchy-display-wake` (or better) reliably restores the external and
undock-while-black restores eDP on the AMD dock setup — without
depending on SSH. Until then, leave the skip in `modules/home/idle.nix`.
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
still slow after.
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
impermanence, BIOS/legacy boot.
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying.
## FUTURE (decided deferred — not the agent queue head)
Work we **intend** someday but explicitly **not** NEXT. Agents do not
pick these unless Bernardo promotes one into NEXT/NOW.
### 20. KVM runner → VM suite in CI `[human]`
**Status (2026-07-10):** keep **eval-only** CI on the current Gitea
stack (act_runner in docker-compose on the 4c/4GB IONOS VPS). Nested
KVM + RAM headroom on that host are a poor fit next to Gitea; full
`checks.*` VMs stay local / promotion-time until a **separate**
KVM-capable machine exists.
**When ready:** register a second runner (host-mode nix + `/dev/kvm`,
label `nix-kvm` — not the existing docker eval runner), then uncomment
the `vm-checks` job in `.gitea/workflows/check.yml` (`runs-on: nix-kvm`,
`nix flake check` + toplevel/HM builds). Do not enable the job until
that label is online (Gitea queues forever otherwise).
### Formatter — adopt later `[human]`
**Intent:** add a Nix formatter (likely `nixfmt-rfc-style`) in a dedicated
pass: reformat the tree once, document in CONVENTIONS, optional CI
check. **Not** the queue head — no drive-by reformats until that pass.
## PROPOSED (agent suggestions — await human triage)
*Agents: append here with a one-paragraph pitch (what/why/cost). Do not
implement. Bernardo moves accepted items into a tier.*
*Open work only. Shipped exam/AC items (#47#63, #14, #52 theme
high-ROI, etc.) live in the journal + ROADMAP — not here.*
### Product / day-2
### 134. `unstable.<pkg>` in the downstream — a newer app without a second flake
Bernardo 2026-07-15, wanting a newer LM Studio (pinned 0.4.15-2 vs unstable

View File

@@ -49,20 +49,36 @@ Everything else below stays open; order is convenience, not a gate.
restores **only** the radios that were on before. If Wi-Fi was already
off, it must stay off after disengage. Glyph must **not** appear when
airplane is off.
- [ ] **#127 docked idle — mitigation check (AMD dev box)** —
**Context:** original brick = docked idle → black, no input wake, undock
no eDP, Caps Lock alive; Ctrl+Alt+F3 gave no usable TTY. **Shipped
mitigation:** skip DPMS-off while docked (internal present-but-disabled);
lock at 5m still. Full DPMS-when-docked deferred (BACKLOG LATER).
- [ ] **#127 clamshell idle — TEST A, the mitigation holds (AMD dev box)** —
**Context:** original brick = clamshell-docked idle → black, no input
wake, undock no eDP, Caps Lock alive; Ctrl+Alt+F3 gave no usable TTY.
**Shipped mitigation:** skip DPMS-off while clamshell (internal
present-but-disabled); lock at 5m still.
**After `nomarchy-home` + relogin:**
1. Dock (eDP disabled in `hyprctl monitors all -j` / not in `monitors`).
2. Idle past 10+ min (or wait for hypridle).
3. **Pass mitigation:** external still shows hyprlock (not black power-save);
1. Dock, **lid closed** (eDP in `hyprctl monitors all -j`, absent from
`hyprctl monitors`).
2. Leave it entirely alone past 10+ min.
3. **Pass:** external still shows hyprlock (not black power-save);
`journalctl --user -t nomarchy-idle --since '-20 min'` has
`dpms-off skipped: docked`.
4. Undocked control: eDP only → at 10 min screen *does* blank; input wakes.
`dpms-off skipped: clamshell`.
4. Undocked control: eDP only → at 10 min it *does* blank; input wakes.
Optional: `nomarchy-display-dump` / SUPER+SHIFT+D for other display faults.
Related: docking round 8; LATER “DPMS when docked”.
- [ ] **#127 TEST B — was DPMS-off the trigger at all? (AMD dev box)** —
**This one outranks #135**: the mitigation is a cure for an *inferred*
cause, and if the inference is wrong we are paying for it forever while
the brick stays live. Bounded and self-recovering (there is no sshd on
that box). Full protocol + how to read each outcome: **BACKLOG #127 §
"The two tests, exactly"**. In short — docked, lid closed:
```sh
( sleep 45; hyprctl dispatch dpms on; hyprctl reload ) &
hyprctl dispatch dpms off
```
then (1) try to wake it with input in the first ~40s, (2) let the 45s
recovery run, (3) open the lid if still dark. **Input wakes it** → the
mitigation is pointless, revert it and #135 never exists. **Dark until
recovery** → mitigation confirmed, #135 is the opt-in. **Dark after
recovery** → the brick, now reproducible on demand, which is the best
outcome available.
- [ ] **#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

View File

@@ -670,11 +670,18 @@ Design/decision records and a running log of shipped work (items marked
`nomarchy-suspend` (hypridle + Power menu). LUKS: unlock session before
encrypted hibernate phase so resume is LUKS-only (not hyprlock twice).
Menu row self-gates on CanHibernate. V3: HARDWARE-QUEUE bag-carry test.
- **Docked idle DPMS (#127 mitigation):** after a docked-idle brick (alive
- **Clamshell idle DPMS (#127 mitigation):** after an idle brick (alive
machine, black external, undock no eDP, no usable Ctrl+Alt+F3), hypridle
**skips DPMS-off** when any laptop internal is present but not enabled
(dock/clamshell). Lock at 5 min unchanged; undocked still blanks at 10 min.
Intentional DPMS-when-docked is LATER (BACKLOG) once wake is proven.
**skips DPMS-off** when any laptop internal is present but not enabled.
Lock at 5 min unchanged; undocked still blanks at 10 min.
**Say clamshell, not "docked" (#136, settled 2026-07-16):** the gate is
"an internal exists and is switched off" — the lid shut — and Bernardo
confirms the incident was exactly that, laptop closed. Docked with the lid
**open** has every output enabled, falls straight through, and still blanks;
whether *that* can brick is untested and not claimed either way. The earlier
wording ("skip when docked") promised twice the coverage the code has, which
is how a recurrence gets misread as a regression.
Intentional DPMS-when-clamshell is LATER (BACKLOG) once wake is proven.
Tooling kept: `nomarchy-display-wake` / `nomarchy-display-dump`.
- ✓ **Generation prune (#128):** weekly `nomarchy-gen-prune` (system + HM):
delete only gens older than **14 days** that are also beyond the **3 most

View File

@@ -52,10 +52,13 @@ let
displayTransition = miniTransition;
}).displayWakeTool;
# #127 mitigation (2026-07-15): blanking the *only* live output while
# docked (eDP disabled) left a black seat that sometimes would not
# recover (and even VT switch looked dead). Skip DPMS-off when any
# laptop internal is present but not enabled. Lock still runs at 5 min.
# #127 mitigation (2026-07-15): blanking the *only* live output with the
# lid shut (eDP disabled) left a black seat that sometimes would not
# recover (and even VT switch looked dead). Skip DPMS-off when any laptop
# internal is present but not enabled — i.e. CLAMSHELL, which is narrower
# than "docked" and is deliberately what the incident was (#136, Bernardo:
# laptop closed). Docked with the lid OPEN keeps every output enabled and
# still blanks; that case is untested, not exempted. Lock still runs at 5 min.
# Revisit: intentional DPMS-on-dock once wake is trustworthy — see
# BACKLOG #127 and LATER "DPMS when docked".
dpmsOff = pkgs.writeShellScript "nomarchy-dpms-off" ''
@@ -69,7 +72,7 @@ let
if ! hyprctl monitors -j 2>/dev/null \
| $jq -e --arg m "$m" 'any(.[]; .name == $m)' >/dev/null 2>&1; then
${pkgs.util-linux}/bin/logger -t nomarchy-idle -- \
"dpms-off skipped: docked (internal $m not enabled)"
"dpms-off skipped: clamshell (internal $m present but off)"
exit 0
fi
done