Commit Graph

237 Commits

Author SHA1 Message Date
Bernardo Magri
eb8f9fad88 feat(nixos): enable Magic SysRq keys for safe crash recovery
Some checks failed
Check / eval (push) Failing after 57s
2026-07-08 22:06:04 +01:00
Bernardo Magri
1d8c1a4314 feat(home): add satty for screenshot annotation
All checks were successful
Check / eval (push) Successful in 3m1s
Added satty (Wayland-native screenshot annotation tool) and integrated it with the Capture flow.
Wired `$mod SHIFT, Print` to slurp a region and open satty.
Added "Annotate region" to the rofi Capture menu (Tools > Capture).
Added satty's configuration via home-manager to inject the active theme's palette colors into its UI.

Verified: V1 (flake check --no-build, home activation package build).
Pending: V3 hardware/visual check for the satty UI (queued in HARDWARE-QUEUE.md).
2026-07-08 21:49:06 +01:00
Bernardo Magri
3dcbb2b0b6 fix(home): make stateVersion overridable
All checks were successful
Check / eval (push) Successful in 3m1s
Changed home.stateVersion assignment to use lib.mkDefault instead of a plain assignment.
This fixes downstream flakes that need to migrate existing NixOS installations without bumping their state version.

Verified: V1 (flake check --no-build, home activation package build).
Pending: nothing.
2026-07-08 21:44:31 +01:00
Bernardo Magri
6bd03747c9 fix(waybar): inhibited notification state reads by shape, not color alone
All checks were successful
Check / eval (push) Successful in 2m59s
Item 28 color-only accessibility sweep. Swept every Waybar status
indicator for shape/glyph redundancy: all already carry non-color
distinction (updates shows a count + self-hides; vpn/doctor/nightlight/
recording self-gate; battery/idle/muted are glyph-distinct) except the
swaync notification module — its `inhibited-*` states reused the normal
bells (󰂜/󰂚), so an app-inhibited (suppressed) state was distinguished
from a normal one by color alone.

Route every suppressed state (DND + inhibited) to the bell-off glyph 󰂛
+ @muted, so "notifications are off right now" reads by SHAPE. Applied
to the generated bar and all four whole-swaps (summer-day/night use
their own bell-off glyph; executive-slate/boreal use 󰂛) — parity rule.
Reuses the glyph DND already ships, so no new codepoint / tofu risk.

Verified: V0 (flake check --no-build; 4 jsoncs valid JSON) + V1 (built
homeConfigurations.nomarchy.activationPackage — the built config has
inhibited-* = U+F009B and style.css routes .inhibited-* → @muted).
V3 pending: live inhibited-state render with an app holding an inhibitor
(HARDWARE-QUEUE). MEMORY gains the "status is never color-only" invariant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:07:10 +01:00
Bernardo Magri
be8d8a7d9b feat(waybar): open a calendar (calcurse) on the clock click
All checks were successful
Check / eval (push) Successful in 2m54s
Item 42, de-scoped by Bernardo to app-only — no account sync (the user
configures that themselves locally). Clicking the Waybar date/clock now
runs nomarchy-calendar → calcurse in a floating, centered ghostty window
tagged --class=com.nomarchy.calendar (+ --gtk-single-instance=false for a
fresh standalone window), matched by a float+size+center windowrule. The
launcher self-gates on calcurse. Dropped the clock's format-alt date
toggle (conflicts with on-click) and moved the long date into the tooltip.
Parity: on-click added to all four whole-swap jsoncs.

Ships calcurse + pwvucontrol uncommented in the downstream template
(opt-out). Enforces ghostty always installed (ghostty.nix: enable = true
unconditional; nomarchy.ghostty.enable now gates only the theming/config)
so the --class launcher can rely on it.

Verified: V0 green; four jsoncs valid JSON; V1 HM built — launcher ships,
generated clock renders on-click=nomarchy-calendar (format-alt gone, date
in tooltip), hyprland has the 3 calendar windowrules, ghostty in the HM
profile. Click→float→calcurse month view is V3 (queued). Sync is
deliberately the user's own config, not a distro feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:19:30 +01:00
Bernardo Magri
1143e67f95 feat(waybar): combined power menu on the battery + profile icons
All checks were successful
Check / eval (push) Successful in 3m5s
Item 36(b). Adds `nomarchy-menu powermgmt` — one flat rofi menu of power
Profile rows (→ powerprofilesctl set) + Charge limit rows (→ the same
theme-sync writer batterylimit uses), each half self-gating on its
hardware. No backend duplication; Custom charge stays in the dedicated
batterylimit picker.

Opened two ways (Bernardo's call): the Waybar battery icon AND the
power-profile icon — the profile icon's click flips from cycle → menu.
Wired in waybar.nix and all four whole-swaps (parity). Named `powermgmt`
to avoid the whole-swaps' existing `custom/powermenu` shutdown button
(→ nomarchy-menu power), which is left untouched. The granular System ▸
Power profile / Battery limit rows stay ("keep both").
nomarchy-powerprofile-cycle kept as a standalone bin, now unbound by
default.

Verified: V0 green; V1 HM built (bash -n) — powermgmt case ships, the
generated waybar-config.json wires both icons (2 hits), all four jsoncs
re-wired and still parse, shutdown buttons intact. Click→menu→apply is
V3 (queued).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:40:40 +01:00
Bernardo Magri
714fbd1daf feat(rofi): show the Print-key hints on the Capture menu rows
All checks were successful
Check / eval (push) Successful in 3m2s
Iteration #66 (direct request: "make sure menu items that have a keybind
display it"). Audit found every SUPER(+CTRL) module-opening row already
hinted (item 28c); the one gap was Tools ▸ Capture — its Region/Full
screen rows run the exact grim commands of the Print / SHIFT+Print /
CTRL+Print binds but showed nothing. Adds `hintForBind mods key` (matches
keybinds.nix by mods+key, so no drift and no brittle full-action compare)
and `-markup-rows` on the Capture menu so the spans render.

Verified: V0 green; V1 HM built — shipped nomarchy-menu shows
`Region → clipboard {{Print}}`, `Region → file {{SHIFT + Print}}`,
`Full screen → file {{CTRL + Print}}`. Substring case-matches precede the
appended span, so dispatch is unchanged. V3 (pango dim render) queued.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:16:45 +01:00
Bernardo Magri
b1cf10ff06 fix(keybinds): SUPER+? bind uses base keysym slash, not question
All checks were successful
Check / eval (push) Successful in 3m10s
Item 32. Item 26 added SHIFT to the modmask but kept the shifted keysym
`question`, so the bind stayed dead. Hyprland 0.55 resolves a bind's
keysym with SHIFT consumed (issue #7750, behaviour since 0.43): with
Shift held, `$mod SHIFT, question` is matched against `slash` and never
fires. The base keysym is what works — same pattern as this file's own
`$mod SHIFT, 1` workspace binds. Also updates rofi.nix prettyKeys to
collapse "SHIFT + /" → "?" so the cheatsheet row still reads "SUPER + ?".

Verified: V0 `nix flake check --no-build` green; V1 HM activation built,
built hyprland.conf carries `bind=$mod SHIFT, slash, exec, nomarchy-menu
keybinds` (kb_layout=us → slash is `?`). The keypress opening the menu is
V3 — queued (HARDWARE-QUEUE, existing SUPER+? entry updated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 13:59:02 +01:00
60c6f14c08 feat(rofi): battery charge-limit toggle in the System menu
All checks were successful
Check / eval (push) Successful in 2m54s
Add a `batterylimit` nomarchy-menu subcommand — a preset picker
(80/90/60/Off/Custom…) that writes settings.power.batteryChargeLimit via
theme-sync (--no-switch; lands on the next sys-rebuild, power.nix's oneshot
applies it). Surfaced as a first-class System row, self-gated on the
charge_control_end_threshold sysfs node like Power profile.

Moved out of the gum control-center (removed its Battery Limit case +
choose entry) so the toggle lives in one place — the rofi menu Bernardo
actually uses, per his request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:50:26 +01:00
a8391c381d feat(hyprland): float+center Bluetooth + printer config dialogs
All checks were successful
Check / eval (push) Successful in 3m8s
Conservative first cut of the floating-window audit (item 41): float and
center the two shipped, unambiguous config dialogs — blueman
(manager/adapters) and system-config-printer — alongside the mixer. Uses
(?i) class regexes tolerant of the XWayland `.…-wrapped` form, consistent
with the existing (?i) usage in this file.

Deliberately conservative: network is dmenu/nmtui (no GUI editor), and the
polkit prompt + GTK file portals are deferred because their window class
can't be verified headlessly — a guessed class is a dead rule. Those wait
for a hardware `hyprctl clients` read (queued).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:39:43 +01:00
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
cd5d3b51e2 fix(rofi): Display menu Back returns to System on single-monitor
All checks were successful
Check / eval (push) Successful in 3m0s
The mode picker's Back did `exec "$0" display`. On a single-monitor
machine with no display profiles the output chooser is skipped, so
re-running `display` dropped straight back into the same mode picker — an
apparent no-op. Track modeBack (display when the chooser was shown, else
system) and dispatch Back to it, so single-monitor Back leaves Display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:45:11 +01:00
6c61b51d55 fix(rofi): drop the go-previous icon from menu Back rows
Some checks failed
Check / eval (push) Has been cancelled
Every submenu's "↩ Back" row rendered two arrows: the ↩ glyph in the
label plus a themed go-previous icon. back() now emits the plain label
only — identical to what pick-lists already append — so Back shows a
single glyph everywhere. BACK="↩ Back" stays the universal label/match,
so no case-branch comparison changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:42:56 +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
907d3123ea style(themes): brand the launcher mark and rework waybar icon sizing
All checks were successful
Check / eval (push) Successful in 3m11s
- Pin `font-family: Nomarchy` on #custom-nomarchy (boreal, executive-slate)
  so the branded mark at U+F000 wins over the Nerd Font's glass glyph at
  the same codepoint (summer bars already pinned it).
- Enlarge the icons on icon+text modules via a Pango span, with a gap so
  the glyph no longer crowds the value. The volume module gets a wider
  gap: its level glyph is unusually wide and overlapped the % otherwise.
- Bump icon-only status modules (caffeine, speedometer, …) to match, so
  they don't read smaller than their neighbours. The generated bar sizes
  everything relative to fonts.size (+2/+3pt) so it scales per theme.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:36:01 +01:00
ddf9b186e4 fix(rofi): resolve whole-swap launcher @import and page the app grid
The Apps launcher (`rofi -show drun -theme launcher`) imported
`custom.rasi` — the name HM gives *generated* themes — but a whole-swap
theme's base is deployed under its source basename (`rofi.rasi`), so the
import resolved against $HOME and rofi errored with "custom.rasi not
found" on every whole-swap theme. Make the default import track the
active base (rofi.rasi for whole-swaps, custom.rasi otherwise) and point
Boreal's own launcher.rasi at rofi.rasi.

Also give Boreal's app grid `flow: horizontal` so Down at a column's foot
pages to the next screen instead of jumping to the next column's top,
mirroring the theme-picker grid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 17:36:01 +01:00
Bernardo Magri
175b877f95 fix(rofi): route the menu's Apps entry through the launcher theme too
All checks were successful
Check / eval (push) Successful in 2m59s
SUPER+Space/D open `rofi -show drun -theme launcher`, but the main menu's
"Apps" entry still ran bare `rofi -show drun`, so that path skipped the
per-theme launcher override (e.g. Boreal's icon grid) and fell back to the
list. Same theme for both launch sites now — consistent launcher whichever
way it's opened.

V0: flake check green; no bare `rofi -show drun` launch sites remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:58:49 +01:00
Bernardo Magri
f2c815ddbd feat(themes): add Boreal + make theme appearance per-theme
All checks were successful
Check / eval (push) Successful in 3m1s
Boreal — a Nord × Everforest × Dracula dark theme: a boreal forest at
night under the aurora. Nord's frost bones, Everforest's comfort, Dracula's
jewel-tone violet/cyan as the aurora light.

Palette (WCAG-checked, all 24 themes × 7 pairings pass): frost-slate base,
soft-vivid violet accent (#B79BE8, 6.35 on base) + frost-cyan accentAlt
(#86C7C0, 7.84 AAA), sage-green/gold/coral semantics, text 10.66 AAA.

Bespoke, non-standard surfaces:
- Waybar whole-swap (jsonc + css): a floating "aurora frost" bar of three
  frosted-glass pills; launcher+clock left, workspaces centre, and the whole
  right side is ONE continuous pill — the system tray shares its glass with
  the status modules, no separators (the seamless-tray requirement). Full
  module set (parity). GeistMono.
- Rofi: a frosted list base (used by every menu), plus a 4×3 large-icon grid
  for the app launcher only.
- Typography GeistMono Nerd Font (57 MB, new); Iosevka was rejected at 1.1 GB.

Per-theme appearance (new): a preset now carries a full fonts+ui block, like
border, so a theme can ship a bespoke font / terminal opacity / border
thickness / rounding and a switch always replaces it — no leak into the next
theme (verified: boreal→everforest resets mono/rounding/opacity). All 23 other
presets get the current defaults (idempotent — zero visual change). Boreal
runs GeistMono + 0.90 terminal opacity + 3px borders + 12px rounding.

Grid launcher (new, backward-compatible): drun renders via `-theme launcher`;
HM writes ~/.local/share/rofi/themes/launcher.rasi which defaults to
`@import "custom.rasi"` (every other theme's launcher = its list, unchanged),
and a theme may override it (Boreal's grid). Text menus keep the base list.

Wallpaper: an original procedurally-generated aurora-over-treeline (ImageMagick),
palette-matched.

V1: nix flake check --no-build green; strict JSON on every preset; contrast
checker green; the real rofi binary parses the base rasi and resolves
`-theme launcher` + @import + grid overlay; end-to-end eval with boreal active
resolves the waybar whole-swap (tray in the right cluster), the rofi grid
launcher, and the bespoke fonts/opacity/geometry reaching nomarchy.theme, with
no cross-theme contamination. NOT done: on-screen render of the desktop
(V2/V3) and preview.png (deferred to on-hardware capture).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:03:50 +01:00
7569b678db feat(system): menu parity for downstream options (slice 2)
Added pickers for terminal, keyboard layout, and auto-login to the Control Center TUI, wiring them directly to Nix defaults via theme-state.json settings. Verified: V1.
2026-07-06 18:25:46 +01:00
28e21af206 feat(system): menu parity for downstream options (slice 1)
Added toggles for updates, battery limit, bluetooth, and printing to the Control Center. Wired default values to read from theme-state.json settings. Verified: V1.
2026-07-06 18:20:00 +01:00
eecc214ca9 style(waybar): remove cpu and memory modules
Simplified waybar based on user feedback. Verified: V1.
2026-07-06 18:14:39 +01:00
93521c8617 feat(system): nomarchy-control-center TUI
Implemented TUI control center using gum. Added to systemPackages and rofi System menu. Verified: V1. Pending: V3 visual check.
2026-07-06 18:09:39 +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
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
8f2e047f4a feat(menu): dimmed keybind hints on menu rows (item 28c)
All checks were successful
Check / eval (push) Successful in 3m1s
The audit flagged the root picker as six short rows in ~760px of
empty width; the space now carries discoverability. menuHint renders
a dimmed pango span (size small, 55% alpha) from the SAME keybinds.nix
the Hyprland binds and the SUPER+? cheatsheet come from (prettyKeys),
so hints can never drift. Applied to the root picker (Apps special-
cased — it's rofi drun), all Tools leaves, and the System rows with
direct binds; those menus pass -markup-rows. Contains-glob matching
and the exact "↩ Back" row are unaffected.

Verified: V0; V1 (rendered menu bash -n, spans + 3x -markup-rows
present); visual — tokyo-night 1080p capture shows the hints dimmed
and subordinate on normal and selected rows alike.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:36:04 +01:00
Bernardo Magri
8874a22a37 feat(bar): group rhythm + semantic @muted dim states (item 28c)
All checks were successful
Check / eval (push) Successful in 2m57s
Two polish items from the 1080p review. The right cluster gains a
wider breath (margin-left 14px) before each functional group — media
(pulseaudio/cpu/memory), toggles (powerprofile/nightlight/language),
status (battery/updates/notification); a self-hiding group head
degrades to the uniform spacing. Dim states (inactive workspaces, DND
bell, muted volume) move from alpha(@text, 0.5) tints to the
palette's @muted role — safe now that 28b floor-gates muted/base >=
2.0 in every theme; the CSS comment that warned against @muted
documented the pre-28b palettes and now states the new invariant.
Whole-swap bars untouched (authored identity).

Verified: V0; V1 (rendered CSS carries the rule + 4 @muted uses);
visual — tokyo-night 1080p re-capture shows the themed backdrop
(iteration #35's fix, navy not black), the group gap, and legible
muted workspaces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:19:14 +01:00
Bernardo Magri
310614bdd2 feat(hyprland): themed backdrop — misc:background_color from the palette (item 28c)
All checks were successful
Check / eval (push) Successful in 3m2s
The compositor backdrop behind a missing or still-loading wallpaper
was hard black; on light themes that flash reads as a glitch (caught
by the 1080p capture harness on summer-day). Now the theme's base
color, so every wallpaper-less moment stays in-palette.

Also from the summer-day capture review: the authored whole-swap menu
+ island bar survive the 28b retunes as one coherent identity (the
slate menu deliberately mirrors the bar strip — an earlier "polarity"
concern retracted); nit filed for the undimmed placeholder in the two
summer rasi. Punch list updated in item 28.

Verified: V0; V1 — template-home rebuilt, hyprland.conf renders
background_color=rgb(1a1b26). Visual confirmation rides the next
capture run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:08:35 +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
bfb80cb60d feat(state): validation + friendly errors on both write and eval paths (item 11)
All checks were successful
Check / eval (push) Successful in 2m56s
'Never has to master Nix' includes the error messages. The tool grows
validate_state (appearance schema = hard errors; settings.* and
unknown keys = warnings so menu writers and newer schemas keep
working), a read-only `validate` subcommand, and validation BEFORE
every write — an invalid set/apply is refused with the on-disk file
untouched. JSON syntax errors now say line, column, and fix. theme.nix
enforces the same schema at eval time (on the post-defaults state, so
sparse/older files still evaluate) and throws a message naming each
field, its got-value, and the fix paths — the hand-edit escape hatch
no longer ends in a Nix stack trace. Doctor's JSON fix-text points at
validate.

V1: new checks.theme-sync-validate fixture corpus green (good state,
trailing comma, bad hex, bad type, unknown key, refused set with
byte-identical file); negative mkFlake eval of a broken scratch
downstream shows the friendly throw; good-path template-home builds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:44:08 +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
3f5e414341 feat(update): sys-update/sys-rebuild end with an nvd what-changed diff (item 9a)
All checks were successful
Check / eval (push) Successful in 3m0s
The informative half of 'informative + rock-stable': both rebuild
wrappers capture /run/current-system before the switch and print a
package-level nvd diff after it — store-path-pinned so no PATH
dependence, || true so an ornamental diff can never fail a succeeded
rebuild, and an identical-path fast-path says 'no changes'. README §3
notes it. V1: template-system toplevel rebuilt, generated scripts
bash -n clean, nvd smoke-run against the real closure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:14:23 +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