# 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:** 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-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 (<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 < 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 < 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 ` 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 `theme-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 `theme-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 "theme-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 `✔ theme-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 → " 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 — theme-sync's commit is pathspec-limited to theme-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