# Journal — append-only iteration log

One entry per iteration, **newest first**. This is the loop's short-term
memory: the next session reads the last 3–5 entries to orient. Keep an
entry under ~15 lines; durable lessons go to MEMORY.md, not here. When
this file grows past ~25 entries, rotate the tail to
[JOURNAL-ARCHIVE.md](JOURNAL-ARCHIVE.md) (history, never edited).

Template:

```markdown
## YYYY-MM-DD — <task title> (<commit sha, or "no commit">)
- **Task:** which BACKLOG item (or QA sweep / bootstrap / escalation)
- **Did:** what changed, in 2–4 lines
- **Verified:** tier reached (V0/V1/V2) + the actual commands/checks run
- **Pending:** V3 queued? follow-ups filed? anything [stuck]?
- **Next suggestion:** what the following iteration should probably take
```

---

## 2026-07-19 — stale workspace-digit colors after theme switch (prev commit + this)
- **Task:** Bernardo live: bar digit colors wrong for boreal /
  executive-slate / kiln after switching; a reboot "fixed" it.
- **Did:** Root-caused on Newton. (1) Every bar poke used `pkill -x
  waybar`, but the nixpkgs wrap renamed the comm `.waybar-wrapped` —
  switches never restarted/reloaded the bar (and unanchored RTMIN pokes
  were killing the supervisor). (2) Stylix's #98 bare `button label` pin
  outranks the colors waybar.css inherits onto `#workspaces button`,
  frozen at bar start — all three screenshots' digits were kiln-clay's
  @text. Fixed the pattern in 7 sites; scoped the #98 pin to dialogs.
- **Verified:** V0 flake check + py_compile; V1 HM generation + system
  toplevel; live on Newton (V3-grade): restart via the new pattern,
  RTMIN poke with the supervisor surviving, pixel-verified boreal digits
  (@base-on-accent active, @muted inactive) under the scoped gtk.css.
- **Pending:** HARDWARE-QUEUE: deploy round-trip after nomarchy-home.
  NB: waybar 0.15.0 `reload_style_on_change` is inert with HM symlinks
  (upstream); harmless now that the restart path works.
- **Next suggestion:** close the queue entry once Bernardo's deploy
  round-trip passes.

## 2026-07-17 — #143 closed via upstream's own knob; executive-slate gets the icon grid (f899b19 + this)
- **Task:** Bernardo live: rofi#2317 got a maintainer reply → adopt; plus
  "make executive-slate's app menu the boreal-style icon grid".
- **Did:** DaveDavenport pointed at the `inputchange {}` config block the
  #143 survey had missed — shipped `action: "kb-row-first"` in the
  generated config.rasi (rofi.nix extraConfig; HM renders nested
  sections). Zero build cost, watch closed. New
  `themes/executive-slate/launcher.rasi`: boreal's 4×3 drun grid, 4px
  cards to keep the flat control-panel identity.
- **Verified:** V2. Scratch theme-shot variant (launcher-shot.nix) drives
  QMP keystrokes: HEAD repro shows the stationary-index bug; patched run
  snaps to row 0 after backspace — in the exec-slate grid, boreal's grid
  (no regression), and the layout dmenu. Contrast check 24/24. Gotchas
  found: hyprctl `exec` needs the multi-arg command quoted, and the
  per-device keyboard watcher's layout pickers (one per virtio/AT
  keyboard) hold the single rofi instance — drain with pkill before
  driving the launcher.
- **Pending:** nothing; grid polish on real hardware is the standing
  guest-rendering caveat (TESTING §5), not a queue entry.
- **Next suggestion:** #152 (generated waybar polish) is the topmost
  actionable NEXT item.

## 2026-07-17 — Wallpapers split shipped: 94 MB out of the flake source
- **Task:** LATER "Wallpapers artifact split" (promoted by Bernardo live —
  he created the Nomarchy-Wallpapers repo mid-session). Also filed #151
  (v1 launch plan, [human]) with measured history numbers.
- **Did:** all 24 themes' backgrounds → Nomarchy-Wallpapers (803b48d,
  pushed); new `nomarchy-wallpapers` non-flake input; overlay drv
  `nomarchy-default-themes` merges repo themes (~3 MB) + input backgrounds
  with a build-failing drift guard; state-sync wrapper points at the
  merged store path (package 94 MB → 32.6 KiB); checks re-pointed;
  `themes/*/backgrounds/` git-rm'd. Split line: eval-time assets stay
  in-repo, runtime-only wallpapers move. Sonnet implemented; design +
  review here.
- **Verified:** V2 — flake check green; theme-wholeswap, state-sync-validate,
  auto-theme VM check, template HM build all green; wallpapers source
  exactly once in the closure; `list` works from a scratch state dir.
- **Pending → V3 PASSED same day:** Bernardo pulled, rebuilt, switched
  themes; `awww query` shows both outputs rendering summer-night from the
  wallpapers-input store path (`a0hwiwm…-source/summer-night/…`). Queue
  entry closed. (Custom-override precedence is code-order, THEMES_DIRS —
  not re-queued.) Side catch: the rebuild's reload re-lit eDP-1 inside the
  shut lid — expected, the pre-fix watcher still runs until relogin;
  re-docked manually. History rewrite decision folded into #151.
- **Next suggestion:** #151 planning session with Bernardo.

## 2026-07-17 — v1-prep hygiene sweep: repo is clean; LICENSE was the real gap
- **Task:** Bernardo asked for a structure/necessity audit + full docs sweep
  ahead of v1. Three Sonnet passes (structure, docs-vs-code, mechanical
  checkers); triage + fixes here.
- **Findings:** zero dead files (every pkg/module/tool/theme traces to a live
  consumer); zero doc hard-errors; all 92 options documented; all 9 checkers
  green; no untracked files. Real items: no LICENSE (fixed — MIT, Bernardo's
  call), `checks/` violated the seven-dir rule (moved → `tools/fixtures/`,
  flake refs updated), miniTransition duplication filed as #150, four one-line
  doc staleness fixes (theme-state wording ×2, VISION ✓ + date).
- **Verified:** V0 — `nix flake check --no-build` green after the fixture
  move (state-legacy-name's eval asserts exercise the new paths).
- **Pending:** nothing from this sweep; wallpapers-in-repo (97 MB) is the
  known LATER split, not re-filed.
- **Next suggestion:** #150 when promoted, or the theme-switch latency
  measurement that decides the wallpapers split.

## 2026-07-17 — #134: `unstable.<pkg>` seam shipped
- **Task:** BACKLOG #134 (NEXT, decided shape from this morning's triage).
- **Did:** `nixpkgs-unstable` input + `unstable` attrset in
  `overlays.default` (plain lazy import, allowUnfree mirrored);
  `lib.nix` untouched by design — mkFlake's pkgs already applies the
  overlay. Template opt-in comment (`unstable.lmstudio`), README costs
  paragraph, `checks.unstable-seam` (eval-only), VERIFICATION §5 +
  CONVENTIONS two-channel lock rules. Lock gained exactly one node
  (nixos-unstable @ 753cc8a, 2026-07-15). Sonnet implemented; design +
  review here.
- **Verified:** V1 — flake check green; unstable-seam check green
  (26.05pre vs 26.11pre lib.version); template HM activation built with
  `unstable.hello` and its closure carries the unstable store path
  (pg2zfr… vs pinned nm7p8w…).
- **Pending:** nothing; Bernardo's real-world case is him adding
  `unstable.lmstudio` to ~/.nomarchy/home.nix whenever he likes.
- **Next suggestion:** queue is NEXT-empty save watches (#143/#146/#149);
  NOW holds only the install-findings stub awaiting Bernardo's call.

## 2026-07-17 — #127 CLOSED: the brick was hypridle eating its own resume path
- **Task:** BACKLOG #127 (NOW). Two Sonnet forensics passes over the machine's
  journal; interpretation, verdict and close-out here. Bernardo ruled: close +
  file upstream.
- **Did:** found the incident was misdated — it ended the Jul 15 **10:54:22**
  boot via power button (unclean end), not the healthy 12:51 boot every prior
  timeline used. In that boot: `dpms off` 10:26; hypridle logs `BUG THIS:
  inhibit locks < 0: -1` at 10:36:29; from then the fired DPMS listener's
  `on-resume` never dispatches again despite provable input (blind PAM
  attempts) — and it was the session's only `dpms on` caller (pre-fix gen).
  Compositor provably alive throughout → the DRM-wedge lead is dead. Filed
  upstream (hypridle 0.1.7, trace attached); watch folded into #146. BACKLOG
  entry deleted; ROADMAP gained "The idle brick's CAUSE found" and its two
  stale "cause still open" tails were patched; [x] #127 HW-queue blocks
  pruned (method lessons moved to MEMORY §Gotchas).
- **Verified:** V0 (docs/backlog); the *evidence* is journal forensics on the
  incident hardware, quoted in ROADMAP. The fix itself was V3-proven 07-16.
- **Pending:** upstream issue is a watch (#146). NOW is empty except the
  install-findings context stub — flagged to Bernardo.
- **Next suggestion:** #134 (NEXT, decided shape) is the queue head.

## 2026-07-17 — Hardware hints: the second (and last) first-session toast
- **Task:** PROPOSED "Post-install hardware hints" (VISION § B), promoted by
  Bernardo in-session.
- **Did:** `nomarchy-first-boot` grew a hints stage after the card:
  self-gated Firmware/Fingerprint lines (fwupdmgr / fprintd-list on PATH),
  one toast, marker `settings.hardwareHintsShown`; marker deliberately unset
  while no tooling matches so late-appearing hardware still gets its hint.
  README row, HARDWARE.md hint paragraphs, VISION § B checked off.
  Implementation by a Sonnet agent from my design; review here.
- **Verified:** V2 — shellcheck clean, flake check green,
  `checks.first-boot` green (bare-node silent → fwupdmgr appears → fires
  once → silent → live-ISO skip).
- **Pending:** real toast rendering is session-surface, same class as the
  proven #81 card; it will fire once for real on the next fresh session.
- **Next suggestion:** #127 close-out (upstream filing pending gh auth).

## 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
  worked example (`loginctl show-user -p Linger`; `yes` → warn + fix command,
  unknown-to-logind → skip); MIGRATION.md § Post-migration cleanup gains
  "`/var/lib` machine state survives migration"; `checks.doctor` grew the
  warn-fires/warn-clears linger sub-case. Implementation delegated to a
  Sonnet agent, design + review here. Loose thread preserved as #149.
- **Verified:** V2 — shellcheck clean, `nix flake check --no-build` green,
  `nix build .#checks.x86_64-linux.doctor` green (linger sub-case exercised
  both branches, doctor exit 0 throughout).
- **Pending:** nothing hardware-blocked; the real-world case (QA box linger)
  was already fixed by hand 2026-07-16.
- **Next suggestion:** hardware hints, then #148 review.

## 2026-07-17 — Live triage with Bernardo: #114 ships as docs; rulings on #120/#134; #143 checked
- **Task:** Bernardo triaged PROPOSED in-session: #148/#147/hardware-hints
  promoted (in flight as separate commits), #120 re-affirmed deferred, #134
  decided (Nomarchy carries unstable, home-scope) → NEXT, #114 = document only.
- **Did:** README "Greeter keyboard layout" note + RECOVERY.md §2 pointer
  (#114); BACKLOG: #114 deleted, #134 rewritten into NEXT with the decided
  shape, Resolved-2026-07-17 block added; ROADMAP #145 cross-ref updated;
  rofi#2317 checked — still open, noted under #143.
- **Verified:** V0 (docs/backlog only; prose reviewed in place).
- **Pending:** #148 fix, #147 doctor drift check, hardware hints — this
  session; #127 boot -4 forensics (the incident is likely the *unclean* boot
  end at Jul 15 10:54:22, not the 12:51 window) being re-ground.
- **Next suggestion:** build #134; close out #127 with the corrected timeline.

## 2026-07-16 — RETRACTION: the #127 diagnosis was as wrong as the test it replaced
- **Task:** Bernardo said "do #147 first, then #146". Both dissolved under
  investigation, and taking them apart disproved the #127 *cause* I had shipped
  hours earlier. The fix stands; the story around it did not. Sweep = retract.
- **The retraction, and it is the whole entry.** I published "blank + hypridle
  deadlock = the brick" into ROADMAP, hyprland.nix's comment, #146 and two commit
  messages, on two pieces of evidence: "19 disconnects in 11 days" and Jul 14's
  "eleven hours of silence". **Both were artifacts of my own measurement.** 18 of
  19 disconnects are hypridle's goodbye while systemd *deliberately stops it*
  (each bracketed by `Stopped`/`Started`, healthy replacement seconds later). The
  "silence" was a grep for **one PID** — systemd logged `Started hypridle` 43s
  later and PID 1678 ran fine all evening, invisible to my filter. **Decisive:**
  at the incident hypridle had **no disconnect at all**, healthy since Jul 14
  13:09. The theory never had a case.
- **The irony is the lesson.** I spent the morning writing up "the invalid TEST B
  measured its own scaffolding — put the mechanism inside the circuit", then did
  exactly that twice more: inferred a lid position from an output list, inferred
  "the session wiring is missing" from a grep that never covered HM's generated
  conf. Three wrong claims, all from inferring where checking was cheap; Bernardo
  caught all three. **Verify the instrument before believing the reading —
  hardest when the reading agrees with you.**
- **#147 → not a distro bug.** The wiring exists (HM's `systemd.enable`, line 1 of
  the generated conf) and fired; systemd *refused* the `graphical-session.target`
  stop because portal-gtk had a start job queued ("destructive transaction"). It
  needs `Linger=yes` to matter — an empty marker dated **2026-01-24, pre-dating
  Bernardo's migration to Nomarchy**. Never shipped by us, ever. Removed. The item
  is rewritten to the class that IS real: `/var/lib` survives a migration, so the
  primary V3 box ran 6 months on a setting the distro never chose and cannot see.
- **#146 → `[watch]`, near-dead.** One genuine zombie ever (verified by `ps`/`ss`,
  not grep); upstream #171 is real but its impact path closed with linger.
- **Did:** retracted the cause in place everywhere it was asserted; **restored
  #127** (deleted this morning as "shipped" — only the fix shipped); rewrote #146
  and #147; filed #148 (a reload re-lights the panel in a shut lid — found because
  my own `hyprctl reload` did it to Bernardo, twice).
- **Verified: V3, the fix survives all of it.** The real 10-min idle, no
  scaffolding: `19:27:58 Executing hyprctl dispatch dpms off` → blanked (the
  revert working) → mouse → `19:32:14 done enabled=1 first=DP-2` — clamshell, the
  incident geometry, from shipped config. Plus the brick condition on purpose
  (hypridle stopped): keypress woke it 4s docked / 6s clamshell. **The fix needed
  no story about why the daemon fails — which is why it survived the story being
  wrong.** V1: flake check + option-docs/clamshell-logind/docking-ux green.
- **Next suggestion:** #127's **cause**, and start at the **dead Ctrl+Alt+F3** —
  the one symptom no theory yet has touched. DPMS-off cannot break VT switching
  and neither can a dead hypridle; a compositor wedged holding DRM master
  explains the black screen and the dead TTY together. Every theory so far has
  been about the wake path, i.e. a symptom of the screen already being black.

## 2026-07-16 — #127 SOLVED: the brick was never DPMS, and TEST B's first run lied
- **⚠ SUPERSEDED — read the entry above first.** Its headline ("SOLVED") and its
  cause ("hypridle deadlock", "19× in 11 days") were **retracted the same day**:
  the evidence was a measurement artifact and hypridle had no disconnect at the
  incident. What survives: the invalid-TEST-B analysis, and the fix itself.
- **Task:** #127 TEST B (Bernardo ran it), then the whole sweep his result forced.
- **Read this if nothing else — the test measured nothing, and said so.** His run
  reported "input never wakes it" **docked *and* undocked alike**, and that tie is
  the tell: the theory demanded those differ. It ran `dpms off` **by hand**, which
  no wake path watches, so "input didn't wake it" was guaranteed on any hardware
  in any dock state. Re-run *through* hypridle so `on-resume` is actually armed:
  input woke it in all 3 configs, **including clamshell on the sole live output**
  (`done enabled=1 first=DP-2`) — the incident's exact geometry. Put the mechanism
  under test inside the circuit, or you are measuring your own scaffolding.
- **Real cause:** Hyprland ships `misc:{key_press,mouse_move}_enables_dpms` **off**,
  so hypridle's `on-resume` was the ONLY caller of `dpms on` anywhere in the session
  — and hypridle deadlocks (hyprwm/hypridle#171, open since 2025-09, in neither
  0.1.7 nor main) **without exiting**, so `Restart=always` never fires and systemd
  still reports it active. Blank + deadlock = a black seat no key escapes. **19× in
  11 days** on his box, incl. the incident day. Two mundane bugs, not one exotic one.
- **Did:** both options on — structural: the wake moves into the compositor where no
  daemon can lose it, so a dead hypridle costs auto-lock, not the machine. Reverted
  the clamshell skip; **#135 retired unbuilt**; ROADMAP § "#127 solved"; filed #146
  (hypridle mortality) + #147 (relogin leaves session units dead) → PROPOSED.
- **Verified: V3 on the incident hardware.** Clamshell, hypridle **deliberately
  stopped** (brick condition on purpose), `dpms off` → keypress woke it in **6s**;
  the identical test before the options stayed black the full 45s. V1: `flake check`
  + `option-docs`/`clamshell-logind`/`docking-ux`/`display-profiles` pass, and I read
  the **emitted** configs, not the source (`key_press_enables_dpms=true`;
  `on-timeout=hyprctl dispatch dpms off`, skip script gone).
- **Pending:** V3 — proven via runtime `hyprctl keyword`, NOT from our Nix yet;
  HARDWARE-QUEUE entry re-checks after `nomarchy-home` + relogin. Ctrl+Alt+F3 dead
  during the incident is still unexplained (upstream co-symptom, hypridle#145).
- **Next suggestion:** #147 before #146 — a guard that restarts hypridle into a dead
  compositor just fail-loops into `start-limit-hit` exactly like cliphist does. Both
  are PROPOSED; Bernardo triages.

## 2026-07-16 — #129/#130 buttons; #133; #132; branding; and a latch bug from TEST A
- **Task:** Bernardo triaged the queue (#136 settled, #130 decided, #143 → wait,
  #120 → PROPOSED), then #133, #132, the codename drop, and #129+#130.
- **The one to read: a latch bug, found by his TEST A.** He undocked, the panel
  stayed dark, re-docking+undocking fixed it — *opening the lid* was the actual
  cure. `awaiting_lid_open` is cleared only when the external is gone AND the
  lid is open, so a clamshell undock latches it and the NEXT undock is silently
  dropped. His journal proved it (first undock logged nothing). 1 of 3 undocks
  ran; now 3 of 3. **This is very likely #127's second symptom, and it is not
  DPMS** — so the brick may be two mundane bugs, not one exotic one. TEST B is
  now the only thing between that and the truth.
- **#129/#130:** the planned fix ("darken the label") was **impossible** —
  measuring all 24 palettes showed nothing clears 4.5 on a saturated mid-tone
  accent (cream 2.72, dark text 1.65). The chip had to move: it is now the
  accent pulled away from the label (white anchor for dark themes, black for
  light), factor 0.70, worst 4.86/5.59. The old chip failed **seven** themes,
  not the four the item assumed. #129 became arithmetic rather than a CSS lint:
  pin both ends and the pair is checkable — the checker models GTK's real
  `mix()` (read from gtkcsscolorvalue.c:234) and its negative test re-derives
  the item's own 2.72/2.74 figures independently.
- **Verified:** every change negative-tested — the chip guard by reverting to
  the old 0.90 chip (9 failures across 7 themes, matching the filed numbers),
  #133's shim check by breaking lib.nix twice, the latch by replaying his
  sequence. V0 green throughout; the branding by reading the built os-release.
- **Pending:** Bernardo runs TEST B tonight (**relogin first** — the latch fix
  is exec-once). Buttons want an eyeball on a light theme at some point, but the
  numbers are pinned and the render harness was declined on purpose.
- **Next suggestion:** whatever TEST B says. If input wakes the screen, revert
  the #127 mitigation wholesale and #135 dies with it.

## 2026-07-16 — #137 + #145: the splash follows the canvas, and says your layout
- **Task:** #137 (splash off-centre docked) + #145 (keyboard layout on the LUKS
  prompt, with Fedora's icon).
- **Did:** one `layout()`, re-run from the existing refresh callback on canvas
  change; `@LAYOUT@` + `@FG_*@` baked by plymouth.nix like the palette already
  is; plymouth's own `keyboard.png` copied at build time and recoloured.
- **The lesson, and it is the whole entry:** I was wrong twice, and only the
  render rig caught it. Not head-0 geometry (`Window.GetWidth()` = `max_width`);
  not per-head sprites (would have duplicated the logo — the plugin already
  centres each display in the canvas). The bug was **time**: positions computed
  once at parse time vs a canvas that resizes when a head arrives. And attempt 1
  of the fix died on a plymouth-script trap: **a bare assignment inside a
  function writes the global if that name exists globally**, so
  `canvas_width = Window.GetWidth()` updated the very global its guard compared
  against — always false, nothing drawn, **no error logged**. Isolated with a
  20-line probe (`[after assign: global.canvas_width=800][guard SKIPPED]`).
  Both facts are now comments in the script; do not re-derive them.
- **Verified:** V0 + V2 by render — `tools/plymouth-preview.sh` with the real
  built theme: both heads centred, and the password dialog showing padlock,
  entry, and ⌨ + `us`. Also caught, only because I read the build log: copying a
  file from the store keeps it **444**, so `recolor` failed, the phase aborted,
  and **every later sed silently did not run** — the theme shipped with `@BG_R@`
  literals. `chmod +w`. Check build logs, not just exit codes.
- **Pending:** V3 queued — a real docked boot/shutdown is the only way to resize
  a canvas (the rig's fake heads exist from the start). Recovery if the splash
  is ever blank: the prompt is invisible but live (type blind), or boot the
  previous generation.
- **Next suggestion:** #129/#130 (GTK contrast) or #133 (the #107 shim check).

## 2026-07-16 — "fix the rest": #140, #142, #144 shipped; #137 re-diagnosed
- **Task:** the T14s round's remainder. Bernardo picked configurable-provider +
  drop-the-CLI for #140 (and asked for chatgpt/grok in the list).
- **Did:** **#140** Ask → web chat; `settings.ask.provider` read at run time
  (switching needs no rebuild), claude-code + `pkgs.nodejs` deleted, query also
  copied to the clipboard because vendor prefill is undocumented and fails
  *silently*. **#142** dock mode is now an intent (`settings.display.dockMode`)
  written at the transition choke point and re-asserted by the watcher as a
  **tick invariant** — reconcile diffs `monitors all`, which lists disabled
  outputs, so it is blind to a panel switching back on. **#144** MIGRATION.md
  gets the Mozilla `profiles.ini` bullet.
- **Verified:** V0 on each. #140: drove the generated script — bad state values
  all fall back, hostile query encodes right. #142: **9/9 gates** unit-tested by
  extracting the real generated `enforce_dock_intent` and stubbing the session
  (incl. *no external → never re-dock*, the #127 brick), plus the state
  write/read round-trip on a scratch checkout so the live flake stayed clean.
- **#137 — read the entry before touching it.** My own first diagnosis was
  **wrong** and is now corrected in place: `Window.GetWidth()` returns
  `max_width`, not head 0, and the plugin *centres every display in the canvas*
  — so per-head sprites (what I originally proposed) would duplicate the logo.
  The real cause is that the script computes positions **once at parse time**,
  so a display arriving/leaving mid-splash re-centres the displays while the
  sprites keep their stale canvas coords. Fix = reposition on canvas change from
  the existing refresh callback. Not shipped: it draws the **LUKS box** on an
  encrypted machine, and the render rig (`x11.so` +
  `PLY_CREATE_FAKE_MULTI_HEAD_SETUP`) needs root.
- **Pending:** V3 queue now holds #138 (dock audio), #139 (Acer sheets), #131
  (Acer menu width), #142 (rebuild while docked — **relogin first**). Bernardo
  must `nomarchy-home` + relogin to get any of today's session work.
- **Next suggestion:** #137 with a sudo session for the rig, or #143's upstream
  (rofi#2317) — nothing to do there until they answer.

## 2026-07-16 — #139 + #141: the quick ones from the T14s round
- **Task:** #139 (terminal sheets open full-screen) and #141 (Waybar updates
  click does nothing) — Bernardo: "let's do the quick ones first".
- **Did:** #139 was two faults: Hyprland 0.55.4 ignores **percentage** `size`
  rules (clean matrix on hardware: both orders, Kitty memory off — px applies,
  % never does, no configerror), and Kitty replays its remembered window size
  into every float. Fix: `remember_window_size no` + a shared
  `modules/home/term-sheet.nix` that reads the focused monitor and hands Kitty
  the px, so the % intent survives without a rule; the size rules are gone.
  #141: whole-swaps used `$TERMINAL`, which Waybar's env does not have →
  `nomarchy-updates upgrade-window` opens its own window, all 5 call sites are
  env-free, `checks.waybar-swap-env` guards the class. Bernardo asked mid-task
  for the update window to float too → third caller of the sheet helper.
- **Verified:** V0 `nix flake check --no-build`. **V3 on the dev box:** built
  the HM package with `--override-input`, drove the real launchers — calendar
  1536×936 (=60%×65%), doctor 1408×1008 (=55%×70%), updates 1152×720
  (=45%×50%), all floating + centred in the work area. #141 tested the way it
  actually fails: `env -i` with no `TERMINAL` (matching Waybar's real env) →
  window opens. Guard proven by reintroducing the bug: it fails with the
  offending file/module/key named.
- **Pending:** Acer (1366×768) V3 queued — the sheets are monitor-relative now,
  so it should hold by construction, which is exactly why it is worth one look.
  #131 is **live on 2560** per Bernardo, so its "only at 1366" model is wrong —
  next up.
- **Next suggestion:** #137 (plymouth per-head) or #142 (rebuild-while-docked).
- **Then #131 shipped too (same session):** the remodel found the real cause —
  text menus are sized in % of the *monitor* while a label is a count of
  *characters* in the *theme's* font, and the whole-swaps disagreed wildly
  (620px fixed vs 40%; GeistMono 11 vs JetBrainsMono 14). Fixed with rofi's own
  `ch` unit + a `min` cap: `width: calc( 84ch min 65% )` everywhere. Bernardo's
  condition ("not ugly on a low-res monitor") is what the cap is for, and it is
  measured, not hoped: 84ch = 756px/924px by font, `min 300px` → 300px. V3 by
  screenshot at both ends, incl. the Acer's exact geometry reproduced on the dev
  box. `checks.rofi-text-width` guards it.

## 2026-07-16 — #138 dock audio: the graph restart was the bug (44aac0f + this)
- **Task:** #138 — Meet "no mic or speakers" in Chromium; Zoom fine; no repro
  on a fresh boot. Bernardo's hypothesis (pipewire restarts → Chromium never
  reconnects) was right; the restarter turned out to be **us**.
- **Did:** `dock-audio.nix` `reprobe` restarted PipeWire/Pulse/WirePlumber on
  every `monitoradded` (#100). Now escalates cheapest-first: select → repair a
  parked card → restart. Added `wait_for_dock_sink` so a slow sink is not read
  as "nothing to select" and cannot fall through to the restart. Filed #137/
  #139/#140/#141 from the same T14s round in 44aac0f.
- **Verified:** V0 `nix flake check --no-build`. **V2/V3 on the dev box** (real
  hardware, real graph): built the HM activation package with
  `--override-input nomarchy path:…` and ran the real tool — rung 1 selects the
  *same* sink the restart-first path picked (`selected=…HiFi__HDMI1__sink`) in
  2.0s vs ~3.5s, with pipewire/pulse/wireplumber MainPIDs **unchanged**; rung 3
  is reachable and restarts+retries as designed. Diagnosis proof: `Started
  app-org.chromium.Chromium-*.scope` vs `reprobe-start` across 4 boots —
  restart-after-Chromium in both broken sessions, 24s *before* in the healthy
  one; Bernardo then reproduced on demand with a dock/undock (09:35:30).
- **Pending:** V3 queued (dev box): real plug with Chromium open on Meet —
  **relogin first**, the watcher is `exec-once` (round 6's lesson). Bernardo
  must `nomarchy-home` to pick this up; his Chromium needs one restart to
  regain audio (his own 09:35 test killed it, not the fix).
- **Process note (mine, worth not repeating):** the rung-3 test was meant to be
  safe via a stubbed `systemctl`, but the `sed` anchored on leading whitespace
  that **Nix's indented strings strip**, so the stub never applied and the run
  restarted his live graph for real. It came back clean (services active, dock
  sink still default) and cost nothing extra only because Chromium was already
  broken. Lesson: when a test's safety depends on a substitution, **assert the
  substitution landed** before running the thing it protects you from.
- **Next suggestion:** #139 (terminal floats — two faults, both root-caused and
  cheap) or #141 (4 jsonc lines, `$TERMINAL` absent from Waybar's env).

## 2026-07-15 — #107 upgrade path proven; state-read tip fixed
- **Task:** Bernardo feared rebuilding on the state.json rename ("what if it
  doesn't boot"). Verify the real upgrade path from a legacy checkout.
- **Did:** Built a scratch downstream box shaped like his (~/.nomarchy: git
  checkout, `theme-state.json`, input → local repo) and drove the whole path.
  **The reassurance: the built derivations are byte-identical across the
  rename** — HM `2yqqy8h…` and system `9qp9l3g…` from `theme-state.json` and
  from `state.json` with identical content. The filename picks the read path
  only; it cannot change the closure, so it cannot affect boot. Fixed the
  footgun in `modules/state-read.nix`: the tip said "rename it to state.json",
  which on a git flake yields an *untracked* (invisible) file and the very
  "state file is missing" error the tip is attached to — now it points at the
  menu write and names `git add state.json`.
- **Verified:** **V1 (strong)** — real eval of both `homeConfigurations` and
  `nixosConfigurations` on the legacy name; migration write (content diff:
  only the key I set, no drift); re-eval clean while dirty/intent-to-add via
  both `.#` and `git+file://`; untracked-state failure is fail-closed at eval
  with the new message (rendered, not assumed). No VM: identical drvPaths are
  stronger evidence than booting the same closure twice.
- **Pending:** no permanent check asserts mkFlake accepts the legacy name —
  the compat shim could rot silently before it's dropped. Filed #133.
- **Next suggestion:** #133, or #131 label call `[human]`.

## 2026-07-15 — V2 the menu IA (#105/#111/#110), no code change
- **Task:** close the V2 gap on 856a9d1 — it shipped at "V0 menu-back; HM
  gen builds" for a user-visible menu restructure.
- **Did:** Rendered all 8 picker screens under the real HM session (scratch
  menu-shot harness off `theme-shot.nix`, invoking each screen through the
  same `$0 <arg>` dispatch). **IA verified**: six-entry root; System →
  Connectivity/Devices/Recovery/Preferences; Recovery lists each scope once;
  Preferences carries the migrated update-checks/Bluetooth/Printing toggles;
  Control Center absent from the dispatch (asserted); no stderr on any screen.
- **Verified:** **V2 (content only)** — screenshots viewed, `v2-menu/`.
  The VM renders menu *geometry* unfaithfully (icons 44→~10px, 6-row root
  clips), so spacing/truncation is not judgeable there → filed #132 + a
  docs/TESTING.md §5 gotcha so the next agent doesn't chase it.
- **Pending:** #131 — measured Inter 11 against the 40% picker width: the two
  long Recovery labels fit at 1920 but ellipsize at 1366, cutting exactly the
  cost hint #111 added. Live on the Acer (1366×768). Needs a design call.
  BTRFS Files row is `command -v`-gated and absent in the guest — unexercised.
- **Next suggestion:** #131 label call `[human]`, or #107 state.json V2.

## 2026-07-15 — #98 follow-up: destructive buttons invisible (V2 sweep)
- **Task:** QA sweep of today's ship — V2 the batch that stopped at V1.
- **Did:** Rendered a real GTK3 GtkMessageDialog under the actual HM session
  (scratch harness off `tools/theme-shot.nix`). Found e9dd3d1 **regressed
  `destructive-action`**: adw-gtk3 derives ONLY the destructive background
  from `currentColor` (`mix(@destructive_color, alpha(currentColor,…))`),
  so pinning the label to base00 dragged the background dark too — "Delete"
  rendered at **1.03:1** (invisible) on Boreal, and in light themes as well.
  Fix: pin the destructive background to suggested-action's opaque pastel
  ladder, enabled-only (`:not(.flat):not(:disabled)`).
- **Verified:** **V2** — boreal before/after, summer-night (whole-swap) and
  summer-day (light) renders viewed; destructive 1.03→6.19:1 (boreal),
  1.03→2.74:1 (summer-day, = upstream's white-on-accent convention).
  `nix flake check --no-build` + check-theme-contrast green. Screenshots:
  `scratchpad/v2-98/boreal-{BEFORE,AFTER}-zoom.png`, `sn-zoom.png`, `sd-zoom.png`.
  Two CSS-derived hypotheses (`.default`, disabled suggested/destructive)
  did NOT reproduce — rendering overruled the theory, both left alone.
- **Pending:** filed the permanent guard + light-mode ~2.7:1 button contrast
  as BACKLOG items. #111/#105/#110 menu IA and #107 rename are still V1.
- **Next suggestion:** V2 the menu IA (#111/#105/#110), or human V3 #115/#127.

## 2026-07-15 — #127 skip DPMS-off when docked (mitigation)
- **Task:** Bernardo: no usable TTY on brick; prefer prevent over dump.
- **Did:** hypridle 10m listener skips `dpms off` when an internal
  (eDP/LVDS/DSI) is present-but-disabled; lock still at 5m. Documented
  LATER “DPMS when docked” revisit. Dump/wake tools remain.
- **Verified:** V0/V1 flake check. V3: HARDWARE-QUEUE mitigation check.
- **Pending:** human docked idle past 10m → external lit under lock.
- **Next suggestion:** V3 docked idle, or other queue.

## 2026-07-15 — #127 no-SSH dump + docked idle wake harden
- **Task:** Tackle docked idle blackout without needing SSH mid-brick.
- **Did:** `nomarchy-display-dump` / `nomarchy-display-wake`; undock always
  `dpms on`; hypridle resume/after_sleep → wake rescue; SUPER+SHIFT+D;
  HARDWARE-QUEUE #127 rewritten for Ctrl+Alt+F3 TTY path.
- **Verified:** V0/V1 flake check. V3: docked idle on AMD box.
- **Pending:** real repro; keep #127 open until V3 pass.
- **Next suggestion:** Bernardo runs forced DPMS dock test + TTY dump if needed.

## 2026-07-15 — #115 suspend-then-hibernate (1h, battery-only + menu)
- **Task:** Human settled delay=1h, battery only, menu enable/disable.
- **Did:** `nomarchy-suspend` + logind/sleep bridge from
  `settings.power.suspendThenHibernate` (default on; needs resumeDevice).
  Preferences toggle (rebuild). Unlock-before-encrypted-hibernate for s2h→disk.
  HARDWARE-QUEUE: #115 steps + suggested-order sweep (top 8).
- **Verified:** V0/V1 `nix flake check --no-build` incl. state-bridges s2h cases.
- **Pending:** V3 bag-carry / 1h on battery (HARDWARE-QUEUE #115).
- **Next suggestion:** human runs HARDWARE-QUEUE suggested order, or #120.

## 2026-07-15 — #98 GTK dialog button black-on-dark (Boreal)
- **Task:** Bernardo: GTK dialog button labels black vs dark Boreal chrome.
- **Did:** Root cause — Stylix pins light `adw-gtk3` for all polarities.
  Force `adw-gtk3-dark` when mode=dark, prefer-dark, and extraCss for
  dialog/action button labels (suggested/destructive keep dark-on-pastel).
- **Verified:** V1 HM gen: settings.ini `gtk-theme-name=adw-gtk3-dark`,
  prefer-dark=true, gtk.css button rules present. V3: open a GTK dialog
  after nomarchy-home.
- **Pending:** V3 eyeball on real session.
- **Next suggestion:** hardware queue or human #115/#120.

## 2026-07-15 — Document generation prune for users (#128 docs)
- **Task:** User-facing docs for the 14d / keep-≥3-past policy.
- **Did:** RECOVERY §4 (how gens are kept); README generation section +
  alias note; REQUIREMENTS hygiene points at the automatic prune.
- **Verified:** V0 (docs only).
- **Pending:** none.
- **Next suggestion:** hardware V3 or human #115/#120.

## 2026-07-15 — #128 generation prune (14d + keep ≥3 past, system+HM)
- **Task:** Implement auto-prune for system and Home Manager generations.
- **Did:** `pkgs/nomarchy-gen-prune` + `modules/nixos/gen-prune.nix` weekly
  timer; pure select (current + 3 past always; else drop if age>14d); store
  GC without `--delete-older-than`; bootloader refresh after system prune.
  Doctor fix + RECOVERY note. Closed #128.
- **Verified:** V0/V1 — `checks.gen-prune` (self-test + dry-run + timer
  assert + no age-only gc options). V3: run on a multi-gen box.
- **Pending:** V3 on real machine with many gens.
- **Next suggestion:** human #115/#120, or hardware queue.

## 2026-07-15 — File #128 generation auto-prune policy (no code)
- **Task:** Bernardo: auto-clean gens older than N days but always keep
  ≥3 past; include HM; prefer 14d over 30d.
- **Did:** NEXT **#128** — system **and** Home Manager; **14 days** + keep
  ≥3 past. Noted existing `nix.gc` weekly 14d has no min-generation floor.
- **Verified:** V0 (docs/agent only).
- **Pending:** implement when picked.
- **Next suggestion:** implement #128, or human #115/#120.

## 2026-07-15 — #111 Recovery + #105 System IA + #110 retire Control Center
- **Task:** Scope-first Recovery, System submenu IA, retire Control Center.
- **Did:** System → Connectivity / Devices / Recovery / Preferences. Recovery:
  Desktop gen (HM list+activate), System boot (toast + RECOVERY.md), Files
  BTRFS, What changed. Migrated CC: blur/gaps presets in Look & Feel; update
  checks + Bluetooth/Printing package toggles in Preferences (rebuild in
  terminal). Removed `pkgs/nomarchy-control-center` + menu entry. Docs
  RECOVERY/HARDWARE/VISION updated. Closed #111 #105 #110 #117.
- **Verified:** V0 menu-back; HM gen builds; flake check --no-build.
- **Pending:** V3 eyeball the new menu tree on a real session.
- **Next suggestion:** #115/#120 human decisions, or hardware queue.

## 2026-07-15 — V2 permanent checks for #104 / #122
- **Task:** V2 everything VM-checkable from the #104/#122 batch.
- **Did:** Extracted `pkgs/nomarchy-airplane` (overlay). Added
  `checks.airplane` (session state machine + menu/keybind/whole-swap
  surfaces; mocked nmcli) and `checks.fastfetch-logo` (sextant count +
  module flags). Host V2 artifacts stay under `/tmp/nomarchy-v2-batch2`
  and `/tmp/nomarchy-v2-122/`.
- **Verified:** **V2** — both new checks green; prior mock round-trip +
  kitty screenshots; #107 migrate re-checked; ecosystem keys in HM conf;
  flake check --no-build.
- **Pending:** V3 airplane radios (HARDWARE-QUEUE).
- **Next suggestion:** #111 Recovery menu, or #98 with screenshot.

## 2026-07-15 — #104 airplane mode + #122 fastfetch sextant logo
- **Task:** NEXT #104 (Airplane) + #122 (crisper fastfetch logo).
- **Did:** (104) `nomarchy-airplane` runtime Wi-Fi+BT kill-switch with prior
  restore; System menu row; SUPER+CTRL+R; Waybar `custom/airplane` (signal 11,
  self-hides); whole-swap jsonc/css parity. (122) chafa `sextant` 24×12,
  rsvg 360px.
- **Verified:** **V1** airplane state machine (on/off/status empty when off);
  bash -n; keybinds-cheatsheet; menu-back; flake check --no-build; HM gen.
  **V2** fastfetch: real kitty screenshots
  `/tmp/nomarchy-v2-122/{boreal-sextant,summer-kitty,boreal-block}.png` —
  sextant clearly finer than block; no tofu; accents track. (Superseded by
  permanent checks entry above.)
- **Pending:** V3 airplane on a machine with live radios (HARDWARE-QUEUE).
- **Next suggestion:** #111 Recovery menu, or #98 with screenshot.

## 2026-07-15 — #107 state.json + nomarchy-state-sync
- **Task:** BACKLOG #107 — rename misnamed system state file and CLI.
- **Did:** `theme-state.json` → `state.json` (template, root, docs, modules);
  package/CLI `nomarchy-theme-sync` → `nomarchy-state-sync` with symlink
  alias; `modules/state-read.nix`; mkFlake/doctor/lifecycle read legacy
  name; writes always land on `state.json` and remove the old file.
- **Verified:** py_compile; legacy-file unit migrate (write removes
  theme-state.json); V0 flake check pending in same push.
- **Pending:** drop `nomarchy-theme-sync` alias after release notes.
- **Next suggestion:** #104 airplane, or #98 with a screenshot.

## 2026-07-15 — Close #94 installed chromium + kill Hyprland update-news
- **Task:** Residual #94 (chromium on installed path) + first-boot polish
  seen while probing.
- **Did:** Re-booted leftover offline install disk (`target.img`). Probe:
  Chromium 150 on PATH, https/html → `chromium-browser.desktop`, Chromium
  window opens (`about:blank` + keyring prompt). Closed #94. Added
  `ecosystem.no_update_news` + `no_donation_nag` (every first boot showed
  “Hyprland updated to 0.55.x!”).
- **Verified:** **V2** install re-boot screenshots `/tmp/nomarchy-v2-94/`;
  V0 `nix flake check --no-build`; HM gen conf contains ecosystem keys.
- **Pending:** V3 optional on Acer (hardware already had no browser on
  *live* before #103; installed path is the one that mattered).
- **Next suggestion:** #107 state.json rename, or #98 if screenshot lands.

## 2026-07-15 — File #127 docked idle blackout (no code)
- **Task:** Bernardo: docked laptop idle → black screens; external KB/mouse
  no wake; unplug external did not restore panel; Caps Lock still worked.
  Explains the "session crash" (seat brick, not agent-only).
- **Did:** NOW **#127** + HARDWARE-QUEUE capture steps. Code read: AC idle
  path is lock+DPMS not suspend; dock mode disables eDP; both wake and
  undock recovery failed on the incident.
- **Verified:** V0 (docs/agent only).
- **Pending:** hardware journal capture on next repro; then fix.
- **Next suggestion:** continue fix batches, or dig into #127 if Bernardo
  can leave SSH open for a controlled repro.

## 2026-07-15 — V2 verification of fix batches (doctor-float + test-install)
- **Task:** V2-test shipped items that admit VM harnesses (#95 doctor,
  #123 bake, #112/#124/#125 install path).
- **Did / Verified:**
  - **V2 doctor-float (boreal):** `nix build --impure -f tools/doctor-float.nix`
    PASS. Class `com.nomarchy.doctor`, floating, centered; client
    `title=kitty`. Screenshots:
    `/tmp/nomarchy-v2-doctor/{desktop-before,doctor-float}.png`. SoftGL
    window body is blank (known softGL terminal paint); float/class
    contract holds.
  - **V2 test-install.sh (offline LUKS+swap):** machine-checkable PASS
    (~28 min). Artifacts `/tmp/nomarchy-v2-batch/`:
    - ISO: `result/iso/nomarchy-live-….iso` (#125)
    - `first-boot.png`: themed Boreal desktop + Waybar, **no** red
      autogenerated banner, no login prompt → HM pre-activate (#123)
      succeeded (visual)
    - install completed poweroff; LUKS boot + passphrase works
  - **Disk filter fixture:** fd0/loop excluded, vda/sda kept (#112 logic)
- **Pending:** V3 on Acer/XPS; mount installed disk for log/flake-url
  strings needs root (skipped). Doctor softGL paint is not a product fail.
- **Next suggestion:** continue backlog or hardware V3.

## 2026-07-15 — #108 keybinds cheatsheet groups + completeness check
- **Task:** BACKLOG #108 — group SUPER+? as Window/Workspace/Menu/Media;
  automated completeness.
- **Did:** every bind/extra in keybinds.nix gets `group`; cheatsheet renders
  section headers; `checks.keybinds-cheatsheet` asserts groups + every desc
  present (proved via assert).
- **Verified:** V0 flake check; cheatsheet file shows ── Window ── etc.
- **Pending:** V3 eyeball SUPER+? on a real session (optional).
- **Next suggestion:** #94 residual or #107 state.json rename.

## 2026-07-15 — Fix batch #112 disk picker, #106 menu Back, #113 offline theme
- **Task:** Second fix-only batch from NEXT/NOW residuals.
- **Did:** (112) `list_installable_disks` excludes fd/loop/sr/zram/tiny
  (&lt;8 GiB), sorts largest-first; installer-safety markers. (106)
  `tools/check-menu-back.py` + `checks.menu-back` (proved to fail when Back
  stripped). (113) offline theme contract: TESTING item 6 scoped to default/
  current theme; `run_switch` prints clear offline-oriented message on
  network/build failures.
- **Verified:** V0 flake check; installer-safety; menu-back prove-fail;
  py_compile theme-sync.
- **Pending:** V2 unattended install with OVMF fd0 present (optional).
- **Next suggestion:** #108 keybinds audit, or #94 test-install chromium.

## 2026-07-15 — Fix batch #123–#126 (install bake, flake ref, ISO name, display)
- **Task:** Fix-only NOW batch: #123 dconf pre-activate, #124 main flake
  seed, #125 ISO filename, #126 display mode list.
- **Did:** (123) pre-activate creates `/run/user/$UID` + prefers
  `dbus-run-session -- activate`. (124) seed `?ref=main`; patch-template
  rewrites `inputs.nomarchy.url` from NOMARCHY_FLAKE_URL (was unused);
  template default main. (125) `image.baseName` → nomarchy-live-….iso.
  (126) sort modes by pixel area; toast shows hyprctl size; TESTING QEMU note.
- **Verified:** V0 flake check; installer-safety; `image.baseName` eval
  `nomarchy-live-…`; patch-template flakeUrl rewrite; mode sort unit fixture.
- **Pending:** V3 reinstall on Acer/XPS (#123/#124); optional full
  `test-install.sh` V2 when time allows.
- **Next suggestion:** #112 disk-picker fd0, or V2 test-install for #123.

## 2026-07-15 — File #125 ISO name + #126 display resolution invert (no code)
- **Task:** Bernardo: (1) installer ISO still named nixos; (2) live VM
  Display picker — higher modes made the screen look lower-res.
- **Did:** NOW **#125** — artifact is `nixos-live-….iso` because
  `image.baseName` is still the nixpkgs default; volumeID already
  NOMARCHY_LIVE. **#126** — rofi display mode list/apply path; separate
  sort bug vs QEMU scaling before coding.
- **Verified:** V0 (docs/agent only); confirmed `result/iso/nixos-live-…iso`
  on tree.
- **Pending:** implement when picked.
- **Next suggestion:** still #123 (HM pre-activate), or #125 if a one-liner.

## 2026-07-15 — #95 complete: Kitty is the only terminal (this commit)
- **Task:** Bernardo: make kitty default and only option — drop dual-terminal
  complexity (Ghostty failed on Acer OpenGL 4.2; fallback was more code).
- **Did:** Removed `ghostty.nix` and `nomarchy.ghostty.enable`. Kitty always
  installed + themed; default `nomarchy.terminal = "kitty"`. Doctor, calendar,
  what-changed → `kitty --class=com.nomarchy.*`. Dropped install-time GL
  probe + mesa-demos. Updated README/REQUIREMENTS/OVERRIDES/TESTING/tools.
- **Verified:** V0 flake check; downstream-template-home builds with kitty,
  no ghostty; installer-safety green.
- **Pending:** V3 Acer SUPER+Return + doctor float (HARDWARE-QUEUE).
- **Next suggestion:** #123 (HM pre-activate dconf).

## 2026-07-15 — #95: themed Kitty when OpenGL < 4.3 (superseded above)
- **Task:** Bernardo: when GL &lt; 4.3 automatically add kitty — and theme it.
- **Did:** `modules/home/kitty.nix` themes Kitty from theme-state (same
  palette/font/opacity as Ghostty); enables when `nomarchy.terminal` is
  kitty or `nomarchy.kitty.enable`. Installer runs `glxinfo` (mesa-demos on
  PATH) and, if OpenGL &lt; 4.3, writes `settings.terminal = "kitty"` (+
  `terminalReason`) into theme-state. Docs/REQUIREMENTS + README rows.
- **Verified:** V0 flake check; HM gen with `settings.terminal=kitty` builds
  — kitty.conf has Boreal colors, `TERMINAL=kitty`, Hyprland `$terminal=kitty`;
  default template HM has ghostty and **no** kitty package.
- **Pending:** V3 Acer install; doctor/calendar still hardcode ghostty.
- **Next suggestion:** #123 (HM pre-activate dconf) — still highest install pain.

## 2026-07-15 — docs/REQUIREMENTS.md (OpenGL floor + disk planning)
- **Task:** Bernardo: why Acer lacks OpenGL 4.3; start documenting minimum
  system requirements including storage (Nix store + snapshots).
- **Did:** Answer is hardware generation (HD 4000 / Ivy Bridge tops at GL
  4.2; Ghostty needs 4.3). New `docs/REQUIREMENTS.md` — UEFI/x86_64, GPU
  table, RAM, disk floors (40/64/128 GiB) + swap≈RAM, why store+snapper grow;
  linked from README, docs map, HARDWARE.md; #95 notes the generation ceiling.
- **Verified:** V0 (docs only).
- **Pending:** #95 still needs a runtime fallback; tighten disk numbers when
  a fresh install is `df`-measured end-to-end.
- **Next suggestion:** #123 (installer bake).

## 2026-07-15 — File install findings from Acer + XPS photos (no code)
- **Task:** Bernardo: real installs on Acer Aspire M5-481T + Dell XPS 9350;
  photos of the install end screens and post-boot sessions. Figure out
  issues and queue them — do not implement in this iteration.
- **Did:** Read the six photos. Three distinct root causes (not one vague
  "home didn't build"):
  1. **#123** — HM pre-activate dies on both machines at dconf
     (`/run/user/1001/dconf: Permission denied`) → partial first boot
     (weird GTK + missing nm-applet are *symptoms*, not separate bugs).
  2. **#124** — seeded flake hardcodes `?ref=v1` while installer writes
     main-era `nomarchy.hardware.*`; v1 lags (~hundreds of commits) →
     `nomarchy.hardware does not exist` on Acer rebuild/pull.
  3. **#95** — Ghostty **Acer-only** (XPS is fine). Two photos = two
     attempts on the Aspire: pre-HM explicit `OpenGLOutdated`, post-HM
     quiet exit after `GDK_DISABLE` (same machine; kitty used as the
     working shell). Primary blocker is GL ≥ 4.3.
  HARDWARE-QUEUE V3 rows. Photos stay local (not committed).
- **Verified:** V0 (docs/agent only).
- **Pending:** implement #123 first; then #124; #95 independent (Acer GL).
- **Next suggestion:** #123 (installer bake) — highest blast radius.

## 2026-07-15 — #95 timeline: both Ghostty fails are Acer, pre/post HM
- **Task:** Bernardo corrected the Ghostty story again.
- **Did:** XPS works. Both fail photos are Aspire M5-481T: (1) before home
  properly built → OpenGL 4.2/`OpenGLOutdated`; (2) after home built →
  silent exit after `GDK_DISABLE=gles-api,vulkan`. Kitty was the manual
  working terminal used to run those experiments. #95 + HARDWARE-QUEUE
  rewritten; not two machines.
- **Verified:** V0 (docs only).
- **Pending:** —
- **Next suggestion:** still #123.

## 2026-07-15 — #119: mime defaults must name a present .desktop (this commit)
- **Task:** BACKLOG #119 — `text/plain` named vscode, which the live ISO never
  ships (same GIO-skip trap as #94, one key over).
- **Did:** `text/plain` is now an ordered fallback
  `code.desktop` → `org.gnome.TextEditor.desktop` (HM tries the next when the
  preferred is missing). Live profile gains `mpv` too — video mime defaults
  had the same hole and the package is free via the template pin. Extended
  `checks.live-baseline-apps` so every `[Default Applications]` key has at
  least one listed `.desktop` on **live** (HM path + system path for thunar)
  and on the **default template install**.
- **Verified:** **V2** — check green; mimeapps.list renders
  `text/plain=code.desktop;org.gnome.TextEditor.desktop`; **proved to fail**
  when `text/plain` is a vscode-only singleton. V0 flake check green.
- **Pending:** V3 launch of a `.txt` on the Acer is covered by the existing
  #103 HARDWARE-QUEUE entry (now expects Text Editor for text files). Opting
  out of *both* vscode and Text Editor still leaves no handler — that is the
  delete-the-line convention, not a silent live-ISO bug.
- **Next suggestion:** #95 (Ghostty does not open) or #94 residual
  (test-install chromium launch).

## 2026-07-14 — #118: smartd self-gates; the doctor was right all along (this commit)
- **Task:** BACKLOG #118 — Bernardo's live ISO showed a red Waybar health icon
  reporting smartd.
- **Did:** smartd's DEVICESCAN finds nothing where no drive answers SMART and
  exits **17**, so systemd marks it failed, the doctor honestly reports a
  failed unit, and Waybar paints `@bad`. Everything downstream was correct —
  **smartd was the bug, not the doctor**. Added an `ExecCondition` running
  `smartctl --scan` (empty exactly when smartd would find nothing), so the
  unit goes **inactive** rather than failed. NOT `SuccessExitStatus = 17`,
  which would also swallow exit 17 on a machine that has drives — the reason
  the daemon exists.
- **Scope was never live-only:** smartd mkDefaults true everywhere and QEMU
  virtio has no SMART, so every VM install has been booting to a health
  warning, and every V2 run had been showing it as noise.
- **Verified:** **V2** — `checks.smartd-gate` boots the **real** distro module
  (not a restatement of it; `nixpkgs.config` needed `mkForce` to yield to the
  test's pkgs) and asserts both halves, since they pull opposite ways: a gate
  that never skips leaves the red icon, one that always skips silently kills
  drive-health monitoring — the failure nobody notices until a disk dies
  quietly. No-SMART node → `ActiveState=inactive`, not failed, absent from
  `systemctl --failed` (what the doctor actually reads); and the gate's logic
  driven against a scan that *finds* a device → exit 0, because QEMU cannot
  answer SMART honestly and pretending otherwise would test nothing. **Proved
  to fail** by unwiring the condition. flake check + doctor + hardware-toggles
  + live-baseline-apps + option-docs + state-bridges green.
- **Pending:** **V3** — that smartd still RUNS on real drives (dev box, real
  NVMe). Queued with an explicit fail-condition: if it skips there, revert the
  gate rather than tune it.
- **Next suggestion:** #119 (`text/plain` names vscode — the last of #94's bug
  class), or #95 (Ghostty does not open) at the top of NOW.

## 2026-07-14 — #121 reverted: the fix worked, the gain didn't survive measuring (this commit)
- **Task:** Bernardo's call on #121 (shipped 0b464af, reverted here): *"Let's
  leave it honestly. Sounds too much work for a negligible gain."*
- **Did:** removed the chromium overlay + `checks.chromium-single-closure`;
  template HM closure back to 9.38 GiB and the duplicate unwrapped chromium is
  back, deliberately. `overlays.default` carries a comment saying so, and the
  ROADMAP entry is now a **✗ decided-against** record holding the measurement,
  the working approach, and the commit to recover it from.
- **Why this was right:** the fix worked, but it substitutes into a
  `buildCommand` nixpkgs owns — coupling us to wrapper internals with a
  *silent no-op* failure mode, which is why it needed a permanent check too.
  That standing cost buys ~195 MiB of download and ~19 MiB of disk. Boring
  beats clever at that exchange rate (CONVENTIONS).
- **The lesson that outlives it, and the real product of the last two items:**
  **closure size is not disk size and is not image size — measure the
  artifact.** Removing a 687 MiB path moved the ISO by 8 KiB, because
  mksquashfs dedupes duplicate files and `auto-optimise-store` hardlinks them
  (the two paths share inodes — `stat`-verified; `du` counts 639 MiB for the
  pair vs ~620 for one). Only the wire can't dedupe. This is stamped on #120,
  whose entire size table is closure arithmetic — without it, "drop the pin,
  save 4 GiB" would have evaporated at the squashfs exactly like this did.
- **Verified:** V0 flake check green; revert confirmed by closure (the plain
  chromium is back, 9.38 GiB) rather than by reading the diff.
- **Next suggestion:** #118 (smartd self-gate).

## 2026-07-14 — #121: one chromium, not two — 687 MiB off every install (superseded by the revert above)
- **Task:** BACKLOG #121, found by measuring #103's ISO delta. Nothing was
  failing — that is the point; a duplicate browser breaks no build.
- **Did:** nixpkgs' chromium wrapper with `enableWideVine = true` (what the
  template ships, for DRM) runs the `-wv` copy but links `share/*` from the
  **plain** unwrapped build. A symlink is a store reference, so a second
  687 MiB chromium rode along for a directory of `.desktop` files nothing
  executes — on every installed machine, not just the ISO. `overlays.default`
  now moves the symlink source to the copy we already run; the template needs
  no change (lib.nix, the repo pkgs, and nixosModules all apply the overlay).
- **The check that decided the approach:** whether the `-wv` copy itself
  references the plain build. It is `cp -a` of it, so it plausibly would —
  and if it did, no wrapper change could drop the duplicate and the whole
  item was dead. Verified self-contained *first*, then fixed. Also verified,
  not assumed: `.override { enableWideVine = true; }` composes with the
  overlay's `overrideAttrs` (order usually matters — here it survives), so
  the template's existing line picks the fix up untouched.
- **Verified:** **V2** — only `-wv` + the tiny sandbox remain in the template
  closure. Equivalence proved, not hoped: the wrapper still execs the `-wv`
  build, WidevineCdm present there, `share/{applications,icons,man}` match
  stock with the `.desktop` **byte-identical**. New
  `checks.chromium-single-closure` asserts the invariant (exactly one full
  unwrapped chromium — plain or `-wv`, never both), **proved to fail** by
  neutering the overlay's regex: reports "found 2", names both, and warns off
  the tempting "fix" of dropping enableWideVine (which silently kills DRM).
- **I overstated this, and measuring the artifact corrected it.** I claimed
  −687 MiB "on every install". True savings, all measured:
  nominal closure −687 MiB (9.38→8.71 GiB), **ISO −8 KiB of 8.078 GiB**,
  **installed disk ~−19 MiB**, **cache-install download −195 MiB**. Two
  dedupes that closure arithmetic cannot see: **mksquashfs detects duplicate
  files** (the `-wv` copy is `cp -a` of the plain build, so the ISO stored the
  blocks once), and **auto-optimise-store hardlinks** identical files — the
  two paths share inodes (verified by `stat`; `du` counts 639 MiB for the pair
  vs ~620 for one). Only the wire can't dedupe: 195 MiB compressed NAR.
- **The rule this establishes:** closure size is not disk size and is not image
  size. **Measure the artifact.** Directly relevant to #120, whose whole size
  table is closure arithmetic — noted there.
- **So the fix is a *download* win** (which is what #120's netinstall wants)
  and ~nothing for today's offline ISO. Whether that is worth coupling an
  overlay to nixpkgs' wrapper internals is Bernardo's call — raised, not
  assumed; it is guarded and no-ops if upstream moves. Worth an upstream patch
  regardless (the wrapper should take its desktop entry from the variant it
  wraps).
- **Pending:** nothing hardware-blocked. #120's size table updated (desktop is
  13.34 GiB post-#121, not 14.01) and carries the closure-vs-artifact caveat.
- **Next suggestion:** #118 (smartd self-gate) — Bernardo's stated order.

## 2026-07-14 — #103: the live ISO gets a desktop, for +41 MiB (this commit)
- **Task:** BACKLOG #103 — Bernardo, live ISO 2026-07-14: still no browser,
  no office. *"That makes a live iso useful."*
- **Did:** the live HM user in `flake.nix` now names its own set — chromium,
  libreoffice-fresh, gnome-text-editor, amberol, snapshot. Firefox dropped
  (Bernardo's call): chromium owns the HTTP mime default and ships in the
  installed template, and it was the one item that would cost a real closure.
- **The finding that inverted the trade:** I asked Bernardo to choose on a
  ~+2 GB premise. Wrong premise — `system.extraDependencies` *already* pins
  the template's HM closure into the ISO for offline installs, so chromium/
  libreoffice/amberol were **already in the image's store**, just absent from
  the live user's profile. Verified, not assumed: the live and template
  chromium resolve to the *same store path*, so reusing the template's exact
  `chromium.override { enableWideVine = true; }` costs zero — a plain
  `chromium` would have been a second 2.5 GiB closure. Only text-editor and
  snapshot are new: 9 paths, 133 MiB uncompressed (99 MiB is gst-plugins-rs).
  His Firefox call still held for the right reason — it was genuinely unpinned.
- **Verified:** **V2** — measured ISO delta (the item's pass condition), both
  built from the same tree: **8.038 → 8.078 GiB = +41.2 MiB (+0.50%)**.
  New `checks.live-baseline-apps` asserts each app is on PATH *and* has a
  .desktop the launcher can see (a bin without one is invisible), that HTTPS
  resolves to a chromium entry that is actually **present** (#94's trap), and
  that Firefox has not crept back. **Proved to fail:** dropping snapshot makes
  it name the missing entry. flake check + live-install-entry + option-docs +
  template-sot green.
- **Pending:** **V3** — that the apps *launch* needs hardware (HARDWARE-QUEUE,
  Acer M5-481T: from the launcher only, no terminal). New **#119**: `text/plain`
  names vscode, which the live ISO never ships — #94's bug one mime key over,
  and now the last of its kind.
- **Where the 8 GiB actually goes** (Bernardo asked how Debian fits one in 4 GB;
  I first blamed the offline-install pin and **that was wrong** — measured, the
  pin is 4.02 of 18.03 GiB uncompressed, ~22%). The live *desktop itself* is
  **14.01 GiB**: libreoffice 1457 MiB, initrd 1369, linux-firmware 770, mesa,
  fonts, two chromiums (see #121). Debian fits ~4 GB by installing *by copying
  the live filesystem* (no second closure) and by sharing one library version
  archive-wide, where Nix pins exact deps per package. Compression is not the
  gap — we already use `zstd -19` (2.23:1). Bernardo's call: **keep the offline
  ISO as is**, add a netinstall variant → **#120** (NEXT). Filed **#121**: the
  Widevine chromium wrapper drags a *second* 687 MiB unwrapped chromium in for
  its `share/applications` alone — pre-existing, on every installed machine.
- **Next suggestion:** #118 (smartd self-gate) — Bernardo's stated order.

## 2026-07-14 — triage: #117 is not work, it's an input to #110 (this commit)
- **Task:** Bernardo's call on #117 (filed by the previous entry, which
  suggested it as the next item — it is not; that suggestion is stale).
- **Did:** Control Center is being retired (#110, NEXT, `[big]`), so fixing
  its toggles builds on a condemned surface. #117 stays filed as a *record*
  feeding #110's migration matrix, marked not-standalone. Gave #110 the facts
  #116 turned up: its Bluetooth/Printing rows write state keys that only
  became live in a9f3a64, so those two toggles never worked for anyone —
  meaning there is no behavior to preserve, but the keys and their bridges are
  real now and must not be dropped with the rows, or a machine whose state
  says `bluetooth.enable = false` silently flips back on at the next rebuild.
  `checks.state-bridges` will fail loudly if that happens.
- **Verified:** V0 — `nix flake check --no-build`. Backlog/journal only.
- **Next suggestion:** #110 (the retirement itself, needs splitting first) or
  #115 suspend-then-hibernate — both `[human]`-gated on a design question.

## 2026-07-14 — #116: the state bridges that never were (a9f3a64)
- **Task:** BACKLOG #116 — NixOS options defaulting from
  `config.nomarchy.settings`, an attribute that exists only on the HM side.
- **Did:** `nomarchy.settings` is declared in exactly ONE place
  (`modules/home/options.nix:412`), so `or <fallback>` had been swallowing a
  missing-attribute error on the NixOS side forever. Options now declare a
  *static* default and the implementing module reads the state via
  `state-read.nix` + `lib.mkIf (state != null) (lib.mkDefault state)` —
  done for `bluetooth.enable` (default.nix) and `services.printing.enable`
  (services.nix). `batteryChargeLimit`'s read is simply deleted: power.nix's
  oneshot reads the key with `jq` at *runtime*, which is why that menu worked.
  greeter.nix moved off a raw `fromJSON` onto the fail-closed reader.
- **The finding that mattered:** the item said "three options … benign today"
  and both halves were wrong. There were four (it grepped `options.nix`, not
  `modules/nixos/` — the same mistake in miniature as the bug it filed), and
  two were **live user-facing bugs**: Control Center's Bluetooth and Printing
  toggles wrote state nothing read and printed "requires rebuild", so they
  reported success and did nothing, for as long as they had existed.
- **Verified:** **V2.** Before/after eval on the *same* flipped state proves
  the bug was real: BEFORE `hardware.bluetooth.enable=true` + `printing=false`
  (toggle inert); AFTER both flip, and a hand-set value still outranks the
  state. Permanent guards, since nothing in a build fails when a bridge dies:
  `checks.state-bridges` (11 eval assertions — absent key → default, state →
  config, hand-set → wins) and `checks.printing-from-state`, a runNixOSTest
  whose node's ONLY input is the state file and which waits for a running
  `cups.service` (+ avahi). **The guard was proved to fail**: re-breaking the
  bluetooth bridge made it throw, naming both broken assertions — a check that
  cannot fail is worse than none (625b7e3's lesson, applied). V0 flake check;
  option-docs, template-sot, downstream-template-{system,home},
  installer-safety, hardware-toggles, battery-charge-limit all green.
- **Pending:** no V3 — the mechanism is fully proved headlessly. New **#117**
  (PROPOSED): the control-center toggles still leave the rebuild to the user,
  unlike every menu toggle since; a user can't tell "needs a rebuild" from
  "broken again", which is the symptom #116 just removed.
- **Next suggestion:** #117 (needs Bernardo's call: rebuild per-toggle vs one
  apply-at-exit), or #115 suspend-then-hibernate — also `[human]`-gated, and
  its state read now has a worked shape to copy.

> The five entries below were reconstructed from the commits on 2026-07-14
> after a session crash — the work was committed and pushed, and each commit
> did its own sync sweep (BACKLOG #115/#116, HARDWARE-QUEUE, docs); only the
> journal was missing. Written from the commit messages, not from memory.

## 2026-07-14 — one fingerprint switch, an auto-login toggle, the state bridge (eb38008)
- **Task:** Bernardo, post-reboot: "Use for login" was the wrong question —
  whether the finger works is one decision, and whether login prompts at all
  is a different one that was never in the menu.
- **Did:** System › Fingerprint is now a single on/off switch (enroll/list/
  verify/delete demoted to plumbing behind it) writing the one
  `settings.fingerprint.pam` key; `idle.nix` defaults `idle.fingerprint` from
  that same key, so lock screen and login/sudo move together. New System ›
  Auto-login (`nomarchy-autologin`), installer-seeded ON for LUKS machines.
  Making both state-owned surfaced two real bugs: `greeter.autoLogin`
  defaulted from `config.nomarchy.settings…`, an attribute that exists ONLY
  on the HM side, and `or null` swallowed it — the state path never worked on
  any machine ever built; and `state-sync get` printed Python's `None` for a
  JSON null, so any `case … null)` would miss.
- **Verified:** **V1** — flake check, installer-safety, option-docs pass.
  Proved by eval/build: a state carrying autoLogin yields greetd
  `initial_session`, template state yields none, a hand-set null beats the
  state; `fingerprint.pam=true` alone renders hyprlock's auth block; both
  tools bash -n clean and land in systemPackages; the get round trip prints
  `null`.
- **Pending:** V3 — HARDWARE-QUEUE "fingerprint + auto-login toggles on
  hardware" (T14s, after unpinning the two system.nix lines). Filed **#116**
  (two more NixOS options read the same phantom bridge — a grep for
  `config.nomarchy.settings` under `modules/nixos/` should return nothing when
  done) and **#115** (suspend-then-hibernate, `[human]` on delay/AC policy).
- **Next suggestion:** #116 — the worked example is in the tree and the two
  remaining reads are known.

## 2026-07-14 — a real lock bind, and a lock screen that speaks in sentences (9792976)
- **Task:** two nits from the post-reboot hardware pass.
- **Did:** Super+Escape never locked anything — there was no lock bind at all,
  only the power menu's Lock row and the idle timeout. Lock now lives at
  Super+Ctrl+L (bare Super+L collides with common app binds), dispatching
  `loginctl lock-session` — the power menu's path, so hypridle's `lock_cmd`
  stays the single place deciding what a lock looks like. The prompt's clipped
  fragments are sentences now, placeholder sentence-cased to match.
- **Verified:** **V1** — flake check; the built home generation carries the
  bind, the new ready/present messages (built with `idle.fingerprint` forced
  on, since the in-repo config leaves it off), and a cheatsheet row the
  generator produced on its own from the shared list.
- **Pending:** V3 — the reader itself only exists on hardware.
- **Next suggestion:** the fingerprint/auto-login menu surface (became eb38008).

## 2026-07-14 — put the fingerprint hint where it can be seen (fbbeac6)
- **Task:** e2de906 fixed the invisible reader and left an invisible message —
  Bernardo: "unlock worked, but still no message", same symptom as before the
  fix, completely different cause.
- **Did:** the lock screen is deliberately clock-only — the input field fades
  out while empty (hyprlock's `fade_on_empty` default), so `$FPRINTPROMPT` in
  its placeholder rendered faithfully into a widget nobody sees until they had
  already given up on the finger and started typing. Moved to a label; the
  placeholder goes back to its exact previous string, so the non-fingerprint
  path is unchanged.
- **The lesson:** I assumed the field was visible and reasoned from there. It
  has never been visible — "it has always been like this" is what cracked it.
  Both candidate surfaces (label vs `fade_on_empty=false`) were tested on
  hardware rather than picked; Bernardo chose the label, which keeps the
  clock-only look instead of making opting into fingerprint mean permanently
  gaining a widget the design never had.
- **Verified:** **V2** — flake check; checks.theme-contrast + option-docs pass
  (`subtext-on-base` is held to a 3.0 floor on all 21 themes by the guard that
  exists because two themes once shipped subtext == base — the same bug as
  this commit, one layer down). **V3** — Bernardo confirms the line reads.
- **Next suggestion:** the lock bind + prompt copy nits (became 9792976).

## 2026-07-14 — unlock by fingerprint; hyprlock never used the PAM flag (e2de906)
- **Task:** Bernardo tested the lock screen with `fingerprint.pam` on — sudo
  took a finger, hyprlock did nothing.
- **Did:** the PAM stack was never the problem (grosshack sits in
  `/etc/pam.d/hyprlock` at order 11400 exactly like sudo's) — hyprlock just
  never asks it: its PAM conversation runs only on submit, so a module wanting
  to prompt *while* polling the reader never gets a turn. It has its own
  fprintd-over-D-Bus backend behind `auth:fingerprint:enabled`, which nothing
  in the distro ever set. New `nomarchy.idle.fingerprint` (default false),
  mirroring `hardware.fingerprint.pam` the way `keyboard.layout` mirrors the
  xkb option — hyprlock is configured in standalone HM, which has no
  `osConfig`. Opt-in, because with no reader hyprlock advertises a scan that
  cannot happen.
- **Verified:** **V2** — flake check; checks.option-docs passes (failed first
  with "undocumented option", the check doing its job).
- **Pending:** V3 — Bernardo applies and confirms both factors work.
- **Next suggestion:** confirm the prompt actually renders (it did not —
  fbbeac6).

## 2026-07-14 — parallel fingerprint: 3 of 4 counts confirmed, 1 removed as a trap (625b7e3)
- **Task:** HARDWARE-QUEUE "parallel fingerprint-or-password on the real
  reader" (Bernardo, AMD dev box, gen 422, `fingerprint.pam = true`).
- **Did:** confirmed on the real reader: one prompt, typed password works,
  sensor works, wrong-finger ×3 falls back to password. The fourth count —
  "password keeps working with fprintd stopped" — is **removed rather than
  checked off**: fprintd is D-Bus activated, so `systemctl stop` does not keep
  it down. Bernardo ran it and the fingerprint still worked, which is exactly
  the evidence the daemon came back and the dead-reader path was never
  exercised. A check that passes whether or not the property holds is worse
  than no check. The property needs no hardware anyway: password is `auth
  sufficient` at 11700, independent of the fprintd rule at 11400 — visible by
  eval, asserted by checks.hardware-toggles in a reader-less VM.
- **Verified:** V3 (hardware, Bernardo) for the three counts; docs-only commit.
- **Pending:** still open on that item — hyprlock, the greeter (needs the
  reboot), and `fingerprint.parallel = false` restoring sequential behavior.
- **Next suggestion:** the hyprlock half (became e2de906).

## 2026-07-14 — docking round 7 FAILED; the keyword was inert, not raced (this commit)
- **Task:** HARDWARE-QUEUE docking round 7 result (Bernardo, AMD dev box) —
  5–6 consecutive unplugs, the panel never came back. Round 6's fix did
  nothing because round 6's diagnosis was wrong.
- **Did:** Probed it live instead of re-reading the journal. With ZERO enabled
  outputs, `hyprctl keyword monitor eDP-1,preferred,auto,1` is *inert* —
  accepted, `ok`, exit 0, and never flushed until a DRM event arrives. Not a
  race: inert across 4s and 5s in two runs. `dispatch forcerendererreload` is
  inert too; only `hyprctl reload` escapes (99ms / 289ms, cable out). So the
  transition issues the keyword, and escalates to `reload` only when
  `monitors` proves it inert — then re-asserts the rule and calls
  `nomarchy-keyboard-layout restore` per device, since a reload drops runtime
  `device[…]:kb_layout` keywords. Also made the menu's `enable` prove itself.
- **The lesson, worth more than the fix:** round 6 built its race theory on
  `enable-timeout` ×2 + `result=ok` ×1, and I extended it with a "phantom
  re-add" theory from the `added= DP-2` lines. Both were the same artifact:
  **every `result=ok` was Bernardo replugging the cable** because the screen
  was black — his hotplug flushed the queued rule and the next poll claimed
  the win. A watcher whose success is indistinguishable from the user working
  around it will keep confirming whatever story you bring. The probe cost 10
  minutes and killed two rounds of theory.
- **Verified:** V3 (partial) — the fixed `nomarchy-display-transition undock
  eDP-1` driven through two real unplugs on the dev box: panel on, workspaces
  1–3 home, 1.8s, `keyword=inert escalate=reload` → `enable=via-reload` →
  `result=ok`. Keyboard-restore proven too, on a K400 whose receiver is in the
  laptop (so it survives the cable): kept its remembered `us`, while a control
  bare `hyprctl reload` dropped it to the session `gb` — the clobber was an
  inference until measured, and it makes `restore_keyboards` load-bearing.
  Plus V2: `nix flake check --no-build` all pass; `checks.docking-ux` +
  `checks.dock-audio` + `monitor-fallback.nix` pass; shellcheck clean on
  display-transition (profile-watch's lone SC2125 is pre-existing).
- **Pending:** HARDWARE-QUEUE round 8 — the *watcher-driven* path needs a
  relogin (exec-once, baked store path) and repetition (5+ unplugs, one with
  the lid shut). New BACKLOG #114 (PROPOSED): tuigreet ignores per-device
  layouts.
- **Next suggestion:** #114, or the dock direction's unverified panel-off
  (it disables the panel trusting hyprctl's exit code — same class, latent).

## 2026-07-14 — docking round 6: undock left the laptop panel black (superseded by round 7 above)
- **Task:** HARDWARE-QUEUE docking round 6 result (Bernardo, AMD dev box).
  Plug side all passed (auto-dock, audio to the monitor, wallpaper on the
  external); unplugging left the panel black until re-docked.
- **Did:** Bernardo's journal was decisive — 3 undocks, 2×
  `result=enable-timeout` + 1× `result=ok`, so an intermittent race. Two
  fixes in modules/home/hyprland.nix: (1) the undock `hyprctl keyword monitor
  eDP-1,preferred,auto,1` exits 0 but is dropped when it lands mid-teardown
  of the departing external — now re-issued every poll for 5s, with
  `monitors` listing the panel as the only proof; (2) the watcher marked the
  departure handled via `"$TRANSITION" undock … || true` + `awaiting_lid_open=1`
  and `known_outputs` had already moved, so nothing ever retried — one lost
  keyword = a dark panel. The undock is now a queued invariant driven on the
  1s tick (6 attempts, then `result=gave-up`).
- **Verified:** V2 — `nix flake check --no-build` all checks passed;
  `nix build --impure -f tools/monitor-fallback.nix` exit 0 in 57s (reaches
  the final `lid-open transition` checkpoint); shellcheck clean on both
  generated scripts (the lone SC2125 is pre-existing + intentional).
  **Honest limit:** the harness CANNOT reach this race — QEMU's headless
  backend aborts Hyprland if the last active output is deleted while the DRM
  output is disabled, so it must undock before removing DP-1 and never
  collides the two. The fix itself is **V3 pending**; comment added at
  tools/monitor-fallback.nix so a reader doesn't mistake a pass for coverage.
- **Pending:** V3 → HARDWARE-QUEUE round 7 (unplug ≥5×; old failure was only
  ~2 in 3). Rounds 4–6 opened 5 counts: 4 are now confirmed fixed on hardware
  (wallpaper, auto-dock, monitor audio, keyboard prompting once — Bernardo),
  the undock is this commit, and **headphone jack-follow is still untested** —
  now its own entry, to fold into the round 7 session.
- **Next suggestion:** wait on round 7 before touching the dock path again.

## 2026-07-13 — #99 live-seed git-init; theme-switch failure filed as #113 (this commit)
- **Task:** BACKLOG #99 — false "state.json NOT git-tracked" doctor/
  Waybar warning in the live session; resolve the held question from Fable.
- **Did:** hosts/live.nix `nomarchy-seed-flake` now `git init`+`add`+`commit`s
  the seeded `~/.nomarchy` (like nomarchy-install.sh:654), so the live
  checkout is a committed repo. Also committed #94's live-posture comment in
  flake.nix (separate commit).
- **Verified:** V2 — focused serial-log VM run (`/tmp/nomarchy-v2-theme-99/`):
  doctor prints `✔ state.json is git-tracked` + `✔ flake checkout is
  clean`, `GIT_REPO_OK`/`TRACKED_OK` markers. Held question RESOLVED: the
  same run's offline gruvbox switch failed with a 1176-drv source rebuild,
  but a local drv-equivalence test proved the failing
  `bvl30ggn…home-manager-generation.drv` is BYTE-IDENTICAL path-seeded vs
  git-seeded → the git-seed is exonerated; the failure is a pre-existing
  ISO-pinning gap (unpinned non-default-theme generation), now filed as #113.
- **Pending:** #113 (offline theme-switch contract); #94 launch check; #97
  V2 click; #96 V3 + #101 round 2 (HARDWARE-QUEUE).
- **Next suggestion:** #113 — decide pin-all-themes vs scope the offline promise.

## 2026-07-13 — #96+#97 menu gates explain instead of vanish/raw-fail (this commit)
- **Task:** BACKLOG #96 (battery-limit row silently absent on threshold-less
  firmware) and #97 (raw "Connection to BlueZ failed").
- **Did:** rofi.nix — Battery limit row now gates on `has_system_battery`
  (like Power profile); the leaf explains a missing
  `charge_control_end_threshold` as a firmware capability. Bluetooth leaf
  checks `bluetooth.service` before exec'ing blueman and explains the
  service-down vs no-adapter (`/sys/class/bluetooth` empty) cases with
  rfkill/firmware hints.
- **Verified:** V1 — built nomarchy-menu, bash -n green, both new paths
  present in the generated script; V0 flake check green. VM can't fake a
  threshold-less battery or a BT adapter, so the behavioral checks are
  queued: #96 → HARDWARE-QUEUE (Acer + T14s row check); #97 → the no-adapter
  message is exercisable in the next QEMU run (planned with #94's
  test-install pass; VMs have no BT adapter).
- **Pending:** #97 V2 click-check in the next VM run; #96 V3 on the Acer.
- **Next suggestion:** #94 test-install run (chromium launch + BT click).

## 2026-07-13 — #107 direct System/Tools menu keybinds (this commit)
- **Task:** BACKLOG #107 — conflict-free direct bindings via keybinds.nix.
- **Did:** `SUPER+CTRL+I` → `nomarchy-menu system` (Super+I settings muscle
  memory), `SUPER+CTRL+T` → `nomarchy-menu tools` in keybinds.nix (single
  source: Hyprland + cheatsheet); root-menu Tools/System rows now carry
  `menuHint` shortcut hints.
- **Verified:** V2 — eval: 41 binds, zero duplicate mods+key; both binds in
  the generated Hyprland config; built nomarchy-menu bash -n green with
  cheatsheet rows + hints rendered. In-session on the live ISO: VIEWED
  `/tmp/nomarchy-v2-swap-93/40-system-menu.png` + `41-tools-menu.png` —
  both submenus open directly from the binds. V0 flake check green.
- **Pending:** nothing.
- **Next suggestion:** #99 pending the focused doctor/theme-switch VM run.

## 2026-07-13 — #93 installer swap prompt labeled + explained (this commit)
- **Task:** BACKLOG #93 — the swap field was a bare prefilled gum input (no
  visible section, label, or unit; the placeholder never shows once a value
  is prefilled).
- **Did:** `nomarchy-install.sh`: visible "Swap & hibernation" section, two
  info lines (default = RAM ⇒ hibernation-ready; 0 = no swap), a labeled
  `--header` on the input, and unambiguous info + Review lines
  ("N GiB swapfile (enables hibernation)" / "none (hibernation disabled)").
- **Verified:** V2 — full interactive KVM UI drive on the built ISO; VIEWED
  `/tmp/nomarchy-v2-swap-93/10-swap-prompt.png` (section, both info lines,
  labeled prefilled input) and `…/30-doctor.png` (Review box:
  "Swap: 6 GiB swapfile (enables hibernation)"); wipe confirm aborted by
  mismatch, nothing touched. bash -n + V0 flake check green.
- **Pending:** side-finding: QEMU exposed `/dev/fd0` as the FIRST picker row
  — recorded as live repro evidence on #112.
- **Next suggestion:** #99/#107 commits from the same evidence run.

## 2026-07-13 — charge-limit start-limit-hit on spaced AC storms (this commit)
- **Task:** Live V3 failure of #101 (TuringMachine doctor badge, 18:16): a
  spaced dock/AC event storm → 5 successful starts in 10s →
  `start-limit-hit`, unit marked failed though every run succeeded.
- **Did:** `unitConfig.StartLimitIntervalSec = 0` on
  `nomarchy-battery-charge-limit` (idempotent sub-second write — rate
  limiting only manufactures false failures); extended
  `checks.battery-charge-limit` with the exemption assert + an 8× sequential
  start loop and a clean-state check.
- **Verified:** V2 — the check passes under KVM (exit 0: exemption asserted,
  8 spaced starts, not failed, no start-limit-hit; original coalescing
  phase still green). V0 flake check green.
- **Pending:** V3 round 2 re-queued in HARDWARE-QUEUE (#101 entry updated).
- **Next suggestion:** land the in-flight #93/#99/#107 evidence run.

## 2026-07-13 — #102 Nextcloud Desktop opt-in in the template (this commit)
- **Task:** BACKLOG #102 — document upstream HM `services.nextcloud-client`
  as a downstream-template opt-in; no Nomarchy wrapper.
- **Did:** Commented `services.nextcloud-client` block in
  `templates/downstream/home.nix` (enable + startInBackground), and pointed
  the bare `# nextcloud-client` package line at it (service = app + session
  lifecycle; re-comment removes both).
- **Verified:** V1 — pinned HM 26.05 module confirmed
  (enable/package/startInBackground); eval test on an uncommented template
  copy via `lib.mkFlake`: unit ExecStart `nextcloud --background`,
  WantedBy/PartOf `graphical-session.target`. `nix flake check --no-build`
  green (includes template-sot + downstream-template-home).
- **Pending:** nothing — runtime lifecycle is the upstream module's contract.
- **Next suggestion:** #93/#99/#107 land next (shared live-VM evidence run).

## 2026-07-13 — User-promoted NEXT queue curation (#102–#112; this commit)
- **Task:** Audit Bernardo's live-ISO, application, menu, and device follow-ups
  against shipped behavior and curate the executable queue.
- **Did:** Promoted scoped work as NEXT #102–#112, with `[big]` split gates for
  System-menu IA and Control Center retirement. Reused NOW #96 for the Acer
  threshold finding; #106/#108 start from shipped navigation/bind coverage.
- **Verified:** V0 — read-only source/option/menu audit and `git diff --check`.
- **Pending:** #105 and #110 must be split before implementation; no runtime
  behavior changed.
- **Next suggestion:** #93 remains the NOW head; #102 is the first NEXT item.

## 2026-07-13 — #92 catalog-only installer keyboard selection (this commit)
- **Task:** Make installer layout/variant choices understandable without
  allowing arbitrary XKB values to break generated Nix.
- **Did:** Added a pinned human-readable XKB catalog, a one-step US/default
  path, strict searchable layout/variant pickers, shared exact validation for
  interactive and unattended input, and a resolved review summary.
- **Verified:** V2 — full flake eval; installer safety/keyboard/package builds;
  shell/Python/diff checks; real validator accepts valid layout-specific rows
  and rejects fabricated/prefix/wrong-layout values before installer setup.
  Exact-current-ISO KVM UI review captured catalog, variant, and review screens;
  strict unmatched input was rejected and the blank disk remained untouched.
- **Pending:** V3 real-hardware install remains; V2 artifacts are under
  `/tmp/nomarchy-v2-keyboard-92-current/`.
- **Next suggestion:** #93 swap-size prompt and explanation.

## 2026-07-13 — #91 installer no-variant normalization (this commit)
- **Task:** Stop the keyboard picker's display-only `(none)` value from
  breaking generated `xkb-console-keymap.drv` and the whole installation.
- **Did:** Normalize at the shared input boundary and defensively in the real
  template patcher. Added a `us` + sentinel fixture, actual console-keymap
  build, and made the offline installer VM carry the literal sentinel.
- **Verified:** V2 — full flake eval; focused installer-keyboard/safety/
  template builds; shell/Python/Nix syntax and diff checks; KVM
  `tools/test-install.sh` installed offline with LUKS + swap, powered off after
  ~400s, unlocked, and booted the themed desktop. Inspected install progress,
  LUKS, and first-boot captures under `/tmp/nomarchy-vm/`.
- **Pending:** #92 now explicitly forbids free-form XKB values. Expanded the
  existing per-device keyboard hardware check for the connected-at-login menu.
- **Next suggestion:** #92 installer keyboard selection clarity/safety.

## 2026-07-13 — #101 charge-limit AC-event burst recovery (this commit)
- **Task:** Fix `nomarchy-battery-charge-limit.service` failing when USB-C
  dock power events arrive while its settling write is still active.
- **Did:** The oneshot now returns inactive after success; immediate and
  delayed udev paths use coalescing `start` jobs instead of interrupting
  `restart` jobs. Strengthened the focused VM with a 12-event Mains burst
  during an extended active run and explicit failed/start-limit assertions.
- **Verified:** V0 `nix flake check --no-build`, module parse, and
  `git diff --check`; V2
  `nix build .#checks.x86_64-linux.battery-charge-limit --no-link -L`
  under KVM — the 12-event active-run burst finished inactive/success,
  changed InvocationID, and produced no failed/start-limit journal result.
- **Pending:** V3 post-rebuild dock-power burst check in HARDWARE-QUEUE.
- **Next suggestion:** run V3 #101 alongside docking round 4, then #91.

## 2026-07-13 — #100 dock transition recovery (this commit)
- **Task:** Fix the real T14s dangling workspace, closed-lid cable-removal
  suspend/apparent crash, and BenQ audio miss after 7ebfab4.
- **Did:** Added one atomic dock/internal-first undock helper; the IPC watcher
  owns a verified low-level lid inhibitor through physical lid-open, with
  validated stale-process cleanup. `monitoradded` now debounces a settled
  PipeWire/WirePlumber reprobe then picks an available dock sink; generic
  audio events never override a manual choice. Named clamshell profiles share
  the safe transition. Replaced the fallback VM tool with a bounded hotplug
  workspace/output/inhibitor test and strengthened generated-artifact guards.
- **Verified:** V2 — `nix flake check --no-build`; targeted docking-UX,
  option-docs, and template-SoT builds; and
  `nix build --impure -f tools/monitor-fallback.nix --no-link -L` all green.
  The KVM guest proved atomic workspace handoff, internal-first restoration,
  inhibitor retention with the simulated lid closed, and release on lid-open.
  The harness restores the DRM output before deleting QEMU's last synthetic
  headless output because that backend-specific zero-output state aborts
  Hyprland before a usable removal lifecycle is delivered.
- **Pending:** V3 exact closed-lid BenQ round 4 in HARDWARE-QUEUE. No
  real-hardware or real monitor-audio claim.
- **Next suggestion:** run V3 round 4, then take installer #91.

## 2026-07-13 — Live ISO hardware findings queued (#91–#99)
- **Task:** Transcribe Bernardo's Acer Aspire M5-481T install/session notes
  and installer failure photo into the executable queue.
- **Did:** Added nine separate NOW items: literal `(none)` XKB install
  failure, keyboard/swap installer clarity, missing browser, Ghostty launch,
  charge-threshold discoverability, BlueZ failure, Boreal button contrast,
  and the false-looking untracked-theme warning.
- **Verified:** V0 — exact backlog IDs/content reviewed; Nix failure's first
  causal error preserved separately from cascading dependency failures;
  `git diff --check` green.
- **Pending:** implementation and item-specific V1–V3 verification.
- **Next suggestion:** #91 — fix the installation-blocking keyboard variant.

## 2026-07-13 — Docking hardware-QA corrections (interactive feedback)
- **Task:** Bernardo's real clamshell QA: menu on hidden eDP, no keyboard
  layout route, audio stayed on speakers, laptop-off lost apps, abrupt
  undock never restored eDP.
- **Did:** Rofi → focused output; replaced eDP disable with safe Dock mode
  (move workspaces/focus, keep fallback); display watcher now unconditional,
  reconnecting and race-tolerant. Keyboard watcher runs by default with all
  XKB layouts + System › Keyboard. Audio also reacts to card/port changes;
  monitor-added re-applies the current awww wallpaper to the new output.
  Corrected the fallback harness assumption; added `checks.docking-ux` and
  synced README/template/ROADMAP/hardware queue.
- **Verified:** V1 — `nix flake check --no-build`; `checks.docking-ux`
  built the real HM generation and passed syntax/invariant/layout-catalog
  assertions; option docs 90/90, template SoT 4/4; Nix parse + Python
  compile + diff check. V2 unavailable (no readable KVM).
- **Pending:** hardware retest in HARDWARE-QUEUE (docking correction round 3).
- **Next suggestion:** hardware retest this exact dock sequence.

## 2026-07-12 — #89 slice 3: Documented Signal tray icon limitation (no commit)
- **Task:** BACKLOG #89 slice 3 — Signal's tray icon is full-colour blue and doesn't match the monochrome bar.
- **Did:** Researched Signal's Linux tray icon behavior. Found it is a hardcoded, dynamically generated pixmap (`Signal_status_icon_1`) driven by its Electron wrapper with no built-in toggle for a monochrome icon. Added an entry to `docs/ROADMAP.md`'s known issues noting that certain Electron apps cannot be natively themed. Closed out #89 from the backlog without introducing brittle `app.asar` repacking hacks.
- **Verified:** N/A (Documentation only).
- **Pending:** #89 is fully closed.
- **Next suggestion:** Pick the next item from the BACKLOG.

## 2026-07-12 — CI OOM: memory-bounded eval for check.yml/bump.yml
- **Task:** Bernardo: check.yml always red since he capped the runner
  container at 2 GB (uncapped it OOM'd the whole 4 GB VPS); bump.yml
  status unknown.
- **Did:** Measured (GNU time, eval-cache off): one-process `nix flake
  check --no-build` peaks **6.0 GB** RSS — that's the whole story. New
  `tools/ci-eval.sh`: same coverage, ONE output per nix process (heap
  freed between outputs); both workflows now call it. bump.yml also got
  max-jobs=1/cores=2. Facts: bump.yml WORKS — landed 8fded63 on
  2026-07-06, on schedule; ROADMAP's "no CI today" was stale → fixed.
- **Verified:** V1+ — ci-eval.sh ran green locally end-to-end (3:22);
  per-output cold peaks measured: worst check 0.99 GB
  (hardware-toggles), nomarchy toplevel 0.78 GB, HM 0.60 GB, **except
  nomarchy-live 2.69 GB** (live ISO eval) — fits only with the
  container's swap allowance (docker default --memory-swap = 2×memory);
  if Bernardo set swap off, that one output still OOMs → server-side
  one-liner (--memory=2g --memory-swap=6g) or a skip-hatch decision.
- **Pending:** real proof is the next push's Actions run on the VPS —
  watch nomarchy-live's step; can't be verified from this machine.
- **Next suggestion:** confirm green run on the remote, then hardware
  V3 batch.

## 2026-07-12 — parallel fingerprint-or-password (interactive, promoted)
- **Task:** Bernardo promoted the PROPOSED "fingerprint or password in
  parallel" item live; wants it default-on for fingerprint-PAM users.
- **Did:** Packaged pam-fprint-grosshack v0.3.0 (pkgs/, source-reviewed
  first: all failure paths → PAM_AUTHINFO_UNAVAIL, typed password only
  ferried via AUTHTOK to pam_unix try_first_pass — can't lock out
  password login). New `nomarchy.hardware.fingerprint.parallel`
  (default true; false = stock sequential). Wiring swaps stock fprintd's
  rule modulePath (mkForce) so ordering is inherited. README/template
  rows; BACKLOG PROPOSED entry closed into ROADMAP ✓.
- **Verified:** V2 — checks.hardware-toggles extended to three nodes and
  green: parallel node asserts grosshack line before pam_unix in
  /etc/pam.d/sudo AND `sudo -S` with the right password succeeds /
  wrong password fails with NO reader present (the lockout invariant);
  seqpam node = stock pam_fprintd, no grosshack; nopam = neither.
  Package builds (meson needed libpam-wrapper + python3). flake check
  green.
- **Pending:** V3 on the AMD dev box (HARDWARE-QUEUE): the actual
  type-or-touch race, fprintd-stopped fallback, hyprlock/greeter
  behavior after a fingerprint win (pthread_cancel'd prompt quirk).
- **Next suggestion:** dock + fingerprint V3 batch on hardware, then
  #89 slice 3.

## 2026-07-12 — undock blackout rescue (interactive follow-up)
- **Task:** Bernardo: "if I turn the laptop screen off and undock, does
  the panel come back?" Answer had to be tested, not recalled.
- **Did:** Built tools/monitor-fallback.nix (softGL desktop VM, crib of
  theme-shot): headless "external" + menu's exact disable + output
  remove. Verdict: Hyprland 0.55.4 does NOT re-enable → shipped
  `rescue_blackout` in the display hotplug watcher (immediate + retried
  keyword re-enable of disabled outputs on zero-active, toast).
- **Verified:** V2 for the hazard (VM reproduced the dead state);
  rescue itself is V1+components — the VM crashes before rescue can act
  in 4/5 runs (aquamarine ABRT in dispatchIdle on the zero-output
  state; control with panel active survives 5/5 — discriminator run).
  Crash predates rescue (run with rescue provably inert also crashed).
  Also fixed: `grep -c .` exit-1-at-zero disabling the rescue's own
  trigger. flake check green.
- **Pending:** real-dock V3 (HARDWARE-QUEUE, "nothing important open"
  warning); if real DRM crashes too → rework row (mirror) or wait for
  Hyprland bump; consider reporting upstream with the coredump.
- **Next suggestion:** dock V3 results, then #89 slice 3.

## 2026-07-12 — dock audio auto-follow + docked menu rows (interactive)
- **Task:** Bernardo's dock test (= #87's V3): audio stayed on speakers,
  had to hand-tweak EasyEffects; wants docked Display/Audio menu options.
- **Did:** Root-caused live on Newton: WirePlumber's stored default
  (default-nodes) outranks the #87 priority rules (control test: new
  sink, no watcher → default pinned), and the running gen lacked the
  rules anyway; EE 8 follows the default fine (kcfg default true +
  observed links move). Shipped `modules/home/dock-audio.nix`
  (`nomarchy.dockAudio.enable`, on): pactl-subscribe watcher
  set-default-sinks new dock-class sinks (regexes imported from
  dock-audio-rules.nix) + toast + startup sweep. Menu: Display gains
  laptop-off/screen-on/move-workspace/swap rows (live-only keywords);
  Audio gains self-gated "Send output → <dock sink>" row.
- **Verified:** V2-equivalent live on Newton — watcher: fake dock-named
  null sink → default followed, unplug → fell back, EE links moved
  mid-playback (pw-link); menu: generated script bash -n, dock-row jq
  exercised against real pactl JSON, dispatchers probed on live
  Hyprland. flake check green; option-docs green. Beware: kill the
  subscribe pipe by PID — pkill -f self-matches the test shell.
- **Pending:** real-dock V3 ×2 in HARDWARE-QUEUE (watcher may miss
  sinks that pre-exist and only flip route availability — capture pactl
  subscribe + sink JSON if it fails again).
- **Next suggestion:** #89 slice 3 (Signal pixmap) or dock V3 feedback.

## 2026-07-12 — auto-commit sweep before pull/rebuild/home (interactive)
- **Task:** Bernardo's request (no BACKLOG item): with autoCommit on,
  hand edits to `~/.nomarchy` (his had 4) stayed forever-dirty —
  state-sync's commit is pathspec-limited to state.json by design.
- **Did:** New internal `nomarchy-autocommit` in nomarchy-lifecycle:
  live-reads `settings.autoCommit`, commits *everything* dirty with
  message `nomarchy: auto-commit before <label>` + file list in the
  body; called (`|| true`, never blocks) at the top of nomarchy-pull /
  -rebuild / -home. Exposed as `passthru.autocommit`; package exported.
  Docs/comments swept: README, RECOVERY, ROADMAP ✓ entry, rofi toggle
  text, control-center prompt, state-sync docstring.
- **Verified:** V1+ — `nix flake check --no-build` green; new permanent
  `checks.lifecycle-autocommit` builds (real binary, sandbox repos:
  sweep/clean/off/non-repo); end-to-end on a *copy* of the real
  ~/.nomarchy via built nomarchy-rebuild with stubbed sudo/rebuild —
  the 4 real dirty files landed in one commit, tree clean. No VM boot:
  no session-visible surface. Real-machine confirmation lands after the
  next nomarchy-pull picks up this rev.
- **Pending:** nothing queued; Bernardo's actual ~/.nomarchy still has
  its 4 uncommitted files until his next pull/rebuild with the new rev.
- **Next suggestion:** back to BACKLOG (#89 slice 3, Signal pixmap).

## 2026-07-12 — #89 slice 2: EasyEffects tray icon themed monochrome (b98248f)
- **Task:** BACKLOG #89 slice 2 — EasyEffects SNI shows Papirus' full-
  colour blue equaliser, clashing with the monochrome tray.
- **Did:** Wrapped the resolved icon set in a `Nomarchy-icons` child
  theme (theme.nix) that Inherits it + ships a scalable, palette-`text`-
  coloured monochrome easyeffects glyph (the app's own -symbolic shape);
  made it the session icon theme. Single shared layer, so it covers all
  themes (all resolve to Papirus-*); colour tracks each theme via
  `checked.colors.text`. iconTheme feeds stylix+rofi, both fine via
  Inherits. Fixed a slip: `git add -A` swept a settings.local.json.tmp.*
  into the commit → untracked + gitignored (f9d5e2c).
- **Verified:** **V3 in Boreal** (Bernardo confirmed live). Mechanism
  pre-validated by GTK IconTheme lookup (override wins 16–32px; udiskie/
  nm/folder still Papirus). No theme uses a custom icon pack, so Boreal+
  Papirus covers the set. NB: cursor-warp doesn't refresh the tray —
  needs a waybar restart (new icon-theme cache).
- **Pending:** slice 3 — Signal (`:1.227`, generated pixmap, full-colour
  blue round icon); pixmap apps can't be fixed by name override.
- **Next suggestion:** #89 slice 3 (Signal) or close #89 if pixmap apps
  are accepted as documented-not-fixable.

## 2026-07-12 — tray menus truly fixed: counter-rules must live in waybar.css (8eb672b, 72e53e2)
- **Task:** Bernardo: icons still missing in the REAL nm-applet menu.
- **Did:** e8658fe's gtk.css cure worked in a plain GTK app but not in
  Waybar: Waybar adds its provider at GTK_STYLE_PROVIDER_PRIORITY_USER
  too (src/client.cpp) and wins the tie in practice (GTK 3.24) — proven
  by repro at priority 900 matching the live menu exactly. Only rules in
  the SAME stylesheet out-specify its `*`, so every waybar.css now
  carries a menu block after the reset: arrow 16px, check/radio 14px,
  separator 1px, `menu, menu *` font (labels are matched directly by
  `*`, inheritance never applies). D-Bus GetLayout first proved
  nm-applet exports per-network icon-data.
- **Verified:** **V3 on the real menu** — opened nm-applet's tray menu
  via wlrctl virtual-pointer clicks, grim captures: arrows, checkmarks,
  separators, Inter labels, Pica's icon, and every network's signal icon
  in the Available-networks submenu. Bar pills intact. NB: cursor-warp
  hover does not open GTK submenus (no motion events) — click the item.
- **Pending:** nothing for this item.
- **Next suggestion:** #89 slice 2 — the override-icon layer.

## 2026-07-12 — #90 correction: scoping regressed the bar; USER-priority cure (e8658fe)
- **Task:** Bernardo reports ac7e6bd broke Boreal's bar + icons still missing.
- **Did:** Two lessons. (1) `window#waybar *` carries id specificity, so
  the reset started *beating* the later class rules (pill radius/borders)
  that plain `*` correctly lost to — reverted all three stylesheets;
  arrows restored instead via stylix gtk.css (`menu menuitem arrow
  { min-width/height:16px }`) — USER priority (800) outranks waybar's
  APPLICATION (600) regardless of specificity, same mechanism as the
  existing check/radio rule. NB comments guard each `*` block.
  (2) The "missing icons" were also GTK3's `gtk-menu-images` default-off:
  set `gtk.gtk3.extraConfig.gtk-menu-images = 1`.
- **Verified:** **V3** — repro (no forced always-show-image): icon, check
  and arrow all render even with Boreal's reset injected screen-wide;
  bar screenshot shows pills restored. Flake check green.
- **Pending:** Bernardo eyeballs the real nm-applet menu (icons + arrow).
- **Next suggestion:** #89 slice 2 — the override-icon layer.

## 2026-07-12 — #76 closed: hibernate V3 PASSED on TuringMachine (this commit)
- **Task:** BACKLOG #76 `[human]` closure — Bernardo reports the V3 check.
- **Did:** Bernardo ran the real power-cycle on the T14s (LUKS + btrfs
  `@swap` swapfile): hibernate → power off → single LUKS unlock →
  session restored, "flawlessly". Deleted #76; HARDWARE-QUEUE resume
  entry pruned (outcome recorded here + ROADMAP ✓-entry updated);
  Decisions note marked shipped. The **no-swap Hibernate notify** check
  stays open in HARDWARE-QUEUE as the one leftover.
- **Verified:** V3 (human, real hardware). Docs-only commit otherwise.
- **Pending:** no-swap notify check (needs a swapless session or
  temporary `swapoff -a`).
- **Next suggestion:** #89 slice 2 — the override-icon layer.

## 2026-07-12 — #90 tray submenu arrows: root cause was waybar CSS (this commit)
- **Task:** BACKLOG #90 `[stuck]` (interactive, Bernardo driving on T14s).
- **Did:** Diagnosis was wrong — Papirus-Dark resolves `pan-end-symbolic`
  fine via its breeze-dark inheritance (breeze ships inside the nixpkgs
  papirus output). Real cause: Waybar's stylesheet applies process-wide,
  and the `* { min-height: 0; }` block collapsed the arrow node in the
  SNI tray menus Waybar hosts. Scoped the `*` block to `window#waybar`
  in boreal + executive-slate waybar.css and waybar.nix generatedStyle
  (tooltip rules restate the font — tooltips are separate toplevels);
  removed the go-next-symbolic remap workaround from stylix.nix.
- **Verified:** **V3** — live GTK3 repro app popping a dbusmenu-shaped
  menu, screenshotted via grim: arrow vanishes with old CSS injected
  screen-wide, renders with fixed CSS; property bisect pinned
  `min-height: 0` as the sole culprit. V1 flake check green.
- **Pending:** post-switch check that the real nm-applet menu shows the
  arrow (needs a human click on the tray icon).
- **Next suggestion:** #89 slice 2 — the override-icon layer.

## 2026-07-11 — interactive T14s session: migration follow-ups (50a5586…6967d89)
- **Task:** Escalation — Bernardo driving, live on the migrated T14s
  (first full session on real Nomarchy hardware; 7 commits).
- **Did:** autologin restored (migration missed the LUKS-machine line —
  fixed downstream + MIGRATION.md example); SUPER+0 → workspace 10;
  VPN tooltip names active VPNs (stuck-shield confusion was Tailscale);
  battery "Not charging" hold-band tooltip (+ 4 whole-swap themes);
  GTK submenu arrows (bundled SVG — Papirus lacks pan-end-symbolic and
  inherits uninstalled breeze); xdg.userDirs.setSessionVariables pinned
  (HM 26.05 flip); easyeffects gated on a live tray host (After=
  waybar.service was inert — waybar is supervisor-run, not a unit);
  #89 slice-1 SNI inventory recorded; #76 unblocked → `[human]`.
- **Verified:** **V3** throughout — live binds via `hyprctl binds`, GTK
  menu screenshot (arrow + check render), SNI watcher registration,
  deployed configs inspected; V1 flake check green on every commit.
- **Pending:** Bernardo: `nomarchy-rebuild` (autologin + new services),
  then the #76 hibernate power-cycle. #89 targets narrowed to
  EasyEffects (slice 2) + Signal (slice 3).
- **Next suggestion:** #89 slice 2 — the override-icon layer.

## 2026-07-11 — hotfix: Left=Back bound over rofi default → every menu dead
- **Task:** Escalation — V3 on the migrated T14s: all `nomarchy-menu`
  entry points (main, battery, power-profile, VPN) showed rofi's
  "Binding 'Left' is already bound" error dialog instead of a menu.
- **Did:** `rofi.nix` — release `kb-move-char-back` to `Control+b` before
  binding `-kb-custom-1 Left`, in both `rofi_menu` copies (menu + vpn).
  rofi hard-fails on duplicate bindings, and reports it in a GUI dialog,
  not stderr — stub tests and `bash -n` can never catch this class.
  Filed PROPOSED: EasyEffects 8 blue tray icon; parallel finger/password.
- **Verified:** **V3** — real rofi on the T14s: old flags reproduce the
  error dialog (screenshot), fixed flags render the menu; after HM
  switch, `nomarchy-menu` renders end-to-end. V0 flake check green.
- **Pending:** flatpak apps missing from drun = stale session env
  (relogin), not a code bug. EasyEffects icon + pam items in PROPOSED.
- **Next suggestion:** V3 sweep of the remaining 05bab55 session items.

## 2026-07-11 — post-migration fix batch (05bab55; entry backfilled)
- **Task:** Escalation — first real-hardware migration (TuringMachine →
  Nomarchy) surfaced a batch of annoyances; session crashed before
  journaling, entry written by the follow-up session.
- **Did:** Force fprintAuth off unless `fingerprint.pam`; first-boot toast
  waits for swaync + retries; night-light handles masked units; VPN menu
  `rofi_menu` no-op fixed; ← = Back in list menus; GTK menu contrast;
  mic-mute LED udev rule + sync; EasyEffects after tray; MIGRATION.md notes.
- **Verified:** V0 flake check; checks.first-boot; checks.hardware-toggles.
- **Pending:** V3 on the migrated machine. Follow-up found the downstream
  `~/.nomarchy` lock still pinned the *previous* commit — the user's rebuild
  excluded this batch (mic LED "regression" was just that). Also: HM
  `programs.chromium.extensions` from the old config wasn't carried over, so
  Chromium externally-uninstalled all extensions **and their data** (wallets
  included); recovered from the `pre-nomarchy-home` btrfs snapshot.
  MIGRATION.md browser note rewritten with the real mechanism.
- **Next suggestion:** V3 sweep of the 05bab55 items after a locked rebuild.

## 2026-07-11 — #88 neon-glass polish / finish
- **Task:** NEXT #88 — finish neon-glass (`VISION § D`), not quarantine.
- **Did:** Rewrote `themes/neon-glass/rofi.rasi` (was invalid `var()` CSS;
  now Boreal-style glass with `@colors`, cyan edge, soft selection).
  btop accent titles/selection; JSON ui rounding 14 + slight glass
  opacity; `preview.png` from theme-shot desktop. No waybar.css.
- **Verified:** **V2** — contrast 24×7 pass; theme-wholeswap green;
  theme-shot neon-glass desktop+menu viewed (cyan bar + glass menu);
  boreal theme-shot regression green (menu/bar intact).
- **Pending:** —
- **Next suggestion:** post-install hardware hints PROPOSED, or human
  HARDWARE-QUEUE.

## 2026-07-11 — Promote neon-glass → NEXT #88 (Bernardo)
- **Task:** human triage: promote neon-glass PROPOSED.
- **Did:** NEXT **#88** neon-glass polish/finish (`VISION § D`); path =
  **finish** (not quarantine). Notes: no broken waybar.css reintro without
  wholeswap parity; V2 theme-shot required. PROPOSED pitch removed;
  VISION pointers updated. **No implementation** this commit.
- **Verified:** V0 (backlog/docs only).
- **Pending:** —
- **Next suggestion:** implement #88 (theme-shot V2).

## 2026-07-11 — VISION hygiene + PROPOSED (queue empty)
- **Task:** QA/docs loop — NEXT still only #76 `[blocked:hw]`; no product
  item to implement without inventing work.
- **Did:** Marked Theme A/B/C shipped rows in `docs/VISION.md` (Firmware
  #43, Fingerprint #55, doctor stack, rebuild errors #56, Boreal default,
  hibernate confidence #77). Rewrote “Suggested agent slices” to open
  work only. PROPOSED: post-install hardware hints; neon-glass
  finish/quarantine `[human]`.
- **Verified:** **V0** — docs/backlog only (no module change). Prior
  iteration’s V2 suite still stands; no regression claimed here.
- **Pending:** human triage of PROPOSED; HARDWARE-QUEUE V3.
- **Next suggestion:** promote post-install hints → NEXT, or human
  HARDWARE-QUEUE / neon-glass call.

## 2026-07-11 — QA sweep (queue empty but #76)
- **Task:** QA sweep — NEXT only has #76 `[blocked:hw]`; no other
  actionable items.
- **Did:** option-docs (88 opts) + template-sot green; HM generation has
  What changed? / doctor float / first-boot unit; rotated JOURNAL.md
  41→18 entries (tail → JOURNAL-ARCHIVE). No product code change.
- **Verified:** **V2** — `nix flake check --no-build`; rebuilt green:
  doctor, first-boot, what-changed, clamshell-logind, dock-audio,
  windowrule-syntax, battery-notify, zram-swap, nightlight-geo.
- **Pending:** #76 V3 hibernate on Nomarchy hardware; #86/#87 V3 dock QA.
- **Next suggestion:** HARDWARE-QUEUE pass by human, or promote LATER
  wallpapers split / triage PROPOSED.

## 2026-07-11 — #87 dock audio sink priority
- **Task:** NEXT #87 — default audio follows dock plug/unplug.
- **Did:** WirePlumber `90-nomarchy-dock-audio` (HDMI/DP session prio
  1100, USB 1050) via `modules/nixos/dock-audio-rules.nix` + default.nix
  pipewire. `checks.dock-audio` pure rule asserts + conf-file VM. V3
  hotplug steps in HARDWARE-QUEUE. Closed #87.
- **Verified:** **V2** — `nix flake check --no-build`; `checks.dock-audio`
  green (pure rule asserts + conf installed under wireplumber.conf.d).
- **Pending:** V3 real HDMI/USB-dock hotplug (HARDWARE-QUEUE).
- **Next suggestion:** QA sweep or LATER wallpapers split if promoted.

## 2026-07-11 — #86 clamshell HandleLidSwitchDocked
- **Task:** NEXT #86 — lid closed + external monitor must not suspend.
- **Did:** Explicit `HandleLidSwitchDocked=ignore` in
  `modules/nixos/power.nix` (logind settings.Login). Undocked lid
  behaviour unchanged. idle.nix comment corrected. `checks.clamshell-logind`
  asserts conf + active logind. V3 steps in HARDWARE-QUEUE. Closed #86.
- **Verified:** **V2** — `nix flake check --no-build`;
  `checks.clamshell-logind` green (`HandleLidSwitchDocked=ignore` +
  logind active).
- **Pending:** V3 real lid+external on laptop (HARDWARE-QUEUE).
- **Next suggestion:** #87 dock audio sink follow.

## 2026-07-11 — #85 hardware hall of fame + split #84
- **Task:** NEXT #85 docs hall of fame; #84 was `[big]` → split.
- **Did:** HARDWARE.md §9 install-tested table (T14s Gen 4 AMD, Latitude
  5410/5310) + DMI PR invite; renumbered following sections. #84 → #86
  clamshell + #87 dock audio. VISION/ROADMAP; closed #85.
- **Verified:** V0 (docs only).
- **Pending:** —
- **Next suggestion:** #86 clamshell or #87 dock audio (both product).

## 2026-07-11 — #83 doctor first-boot pre-activate flag
- **Task:** NEXT #83 — doctor points at failed installer HM pre-activate.
- **Did:** doctor row: if `/var/log/nomarchy-hm-preactivate.log` is present
  and no Home Manager generation exists → ✖ +
  `home-manager switch --flake ~/.nomarchy -b bak`. Skip/ok otherwise.
  Extended `checks.doctor` (fail arm + recovered gen). Closed #83.
- **Verified:** **V2** — checks.doctor green (fail arm + recovered gen);
  local smoke with NOMARCHY_HM_PREACTIVATE_LOG / empty HOME.
- **Pending:** —
- **Next suggestion:** #85 docs hall of fame (cheap) or split #84 dock.

## 2026-07-11 — #82 generation readability
- **Task:** NEXT #82 — plain-language “what changed last rebuild”.
- **Did:** `pkgs/nomarchy-what-changed` (nvd → “N added, M removed, K
  updated”); wired into lifecycle post-`nomarchy-rebuild` / `nomarchy-home`
  (full nvd + notify toast); System › What changed? (toast + floating
  full report). `checks.what-changed` with NOMARCHY_NVD fixture. Closed #82.
- **Verified:** **V2** — checks.what-changed green; local smoke on real
  HM gens (“Desktop: 1 added, 1 removed”) + fixture counts.
- **Pending:** —
- **Next suggestion:** #83 doctor pre-activate fail flag or #85 docs.

## 2026-07-11 — #81 first-boot welcome toast
- **Task:** NEXT #81 — dismissible “you're set” card (`VISION § B`).
- **Did:** `pkgs/nomarchy-first-boot` + user oneshot
  (`modules/home/first-boot.nix`): notify once with SUPER+M/T/? + Network
  + Doctor pointers; marker `settings.firstBootShown` via theme-sync
  `--no-switch` (in-checkout state). Skip live hostname. Option
  `nomarchy.firstBootWelcome.enable` (default on). `checks.first-boot`.
  ROADMAP/VISION/README; closed #81.
- **Verified:** **V2** — `nix flake check --no-build`; `checks.first-boot`
  green (toast + marker + silent re-run + live skip); local shim smoke;
  downstream-template-home carries the user unit.
- **Pending:** V3 — real swaync rendering on first installed login
  (toast body is pure text; not queued unless it misbehaves).
- **Next suggestion:** #82 generation readability or #85 docs hall of fame.

## 2026-07-11 — #80 doctor battery health readout
- **Task:** NEXT #80 — battery health in `nomarchy-doctor` (`VISION § C`).
- **Did:** Doctor row: `cycle_count` + retained capacity
  (`charge_full`/`design` or `energy_*`) as `% of design`; system
  batteries only; soft warn &lt;70% design; skip when attrs absent.
  `NOMARCHY_POWER_SUPPLY_ROOT` for the VM fixture. Extended
  `checks.doctor` (fake BAT0 + Device-scope MOUSE0 ignore). HARDWARE §10,
  ROADMAP ✓, VISION ✓; closed #80.
- **Verified:** **V2** — `nix flake check --no-build`; `checks.doctor`
  green; local smoke on real BAT0 (342 cycles, 81% via energy_*) + fixture.
- **Pending:** Power-menu surface left optional/out of scope (doctor is
  the product surface for report-only health).
- **Next suggestion:** #81 first-boot welcome card (highest product ROI)
  or #85 docs-only hall of fame.

## 2026-07-11 — Triage: VISION-gap PROPOSED → NEXT #80–#85 (Bernardo)
- **Task:** Bernardo promoted five of the six fresh VISION-gap pitches
  (all but the wallpapers split, which stays LATER).
- **Did:** NEXT gains #80 battery health readout, #81 first-boot welcome
  card, #82 generation readability, #83 doctor pre-activate fail flag,
  #84 dock life polish `[big]`, #85 hardware hall of fame. PROPOSED
  placeholder left. **No implementation started** (explicit instruction).
- **Verified:** V0 (backlog only).
- **Pending:** —
- **Next suggestion:** #80 (smallest) or #81 (highest product ROI); #84
  must be split first.

## 2026-07-11 — Quick wins: exit-node locations + geo tooltip
- **Task:** BACKLOG LATER "VPN exit-node richer display" + a geo-mode
  tooltip nit from the previous iteration (human asked for quick wins).
- **Did:** Tailscale Exit node picker rows now "hostname — Country City",
  sliced by the header's fixed-width column offsets (COUNTRY/CITY are
  multi-word; never field-split), locationless nodes stay bare, pick strips
  back to hostname at the first space. Nightlight Waybar tooltip says
  "follows your location" in geo mode. Queue's VPN entry updated.
- **Verified:** V1 — awk proven against a fabricated 3-case fixture
  (Mullvad 1-word + multi-word city + bare node); flake check exit 0;
  downstream-template-home builds (writeShellScriptBin syntax-gates both
  scripts). Real tailscale output is V3 (existing VPN queue entry).
- **Pending:** V3 VPN menu live paths (extended, not new).
- **Next suggestion:** wallpapers artifact split (LATER, decided).

## 2026-07-11 — Night-light geo mode (lat/long → wlsunset)
- **Task:** BACKLOG LATER "Night-light geo mode" (human-picked).
- **Did:** `nomarchy.nightlight.latitude`/`.longitude` (both set = geo mode):
  `services.wlsunset` (location-computed sunrise/sunset, `.temperature` →
  night temp) replaces hyprsunset; the toggle script's `unit=` and the
  live-state ExecCondition follow via one `unit` let-binding. Template
  example lines, README row, ROADMAP ✓. New `checks.nightlight-geo`:
  pure-eval asserts on the rendered units (coords + temp in ExecStart,
  ExecCondition present, hyprsunset off). Declarative-only (no menu writer —
  coords are machine config like keyboard layout).
- **Verified:** V1 — `nix flake check --no-build` exit 0 (evals the new
  asserts + option-docs); built `downstream-template-home` (default
  hyprsunset path unregressed) + the check drv. Caught en route: HM renders
  `ExecStart` as a list (`toString` needed in eval asserts).
- **Pending:** V3 queued (HARDWARE-QUEUE § Any machine): visible warm shift
  at location night + toggle/persistence on the swapped unit.
- **Next suggestion:** wallpapers artifact split (LATER, decided).

## 2026-07-11 — Sync-sweep rule + grooming + zram user docs (human-requested)
- **Task:** Bernardo: groom stale items and codify the check so they don't
  recur; close the zram user-doc gap.
- **Did:** LOOP.md §5 gains step 4 **Sync sweep** (grep item/feature across
  agent/+docs/ on ship; prune recorded `[x]` queue entries) + an AGENTS.md
  rule line. Applied it: BACKLOG PROPOSED pruned (auto-theme pitch was
  stale — shipped as #79; shipped/promoted placeholders removed; v1.0
  pointer rewritten; FUTURE promoted-stub dropped). HARDWARE-QUEUE pruned
  29 checked `[x]` entries (363→~250 lines), dangling refs reworded,
  prune rule in header. README §3 now documents the zram default +
  hibernation out-of-the-box + MIGRATION pointer.
- **Verified:** V0 (docs/meta only) — grep sweep for dangling refs clean.
- **Pending:** night-light geo mode next (same session).
- **Next suggestion:** wallpapers artifact split (LATER, decided).

## 2026-07-11 — Vendor-neutral agent docs restructure (human-requested)
- **Task:** Bernardo: de-vendor the agent instructions (multi-model repo),
  clean stale files, better docs/agent/.claude structure.
- **Did:** New root `AGENTS.md` entry (CLAUDE.md → symlink). Skill bodies
  moved to `agent/`: VERIFICATION.md (enforcement, ex-nomarchy skill),
  DELEGATION.md (tiers light/standard/frontier + scout/runner contracts +
  fan-out; consolidates CLAUDE.md table + LOOP §economy + skill §6.5),
  THEME-DESIGN.md (ex-theme skill, now with frontmatter’d shim). `.claude/`
  is a thin adapter (shims route into `agent/`). Maps updated (agent/,
  docs/, README tree). Stale: `.gitignore` old_distro entry dropped; local
  result*/__pycache__ removed. JOURNAL rotated (29 kept, 120 →
  JOURNAL-ARCHIVE.md).
- **Verified:** V0 — `nix flake check --no-build` exit 0 with the symlink
  tracked; vendor-name grep clean outside DELEGATION's mapping table.
- **Pending:** nothing; other harnesses add their own adapter row when used.
- **Next suggestion:** #76/#79 remain [blocked:hw]; otherwise groom PROPOSED.

## 2026-07-11 — #76 checks.hibernate-swapfile (VM-verify the swapfile+offset)
- **Task:** Bernardo — push #76's swapfile-resume residual into a VM (like
  the auto-theme sunset trick).
- **Did:** Tried a full runtime-swapfile hibernate→resume test; it hit a
  bounded, definitive **`CanHibernate=no`** — systemd won't hibernate to a
  swap that isn't declared in the boot config, and a runtime-computed
  `resume_offset` can't reach the boot cmdline (the genuine chicken-and-egg).
  First cut also had an **unbounded `wait_for_shutdown`** that hung → killed
  it, bounded the wait in a thread (180s) + a `CanHibernate` pre-check, which
  is what surfaced the clean verdict. Pivoted `checks.hibernate-swapfile` to
  what a VM *can* prove deterministically and the partition test can't: the
  installer swapfile **build + offset** — NOCOW `mkswapfile`, file-type
  swapon, valid `map-swapfile -r` offset accepted by `/sys/power/resume_offset`,
  zram above it. (Device fix: `/dev/vdb` once `useNixStoreImage` was dropped.)
- **Verified:** **V2 PASS** (fresh build, exit 0). BACKLOG/ROADMAP updated;
  #76 residual narrowed to the literal encrypted power-cycle (dev box runs it).
- **Lesson:** the sunset-trick spirit applies but not always the full cycle —
  when systemd/kernel gates the behaviour (CanHibernate), verify the
  *construction* deterministically and leave only the physical cycle to HW.
  Always bound `wait_for_shutdown` (no timeout param → thread + join).
- **Next suggestion:** #76/#79 both `[blocked:hw]`; QA / PROPOSED otherwise.

## 2026-07-11 — Code-review fixes (parallel review of the session batch)
- **Task:** Bernardo asked for parallel work; spawned a fresh-eyes opus
  review of `2055842..HEAD` (static). It found the batch sound — 2 real LOW
  edge bugs, 1 false positive, 1 cosmetic.
- **Did:** (1) `cmd_auto` — inverted sunrise/sunset (sunrise > sunset) went
  perpetually day; replaced the `not between` shortcut with a real
  wrap-around `mins >= sunrise or mins < sunset`, so a cross-midnight day
  window switches both ways. (2) `rofi.nix` autotheme HH:MM guard tightened
  `[0-2][0-9]` → `[01][0-9]|2[0-3]` so 24–29 are rejected. Dismissed the
  `breeze` finding (papirus DOES ship breeze/breeze-dark — verified) and the
  cosmetic doctor-RAM one.
- **Verified:** py_compile; boundary truth-table (normal + wrap-around);
  HH:MM case table (23:00/19:59/20:00 accept, 24/25/30:00 reject); `auto
  --which` normal-case unchanged; menu bash -n; flake check green. Normal
  case unchanged so `checks.auto-theme` still holds.
- **Pending:** hibernate-swapfile VM test still running (uncommitted flake.nix).
- **Next suggestion:** land hibernate-swapfile once its VM test returns.

## 2026-07-10 — #79 V2: checks.auto-theme (simulated sunset) — was mis-framed V3
- **Task:** Bernardo challenged the "V3 real-session" framing — a simulated
  sunset IS VM-testable. He's right; I'd conflated "needs a real desktop"
  with "needs the generic home-manager switch."
- **Did:** Added `checks.auto-theme` runNixOSTest: installs nomarchy-state-sync,
  points `NOMARCHY_DEFAULT_THEMES` at presets, stubs the switch via
  `NOMARCHY_REBUILD` (a marker), configures the pair+schedule, then **walks
  the VM clock** 19:55→20:05→20:06→next-day 07:30 asserting slug goes
  summer-day → night (1 rebuild) → night (idempotent, still 1) → day (2). The
  self-gating I relied on: presets via env, wallpaper/notify no-op headless,
  run_switch honors NOMARCHY_REBUILD + waybar nudge is check=False.
- **Verified:** **V2 PASS** — `nix build .#checks…auto-theme` fresh, exit 0.
  Dropped #79's `[blocked:hw]`; it's done (V2). Updated ROADMAP.
- **Lesson:** don't default "timer/session feature" to V3 — stub only the
  generic step (the real HM switch, covered elsewhere) and simulate the rest
  (clock) headlessly. Only the literal timer-fires-on-OnCalendar is on-HW,
  and its schedule is systemd-analyze-validated.
- **Next suggestion:** NEXT head #76 `[blocked:hw]`; else QA / PROPOSED.

## 2026-07-10 — #79 slice 3: Look & Feel › Auto theme menu (#79 complete)
- **Task:** BACKLOG #79 slice 3 — the menu; completes the feature.
- **Did:** `rofi.nix` — "Auto theme (on/off)" row in Look & Feel + an
  `autotheme)` submenu: toggle, pick day/night (from
  `nomarchy-state-sync list`), set sunrise/sunset (HH:MM input, validated).
  Enable writes the flag + defaults the summer pair `--no-switch`, then
  `exec auto --force` — one rebuild installs the timer AND applies the right
  theme. Disable is instant `--no-switch` (`auto` self-gates; the lingering
  timer no-ops). Day/night/times are live `--no-switch`.
- **Verified:** **V1** — HM generation builds (`writeShellScriptBin` bash -n
  on nomarchy-menu passes) + bash -n on the built menu. **Logic test**
  (stubbed rofi/theme-sync/notify): selecting "Auto theme (off)" runs exactly
  `set day summer-day`, `set night summer-night`, `set enable true`
  (all `--no-switch`), then `auto --force` — the intended one-rebuild path.
  Disable path is static-clear (set false `--no-switch`, no `auto`); a
  stateful-stub dynamic test would loop (stub doesn't reflect the flip). V0
  flake check green. **#79 collapsed to `[blocked:hw]`** (all 3 slices done;
  only live-session switch remains, needs a Nomarchy machine).
- **Pending:** V3 real-session (timer switches day↔night across sunset).
- **Next suggestion:** NEXT head #76/#79 both `[blocked:hw]`; QA/PROPOSED.

## 2026-07-10 — #79 slice 2: auto-theme systemd timer
- **Task:** BACKLOG #79 slice 2 — the timer/hook.
- **Did:** `modules/home/autotheme.nix` (imported in home default.nix):
  `systemd.user.{service,timer}.nomarchy-auto-theme`, install gated on
  `config.nomarchy.settings.autoTheme.enable or false` (state-driven, like
  updates.nix gates on its enable). Service runs `nomarchy-state-sync auto`
  with a PATH for `home-manager`; timer `OnStartupSec=1min` +
  `OnCalendar=*:0/15` + `Persistent`. Chose periodic over exact
  sunrise/sunset timers — robust to suspend/DST, and `auto` is idempotent so
  ticks are cheap (rebuild only at the transition).
- **Verified:** **V1** — real-config eval (state temp-enabled, restored):
  `settings.autoTheme.enable`→true, timer `OnCalendar`=`*:0/15`, service
  ExecStart=`… nomarchy-state-sync auto`; disabled→no units (isolation eval
  condition=false). `systemd-analyze calendar '*:0/15'` valid. **V0** flake
  check green. (Gotcha: a new .nix file is invisible to the flake until
  `git add` — flake eval only sees tracked files.)
- **Pending:** live timer→`home-manager switch` on a real Nomarchy session
  (needs a Nomarchy machine, like #76). Slice 3 (menu) next.
- **Next suggestion:** #79 slice 3 — Look & Feel › Auto theme menu.

## 2026-07-10 — #79 slice 1: auto time-of-day theme CLI primitive
- **Task:** Split BACKLOG #79 (`VISION § D`, `[big]`) into 3 slices (CLI /
  timer / menu); took slice 1.
- **Did:** `nomarchy-state-sync auto` — reads
  `settings.autoTheme.{enable,day,night,sunrise,sunset}`, computes day/night
  by the local clock, and applies the matching preset **only if different**
  (idempotent → the slice-2 timer won't rebuild needlessly). `--which`
  prints the decision without switching; `--force`/`--no-switch` too.
  Factored `cmd_apply`'s core into `apply_named` so `auto` reuses the exact
  same one engine (GOALS: no second pipeline).
- **Verified:** **V1** package builds; functional tests on the built bin
  against scratch state: wide-day→day slug, 00:00–00:01 window→night,
  disabled→no-op, missing day/night→die, `auto --no-switch` writes state,
  re-run→"already on" no-op, plain `apply` still writes (refactor intact).
  **V0** py_compile + flake check + `checks.state-sync-validate` green.
- **Pending:** slices 2 (systemd sunrise/sunset timers + login apply, V2)
  and 3 (Look & Feel menu) remain under #79.
- **Next suggestion:** #79 slice 2 — the timer/hook (V2 runNixOSTest).
