Commit Graph

39 Commits

Author SHA1 Message Date
28a28e05d3 feat(waybar): right-click volume opens a floating pwvucontrol mixer
All checks were successful
Check / eval (push) Successful in 2m55s
Right-clicking the Waybar volume module now opens pwvucontrol (per-app
mixer); left-click still mutes. Adds:

- on-click-right in waybar.nix + all four whole-swap jsoncs (parity)
- hyprland.nix: the config's first windowrule block — float + center the
  mixer (pwvucontrol, with pavucontrol as a fallback class); normal-
  priority list so a downstream windowrule concatenates
- pwvucontrol in the template home.packages

pwvucontrol is PipeWire-native + GTK4/libadwaita, so it themes via the
portal color-scheme like Amberol. app-id verified from the built package.
This settles the windowrule pattern/placement; item 41 is now just
broadening the float set. Right-click→float→center is a V3 session check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:31:00 +01:00
92b3c1a1e3 feat(keybinds): move window focus to SUPER+arrows
All checks were successful
Check / eval (push) Successful in 3m4s
Swap the vim-style SUPER+H/J/K/L focus binds for SUPER+arrow keys
(movefocus l/r/u/d). SUPER+arrows were unbound and there are no vim
move-window binds, so no collision. prettyKeys renders the arrow keysyms
as glyphs so the SUPER+? cheatsheet shows "SUPER + <-" not "SUPER + left".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:07:21 +01:00
1928cd94f6 feat(mime): open audio files in Amberol, not mpv
All checks were successful
Check / eval (push) Successful in 2m59s
mpv (video) and amberol (audio) are both already in the template suite;
the gap was mime wiring — audio/* still resolved to mpv.desktop. Repoint
the audio types to io.bassi.Amberol.desktop (id verified against the
built package) and broaden coverage to mp3/flac/ogg/opus/wav/m4a/aac,
both canonical and x- names. Video stays on mpv. Degrades to mpv if a
downstream drops Amberol from the suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:59:35 +01:00
21c0c58ea2 feat(keybinds): capture region/screen to file
All checks were successful
Check / eval (push) Successful in 2m54s
Add SHIFT+Print (region → file) and CTRL+Print (screen → file) next to
the existing bare Print (region → clipboard). Each saves a timestamped
PNG under ~/Pictures/Screenshots and toasts the path — the same grim/slurp
plumbing and directory the Capture menu's "→ file" rows already use.
Single-source keybinds.nix, so the SUPER+? cheatsheet lists both new rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:55:31 +01:00
926be22fd4 fix(rofi): normal sorter for fuzzy search + power-profile icons
All checks were successful
Check / eval (push) Successful in 3m8s
Two rofi fixes:

- Search (item 31): rofi 2.0.0's fzf sorter mis-ranks fuzzy matches —
  typing "steam" left ghostty selected instead of Steam. Bernardo's live
  A/B confirmed `-sorting-method normal` and `-matching normal` both fix
  it while `-no-sort` does not. Switch sorting-method fzf → normal, keep
  matching=fuzzy + case-sensitive=false.

- Power profile menu (item 39): list profiles with per-profile icons from
  Papirus' colored battery-profile-{performance,balanced,powersave} family
  (the -symbolic variants are #444 grey, invisible on dark). Row text
  stays the bare profile name so `powerprofilesctl set` is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:51:07 +01:00
d078ba2a82 feat(control-center): battery charge-limit as a preset toggle
All checks were successful
Check / eval (push) Successful in 3m10s
The "Battery Limit" entry was a raw number prompt; make it a proper
toggle picker — Off / 80% (recommended) / 90% / 60% / Custom… — writing
settings.power.batteryChargeLimit. Kept in the control-center, where the
baked rebuild-valued System Toggles live (the rofi nomarchy-menu is the
instant surface; the CC holds config values), and reachable from the menu
via System › Control Center. Base machinery (the sysfs oneshot + AC-replug
udev in power.nix) is unchanged, so it lands on the next sys-rebuild like
its siblings.

Instant-effect (no rebuild) is refiled in BACKLOG § PROPOSED as
[blocked:hw]: it needs a udev-writable threshold node, confirmable only on
a laptop with the control.

Verified: V0 (bash -n; nix flake check --no-build, green) + V1
(nomarchy-control-center package builds — the writeShellApplication
shellcheck gate passes). Session spot-check queued in HARDWARE-QUEUE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:06:15 +01:00
cfecb612a6 fix(waybar): drop the bar to the bottom layer so fullscreen covers it
Some checks failed
Check / eval (push) Has been cancelled
On Hyprland the `top` layer renders above every window, including a real
fullscreen surface — so a browser video gone fullscreen sat *under* the
bar (BACKLOG item 30). Move the bar to `layer: bottom` in the generated
waybar.nix and all four whole-swap jsoncs (parity rule); the exclusive
zone still reserves its space in normal tiling, but a fullscreen window
now covers it. Rationale comment kept in waybar.nix; data jsoncs left
comment-free (they carry none).

Verified: V0 (nix flake check --no-build, green) + V1
(homeConfigurations.nomarchy.activationPackage builds; built
.config/waybar/config shows "layer": "bottom"). V3 queued in
HARDWARE-QUEUE (fullscreen covers bar + returns on exit; floating-overlap
trade-off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:51:38 +01:00
Bernardo Magri
edd0bd38ce feat(display): per-profile workspace→output pins (item 15c)
All checks were successful
Check / eval (push) Successful in 3m0s
Lock bump / bump (push) Successful in 2m53s
A display profile can now pin workspaces to outputs:

  docked = {
    monitors   = [ … ];
    workspaces = { "1" = "DP-3"; "9" = "eDP-1"; };
  };

The bare list-of-monitors shape still works (either-type; hyprland.nix
normalizes — coercedTo refuses list-of-submodule sources). Pins bake as
Hyprland `workspace` rules from the active profile and apply instantly
on `nomarchy-display-profile apply`: keyword sets the session rule,
moveworkspacetomonitor moves open workspaces over. Pins from a
previously applied profile linger in-session until reload/rebuild
(hyprctl can only add) — inert if the output is absent, noted in the
applier.

V1: flake check green (checks.display-profiles now asserts
workspaceRule) + scratch-downstream eval: baked settings carry both
rule sets, the generated applier has the keyword+dispatch pairs
(bash -n), match/list correct with mixed shapes. V3 queued in
HARDWARE-QUEUE (rides the dock test). Item 15 complete — deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 23:09:14 +01:00
Bernardo Magri
7d5f091c29 docs(agent): queue on-hardware check for the stub-bar themes (item 28)
All checks were successful
Check / eval (push) Successful in 3m7s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:23:15 +01:00
Bernardo Magri
01ee847490 feat(bar): timezone line in the clock tooltip (LATER item)
All checks were successful
Check / eval (push) Successful in 3m3s
Hovering the clock now leads with the zone — "{:%Z (UTC%z)}" above the
calendar — on the generated bar and both summer whole-swaps (parity).
Under auto-timezone the existing tz-watch SIGUSR2 reload keeps it
showing the currently detected zone. %Z is the abbreviation (BST), not
the IANA name — strftime has no code for that, and it satisfies the
"(optional)" nicety.

Verified: V0; V1 — the rendered generated config carries the new
tooltip-format and both summer jsonc parse with it. Hover render
queued for hardware.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:56:44 +01:00
Bernardo Magri
808a3febdd feat(bar): doctor tripwire indicator (LATER item)
All checks were successful
Check / eval (push) Successful in 3m3s
nomarchy-doctor-status: silent while nomarchy-doctor exits 0 (or is
absent), so the module only exists when something is actually broken —
then a red warning glyph with the first ✖ lines jq-escaped into the
tooltip; clicking opens the full sheet (nomarchy-menu doctor).
Interval 300s: the health check is real work (unit/disk/git sweeps),
and this is a tripwire, not a monitor. Added to the generated bar
(status group, after battery) and to BOTH summer whole-swap bars
(module list + jsonc block + CSS membership + @red rule) per the
parity rule.

Verified: V0; V1 — template-home builds with the module in config +
CSS, the status script passes bash -n, both summer jsonc still parse;
functional — the self-gate exits silently, and a shimmed failing
doctor yields jq-valid JSON with class "bad" and the ✖ lines in the
tooltip. Live render queued for hardware.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:46:29 +01:00
Bernardo Magri
4ad564bd80 feat(capture): OCR region to clipboard (LATER item)
All checks were successful
Check / eval (push) Successful in 3m6s
Tools › Capture gains "OCR region → clipboard", self-gated on
tesseract: slurp the region (Esc cancels silently, the recording.nix
pattern), grim it, tesseract stdin→stdout, wl-copy. Whitespace-only
recognition toasts "No text recognized" instead of clobbering the
clipboard; success toasts the word count. tesseract ships English-only
through the menu's package set — the unscoped wrapper pulls every
language's traineddata, a real cost on a compile-from-source distro;
overriding with more languages is documented at the package line.

Verified: V0; V1 — template-home builds with the gated row + case and
menu bash -n; functional — the profile's own tesseract read a rendered
test image back verbatim, proving the OCR half of the pipe. The
grim/slurp half needs a session → HARDWARE-QUEUE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:29:08 +01:00
Bernardo Magri
3e49481d46 feat(menu): Look & Feel category — theme, wallpaper, night light (item 19)
All checks were successful
Check / eval (push) Successful in 3m3s
The gate was >=3 appearance entries and it's met: the root's Theme row
becomes "Look & Feel" (pango-escaped &amp; — the root passes
-markup-rows; matched on *Look*), a new lookfeel submenu carries the
Theme grid (SUPER+T hint), Next wallpaper (SUPER+SHIFT+T hint via the
new generalized hintForAction, which menuHint is now sugar over), and
the Night light toggle moved out of System — which thins by one row.
The root stays at six entries; both direct binds still hit the leaves.

Verified: V0; V1 — rendered menu passes bash -n with the mode, escaped
label and relocated night-light row; visual — tokyo-night 1080p
capture renders "Look & Feel" correctly with hints intact. Submenu
flow queued for a session test (HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 21:04:15 +01:00
Bernardo Magri
8d54eecd67 feat(hyprland): launch-or-focus binds (item 17)
All checks were successful
Check / eval (push) Successful in 3m4s
nomarchy.launchOrFocus (opt-in, commented template example): each
entry gives a key that FOCUSES the app's existing window
(case-insensitive class match via jq + a "(?i)" focuswindow regex) or
launches it when none is open. One shared nomarchy-focus-or-launch
script; a bind whose command was removed from the suite fires a
"not installed" toast instead of failing silently. The same entries
render into the SUPER+? cheatsheet through prettyKeys — bind and
documentation stay one source. README row added.

Verified: V0; V1 — a scratch downstream with two entries renders both
binds (default command = lowercased class; custom mods/command/desc
honored), the script passes bash -n with a usage guard, and the
cheatsheet carries both rows. Focus behaviour needs a session -> V3
(HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:56:01 +01:00
Bernardo Magri
44d5516191 fix(themes): P2 contrast floors retuned + gated across 9 themes (item 28b done)
All checks were successful
Check / eval (push) Successful in 3m10s
18 hex retunes, hue-preserving OKLCH minimal raises except where the
upstream palette defines a canonical value: muted lifted to >= 2.0 on
lumon/white/retro-82/everforest (solved), gruvbox (bg4 #7c6f64), nord
(#616e88, the community comment-brightening), catppuccin-latte
(overlay1 #8c8fa1); text-on-surface >= 4.5 via surface nudges
(ristretto darker, latte lighter); accentAlt >= 3.0 (latte, summer-day
darkened); warn >= 2.5 — the bar's battery tint — darkened on the
light bases (latte, rose-pine dawn, summer-day) and flexoki-light's
four status colors moved to their canonical light-mode 600 series.

check-theme-contrast.py now gates all four adopted floors (7 pairings
x 21 themes, green). good/bad stay audit-only — identity themes sit at
2.0-2.3 there by design, so no exemption mechanism was needed. The
design audit's contrast findings drop 20 -> 1 (miasma's earthy `bad`,
kept as identity). Item 28 slice (b) complete.

Verified: V0, checks.theme-contrast green, flake check green,
re-audit. Rendering is V3 -> HARDWARE-QUEUE eyeball entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:44:46 +01:00
Bernardo Magri
47c8b6f997 fix(themes): P1 palette retunes from the design audit + gated subtext floor (item 28b)
All checks were successful
Check / eval (push) Successful in 3m0s
The audit's P1s, fixed at the palette level (items 25/27 previously
patched the consumers around these):
- summer-day: surface was the TEXT color (slate #5c6a72 on cream) and
  subtext was the BASE (ratio 1.0, invisible) — now a raised cream
  surface #efe7d0 and a slate-teal subtext #6e828a in the theme's
  family.
- flexoki-light: surface/overlay/muted were all the text black and
  subtext the paper — retuned to canonical upstream Flexoki tones
  (ui #E6E4D9, #B7B5AC, tx-2 #6F6E69, base-500 #878580).
- miasma: pure-black surface under a #222 base → raised #2e2e2e.
- kanagawa: surface was sumiInk0 (darker than the bg) → upstream's
  raised sumiInk4 #2a2a37.
check-theme-contrast.py gains the adopted rule: subtext on base >= 3.0
(all 21 themes pass), so the invisible-secondary-text class cannot
ship again. Model note in the backlog: `overlay` is a mid-tone role
(catppuccin semantics), not a third bg tier.

Verified: V0 + checks.theme-contrast green (21x3); re-audit shows all
four P1 findings gone; a scratch downstream with summer-day active
builds and the new surface hex lands in the rendered ghostty config.
Remains: V3 four-theme eyeball (HARDWARE-QUEUE) and 28b's P2 floors +
exemption list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:55:44 +01:00
Bernardo Magri
a360bc87ca feat(greeter): tuigreet + console themed from the state JSON (item 16)
Some checks failed
Check / eval (push) Has been cancelled
New modules/nixos/greeter.nix owns the greetd block (moved out of
default.nix) plus the theming, the Plymouth model — baked at SYSTEM
rebuild from nomarchy.system.stateFile. Two parts, because tuigreet
draws on the VT with the 16 ANSI slots: console.colors becomes the
theme's ansi[] (vt.default_* kernel params — which also themes raw
ttys and the LUKS passphrase prompt), and tuigreet gets a --theme of
NAMED slots (its parser is ratatui Color::from_str, checked in source;
the names map to the standard indexes the retinted palette now
serves). A sparse state without ansi skips theming cleanly.

Verified: V0; V1 — the template-system toplevel bakes the full --theme
spec into greetd.toml and all three vt.default_{red,grn,blu} palettes
into kernel-params. A checks.greeter VM test was attempted and dropped
after bisecting: tuigreet dies under runNixOSTest even without the
flag (harness limit; nixpkgs's own greetd test uses agreety) —
recorded in agent/MEMORY.md. Remains: V3 rendering +
theme-follows-rebuild on hardware (HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:37:32 +01:00
Bernardo Magri
1921839e0e feat(displays): hotplug auto-switch for display profiles (item 15b)
All checks were successful
Check / eval (push) Successful in 2m57s
nomarchy-display-profile gains `match <outputs>`: an exact set match
wins, else the largest profile whose named outputs are all connected;
any tie prints nothing — the watcher must never flap between ambiguous
layouts. nomarchy-display-profile-watch (exec-once when profiles are
declared, the keyboard-watcher poll pattern — session units race
Hyprland IPC on relogin) reacts to output-set CHANGES only, reads
settings.displayProfileAuto from the live state each time (the menu's
new Auto-switch row toggles it instantly, no rebuild), and applies via
the tool — so an auto-pick persists a concrete profile that the next
rebuild bakes. A separate boolean rather than an "auto" pseudo-profile
for exactly that bake semantics.

Verified: V0 (flake check); V1 — a scratch 4-profile downstream builds;
tool + watcher pass bash -n; the match matrix exercised end-to-end:
three exact picks, an unambiguous-superset pick, a tie -> none, a
no-fit -> none; the watcher is exec-once'd in the rendered config; the
menu renders the Auto-switch row. Remains: V3 real dock/undock
(HARDWARE-QUEUE, rides the slice-a entry). Slice c (workspace binding)
stays, tagged optional.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:14:07 +01:00
Bernardo Magri
9df18261f9 feat(displays): display profiles — docked/undocked switching (item 15a)
All checks were successful
Check / eval (push) Successful in 3m4s
nomarchy.displayProfiles: named layouts for the same outputs, each a
list of nomarchy.monitors-style entries. The active profile lives in
the in-flake state (settings.displayProfile) on the night-light
pattern: nomarchy-display-profile apply <name> fires the baked hyprctl
rules instantly and writes the state with --no-switch; the next rebuild
bakes the profile's entries over nomarchy.monitors whole-by-name.
Monitor-rule composition is extracted to pure monitor-rules.nix
(renderer + 3-layer resolve, incl. a disable-guard so a stale menu
resolution pick can't resurrect a profile-disabled panel, and junk
state degrading to base config instead of an eval error) — because a
writeText/toFile state fixture is unreadable at flake-check eval time,
the new checks.display-profiles unit-tests the pure function directly.
Menu: System › Display gains a self-gated "Profiles ›" row (●/○ active
mark, Base layout to clear); commented template example + README row.

Verified: V0 (flake check, forcing the new check's asserts); V1 — a
scratch downstream through lib.mkFlake builds: the generated tool
passes bash -n with correct rules (disable/vrr/float scale), the baked
hyprland.conf shows the exact expected overlay, the menu renders, and
list/usage smoke-test clean. Remains: V3 live switching on a dock
(HARDWARE-QUEUE). Slices b (hotplug auto-switch) + c (workspace
binding) stay in item 15.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:52:19 +01:00
Bernardo Magri
26e393b65d feat(menu): hyprpicker color picker — Tools › row + SUPER+CTRL+P (item 13 done)
All checks were successful
Check / eval (push) Successful in 3m0s
The last niceties slice: pick a pixel's color into the clipboard.
hyprpicker joins grim/slurp in systemPackages; a new nomarchy-menu
`colorpicker` mode captures its output, wl-copies it, and toasts the
hex (Esc = silent cancel). Tools › row self-gates on hyprpicker being
present; SUPER+CTRL+P added in keybinds.nix, so the Hyprland bind and
the SUPER+? cheatsheet row come from the one source. Item 13 complete
and deleted from BACKLOG.

Verified: V0 (flake check); V1 — template-home and template-system
build; rendered nomarchy-menu passes bash -n and contains the mode +
gated row; cheatsheet has the SUPER+CTRL+P row; hyprland.conf has the
bind; the system toplevel ships sw/bin/hyprpicker. Remains: V3 — real
loupe → click → wl-paste round-trip in a session (HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 15:54:30 +01:00
Bernardo Magri
fb78c814cc feat(battery): low-battery notifications at the bar's 25/10% thresholds (item 13 slice)
All checks were successful
Check / eval (push) Successful in 3m4s
The bar colored the battery at 25/10% but nothing notified. New
shellcheck-gated watcher (pkgs/nomarchy-battery-notify, overlay) polls
the same sysfs the bar reads — type=Battery, scope!=Device — and fires
one toast per downward crossing (normal at 25%, critical at 10%; swaync
keeps critical up until dismissed), re-armed by charging. Self-gates on
battery presence (the powerprofile pattern), so desktops get a silent
no-op. HM user unit behind nomarchy.batteryNotify.enable (default on)
with libnotify on the unit PATH; notify-send resolves from PATH so the
VM check can shim it. poweralertd rejected: its UPower thresholds
(20/5) wouldn't match the bar, and it toasts every plug/unplug.

Verified: V0 (flake check, bash -n); V1 (template-home builds, unit in
the generation); V2 GREEN (new checks.battery-notify, test_power fake
battery: self-gate, silence at 80%, one toast per crossing, critical
urgency, charging re-arms). Remains: V3 — real swaync toast on a
draining laptop (HARDWARE-QUEUE).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 15:11:06 +01:00
Bernardo Magri
b7b51e9354 feat(bar): idle-inhibit (caffeine) toggle — generated bar + summer-day parity (item 13 slice)
All checks were successful
Check / eval (push) Successful in 2m55s
Summer-night's whole-swap bar had an idle_inhibitor the generated bar
lacked (reverse parity gap from the 2026-07-04 sweep). The generated
bar now carries it — 󰾪 click → 󰅶, warm tint while active, tooltips
saying what it holds — blocking hypridle's lock/display-off/suspend
for videos and presentations; state deliberately resets with the bar.
summer-day mirrors summer-night's module + CSS, and the keybinds
cheatsheet mentions the bar click (via keybinds.extra).

V1: rendered config has the module with correct glyphs, CSS rules
present, cheatsheet row renders. Inhibit behaviour needs a session →
V3 queued.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 21:05:03 +01:00
Bernardo Magri
0fe46221ea feat(capture): screen recording — menu start, bar-click stop (item 12)
All checks were successful
Check / eval (push) Successful in 2m56s
nomarchy-record owns the lifecycle: start prefers wl-screenrec (VAAPI
hardware encode) and falls back to wf-recorder if it dies at startup
(the no-VAAPI case), stop SIGINTs so the file finalizes cleanly, and
status feeds a self-gating Waybar ⏺ REC (alert red) whose click is
the one stop surface — signal 8 pokes make it appear/vanish
instantly. Capture gains Record region/screen (+ audio variants) and
self-swaps to '■ Stop recording' while one runs. Parity: both summer
whole-swap bars carry the same module. Recordings land in
~/Videos/Recordings with the screenshot timestamp naming (deviation
from 'next to Screenshots': an .mp4 in Pictures felt wrong).

V1: rendered bar config + CSS asserted, summer jsonc parse, recorder
dry-run state machine correct (idle/active/usage exits), both
recorders in home-path. Real capture needs a session → V3 queued
(incl. the VAAPI-vs-fallback check on the AMD box).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:58:40 +01:00
Bernardo Magri
2a23e82169 feat(doctor): nomarchy-doctor — read-only health sheet + VM check (item 10)
Some checks failed
Check / eval (push) Has been cancelled
The things that actually break user machines, one command (and menu ›
System › Doctor): failed system/user units, disk space (real
filesystems only — fstype allowlist makes it VM/live safe, device
dedupe collapses / and /nix), theme-state.json parses + is
git-tracked, flake dirty/behind as warnings, generation age (profile
symlink mtime — store paths are epoch-1), snapper timeline when
enabled. Read-only by contract; every ✖ prints the one fixing command;
exit 1 on any problem. Ships as pkgs/nomarchy-doctor so the VM check
runs on a minimal node.

Verified: V1 package build (shellcheck-gated) + a real-hardware smoke
that correctly flagged a genuinely failed user unit on the dev box;
V2 green — checks.doctor VM test: induced failed unit flips the sheet
to exit-1 naming the unit and its fix, reset-failed returns healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:34:11 +01:00
Bernardo Magri
1b8eccbdca feat(menu): System › Rollback — desktop generations picker (item 9b, item 9 done)
All checks were successful
Check / eval (push) Successful in 3m1s
Undo is one menu away: the new rollback flow lists the last ten Home
Manager generations (the theme/config history is already generations,
newest marked current) and activates the picked one in a terminal —
HM's supported rollback, reversible by re-picking a newer one, so no
typed-yes gate. System-level undo deliberately links out rather than
reimplementing destructive flows in rofi: the Snapshots row opens the
existing gated flow, and 'boot an older generation (how)' fires an
instruction notification pointing at docs/RECOVERY.md §3 (which now
cross-references the menu path).

V1: template-home built, generated menu bash -n clean, row rendering
and pick-parsing verified against fixture generations output. V3
exercise queued.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:24:37 +01:00
Bernardo Magri
774bdad6e4 feat(desktop): viewers (zathura+imv) + xdg-mime defaults (item 8)
All checks were successful
Check / eval (push) Successful in 2m52s
The complete-workstation gap: no PDF/image viewer shipped and nothing
set mime defaults, so 'open a photo' fell to GIMP. Two new default-on
toggles: nomarchy.viewers (programs.zathura — Stylix themes it, target
added to the explicit list — plus imv) and nomarchy.mime (mkDefault
xdg.mimeApps: pdf→zathura, image→imv, av→mpv, text→code, dir→thunar;
html/http(s)→firefox as inert entries that only activate when a
browser is installed — the ship-a-browser Decision stays open, now
smaller). Associations for absent apps are skipped by GIO, so the
whole thing degrades as the user curates the suite. Deviation from
the backlog text: viewers are a module toggle, not template package
lines — zathura's theming needs its HM module (the real-config
boundary of the toggle discipline).

V1: template-home generation asserted — mimeapps.list rendered, themed
zathurarc, binaries in home-path. V3 open-a-file smoke queued.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:06:45 +01:00
Bernardo Magri
9cd6f5e30c fix(menu): Back audit — cheatsheet gets ↩ Back, exceptions documented (item 24)
All checks were successful
Check / eval (push) Successful in 3m3s
Audited every rofi -dmenu call site in the generated nomarchy-menu +
nomarchy-vpn. The earlier "Back everywhere" pass held up: power,
power-profile, theme, clipboard, files, audio, display (both stages),
capture, tools, system and all four VPN lists already end in ↩ Back.

Fixed: the keybinds cheatsheet — Enter on any row silently closed it;
it now ends in ↩ Back returning to the root picker (its launch point),
and Enter elsewhere stays a deliberate no-op.

Documented (rofi.nix, at the convention comment + call sites): external
modi (rofi-calc, rofi-emoji, networkmanager_dmenu, rofi-pulse-select)
cannot take an injected row — Esc is their back path; free-text
prompts (web, ask) have no list to append to.

Verified: V0; V1 (built nomarchy-menu, bash -n OK; awk audit over the
generated script shows the only back-less lists are the root picker —
by design — and the free-text prompt). V3 spot-check queued.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:20:08 +01:00
Bernardo Magri
a83edb0d36 fix(menu): network picker blank rows — compact mode for networkmanager_dmenu (item 22)
All checks were successful
Check / eval (push) Successful in 3m13s
Source-read of the pinned networkmanager_dmenu 2.6.2: with
compact = False (what our config.ini set) the script inserts literal
empty rows as separators between the ethernet/wifi/VPN sections —
the nameless entries Bernardo saw on the Latitude. compact = True
drops the separators and uses the unpadded row format, which also
renders better in rofi's proportional font than space-aligned columns.

Known residual, documented in config comment + HARDWARE-QUEUE: a
hidden-SSID AP still renders one nameless row (sec+bars only; the
name dedup keeps exactly one, and no config option filters it). If the
V3 retest still shows it, the follow-up is a small source patch
skipping empty-name APs in create_ap_list.

Verified: V0; V1 (rendered config.ini shows compact = True). Behavior
needs Wi-Fi hardware — V3 queued on the Latitude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:15:05 +01:00
Bernardo Magri
fd6e5f60e9 fix(theme): generated waybar/rofi CSS invisible on on-surface palettes (item 27)
All checks were successful
Check / eval (push) Successful in 2m53s
Follow-on to the swaync fix (a643391): the generated Waybar CSS colored
the module row and #window @subtext and dim states @muted, and the
generated rofi theme put @fg text on @surface chips — invisible where
palettes use those roles as "on-surface" (flexoki-light: subtext==base,
surface==text; gruvbox: muted/base = 1.27). flexoki-light has no
whole-swap, so it ran this CSS. Generated surfaces only — the summer
whole-swaps are hand-authored and untouched.

- waybar: secondary text → alpha(@text, 0.85); dim states (inactive
  workspaces, DND bell, muted volume) → alpha(@text, 0.5).
- rofi: @dim → text+"80", @surface → text+"1a" (#RRGGBBAA fg tints).
- check-theme-contrast.py pairings/docs now cover all three generated
  surfaces; status glyphs (good/warn/bad on base, worst 2.05) are
  deliberately not held to a text ratio — raising them is palette
  design, a human decision.

Verified: V0; V1 (checks.theme-contrast green over 21 themes;
downstream-template-home builds; rendered waybar style has zero
@subtext/@muted/@surface; rendered custom.rasi shows fg-derived
tints). Honesty: NOT visually verified — V3 queued (flexoki-light
eyeball + dark-theme regression spot-check).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:08:18 +01:00
Bernardo Magri
a643391d3d fix(swaync): palette-safe pairings — text was invisible on summer-day (item 25)
All checks were successful
Check / eval (push) Successful in 2m52s
Palette roles aren't uniform: summer-day and flexoki-light use subtext
as text-ON-surface (== base) and surface as a dark chip (== text), so
swaync's @subtext-on-@base body text and @surface/@text buttons were
self-colored (caught on the Latitude).

- body/summary: @text on @base — the one text pairing that passes in
  every palette (survey: worst WCAG ratio 5.18 across all 21 themes;
  subtext/base bottomed at 1.00 twice, muted/base at 1.27).
- hover rows + widget-title buttons: alpha(@text, 0.1) tints —
  contrast by construction; subtext/surface defines dropped.
- NEW checks.theme-contrast (tools/check-theme-contrast.py): asserts
  the hex-on-hex text pairings across themes/*.json, cheap (no VM).

The audit also found the generated waybar/rofi CSS shares the bug
class, live on flexoki-light (no whole-swap) → BACKLOG item 27.

Verified: V0; V1 (checks.theme-contrast builds green, 21 themes;
rendered services.swaync.style has zero @subtext/@surface). Honesty:
NOT visually verified — V3 queued (notify-send on summer-day).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:53:01 +01:00
Bernardo Magri
63136a8cb1 fix(keybinds): SUPER+? never fired — shifted keysym needs SHIFT in the modmask
All checks were successful
Check / eval (push) Successful in 2m53s
? is typed as Shift+/, so Hyprland saw mods SUPER+SHIFT with keysym
`question` and the exact-modmask match skipped the `$mod, question`
bind — the keypress fell through to the focused window (caught on the
Latitude, BACKLOG item 26; the menu itself was fine from a terminal).

- keybinds.nix: bind is now `$mod SHIFT, question`.
- rofi.nix prettyKeys collapses "SHIFT + ?" back to "?" so the
  cheatsheet row keeps the documented SUPER + ? label.
- MEMORY.md gotcha: shifted keysyms need SHIFT in mods — invisible to
  eval-tier tests.

Verified: V0 (flake check --no-build); V1 both surfaces (eval'd
settings.bind renders `$mod SHIFT, question, exec, nomarchy-menu
keybinds`; built nomarchy-menu's cheatsheet renders `SUPER + ?`).
V3 queued: SUPER+? on the Latitude after home-update + relogin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:46:50 +01:00
Bernardo Magri
0d80ab272f docs(agent): sweep #2 late confirms — auto-timezone PASS, item 26 is bind-only
All checks were successful
Check / eval (push) Successful in 2m58s
timedatectl shows Europe/London → auto-timezone checked off (manual
set-timezone re-trigger not exercised). `nomarchy-menu keybinds` opens
fine from a terminal, so item 26 is confirmed to be only the
`$mod, question` bind, not the menu.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:40:34 +01:00
Bernardo Magri
397dd5991a docs(agent): Latitude sweep #2 booked — 6 passes, SUPER+? bug filed (item 26)
All checks were successful
Check / eval (push) Successful in 3m0s
PASS: waybar supervisor resilience, snapshots polkit+GUI (restore/
rollback residual queued), sys-rebuild, night-light on/off,
auto-commit, hibernate single-unlock (1b0eeea confirmed).
Partial: auto-timezone (enabled; zone check pending).

Item 26: SUPER+? never fires — `$mod, question` can't match because a
real ? carries SHIFT in the modmask; fix is `$mod SHIFT, question`.
SUPER+SHIFT+K no-op was the comma-layout gate working (none
configured) — retest annotated in HARDWARE-QUEUE.

PROPOSED: menu parity for downstream-flake options (Bernardo) — every
hand-edit-the-flake option gets a menu writer, per the in-flake-state
philosophy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:36:32 +01:00
Bernardo Magri
0e6c678835 docs(agent): Latitude QA 2026-07-04 — media keys PASS, swaync contrast bug filed
- HARDWARE-QUEUE: media keys + touchpad gestures (dccceb4) checked off
  — verified working on the Latitude 5410 by Bernardo.
- BACKLOG item 25 (NOW): swaync text unreadable on summer-day. Traced:
  swaync.nix pairs @subtext on @base, but summer-day (and flexoki-light)
  use subtext as text-ON-surface (== base), and summer-day also has
  surface == text — self-colored body text and buttons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:07:32 +01:00
Bernardo Magri
18b854563b fix(waybar): supervisor respawn + clean-restart switches; add sys-rebuild
Recovers the crashed iteration #7 (items 21+23, Latitude QA findings):

- nomarchy-waybar supervisor (waybar.nix, exec-once'd from
  hyprland.nix): any waybar exit respawns the bar; crash-loop guard
  (5 fast exits -> critical notify + stop); TERM trapped for a real
  stop. Fixes the bar-less session after a theme-switch crash.
- nomarchy-theme-sync prefers a clean `pkill -x waybar` (supervisor
  restart with fresh config+style) over the crash-prone in-place
  SIGUSR2 reload when the supervisor is running; SIGUSR2 stays as the
  unsupervised fallback.
- sys-rebuild: snapshot-first system rebuild against the CURRENT lock
  (no `nix flake update`) — the no-update twin of sys-update;
  README §3/§5 + motd list it.

Verified: V0 re-run green after crash recovery (flake check --no-build
+ py_compile); V1/V2 per the crashed session's journal entry (HM
renders exec-once=nomarchy-waybar; headless software-GL VM:
SIGKILL -> new pid, clean kill -> respawn, SIGUSR2 -> alive).
V3 pending on the Latitude (queued in HARDWARE-QUEUE.md).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:07:05 +01:00
Bernardo Magri
f70838c5b5 feat(keyboard): multi-layout cycle bind + summer-bar language parity
All checks were successful
Check / eval (push) Successful in 2m59s
SUPER+SHIFT+K → hyprctl switchxkblayout current next, rendered only
when the session layout has a comma — the same gate as the Waybar
language indicator. Bind data is a separate multiLayoutBinds list in
keybinds.nix (still one source); hyprland.nix and the rofi cheatsheet
gate on the identical condition, so bind and cheatsheet cannot drift.
`current` targets the focused keyboard, so per-device-overridden
boards (single layout) are a no-op.

Parity: hyprland/language (with the keyboard glyph) added to both
summer waybar.jsonc whole-swaps + #language in their CSS. Static JSON
cannot eval-gate, so on summer themes the module shows even with one
layout — parity-over-minimalism, deliberate. Found en route:
summer-night has an idle_inhibitor the generated bar lacks (reverse
gap) — folded into backlog item 13.

Also — agent/BACKLOG.md gains items 21-24 from Bernardo's Latitude
5410 hardware QA (waybar crash on theme switch + no respawn; nameless
network-menu rows; a no-update sys-rebuild; Back-everywhere audit),
queued ahead of everything; HARDWARE-QUEUE's snapshot re-test now
carries the "update to >= a47aa3a + relogin" precondition, since
findings 5/6 match the pre-polkit-agent behavior exactly.

Verified: V0 (flake check, both summer JSONs parse) + V1 both
directions (default single-layout build renders no bind; us,de via
extendModules renders bind AND cheatsheet row). V3 cycle check queued.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 14:11:12 +01:00
Bernardo Magri
a47aa3aff5 fix(snapshots): un-break the GUI — polkit agent + launcher, not an override
All checks were successful
Check / eval (push) Successful in 3m4s
Re-diagnosis of the "btrfs-assistant 2.2 segfaults on launch" bug, and
the fix is not the planned nixpkgs override:

1. gdb: the crash is btrfs_util_subvolume_iterator_next() —
   libbtrfsutil's UNPRIVILEGED subvolume-iteration path in btrfs-progs
   6.17.1 (upstream-fixed after 6.17.1, kdave/btrfs-progs 886571653).
   Symbol versions were checked and match: not ABI/link drift.
2. VM A/B on real btrfs: as root it works (exit 0); as a user it
   segfaults (139). The pkexec launcher runs it as root — so the GUI
   path was fine all along, except…
3. …the distro shipped NO polkit authentication agent: every pkexec
   prompt in the session failed silently. That is the actual root
   cause of "the GUI doesn't open".

Shipped: hyprpolkitagent (Hyprland's Qt agent — the prompt is
Stylix-themed) via exec-once in hyprland.nix; `nomarchy-menu snapshot`
prefers btrfs-assistant-launcher again, with the nomarchy-snapshots
fzf flow kept as fallback (and for SSH); checks.snapshot-gui guards
the root path + offscreen GUI event loop on a real btrfs volume so a
lock bump can't silently regress it. No btrfs-progs patch: all our
flows are root-side; the unprivileged fix arrives with a lock bump.

Verified: V0 (flake check) + V2 (snapshot-gui check, executed green) +
V1 (HM generation builds; hyprland.conf carries the agent exec-once;
generated menu passes bash -n with the launcher wiring). Remaining V3
queued: first on-hardware themed polkit prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 10:07:40 +01:00
Bernardo Magri
bc4e8e1410 feat(state): opt-in auto-commit of state mutations (Phase 4)
settings.autoCommit — toggled from System › Auto-commit (self-gated on
the flake being a git repo) — makes every nomarchy-theme-sync apply/set
also git-commit theme-state.json in the downstream flake, so settings
history is `git log`. Off by default.

Design: the flag is live-read by the tool on each write (nothing in Nix
consumes it → instant toggle, no rebuild, no new option); the commit is
pathspec-limited to theme-state.json so unrelated dirty files are never
swept up; it fires when the flag is on before OR after the write, so
the disable-toggle itself lands in history; same-value writes no-op
(diff against HEAD); missing git identity falls back to
Nomarchy <nomarchy@localhost>; `bg next` is deliberately excluded.

Rider fix: `get` now prints booleans JSON-style (true, not Python's
True) — un-sticking the System menu's "Auto timezone (on/off)" label,
whose `= true` comparison could never match. All shell consumers
already normalise true|True, so no other behavior change.

Verified: V0 (py_compile, nix flake check) + V1 (HM generation builds,
generated nomarchy-menu passes bash -n, wiring present in the rendered
script) + a 7-assertion sandbox-repo round-trip covering enable/set/
same-value/disable/post-disable/apply paths and the identity fallback.
Remaining: on-machine check queued in agent/HARDWARE-QUEUE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:12:29 +01:00
Bernardo Magri
6d70bba8e6 feat(agent): autonomous-loop infrastructure + roadmap rework
A git-tracked agent/ directory so AI agents can iterate on the distro
unattended (runner-agnostic: /loop, headless claude -p, or a fresh
manual session — all state lives in the checkout, per the distro's own
philosophy):

- LOOP.md — the iteration protocol: orient → pick one BACKLOG task →
  verify up the V0–V3 ladder → commit+push main → record. Safety rails
  (v1 untouchable, no force-push, no surprise lock bumps) and
  stop-and-escalate conditions.
- BACKLOG.md — the forward half of docs/ROADMAP.md reworked into a
  prioritized queue (5 NOW / 6 NEXT / LATER / PROPOSED / Decisions);
  ROADMAP.md stays the design/decision record + shipped log.
- GOALS.md — the four pillars (stable > reproducible/zero-hidden-state >
  effortless config > beautiful), quality bars, non-goals.
- CONVENTIONS.md — coding/design rules (in-flake state, menu placement,
  Waybar whole-swap parity, toggle-vs-package, no formatter).
- MEMORY.md — curated hard-won lessons (VM recipes, btrfs-assistant
  segfault watch, rofi/WirePlumber/hyprlock gotchas).
- HARDWARE-QUEUE.md — every pending V3 on-hardware check collected from
  the ROADMAP, with exact steps, split by machine.
- JOURNAL.md — append-only iteration log, seeded with this bootstrap.

Plus a root CLAUDE.md entry point and README/ROADMAP pointers.

Verified: V0 — docs-only; nix flake check --no-build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 08:51:11 +01:00