# 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. 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-05 — low-battery notifications (iteration #24, item 13 slice) - **Task:** BACKLOG NEXT#13 second slice — the bar colors the battery at 25/10% but nothing *notified*. - **Did:** pkgs/nomarchy-battery-notify (shellcheck-gated watcher: polls the same sysfs the bar reads — type=Battery, scope≠Device — one toast per downward crossing: normal@25, critical@10; re-armed by charging; self-gates on battery presence → desktop no-op) + overlay. HM unit in modules/home/battery-notify.nix behind nomarchy.batteryNotify.enable (default on), graphical-session-bound, libnotify on the unit PATH (notify-send resolved from PATH so the check can shim it). README row. Chose the tiny watcher over poweralertd: its UPower thresholds (20/5) wouldn't match the bar, and it toasts every plug/unplug. - **Verified:** V0; V1 — template-home builds, unit present; V2 GREEN — new checks.battery-notify (test_power fake battery): self-gate exits 0 without a battery, silence at 80%, exactly one low toast crossing 25, one critical-urgency toast crossing 10, charging re-arms → a second drain toasts again. - **Pending:** V3 queued (real toast through swaync while draining). - **Next suggestion:** item 13 final slice — hyprpicker color picker (Tools › entry + SUPER+CTRL bind). ## 2026-07-04 — caffeine/idle-inhibit toggle (iteration #23, item 13 slice) - **Task:** BACKLOG NEXT#13 first slice — idle_inhibitor in the generated bar (summer-night had it; reverse parity gap). - **Did:** waybar.nix: idle_inhibitor module (󰾪/󰅶, tooltips, placed after the ⏺), CSS membership + `.activated { color: @warn; }`. summer-day: module + CSS mirrored from summer-night (same glyphs, no tint — glyph change is the state signal there). keybinds.nix extra row → cheatsheet mentions the bar click. - **Verified:** V0; V1 — template-home built; rendered config has the module second in modules-right with correct glyphs; CSS rules present; cheatsheet row renders. Inhibit behaviour needs a session → V3 queued (hold past the lock timeout). - **Pending:** V3. Item 13 remaining slices: low-battery notifications, color picker. - **Next suggestion:** item 13 next slice (low-battery notifications, gate on power.laptop) — or #14 lock-bump CI if variety is better. ## 2026-07-04 — screen recording in Capture (iteration #22, item 12) - **Task:** BACKLOG NEXT#12 — record region/screen from the menu, stop from the bar. - **Did:** modules/home/recording.nix: `nomarchy-record` start/stop/active/status — wl-screenrec first, wf-recorder fallback if it dies at startup (no-VAAPI case), SIGINT-finalized stop, runtime pidfile, signal-8 bar pokes. Capture menu: 4 record rows that self-swap to "■ Stop recording" while one runs. Waybar: self-gating custom/recording (⏺ REC, @bad red), click = stop — the one stop surface. Parity: both summer whole-swap bars got the module + CSS (@red). DEVIATIONS: saves to ~/Videos/Recordings (an .mp4 in Pictures/Screenshots felt wrong — same timestamp naming though); audio = two extra explicit rows, not a toggle state. - **Verified:** V0; V1 — template-home builds; rendered bar config has the module (first in modules-right) + CSS rule; summer jsonc still parse; nomarchy-record dry-run: status empty+exit 0 idle, active exit 1, usage exit 2; both recorders in home-path. Real capture needs a session → V3 (headless GL recipe can't confirm an encoder). - **Pending:** V3 recording exercise (incl. VAAPI-vs-fallback check). - **Next suggestion:** NEXT#13 niceties, one slice: idle-inhibit (caffeine) toggle — reverse-parity gap already noted in the item. ## 2026-07-04 — state-file validation & friendly errors (iteration #21, item 11) - **Task:** BACKLOG NEXT#11 — hand-edited state must fail with field + problem + fix, never a Nix stack. - **Did:** (a) tool: validate_state() (appearance schema = hard errors; settings.*/unknown keys = warnings), `validate` subcommand, validate-BEFORE-write in write_state (invalid set/apply never touches disk), JSON syntax errors now say line/column/fix. (b) theme.nix: same schema at eval time on `parsed` (post-defaults, so sparse files stay fine); throws a multi-line message naming each field, the got- value, and the three fix paths. Doctor's JSON fix-text now points at `validate`. - **Verified:** V0; V1 — new checks.theme-sync-validate GREEN: good template passes, trailing comma → line/col message, bad hex/type → field named, unknown key → warning only, invalid `set` refused with the file byte-identical (cmp). Negative eval of a broken scratch downstream through mkFlake shows the friendly throw (both fields); good-path template-home still builds. - **Pending:** nothing — no hardware dependency. - **Next suggestion:** NEXT#12 (screen recording) or #13 niceties slice; #12 first (it reshapes the Capture submenu the OCR LATER item waits on). ## 2026-07-04 — nomarchy-doctor (iteration #20, item 10) - **Task:** BACKLOG NEXT#10 — one-shot read-only health check. - **Did:** pkgs/nomarchy-doctor (writeShellApplication → shellcheck- gated; a package so the VM check runs on a minimal node): failed system+user units (user bus self-skips), disk space on real filesystems only (fstype allowlist skips tmpfs/9p → VM-safe; device dedupe), state-file parses + git-tracked, flake dirty/behind (warn-level), generation age via the profile SYMLINK mtime (store paths are epoch-1), snapper timer when enabled. Every ✖ prints its fix; exit 1 on any ✖. Wired: overlay, systemPackages, System › Doctor menu row (terminal), README §5. - **Verified:** V0; V1 package build (shellcheck) + real-hardware smoke: correctly flagged a genuinely failed user unit on the dev box; V2 GREEN — VM test: induced failed unit → exit 1 + names it + prints fix; reset-failed → healthy exit 0. Menu rebuild bash -n ok. - **Pending:** V3 menu-row check queued. Waybar-warning follow-up → LATER. - **Next suggestion:** NEXT#11 (state-file validation) — doctor's "parses" check is its little sibling; 11 adds validate-before-write + eval-time schema messages. ## 2026-07-04 — System › Rollback menu (iteration #19, item 9b → item 9 done) - **Task:** BACKLOG NEXT#9 half (b) — undo one menu away. - **Did:** new `rollback` case in nomarchy-menu (System submenu row): lists the last 10 `home-manager generations` (newest marked current), picking one activates it in a terminal — HM's supported rollback, reversible, so no typed-yes. System-level undo LINKS OUT by design: Snapshots row → existing flow; "boot an older generation (how)" → instruction notification pointing at docs/RECOVERY.md §3 (no destructive reimplementation in rofi). RECOVERY §1 cross-references the menu path. Item 9 complete → deleted. - **Verified:** V0; V1 — template-home built, generated nomarchy-menu bash -n clean, row-render + pick-parse logic run against a fixture `generations` output (id/path extraction correct). V2 skipped deliberately: the only novel logic is the parsing (fixture-covered); the activate step is HM's own mechanism. V3 queued. - **Pending:** V3 rollback-menu exercise on hardware. - **Next suggestion:** NEXT#10 `nomarchy-doctor` (V2-able in a VM). ## 2026-07-04 — sys-update/-rebuild what-changed diff (iteration #18, item 9a) - **Task:** BACKLOG NEXT#9 half (a) — a human diff after system rebuilds. - **Did:** both sys-update and sys-rebuild (twins stay twins) capture /run/current-system before, and after the switch print `nvd diff before after` (store-path-pinned ${"$"}{pkgs.nvd}, `|| true` so it can't fail a succeeded rebuild; identical-path fast-path says "no changes"). README §3 shortcut block notes it. - **Verified:** V0; V1 — template-system toplevel rebuilt, generated scripts extracted + bash -n, nvd smoke-run against the real closure (correct output format). Runtime behaviour is a trivial shell path — no VM needed. - **Pending:** half (b) — the System → Rollback menu flow (desktop generations picker; system → boot menu + snapshots pointer). - **Next suggestion:** item 9b — it completes the item; V2 the generation-activate path. ## 2026-07-04 — viewers + mime defaults (iteration #17, item 8) - **Task:** BACKLOG NEXT#8 — PDF/image viewers + xdg-mime defaults. - **Did:** modules/home/viewers.nix (`nomarchy.viewers.enable`, default on): programs.zathura (Stylix themes it — target added to stylix.nix's explicit list, autoEnable=false bit us first try) + imv. modules/home/mime.nix (`nomarchy.mime.enable`): mkDefault xdg.mimeApps for pdf→zathura, image/*→imv, av→mpv, text→code, dir→thunar, html/http(s)→firefox as INERT entries (browser Decision untouched — absent .desktop entries are skipped by GIO). README rows added (option-docs enforces them now). DEVIATION from the item text: viewers are a module toggle, not template packages — zathura's theming needs its HM module, which is exactly the "real config" boundary of the toggle discipline. - **Verified:** V0; V1 — template-home generation builds; asserted rendered mimeapps.list (all associations), themed zathurarc (Tokyo Night rgb), imv/zathura in home-path. V3 smoke queued. - **Pending:** V3 open-a-file smoke; browser Decision now smaller (inert entries ship; only "active in suite?" remains). - **Next suggestion:** NEXT#9 (update & rollback UX) — or #10 doctor. ## 2026-07-04 — docs slice (c): install story — item 6 COMPLETE (iteration #16) - **Task:** BACKLOG NOW#6 slice (c), the last one — install/first-run narrative end to end. - **Did:** read README §2 (ISO/installer) → §3 (downstream) → template README as one story; checked the facts en route: mkFlake's arg surface == the §3 table (src/username/hardwareProfile/system, lib.nix), disko really makes @snapshots + the hibernation swapfile, .#default / .#me names consistent. One seam fixed: §3 showed `nix flake init -t` but never handed off to the template README's six first-run steps — connective paragraph added (+ "the installer does all of this"). Item 6 done → deleted from BACKLOG (NOW now empty); ROADMAP § Full docs review flipped to ✓ with the slice summary. - **Verified:** V0 (docs-only) — flake check clean pre-commit. - **Pending:** docs-site-vs-Markdown stays a Decision. - **Next suggestion:** NEXT#8 (viewers + mime defaults) is the new queue head; note its "Default browser" Decision dependency — the mime module half can proceed with mkDefault degradation regardless. ## 2026-07-04 — docs slice (d): recovery runbook (iteration #15, item 6d) - **Task:** BACKLOG NOW#6 slice (d) — write the undo story. - **Did:** docs/RECOVERY.md, symptom-ordered: (1) bad theme/HM change → generations / apply-known-good / git-revert ~/.nomarchy; (2) desktop won't start → Ctrl+Alt+F2, greetd + user journals, VM-GL and preactivate-log pointers; (3) system change → systemd-boot older generation (kept: 10) + make-it-stick via sys-rebuild; (4) files → btrfs-assistant / sudo nomarchy-snapshots (undochange, root rollback, typed-yes); (5) ISO + nixos-enter. Every command checked against the tree (greetd cfg, configurationLimit, snapshots script actions, helper names). Linked from README tree + §5. - **Verified:** V0 (docs-only) — flake check clean pre-commit. - **Pending:** slice (c) install-story read-through — the last of item 6. - **Next suggestion:** finish item 6 with slice (c). ## 2026-07-04 — docs slice (b): drift pass (iteration #14, item 6b) - **Task:** BACKLOG NOW#6 slice (b) — OVERRIDES.md / TESTING.md / templates/downstream/README.md vs the live tree. - **Did:** spot-checked every factual claim: mkDefault sites (hyprland behaviour knobs, ghostty paddings, waybar mainBar/style), bind-list normal priority, `set ui.gapsOut` CLI key, awww(-daemon) naming, 21 presets, tokyo-night's 4 wallpapers, summer-night's light @fg bar, live `,highres` mkForce, .gitea/workflows/check.yml, QEMU gl flags, template `.#me` + ~/.nomarchy/NOMARCHY_PATH. All accurate. Fixed the two real gaps: OVERRIDES §2 predated `nomarchy.monitors` (now routes there + quick-ref row); TESTING §1 lacked the shell-syntax check §1b claims CI runs (added `bash -n`). - **Verified:** V0 (docs-only; flake untouched — check run pre-commit). - **Pending:** slices (c) install story, (d) recovery runbook. - **Next suggestion:** item 6 slice (c) or (d) — (d) is self-contained and ships user value (the runbook). ## 2026-07-04 — docs slice (a): option-surface reconcile (iteration #13, item 6a) - **Task:** BACKLOG NOW#6, first slice — README tables vs live options. - **Did:** split item 6 into slices (a–d) in BACKLOG. Reconciled: 9 undocumented options (autoTimezone.enable!, camera.hideIrSensor + irMatch!, intel.guc, amd.pstate/vaapi, package, system.stateFile, restic.paths) — rows/inline mentions added, no stale rows found. Made it permanent: `checks.option-docs` — flake.nix eval-walks option names from the 4 option files (config halves stay lazy; dummy args) → tools/check-option-docs.py enforces row-or-`.leaf`-mention + flags stale rows. - **Verified:** V0; V1 — check builds green (78 options); negative test: same script vs the pre-fix README reports exactly the 9 gaps. - **Pending:** slices (b) doc drift pass, (c) install story, (d) recovery runbook. - **Next suggestion:** item 6 slice (b) — or NEXT#8 (viewers+mime) for variety; both fine. ## 2026-07-04 — Back audit (iteration #12, item 24) - **Task:** BACKLOG NOW#24 — every list menu ends in ↩ Back. - **Did:** audited every `rofi -dmenu` call site in the generated nomarchy-menu + nomarchy-vpn. Already compliant: power, power-profile, theme, clipboard, files, audio, display (both stages), capture, tools, system, VPN main/import/exit-node/tailscale. Fixed: keybinds cheatsheet (Enter was a silent exit) now ends in ↩ Back → root picker. Documented the exceptions in rofi.nix: external modi (calc/emoji/networkmanager_dmenu/rofi-pulse-select) can't take an injected row (Esc = back); free-text prompts (web/ask) have no list. - **Verified:** V0; V1 — built nomarchy-menu, bash -n OK; awk audit over the generated script: only rows lacking back are the root picker (by design) and the free-text prompt. V3 spot-check queued. - **Pending:** V3 spot-check. NOW now holds only item 6 (docs, [big]). - **Next suggestion:** item 6 first slice, or NEXT#8 (viewers+mime). ## 2026-07-04 — network menu blank rows (iteration #11, item 22) - **Task:** BACKLOG NOW#22 — nameless entries in the network picker. - **Did:** read the pinned networkmanager_dmenu 2.6.2 source: with `compact = False` (our config!) it inserts literal empty rows as section separators (eth/wifi/vpn) — the systematic blank entries. Flipped config.ini to `compact = True` (also unpadded row format, better in rofi's proportional font). Hidden-SSID APs additionally render ONE nameless sec+bars row (dedup keeps one; not config- filterable) — documented, V3-gated, source-patch follow-up if seen. - **Verified:** V0; V1 — rendered config.ini shows compact = True. Behavior needs Wi-Fi hardware → V3 queued. - **Pending:** V3 on the Latitude (no separators; hidden-AP residual?). - **Next suggestion:** item 24 (Back audit) — grep-the-generated-script. ## 2026-07-04 — waybar/rofi palette-safe pairings (iteration #10, item 27) - **Task:** BACKLOG NOW#27 — generated waybar/rofi CSS invisible on on-surface palettes (flexoki-light runs it; summers whole-swap). - **Did:** waybar.nix generated CSS: module row + #window → alpha(@text, 0.85); inactive workspaces / dnd / pulseaudio.muted → alpha(@text, 0.5) (gruvbox's muted/base was 1.27). rofi.nix: @dim → text+80, @surface → text+1a (#RRGGBBAA fg tints). Contrast check docstring/pairings now cover all three generated surfaces. Status glyphs (good/warn/bad on base, worst 2.05) left as-is — palette design, human call. Generated bar only; whole-swaps untouched. - **Verified:** V0; V1 — checks.theme-contrast green (21 themes), downstream-template-home builds, rendered waybar style has zero @subtext/@muted/@surface, rendered custom.rasi shows fg-derived tints. NOT visually verified — V3 queued (flexoki-light eyeball). - **Pending:** V3 flexoki-light + dark-theme regression spot-check. - **Next suggestion:** item 22 (network menu rows) — V1-actionable. ## 2026-07-04 — swaync contrast fix + theme-contrast check (iteration #9, item 25) - **Task:** BACKLOG NOW#25 — swaync text invisible on summer-day. - **Did:** swaync.nix now uses only palette-safe pairings: body/summary = @text on @base (worst ratio 5.18 across all 21 themes; subtext/base was 1.00 on summer-day + flexoki-light), hover rows + widget buttons = alpha(@text, 0.1) tints (contrast by construction); dropped the subtext/surface defines. NEW permanent check `checks.theme-contrast` (tools/check-theme-contrast.py) asserts the hex-on-hex text pairings across every theme JSON. Audit found the same mispairing in generated waybar/rofi CSS, live on flexoki-light (no whole-swap) → NOW item 27. - **Verified:** V0; V1 — checks.theme-contrast builds green (21 themes), rendered services.swaync.style contains zero @subtext/@surface. NOT V2/V3-verified visually — queued (Bernardo is at the Latitude). - **Pending:** V3 notify-send readability check on summer-day; item 27. - **Next suggestion:** item 27 (same bug class, waybar/rofi surfaces). ## 2026-07-04 — SUPER+? bind fix (iteration #8, item 26) - **Task:** BACKLOG NOW#26 — cheatsheet bind never fires on hardware. - **Did:** keybinds.nix: `$mod, question` → `$mod SHIFT, question` (? is Shift+/, Hyprland matches the exact modmask); rofi.nix prettyKeys collapses "SHIFT + ?" → "?" so the row stays SUPER + ?. Gotcha added to MEMORY.md (shifted keysyms need SHIFT in mods). - **Verified:** V0 (flake check --no-build); V1 both surfaces — eval'd hyprland settings.bind renders `$mod SHIFT, question, exec, nomarchy-menu keybinds`, and built nomarchy-menu's cheatsheet file renders `SUPER + ?`. No other shifted keysyms in the bind list. - **Pending:** V3 queued (SUPER+? on the Latitude after home-update + relogin). - **Next suggestion:** item 25 (swaync contrast on summer-day). ## 2026-07-04 — Latitude hardware sweep #2 (Bernardo; results booked, no code) - **Task:** V3 sweep on the Latitude after pulling ≥18b8545 + reboot. - **PASS:** waybar supervisor (theme switches clean; `pkill -x waybar` respawns instantly); snapshots — themed polkit prompt → GUI as root, fzf fallback works (restore/rollback gate not exercised → residual queued); `sys-rebuild`; night-light enable/disable; auto-commit (theme commits + the toggle self-commit); hibernate single-unlock + suspend-still-locks (1b0eeea CONFIRMED). - **PASS (late confirm):** auto-timezone — `timedatectl` shows Europe/London. - **Bug:** SUPER+? never fires — `$mod, question` misses because a real ? carries SHIFT in the modmask → BACKLOG 26 (bind-only: the menu itself opens fine from a terminal). SUPER+SHIFT+K no-op was the comma-layout gate working (none configured) — retest queued. - **Next suggestion:** item 25 (swaync contrast) or 26 (cheatsheet bind). ## 2026-07-04 — Waybar resilience + sys-rebuild (iteration #7, items 21+23) - **Task:** the two top Latitude findings. - **Did:** (21) `nomarchy-waybar` supervisor (waybar.nix, exec-once'd from hyprland.nix): ANY waybar exit respawns, crash-loop guard (5 fast exits → critical notify + stop), TERM trap for a real stop. theme-sync now prefers a clean `pkill -x waybar` when the supervisor is running (restart with fresh config+style — avoids waybar's in-place SIGUSR2 reload entirely, the suspected double-reload race with reload_style_on_change during symlink flips); SIGUSR2 stays as the unsupervised fallback; reload_style_on_change kept for manual home-update restyles. (23) `sys-rebuild` — snapshot-first rebuild against the current lock, no `nix flake update`; README §3/§5 + motd. - **Verified:** V0; V1 (HM renders `exec-once=nomarchy-waybar`, supervisor bash -n; `sys-rebuild` in systemPackages). V2: headless software-GL desktop VM (recipe from memory — gotchas hit: node needs the overlay, users.users., useGlobalPkgs) — SIGKILL→new pid, clean kill→respawn, SIGUSR2→alive. Result recorded below once green. - **Pending:** V3 on the Latitude: theme switch survives / bar returns. - **Next suggestion:** item 22 (network menu rows) or 24 (Back audit). ## 2026-07-04 — Keyboard cycle bind + parity (iteration #6) + Latitude triage - **Task:** BACKLOG NOW#5; mid-iteration Bernardo delivered Latitude 5410 hardware-QA findings (the machine got a real session!). - **Did:** `multiLayoutBinds` in keybinds.nix (SUPER+SHIFT+K → `switchxkblayout current next`), gated in hyprland.nix AND the rofi cheatsheet on the same comma-in-layout condition; `hyprland/language` + `#language` CSS added to both summer whole-swaps (shows even single-layout there — static JSON can't gate; deliberate). Reverse parity gap found (summer-night has idle_inhibitor, generated bar doesn't) → folded into item 13. **Triage:** four new NOW items from hardware: #21 Waybar crash on theme switch + no respawn (top), #22 nameless network-menu entries, #23 `sys-rebuild` (no-update rebuild), #24 Back-everywhere audit. - **Verified:** V0 (JSON + flake check) + V1 both directions (single-layout build: no bind; `us,de` via extendModules: bind + the cheatsheet row render; summer JSONs parse). - **Pending:** V3 cycle-bind check queued; items 21/22/24 carry V3 re-checks on the Latitude. - **Next suggestion:** #21 (waybar crash — stability pillar, user-hit). ## 2026-07-04 — Webcam follow-ups (loop iteration #5, quick item) - **Task:** BACKLOG NOW#7 (Bernardo asked for "a quick item"). - **Did:** template `home.packages` gains a commented "Webcam tuning" pair (cameractrls GUI, v4l-utils CLI — bare packages, so template not toggle, per the option-surface rule); the portal/Flatpak libcamera IR gap is written up as a PROPOSED item with three options (recommend: document-only now, libcamera-rule investigation when the T14s is available). CI runs #60/#61 confirmed green (earlyoom + snapshot fix). - **Verified:** V0 (flake check — template evals through mkFlake; changes are comments + agent docs). - **Next suggestion:** NOW#5 (keyboard cycle bind + summer parity) — the last code item in NOW besides the docs pass. ## 2026-07-04 — Snapshot GUI un-broken + polkit agent (loop iteration #4) - **Task:** BACKLOG NOW#4 — btrfs-assistant segfault. - **Did:** re-diagnosed instead of overriding. gdb: crash is in libbtrfsutil's *unprivileged* subvolume iteration (btrfs-progs 6.17.1; upstream fix `886571653` post-6.17.1; symbol versions verified — not ABI drift). VM A/B: root works (exit 0), user crashes (139). The launcher runs it as root → GUI fine *if* pkexec can prompt — and the distro shipped **no polkit agent** (all pkexec silently failed; the actual root cause of "GUI is dead"). Shipped: hyprpolkitagent (exec-once, Stylix-themed Qt), menu re-pointed to the launcher with the fzf flow as fallback, `checks.snapshot-gui` canary (root path + offscreen event loop on real btrfs). No btrfs-progs patch — root-side flows don't hit the bug; the fix arrives with a lock bump. - **Verified:** V0 + V2 (snapshot-gui check, run for real) + V1 (HM generation renders the agent exec-once; menu bash -n). - **Pending:** V3 queued — first on-hardware themed polkit prompt. - **Next suggestion:** NOW#5 (keyboard cycle bind + summer parity). ## 2026-07-04 — CI is green (run #58) — item 20 main half closed - **Task:** monitor follow-up, no code. Run #58 (the .gitea path fix) **succeeded** — first green CI: the container recipe works on the live runner, eval tier confirmed end to end. - **Did:** BACKLOG item 20 reduced to its stretch (KVM runner → enable vm-checks), moved to NEXT. Run #59 (earlyoom push) monitored. - **Next suggestion:** NOW#4 (btrfs-assistant override attempt). ## 2026-07-04 — Memory-pressure protection shipped (loop iteration #3) - **Task:** BACKLOG NOW#3 — default-on userspace OOM killer. - **Did:** `modules/nixos/oom.nix`: earlyoom (mkDefault on, desktop notifications, session-plumbing `--avoid` list, no `--prefer`) + `systemd.oomd.enable = false`. Choice rationale: Hyprland session is one cgroup scope → oomd's cgroup-level kill would nuke the desktop; earlyoom is process-level. Found en route: nixpkgs default-enables oomd *inert* (enable=true, all slice toggles false). README "beyond the surface" note added. - **Verified:** V0 (flake check) + **V2** — new `checks.oom-protection` runNixOSTest, run for real: hog (chunked allocator, 686 MB peak in a 1 GB VM) SIGTERM'd by earlyoom in 0.1 s, bystander unit survived, oomd asserted inactive. 25 s test, cheap enough for the suite. - **Pending:** nothing hardware-specific (behavior fully VM-provable). - **Next suggestion:** NOW#4 (btrfs-assistant override attempt). Also: CI run #58 (the .gitea fix) was *running* at commit time — check its outcome next tick; this push triggers #59. ## 2026-07-04 — CI fix: wrong workflow dir (correction to iteration #2) - **Task:** Bernardo reported no run despite an active runner + shared his compose file: the server is **Gitea** (gitea/act_runner), not Forgejo — I'd shipped the workflow to `.forgejo/workflows/`, which Gitea never reads. The legacy repo's `.gitea/workflows/` path was the clue I under-weighted. - **Did:** `git mv` → `.gitea/workflows/check.yml`; corrected the Forgejo references in the workflow header, TESTING.md §1b, BACKLOG item 20 (now: watch the first run; runner was never the problem); MEMORY.md gains the Gitea-not-Forgejo line. - **Verified:** the push of this commit is itself the test — the run must appear; monitoring. - **Pending:** first green run (item 20); runner-container memory headroom is the watch-out. ## 2026-07-04 — CI checks-on-push shipped (loop iteration #2) - **Task:** BACKLOG NOW#2 — Forgejo Actions workflow. - **Did:** `.forgejo/workflows/check.yml` (push to main/v1 + dispatch): flake check --no-build, theme-sync py_compile, bash -n over tracked .sh. Scoped to the **eval tier** deliberately — API probing + archaeology of the legacy `.gitea/workflows/check.yml` (57 runs on this instance) showed the runner is an act_runner docker container (no KVM/systemd), so the VM suite can't run there; a commented `vm-checks` job documents the KVM-runner upgrade path. Legacy's hard-won container gotchas (nixbld setup, sandbox=false for Stylix IFD, Nix 2.31.5 pin) carried over verbatim. TESTING.md §1b added. - **Verified:** V0 locally (same commands the workflow runs, minus the container Nix install) + yq YAML parse. The real green run needs the runner to be alive — **not confirmable from here** (runners API is 401 unauthenticated); watching the run after push. - **Pending:** new `[human]` item 20 — confirm/re-register the runner; stretch: a KVM runner unlocks the vm-checks job. - **Next suggestion:** NOW#3 (memory-pressure protection). ## 2026-07-04 — Opt-in auto-commit shipped (loop iteration #1) - **Task:** BACKLOG NOW#1 — auto-commit of state mutations (in-flake state Phase 4). First autonomous /loop iteration. - **Did:** `settings.autoCommit` live-read by nomarchy-theme-sync; pathspec-limited commit of theme-state.json on apply/set (fires if flag on before OR after the write → the off-toggle commits too); identity fallback; same-value no-op; `bg` excluded. Menu: System › Auto-commit (instant, self-gated on `.git`). Rider: `get` prints booleans as JSON `true`/`false`, fixing the stuck "Auto timezone (on/off)" menu label. BACKLOG numbering switched to stable IDs (no renumbering). - **Verified:** V0 (py_compile, flake check) + V1 (HM generation builds; generated menu `bash -n`) + a 7-assertion sandbox git-repo round-trip (enable/set/same-value/disable/post-disable/apply/identity-fallback). - **Pending:** V3 queued in HARDWARE-QUEUE (on-machine toggle + apply). - **Next suggestion:** NOW#2 (CI checks-on-push) — but its runner half may need Bernardo to register a Forgejo runner; deliver the workflow regardless. ## 2026-07-04 — Backlog revision pass (human-requested) - **Task:** Bernardo asked for a philosophy-aligned revision of BACKLOG.md ("I'm sure I'm forgetting important items"). - **Did:** verified guessed gaps against the tree first (GC/optimise/boot limit + all 21 previews already ship — dropped those ideas). Added the confirmed gaps: CI-on-push promoted to NOW#2 (agents push to main now), memory-pressure protection NOW#3 (nothing mitigates OOM; the release bump died to one), viewers+mime defaults #8, update/rollback UX #9, nomarchy-doctor #10, state-file validation #11, screen recording #12, niceties batch #13 (idle-inhibit, low-battery notify, hyprpicker), OCR in LATER; recovery runbook folded into the docs item; zram + default browser added to Decisions. - **Verified:** V0 (docs-only). - **Pending:** Bernardo to sanity-check the new NOW ordering, esp. CI at #2 and the two new Decisions. - **Next suggestion:** unchanged — NOW#1 (opt-in auto-commit). ## 2026-07-04 — Bootstrap the loop infrastructure (this commit) - **Task:** create `agent/` (human-requested): LOOP/GOALS/BACKLOG/ CONVENTIONS/MEMORY/HARDWARE-QUEUE/JOURNAL, root CLAUDE.md, README + ROADMAP pointers. - **Did:** reworked the forward half of docs/ROADMAP.md into the tiered BACKLOG (5 NOW / 6 NEXT / LATER / Decisions); collected every pending on-hardware check into HARDWARE-QUEUE.md; seeded MEMORY.md from the ROADMAP's decision records. - **Verified:** V0 (docs-only change; `nix flake check --no-build` run to confirm the tree still evaluates). - **Pending:** Bernardo to sanity-read GOALS.md pillars + BACKLOG tiering — reorder freely, the order *is* the instruction. - **Next suggestion:** BACKLOG NOW#1 (opt-in auto-commit — Phase 4 of in-flake state, already the agreed next step).