Files
Nomarchy/agent/JOURNAL.md
Bernardo Magri a09399702d
All checks were successful
Check / eval (push) Successful in 3m44s
docs: REQUIREMENTS.md — OpenGL 4.3 floor, disk/RAM planning
Acer M5-481T fails Ghostty because Ivy Bridge HD 4000 tops out at
OpenGL 4.2 in Mesa; Ghostty needs 4.3 — a generation ceiling, not a
missing driver. Document that plus UEFI/x86_64, RAM, and storage floors
(Nix store generations + BTRFS snapper + hibernate swap≈RAM). Linked
from README, docs map, HARDWARE.md; #95 points here.
2026-07-15 09:40:17 +01:00

80 KiB
Raw Blame History

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 35 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 (history, never edited).

Template:

## YYYY-MM-DD — <task title> (<commit sha, or "no commit">)
- **Task:** which BACKLOG item (or QA sweep / bootstrap / escalation)
- **Did:** what changed, in 24 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 — 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.desktoporg.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: V2checks.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 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) — 56 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 13 home, 1.8s, keyword=inert escalate=reloadenable=via-reloadresult=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 46 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+commits 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+Inomarchy-menu system (Super+I settings muscle memory), SUPER+CTRL+Tnomarchy-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 V1V3 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 <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, theme-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 1632px; 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 iterations 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: V2nix 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: V2nix 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: V2nix 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: V2nix 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 <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: V2nix 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 frontmatterd 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 2429 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-theme-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 PASSnix 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-theme-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-theme-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-theme-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-theme-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:0000: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.theme-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).