Compare commits

...

243 Commits

Author SHA1 Message Date
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
b18980f642 docs(backlog): fold Bernardo's power-menu mockup into item 36(b)
He supplied a reference `power-menu` rofi script (battery charge + power
profile rows, icon protocol, notify-send). Un-gates the design hold on
36(b): records the shape + the Nomarchy adaptation (reuse the existing
batterylimit/power-profile writers instead of raw TLP; wire the Waybar
battery module on-click). One [human] call left: replace the separate
System rows or keep both surfaces.

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
239c3c4551 docs(backlog): gate item 35 on 41 (no windowrule infra yet); rec pwvucontrol
All checks were successful
Check / eval (push) Successful in 2m59s
Investigating the right-click mixer showed the config has zero
windowrules today, so the float rule needs the infrastructure item 41 is
meant to design list-first. Recorded the gate + the mixer recommendation
(pwvucontrol, app-id com.saivert.pwvucontrol) so the research is not lost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:18:51 +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
8961fd6936 docs(backlog): scope item 33 (rofi scrollbar) as a ~10-file visual slice
All checks were successful
Check / eval (push) Successful in 3m2s
Found every authored .rasi deliberately hides the scrollbar (width:0), so
the overflow hint needs per-theme tuning across ~10 files with a V3-only
payoff — recorded so it is picked up in a themed session, not blind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 22:03:36 +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
28cbaf6f5e docs(backlog): queue 11 rofi/menu/power items from Bernardo (31–41)
All checks were successful
Check / eval (push) Successful in 3m5s
Bugs: 31 rofi search broken under rofi 2.0.0 (lock bump 8fded63 pulled a
major version; case-insensitive fuzzy is configured + honoured yet
matching behaves case-sensitively and Enter launches row 0), 32 SUPER+?
still not opening, 34 Back row shows double arrows, 40 Display menu Back
doesn't return. Plus 36 battery/power quick menu (move the threshold
toggle into the rofi System submenu + a battery-icon power menu, mockup
pending), 39 power-profile menu icons. Features (NEXT tail): 33 rofi
page-overflow scrollbar, 35 right-click volume → mixer, 37 default
media players + mime, 38 capture-to-file binds, 41 floating-window audit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 21:36:19 +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
ce89fddab6 docs(backlog): propose a quick toggle for the battery charge threshold
All checks were successful
Check / eval (push) Successful in 3m4s
Raised by Bernardo. The charge-limit base already ships (batteryChargeLimit
option + systemd sysfs writer + udev re-trigger + control-center entry +
VM check); the gap is UX — it's a type-a-number system option needing a
rebuild. Filed under PROPOSED as a one-click conservation-mode toggle
(~80% ↔ 100%), ideally instant-effect, referencing the existing mechanism
so the base isn't re-implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:52:52 +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
be5888d354 docs(backlog): queue laptop-docking UX and fullscreen-waybar items
All checks were successful
Check / eval (push) Successful in 2m59s
29 (docking/external-monitor UX): unplug→restore the internal panel,
plug→extend by default, easy HDMI/DP audio routing, and clearer
workspace↔monitor behavior. Raised by Bernardo; [human]/[blocked:hw].

30 (waybar overlaps fullscreen video): a fullscreen YouTube video is
drawn under the bar's overlay layer; hide the bar on true fullscreen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:28:35 +01:00
f848e7390f fix(waybar): widen the battery icon-to-text gap when discharging
The discharging `format` put a single space between the icon span and
{capacity}%, but the battery-bar glyphs carry almost no right bearing,
so the state read cramped — while charging/plugged use glyphs with their
own spacing and looked fine. Widen only the discharging line to two
spaces across all four themes that ship their own bar (boreal,
summer-night, summer-day, executive-slate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:28:35 +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
7b599c5786 fix(themes): restore summer-night's blank notification bell icons
All checks were successful
Check / eval (push) Successful in 2m59s
summer-night shipped with its custom/notification format-icons all empty
(the same Nerd Font glyph-loss seen in executive-slate/boreal), so its bar
showed a blank notification indicator regardless of state — while its
sibling summer-day kept the icons. Filled them in from summer-day so the
FontAwesome bell (U+F0F3) / bell-slash (U+F1F6) match across the pair.
Surgical edit — only the 8 icon values changed, hand-formatting untouched.

V1: builtins.fromJSON parses; icons now byte-identical to summer-day; zero
empty glyph fields remain across all four whole-swap bars.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:45:11 +01:00
Bernardo Magri
f924d92f1b fix(themes): restore lost Nerd Font glyphs in executive-slate + boreal bars
All checks were successful
Check / eval (push) Successful in 3m13s
The whole-swap waybar.jsonc for both themes shipped with every Nerd Font
private-use glyph dropped to an empty string — the launcher (U+F000), the
power button (U+F011), and all pulseaudio / battery / notification
format-icons — so the bars rendered with those icons blank. The glyphs were
lost when the files were first written; restored here from the known-good
generated bar (waybar.nix) and summer-night, verified by codepoint:
launcher U+F000, power U+F011, notification bells U+F009A-C, full
battery/volume icon sets. (Files re-dumped with ensure_ascii=False, so the
module arrays are now multi-line — content only, no layout change.)

V1: strict JSON + builtins.fromJSON parse both (13 right-modules each);
comprehensive scan reports zero empty glyph fields; flake check green.

Found while sweeping the branch: summer-night/day carry the same empty
notification format-icons (pre-existing, untouched here) — flagged for a
separate fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 21:24:42 +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
Bernardo Magri
16275947ec feat(themes): full revision of executive-slate — palette rebuild + real whole-swap bar
All checks were successful
Check / eval (push) Successful in 3m15s
The executive-slate preset shipped a waybar.css that referenced @base/@accent
with no @define-color block, and no matching waybar.jsonc — so the raw-read
override ran a largely unstyled bar (the item-28 partial-whole-swap defect).
The palette was also flat (base->surface only 1.13x) with a dim accent
(#4D78CC, 3.91 on base).

- Palette: widen surface stepping (base->surface 1.13x -> 1.29x so panels/
  selection/hover separate), brighten the accent (#4D78CC -> #5A8AE6, 3.91 ->
  5.04 on base, now AA text), lift subtext/good/warn/bad a tier, realign ansi,
  soften bright-white (#FFFFFF -> #F0F3F9). text stays 12.5 AAA. Contrast
  checker: all 23 themes x 7 pairings pass.
- Waybar: complete the bespoke bar as a real whole-swap. New waybar.jsonc with
  the full 17-module set (parity with summer-day/night); waybar.css rewritten
  to lead with its own @define-color block and cover every module + state class
  the helper scripts emit (.recording/.activated/.on/.available/.dnd-*/battery).
  Identity: solid, edge-to-edge, sharp corners, accent underline on the active
  workspace.
- Rofi: drop the broken var() idiom for the house *{}/@name convention (split
  border syntax, message/textbox/scrollbar elements for the theme-grid picker).
- CONVENTIONS: Waybar parity rule now names executive-slate as a third
  whole-swap target (was stale at summer-only).

V1: nix flake check --no-build green; strict JSON parse of both files; real
rofi parsed the .rasi cleanly; with executive-slate set active, the module
eval resolves waybar.settings -> the jsonc, waybar.style -> the css, rofi.theme
-> the override rasi, and theme.nix accepts the palette. NOT yet done: on-screen
render of the bar/rofi/desktop (V2/V3) and preview.png (deferred to on-hardware
capture, per the chosen path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 20:14:30 +01:00
7f10a12ec8 style(themes): improve usability scaling for executive-slate
All checks were successful
Check / eval (push) Successful in 2m54s
Increased base font size from 13px to 15px and expanded padding to provide better touch targets and functional proportions.
2026-07-06 18:56:29 +01:00
825b7e25a4 fix(themes): correct rofi border syntax for new themes
All checks were successful
Check / eval (push) Successful in 3m8s
Rofi expects border width and border-color to be separated, the CSS shorthand is not supported.
2026-07-06 18:53:08 +01:00
801ffa4e24 feat(themes): add executive-slate professional dark theme
All checks were successful
Check / eval (push) Successful in 2m56s
Leveraged Nano Banana to generate a 4k functional minimalist wallpaper. Added the new preset with a solid, high-legibility waybar and flat rofi launcher.
2026-07-06 18:40:54 +01:00
ccd896c8dc feat(themes): add neon-glass theme with segmented pill waybar and glassmorphic rofi
Leveraged Nano Banana to generate a 4k wallpaper. Added the new preset with glassmorphism aesthetic.
2026-07-06 18:35:36 +01:00
51e319d357 chore(backlog): remove completed menu parity item 2026-07-06 18:26:17 +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
3f15f6451f docs(backlog): control center design options
Moved item 18 to Decisions, elaborated form-factor options, and appended journal entry. Verified: V0. Pending: human decision.
2026-07-06 18:01:09 +01:00
nomarchy-bump
8fded63b10 chore(lock): scheduled upstream bump
• Updated input 'home-manager':
    'github:nix-community/home-manager/868d0a692de703c2de98fab61968e4e310b7c28e?narHash=sha256-G1I1gd32F7mp9LAe1DaZ4ZL7NX5gyiKwdCMwro1Vrck%3D' (2026-06-29)
  → 'github:nix-community/home-manager/af2beae5f0fae0a4310cc0e6aef2572f56090353?narHash=sha256-ESQnuNHEDChsB4IxoLRhscVahqkDWkTb%2BqdIz8euYt4%3D' (2026-07-05)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/1f01958ffb5b3545c96d9ef2f4e24c5e5e1eb846?narHash=sha256-i5nw9BYYsMDAaOC4J%2BJmTof6b2GhlyH076awYRNrTV8%3D' (2026-06-29)
  → 'github:NixOS/nixpkgs/a50de1b7d8a586adc18d2395c19de7d6058e6030?narHash=sha256-uslt2pqShTIXDdAHRHv2QkYLsVdY8Oqwz0EA48/RSM8%3D' (2026-07-04)
• Updated input 'stylix':
    'github:nix-community/stylix/3ed763829fc06d32cab3c1f31672379a1f53450e?narHash=sha256-%2B8RpmHKn5n2tYmoRCwiKJ6PeU85q15qnXzGQ2WGMn9Q%3D' (2026-06-29)
  → 'github:nix-community/stylix/a5b35360dd2f9a931e85457c3d6d9688307b939f?narHash=sha256-n%2BOcIYCfHofQgsuDJ5w42BNoGDnJwmd7tHqLNpVaJOU%3D' (2026-07-06)

Gate: nix flake check --no-build (eval tier — see bump.yml header).
2026-07-06 05:20:08 +00: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
096440a7d7 docs(agent): icon-weight A/B captured + published for the item-28 human call
All checks were successful
Check / eval (push) Successful in 3m0s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:33:41 +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
90a5104f94 fix(themes): drop stub waybar.css whole-swaps — four themes ran an unstyled bar (item 28)
Some checks failed
Check / eval (push) Has been cancelled
catppuccin, lumon, nord and retro-82 carried 2-14 line waybar.css
stubs from the rewrite import (f211ef0). A theme waybar.css replaces
the entire generated stylesheet, so those four shipped a raw default
Waybar: no workspace pill, cramped flush-edge right cluster, none of
the generated polish. Deleting the stubs falls them back to the
generated bar in their own palette; their full ~110-line rofi.rasi
designs are untouched. Authored whole-swap bars remain only
summer-day/night.

V2: lumon captured before/after — stub bar was default-styled,
re-capture renders the generated styling. flake check green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:22:44 +01:00
Bernardo Magri
39cfe0fb12 feat(tools): seed guest state at boot so captures render wallpapers (item 28c)
All checks were successful
Check / eval (push) Successful in 3m8s
theme-shot guests had no ~/.nomarchy, so the session's exec-once
`nomarchy-theme-sync wallpaper` died on missing state and captures
showed a bare base-color backdrop. Seed theme-state.json via tmpfiles
before greetd starts the session — the real exec-once path now paints.
(A post-boot testScript seed is too late for exec-once, and a manual
re-run fails without WAYLAND_DISPLAY: awww's client socket is
display-named.)

V2: summer-night capture renders wallpaper + inverted cream bar +
menu + placeholder dim, all coherent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:09:52 +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
4e3acbe89c fix(themes): dim the summer rasi search placeholder + latte/backdrop findings (item 28c)
All checks were successful
Check / eval (push) Successful in 2m59s
Both summer whole-swap rasi rendered prompt and placeholder in the
same bg0 on the green inputbar, reading as one run-on phrase — the
placeholder is now bg0 at 60% alpha, matching the generated theme's
hierarchy.

Recorded with it: the catppuccin-latte 1080p capture shows the
generated menu fully coherent on light themes (the audit's "dark
terminal on latte" was a stale preview), and a dedicated VM probe
confirms misc:background_color is applied (hyprctl getoption =
0xFFEFF1F5) — the dark backdrop in captures is awww's empty-state
layer over it, a wallpaper-less-VM artifact, not a defect.

Verified: rasi edits grep-verified (visual rides the next summer
capture / hardware); latte capture + probe reviewed. Item 28 is now
gated on the icon-weight [human] call and the queued hardware
eyeballs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:45:36 +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
fb75c3dedf feat(tools): 1080p themed-desktop capture harness + first polish findings (item 28c)
All checks were successful
Check / eval (push) Successful in 3m2s
tools/theme-shot.nix: boots the full themed desktop headlessly
(software-GL Hyprland, THEME env picks the slug) at 1920x1080 and
QMP-dumps desktop.png + menu.png (rofi root open) — the review
artifact for slice (c). Two fixes over the 2026-06-19 spike:
home-manager.useGlobalPkgs (the HM submodule otherwise misses the
overlay) and the menu shot via hyprctl dispatch exec. Maintainer tool,
deliberately not a checks.* gate. Known gap in the header: the
wallpaper needs ~/.nomarchy seeded in the guest.

First full-res review (tokyo-night) added to the item-28 punch list:
rofi root menu is ~760px of mostly empty row space (candidate:
right-aligned SUPER+CTRL hints from keybinds.nix), full-color Papirus
menu icons vs the all-monochrome bar glyphs is a [human] identity
call, bar right-cluster group margins confirmed at full res, and the
bar's alpha(text,.5) dim states should become the @muted role now
that 28b floors guarantee its legibility. Whole-swap bar module
parity verified textually — no drift.

Verified: harness end-to-end green (screenshots read back); evals
from tools/ via the relative flake path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:00:28 +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
abc953ea84 feat(tools): theme design audit + item 28 punch list (slice a)
All checks were successful
Check / eval (push) Successful in 3m0s
tools/audit-theme-design.py (report-only, maintainer-run): for every
themes/*.json it checks OKLCH lightness architecture (raised bg stack,
fg hierarchy), WCAG contrast beyond the guarded pairs, status-color
hue families, accent harmony, protanopia/deuteranopia
distinguishability (Machado matrices), and ANSI slot semantics the
greeter/tty now rely on. Ran it over all 21 palettes + a visual pass
over representative previews; findings curated identity-aware (lumon/
vantablack/white/hackerman exemptions are identity, not defects) into
the P1-P3 punch list now recorded in BACKLOG item 28: two palettes
with literally invisible subtext (summer-day, flexoki-light — the
item-25/27 trap at its source), two inverted bg stacks (miasma,
kanagawa), muted/warn floors, a systemic "status is never color-only"
rule, the light-theme ANSI convention decision, and bar grouping
rhythm notes.

Verified: V0 (py_compile; the tool is report-only, deliberately not a
checks.* gate until slice b adopts rules + exemptions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:45:56 +01:00
Bernardo Magri
d1d6a09d9d docs(backlog): item 28 — theme UI review grounded in design theory (Bernardo)
All checks were successful
Check / eval (push) Successful in 3m4s
Human-requested: a thorough visual review of the whole themed surface
(Waybar incl. whole-swaps, rofi, swaync, hyprlock, terminals, splash,
greeter) with "stunning" as the bar. Grounded in design theory rather
than taste: color-harmony models + perceptual (OKLCH) lightness
ordering of the palette roles, color-blind-safe status colors, an
extended contrast checker encoding the adopted rules, a consistent
type scale + spacing grid, and explicit visual hierarchy. Image-expert
models are available for designed artwork — slices that need graphics
stop and ask instead of shipping programmer-art. Sliced audit-first.

Placed at the head of NEXT for Bernardo to reorder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:38:13 +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
e02b4d8200 docs(backlog): design pass for display profiles (item 15)
All checks were successful
Check / eval (push) Successful in 3m1s
The item required a written plan before coding. Option shape reuses the
existing monitorType submodule (attrsOf (listOf monitorType)); active
profile is in-flake state (settings.displayProfile) on the night-light
instant+bake pattern; whole-entry-by-name rebuild overlay with a
disable-guard so a stale menu resolution pick can't resurrect a
disabled panel; self-gated System > Display profile row; sliced a/b/c
with hotplug auto-switch and workspace binding deferred behind the
core picker. kanshi remains a non-goal.

Verified: V0 (docs-only change).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:20:26 +01:00
Bernardo Magri
5eadf0cff6 feat(ci): scheduled weekly lock bump + dispatch fast-lane (item 14 b+c)
Some checks failed
Check / eval (push) Has been cancelled
.gitea/workflows/bump.yml: Mondays 05:17 UTC (+ workflow_dispatch for
security bumps), nix flake update — within the pinned release branches
by construction, never a release jump — gated by nix flake check
--no-build, then a pathspec-limited flake.lock commit pushed to main
with the update's change list as the body. That push triggers check.yml
as a second net. Red gate or push race = a visibly failed run, nothing
lands. v1 promotion stays human. py_compile/bash -n deliberately not in
the gate: they don't read the lock. Container recipe inherited from
check.yml (nixbld, sandbox=false, pinned Nix 2.31.5).

Verified: V0 (yq parse, bash -n per run block); the update/commit/push
logic simulated end-to-end in a scratch clone with a real nix flake
update and a bare remote (changed + unchanged branches, bystander file
stays uncommitted, push lands); today's bumped lock evals green.
Remains: the first real run (cron pickup + Actions-token push) is not
confirmable from a session — BACKLOG item 14 slimmed to that watch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 17:18:54 +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
821032e81c update claude.md
All checks were successful
Check / eval (push) Successful in 3m5s
2026-07-04 22:53:02 +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
352c681f48 docs(agent): MEMORY — check git status before add -A (harness drops files)
All checks were successful
Check / eval (push) Successful in 2m54s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:35:01 +01:00
Bernardo Magri
d2ac131b75 chore: untrack .claude/skills (harness-dropped, machine-local)
Some checks failed
Check / eval (push) Has been cancelled
A git add -A in 2a23e82 swept in .claude/skills/use-gemini/SKILL.md —
a Claude Code harness-injected tool doc, not part of the distro. Same
treatment as settings.local.json (db6a0ba): untracked + gitignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:34: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
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
e05e3647e6 docs(install): stitch the README → template first-run handoff — item 6 complete
All checks were successful
Check / eval (push) Successful in 2m55s
Slice (c), the last of the docs review: the install/first-run story
read end to end against the tree. Facts held (mkFlake args match the
table, disko's @snapshots + hibernation swapfile, .#default/.#me
naming); the one seam — README §3 never pointed the manual path at the
template README's first-run steps — got a connective paragraph. Item 6
deleted from BACKLOG; ROADMAP § Full docs review flipped to ✓.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:45:34 +01:00
Bernardo Magri
472d7502b4 docs(recovery): the undo runbook — generations, journals, snapper, nixos-enter (item 6d)
All checks were successful
Check / eval (push) Successful in 3m2s
Slice (d) of the docs review: docs/RECOVERY.md tells the recovery
story that existed only in pieces, ordered by symptom — bad theme/HM
change (generations, apply-known-good, git-revert the flake), desktop
won't start (TTY, greetd + user journals, VM-GL and preactivate-log
pointers), broken system change (older systemd-boot generation, then
make it stick), lost files (btrfs-assistant / nomarchy-snapshots), and
the ISO + nixos-enter last resort. Commands checked against the tree;
linked from the README file tree and §5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:41:05 +01:00
Bernardo Magri
b0b8a9a09b docs(review): drift pass over OVERRIDES/TESTING/template README (item 6b)
All checks were successful
Check / eval (push) Successful in 3m3s
Slice (b) of the docs review: every factual claim in the three docs
spot-checked against the tree (mkDefault sites, bind priority, CLI
keys, awww naming, preset/wallpaper counts, summer-night's light bar,
live-ISO monitor rule, CI workflow, QEMU GL flags, template steps) —
all held. Two gaps fixed: OVERRIDES §2 predated nomarchy.monitors and
never routed readers to it (added, plus a quick-reference row);
TESTING §1 didn't list the shell-syntax check that §1b says CI runs
(added bash -n).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:36:01 +01:00
Bernardo Magri
56f1cc3fa9 docs(options): README tables reconciled + permanent option-docs check (item 6a)
All checks were successful
Check / eval (push) Successful in 2m59s
Slice (a) of the docs review: 9 undocumented nomarchy.* options got
README rows or inline .leaf mentions (autoTimezone, camera IR-hide,
intel.guc, amd.pstate/vaapi, package, system.stateFile, restic.paths).
The diff is now a flake check: option names are eval-walked from the
four option-declaring modules (config halves stay lazy) and compared
to the tables by tools/check-option-docs.py — undocumented options and
stale table rows both fail. Negative-tested against the pre-fix README
(reports exactly the 9 gaps). Item 6 split into slices a–d in BACKLOG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:31:20 +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
274ffc25e1 docs(agent): model & token economy rules for the loop
All checks were successful
Check / eval (push) Successful in 3m1s
Plan/reason/review on the strong model (Fable 5); delegate mechanical,
fully-specified subtasks (grep sweeps, table reconciliation, repeated
edits, log summarization) to cheaper models (haiku/sonnet) via subagent
model overrides — the strong model writes the spec and owns the commit.
Plus narrow-reading and terse-writing rules, and a cheaper --model
allowance for low-stakes headless iterations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:07: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
1e4427f6af docs(webcam): tuning tools in the app-suite template + libcamera gap filed
All checks were successful
Check / eval (push) Successful in 3m2s
- templates/downstream/home.nix: commented "Webcam tuning" entries
  (cameractrls, v4l-utils) — bare packages, so template not toggle,
  per the option-surface rule; rarely needed since the IR-hide covers
  the common dual-sensor case.
- agent/BACKLOG.md PROPOSED: the portal/Flatpak camera picker still
  lists the internal IR sensor (libcamera path, untouched by the v4l2
  rule) — three options laid out, recommending document-only until a
  T14s-style machine is available to test a libcamera-side rule.

Closes BACKLOG NOW#7. Verified: V0 (flake check green; template still
evaluates through mkFlake).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 10:13:50 +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
baab2d3b88 docs(agent): CI green (run #58) — item 20 reduced to the KVM-runner stretch
All checks were successful
Check / eval (push) Successful in 2m52s
First successful run of .gitea/workflows/check.yml on the live
act_runner: the eval tier is confirmed end to end. What remains of
item 20 is optional infra ([human]): a KVM/nix host-mode runner to
unlock the commented vm-checks job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:46:38 +01:00
Bernardo Magri
5ea4f0c9ac feat(stability): default-on memory-pressure protection (earlyoom)
Some checks failed
Check / eval (push) Has been cancelled
modules/nixos/oom.nix — running out of memory now kills the offending
process (with a desktop notification via systembus-notify) instead of
freezing the desktop for minutes until the kernel OOM killer fires.

earlyoom over systemd-oomd, deliberately: oomd kills whole cgroups,
and a Hyprland session runs as ONE scope (nothing here spawns per-app
systemd scopes, unlike GNOME) — under pressure oomd would take out the
entire desktop to save it. earlyoom kills the single largest process
before the thrash point. nixpkgs default-enables oomd in an inert
state (no slices monitored); it is disabled outright so there is one
owner. Session plumbing is --avoid-listed (unanchored — NixOS wrappers
rename comm to .foo-wrapped); no --prefer tuning, largest-RSS
selection already finds the hog. All mkDefault; opt out with
services.earlyoom.enable = false (README note added).

Verified: V0 (flake check) + V2 — new checks.oom-protection
runNixOSTest, executed: a chunked allocator (686 MB peak, 1 GB VM) is
SIGTERM'd by earlyoom in 0.1 s, a bystander unit survives (the
process-level granularity the module exists for), and systemd-oomd is
asserted inactive. 25 s runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:45:21 +01:00
Bernardo Magri
938753273d fix(ci): move workflow to .gitea/workflows — the server is Gitea, not Forgejo
All checks were successful
Check / eval (push) Successful in 2m50s
The runner was alive all along (gitea/act_runner, ubuntu-latest label);
no run ever appeared because Gitea only reads .gitea/workflows/ (or
.github/workflows/) and ignores the .forgejo/ path the workflow shipped
under. The legacy repo's .gitea/workflows/check.yml was the clue.
References corrected (workflow header, TESTING.md, agent files); this
push doubles as the first live trigger of the workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:42:15 +01:00
Bernardo Magri
46af2f0632 feat(ci): checks-on-push workflow (Forgejo Actions, eval tier)
.forgejo/workflows/check.yml runs on every push to main/v1 (+ manual
dispatch): nix flake check --no-build (full module-system eval incl.
the downstream template through mkFlake), py_compile of
nomarchy-theme-sync, and bash -n over tracked .sh files. The always-on
net under direct-to-main pushes — first slice of the ROADMAP lock-bump
CI item.

Scoped to the eval tier deliberately: the instance's runner is an
act_runner docker container (no systemd, no /dev/kvm — established
from the legacy repo's .gitea/workflows/check.yml, which ran 57 times
on it), so the checks.* VM suite and real builds can't run there. A
commented vm-checks job documents the KVM-runner upgrade path; the
legacy workflow's container gotchas (nixbld setup for the single-user
installer, sandbox=false for Stylix IFD, Nix pinned 2.31.5 vs
lazy-trees, no JS actions past node20) are carried over verbatim in
the header. docs/TESTING.md §1b documents what a green run does and
does not mean.

Verified: V0 locally (the same check commands, minus the container
Nix install) + YAML parse. A real green run depends on the runner
still being registered — not API-visible unauthenticated, so that is
queued as [human] BACKLOG item 20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:20:45 +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
4c2ad38656 docs(agent): backlog revision — fill the workstation gaps
Checked guesses against the tree before adding (GC/auto-optimise/boot
limit and all 21 theme previews already ship — not added). New items,
all confirmed absent:

- NOW#2 CI checks-on-push, promoted because agents push main directly
- NOW#3 memory-pressure protection (systemd-oomd/earlyoom; nothing
  guards the session from OOM today and the release bump died to one)
- NEXT#8 PDF/image viewers + xdg-mime defaults (template ships only mpv)
- NEXT#9 update diff (nvd) + menu-driven rollback
- NEXT#10 nomarchy-doctor health check
- NEXT#11 state-file validation + friendly eval errors
- NEXT#12 screen recording in Capture (+ stop via Waybar indicator)
- NEXT#13 niceties: idle-inhibit toggle, low-battery notify, hyprpicker
- LATER: OCR screenshot-to-text
- Decisions: zram-vs-hibernation, default browser for mime defaults
- docs item now includes a recovery runbook

Verified: V0 — docs-only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:00:57 +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
Bernardo Magri
9726ba3b2f update flake lock 2026-06-30 23:10:09 +01:00
Bernardo Magri
4024da791f feat(display): menu-driven per-output resolution, in-flake
Phase 3 of the in-flake-state philosophy (mirroring night-light + the
keyboard-layout watcher): a System ▸ Display menu item picks an output's
resolution and persists it into the flake instead of a runtime-only tweak.

- rofi.nix: the `display` case reads `hyprctl monitors -j` (auto-skips the
  chooser with one output), lists the output's advertised modes (rounded
  refresh, deduped, highest-first) plus the preferred/highres/highrr
  tokens, applies the pick INSTANTLY via `hyprctl keyword monitor` —
  keeping the output's current position + scale, so only the resolution
  changes — then persists it with `nomarchy-theme-sync set
  settings.monitors.<name> <mode> --no-switch` (no rebuild).
- theme.nix: seed `settings.monitors = {}` (output-name -> "WxH@R") so a
  sparse/older state file still evaluates; exposed via nomarchy.settings.
- hyprland.nix: `resolvedMonitors` overlays settings.monitors onto
  nomarchy.monitors by name — a declared output keeps its other fields
  with only the resolution swapped, an undeclared output (wildcard-only,
  e.g. the laptop panel) becomes a fresh rule. So a later rebuild bakes
  the menu pick into the generated monitor rule — the monitor twin of the
  keyboard graduation. The menu pick wins over a hand-set resolution (it's
  the explicit live action).

Monitor connector names are dot-free, so a direct dotted set-path is safe
(no whole-map read-modify-write like the keyboard watcher needs).

VM-verified via a headless-Hyprland nixosTest (run + removed; too heavy
for a CI gate): the graduation baked the rule into the generated
hyprland.conf, the set/get round-trip persisted, and `hyprctl keyword
monitor` actually changed the reported resolution. Only the interactive
rofi picker isn't headlessly driveable (same caveat as the keyboard watcher).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 23:08:39 +01:00
Bernardo Magri
aac678335c feat(branding): GRUB (UEFI) theme + distroId=nomarchy
Completes the remaining "Distro branding, round 2" work.

GRUB theme: hosts/live.nix builds nomarchyGrubTheme whose background is
the same composed splash as the isolinux/BIOS splash (accent logo on
base), with a palette-coloured boot menu and accent timeout bar. Derived
from nixos-grub2-theme only to reuse its bundled DejaVu .pf2; the stock
NixOS logo is dropped (ours is in the background). Wired as
isoImage.grubTheme so UEFI boot matches BIOS.

distroId: set system.nixos.distroId = "nomarchy" so os-release is honest
(ID=nomarchy, ID_LIKE=nixos). Safe — switch-to-configuration builds its
"is this NixOS?" guard from the configured distroId (and /etc/NIXOS is
the fallback), so rebuilds keep working. isNixos→false blanks the
nixos.org URLs, so extraOSReleaseArgs restores them to the project.

Adds checks.distro-id (runNixOSTest): boots distroId=nomarchy and runs
switch-to-configuration dry-activate green; os-release eval-verified from
the real downstream config; grub theme built + structure-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 22:02:27 +01:00
Bernardo Magri
7d6d74fd7f test(power): VM check for battery charge-limit udev re-apply
Add checks.battery-charge-limit (runNixOSTest): the test_power module
fakes a Mains adapter, toggling ac_online emits a real power_supply
uevent, and the test asserts the udev rule restarts the oneshot (changed
InvocationID). Verifies the re-apply *trigger* at runtime, beyond the
earlier eval check; the sysfs write still needs real hardware. Also
records the SSH_AUTH_SOCK rendered-artifact verification in the roadmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:41:24 +01:00
Bernardo Magri
cdd1897b14 feat(keys): session-level SSH_AUTH_SOCK for GUI clients
The agent's SSH socket was only exported via the zsh integration, so a
GUI client launched outside a shell (rofi launcher, autostarted apps)
couldn't use gpg-agent for SSH. Add a home.sessionVariables export
resolving the socket with gpgconf --list-dirs agent-ssh-socket — the
same lookup the shell integration uses, so the two can't drift — which
reaches GUI apps via the login shell that starts Hyprland (the path
NIXOS_OZONE_WL already takes).

Eval-verified: the var resolves to the gpgconf substitution; the home
config still builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:27:07 +01:00
Bernardo Magri
9976ea06f5 feat(power): re-apply battery charge limit on AC state change
The threshold was written boot-only, so a firmware that clears
charge_control_end_threshold on unplug would silently lose the cap
until the next reboot. Add a services.udev rule on the mains adapter
(ATTR{type}=="Mains", vendor-neutral) that restarts the oneshot via
systemctl --no-block on every AC state change. Uses restart (not
try-restart) so it re-applies even if the boot run was inactive.

Eval-verified: rule present when the charge limit is on, absent when
off; downstream-template-system still evaluates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:10:44 +01:00
Bernardo Magri
cdfe92a089 feat(hardware): hide dual-sensor webcam IR node (fixes dark image)
A built-in RGB+IR webcam (common on recent ThinkPads, e.g. the T14s AMD
Gen 4) exposes its IR face-unlock sensor as a second, identically-named
"Integrated Camera"; apps that pick it show a dark, 8-bit-greyscale
image. nomarchy.hardware.camera.hideIrSensor disables that node on the
V4L2 PipeWire path (WirePlumber monitor.v4l2.rules) so only the colour
camera is offered, matched by card name (overridable irMatch).

- libcamera is left untouched: an external camera you plug in is never
  affected, and surgical internal-only libcamera scoping isn't possible
  (its distinguishing props bind after the monitor rule runs).
- Only the PipeWire node is hidden; the kernel /dev/video* stays open,
  so Howdy face-unlock still reads the IR sensor directly.
- The installer's hardware-db.sh auto-detects a paired RGB+IR webcam
  from /sys/class/video4linux/*/name and bakes the toggle into
  system.nix; commented example added to the downstream template.

Validated live on a T14s: collapses the four enumerated "Integrated
Camera" entries to one colour source; the exact shipped regex disables
the IR node (confirmed in the WirePlumber log) and leaves libcamera
intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:39:49 +01:00
Bernardo Magri
431af618cc feat(tailscale): operator-set login user — VPN menu skips the sudo terminal
nomarchy.services.tailscale now makes the login user the Tailscale
operator (services.tailscale.extraSetFlags = ["--operator=<username>"],
mkDefault), so `tailscale up/down/set` — and the System → VPN menu's
Tailscale controls — run without sudo. The user is already in wheel, so
the operator grant is no real new privilege, just skips the prompt.

nomarchy-vpn's Tailscale actions are now operator-aware: a ts_priv
helper runs the subcommand inline and only falls back to a sudo terminal
if the operator grant is absent (downstream dropped it). Connect brings
an authed-but-down node up inline; a node that still needs interactive
login goes to a terminal so the auth URL is visible.

Option description + template comment updated; ROADMAP follow-up closed.
System + home builds green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:26:15 +01:00
Bernardo Magri
47526ae6e2 feat(menu): VPN setup & management submenu (WireGuard/OpenVPN/Tailscale)
A dedicated System → VPN flow (nomarchy-vpn) past the wifi picker:
- NetworkManager VPN/WireGuard connections shown ● active / ○ inactive,
  toggled up/down on select (networkmanager-group users need no sudo).
- Import a *.conf / *.ovpn via the fd picker → nmcli connection import,
  type chosen by extension. networkmanager-openvpn ships system-side
  (networking.networkmanager.plugins, mkDefault) so the openvpn type is
  available; WireGuard is native to NetworkManager.
- Tailscale block, self-gated on the CLI (= nomarchy.services.tailscale):
  read-only status + up/down + exit-node; privileged actions go through a
  sudo terminal (the snapshot pattern), no operator wiring.

Waybar custom/vpn shield (nomarchy-vpn-status): self-hides unless a NM
tunnel or Tailscale is up; @good tone; click opens the submenu. Wired
into the generated bar and both summer whole-swaps. Decided import-first;
creation deferred to nm-connection-editor. ROADMAP marked shipped.

Eval + home/system builds green; both scripts pass bash -n. Pending an
on-machine check (live nmcli import/connect + Tailscale paths).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:13:03 +01:00
Bernardo Magri
86802f244e docs(roadmap): system VPN setup & management menu (WireGuard/OpenVPN/Tailscale)
A forward-looking item for a dedicated System → VPN flow: import
WireGuard .conf / OpenVPN .ovpn into NetworkManager and toggle them,
plus Tailscale up/down + exit-node (self-gated on nomarchy.services.
tailscale). Notes the plugin dep (networkmanager-openvpn), the Files
picker + Waybar-parity patterns, secrets staying in the connection
manager, and the import-first-vs-creation open question.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:35:11 +01:00
Bernardo Magri
f3325385c1 feat(timezone): opt-in automatic, location-following timezone
nomarchy.system.autoTimezone (off by default): geoclue + automatic-
timezoned drive /etc/localtime from your location, so travelling to
another zone updates the Waybar clock on its own.

Menu-driven, in-flake state (the night-light / keyboard philosophy):
the flag is settings.autoTimezone in theme-state.json (git-tracked); a
System-menu entry toggles it. Because it's a SYSTEM service — not a user
unit it can start/stop instantly like night-light — the toggle
(nomarchy-autotimezone, run in a terminal) writes the flag --no-switch
then drives `sudo nixos-rebuild` (bakes the service + the time.timeZone
override) plus `home-manager switch` (the Waybar-refresh watcher); both
sides read the one flag.

- modules/nixos/timezone.nix: parses settings.autoTimezone from
  nomarchy.system.stateFile (like Plymouth), gates geoclue2 +
  automatic-timezoned, and mkForce-nulls time.timeZone — automatic-
  timezoned sets it null at normal priority, which collides with the
  installer's static value (a hard eval error), so force wins and
  reverts cleanly when disabled. Ships the nomarchy-autotimezone toggle.
- modules/home/timezone.nix: a tiny user service watching timedate1's
  change signal that reloads Waybar (SIGUSR2) on a real zone change, so
  the clock follows live (Waybar captures the zone at construction).
  Gated on the same flag via nomarchy.settings.autoTimezone.
- rofi menu: "Auto timezone (on/off)" in the System submenu.
- option + template example + ROADMAP entry; theme.nix seeds the flag.

Eval-verified on (geoclue+daemon on, tz forced null over a static
installer value, watcher present) and off (static tz intact, nothing
extra); home + system both build green. Needs an on-hardware check
(geoclue detection + the SIGUSR2 clock refresh aren't CI-testable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:34:45 +01:00
Bernardo Magri
aed41793f8 feat(keyboard): per-device layout memory in-flake (no ~/.local/state)
Phase 2 of the in-flake-state philosophy, mirroring night-light: the
nomarchy-keyboard-watch daemon now remembers per-device layouts in the
git-tracked state file (settings.keyboard.devices) instead of
~/.local/state/nomarchy/keyboard-layouts.

- Reads the LIVE working-tree map (theme-sync get → jq) so a pick this
  session is honoured at once; writes instantly with --no-switch (no
  rebuild). Writes the whole map back at the dot-free parent path
  (read-modify-write) so a device name with a dot can't corrupt the
  tool's dotted set-path (Hyprland only sanitises spaces→-).
- Graduation: nomarchy.keyboard.devices now mkDefault-merges entries
  from settings.keyboard.devices, so each remembered device becomes a
  generated Hyprland device{} block on the next rebuild (reproducible,
  hotplug-applied). A hand-written keyboard.devices.<name> still wins;
  once baked, the watcher's is_declared check makes it step back.
- theme.nix seeds settings.keyboard.devices = {} (sparse state still
  evaluates); options/template descriptions updated; ROADMAP marked done.

Eval-verified (graduation + mkDefault precedence) and the set/get
writer round-trips. Still needs the on-hardware hotplug re-verify (the
picker path isn't testable in CI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 09:18:33 +01:00
Bernardo Magri
685126ab47 feat(nightlight): menu-driven, in-flake on/off (no ~/.local/state)
Night light is off by default and configured entirely through the menu,
writing into the downstream flake instead of ~/.local/state — so the
choice is git-tracked and reproducible across a clone+rebuild.

Two flags under a new settings.* section of the state file (exposed as
the nomarchy.settings option):
- settings.nightlight.installed — sticky, gates the hyprsunset unit;
  nomarchy.nightlight.enable mkDefault-reads it. First enable from the
  menu rebuilds to create the unit (the one accepted rebuild).
- settings.nightlight.on — runtime on/off, toggled instantly
  (theme-sync set --no-switch + systemctl), no rebuild. An ExecCondition
  reads the live flag at session start so an off survives reboot;
  splitting it from the sticky flag means a later unrelated rebuild
  never undoes an instant-off (no decay).

Drops the ~/.local/state/nomarchy/nightlight-off marker and the
ConditionPathExists hack. theme-sync set now skips the wallpaper
re-apply for non-wallpaper keys, and its rebuild notifications are
generic ("changes" not "theme").

First cut of the broader principle — any user-settable config gets a
menu writer that lands it in the flake; no state outside the checkout.
Docs (README §4, ROADMAP, template) updated to reflect it.

Eval- and round-trip-verified; on-hardware check pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 20:24:58 +01:00
Bernardo Magri
c8d0b09044 docs(readme): label which file each nomarchy.* option goes in
Split the options reference into separate home.nix / system.nix tables
and note the prefix rule (system/hardware/services → system.nix, the
rest → home.nix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 16:42:03 +01:00
Bernardo Magri
c2f90c7d0a feat(menu): printer setup — system-config-printer in the System submenu
A `nomarchy-menu printers` entry opens system-config-printer (the CUPS
admin GUI), mirroring the bluetooth/blueman pattern: the printing service
ships the package via environment.systemPackages and the menu execs it,
self-gated on the system-config-printer binary so the entry appears only
when nomarchy.services.printing is on. No direct keybind (rare one-off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:09:35 +01:00
Bernardo Magri
5747dc9839 feat(nightlight): persistent off via state marker
A manual `off` now drops ~/.local/state/nomarchy/nightlight-off and the
hyprsunset unit refuses to (re)start while it exists
(Unit.ConditionPathExists=!%h/...), so the off survives logout/reboot
until toggled back on. `on`/toggle-on remove the marker before starting
(a failed condition would otherwise no-op the start). Without the marker
behaviour is unchanged: the service stays WantedBy=graphical-session.target
and resumes its schedule at login, so persistence is opt-in via the toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:09:22 +01:00
Bernardo Magri
d1344712b8 docs(roadmap): note automated upstream lock-bump CI
Capture the maintainer-side plan to automate advancing Nomarchy's own
flake.lock (the single-input delivery channel): a bot drives `main`
behind `nix flake check` + the runNixOSTest suite, and a human still
promotes `main -> v1` behind the on-hardware QA gate that can't be CI'd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:45:56 +01:00
Bernardo Magri
37204f5f45 feat(themes): per-theme rofi identities for nord/retro-82/lumon/kanagawa
Author a designed rofi.rasi whole-swap for the four remaining identity
themes, so all six now carry a distinct menu look (summer-day/night
already shipped their legacy ports). Each is built from the theme's
character rather than a legacy layout (their waybar overrides are
palette-only, so there was nothing to port):

- nord:     soft rounded "frost panel" — frost border, brighter-frost
            selection, aurora-purple prompt
- retro-82: sharp CRT terminal — square corners, amber-on-navy, a teal
            scanline underline, mono
- lumon:    clinical cyan "bezel" — thick cyan frame, a framed-not-filled
            readout inputbar, mono
- kanagawa: ink & paper — warm washi-paper frame, deep ink-well inputbar,
            crystal-blue wave selection

Each file is self-contained (it replaces the generated theme) and keeps
the element structure the theme-grid picker's per-invocation -theme-str
layers onto. rofi.nix needs no change — its existing themes/<slug>/
rofi.rasi probe whole-swaps when the file is present. All four parse
clean under `rofi -dump-theme`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 19:45:37 +01:00
97b5944dc1 fix(menu): force-own networkmanager-dmenu config.ini
networkmanager_dmenu writes a default config on first run, so an
unmanaged file can already sit at ~/.config/networkmanager-dmenu/
config.ini and abort `home-manager switch` with a clobber error. We
fully generate this file, so set force = true to overwrite it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:27:35 +01:00
0c483f9512 feat(menu): network + audio modules; case-insensitive fuzzy search
- network: replace the nmtui-in-a-terminal flow with networkmanager_dmenu
  (native rofi wifi/VPN picker), configured via xdg config.ini to drive
  rofi with rofi_highlight; editing a connection drops to nmtui.
- audio: new System -> Audio sink/source switcher via rofi-pulse-select,
  self-gated on the PipeWire/pulse socket.
- search: case-insensitive fuzzy matching with fzf sorting across every
  menu module and the launcher (so "system" finds "System").

Secrets module (rbw/pass) deferred — no Bitwarden/pass setup here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
dfb57c2e34 feat(home): create XDG user directories by default
Enable xdg.userDirs with createDirectories so a fresh install lands with
Downloads/Documents/Pictures/Music/Videos/Desktop/Public/Templates (plus
home-manager's Projects) present and written to user-dirs.dirs, rather
than appearing only on first app use. mkDefault so a downstream home.nix
can flip it off or remap individual paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
995810927d fix(install): pin HOME=/root so root nix never strays into /home/nomarchy
`exec sudo --preserve-env` (needed to carry the NOMARCHY_* vars) also
drags the live session user's HOME=/home/nomarchy into the root install
process. Root-run `nix build` then writes an eval cache + .nix-defexpr
there, and the in-chroot one lands on the TARGET as a stray, orphaned
/home/nomarchy (no such user on the installed system). Pin root's own
HOME after the sudo re-exec, and again inside the nixos-enter chroot
script. The user activation already sets HOME=/home/$USERNAME and is
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:23:21 +01:00
e1cf190dd2 feat(menu): visual theme picker — rofi preview grid
Replace the plain-text `nomarchy-menu theme` list with a grid of real
desktop previews. The grid, the Name→slug map and the active `✓` mark
are generated at eval time from the preset JSONs (`readDir` + `fromJSON`
in rofi.nix); "active" is just `t.slug`, since every switch rebuilds the
menu. Grouped dark-first then light in one scrollable grid (the previews
make the mode obvious — no light/dark submenu split), `flow: horizontal`
so Down scrolls row-by-row.

Previews are committed as themes/<slug>/preview.png at 480×270 (~2.4 MB
for all 21). rofi's element-icon `size` is single-value → a *square*
cell that *contains* the icon, so a 16:9 preview would letterbox; a
build-time imagemagick step centre-crops each to a square so it fills
the cell edge-to-edge (source stays 16:9, crop is reversible). The
window width is derived from `themeGridIconW` so a column is exactly the
icon side — no slack margins. Themes without a preview degrade to a
plain-name row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:26:39 +01:00
Bernardo Magri
70334e68bb docs(roadmap): record the faster-switches diagnosis + decided approach
Flesh out the "Faster switches" item with the confirmed diagnosis (themes/
is 86 MB and backgrounds/ is all of it; the config is ~208 KB; wallpapers
are never read at eval; theme-state.json is git-tracked so every apply
re-copies the whole 86 MB source) and the decided-but-deferred approach:
Option 1, a separate pinned wallpapers artifact (repo/tarball via flake
input/fetchurl, read through the existing NOMARCHY_DEFAULT_THEMES hook),
which keeps eval pure. Notes the rejected impure-state alternative and the
pre-built-theme-variants follow-on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:13:08 +01:00
Bernardo Magri
6a4af69b0f feat(updates): passive update-awareness indicator + notification
Add opt-in nomarchy.updates (modules/home/updates.nix): a nomarchy-updates
checker on a systemd user timer (interval, default daily) that surfaces a
Waybar indicator + a notification when updates are available, without ever
changing anything (you still run sys-update / flatpak update).

It counts:
- flake inputs behind upstream — only the flake's DIRECT inputs (root.inputs:
  nixpkgs, the Nomarchy input, home-manager, stylix…), not the transitive
  closure, via `git ls-remote` vs the locked rev. Offline → skipped, never a
  false alarm.
- Flatpak updates, when the flatpak CLI is present (.flatpak, gated on
  services.flatpak being on) — Bernardo's suggestion.

Waybar custom/updates self-gates (hidden until the timer finds something;
accent "󰚰 N", signal 9 for instant refresh), in the generated bar and both
summer whole-swaps. The notification fires only when the count grows, so a
daily timer doesn't nag. Click → the upgrade flow in a terminal (sys-update /
flatpak update, each confirmed). nomarchy-updates ships always on PATH and
self-gates so the static whole-swap bars can exec it even when the feature
is off.

Verified: flake check clean; home generation builds; the jq direct-input
filter + status self-gate exercised against the real flake.lock; script
passes bash -n. Pending an on-machine check (ls-remote/notify/timer need a
live session).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:59:09 +01:00
Bernardo Magri
d9466d6555 docs(roadmap): plan the visual theme picker (preview thumbnails)
Add the agreed approach for prettifying nomarchy-menu theme into a rofi icon
grid of real themed-desktop previews: Bernardo captures previews on real
hardware and commits themes/<slug>/preview.png; a build-time derivation
downscales each to a thumbnail that the picker loads as the rofi element icon
(rofi already renders image files — same as the Papirus menu icons), with a
wallpaper/swatch/plain-name fallback while previews are added incrementally.

Notes the headless VM-render route was prototyped and works (runNixOSTest +
software-GL Hyprland + machine.screenshot QMP dump) but real-hardware capture
was chosen for fidelity; the working VM recipe is parked in memory. The
temporary flake checks.theme-shot spike is removed (too heavy for a CI gate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:36:01 +01:00
Bernardo Magri
0e42763aea feat(menu): Back on every submenu + working snapshot fallback
Two menu improvements.

Back everywhere: every list menu now ends with a "↩ Back" entry that
returns one level up — power, theme, power-profile, clipboard, files,
capture, not just Tools/System — so you no longer have to Esc out and
reopen to back up. A `back` helper + a shared `BACK` label keep it uniform;
matched exactly so it can't collide with clipboard/filename content. Esc
still quits instantly.

Snapshot fallback: btrfs-assistant 2.2 segfaults on 26.05 (libbtrfsutil
ABI), so `nomarchy-menu snapshot` now launches nomarchy-snapshots instead —
a keyboard-driven snapper browser/restore. snapper is root-only here and
rofi can't run as root under Wayland, so it runs in a terminal via sudo
(one password prompt) and uses fzf to pick: browse/diff (read-only),
restore files (undochange), or roll back (root config only), each behind a
typed-`yes` confirmation — safer than a one-click rofi rollback.
btrfs-assistant stays installed for when nixpkgs fixes it.

Verified: flake check clean; home generation builds and the menu passes
bash -n with all Back + snapshot wiring; nomarchy-snapshots passes
shellcheck (writeShellApplication) in a snapper-enabled system build.
Pending an on-machine check of the restore/rollback paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:54:42 +01:00
Bernardo Magri
a6d6860054 docs(roadmap): note night-light toggle is session-scoped; persistent-off follow-up
The menu/Waybar toggle stops hyprsunset for the session only — it's
WantedBy=graphical-session.target, so the next login auto-starts it and the
off-state doesn't persist (deliberate "true colours for now" behaviour).
Record the persistent-off option (a state marker honoured via the unit's
ConditionPathExists) as an optional follow-up so it isn't forgotten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:29:54 +01:00
Bernardo Magri
5c43a93285 feat(nightlight): menu + Waybar on/off toggle
Add a runtime toggle for the scheduled blue-light filter, the remaining
optional item on the night-light roadmap entry.

nomarchy-nightlight (nightlight.nix, always on PATH) starts/stops the
hyprsunset *service* rather than poking its runtime gamma: `systemctl
--user is-active` is the single source of truth, the screen restores to
true colours when stopped, and it lets you force the filter off for
colour-sensitive work (even at night) and back on without touching config.
It self-gates — `status` prints nothing when the unit is absent — so the
indicator hides itself when night-light isn't enabled.

- Menu: `nomarchy-menu nightlight` toggles it; a self-gated row in the
  System submenu (shown only when the hyprsunset unit exists).
- Waybar: a self-gating `custom/nightlight` indicator (moon = on, sun =
  off), in the generated bar and both summer whole-swap themes for parity.

Verified: home generation builds, generated waybar config carries
custom/nightlight, the menu has the dispatcher case + System row, both
scripts pass bash -n, and status self-gates to empty with no unit. Pending
an on-machine check that stopping hyprsunset cleanly restores the gamma.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:23:12 +01:00
Bernardo Magri
09c308b93c chore(boot): default boot.zfs.forceImportRoot = false (silence eval warning)
Nomarchy roots are BTRFS, never ZFS, but the zfs module's `true` default for
forceImportRoot emits a warning on every eval/build ("recommended to set it
to false, the new default from 26.11"). Adopt that default early via
mkDefault — silences the noise, future-proofs for 26.11, and a real ZFS-root
downstream can still force it back on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 22:10:28 +01:00
Bernardo Magri
bdf20f2d8e feat(hardware): xe-aware GuC, class-based NPU detect, latestKernel + VM test
Harden the hardware-enablement broad seed for newer hardware:

- xe vs i915: intel.guc emits the i915 param (i915.enable_guc=3), which the
  newer `xe` driver (Lunar Lake / Battlemage / Panther Lake, recent Xe GPUs)
  ignores — it enables GuC by default. hardware-db.sh now reads the in-use
  GPU driver (lspci -k) and writes `intel.guc = false` on xe hardware, so the
  toggle isn't a misleading no-op there.

- NPU detection by PCI accelerator class [1200] (+ keywords), attributing the
  vendor, instead of a fixed device-ID list — so new NPUs (e.g. Panther Lake)
  are caught without a code change. Known IDs kept as a fallback/reference.

- Kernel awareness: a nomarchy.hardware.latestKernel escape hatch
  (linuxPackages_latest) for very-new hardware whose drivers only just landed,
  and a build-time warning when npu.enable predates the shipped kernel
  (amdxdna needs 6.14+, intel_vpu wants recent). Downstream default is 6.18,
  which already carries amd-pstate + amdxdna; the reference host pins latest.

- VM test (checks.hardware-toggles, pkgs.testers.runNixOSTest): boots a
  minimal VM with the toggles on and asserts the config landed — amd_pstate
  + i915.enable_guc in /proc/cmdline, fprintd.service present, pam_fprintd in
  /etc/pam.d/sudo. Passing. (Hardware behaviour still needs bare metal.)

Docs: README + template note latestKernel; ROADMAP records the hardening.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 20:13:31 +01:00
Bernardo Magri
c57d26864e feat(hardware): nomarchy.hardware.* enablement beyond nixos-hardware
The nixos-hardware "common-*" profiles the installer selects cover the
basics (microcode, the Intel/AMD VA-API media stack, weekly fstrim), and
power.nix adds thermald + power-profiles-daemon. This adds the gap above
them, generically and detected at install time.

New modules/nixos/hardware.nix exposes a vendor-keyed nomarchy.hardware.*
surface — broadly-beneficial bits default ON when the vendor is detected
(opt-out), heavy/experimental bits behind opt-in toggles:
  - intel.enable -> GuC/HuC (i915.enable_guc=3); intel.computeRuntime
    (opt-in: intel-compute-runtime + vpl-gpu-rt)
  - amd.enable -> amd_pstate=active + radeonsi VA-API env; amd.rocm.enable
    + amd.rocm.gfxOverride (opt-in: ROCm HIP/OpenCL)
  - fingerprint.enable -> fprintd; fingerprint.pam (opt-in: login + sudo)
  - npu.enable (opt-in/experimental) -> the in-kernel driver
    (amdxdna/intel_vpu) keyed by vendor; userspace runtime is BYO

hardware-db.sh now detects Intel/AMD, a fingerprint reader (libfprint USB
vendor IDs) and an NPU (Intel VPU / AMD XDNA PCI IDs), emitting NOMARCHY
lines the installer bakes into system.nix — safe defaults active, opt-ins
commented. Audited against the commons to avoid double-setting.

Verified: flake check green; a toggles-on build has amd_pstate=active +
i915.enable_guc=3 in kernel-params and ships fprintd.service; detection
runs correctly on the (AMD Ryzen-AI) dev machine. On-hardware verification
of the AMD/NPU/Intel-compute runtime bits is still pending.

Docs: template commented examples, README option table, ROADMAP status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:49:39 +01:00
Bernardo Magri
019fdfc8bb feat(waybar): drop redundant network module, enlarge power-profile glyph
The network/wifi indicator duplicated nm-applet, which already sits in the
tray (services.network-manager-applet.enable), so remove it from the bar.
And the power-profile speedometer glyph rendered noticeably smaller than
the neighbouring indicators, so bump its font-size (+3pt generated; 20px
summer-day, 17px summer-night).

Applied to the generated bar (waybar.nix) and both whole-swap themes
(summer-day/night waybar.{jsonc,css}) for parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:13:20 +01:00
Bernardo Magri
c2281dbc61 fix(waybar): launch from exec-once, not a systemd user service
Waybar vanished after a logout→login (no reboot): the bar's systemd user
service landed in `failed`. HM's Hyprland integration cycles
hyprland-session.target on every launch (exec-once stop && start), which
re-pulls WantedBy units — including waybar.service — very early, before
Hyprland's IPC socket is ready. Waybar's hyprland/workspaces & window
modules can't connect, so it exits non-zero and is never retried. A cold
boot's extra latency hid the race, so it only bit on warm relogins. The
non-IPC session services (swaync/hypridle/hyprsunset) survive the same
cycling fine, which is why night-light kept working.

Launch waybar from Hyprland's own exec-once instead (systemd.enable =
false). exec-once entries are dispatched once the compositor is up, so the
IPC socket is ready — the same reliable pattern awww-daemon, the wallpaper
sync and the keyboard watcher already use across relogins. Since the unit
no longer exists to be restarted on a home-manager switch, nudge the
running bar to re-read its rebuilt config/style with SIGUSR2 from
nomarchy-theme-sync so theme switches still reapply the bar live.

Found on the Latitude 5410 hardware sweep (systemctl --user status showed
waybar.service loaded/failed; manual start worked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:00:39 +01:00
Bernardo Magri
b4fe52261b fix(keyboard): isolate new-keyboard layout to its own device
The interactive new-keyboard picker applied the chosen layout with
`hyprctl switchxkblayout <device> <index>`, which flips the *shared*
layout for every keyboard without its own per-device config — so picking
a layout for an external board dragged the built-in keyboard along, and
the change stuck after unplug (it's global state, not device-bound).
Compounding it, the candidate `keyboard.layouts` pool was merged into
`input.kb_layout`, loading those layouts onto the session keyboard too.

Apply the choice as a per-device `hyprctl keyword device[<name>]:kb_layout`
instead — the runtime twin of the declarative `keyboard.devices` blocks —
so it isolates that one keyboard and never touches the built-in board.
Keep the candidate pool out of `input.kb_layout` (session keyboard carries
only `keyboard.layout`); the pool is for external boards, applied per-device.

Also put `nomarchy-keyboard-watch` on PATH when enabled (it was store-path
only via exec-once, so undiscoverable) for debugging.

Found on the Latitude 5410 hardware sweep; picker itself verified working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:47:19 +01:00
Bernardo Magri
82659566ac chore: untrack config.rasi (scratch reference committed by mistake)
config.rasi was the rofi theme dropped in to show the desired style; it's
not part of the distro. Remove it from tracking (the file stays on disk,
untracked). Its content informed 994025f and remains in that history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:12:02 +01:00
Bernardo Magri
994025f0c8 feat(rofi): real icons, zebra rows, content-fit menus across all themes
Reworked the menu look, palette-driven so it lands on every theme:

  - Real Papirus icons in place of Nerd Font glyphs: a row() helper emits
    rofi's per-row icon protocol, and the five static menus (root, Tools,
    System, Power, Capture) map each entry to a freedesktop icon name
    (verified present in Papirus). Entry text is now clean ("Apps"), the
    case-matching is unchanged.
  - Larger icons via a new t.ui.iconSize knob (36px, generated theme); the
    summer whole-swaps keep their 40px identity.
  - Rounded, roomy rows with subtle zebra striping: alternate rows lift to
    @surface (a faint bg0 wash on the summer cream/slate windows), the
    selected row to @accent — using rofi's normal/alternate/selected row
    parities.
  - Content-fit windows: fixed-num-lines=false so a 6-entry menu stops
    reserving 8 rows of empty space (the launcher still fills + scrolls).
    Dropped the fixed height:50% from the summer .rasi so they size to
    content too — which also unhid their clipped Power/Keybindings rows.
  - Title-cased the menu prompts.

Verified by building the HM config and rendering the generated theme plus
both summer whole-swaps headlessly (sway + grim): icons resolve, zebra
shows, every menu hugs its rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:11:25 +01:00
Bernardo Magri
b0b2a4d732 docs(services): note the restic-nomarchy list/restore wrapper
VM-verified the full batch of opt-in service toggles (libvirt, docker, obs
+ v4l2loopback, ollama, printing, openrgb, wireshark, adb, gamemode,
kdeconnect, restic) in a headless nixosTest — all green, no module bugs.

The one thing worth surfacing: services.restic ships a pre-configured
`restic-nomarchy` wrapper (repository + passwordFile baked in) for listing
and restoring snapshots. Mention it in the option description and README so
the restore path is discoverable — a backup you can't restore is half a
feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:18:49 +01:00
Bernardo Magri
964a5c98b7 feat(services): openrgb + restic toggles; close out the opt-in services item
Finishing the four remaining "opt-in services" candidates — but two of
them aren't service-shaped, so the package-vs-toggle principle applies:

  - Nextcloud client  → a bare tray app; already in the app-suite menu,
                        not a service.
  - WireGuard         → NetworkManager (on distro-wide) imports .conf
                        tunnels natively, so there's no daemon to "just
                        enable". Only the wg/wg-quick CLI is missing →
                        wireguard-tools added to the app-suite menu.

The two that ARE config-backed become nomarchy.services.* toggles:

  - openrgb → services.hardware.openrgb (daemon + device udev rules).
  - restic  → a small scaffolded surface (repository / passwordFile /
              paths) over services.restic.backups: daily timer, 7/4/6
              retention, --exclude-caches, repo auto-init. Asserts that
              repository + passwordFile are set when enabled. passwordFile
              is typed `str`, not `path`, so a flake-relative secret can't
              be copied into the world-readable store.

README option table and ROADMAP (services now "Seventeen shipped";
cloud-sync / networking / backup / devices areas updated) + the downstream
system.nix examples. Both branches eval-verified; the restic assertions
confirmed firing when repo/password are unset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:02:20 +01:00
Bernardo Magri
c45fb80b79 feat(services): full optional app menu + 7 more opt-in service toggles
Make the whole workstation menu available, all off/commented by default.

home.nix: the bare GUI apps become a categorized commented menu (~50
suggestions across browsers, comms, office, notes, code, graphics, A/V,
media, gaming, files/sync, security, system, local AI) — uncomment to add.

services.nix: the members that need system config (not just a package)
become opt-in nomarchy.services.* toggles, each with a commented example
in the downstream system.nix:
  - docker     → rootful Docker + user in docker group; asserts against the
                 podman docker-compat (both provide the `docker` command)
  - kdeconnect → programs.kdeconnect (opens phone-pairing ports)
  - gamemode   → Feral GameMode daemon
  - adb        → android-tools (programs.adb was removed; systemd ≥258 does
                 the device uaccess udev rules, so no group needed)
  - wireshark  → Qt GUI + user in wireshark group (capture without root)
  - ollama     → local LLM runtime on 127.0.0.1:11434
  - printing   → CUPS + Avahi/mDNS network printer discovery

README option table and ROADMAP (services item now "Fifteen shipped";
local-AI / containers / gaming / devices areas marked) updated. All seven
branches eval-verified with the toggles forced on; the docker/podman
assertion confirmed firing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:52:38 +01:00
Bernardo Magri
0c668c2eb1 feat(apps): starter workstation app suite + steam/libvirt/obs services
The "default application suite" roadmap item, but deliberately NOT a
nomarchy.apps.* option surface: a bare package install has no config to
put behind a toggle, so a thin `mkIf x { systemPackages = [p]; }` only
reinvents what a package list already gives. Instead the suite is a
curated home.packages list in templates/downstream/home.nix (extending
the existing `# firefox` convention) — active staples (libreoffice,
vscode, gimp, inkscape, mpv, amberol) plus commented suggestions. The
opt-out is deleting a line; it also keeps the apps out of the live ISO
automatically (the ISO never scaffolds from the template).

The members that DO need system config become nomarchy.services.* opt-in
toggles instead (config behind the switch, like the existing services):
  - steam   → programs.steam (32-bit stack, controller udev, RP ports)
  - libvirt → libvirtd + virt-manager, login user added to libvirtd group
  - obs     → obs-studio + a v4l2loopback virtual camera (exclusive_caps=1)
              selectable as a webcam in Zoom/Teams

README option table, ROADMAP (services item now "Eight shipped"; suite
item marked done with the no-surface rationale) and the downstream
system.nix examples updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 20:45:10 +01:00
Bernardo Magri
1f4bd3d5fa docs(readme): document the curated shell aliases in §5 Day-to-day
List the on-by-default zsh aliases (navigation, git, nix, misc) in the
Day-to-day section so they're discoverable from the README, not just by
running `alias`. Note the doc location in the roadmap item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:48:22 +01:00
Bernardo Magri
017bb1aa41 feat(shell): curated git/nix/navigation aliases on by default
Extend the zsh shellAliases in shell.nix with a curated, on-by-default
set: navigation (.., ..., ....), a git block (g, gst, ga/gaa, gc/gcm,
gco/gsw, gb, gd/gds, gl/glg, gp/gpl, gf), a nix block (ns, nr, nfu, nfc,
nsearch, ngc), and path/reload. Same restraint as rg/fd: short where it
helps but never shadowing a real binary — the git set keeps off gs
(ghostscript), and the system/home rebuilds keep their sys-update /
home-update names rather than cryptic aliases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:46:17 +01:00
Bernardo Magri
b856bb93e8 docs(roadmap): close the sanitize file-audit; menu grouping is ongoing
The repo-organization re-check found nothing to move: no orphaned modules
(keybinds.nix is a data import, not a HM module), no dead-code markers or
editor/backup cruft, every themes/<slug>.json pairs with its optional
overrides dir, and hosts/{default,live} are the reference + ISO hosts.
Recorded the one open decision: no formatter is declared and the .nix
hand-formatting is deliberate, so adopting nixfmt is a maintainer call,
left untouched. Also marked the menu category grouping a standing
convention — new menu-facing features get placed in the right submenu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:40:50 +01:00
Bernardo Magri
e0ee5b9e7e feat(menu): group the rofi picker into Tools / System submenus
The root picker had grown to 16 flat entries. Collapse it to six —
Apps · Theme · Tools › · System › · Power · Keybindings — with Tools
(Calculator, Clipboard, Emoji, Files, Web, Capture, Ask) and System
(Network, Bluetooth, DND, plus the self-gated Snapshots / Power-profile)
as submenus the dispatcher routes via `nomarchy-menu tools|system`. Each
submenu ends in a ← Back entry that re-opens the root. The direct
SUPER+CTRL+<mnemonic> binds still reach the leaves straight (bypassing
the menu), and the gated entries still hide when unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:35:03 +01:00
Bernardo Magri
f68b245d92 feat(services): add podman, flatpak (+ Flathub) and pika backup
Extend nomarchy.services.* with three more opt-in toggles:
- podman: rootless containers, `docker` aliased to it, container DNS, and a
  subuid/subgid range for the login user.
- flatpak: services.flatpak + a oneshot that adds the Flathub remote
  system-wide (idempotent, retries when online).
- pika: ships pika-backup, the GUI for scheduled Borg backups.
Commented examples in the downstream system.nix template, per convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:15:45 +01:00
Bernardo Magri
fb748c217c feat(services): opt-in Tailscale + Syncthing (nomarchy.services.*)
Start the opt-in services surface: nomarchy.services.{tailscale,syncthing},
off by default, in modules/nixos/services.nix. Tailscale ships the daemon
(authenticate with `sudo tailscale up`); Syncthing runs as the login user
with its GUI on 127.0.0.1:8384 (overrideDevices/Folders off so GUI-managed
folders survive rebuilds). username is read lazily, only when Syncthing is
on. Commented examples in the downstream system.nix template per the opt-in
convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:08:41 +01:00
Bernardo Magri
8d3911de1a docs(template): ship opt-in features as commented examples downstream
Convention: an off-by-default feature that needs nomarchy.* settings in the
downstream files should appear commented-out in templates/downstream/{home,
system}.nix, so a new install enables it by uncommenting + tweaking rather
than hunting the docs for the option name. Apply it now: night light,
nomarchy.monitors, per-device keyboard layouts (home.nix) and power
management (system.nix). Also fix the stale keyboard.layout note
(console/LUKS follow xkb automatically now). Documented in README S6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:53:13 +01:00
Bernardo Magri
6ad80c045e feat(nightlight): scheduled blue-light filter via hyprsunset
Add nomarchy.nightlight (opt-in): a scheduled colour-temperature shift via
the HM services.hyprsunset module -- warm (.temperature, default 4000K) at
night, identity (no shift) by day, switching at .sunset / .sunrise. Two
time-based hyprsunset profiles, so hyprsunset handles the schedule and the
on-login state. New modules/home/nightlight.nix, imported in the home module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:37:03 +01:00
Bernardo Magri
54c6e100fb docs(roadmap): organize the menu picker into category submenus
The root picker has grown into a long flat list; group it into category
submenus (System / Tools / Look & Feel / Power) with the dispatcher routing
nomarchy-menu <category> to its leaf modules, keeping the SUPER+CTRL+<mnemonic>
binds as direct fast paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:29:30 +01:00
Bernardo Magri
eb76bd61b4 docs(roadmap): note btrfs-assistant segfault; snapper backend VM-verified
A VM regression test caught that btrfs-assistant 2.2 segfaults on launch
inside libbtrfsutil.so.1.4.0 -- a library ABI mismatch in nixpkgs 26.05, not
a GL/VM artifact (crashes with software GL and every Qt platform; the fault
is in a btrfs library). The snapshot menu wiring is correct and the app is
installed, but the GUI doesn't open. The snapshot backend is unaffected and
is itself VM-verified (full BTRFS+LUKS install: root+home snapper configs, a
/home timeline snapshot, /home/.snapshots a real subvolume, the oneshot
Result=success). Keep the entry, document the bug pending a nixpkgs fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:43:46 +01:00
Bernardo Magri
71f37bfc30 feat(keyboard): interactive new-keyboard layout prompt + persist
The runtime-remember complement to nomarchy.keyboard.devices. Set
nomarchy.keyboard.layouts (candidate layouts) and a nomarchy-keyboard-watch
daemon runs (exec-once): it polls hyprctl devices, and when a keyboard
connects after login that isn't declared and hasn't been chosen before, it
pops a rofi layout picker, applies the choice via hyprctl switchxkblayout (an
index into the candidate set carried by input.kb_layout), and remembers it
per-device in ~/.local/state -- re-applied silently on later reconnects. The
boot-time set (incl. the built-in keyboard) is never prompted.

Pure bash + jq/hyprctl/rofi (all in the session PATH); state lives outside
the flake (a stateful runtime piece by design). Eval + bash-syntax verified;
the hotplug behaviour needs an on-hardware test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:43:59 +01:00
Bernardo Magri
69f959365e docs(roadmap): interactive new-keyboard layout prompt + persist
A runtime-remember complement to the declarative nomarchy.keyboard.devices:
a daemon watches Hyprland's socket, pops a rofi layout picker when an unknown
keyboard connects, applies it, and persists per-device so it re-applies on
reconnect — optionally writing the choice back into nomarchy.keyboard.devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:32:05 +01:00
Bernardo Magri
aef45b3c8d feat(keyboard): per-device layout for external keyboards
Add nomarchy.keyboard.devices ({ "<hyprctl-device-name>" = { layout; variant; }; })
generating Hyprland `device` blocks that override the session
nomarchy.keyboard.layout for a named keyboard -- e.g. an external board
that's physically a different layout than the laptop's built-in one.
Hyprland applies it whenever that device connects, so the external keyboard
"remembers" its layout the declarative way. A Waybar hyprland/language
indicator shows the active layout, placed only when more than one layout is
in play (a comma in the session layout, or any per-device override) so
single-layout bars stay clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:27:28 +01:00
Bernardo Magri
0897391cfb feat(snapshots): desktop browse/restore + snapshot /home by default
Surface snapper snapshots from the desktop: `nomarchy-menu snapshot` (in the
SUPER+M picker, self-gated) launches btrfs-assistant, the polkit-aware
browse/diff/restore/rollback UI -- safer than a fat-fingerable rofi rollback
for a destructive root-only op. Shipped system-side gated on
nomarchy.system.snapper.

Also snapshot /home by default (hourly 5 / daily 7 / weekly 4) when it's its
own BTRFS subvolume -- the installer only set up snapper for /. A boot
oneshot creates the required /home/.snapshots subvolume if missing, so
already-installed machines get it too (no disko change).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:14:05 +01:00
Bernardo Magri
5c83b52727 docs: mark the GTK/Qt color-scheme portal confirmed in a live session
Verified this session in headless QEMU: the org.freedesktop.appearance
color-scheme portal reads 1 on a dark theme and 2 after switching to a light
one, so the dconf value reaches GTK4/Qt apps. Removes the stale "needs a
live session to confirm" note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:47:59 +01:00
Bernardo Magri
0751102915 fix(waybar): show the power-profile indicator in the summer themes
custom/powerprofile only appeared in the generated bar -- its exec was a
writeShellScript store path, which can't go in the summer themes' static
whole-swap waybar.jsonc, so summer-day/night were missing it (the DND bell
already had parity). Promote powerProfileStatus/Cycle to named
writeShellScriptBins on PATH (waybar.nix home.packages) and reference them
by bare name, then add custom/powerprofile to both summer waybar.jsonc +
their waybar.css. Same pattern as the swaync bell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:46:45 +01:00
Bernardo Magri
5c59da73e7 docs(roadmap): full hardware enablement beyond nixos-hardware
nixos-hardware covers basic per-model enablement, but advanced features
(keyed on CPU/GPU generation) are left to the user. Capture a big item:
extend the installer's autodetection + a nomarchy.hardware.* surface to turn
on safe features automatically (opt-out) and gate the heavy/experimental
ones (opt-in), without per-feature fiddling. Worked example on the ThinkPad
T14s (Ryzen 7 PRO 7840U + Radeon 780M): ROCm (gfx1103 override), Ryzen AI
NPU/XDNA, AMD P-State EPP, VA-API, fprintd biometrics (fwupd already ships),
SSD fstrim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:35:07 +01:00
Bernardo Magri
42801548b7 feat(displays): declarative nomarchy.monitors + nwg-displays arranger
Add a per-output monitor schema (name / resolution / position / scale /
transform / mirror / bitdepth / vrr / disable) that generates Hyprland
`monitor` rules, keeping the ,preferred,auto,1 wildcard as the fallback.
Hyprland applies the rules on hotplug, so a declared external/dock output
arranges itself on connect (no kanshi -- it fights Hyprland's own output
management). Default [] -> wildcard only, so no change for existing setups;
set wayland.windowManager.hyprland.settings.monitor directly to override.

nwg-displays ships behind nomarchy.displays.enable (default true) as an
interactive arranger -- a helper to discover values; the declarative config
stays the source of truth (its output file isn't sourced).

Remaining (roadmap): docked/undocked profile switching, workspace-to-monitor
binding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:22:18 +01:00
Bernardo Magri
1b0eeeaf6c fix(idle): don't lock before an encrypted hibernate (was: unlock after)
The previous nomarchy-hibernate-unlock ran `pkill hyprlock` after a
hibernate resume, but killing a Wayland session-lock client without
releasing the lock trips hyprlock's "go to a tty" crash failsafe -- the
compositor keeps the screen locked for safety. So resume showed a hyprlock
error screen instead of unlocking.

Fix it the right way: never engage the lock before an encrypted hibernate
(the LUKS passphrase at resume is the gate). Replace the post-resume unlock
with a nomarchy-lock-before-sleep unit that takes over hypridle's
before_sleep_cmd: it locks on the RAM-resume sleeps (suspend / hybrid-sleep
/ suspend-then-hibernate) always, and on hibernate.target only when the disk
is unencrypted. idle.nix drops before_sleep_cmd accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:15:31 +01:00
a606c0d498 docs: log power-profile Waybar parity as a follow-up
The custom/powerprofile indicator is missing from the whole-swap summer
themes (its exec uses Nix-built script paths that can't go in static
JSON). Note the fix (promote the scripts to named bins on PATH) and the
general "new module → also add to whole-swap themes" rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:12:38 +01:00
cd495d1720 fix(dnd): add the notification bell to the whole-swap summer themes
The DND bell only landed in the generated Waybar config; summer-day and
summer-night ship waybar.jsonc whole-swaps that replace it entirely, so
they'd have shown no bell. Add custom/notification (swaync-client -swb,
left-click panel / right-click DND) to both legacy layouts with their
FontAwesome bell / bell-slash glyphs, and style it alongside the other
right-side modules in each waybar.css.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:08:01 +01:00
dbcb8efd1e feat(dnd): swaync Do-Not-Disturb toggle + Waybar bell indicator
Adds a Do-Not-Disturb story on top of swaync:

- Waybar `custom/notification` module via `swaync-client -swb` — streams
  the notification count + DND state (no polling). Left-click toggles the
  panel, right-click toggles DND; bell-off glyph + muted color when DND is
  on, accent when notifications are waiting.
- `nomarchy-menu dnd` toggle in the root picker and bound to SUPER+CTRL+D
  (so it shows in the SUPER+? cheatsheet). Turning DND off confirms with a
  toast; turning it on is silent — notifications are suppressed and the
  bell-off icon is the cue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:01:11 +01:00
07e467027b feat(nix): silence the "Git tree is dirty" warning on rebuilds
The downstream flake at ~/.nomarchy is a live working tree by design —
nomarchy-theme-sync rewrites theme-state.json on every switch — so Nix's
dirty-tree warning fired on every sys-update/home-update and was just
noise. Default nix.settings.warn-dirty to false (mkDefault, overridable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:53:18 +01:00
9a5473e179 docs: record the branch/release model + sanitize progress
Document the main=dev / v1=release-pointer model in flake.nix (where the
v1 ref is defined) and mark the repo-sanitize roadmap items shipped
(installer prune, option-table reconcile, settings untrack, branch
realignment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:48:06 +01:00
db6a0ba455 chore: untrack .claude/settings.local.json (machine-local)
It holds this machine's Claude Code permission allowlist — local by
convention (the `.local` suffix; settings.json is the shared one), so it
shouldn't be versioned. Remove from tracking (keeping the working file)
and gitignore it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:45:54 +01:00
8c47716f9a chore(install): stop writing distro-default config into system.nix
console.useXkbConfig and boot.initrd.systemd.enable are set distro-wide
(lib.mkDefault true in modules/nixos/default.nix, since the LUKS-keymap
fix), so the installer rewriting them in the generated system.nix was
redundant. Drop them there and from the offline-pin test fixture in
flake.nix — the nomarchy module still defaults them on, so the closure
(and the pin set) is unchanged; verified the values still resolve true
and `nix flake check` passes. Only the machine-specific xkb layout is
written now. Adjusts the README keyboard-option wording to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:42:36 +01:00
f60f3655f9 merge: adopt the v1 development line onto main
Brings main current with the 51 commits of post-rewrite feature work
(laptop power, idle cohesion, menu plugins, fwupd, docs restructure)
that had been accumulating on v1. New model: main is the development
default; v1 is the release pointer downstreams pin (?ref=v1), advanced
to main once a batch is tested. Rolling, no tags.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:38:36 +01:00
afdb928ed7 docs: split roadmap into docs/ROADMAP.md, reconcile README
The roadmap + known-issues had grown to ~280 lines, nearly half the
README. Moved both into docs/ROADMAP.md (forward-looking plans + a log of
shipped fixes) and left a short pointer, so the README is back to a
focused entry point (572 -> 302 lines).

Also fixed drift surfaced in the pass:
- layout tree: add modules/nixos/power.nix, modules/home/{keybinds,keys,
  fastfetch}.nix and docs/ROADMAP.md, which were missing.
- option table: add keys.enable, fastfetch.enable, system.snapper.enable
  (default false — the installer opts in) and system.greeter.autoLogin.

OVERRIDES.md, TESTING.md and the downstream template README were checked
and are accurate (.#default / .#me names and the 21-preset count match),
so they're left as-is. Docs stay Markdown-in-repo — no docs site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 18:29:16 +01:00
882ba6646b revert(menu): keep the hand-rolled fd files module, drop rofi-file-browser
The rofi-file-browser-extended navigate-a-tree model felt worse than flat
fuzzy-find for a quick launcher (yazi already covers real browsing), so
`files` goes back to `fd` → `rofi -dmenu` → xdg-open. calc (rofi-calc)
and emoji (rofi-emoji) stay. Re-adds `fd` to the module's packages and
notes the dropped plugin in the roadmap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 18:16:06 +01:00
db202f90e4 feat(menu): adopt rofi plugins for calc, files, emoji
Replace the hand-rolled calc/files modules with purpose-built rofi modi
(via programs.rofi.plugins) and add an emoji picker — all themed through
the same .rasi as every other modi:

- calc -> rofi-calc: live results as you type via libqalculate directly,
  which also dodges the qalc CLI's "15% of 200" -> rem(15, 1 B) misparse.
  Enter copies; the menu persists to chain calculations. Drops the
  libqalculate (qalc CLI) dependency.
- files -> rofi-file-browser-extended: live directory navigation +
  xdg-open, replacing the flat 50k-cap `fd` dump of $HOME. yazi (SUPER+E)
  stays the power browser; `fd` already ships via shell.nix.
- emoji -> rofi-emoji (new module): glyph picker, copies through the
  plugin's Wayland clipboard adapter. Bound to SUPER+CTRL+E in
  keybinds.nix (so it lands in the SUPER+? cheatsheet too).

rofi 2.0 rebuilds cleanly with all three plugin .so (ABI-matched, same
nixpkgs). README roadmap updated: calc rework marked shipped, with a
follow-up item for the script-based plugins (network/secrets/audio).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 18:09:11 +01:00
51c11e1df3 feat(idle): suspend on battery only, cohering with power management
hypridle's suspend listener now runs `nomarchy-on-ac || systemctl
suspend`, so a plugged-in machine no longer suspends itself mid-idle
(long builds, media, presentations survive). It also fires sooner —
15 min, vs the old fixed 30 — since it now only applies unplugged.
Lock and screen-off are unchanged on both power sources, and logind's
lid handling stays at its sensible defaults (suspend on lid close,
ignore when docked).

Closes the idle-cohesion follow-up under the laptop-power roadmap item.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:53:49 +01:00
6a22eba0c4 docs: add roadmap item for a full docs review & restructure
A dedicated prose pass distinct from the repo-sanitize item: split the
~200-line roadmap out of the README, reconcile the option tables with the
live nomarchy.* surface, and review docs/ + the downstream template for
drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:46:30 +01:00
c47eb5f688 feat(system): enable fwupd by default; document power options
Ship `services.fwupd.enable` distro-wide (mkDefault) for firmware updates
via LVFS — the daemon + metadata-refresh timer only, no automatic
flashing, so applying an update stays an explicit `fwupdmgr update`. A
no-op where LVFS sees no devices; overridable natively. Sits with the
other mkDefault desktop services rather than a nomarchy toggle (it's a
single service, like upower).

Also documents the `nomarchy.system.power.*` options in the README table
(missed when the power module landed) and adds a short note on the
fwupd default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:44:23 +01:00
00f5311499 feat(power): laptop power management (power-profiles-daemon + TLP)
Adds a `nomarchy.system.power.*` surface (modules/nixos/power.nix),
replacing the bare `services.upower` (Waybar reporting only) baseline:

- power-profiles-daemon ships by default; switched through polkit (no
  root prompt). TLP is the opt-in `backend = "tlp"` for deeper battery
  tuning — the two are mutually exclusive (asserted).
- thermald behind `power.thermal.enable` (Intel-only).
- `power.batteryChargeLimit` (e.g. 80): a backend-independent sysfs
  oneshot writes charge_control_end_threshold; gated on `power.laptop`.

Switcher + indicator live home-side and self-gate on a battery being
present + powerprofilesctl running, so they hide on desktops and under
TLP — no system->home wiring (like the Waybar battery widget):

- rofi.nix: `nomarchy-menu power-profile` module + a picker entry shown
  only on laptops with PPD.
- waybar.nix: first `custom/*` module — click cycles the profile.

Installer writes `power.laptop = true` (battery probe) and
`power.thermal.enable` (GenuineIntel) into the generated system.nix, and
scaffolds the charge limit commented-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:35:42 +01:00
Bernardo Magri
8853f6ae49 feat(idle): no double-unlock after an encrypted hibernate resume
hypridle locks hyprlock before every sleep -- right for suspend, but
redundant for hibernate: the LUKS passphrase entered at boot already gates
the resume, so the user typed a password twice. Add a nomarchy-hibernate-
unlock systemd unit (WantedBy hibernate.target, After systemd-hibernate.service
=> runs post-resume) that dismisses hyprlock, gated on the disk being LUKS-
encrypted. Suspend (and the RAM-resume phase of suspend-then-hibernate) keep
locking -- they have no passphrase gate; an unencrypted hibernate keeps its
lock too. idle.nix gains a pointer comment; roadmap item marked done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 13:06:10 +01:00
Bernardo Magri
e0182ae7dd docs: explain the sys-update -> home-update ordering
home-update rebuilds the desktop against the current flake.lock and never
touches it; only sys-update runs `nix flake update`. So pulling a distro
change that affects the home layer requires sys-update first (updates the
lock), then home-update -- the reverse order rebuilds against stale inputs
and silently skips the new home-side changes (hit while testing the mic
fix). Also note hyprctl reload after a home-side keybind change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:51:01 +01:00
Bernardo Magri
58096c09ed fix(hyprland): show the mic level in the mute OSD
The wpctl-based mic toggle lost the volume bar the native swayosd input OSD
used to draw -- it only showed an icon + text. swayosd 0.3.x exposes
--custom-progress (0.0-1.0), so read the level back from wpctl after the
toggle and render icon + bar (muted/active glyph via --custom-icon),
matching the output-volume OSD. Parsing is pure bash builtins so the script
needs no PATH beyond the two pinned tools.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:51:01 +01:00
Bernardo Magri
7da6bb979e docs: expand roadmap (power, opt-in services, display/UX, control center)
Captures a batch of new roadmap items from a planning pass:
- repo sanitize/organize (incl. the now-redundant console.useXkbConfig /
  boot.initrd.systemd.enable the installer writes, post LUKS-keymap fix)
- laptop power/battery management (PPD-vs-TLP, thermald, charge thresholds)
- opt-in services & integrations (Nextcloud/Syncthing, LM Studio/Ollama,
  containers, Tailscale, Steam/OBS, printing, backups, Flatpak)
- display/monitor management (hotplug profiles, dock story)
- night light (hyprsunset), runtime keyboard-layout switching, swaync DND
- snapshot browse/restore UX, update-awareness indicator
- a "nomarchy" control center + first-boot welcome

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:27:32 +01:00
Bernardo Magri
3f36183021 feat(menu): Google web search; note calc rework in the roadmap
The web module now opens google.com/search instead of DuckDuckGo. Also log
a roadmap item to rework calc: qalc -t misparses common phrasings (15% of
200 -> rem(15, 1 B)) and the result only shows in the next menu's -mesg
line -- move toward live results as you type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:11:20 +01:00
Bernardo Magri
d9301ad954 fix(yazi): previewer match field name -> url (yazi 26.x)
yazi 26.x renamed the previewer/fetcher match key from name to url. The
fetchers were updated but the markdown previewer still used name, so the
whole [plugin] config failed to parse ("at least one of 'url' or 'mime'
must be specified") and yazi fell back to its presets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:11:20 +01:00
Bernardo Magri
66f58fa228 fix(hyprland): mic mute via wpctl -- swayosd 0.2.1 input-mute is a no-op
swayosd-client --input-volume mute-toggle draws the OSD but never flips the
default source's mute (its output-mute path works; input does not). Confirmed
on hardware: wpctl set-mute @DEFAULT_SOURCE@ toggle flips the state, swayosd
does not. Do the real toggle with wpctl and keep an OSD via swayosd's
--custom-icon/--custom-message.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:11:20 +01:00
Bernardo Magri
2c8e0b47f9 fix(keyboard): carry the configured layout to the LUKS prompt
Setting only services.xserver.xkb.layout moves the graphical session but
leaves console.useXkbConfig unset, so the virtual-console keymap -- and the
systemd initrd the LUKS passphrase prompt runs in -- fall back to US (seen
on a Latitude 5410: X11 Layout gb, VC Keymap unset, LUKS prompt US).

Default console.useXkbConfig + console.earlySetup + boot.initrd.systemd.enable
distro-wide, so xkb.layout alone reaches the console and the encrypted-disk
prompt. Moved the latter two out of plymouth.nix -- they no longer hinge on
the splash being enabled. All mkDefault: an explicit console.keyMap or a
scripted initrd still wins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:11:20 +01:00
Bernardo Magri
69f7ae9051 feat(branding): fastfetch logo + Nomarchy.ttf glyph in the Waybar button
Two branding follow-ups, using the vendored legacy assets:

- fastfetch (modules/home/fastfetch.nix, nomarchy.fastfetch.enable): the
  vector logo recolored to the palette accent and rendered to compact
  block-art via chafa at build time (so it tracks the theme), fronting a
  curated module list. Replaces the oversized legacy ASCII.
- Waybar logo glyph: vendor Nomarchy.ttf (modules/nixos/branding/),
  install it via fonts.packages, and switch the summer-day/night menu
  buttons to its U+F000 mark with `font-family: Nomarchy` pinned in CSS
  (Nerd Fonts also occupy U+F000, so the pin is required). Glyph stored as
  the  JSON escape for robustness.

Also log a roadmap item for a quality-of-life alias collection.

Verified: font family resolves as "Nomarchy" and the ttf maps U+F000;
both summer jsonc parse; chafa logo builds headless; nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:07:28 +01:00
Bernardo Magri
4017b6cff6 fix(stylix): drive GTK4/Qt dark-light from the palette via color-scheme
Stylix set polarity and the GTK theme but not the XDG portal's
org.freedesktop.appearance color-scheme, which GTK4/libadwaita and Qt6
apps actually read to choose dark vs light — so a light theme could still
render those apps dark (and vice-versa).

Set dconf org/gnome/desktop/interface color-scheme to
prefer-light/prefer-dark from theme.mode in stylix.nix.
xdg-desktop-portal-gtk already ships and programs.dconf is on system-side,
so the portal exposes it to apps.

Also log two branding follow-ups from the legacy assets: fastfetch ASCII
logo (logo.txt) and the Nomarchy.ttf glyph for the Waybar menu button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:53:08 +01:00
Bernardo Magri
cde279af0b feat(branding): ISO boot splash from the vendored Nomarchy logo
Vendor the vector logo from the legacy branch
(modules/nixos/branding/logo.svg) and use it for the live ISO's isolinux
splash: rsvg-convert renders it, recolored to the palette accent and
centred on the theme base (from theme-state.json) at build time, wired via
isoImage.splashImage in hosts/live.nix. Verified the generated PNG by
building it in the sandbox.

UEFI/GRUB still uses the stock theme (grubTheme needs a full theme dir —
left on the roadmap), as does the distroId switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:46:40 +01:00
Bernardo Magri
6513908067 feat(branding): tuigreet greeting + branded MOTD
Round 2 of distro branding, text half: tuigreet now shows
'Welcome to <distroName>', and users.motd carries a branded banner that
doubles as a cheat sheet for the distro helpers (sys-update, home-update,
nomarchy-theme-sync, SUPER+?). Both key off system.nixos.distroName so the
brand name stays in one place.

Deferred (need an asset / a boot test): ISO splashImage/grubTheme art and
the distroId switch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:35:47 +01:00
Bernardo Magri
fdb0685eec docs: mark plymouth contrast done; log GTK/Qt light-dark issue
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:18:37 +01:00
Bernardo Magri
e411d35544 fix(plymouth): recolor splash art from the palette for contrast
The shipped boot art was a fixed navy that vanished against dark theme
bases. Recolor each element from theme-state.json at build time (flat
fill, alpha preserved): foreground glyphs (logo, padlock, password dots)
→ text, the field/track boxes (entry, progress_box) → surface, and the
progress fill → accent. text/surface both contrast with base in either
polarity, so the splash now reads on light and dark themes alike.

Adds imagemagick as a build-time input to the theme derivation. Like the
background tint, this follows the theme as of the last system rebuild.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:17:42 +01:00
Bernardo Magri
3757e7d66f feat(keys): SSH + GPG agent via gpg-agent, pinentry-qt
Add modules/home/keys.nix (nomarchy.keys.enable, default on): one agent —
services.gpg-agent with enableSshSupport fronts SSH, so a single pinentry
handles every passphrase (GPG signing/decryption and SSH key unlocks).
pinentry-qt for a reliable native-Wayland prompt under Hyprland, themed by
Stylix's Qt config. Cache TTLs 30 min / 2 h.

SSH_AUTH_SOCK is exported by the agent's zsh integration (terminal git/ssh
is the supported path). gnome-keyring stays the Secret Service — modern
versions run no SSH agent, so there's no socket contention; screen lock
doesn't flush the agent cache.

nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:50:45 +01:00
Bernardo Magri
05d995f8e0 feat(keybinds): direct SUPER+CTRL binds for the menu functions
Each nomarchy-menu module is now reachable directly, not just through the
SUPER+M picker: SUPER+CTRL+ V clipboard · C calc · W web · F files ·
N network · B bluetooth · S capture · A ask. Added to keybinds.nix (the
single source), so they render in the SUPER+? cheatsheet too. No key
collisions; nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:39:12 +01:00
Bernardo Magri
f6c4a2a591 fix(waybar): stop summer themes rendering phantom workspaces
summer-day/summer-night showed all 10 workspace icons up-front. The cause:
legacy used the deprecated `persistent_workspaces` (underscore), which
current Waybar silently ignores — so legacy only ever showed *existing*
workspaces. The v1 port rewrote it to the modern `persistent-workspaces`
(hyphen), which Waybar honours, surfacing all ten as phantoms.

Drop the block from both summer waybar.jsonc whole-swaps. The other themes
render from waybar.nix, which never declared persistent-workspaces, so they
already only show existing workspaces — no change needed there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:35:25 +01:00
Bernardo Magri
7c34eed2ca fix(theme): solid per-theme hyprland borders, matching legacy
v1 forced an accent→accentAlt 45deg gradient on every theme's window
border; the legacy identity themes all used a *solid* border — accent for
nord/retro-82/lumon, the text tone for kanagawa/summer-day/summer-night.

Add a palette-resolved `border` field ({active, inactive}, each a palette
key or literal hex) to the theme schema with accent/overlay defaults in
theme.nix, consume it solid in hyprland.nix, and declare it in every
preset (and the live state). Declaring it everywhere is required because
`nomarchy-theme-sync apply` deep-merges presets — an omitted field would
stick across switches, like colors already are fully specified per theme.

All six identity themes resolve to their exact legacy active border
(verified: nord #81a1c1, retro-82 #faa968, lumon #f2fcff, kanagawa
#dcd7ba, summer-night #d3c6aa, summer-day #5c6a72). nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:32:18 +01:00
Bernardo Magri
d252f01f17 feat(shell): flat starship prompt, drop the powerline styling
The powerline look (the `[]` separator glyph + bg-filled segments) didn't
read well. Switch to a flat prompt: bold-accent directory, warn git
branch, subtext git-status/duration, `❯` character — plain colored text,
no background fills, each module carrying its own trailing space so there
are no stray gaps. Also reference cmd_duration in `format` (it was
configured but never shown).

Generated starship.toml verified valid; nix flake check passes. Visual
pass still wants a live terminal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:18:46 +01:00
Bernardo Magri
28a2f5d5ae fix: yazi fetcher group field, xfce package renames, enable nix-ld
Three quick known-issue fixes:

- yazi: 26.x made the fetcher `group` field required, so the git
  prepend_fetchers failed to parse and yazi fell back to presets. Add
  `group = "git"` to both entries (the git plugin's setup() only renders
  the linemode — the fetcher still needs registering here).
- file-manager: xfce.exo and the three Thunar plugins moved to top-level;
  use pkgs.xfce4-exo / pkgs.thunar-{archive-plugin,volman,media-tags-plugin}.
  Clears the eval-time deprecation warnings.
- nix-ld: enable programs.nix-ld distro-wide so foreign dynamically-linked
  binaries (downloaded tools, npx-fetched claude-code, language servers)
  run without patchelf.

Also drops the now-stale claude-code mention from the allowUnfree comment
(the ask module uses npx). nix flake check passes; xfce warnings gone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:14:11 +01:00
Bernardo Magri
f06d7f361c docs: roadmap item for SSH/GPG agent + graphical pinentry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:06:06 +01:00
Bernardo Magri
58d79aad17 feat(menu): network/bluetooth/capture/ask modules + keybindings cheatsheet
Build out the rofi menu system roadmap item:

- New nomarchy-menu modules: network (nmtui), bluetooth (blueman-manager),
  capture (grim/slurp submenu → clipboard/file), ask (free-text → claude
  CLI in a terminal), and keybinds (the SUPER+? cheatsheet).
- modules/home/keybinds.nix is now the single source of truth for both the
  Hyprland bind strings and the cheatsheet, so the two can't drift —
  hyprland.nix maps it into `bind`, rofi.nix renders the padded two-column
  sheet (generated/mouse binds live in its `extra` rows).
- New binds: SUPER+Space (quick launch), SUPER+M (main menu), SUPER+?
  (cheatsheet). SUPER+D stays rofi -show drun.
- Ask Claude pulls claude-code fresh from npm via `npx
  @anthropic-ai/claude-code@latest` (the nixpkgs package lags model
  releases); nodejs is bundled for npx.

nix flake check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:59:46 +01:00
Bernardo Magri
60138e20d0 docs: log known issues & follow-ups in the roadmap
Yazi prepend_fetchers group field, starship powerline removal, xfce
package deprecations, rofi function keybinds, nix-ld default, hyprland
border colors, and waybar phantom workspaces.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 19:25:40 +01:00
Bernardo Magri
df7c0ef7ae feat: zsh default shell + starship/bat/eza/zoxide shell experience
zsh is now the default login shell (programs.zsh.enable +
users.defaultUserShell, mkOverride 500 to beat bash.nix's mkDefault;
per-user shell= still overrides). modules/home/shell.nix configures the
interactive experience (nomarchy.shell.enable):

- zsh: completion, autosuggestions, syntax highlighting, big shared
  history, autocd.
- starship prompt themed from the palette (a `nomarchy` palette built
  from theme-state.json colors).
- bat as cat (theme "ansi" → tracks the terminal/theme palette), eza as
  ls (icons + git + dirs-first), zoxide as cd (--cmd cd).
- rg/fd shipped as themselves — deliberately NOT aliased over grep/find
  (flag differences surprise).

home.shell.enableZshIntegration = true is the single lever; starship,
eza, zoxide, ghostty and yazi all emit their zsh hooks from it (no
per-module edits). Verified: flake check green, default shell resolves
to zsh, .zshrc carries the starship/zoxide/eza/yazi hooks + aliases,
starship palette renders, all tools in the live ISO closure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:25:21 +01:00
Bernardo Magri
f97054c804 feat: icon themes — Papirus, resolved from the JSON, for GTK + rofi
Ships papirus-icon-theme and resolves an icon-theme name once in
theme.nix → nomarchy.theme.iconTheme: the JSON's optional `icons` field,
else Papirus-Dark/Light picked by `mode`. That single value feeds:
- Stylix `stylix.icons` → gtk.iconTheme (Thunar and GTK apps)
- rofi `show-icons` + `icon-theme` (the launcher now shows app icons)

A preset or theme-state.json can set `icons` to any theme in the icon
package to override per theme.

Verified: flake check green; iconTheme resolves Papirus-Dark for
tokyo-night and Papirus-Light for summer-day; gtk.enable true with
gtk.iconTheme.name set; papirus in home.packages and the live ISO
closure; rofi config.rasi carries icon-theme + show-icons:true. The
rendered icons need a real session to see.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:12:19 +01:00
Bernardo Magri
520c878bb3 feat: swayosd on-screen display for volume/brightness/mute
The media keys called wpctl/brightnessctl directly with no visual
feedback — changing volume showed nothing on screen. Now they drive
swayosd-client, which performs the action AND shows a themed OSD.

- modules/home/osd.nix: services.swayosd, style.css generated from the
  palette (base bg, accent progress/border); nomarchy.osd.enable.
- hyprland media binds → swayosd-client (--output-volume raise/lower/
  mute-toggle, --input-volume mute-toggle, --brightness raise/lower).
- modules/nixos: services.udev.packages = [ swayosd ] installs the
  backlight rule (chgrp video + g+w) so brightness works for video-group
  users without root (the installer/live user is in video).

swaync is unaffected (it was already running — this was purely the
missing OSD). Verified: flake check green, swayosd.service bound to
graphical-session.target, hyprland config verifies, swayosd in the live
ISO closure. On-screen rendering needs a real session to confirm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 16:04:55 +01:00
Bernardo Magri
084b2a0500 feat(theme-sync): persistent switch notification so slow switches read as progress
The "Applying theme — rebuilding…" toast used the default timeout, so it
vanished seconds into a multi-minute home-manager switch — leaving the
user with no signal and the impression the selection failed. Now it's
persistent (timeout 0) and all theme notifications share a synchronous
tag, so swaync REPLACES it in place with "Theme applied ✓" (or a critical
failure toast that persists) when the rebuild finishes.

notify() grew persistent/urgency/summary params. Honest indeterminate
feedback — HM exposes no percentage, so no fake progress bar.

Verified: py_compile clean; the full apply→switch→notify path runs end to
end (dummy NOMARCHY_REBUILD). The in-place replacement is visible only in
a real swaync session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:52:41 +01:00
Bernardo Magri
20df0d8a60 docs: fold todo.md items into the roadmap
Theme-switch progress feedback; per-theme icon themes (unblocks rofi
show-icons); starship prompt + modern-CLI aliases (bat/eza/rg/fd/zoxide);
opt-out default app suite (nomarchy.apps.*); Plymouth logo contrast fix;
hibernate double-unlock (hyprlock redundant after LUKS resume); plus a
keybindings-cheatsheet menu module and the SUPER+Space / SUPER+M binds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:48:50 +01:00
Bernardo Magri
abfe872109 feat(themes): port legacy rofi identities for summer-day/night
themes/<slug>/rofi.rasi whole-swaps for the two themes that had a
distinct launcher look in the legacy branch — inverted window (bg=fg),
green inputbar, yellow bottom-border accent, rounded. Adapted: dropped
the configuration{} block (rofi.nix owns modi/terminal/show-icons) and
swapped the unshipped Iosevka 17 for a shipped Nerd Font.

Verified: both .rasi parse under `rofi -dump-theme`; the whole-swap
selects per active theme (summer-night → #2d353b/#a7c080, summer-day →
#fdf6e3, others → the generated palette theme); flake check green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 15:45:39 +01:00
Bernardo Magri
961314ea86 feat(home): switch launcher/menu from fuzzel to rofi 2.0
rofi 2.0 landed in nixpkgs 26.05 with native Wayland (mainline upstreamed
the lbonn rofi-wayland fork — `wayland enabled`), so the original reason
to prefer fuzzel (rofi being X11-only / needing a community fork) is gone.
rofi's `.rasi` is far more expressive than fuzzel's flat INI, which lets
the launcher be a proper themed hero surface and revives the legacy
per-theme launcher designs.

- modules/home/rofi.nix replaces fuzzel.nix: per-element theme generated
  from theme-state.json (accent border, highlighted selection, rounded
  inputbar) via lib.formats.rasi.mkLiteral; themes/<slug>/rofi.rasi
  whole-swap; the nomarchy-menu dispatcher re-pointed to `rofi -dmenu`
  (power/theme/clipboard/calc with -mesg/files/web + root picker).
- SUPER+D is now `rofi -show drun`; the menu binds are unchanged.
- nomarchy.fuzzel.enable → nomarchy.rofi.enable; fuzzel dropped from
  home.packages. Pango handles nf-glyph fallback (no explicit font list
  needed as fuzzel's fcft did). show-icons off until an icon theme ships.

Verified: flake check green, HM generation builds, the generated
theme.rasi parses under `rofi -dump-theme`, Hyprland config verifies.
Not verified: rofi's on-screen rendering needs a real session.

Docs: README menu/override/roadmap sections and the module tree updated;
the per-theme launcher roadmap item now points at porting the legacy
rofi.rasi designs (the whole-swap mechanism is in place).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:41:46 +01:00
Bernardo Magri
76582c1ee9 feat: file manager — yazi (flagship TUI) + Thunar (GUI)
Legacy shipped only Thunar (and a stray nautilus extension, mid-migration).
This pairs a keyboard-driven TUI flagship that fits the distro's identity
with the GUI half for point-and-click + the "open folder" handler.

yazi (modules/home/yazi.nix, nomarchy.yazi.enable, SUPER+E, `y` shell
wrapper cd's on exit): themed from theme-state.json (mgr/mode/status
mapped from the palette; merges over yazi's defaults so it tracks the
theme) with a curated, correctly-wired plugin set —
  full-border, git (setup + prepend_fetchers), smart-enter (l/Enter),
  chmod (cm), mount (M), compress (caa), mediainfo (audio/subtitle only —
  images/videos keep native thumbnails), glow (markdown previews).
Preview/tooling deps: ffmpegthumbnailer, poppler-utils, p7zip, jq, fd,
ripgrep, fzf, chafa, mediainfo, glow.

Thunar (modules/nixos/file-manager.nix, nomarchy.system.fileManager.enable):
GTK file manager (Stylix-themed) + archive/volman/media-tags plugins +
gvfs/tumbler/udisks2 backends + thumbnailers + exo. udisks2 also backs
yazi's mount plugin.

Verified: flake check green, HM generation builds (yazi TOML/init.lua
render, all 8 plugins resolve), every package present in the live ISO
closure (offline install draws from it — no source-build leak).
Not verified: yazi's runtime preview/plugin behaviour needs a real
session (TUI; can't drive in CI here).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:32:11 +01:00
Bernardo Magri
d06373b155 feat(home): mkDefault behaviour settings so downstream can plain-override
Nomarchy's Hyprland/Ghostty/Waybar settings were set at normal priority,
so a downstream `home.nix` setting any value Nomarchy already set threw a
"conflicting definition values" error — overriding required lib.mkForce,
contradicting the README's "mkDefault throughout" claim.

Now: behaviour/chrome leaves (input, misc, monitor, animations, dwindle,
gesture, decoration.blur sizing, ghostty padding/decoration, waybar
settings+style) are lib.mkDefault — a plain home.nix assignment wins.
Appearance values that flow from theme-state.json (gaps, colors,
rounding, opacity, fonts) stay at normal priority on purpose: change them
via nomarchy-theme-sync (their source of truth) or lib.mkForce to
hardcode. bind/exec-once lists stay normal priority so additions
concatenate rather than replace.

Verified: behaviour/ghostty plain overrides build; theme values still
conflict (steering to the CLI); default rendered config is byte-identical.

New docs/OVERRIDES.md with the appearance→CLI / behaviour→home.nix /
component→toggle model and worked examples; linked from the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 14:05:59 +01:00
Bernardo Magri
bd42462291 fix(home): power menu on SUPER+X, not SUPER+Escape
Super+Escape didn't fire on real hardware (Latitude 5410) while its
neighbour binds did — the chord gets swallowed before reaching
Hyprland's dispatcher on some setups. SUPER+X is unambiguous.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 13:44:22 +01:00
Bernardo Magri
583708d78c docs: README for the v1 QA batch (menu, swaync, idle, Plymouth, sys-update)
Options/keybinds/layout tables, sys-update/home-update workflow, the new
module tree, and roadmap updates (menu system + nm-applet shipped; theme
parity, branding round 2, swayosd, ask-Claude module remain).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:38:31 +01:00
Bernardo Magri
6763bef73e fix(install): pre-activation, writable home.nix, keyboard/locale selection
- Pre-activation failure ("desktop pre-activation failed"): the in-chroot
  HM build hard-coded `--option substituters ""`, which only works on
  OFFLINE installs (where the ISO store is copied in). Online installs
  (now common — wifi works) had no pins and built from source. Fix: build
  the generation in the live env (its store always has the pins), nix copy
  the closure to the target, chroot only activates. In-chroot build kept
  as fallback. Uses path: (not git+file:) to sidestep libgit2 ownership.
- Read-only home.nix: templates come out of the store mode 0444 and cp
  preserves it — chmod -R u+w after staging so the user can edit it.
- Keyboard + locale selection: gum prompts (localectl layouts/variants +
  curated UTF-8 locale list) and NOMARCHY_LOCALE/KB_LAYOUT/KB_VARIANT for
  unattended. One choice flows everywhere — services.xserver.xkb (source
  of truth) + console.useXkbConfig (tty/greeter) + boot.initrd.systemd
  (so the LUKS passphrase prompt uses the layout; script initrd can't) +
  the generated home.nix's nomarchy.keyboard.* for the Hyprland session.
- test-install.sh: NOMARCHY_OVMF env override for sandboxed OVMF paths.

Verified end-to-end in QEMU: offline LUKS install completes, boots to a
themed desktop (no autogenerated-config banner), Plymouth renders the LUKS
prompt on the theme-tinted background.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:38:31 +01:00
Bernardo Magri
465f013155 feat(home): menu system, swaync, idle/lock, nm-applet, theme parity
- Themed fuzzel (modules/home/fuzzel.nix): palette/fonts/border from the
  JSON, themes/<slug>/fuzzel.ini whole-swap mechanism. Plus the
  nomarchy-menu dispatcher: root picker · power (SUPER+Escape) · theme
  (SUPER+T) · clipboard (SUPER+CTRL+V, cliphist) · calc (qalc) · files
  (fd→xdg-open) · web (DuckDuckGo).
- swaync notifications themed from the JSON (SUPER+N) — until now nothing
  rendered notify-send (theme toasts, font warnings, welcome msg).
- hyprlock + hypridle (modules/home/idle.nix): lock 5min, dpms off 10,
  suspend 30; enables the power menu's Lock entry. cliphist daemon on.
- NetworkManager applet in waybar's tray (preferStatusNotifierItems);
  network module on-click → $TERMINAL -e nmtui.
- nomarchy.keyboard.layout/.variant option → Hyprland kb_layout/kb_variant
  (the installer writes it; pairs with system-side xkb for tty/LUKS).
- Media-key/audio on-click moved to wpctl (pamixer was inconsistent).
- Theme parity: summer-day/night carry their legacy bar LAYOUTS as
  waybar.jsonc whole-swaps (the original import took waybar.css but not
  config.jsonc, so identity themes styled nonexistent modules). Dead
  legacy script-modules dropped, Nerd-Fonts-v2 codepoints → FontAwesome/v3
  (font-awesome now shipped), logo buttons open nomarchy-menu.

New toggles: nomarchy.{fuzzel,swaync,idle}.enable (all default true).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:37:47 +01:00
Bernardo Magri
eda8461304 feat(system): Plymouth splash, distroName, allowUnfree, offline-pin hardening
- Plymouth boot splash ported from the legacy branch (modules/nixos/
  plymouth/): logo + eased progress + LUKS entry, background tinted from
  theme-state.json via the new nomarchy.system.stateFile (wired by
  mkFlake/lib.nix; null → Tokyo Night fallback). Default on; OFF on the
  live ISO (boot-message visibility on the install medium). Pulls
  boot.initrd.systemd, which also drives the keyboard-at-LUKS feature.
- system.nixos.distroName = "Nomarchy" (os-release PRETTY_NAME,
  systemd-boot entries, ISO menu label). distroId left "nixos" (feeds
  DEFAULT_HOSTNAME + upstream isNixos checks — roadmapped).
- nixpkgs.config.allowUnfree distro-wide (here + both import-nixpkgs
  sites) — unblocks claude-code for the menu's ask-Claude module.
- systemd-boot.configurationLimit = 10 so entries don't fill the ESP.
- Live ISO: nomarchy.idle.enable = false — hypridle was suspending the
  VM mid-install (the install-hung regression); installed systems keep it.
- flake.nix offline pins (verified 0-leak via a foreign-identity
  gap-analysis probe): the repo's own standalone HM gen + inputDerivations,
  mustache-go + stdenv (stylix re-renders base16 per switch), microcode-amd/
  intel (enableRedistributableFirmware activated updateMicrocode →
  source-build cascade), buildEnv's builder.pl, findXMLCatalogs, and the
  representativeInstall mirror (xkb/initrd-systemd/microcode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:37:32 +01:00
Bernardo Magri
1ba1eeed64 fix(live): re-enable fontconfig + xdg integration the minimal-CD profile strips
installation-cd-minimal forces fonts.fontconfig.enable = false
(mkOverride 500) to slim a console-only installer — but this ISO is the
desktop. Without fontconfig no family resolves: Waybar's nf-md icons
rendered as tofu boxes and Ghostty silently fell back to the wrong
font (reported on the Latitude 5410, reproduced + fix verified in QEMU
via VNC screenshots: icons render, fc-match resolves JetBrainsMono
Nerd Font). Also restore xdg icons/mime/autostart and man pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:23:28 +01:00
Bernardo Magri
c1781f45ca feat: allow unfree packages distro-wide
nixpkgs.config.allowUnfree in the system module (covers the live ISO
via useGlobalPkgs and every mkFlake machine) plus the two explicit
`import nixpkgs` sites (repo + lib.nix) so the standalone HM desktop
sees the same package set. Unblocks claude-code for the menu system's
ask-Claude module, vendor drivers, etc. Opt out with
`nixpkgs.config = lib.mkForce { allowUnfree = false; }`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:16:20 +01:00
Bernardo Magri
ed7be0206d docs: roadmap — menu script modules: calc, clipboard, files, web, ask-Claude
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:11:28 +01:00
Bernardo Magri
009bdbad8f docs: roadmap — menu system design: fuzzel dmenu dispatcher, themed from the JSON
Resolves the old Walker/Lua previews-vs-simplicity question in favor of
fuzzel: already shipped, nested menus via dmenu mode, INI theme bakeable
from theme-state.json like every other app.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:03:58 +01:00
Bernardo Magri
a33127cd3c docs: roadmap — Nomarchy distro branding (os-release, ISO boot menu, splash)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:59:52 +01:00
Bernardo Magri
dccceb4c48 fix: real-hardware QA sweep — wifi, fonts, firmware, Hyprland 0.55, offline switching
Roadmap items 1+2 plus the issues found testing on a Latitude 5410:

- Ship the 10 most popular Nerd Fonts by default (iosevka swapped for
  mononoki: 1.1 GB vs 27 MB) and warn from nomarchy-theme-sync when a
  configured fonts.mono/fonts.ui family isn't installed (fc-match) —
  fontconfig substitutes silently otherwise.
- hardware.enableRedistributableFirmware: installed systems shipped NO
  firmware blobs (wifi/SOF audio/BT) — nixos-generate-config only emits
  microcode lines referencing this flag, it never sets it.
- Live ISO wifi: networking.wireless.enable = mkForce false killed
  NetworkManager's supplicant — since 26.05 the NM module drives its
  wifi backend THROUGH networking.wireless (dbusControlled). Devices
  vanished from nmtui with iwlwifi loaded and no rfkill block.
- Hyprland 0.55: launch sessions via start-hyprland (watchdog; bare
  binary warns), add the new gesture keyword (workspace_swipe is gone —
  touchpads had no gestures at all), media keys via wpctl/bindel/bindl
  plus the missing mic/play/next/prev binds.
- Offline theme switching: pin mustache-go + stdenv + the repo's own
  standalone HM generation (incl. home-files inputDerivation) into the
  ISO — stylix re-renders base16 templates per switch and an offline
  `apply` cascaded into building stdenv from source (1107 drvs).
  Verified with tools/vm/gap-analysis.py: 17 config drvs, zero fetches.
- Stylix: track release-26.05 (kills the HM version-skew warning and
  the stale home-manager follows), lock updated.
- Roadmap: swaync (no notification daemon ships today).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:50:55 +01:00
bc5faec6b8 Update README.md 2026-06-12 14:05:10 +01:00
Bernardo Magri
77a0e70146 Nomarchy v1: the NixOS 26.05 ground-up rewrite replaces the legacy distro
main now carries the new distro (theme-state.json engine, mkFlake
downstream API, nomarchy-install with verified offline installs). The
old distro's history stays reachable here and lives on as the `legacy`
branch. Tree is identical to refactor/v1 @ 37819bb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:14:44 +01:00
Bernardo Magri
37819bb50f feat: v1 release branch — generated flakes and template track ?ref=v1
Installed machines and the downstream template now resolve nomarchy from
git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1 (the lock's
original node carries the ref too), so `nix flake update` follows the
release branch instead of the forge's default branch. README quickstart
points at the real forge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:50:09 +01:00
Bernardo Magri
a50e9793ea feat(tools): commit the VM test harness + one-command installer regression
Promotes the throwaway harness that verified the installer into tools/:
- tools/vm/qmp.py        QMP keystroke injection / typing / quit
- tools/vm/vncshot.py    GL-safe screenshots via VNC readback (QMP
                         screendump shows "no surface" with virtio-vga-gl)
- tools/vm/gap-analysis.py  drv-graph diff that converged the offline
                         pin set; run it when offline installs build
                         from source
- tools/test-install.sh  the full offline regression: build ISO, boot
                         offline, unattended LUKS+swap install via a
                         config disk (typed long commands drop keys),
                         wait for poweroff, boot the installed disk,
                         screenshot the first boot for visual verdict

docs/TESTING.md §4 now points at the script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:05:13 +01:00
Bernardo Magri
7b5a22c800 fix(install): run a nix-daemon for the chroot HM activation + backup collisions
The pre-activation log (now persisted) showed activate dying at its
profile operation: as the target user with no daemon in the chroot,
nix can't open the store db ("big-lock: Permission denied"). Run a
temporary nix-daemon inside the chroot and point activate at it via
NIX_REMOTE=daemon. HOME_MANAGER_BACKUP_EXT=bak makes file collisions
(e.g. a stray autogenerated hyprland.conf) move aside instead of
aborting. Verified in the chroot on a real installed disk: HM symlinks
land, collision backed up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 21:51:28 +01:00
Bernardo Magri
134695806c fix(install): stage the pre-activation script in /root — nixos-enter tmpfs-mounts /tmp
The HM pre-activation never ran: the script was written to /mnt/tmp and
nixos-enter mounts a fresh tmpfs over /tmp inside the chroot, so `bash
/tmp/...` found nothing and the best-effort guard swallowed it. Verified
interactively that every inner step (path: build as root, runuser leg)
works in the chroot — only the staging path was broken. Now staged in
/root, with set -x logging persisted to /var/log/nomarchy-hm-preactivate.log
on the target and the tail echoed into the install transcript on failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:53:21 +01:00
Bernardo Magri
727f21005d fix(install): root-side flake refs must be path:, not git+file
The chown fix made the flake user-owned, and root's libgit2 then refuses
git+file fetches of it ("not owned by current user") — which silently
killed the seeding step under set -e and would equally break
nixos-install --flake. Use path: refs for the live-side archive and the
nixos-install invocation (path flakes read flake.lock, skip libgit2),
and make seeding failure non-fatal as intended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 19:39:09 +01:00
Bernardo Magri
1403ea82b2 fix(install): chown the generated flake to the user; harden HM pre-activation
End-to-end VM verification found the installed system perfect (LUKS,
auto-login, swapfile+resume, snapper) except: ~/.nomarchy was root-owned,
so libgit2 refused every `home-manager switch` ("repository not owned by
current user"). chown 1000:users after generation (the account doesn't
exist in the target yet — numeric ids).

The chroot pre-activation now builds via path: instead of git+file —
no libgit2, no ownership sensitivity — and the fallback message suggests
-b bak (a default-config Hyprland session leaves an autogenerated
hyprland.conf in the way otherwise; observed live).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:17:38 +01:00
Bernardo Magri
29818d2fb6 fix(install): offline installs copy the ISO store into the target up front
Substituter plumbing through nixos-install proved unreliable (env config
is overridden by its hardcoded flags; the forwarded --substituters flag
still left the plan building gcc from source). Make every ISO path valid
in the target store with one nix copy --all before nixos-install — the
in-target build then resolves everything locally and only the per-machine
config drvs are built.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:01:49 +01:00
Bernardo Magri
77dba7ca5d fix(install): pass --substituters through nixos-install, not just the env
nixos-install hardcodes --extra-substituters "auto?trusted=1" as a CLI
flag, and flags override NIX_CONFIG for the same setting — so the env
fix never reached the in-target build. nixos-install forwards a
--substituters flag verbatim; use that.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 09:24:00 +01:00
Bernardo Magri
dba349a539 fix(install): substitute from the live daemon store during the target build
nixos-install builds with --store /mnt and passes its own substituter as
"auto?trusted=1", which in that context resolves to the target store
itself — so nothing flowed from the ISO and nix fell back to a
full-source bootstrap (gcc/python/bash tarball fetches → offline death).
Host-store simulation (fresh local?root, substituters cleared) proves
"daemon?trusted=1" makes the whole install substitute + build offline:
1125 paths copied, only the per-machine config drvs built locally.

Also drop --offline from the chroot HM build (that flag disables ALL
substituters; plain empty substituters does what we want without it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:51:30 +01:00
Bernardo Magri
3b0b2872aa fix(install): seed target store via nix copy --no-check-sigs
`nix flake archive --to` rejects locally-evaluated source paths ("lacks
a signature by a trusted key"); enumerate the archive tree as JSON and
copy with --no-check-sigs instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:02:52 +01:00
Bernardo Magri
804d708f87 fix(install): pin the common nixos-hardware profile stacks into the ISO
Round 7 in-VM: autodetection emitted common-cpu-intel, whose nixos-hardware
module pulls the whole Intel graphics stack (intel-media-driver,
compute-runtime, libva, level-zero…) — none of it on the ISO, so the
offline install built graphics drivers from source. The representative
pin now imports the common-* profiles (intel+amd cpu/gpu, laptop, ssd,
pc), covering what the detector emits on ordinary machines.
Model-specific profiles may still need network.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:59:02 +01:00
Bernardo Magri
8da429d611 fix(install): converge the offline pin set via derivation-graph gap analysis
Walking the install-shaped toplevel's drv graph against the ISO's actual
path set (tools/gap analysis) found why offline installs kept building
from source:

- system-path is rebuilt on EVERY machine: buildEnv's package-list order
  follows module-graph order, which any hardware profile reshuffles →
  pin its builders (texinfo, getconf)
- greetd.toml embeds the auto-login user and is generated by remarshal;
  building remarshal offline drags its test closure (matplotlib → ffmpeg
  → x265…, ~1900 drvs) → pin pkgs.remarshal
- second-level tools for the always-rebuilt drvs: kmod.dev +
  nukeReferences (modules-shrunk), xorg.lndir (unit dirs), libxslt.bin
  (dbus config), python3Minimal (etc)
- the representative pin now has a vfat /boot like a real install
  (mtools/dosfstools enter systemPackages via the vfat fs-type gate)

Verified on the host: the remaining offline build set for a foreign
username/hostname/UUID config is 66 trivial config drvs + eval-time file
copies from the pinned nixpkgs tree — zero network. Same check for the
per-username HM activation: 15 drvs, offline-clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:36:46 +01:00
Bernardo Magri
996fdc28c9 revert(install): includeBuildDependencies was a 134 GB hammer
The full system build closure adds ~134 GB to the store and would have
produced an unusable multi-ten-GB ISO. Back to targeted inputDerivation
pins; the still-missing second-level build tools get pinned explicitly
next, from a clean committed-tree gap analysis.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 22:09:22 +01:00
Bernardo Magri
609bb7e7dc fix(install): carry the full system build closure on the ISO
Round 6 still hit a from-source cascade: every per-machine drv (units,
initrd pieces, dbus config, modules-shrunk) needs its own build tools
(lndir, builder.pl, kmod-dev, nuke-refs, libxslt-bin, …), one level
deeper than targeted inputDerivation pins reach. Use the documented
mechanism instead: system.includeBuildDependencies = true on the
representative install pin. Bigger ISO, networkless installs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:41:37 +01:00
Bernardo Magri
ecf94e84ab fix(install): pin build-time deps of per-install drvs; no substituters offline
Offline nixos-install still built from source: the drvs that embed the
user's hostname/username/UUIDs (etc, initrd, toplevel, HM activation)
must be REBUILT, and their builders need tools outside every runtime
closure (etc → python3-minimal, initrd → packing tools). Pin each drv's
inputDerivation into the ISO — the standard nixos-test offline trick.

Also: with no network every substituter query is a DNS retry storm that
ends in a nix goal.cc assertion crash (core dump observed); the installer
now probes connectivity and disables substituters when offline, both on
the live system and inside the nixos-enter HM pre-activation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:05:17 +01:00
Bernardo Magri
c813055578 fix(install): pin the install-shaped system into the ISO, not the bare template
The offline nixos-install failed building from source: the generated
config enables snapper/auto-login/LUKS-initrd/swapfile-resume on a BTRFS
root, none of which the bare template (ext4 placeholder!) pulls in — so
snapper, nixos-rebuild-snap, btrfs-progs-lib and cryptsetup-bin were
absent from the ISO. The pinned system now mirrors the installer's output
(closure-diff verified).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:33:15 +01:00
Bernardo Magri
8881ad3bae fix(install): rewrite follows paths when grafting the upstream lock
Lock follows paths are absolute from the root node; nomarchy's nodes move
one level down in the composed downstream lock, so ["nixpkgs"] must
become ["nomarchy","nixpkgs"] — otherwise every follows dangles
("input 'nomarchy/home-manager/nixpkgs' follows a non-existent input").
Verified: the composed lock now resolves the full input tree with
`nix flake metadata --offline`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:53:11 +01:00
Bernardo Magri
7aba5fa0a8 fix(install): pin a representative disko script into the ISO closure
disko builds its format/mount script at install time; offline, the
script's tool closure (file, which, wrapper hooks) wasn't on the ISO and
nix tried to build it from source — which needs the network. Pre-building
one representative script (luks + swap variant) pins every input; the
user's actual invocation then differs only in the final text-generation
derivation, which builds offline in seconds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:09:08 +01:00
Bernardo Magri
479193b10b fix(install): survive the live ISO offline and its non-interactive bash
Found by running the unattended install in an offline QEMU VM:
- compgen is missing from nixpkgs' non-interactive bash (the package
  shebang) — replaced with plain glob tests in hardware-db and prewipe
- disko's eval resolves <nixpkgs> via NIX_PATH (a dead channel on the
  ISO) and tried channels.nixos.org — export NIX_PATH to the pinned
  nixpkgs source and point the flake registry at an empty baked file
- lock nomarchy by path (the source the ISO carries) instead of
  git+https: git inputs clone even when narHash is known, which kills
  offline installs; `original` keeps the forge URL so a later
  `nix flake update` re-resolves normally. Works from dirty-tree ISOs too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:48:33 +01:00
Bernardo Magri
71614fa6ca feat(install): LUKS by default → auto-login, snapper snapshots, hibernation swapfile
- LUKS2 is the installer default; in exchange the generated config sets
  the new nomarchy.system.greeter.autoLogin (greetd initial_session) —
  the disk passphrase already gates the machine.
- @snapshots subvolume + nomarchy.system.snapper.enable: hourly/daily
  timeline snapshots of / and the nixos-rebuild-snap helper, ported from
  the previous iteration (3bdfc35), guarded to no-op on non-BTRFS roots.
- @swap subvolume with a swapfile sized to RAM by default (disko
  mkswapfile handles NOCOW); the installer computes the resume offset
  and wires boot.resumeDevice + resume_offset for hibernation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:19:14 +01:00
Bernardo Magri
f3707357a6 feat: nomarchy-install — guided live-ISO installer (disko + mkFlake)
Ported from the previous iteration's installer (3bdfc35), adapted to the
mkFlake world. gum TUI: disk pick, optional LUKS2, user/hostname/timezone,
DMI → nixos-hardware autodetection (hardware-db.sh). disko partitions
GPT + 1 GiB ESP + BTRFS subvolumes; the generated machine flake lands at
~user/.nomarchy (one mkFlake call, /etc/nixos symlinks to it) and
nixos-install makes it bootable (UEFI/systemd-boot, v1 single disk).

Offline by construction: the target flake.lock is composed from the rev
the ISO was built from (compose-lock.py), `nix flake archive` seeds the
target store, and the ISO pre-builds the template system + desktop.
First boot lands themed: the HM generation is pre-activated in chroot.

mkFlake grows two things for this: hardwareProfile now also takes a list
(autodetection emits several common-* modules), and installed systems get
the standalone home-manager CLI from the pinned input. Unattended mode
(NOMARCHY_UNATTENDED=1 + env) documented in docs/TESTING.md §4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 16:05:29 +01:00
Bernardo Magri
8ded92ea63 chore: drop renamed package aliases (swww→awww, greetd.tuigreet→tuigreet)
Silences nixpkgs' rename warnings on every eval.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:43:16 +01:00
Bernardo Magri
1f22e996dc feat: nomarchy.lib.mkFlake — one-call downstream wrapper + hardware profiles
Downstream users now own only system.nix and home.nix; their flake.nix is
generated once from the template and never hand-edited. mkFlake (lib.nix)
wires both layers and maps hardwareProfile = "<name>" to nixos-hardware
modules (new input, pinned here, nixpkgs follows ours; unknown names fail
with did-you-mean suggestions). Flake checks evaluate the template through
mkFlake — including a real profile — so drift fails `nix flake check`.

Also from the live-VM testing session: install the home-manager CLI in the
live ISO and pin flake inputs transitively (offline theme switching needs
stylix's own inputs too), plus swww→awww doc updates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:39:21 +01:00
Bernardo Magri
8a0ec763e0 fix: live-VM desktop fixes — Hyprland 0.55 compat + swww→awww rename
Found by booting the live ISO (docs/TESTING.md checklist):
- pin HM configType = "hyprlang" (26.05 stateVersion defaults to the new
  Lua config and renders these settings as broken hl.$mod(...) calls,
  booting Hyprland into emergency mode)
- drop dwindle:pseudotile (removed in Hyprland 0.55; aborts config parse)
- disable splash rendering alongside the logo
- launch awww-daemon and call awww with swww fallback (nixpkgs' swww is
  the renamed awww fork; no `swww` binary exists anymore)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:38:32 +01:00
Bernardo Magri
f211ef0d09 feat: Nomarchy ground-up rewrite on NixOS 26.05
Full replacement of the previous iteration, rebuilt around three ideas:

- Pure evaluation: theme-state.json lives inside the flake and is read
  via the nomarchy.stateFile option — no --impure, ever.
- All-Home-Manager theming: `nomarchy-theme-sync apply` writes the JSON
  and runs `home-manager switch`; every theme change is one atomic,
  rollbackable generation. Wallpaper (swww) is the sole runtime piece.
- Flat, downstream-first layout: modules/{nixos,home} with one
  options.nix each, exported as nixosModules/homeModules + overlay +
  flake template; system (nixos-rebuild) and desktop (home-manager
  switch) rebuild paths are fully split.

Ships 21 themes imported from the previous iteration (palettes,
wallpapers, btop themes, six whole-swap Waybar identities), Stylix for
the GTK/Qt/cursor long tail, a live ISO target with offline theme
switching, and docs/TESTING.md with the QEMU verification workflow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:59:13 +01:00
705 changed files with 18132 additions and 20440 deletions

90
.gitea/workflows/bump.yml Normal file
View File

@@ -0,0 +1,90 @@
# Nomarchy scheduled lock bump — the automated half of "rock-stable
# without rotting": once a week, update flake.lock (inputs track pinned
# release branches, so this never jumps a NixOS release — that's a
# deliberate hand-edited v2, see agent/GOALS.md), gate it, and land it
# on main only on green. `v1` promotion stays human, always.
#
# Fast lane: workflow_dispatch. For a security fix upstream, run this
# workflow manually from the Actions tab instead of waiting for Monday.
#
# Gate scope: `nix flake check --no-build` — the same eval tier as
# check.yml (this runner has no KVM; see item 20 for the VM-suite
# upgrade path). The py_compile/bash -n steps are skipped on purpose:
# they don't read flake.lock, so a lock bump cannot break them. The
# bump commit's own push then triggers check.yml as a second net.
# A green bump therefore guards evaluation, not VM behaviour — the
# checks.* suite still runs locally / at promotion time.
#
# Failure mode: a red gate fails this run visibly and pushes nothing;
# next schedule retries. A push race (someone landed on main mid-run)
# also just fails the final push — rerun or wait a week.
#
# Container recipe (nixbld users, sandbox=false, pinned Nix, plain-shell
# install) inherited from check.yml — the gotchas are documented there.
name: Lock bump
on:
schedule:
- cron: '17 5 * * 1' # Mondays 05:17 UTC
workflow_dispatch:
jobs:
bump:
runs-on: ubuntu-latest
timeout-minutes: 90
env:
NIX_CONFIG: |
experimental-features = nix-command flakes
sandbox = false
NIX_SSL_CERT_FILE: /etc/ssl/certs/ca-certificates.crt
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Nix
run: |
NIX_VERSION=2.31.5
groupadd -r nixbld 2>/dev/null || true
for i in $(seq 1 10); do
useradd -r -g nixbld -G nixbld -d /var/empty \
-s /usr/sbin/nologin -c "Nix build user $i" "nixbld$i" 2>/dev/null || true
done
curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon
echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH"
- name: Update flake.lock
# Inputs track release branches (nixos-26.05, release-26.05), so
# the update stays within them by construction. The human-readable
# change list goes into the commit body below.
run: |
nix flake update 2>&1 | tee /tmp/bump-log
if git diff --quiet flake.lock; then
echo "Lock already up to date — nothing to do."
echo "changed=0" >> "$GITHUB_OUTPUT"
else
echo "changed=1" >> "$GITHUB_OUTPUT"
fi
id: update
- name: Gate — nix flake check (eval only)
if: steps.update.outputs.changed == '1'
run: nix flake check --no-build
- name: Commit + push on green
if: steps.update.outputs.changed == '1'
# The checkout step persists the Actions token, so this push
# authenticates as the workflow; its push triggers check.yml as
# the second net. Only flake.lock is committed — pathspec-limited,
# nothing else can ride along.
run: |
{
echo "chore(lock): scheduled upstream bump"
echo
grep -E '^(• | )' /tmp/bump-log || true
echo
echo "Gate: nix flake check --no-build (eval tier — see bump.yml header)."
} > /tmp/bump-msg
git -c user.name="nomarchy-bump" -c user.email="actions@git.bemagri.xyz" \
commit --only flake.lock -F /tmp/bump-msg
git push origin HEAD:main

View File

@@ -1,42 +1,40 @@
# Nomarchy CI — eval + lint. # Nomarchy CI — the always-on net under direct-to-main pushes.
# #
# Catches the regressions that hurt today: # Scope (deliberate): the EVAL tier only. The runner behind this Gitea
# 1. Flake stops evaluating (broken option ref, missing import, etc.). # instance is act_runner + docker containers (the legacy repo's check.yml
# Checked per-output rather than via `nix flake check` so we can skip # ran 57 times on it) — no systemd, no /dev/kvm — so the checks.* VM
# packages.allThemeVariants — the 22-generation linkFarm that OOMs the # tests and full toplevel builds can't run here. `nix flake check
# runner; every palette is validated one-at-a-time by the matrix (4). # --no-build` still catches most breakage (type errors, missing options,
# 2. A `nomarchy-*` shell script has a syntax error or a shellcheck # bad merges, template/mkFlake drift — see docs/TESTING.md §1); the VM
# error-severity issue. # suite stays a local/promotion gate until a KVM-capable NixOS runner
# 3. `docs/SCRIPTS.md` drifts from the repo state because somebody # exists (see the commented vm-checks job at the bottom).
# added / removed / renamed a script and didn't run the generator
# (the pre-commit hook handles this, but only when enabled per-clone).
# 4. An opt-in nomarchy.* toggle stops evaluating. `nix flake check`
# only evaluates the four default configs, so a bug that only fires
# when a toggle is flipped sails through (e.g. the impermanence
# systemd-stage-1 assertion). nomarchy-eval-matrix layers each toggle
# onto the default config and forces system.build.toplevel.
# #
# Doesn't build ISOs — that needs a binary cache. Add a separate job # Inherited-from-legacy gotchas (learned over 57 runs, kept verbatim):
# once Cachix/Attic is in place. # - Single-user Nix (--no-daemon): no systemd in the container. The
# installer runs as root and honours build-users-group=nixbld from
# its bundled nix.conf, aborting unless the group exists AND has
# members — create nixbld + users first.
# - sandbox=false: Stylix/base16.nix do import-from-derivation; the
# single-user Nix in this container can't set up the build sandbox
# (no user namespaces), which otherwise surfaces as
# "path '…-source' is not valid".
# - Pin the Nix version: 2.34's lazy-trees git cache doesn't
# materialise flake-input `-source` paths into the store, breaking
# the same IFD reads. 2.31.5 matches what wrote flake.lock.
# - Plain-shell Nix install, not a JS action: act_runner's bundled act
# tops out at node20; a `run:` step has no node-runtime coupling.
name: Check name: Check
on: on:
push: push:
branches: [main] branches: [main, v1]
pull_request: workflow_dispatch:
jobs: jobs:
eval-and-lint: eval:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Flakes for every `nix` invocation in the job (the bare `nix flake timeout-minutes: 60
# check` below relies on this; the scripts pass the flag themselves).
# sandbox=false because Stylix/base16.nix do import-from-derivation —
# eval realizes fetched `-source` paths mid-check, and the single-user
# Nix in this container can't set up the build sandbox (no user
# namespaces), which otherwise surfaces as "path '…-source' is not
# valid". NIX_SSL_CERT_FILE because we add nix to PATH without sourcing
# the installer's profile (which is what normally exports it).
env: env:
NIX_CONFIG: | NIX_CONFIG: |
experimental-features = nix-command flakes experimental-features = nix-command flakes
@@ -47,22 +45,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Nix - name: Install Nix
# Plain shell install rather than a JS action: act_runner's bundled
# `act` only supports up to node20, and nix-installer-action@main
# moved to node24 ("runs.using ... got node24"). A run step has no
# node-runtime coupling. Single-user (--no-daemon) needs no systemd,
# which the catthehacker container doesn't run as PID 1.
#
# The installer runs as root here and honours build-users-group=
# nixbld from its bundled nix.conf, aborting unless that group exists
# AND has members. Create the nixbld group + build users (what a
# multi-user install does) before running it.
#
# Pin the Nix version: the latest (2.34) uses lazy-trees / a git
# cache that doesn't materialise flake-input `-source` paths into
# the store, so Stylix's import-from-derivation reads fail with
# "path '…-source' is not valid". 2.31.5 matches the Nix that wrote
# flake.lock locally and evaluates the flake cleanly.
run: | run: |
NIX_VERSION=2.31.5 NIX_VERSION=2.31.5
groupadd -r nixbld 2>/dev/null || true groupadd -r nixbld 2>/dev/null || true
@@ -73,118 +55,47 @@ jobs:
curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon curl -L "https://releases.nixos.org/nix/nix-${NIX_VERSION}/install" | sh -s -- --no-daemon
echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH"
- name: Materialize flake-input sources for eval - name: nix flake check (eval only)
# Walker's flake reads its own source tree during evaluation: # Full module-system evaluation of every output — both nixos
# importTOML resources/config.toml in its home-manager module and # configs, the home config, the checks.* derivations (instantiated,
# lib.fileset.toSource for the package src (cargoLock.lockFile = # not run) and the downstream template through lib.mkFlake.
# "${src}/Cargo.lock"). On a cold store the input source isn't run: nix flake check --no-build
# materialised, so flake check / eval die with "path '…-source' is
# not valid" (works locally only because prior builds materialised
# it; `nix flake archive` reports the input but doesn't make the
# fileset-derived src readable). `nix build` of walker's src
# eagerly realises it — and the input it reads from — priming the
# store so the later eval steps find every path valid.
run: |
nix build --no-link --impure --expr 'let f = builtins.getFlake (toString ./.); in f.inputs.walker.packages.${builtins.currentSystem}.default.src'
- name: Check flake outputs (except the 22-variant pre-cache package) - name: Python syntax (nomarchy-theme-sync)
# Stand-in for `nix flake check --no-build`. That also evaluates # Via nix shell so the step doesn't depend on the runner image
# packages.allThemeVariants — a linkFarm whose drvPath forces all 22 # preinstalling python3.
# home generations into memory at once, OOM-killing the runner. The
# eval matrix below already validates every palette one-at-a-time, so
# that package's CI value is near-zero. Here we force every other
# output (all 4 system closures incl. assertions, both standalone
# home generations, the installer VM package, the overlay, the app)
# and skip only allThemeVariants/default. Each tryEval is GC'd before
# the next, so peak memory is one config, not twenty-two.
run: | run: |
nix eval --impure --raw --expr 'let nix shell nixpkgs#python3 --command \
f = builtins.getFlake (toString ./.); python3 -m py_compile pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py
lib = f.inputs.nixpkgs.lib;
sys = "x86_64-linux";
ok = x: (builtins.tryEval (builtins.seq x true)).success;
checks = {
"nixos.default" = ok f.nixosConfigurations.default.config.system.build.toplevel.drvPath;
"nixos.nomarchy-installer" = ok f.nixosConfigurations.nomarchy-installer.config.system.build.toplevel.drvPath;
"nixos.installerVm" = ok f.nixosConfigurations.installerVm.config.system.build.toplevel.drvPath;
"nixos.nomarchy-live" = ok f.nixosConfigurations.nomarchy-live.config.system.build.toplevel.drvPath;
"home.nomarchy" = ok f.homeConfigurations.nomarchy.activationPackage.drvPath;
"home.nixos" = ok f.homeConfigurations.nixos.activationPackage.drvPath;
"packages.installerVm" = ok f.packages.${sys}.installerVm.drvPath;
"overlays.default" = ok (builtins.isFunction f.overlays.default);
"apps.installerVm" = ok f.apps.${sys}.installerVm.program;
};
failed = builtins.attrNames (lib.filterAttrs (_: v: !v) checks);
in if failed == [] then "ALL OK"
else throw ("flake-output checks failed: " + builtins.concatStringsSep ", " failed)'
- name: Evaluate opt-in toggle / palette / home matrix - name: Shell syntax (tracked scripts)
# flake check never flips a nomarchy.* toggle; this does. jq is # The distro's user-facing scripts are generated by Nix (their
# provided via nix shell so the step doesn't depend on the runner # syntax is exercised by the eval + local builds); this covers the
# image preinstalling it. # hand-written .sh files: installer helpers and maintainer tools.
run: nix shell nixpkgs#jq --command ./bin/utils/nomarchy-eval-matrix
- name: Lint nomarchy-* scripts (bash -n + shellcheck)
run: | run: |
# Mirror what .githooks/pre-commit runs locally, but across the
# whole tree instead of just changed files. Pre-commit gates
# individual commits; CI gates branches (including --no-verify
# bypasses).
set -e set -e
fail=0 fail=0
while IFS= read -r script; do while IFS= read -r script; do
[[ -f "$script" ]] || continue head -1 "$script" | grep -qE '^#!.*\b(bash|sh)\b' || continue
# Python helpers ship under the same nomarchy- prefix
# (e.g. nomarchy-haptic-touchpad). Skip non-bash.
head -1 "$script" | grep -qE '^#!.*\bbash\b' || continue
if ! bash -n "$script"; then if ! bash -n "$script"; then
echo "::error file=$script::bash syntax error" echo "::error file=$script::bash syntax error"
fail=1 fail=1
fi fi
if ! nix shell nixpkgs#shellcheck --command shellcheck \ done < <(git ls-files '*.sh')
--severity=error --shell=bash "$script"; then
echo "::error file=$script::shellcheck error-severity issue"
fail=1
fi
done < <(find features/scripts/utils core/system/scripts \
themes/engine/scripts \
-maxdepth 1 -type f -name 'nomarchy-*')
exit "$fail" exit "$fail"
- name: docs/SCRIPTS.md is up to date # ── vm-checks (DISABLED until a KVM runner exists) ────────────────────
run: | # The real prize: running the checks.* VM suite + a toplevel build in
# Regenerate to a temp file and compare. If different, the # CI. Needs a runner on a NixOS (or at least nix + /dev/kvm) host —
# contributor forgot to run the generator (or skipped the # register one with a dedicated label, then uncomment and set runs-on
# pre-commit hook). Fail loudly and tell them the fix. # to that label. Do NOT enable this against a label that doesn't
./bin/utils/nomarchy-docs-scripts --out /tmp/SCRIPTS.regen.md # exist: Gitea queues such jobs forever instead of failing.
if ! diff -q docs/SCRIPTS.md /tmp/SCRIPTS.regen.md >/dev/null; then #
echo "::error::docs/SCRIPTS.md is stale." # vm-checks:
echo "Run: ./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md" # runs-on: nix-kvm
echo "Then commit the regenerated file." # timeout-minutes: 120
echo "--- diff ---" # steps:
diff -u docs/SCRIPTS.md /tmp/SCRIPTS.regen.md || true # - uses: actions/checkout@v4
exit 1 # - run: nix flake check # builds + runs the VM tests
fi # - run: nix build .#nixosConfigurations.nomarchy.config.system.build.toplevel --no-link
# - run: nix build .#homeConfigurations.nomarchy.activationPackage --no-link
- name: installer/hardware-db.sh references real nixos-hardware modules
run: |
# Every 4th-pipe-field in HARDWARE_DB is a nixos-hardware module
# name. Half the DB used to point at modules that don't exist
# (e.g. microsoft-surface-pro-8 — there's only -pro-intel and
# -pro-9), which made the install fail at eval time with
# cryptic "attribute not found" errors on real laptops. This
# step catches that regression class.
awk -F'|' '/^ "/ { gsub(/"/,"",$4); gsub(/^[[:space:]]+|[[:space:]]+$/,"",$4); if ($4) print $4 }' \
installer/hardware-db.sh | sort -u > /tmp/db-refs.txt
nix eval --impure --json --expr '
let
nh = (builtins.getFlake (toString ./.)).inputs.nixos-hardware.nixosModules;
in builtins.attrNames nh' \
| nix shell nixpkgs#jq --command jq -r '.[]' | sort -u > /tmp/db-real.txt
missing=$(comm -23 /tmp/db-refs.txt /tmp/db-real.txt)
if [[ -n "$missing" ]]; then
echo "::error::hardware-db.sh references nixos-hardware modules that don't exist:"
printf ' - %s\n' $missing
echo "Either fix the name (check the actual attr in nixos-hardware) or drop the row."
exit 1
fi

View File

@@ -1,54 +0,0 @@
#!/usr/bin/env bash
# Nomarchy pre-commit hook.
#
# Enable per-clone with:
# git config core.hooksPath .githooks
#
# Two responsibilities:
# 1. Lint changed nomarchy-* scripts (bash -n + shellcheck if available)
# so syntax errors and unquoted-var bugs don't ship.
# 2. Regenerate docs/SCRIPTS.md when any nomarchy-* script under the three
# script directories is added, modified, or deleted in this commit, and
# stage the refreshed file so it lands with the change.
set -e
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
script_dirs_re='^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/nomarchy-'
# 1. Lint changed scripts. bash -n catches syntax errors (always fatal).
# shellcheck catches unquoted-var, use-before-define, missing-shebang, etc.
# We only fail on severity=error so the long tail of pre-existing warnings
# (info / style / warning) doesn't block commits — those can be cleaned up
# incrementally without a flag day.
changed_scripts=$(git diff --cached --name-only --diff-filter=ACMR \
| grep -E "$script_dirs_re" || true)
if [[ -n "$changed_scripts" ]]; then
while IFS= read -r script; do
[[ -f "$script" ]] || continue
# Only lint scripts with a bash shebang. nomarchy-* is a name
# convention, not a language guarantee — at least one Python helper
# ships under the same prefix (nomarchy-haptic-touchpad).
head -1 "$script" | grep -qE '^#!.*\bbash\b' || continue
if ! bash -n "$script"; then
echo "pre-commit: bash syntax error in $script — aborting commit." >&2
exit 1
fi
if command -v shellcheck >/dev/null 2>&1; then
if ! shellcheck --severity=error --shell=bash "$script"; then
echo "pre-commit: shellcheck found error-level issues in $script — aborting commit." >&2
echo "pre-commit: fix the reported issues, or rerun with --no-verify after a deliberate decision to ship." >&2
exit 1
fi
fi
done <<< "$changed_scripts"
fi
# 2. Regenerate the script audit doc.
if git diff --cached --name-only --diff-filter=ACMRD | grep -qE "$script_dirs_re"; then
echo "pre-commit: regenerating docs/SCRIPTS.md (script change detected)…"
./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md
git add docs/SCRIPTS.md
fi

21
.gitignore vendored
View File

@@ -1,15 +1,14 @@
# ---> Nix # Study material from the previous iteration — not part of the flake.
# Ignore build outputs from performing a nix-build or `nix build` command # Delete the directory once everything worth porting has been ported.
old_distro/
# Nix build artifacts
result result
result-* result-*
# Ignore automatically generated direnv output __pycache__/
.direnv .DS_Store
# Local IDE settings # Claude Code machine-local settings (permissions etc.)
.claude/ .claude/settings.local.json
.claude/skills/
# VM and ISO artifacts
*.qcow2
*.qcow
*.iso

42
CLAUDE.md Normal file
View File

@@ -0,0 +1,42 @@
# Nomarchy — agent entry point
Nomarchy is a NixOS-based distro: rock-stable, fully reproducible, themed
from one JSON, configured through a menu that writes into the user's own
flake checkout. Read the README for the architecture.
## If you're here to work autonomously (the loop)
Follow **`agent/LOOP.md`** — it defines one iteration: orient
(GOALS/BACKLOG/JOURNAL/MEMORY), take the top actionable BACKLOG item,
verify up the V0V3 ladder, commit+push on `main`, record. All loop state
is git-tracked in `agent/`.
## Rules that apply to every session, loop or not
- **Honesty rule** (docs/TESTING.md): for visual/interactive changes,
evaluation is not rendering — state exactly what you verified and at
which tier. Cheap check first, always: `nix flake check --no-build`.
- **Conventions** (`agent/CONVENTIONS.md`): in-flake state, menu
placement, Waybar parity with the summer whole-swaps, toggle-vs-package
discipline, commented template examples for opt-ins.
- **Git:** direct commits on `main`, pushed; `v1` is the human-only
release pointer — never touch it. Never run `nix flake update` unless
the task is a lock bump. No formatter — match the aligned
hand-formatting.
- Machine-specifics live in `hosts/`, the distro in `modules/`, data in
`themes/`, code in `pkgs/`, maintainer tools in `tools/`.
## Delegation
The higher your tier, the more you delegate. Push the work down, keep your own context for judgment. Brief every child: the context, the why, what done looks like. It starts blank and inherits nothing.
| Model | Best for | Delegate? | Effort |
|---|---|---|---|
| Haiku | bulk mechanical | never | low |
| Sonnet | scoped research | when it helps | medium |
| Opus 4.8 | multi-step reasoning | on clear benefit | xhigh |
| Fable 5 | judgment, taste | by default | medium |
Fable goes xhigh only for the hardest calls. Skip high.
## Escalation
The parent doesn't have to be the top model. An Opus parent spawns a Fable child for the one hard call. The child answers and returns. Work above your tier? Return it, don't burn tokens on it.

510
README.md
View File

@@ -1,118 +1,442 @@
# 👑 Nomarchy # Nomarchy
**Nomarchy** is a professional-grade NixOS distribution that ships a highly curated Hyprland desktop on a strictly declarative, flake-based foundation. It provides a highly polished, "it just works" experience for power users who want a beautiful Wayland environment without sacrificing the reliability of NixOS. **The rock-solid reproducibility of NixOS 26.05. The out-of-the-box polish of
Omarchy/Omakub.** One JSON file rules the look of the entire desktop; every
theme change is a Home Manager generation — atomic, rollbackable, never
partial.
## ✨ Key Features
- **Purely Declarative:** Your entire desktop—themes, fonts, wallpapers, and toolchains—is defined in code.
- **Modular Merging Architecture:** Distro code and user code are strictly separated. You can update the distro core without ever touching or breaking your personal configurations.
- **Interactive "Smart" Installer:** Detects hardware, sets up networking, localizes timezones/keymaps, and offers multi-select software profiles.
- **Erase Your Darlings (Optional):** Optional BTRFS root-wipe on boot ensures your system stays pristine and 100% declarative.
- **Dynamic Theming Engine:** 20+ built-in themes with instant UI feedback via IPC (swww, waybar, and stylix).
- **Portable State (`nomarchy-sync`):** Easily backup and sync your declarative config and dynamic state (theme/wallpaper choices) to a private Git repo.
---
## 📂 Component-Based Architecture
Nomarchy uses a **Feature-Centric Directory Structure**. For a comprehensive breakdown of the system architecture, folder roles, and module logic, see the [Detailed Architecture Documentation](docs/STRUCTURE.md).
Configuration, modules, and utilities are strictly organized to maintain sanity as the system grows.
- **`core/`**: Foundational OS & User defaults (Bootloader, Audio, Bluetooth, core system features).
- **`features/`**: Isolated modules containing Nix logic and raw dotfiles.
- **`features/apps/`**: App-specific configs (e.g., `features/apps/btop/`, `features/apps/kitty/`), each containing their own `default.nix` and standalone `config/` directory mapped via Home Manager.
- **`features/desktop/`**: Desktop environment components (e.g., Hyprland, Waybar).
- **`features/scripts/utils/`**: Consolidated repository for all custom Nomarchy bash scripts, centrally packaged and injected into the user's `PATH` with correct dependencies.
- **`themes/`**: The global theming engine. It holds pure color data and logic. *Theme-specific app layouts* (like a custom Waybar layout) are stored directly inside the app's feature folder, solving the matrix problem of theming.
---
## 📥 Installation
### 1. Try it in a VM (Recommended)
Verify the experience without touching your hardware:
```bash
./bin/utils/nomarchy-test-installer
``` ```
This builds a full graphical VM of the installer environment. Once inside, click the **Install Nomarchy** icon or run `nomarchy-install`. ┌──────────────────────────────────────────────────────────────────────────┐
│ theme-state.json (single source of truth) │
### 2. Build the Installer ISO │ lives INSIDE your flake checkout, git-tracked │
To install on physical hardware, generate your own bootable image: └───────────────────────────────────┬──────────────────────────────────────┘
```bash
./features/scripts/utils/nomarchy-build-iso # Minimal TTY installer nomarchy-theme-sync apply gruvbox
./features/scripts/utils/nomarchy-build-live-iso # Graphical try-before-install 1. merges the preset into the JSON (atomic write)
2. runs `home-manager switch` (no sudo, no system rebuild)
▼ pure read (nomarchy.stateFile)
┌──────────────────────────────────────────────────────────────────────────┐
│ Home Manager bakes EVERYTHING into one read-only generation: │
│ Hyprland (colors/gaps/borders) Waybar (palette or whole-swap) │
│ Ghostty (full ANSI palette) btop (asset or generated) │
│ Stylix → GTK, Qt, cursors, fonts │
└───────────────────────────────────┬──────────────────────────────────────┘
wallpaper via awww (the one runtime piece:
applied post-switch + at session start, `bg next` cycles)
``` ```
The ISO will be located at `./result/iso/nixos-*.iso`. Flash it to a USB drive and boot.
### 3. Run the Installer ## 1. Layout
Once booted into the Live environment, launch the installer:
```bash Flat on purpose. Two module trees, one options file each, no hidden layers.
nomarchy-install
```
.
├── flake.nix # inputs + the downstream API (exports below)
├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper
├── theme-state.json # ★ THE single source of truth (git-tracked!)
├── themes/ # 21 presets: <slug>.json + optional <slug>/ assets
│ ├── nord.json # palette (required, works alone)
│ └── nord/ # assets (optional, fixed filenames)
│ ├── backgrounds/ # wallpapers (auto-picked, SUPER+SHIFT+T cycles)
│ ├── btop.theme # hand-made config drop (else generated)
│ └── waybar.css # whole-swap: replaces the generated bar style
├── modules/
│ ├── nixos/ # the distro, system side
│ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts
│ │ ├── options.nix # nomarchy.system.* toggles
│ │ ├── plymouth.nix # boot splash, tinted from the JSON
│ │ ├── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2
│ │ └── power.nix # power-profiles-daemon/TLP, thermald, charge limit
│ └── home/ # the distro, user side
│ ├── default.nix # entry point
│ ├── options.nix # nomarchy.* option surface
│ ├── theme.nix # JSON ingestion + wallpaper hook
│ ├── stylix.nix # GTK/Qt/cursors/fonts from the same JSON
│ ├── hyprland.nix # all JSON-driven
│ ├── waybar.nix
│ ├── ghostty.nix
│ ├── btop.nix
│ ├── rofi.nix # launcher + nomarchy-menu (calc, emoji, clip…)
│ ├── keybinds.nix # single source: Hyprland binds + SUPER+? sheet
│ ├── swaync.nix # notifications, same JSON
│ ├── idle.nix # hyprlock + hypridle, same JSON
│ ├── yazi.nix # flagship TUI file manager + plugins
│ ├── osd.nix # swayosd volume/brightness OSD
│ ├── shell.nix # zsh + starship + bat/eza/zoxide
│ ├── keys.nix # gpg-agent fronting SSH, pinentry-qt
│ └── fastfetch.nix # themed Nomarchy logo + system info
├── hosts/
│ ├── default/ # reference machine (thin: boot, user, hostname)
│ └── live.nix # bootable live ISO (try the distro, no install)
├── pkgs/
│ ├── nomarchy-theme-sync/ # state writer + rebuild dispatcher (Python)
│ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake)
├── templates/downstream/ # `nix flake init -t` starter for users
├── docs/TESTING.md # how to verify changes (incl. AI-agent rules)
├── docs/OVERRIDES.md # how downstream users override defaults
├── docs/RECOVERY.md # runbook: broken theme/desktop/boot → undo it
├── docs/ROADMAP.md # design/decision records + shipped-fixes log
├── agent/ # autonomous-agent loop: protocol (LOOP.md),
│ # prioritized BACKLOG, journal, memory
└── tools/ # maintainer-only
├── import-palettes.py # converts old-distro themes → JSON + assets
├── test-live-iso.sh # build the ISO + boot it in QEMU
├── test-install.sh # full offline-install regression in QEMU
└── vm/ # headless-VM helpers (QMP keys, VNC shots,
# offline-pin gap analysis)
``` ```
The wizard will guide you through:
- **Networking:** An interactive wizard to connect to Wi-Fi if needed.
- **Hardware:** Optimized profiles for Dell XPS, Framework, Apple T2, and more.
- **Storage:** Choice between Standard Ext4 or Encrypted BTRFS with optional **Impermanence**.
- **Localization:** Searchable timezones and keyboard layout selection.
### Already on NixOS? **Rule of thumb:** `modules/` is the distro (reusable, no machine specifics),
Layer Nomarchy onto an existing 25.11 install without reformatting — see the [Migration Guide](docs/MIGRATION.md). `hosts/` is a machine, `themes/` is data, `pkgs/` is code, `tools/` is
maintainer-only. If a new file doesn't obviously belong to one of those, it
probably shouldn't exist.
--- ## 2. Try it first (live ISO)
## 🛠️ Configuration & Usage Boot the full desktop from a USB stick or VM without installing anything:
Nomarchy uses a "Downstream" model. After installation, your configuration lives in `/etc/nixos/` and is split into three main parts: ```sh
nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
# → result/iso/*.iso — dd to a stick, or boot it in QEMU:
tools/test-live-iso.sh
```
### 1. Distro Core (Upstream) The live session auto-logs-in, seeds the flake at `~/.nomarchy`, and pins
Managed via the public Git repository. This is the engine. You should generally not modify files here. the locked inputs into the ISO store — so theme switching (including the
`home-manager switch` it triggers) works **offline**, exactly like on an
installed system. Verification checklist: [docs/TESTING.md](docs/TESTING.md).
Like what you see? **`nomarchy-install`** (in a terminal) walks you through
installing to disk: pick a disk, LUKS2 full-disk encryption **by default**
(in exchange the desktop logs in passwordless — the passphrase already
gates the machine), user + hostname + timezone, hardware autodetection
(DMI → nixos-hardware profile), a hibernation-ready swapfile sized to RAM,
then disko partitions (GPT + ESP + BTRFS subvolumes incl. `@snapshots`
snapper timeline snapshots are on) and `nixos-install` runs — **without a
network** when the ISO was built from a clean tree (the target's
`flake.lock` is composed from the rev the ISO carries). The installed
machine gets the standard downstream layout: the flake at `~/.nomarchy`
(`/etc/nixos` symlinks to it), one `mkFlake` call, your
`system.nix`/`home.nix`. First boot lands in the fully themed desktop —
the installer pre-activates the Home Manager generation. UEFI only for now.
## 3. Using Nomarchy on your machine (downstream)
Nomarchy is consumed as a flake input — you never fork or edit this repo:
```sh
mkdir my-machine && cd my-machine
nix flake init -t "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1"
```
The template's own README then walks the first-run steps: a real
`hardware-configuration.nix`, `git init` (flakes only see tracked
files), the two first rebuilds, and landing the checkout at
`~/.nomarchy` so the theme CLI finds it. (The installer does all of
this for you.)
You own two files day-to-day: `system.nix` and `home.nix` (plus
`theme-state.json`, written by the CLI). Your `flake.nix` is set up once —
later by the installer — and never hand-edited; it's a single call:
### 2. Your System (`system.nix`)
Add system-wide packages, services, and hardware tweaks here.
*Example: Adding a persistent directory for Docker if using Impermanence:*
```nix ```nix
environment.persistence."/persist" = { outputs = { nomarchy, ... }:
directories = [ "/var/lib/docker" ]; nomarchy.lib.mkFlake {
}; src = ./.;
username = "me";
hardwareProfile = "framework-13-7040-amd"; # optional, nixos-hardware name
};
``` ```
### 3. Your User Environment (`home.nix`) | `mkFlake` arg | Default | Purpose |
Add user-level packages, aliases, and dotfiles here. |---|---|---|
*Example: Overriding the default terminal:* | `src` | — (required) | Your flake directory (`./.`) |
```nix | `username` | — (required) | Login name; flows into `system.nix` and names the HM config |
nomarchy.home.terminal = "kitty"; | `hardwareProfile` | `null` | One [nixos-hardware](https://github.com/NixOS/nixos-hardware) module name, or a list of them (pinned + tested by Nomarchy; unknown names fail with suggestions) |
| `system` | `"x86_64-linux"` | Platform |
(Power users can skip `mkFlake` and wire `nixosModules.nomarchy` /
`homeModules.nomarchy` / `overlays.default` by hand — the wrapper is sugar.)
Two deliberately separate rebuild paths:
```sh
sudo nixos-rebuild switch --flake .#default # system: rare
home-manager switch --flake .#me # desktop: every theme change, no sudo
``` ```
For the full list of `nomarchy.*` options you can set in `system.nix` and `home.nix`, see the [Options Reference](docs/OPTIONS.md). For custom automation, see [Runtime Hooks](docs/HOOKS.md). Hit a rebuild error? Check [Troubleshooting](docs/TROUBLESHOOTING.md). For where the project is heading next, see the [Roadmap](docs/ROADMAP.md). Day-to-day you'll use the shipped shortcuts instead:
### Applying Changes ```sh
After editing your files, apply them instantly. **IMPORTANT:** Nomarchy requires the `--impure` flag for evaluation. You **MUST** use the following aliases rather than standard NixOS commands: sys-update # nix flake update + system rebuild (BTRFS snapshot first when available)
sys-rebuild # system rebuild against the CURRENT lock (config changes only, no update)
```bash # …both end with a package-level diff of what the rebuild changed (nvd)
sys-update # Rebuilds the NixOS system (Runs: sudo nixos-rebuild switch --flake .#default --impure) home-update # home-manager switch (no flake update, no sudo)
env-update # Reloads your Home Manager environment (Runs: home-manager switch --flake .#default --impure)
``` ```
--- **Order matters when pulling distro updates.** `home-update` does *not*
touch the lock — it rebuilds the desktop against the **current**
`flake.lock`. A new Nomarchy revision (new keybinds, theming, modules)
arrives only when the lock is updated, which `sys-update` does
(`nix flake update`). So to pull an update that affects the desktop layer:
run `sys-update` **first** (updates the lock + rebuilds the system), **then**
`home-update` (re-applies the desktop against the new lock). Doing them in
the other order rebuilds the desktop against the *old* inputs and silently
skips the new home-side changes. After a home-side keybind/config change,
also `hyprctl reload` (or relogin) so the running session re-reads it.
## 🚀 Commands & Keybindings Override anything via the `nomarchy.*` surface or plain NixOS/HM options:
appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`,
behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
assignment wins, and bind/exec-once lists concatenate. Full guide with
examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
The full list lives in [`docs/KEYBINDINGS.md`](docs/KEYBINDINGS.md) (auto-generated from the Hyprland configs). A few highlights: **Where each option goes.** `nomarchy.system.*`, `nomarchy.hardware.*`, and
`nomarchy.services.*` are NixOS options — set them in `system.nix`. Everything
else under `nomarchy.*` is a Home Manager option — set it in `home.nix`. The
two tables below are split along exactly that line.
| Keybinding | Action | **`home.nix`** (Home Manager — the desktop):
| :--- | :--- |
| `Super + Space` | **App Launcher** (Walker) |
| `Super + Shift + Space` | **Nomarchy Menu** (Walker) |
| `Super + Alt + Space` | **Toggle Top Bar** (Waybar) |
| `Super + Return` | Open Terminal |
| `Super + Q` | Close Window |
### Utility Scripts | Option | Default | Purpose |
Nomarchy includes dozens of productivity scripts available in your PATH. Some highlights: |---|---|---|
- `nomarchy-sync push <repo>`: Backup your setup to Git. | `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
- `nomarchy-theme-bg-next`: Cycle to the next wallpaper in the current theme. | `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
- `nomarchy-menu`: The central hub for all utilities and pickers. | `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
| `nomarchy.keyboard.layouts` | `[]` | Extra candidate layouts; when set, a watcher prompts (rofi) for a layout on a newly-connected keyboard and remembers it per-device |
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day; off by default — enable it from the menu (System Night light; the first enable rebuilds), then toggle on/off instantly from the menu or the Waybar moon indicator (writes `settings.nightlight.on` in your flake, no rebuild) and it survives reboot, so it stays reproducible |
| `nomarchy.updates.enable` | `false` | Passive update awareness: a background check (`.interval`, default daily) that flags when flake inputs (nixpkgs, the Nomarchy input, …) are behind upstream — and, with Flatpak on, when apps have updates (`.flatpak`) — via a Waybar indicator + notification. Never changes anything; click the indicator to run the upgrade flow |
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
| `nomarchy.swaync.enable` | `true` | swaync notifications, themed |
| `nomarchy.batteryNotify.enable` | `true` | Low-battery toasts at the bar's thresholds — 25% low, 10% critical (stays up until dismissed); silent no-op on machines without a battery |
| `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) |
| `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins |
| `nomarchy.osd.enable` | `true` | swayosd on-screen display for volume/brightness/mute |
| `nomarchy.shell.enable` | `true` | zsh + starship prompt + bat/eza/zoxide (zsh is the default login shell) |
| `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty |
| `nomarchy.btop.enable` | `true` | btop with per-theme colors |
| `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming |
| `nomarchy.fastfetch.enable` | `true` | fastfetch fronted by the themed Nomarchy logo |
| `nomarchy.keys.enable` | `true` | gpg-agent fronting SSH + pinentry-qt |
| `nomarchy.displays.enable` | `true` | nwg-displays interactive monitor arranger (helper for `nomarchy.monitors`) |
| `nomarchy.viewers.enable` | `true` | Document/image viewers: zathura (Stylix-themed PDF) + imv |
| `nomarchy.mime.enable` | `true` | Default "open with" associations (PDF/image/video/text/browser/directory); entries for apps you removed are skipped, so it degrades with the suite |
| `nomarchy.monitors` | `[]` | Declarative per-output layout → Hyprland `monitor` rules (applied on hotplug); `,preferred,auto,1` wildcard kept as fallback |
| `nomarchy.displayProfiles` | `{}` | Named layouts for the same outputs (docked/undocked/…), switched from System Display Profiles: instant via hyprctl, persisted in-flake (`settings.displayProfile`), baked over `nomarchy.monitors` at the next rebuild. The menu's Auto-switch row (`settings.displayProfileAuto`) makes plugging/unplugging outputs apply the matching profile automatically. A profile can also pin workspaces to outputs (`workspaces = { "1" = "DP-3"; }`) — moved instantly on switch, baked as Hyprland `workspace` rules |
| `nomarchy.launchOrFocus` | `[]` | Launch-or-focus binds: `SUPER+<key>` focuses the app's window (case-insensitive class match) or launches it; entries land in the SUPER+? cheatsheet, and a bind whose app was removed notifies instead of failing silently |
| `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed |
| `nomarchy.package` | overlay's `nomarchy-theme-sync` | The theme/state tool package, overridable if you fork it |
--- **`system.nix`** (NixOS — the machine):
*Built with ❤️ using NixOS, Hyprland, Stylix, and the Nomarchy Community.*
| Option | Default | Purpose |
|---|---|---|
| `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) |
| `nomarchy.system.stateFile` | `null` | theme-state.json for the system-side consumers (the Plymouth tint); `lib.mkFlake` wires it for you — set it only when composing the modules by hand |
| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) |
| `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet |
| `nomarchy.system.greeter.autoLogin` | `null` | Auto-login this user into Hyprland (installer sets it on LUKS machines) |
| `nomarchy.system.audio.enable` | `true` | Pipewire stack |
| `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman |
| `nomarchy.system.autoTimezone.enable` | `false` | Automatic timezone (geoclue + automatic-timezoned) — the clock follows your location; toggle from System Auto timezone (a menu enable rebuilds: it has to unset the static `time.timeZone`) |
| `nomarchy.system.snapper.enable` | `false` | Hourly/daily BTRFS timeline snapshots + `nixos-rebuild-snap` (installer enables it; no-op unless root is BTRFS) |
| `nomarchy.system.power.enable` | `true` | Active power management (see below) |
| `nomarchy.system.power.backend` | `"ppd"` | `"ppd"` (power-profiles-daemon + menu/Waybar switcher) or `"tlp"` (deeper battery tuning, no switcher) — mutually exclusive |
| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present |
| `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU |
| `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` |
| `nomarchy.hardware.intel.enable` | `false` | Intel enablement above nixos-hardware (GuC/HuC firmware via `i915.enable_guc=3``.guc` toggles just that; the installer unsets it on `xe`-driver GPUs); the installer sets it on an Intel CPU/GPU |
| `nomarchy.hardware.intel.computeRuntime` | `false` | Opt-in: Intel GPU compute — OpenCL/Level-Zero (`intel-compute-runtime`) + oneVPL (`vpl-gpu-rt`) |
| `nomarchy.hardware.amd.enable` | `false` | AMD enablement above nixos-hardware (amd-pstate EPP + radeonsi VA-API, each toggleable via `.pstate` / `.vaapi`); installer-set on an AMD CPU/GPU |
| `nomarchy.hardware.amd.rocm.enable` | `false` | Opt-in: ROCm HIP/OpenCL GPU compute (multi-GB); pair with `.gfxOverride` (e.g. `"11.0.0"`) for an unlisted iGPU |
| `nomarchy.hardware.fingerprint.enable` | `false` | fprintd for a detected fingerprint reader (installer-set); enroll with `fprintd-enroll` |
| `nomarchy.hardware.fingerprint.pam` | `false` | Opt-in: use the fingerprint for login + sudo (PAM) |
| `nomarchy.hardware.npu.enable` | `false` | Opt-in/experimental: load the on-die NPU driver (`amdxdna`/`intel_vpu`); userspace runtime is BYO |
| `nomarchy.hardware.latestKernel` | `false` | Opt-in: ship `linuxPackages_latest` instead of the default kernel — for very new hardware whose drivers landed recently |
| `nomarchy.hardware.camera.hideIrSensor` | `false` | Hide a dual-sensor webcam's IR node from PipeWire so apps only ever see the colour camera (the "second, dark Integrated Camera"); installer-set on a paired RGB+IR webcam. `/dev/video*` stays open, so Howdy-style face unlock still works; `.irMatch` overrides the IR-name regex |
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN (then `sudo tailscale up`) |
| `nomarchy.services.syncthing.enable` | `false` | Opt-in: Syncthing file sync as the login user (GUI at `127.0.0.1:8384`) |
| `nomarchy.services.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
| `nomarchy.services.flatpak.enable` | `false` | Opt-in: Flatpak + the Flathub remote |
| `nomarchy.services.pika.enable` | `false` | Opt-in: Pika Backup (GUI Borg backups) |
| `nomarchy.services.steam.enable` | `false` | Opt-in: Steam via `programs.steam` (32-bit libs, controller udev, Remote-Play ports) |
| `nomarchy.services.libvirt.enable` | `false` | Opt-in: libvirt/KVM + virt-manager GUI (login user added to `libvirtd`) |
| `nomarchy.services.obs.enable` | `false` | Opt-in: OBS Studio + a v4l2loopback virtual camera (selectable as a webcam in Zoom/Teams) |
| `nomarchy.services.docker.enable` | `false` | Opt-in: rootful Docker (user in `docker` group); don't enable alongside podman |
| `nomarchy.services.kdeconnect.enable` | `false` | Opt-in: KDE Connect phone integration (opens its firewall ports) |
| `nomarchy.services.gamemode.enable` | `false` | Opt-in: Feral GameMode performance daemon (`gamemoderun`) |
| `nomarchy.services.adb.enable` | `false` | Opt-in: Android adb/fastboot tools (systemd handles device udev rules) |
| `nomarchy.services.wireshark.enable` | `false` | Opt-in: Wireshark Qt GUI (user in `wireshark` group, capture without root) |
| `nomarchy.services.ollama.enable` | `false` | Opt-in: Ollama local LLM runtime on `127.0.0.1:11434` (CPU; GPU via `services.ollama.acceleration`) |
| `nomarchy.services.printing.enable` | `false` | Opt-in: CUPS + Avahi/mDNS network printer discovery |
| `nomarchy.services.openrgb.enable` | `false` | Opt-in: OpenRGB daemon for RGB peripheral/motherboard lighting |
| `nomarchy.services.restic.enable` | `false` | Opt-in: scheduled daily restic backup (set `.repository` + `.passwordFile`; backs up `.paths`, default `/home`; 7/4/6 retention; list/restore via the `restic-nomarchy` wrapper) |
Beyond the `nomarchy.*` surface, the system layer turns on the usual
desktop services with `lib.mkDefault` (override natively). Two worth
calling out: **`services.fwupd.enable`** is on by default for firmware
updates via LVFS — it only refreshes metadata, never flashes on its own,
so run `fwupdmgr update` to apply; disable with `services.fwupd.enable =
false;` on machines without real firmware (VMs/headless). And
**`services.earlyoom`** is on by default so running out of memory kills
the offending process (with a desktop notification) instead of freezing
the desktop — process-level on purpose, since a Hyprland session is one
cgroup and systemd-oomd would kill all of it (oomd is disabled
accordingly). Opt out with `services.earlyoom.enable = false;`.
## 4. How theming works
### Pure JSON ingestion
The trap with "read a mutable file from Nix" is pure evaluation: flakes
cannot read arbitrary `$HOME` paths without `--impure` (the old prototype
required it — never again). Nomarchy's convention: **the state file lives
inside the consuming flake** and is wired via
`nomarchy.stateFile = ./theme-state.json;`. Reading it is pure — it's flake
source. It must be git-tracked (`nomarchy-theme-sync` runs
`git add --intent-to-add` after every write as a safety net).
### One change = one generation
`nomarchy-theme-sync apply <theme>` merges the preset into the JSON and runs
`home-manager switch` (override the command with `$NOMARCHY_REBUILD`, or pass
`--no-switch` to only write). Everything is baked: Hyprland, Waybar, Ghostty,
btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and
fonts. No runtime patching means no partial states, and `home-manager
generations` is also your theme history. Waybar even restyles in place: it
re-reads `style.css` when the symlink flips.
The **wallpaper** is the one runtime piece (awww — nixpkgs' swww — is
imperative; nothing in Nix consumes the path): applied at session start and
after every switch via a tiny activation hook, cycled instantly with
`bg next`.
### Config the menu writes (not just themes)
The in-flake-state model isn't only for appearance. **Feature toggles you flip
from the menu are written into a `settings.*` section of the *same* state file**
— git-tracked, reproducible, never stashed in `~/.local/state`. The menu is just
an ergonomic writer for your flake, so version-controlling your downstream
reproduces the machine, settings and all. Where a toggle can take effect without
a rebuild it does: the menu writes the key (`--no-switch`) and flips the running
service, which reads the *live* flake state at session start, so the choice is
both instant and survives reboot.
**Night light** is the first to use this — enable it from the menu (System
Night light; the first enable rebuilds to install hyprsunset), then on/off is
instant. Internally it's two keys: `settings.nightlight.installed` (sticky —
gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the
instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern.
**Auto-commit (opt-in):** System Auto-commit makes every `apply`/`set`
mutation also `git commit` theme-state.json in your flake — *only* that
file, so unrelated dirty work is never swept up — turning your settings
history into `git log`. Off by default; the toggle is instant (nothing in
Nix consumes the flag), the off-write is itself committed so history stays
consistent, and wallpaper cycling (`bg next`) is deliberately excluded —
the current wallpaper rides along with the next real commit.
### Per-theme app assets (`themes/<slug>/`)
Recoloring covers 95% of theming; the rest is one optional assets directory
per theme — a single place to look, unlike the old distro's split:
| Asset | Mechanism |
|---|---|
| `backgrounds/` | wallpapers; empty `wallpaper` in the state means "first one"; `bg next` cycles |
| `btop.theme` | baked into the generation (generated from the palette when absent) |
| `waybar.css` | **whole-swap**: replaces the generated bar style entirely (probed at eval time, self-contained) |
| `waybar.jsonc` | whole-swap for the bar *layout* (must be plain JSON) |
| `rofi.rasi` | **whole-swap**: replaces the generated launcher/menu theme entirely |
Six ported themes ship a `waybar.css` identity (catppuccin, lumon, nord,
retro-82, summer-day, summer-night). Custom user themes can live in
`$NOMARCHY_PATH/themes/` (preset lookup) and `nomarchy.themesDir` (eval-time
asset probe).
## 5. Day-to-day
```sh
nomarchy-theme-sync list # 21 presets (nord, gruvbox, rose-pine, …)
nomarchy-theme-sync apply kanagawa # whole desktop, one generation (~a switch)
nomarchy-theme-sync set ui.gapsOut 16 # tweak one knob (also a switch)
nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuild
nomarchy-theme-sync bg auto # back to the theme's default wallpaper
nomarchy-theme-sync get colors.accent
sys-update # update inputs + rebuild the system (snapshots first)
sys-rebuild # rebuild the system, current lock (no update)
home-update # rebuild just the desktop layer
nomarchy-doctor # read-only health sheet (also: menu System Doctor)
```
Something broke anyway? Every rebuild is a generation and (on BTRFS)
every hour is a snapshot — the undo story, from a bad theme to a
machine that won't boot, is **[docs/RECOVERY.md](docs/RECOVERY.md)**.
Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme
picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu ·
`SUPER+E` file manager (yazi) · `SUPER+N` notifications · `SUPER+CTRL+V`
clipboard history · `SUPER+Q`
close · `SUPER+1..9` workspaces · `Print` region screenshot.
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
all; the curated set:
```sh
# navigation
.. ... .... # cd up 1 / 2 / 3 levels
# git
g gst # git · git status -sb
ga gaa # git add · git add -A
gc gcm # git commit · git commit -m
gco gsw gb # checkout · switch · branch
gd gds # diff · diff --staged
gl glg # log graph (last 20 · all branches)
gp gpl gf # push · pull · fetch --all --prune
# nix (system/home rebuilds keep their full sys-update / home-update names)
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
nfu nfc # nix flake update · check
nsearch ngc # nix search nixpkgs · nix-collect-garbage -d
# misc
path # print $PATH, one entry per line
reload # exec zsh (reload the shell)
```
## 6. Extending
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
plus an optional `themes/<slug>/` assets directory.
- **New themed value:** add the key to `theme-state.json` and consume it in
the Nix modules. One place — there is no second renderer to keep in sync.
- **Importing more old-distro palettes:**
`tools/import-palettes.py <palettes-dir> themes/`.
- **New opt-in feature (convention):** when a feature is off by default and
needs the user to set `nomarchy.*` options (e.g. night light, per-device
keyboard layouts, monitor layout, power management), ship a **commented**
example of it in `templates/downstream/home.nix` or `system.nix`. New
installs then enable it by uncommenting + tweaking, rather than hunting the
docs for the option name.
- **Applications:** the starter complete-workstation set (LibreOffice, VS
Code, GIMP, Inkscape) lives in the `home.packages` list in your
`home.nix` — it's a plain package list, so curate it directly: delete a
line to slim the machine, uncomment an extra (a browser, email, full TeX
Live), or add your own. No `nomarchy.apps.*` toggles — a package list is
already its own opt-out, so the distro doesn't impose these or wrap them.
## Roadmap & known issues
The prioritized queue of what's next lives in
**[agent/BACKLOG.md](agent/BACKLOG.md)**; the detailed design/decision
records and the log of shipped fixes stay in
**[docs/ROADMAP.md](docs/ROADMAP.md)**. Development runs on an
agent-driven loop — see **[agent/LOOP.md](agent/LOOP.md)**. Kept out of
the README so this stays a focused entry point.

352
agent/BACKLOG.md Normal file
View File

@@ -0,0 +1,352 @@
# Backlog — the prioritized task queue
The forward-looking half of the old docs/ROADMAP.md, reworked as a queue
agents can execute. Detailed design notes and decision records stay in
`docs/ROADMAP.md` (referenced as "ROADMAP § <item>"); this file is *what's
next, in what order*.
**Rules:**
- Agents take the topmost actionable item (see LOOP.md). Finished items
are **deleted** here — the journal + git log are the record; durable
design notes get a ✓-entry in docs/ROADMAP.md if worth keeping.
- Item numbers are **stable IDs** — never renumbered or reused. A gap in
the sequence means shipped (or dropped) work; new items take the next
free number regardless of tier.
- Tags: `[blocked:hw]` needs real hardware (see HARDWARE-QUEUE.md) ·
`[human]` needs Bernardo · `[stuck]` two failed attempts, needs help ·
`[big]` must be split before starting.
- Agents may append to **PROPOSED** and **Decisions** freely; only
Bernardo moves items *out* of PROPOSED into the tiers.
---
## NOW
*(empty — NEXT's top item is the queue head)*
## NEXT
### 36. Battery / power quick menu `[human]`
(Raised by Bernardo, 2026-07-07 — item 6.) Slice (a) — the charge-threshold
toggle in the rofi System submenu — shipped 2026-07-07 (iteration #64;
`nomarchy-menu batterylimit`, self-gated on the sysfs node, and MOVED out
of the gum control-center). Remaining:
- (b) **Battery-icon click → a dedicated power menu** offering charge
threshold + power profile. Mockup provided by Bernardo 2026-07-08 (a
`power-menu` rofi script from another system): a `rofi -dmenu` with
icon'd rows via the `\x00icon\x1f<name>` protocol — "Charge to 100%",
"Limit to 80%" (battery-*-charging-symbolic icons) + power-profile rows
(power-profile-{power-saver,performance}-symbolic), each `case` firing a
backend command + `notify-send`. The submitted script drives **TLP**
(`tlp fullcharge` / `tlp setcharge 75 80 BAT0`) and ryzenadj (ignore the
ryzenadj half). **Nomarchy adaptation:** reuse the existing writers, not
raw TLP — charge rows call `nomarchy-menu batterylimit`'s logic
(sysfs `charge_control_end_threshold` + baked `settings.power.
batteryChargeLimit`), profile rows call `power-profile`'s (PPD). Wire the
Waybar **battery module `on-click`** (generated `waybar.nix` + all four
whole-swap jsoncs — parity) to a new `nomarchy-menu powermenu`. Open
call left: whether to *replace* the separate System ▸ Battery limit /
Power profile rows or keep both surfaces. Pairs with the instant-effect
follow-up in PROPOSED (a udev grant makes the charge write no-rebuild).
### 29. Laptop docking / external-monitor UX `[human]` (needs hardware)
(Raised by Bernardo, 2026-07-07.) The shipped display application is
weak and unintuitive, and Hyprland's model for workspaces across
multiple monitors is unclear to users. Make docking a laptop to
external monitors first-class:
- **Unplug → restore the panel:** when the external monitor is removed
and the laptop's own panel is off (was the only-active output while
docked), turn the internal panel back on *immediately* — never leave
the user staring at a dead lid.
- **Plug → extend by default:** a newly connected external monitor
should *extend* the laptop panel (not mirror), out of the box.
- **HDMI audio:** an easy way to route audio to the monitor when a
display with audio (HDMI/DP) is connected — a menu toggle / quick
action, not manual pavucontrol digging.
- **Workspaces + multi-monitor:** document (cheatsheet + menu) and,
where sensible, sane defaults for how Hyprland binds workspaces to
outputs so multi-monitor behavior is predictable.
Likely a `hyprland`/`kanshi`-style profile layer plus a menu surface;
split into slices before starting. `[blocked:hw]` for the real
dock/undock + HDMI-audio verification (see HARDWARE-QUEUE.md).
### 28. Theme UI review — make every surface stunning `[big]`
(Raised by Bernardo, 2026-07-05 — placed at the head of NEXT; reorder
freely.) A thorough visual pass over the whole themed UI — Waybar (the
generated bar AND the summer whole-swaps), rofi menus, swaync,
hyprlock, the cheatsheet, ghostty/btop, GTK/Qt, boot splash, greeter —
with the bar set at *stunning*, not merely consistent (pillar 4).
Grounded in design theory, not taste alone:
- **Color:** palette-role relationships that demonstrably work
together — harmony models (analogous/complementary accents),
perceptual lightness ordering of base→surface→overlay→text (check in
OKLCH, not raw hex), accent/warn/bad distinguishable for color-blind
users; extend tools/check-theme-contrast.py to encode the adopted
rules (it currently guards legibility pairs only).
- **Typography & rhythm:** a consistent type scale across bar/menus/
OSD, spacing on the ui.gapsIn/gapsOut grid, aligned paddings (the
bar's per-module padding vs rofi rows vs swaync toasts).
- **Hierarchy:** what reads first — bar module ordering/weights, menu
title vs rows, toast title vs body; icon weights consistent with the
adjacent text.
Bernardo has offered image-expert models for designed graphics
(wallpapers, logos, glyphs, preview/branding art): an agent slice that
needs artwork must STOP and ask ("say the word") instead of shipping
programmer-art.
Slices: (a) ✓ the audit (2026-07-05, iteration #31 — findings below);
(b) palette-level fixes + the extended contrast/harmony checker —
P1s ✓ shipped 2026-07-05 (iteration #32): summer-day + flexoki-light
subtext/surface/overlay/muted retuned (flexoki to its canonical
upstream tones), miasma + kanagawa surfaces raised (kanagawa to
sumiInk4); `subtext/base ≥ 3` now GATED in check-theme-contrast.py
(all 21 pass). P2s ✓ shipped 2026-07-05 (iteration #33): 18 hex
retunes across 9 themes (OKLCH hue-preserving minimal raises;
canonical where upstream defines one — gruvbox bg4, nord #616e88,
latte overlay1, flexoki 600-series statuses for light mode) and the
four floors GATED: muted ≥ 2, text/surface ≥ 4.5, accentAlt ≥ 3,
warn ≥ 2.5 (21×7 pairings green; audit contrast findings 20 → 1 —
miasma's earthy `bad` stays as identity, good/bad floors stay
audit-only so no exemption list was needed). Remaining (b): nothing —
further palette work graduates from slices (c)/(d) findings;
(c) per-surface polish slices — capture harness ✓ shipped 2026-07-05
(iteration #34): `tools/theme-shot.nix` boots the themed desktop
headlessly at 1080p and screenshots desktop + open root menu (proven
on tokyo-night); per-surface punch items from the first full-res
review:
-**rofi keybind hints** (shipped iteration #37): dimmed pango
hints on root/Tools/System rows, generated from keybinds.nix via
the cheatsheet's prettyKeys (single source, can't drift) —
capture-verified on tokyo-night.
- **Icon weight mix `[human]`:** full-color Papirus icons in the
menus vs all-monochrome bar glyphs — pick: symbolic/monochrome
menu icons for one system, or color as the menu's identity.
A/B captured on lumon (iteration #46, side-by-side:
https://claude.ai/code/artifact/1ccd10fe-4b6f-454a-888d-e7fdf2362b03):
B (symbolic) matches the bar's weight but Papirus symbolic
coverage has holes (preferences-desktop-theme-symbolic missing —
Look & Feel lost its icon) and rofi tints symbolics a fixed dim
gray, not the theme text color — going B needs an icon audit +
recolor slice. A costs nothing. Awaiting the call; A stays until
then.
-**Bar right-cluster rhythm** (shipped iteration #36): group
margins before pulseaudio/powerprofile/battery — heads that
self-hide degrade to the uniform spacing.
-**Semantic dim** (shipped iteration #36): workspaces + dnd +
muted-volume now use `@muted` (floor-guaranteed by 28b) instead
of `alpha(@text, 0.5)` tints; the CSS comment now documents the
new invariant.
-**compositor backdrop** (shipped iteration #35): Hyprland
`misc:background_color` now the theme's base — no more black flash
behind a missing/slow wallpaper (glaring on light themes in the
summer-day capture).
- summer-day whole-swap capture reviewed (iteration #35): the
authored slate menu + island-pill bar survive the 28b retunes and
read as one coherent identity — keep. ✓ placeholder-dim nit fixed
(iteration #38: both summer rasi, bg0 at 60% alpha).
- latte capture reviewed (iteration #38): the generated menu is
fully coherent on light themes (zebra rows, retuned surface, blue
selected, dimmed hints) — the audit's "dark terminal on latte"
preview concern was a stale preview, not a defect. Backdrop
probe: misc:background_color IS applied (hyprctl getoption =
0xFFEFF1F5); captures show dark because awww's empty-state layer
paints over it in the wallpaper-less VM — on hardware the fix
covers its intended window (the pre-awww startup gap).
- ✓ Harness gap fixed (iteration #44): guest `~/.nomarchy` seeded
via tmpfiles BEFORE greetd starts the session, so the session's
own exec-once wallpaper paint runs (a post-boot testScript seed
was too late for exec-once, and a manual re-run fails without
WAYLAND_DISPLAY — awww's client socket is display-named).
Wallpaper-complete summer-night capture reviewed: lakeside night
scene + cream inverted bar + menu-over-scene all cohere; the
placeholder dim (#2d353b99) renders. Note: summer-night's cream
bar/menu on dark backdrop is the authored inverted design (fg as
surface), not a rendering bug.
- ✓ Identity-theme capture + stub purge (iteration #45): the lumon
capture exposed that catppuccin/lumon/nord/retro-82 shipped stub
waybar.css files (214 lines, from the f211ef0 rewrite import) —
and a theme waybar.css whole-swaps the ENTIRE generated
stylesheet, so those four ran an unstyled default bar. Stubs
deleted; re-capture confirms lumon falls back to the generated
styled bar (workspace pill, padded right cluster). Their full
~110-line rofi.rasi designs kept. Real whole-swaps remain only
summer-day/night (+ nord/retro-82 lost nothing: the stubs had no
design).
(d) the whole-swap themes brought to the same bar (module parity
verified textually 2026-07-05: whole-swaps carry every functional
module; omitting cpu/memory is identity). Visual work is
V3-bound by nature — screenshots are the review artifact, hardware
confirms.
**Audit findings (slice a — `tools/audit-theme-design.py` over all 21
palettes + visual pass over previews; identity-aware curation):**
- ~~P1 · invisible-secondary-text palettes~~ ✓ fixed iteration #32
(summer-day subtext #6e828a, surface #efe7d0; flexoki-light to
canonical Flexoki: surface #E6E4D9, overlay #B7B5AC, subtext
#6F6E69, muted #878580). Gate added.
- ~~P1 · inverted bg stacks~~ ✓ fixed iteration #32 (miasma surface
#2e2e2e; kanagawa surface #2a2a37 = upstream sumiInk4). Note
learned: in this palette model `overlay` is a MID-TONE
(borders/dim, catppuccin semantics), not a third bg tier — the
dark-theme overlay==muted pattern is intentional.
- **P2 · muted floor:** muted-on-base < 2.0 on gruvbox (1.27),
everforest (1.55), lumon (1.68), nord (1.69), white (1.82), latte
(1.91), retro-82 (1.93) — dimmed workspaces/inactive items barely
render (visible on the lumon preview). Adopt ≥ 2.0 and retune.
- **P2 · warn barely reads:** warn-on-base ≈ 2.02.3 on rose-pine,
summer-day, latte, flexoki-light — this is the bar's 25%-battery
color. Floor 2.5 (or see the color-only rule below).
- **P2 · text-on-surface < 4.5:** ristretto 3.88, latte 4.39 (menus
and chips draw text on surface).
- **P3 · accentAlt-on-base < 3:** latte 2.34, summer-day 2.83.
- **Design rule (systemic, beats palette surgery): status is never
color-only.** good/warn collapse under protanopia on ~half the
themes and good/bad under deuteranopia (59 pair-findings) — that's
inherent to red/green/yellow, not fixable per-palette. Battery and
recording already carry glyph changes; sweep the remaining
color-only indicators (updates count, notification dot, VPN shield
states) for shape/glyph redundancy.
- **Identity exemptions, made explicit:** vantablack + white
(deliberately hueless statuses), lumon (all-blue), hackerman
(matrix green statuses), matte-black/retro-82 (off-family hues) are
identity, not defects — slice (b)'s checker gets a per-theme
exemption list instead of silently lowered floors.
- **ANSI / greeter notes:** latte, rose-pine and white ship
`ansi[0]` LIGHTER than `ansi[15]` — decide the light-theme ANSI
convention (terminals conventionally keep black dark) before the
greeter/tty theming ships to them. tokyo-night/summer-night
`ansi[7]-on-ansi[0]` ≈ 2.83.0 = the greeter's muted text; fine as
muted, watch on hardware.
- **Visual pass (480×270 previews — too small for type/spacing; the
deep per-surface pass needs 1080p captures):** the generated bar's
right cluster runs ~9 modules with uniform spacing — no grouping
rhythm (consider micro-gaps between tray/net · stats · toggles ·
battery/notify groups); latte's preview shows a DARK terminal on
the light desktop — verify ghostty consumes the latte palette (or
the preview is stale); summer-day's island-pill whole-swap bar and
lumon's globe identity are strong — preserve through any polish.
### 14. Automated lock bumps — confirm the first real run
Slices b+c shipped 2026-07-05 (`.gitea/workflows/bump.yml`): weekly
schedule (Mon 05:17 UTC) + `workflow_dispatch` as the security
fast-lane; `nix flake update` → eval gate → pathspec-limited lock
commit pushed to `main` on green (that push triggers check.yml as the
second net). Update/commit/push logic simulated locally end-to-end in
a scratch clone, incl. a real update; today's bumped lock evals green.
Remaining (not confirmable from a session): the first scheduled or
dispatched run must land — watch that the runner picks up the cron and
that the Actions token can push. Then delete this item. Item 20's KVM
runner later upgrades the gate from eval-only to the VM suite.
### 20. KVM runner → VM suite in CI `[human]`
The remaining stretch of the CI item — checks-on-push is live and
**green** (run #58; runner = gitea/act_runner docker, eval tier).
Register a second runner on a host with `/dev/kvm` + nix (host-mode
label `nix-kvm`), then an agent uncomments the workflow's `vm-checks`
job: the `checks.*` VM suite + real toplevel/HM builds on every push
that later upgrades item 14's bump gate from eval-only to the full suite.
### 33. rofi page-overflow hint (scrollbar)
(Raised by Bernardo, 2026-07-07 — item 3.) When a list/grid overflows
onto another page, nothing signals there's more below. Add a scrollbar
(rofi's `scrollbar` element) or a "more ↓" hint, themed to the palette,
on the launcher grid and the long text menus. Check rofi 2.0.0's
scrollbar support/theming.
_Scoped 2026-07-07:_ rofi only draws the scrollbar when the list actually
overflows (`lines < rows`), so enabling it self-hides on short menus —
the right primitive. But it's a **visual, ~10-file slice**: the generated
palette theme (`rofi.nix`, `scrollbar = false`) **plus every authored
`.rasi`** (boreal/nord/lumon/kanagawa/retro-82/neon-glass/executive-slate/
summer-day/summer-night) — all deliberately set `scrollbar { width:0 }`
today, each needs a per-theme handle width/colour. Payoff is V3-only
(rendering), so it wants a themed session, not a headless edit.
### 41. Floating-window audit — remaining (needs hardware class checks)
(Raised by Bernardo, 2026-07-07 — item 11.) Broaden the `windowrule`
float set beyond the conservative first cut.
_Conservative cut shipped 2026-07-07 (iteration #63, on Bernardo's "take a
conservative approach"):_ float + center the mixer (item 35), **blueman**
(manager/adapters) and **system-config-printer** — the only shipped,
unambiguous config dialogs whose classes are confident. **Remaining
candidates, deliberately deferred because their window class can't be
verified headlessly** (a guessed class = dead rule): the polkit auth
prompt (hyprpolkitagent — no discoverable class in the package), GTK
file-chooser portals (`xdg-desktop-portal-gtk`), and any pinentry dialog.
Next slice: on hardware, run `hyprctl clients` while each is open, read
the real `class`, then append rules. Also revisit whether blueman/s-c-p
actually float once seen (regex tolerance for the `.…-wrapped` form).
## LATER
- **Wallpapers artifact split** (ROADMAP § Faster switches — decided,
deferred): pinned `Nomarchy-wallpapers` input so a state write stops
re-copying 86 MB. Follow-on: pre-built theme variants if switches are
still slow after.
- **Installer round 2** (ROADMAP § Installer): multi-disk BTRFS RAID,
impermanence, BIOS/legacy boot.
- **Boot-from-snapshot**: a systemd-boot equivalent of grub-btrfs.
- **Night-light geo mode**: lat/long auto sunset/sunrise (means wlsunset).
- **Per-theme icon overrides** / more icon packs (ROADMAP § Icon themes).
- **MIPI/IPU software-ISP camera** support (no-UVC machines).
- **VPN exit-node richer display** (country/city) (optional).
- **NixOS release bump → v2** `[human]`: deliberate, hand-edited, never
automated; the previous attempt was discarded (2026-06-22) over a
Hyprland OOM blocker — see MEMORY.md before retrying (NOW#3 should
also soften that blocker class).
## PROPOSED (agent suggestions — await human triage)
*Agents: append here with a one-paragraph pitch (what/why/cost). Do not
implement. Bernardo moves accepted items into a tier.*
- **Battery charge-limit — make the toggle instant** `[blocked:hw]`
(follow-up to the preset toggle shipped 2026-07-07, iteration #55).
The control-center now has a proper toggle (Off / 80% / 90% / 60% /
Custom…) writing `settings.power.batteryChargeLimit` — but that's a
baked NixOS option, so it only lands on the next `sys-rebuild`. To make
it instant (no rebuild): add a udev rule making
`/sys/class/power_supply/BAT*/charge_control_end_threshold`
group-writable (`MODE`/`GROUP`) so the menu can `echo N >` it live
alongside persisting state (the sysfs oneshot in power.nix still owns
boot + AC-replug re-apply), and/or surface a rofi-menu quick row or a
Waybar action. `[blocked:hw]`: the sysfs write + no-rebuild effect can
only be confirmed on a laptop exposing the threshold. Decide first
whether the small privilege grant (a local user can set the charge cap)
is acceptable.
- **Portal/Flatpak camera picker still lists the internal IR sensor**
(ROADMAP § Webcam follow-up). The shipped IR-hide is a *WirePlumber
v4l2* rule, but Flatpak/portal apps consume cameras via the
**libcamera** path, where both sensors remain visible — a Flatpak
Zoom user can still pick the black IR "camera". Options, roughly
ascending cost: (a) do nothing — document it (portal camera support
is still rare in practice); (b) a WirePlumber *libcamera* monitor
rule disabling GREY-only nodes — needs verifying that libcamera
monitor rules can match early enough (the v4l2 investigation found
only `device.api` binds pre-rule, which is why surgical scoping
failed before — same wall likely applies); (c) a libcamera
configuration/udev quirk hiding the IR sensor at the libcamera layer
itself. Cost: (b)/(c) need a T14s-style RGB+IR machine to verify →
pairs with a hardware-queue session. Recommend (a) now, (b)
investigated when the T14s is next available.
## Decisions `[human]`
Open calls only Bernardo can make; agents add options/evidence but never
decide.
- **Formatter adoption:** repo deliberately has none; `nixfmt-rfc-style`
would flatten the aligned hand-formatting of ~33 files. Adopt or
declare never?
- **Docs site vs Markdown-in-repo** (from the docs-review item).
- **zram swap:** faster under pressure and pairs with NOW#3, but it
interacts with the hibernation-swapfile story (resume device/priority
ordering) — adopt, adopt-with-hibernation-guard, or skip?
- **Default browser:** the template comments Firefox out. The shipped
mime defaults (item 8, done) point `text/html`/http(s) at
`firefox.desktop` as *inert* entries — they activate the moment
Firefox is installed and are skipped otherwise, so the remaining call
is only: ship a browser active in the suite, or stay
browserless-by-default?

70
agent/CONVENTIONS.md Normal file
View File

@@ -0,0 +1,70 @@
# Conventions — how Nomarchy code is written
The standing rules an agent must follow while coding. GOALS.md says what
we're building; this says how. Details/rationale live in docs/ROADMAP.md's
decision records and the README.
## Repo layout (the rule of thumb)
`modules/` is the distro (reusable, no machine specifics) · `hosts/` is a
machine · `themes/` is data · `pkgs/` is code · `tools/` is maintainer-only
· `agent/` is loop state. If a new file doesn't obviously belong to one of
those, it probably shouldn't exist.
## Nix style
- **No formatter.** Files use deliberate aligned hand-formatting — match
the surrounding style exactly; never reflow a file you're only touching
a line of.
- Distro defaults use `lib.mkDefault` so a plain downstream assignment
wins; bind/exec lists concatenate. Behaviour options overridable,
appearance flows from the state JSON.
- Options live in the existing surfaces: `nomarchy.system.*` /
`nomarchy.hardware.*` / `nomarchy.services.*` (NixOS, `system.nix`),
everything else `nomarchy.*` (HM, `home.nix`). Update the README tables
when the surface changes.
## Feature design
- **In-flake state:** any user-settable config gets a menu writer that
lands it in `theme-state.json` (`settings.*`), git-tracked. No
`~/.local/state`, no side files. Instant-effect where possible
(`--no-switch` + flip the service; the service reads the *live* working
tree at start — the night-light `ExecCondition` pattern). Rebuild-baked
values graduate via `mkDefault` reads of the settings key.
- **Toggle vs package:** a `nomarchy.*` toggle only when there's real
config behind it (units, groups, udev, firewall). A bare package goes
in the downstream template's `home.packages` — opt-out is deleting the
line.
- **Opt-in features** ship a commented example in
`templates/downstream/home.nix` or `system.nix`.
- **Menu:** new entries go in the right submenu (Tools / System ; root
stays six entries), end lists with the shared `↩ Back`, self-gate on
the feature's availability, and add the direct
`SUPER+CTRL+<mnemonic>` bind in `keybinds.nix` (single source — it
feeds both Hyprland and the SUPER+? cheatsheet).
- **Waybar:** new indicators self-gate (hidden when irrelevant), use
named `writeShellScriptBin`s on PATH (so static configs can exec them
by bare name), and are added to **both** the generated `waybar.nix`
config **and** every `waybar.jsonc` whole-swap — summer-day, summer-night
and executive-slate (the parity rule).
- **Theming:** every new visual surface consumes the palette from the
state JSON. There is no second renderer to keep in sync — add the key
to the JSON, consume it in the module.
## Testing
- docs/TESTING.md is canonical; LOOP.md's ladder (V0V3) sets the
required tier. Cheap first: `nix flake check --no-build`, `bash -n`,
`py_compile`.
- Prefer a permanent `checks.*` runNixOSTest over a one-off manual poke;
reusable recipes (headless Hyprland with software GL, QMP screenshots,
udev-event fakes) are indexed in MEMORY.md and demonstrated by the
existing checks (`distro-id`, `hardware-toggles`,
`battery-charge-limit`).
- The honesty rule: report exactly what you verified and at which tier.
## Git
- `main` is development (direct commits, pushed); `v1` is the release
pointer — **human-only, fast-forward-only, never touched by agents**.
- Commit style: `feat|fix|test|docs|chore(scope): summary`, body with
what/why + verification tier + what remains. Bookkeeping (`agent/`
updates) rides in the same commit as the change.
- `flake.lock` moves only when the task *is* a lock bump, and only within
the pinned release branches.

58
agent/GOALS.md Normal file
View File

@@ -0,0 +1,58 @@
# Goals — what "done" looks like for Nomarchy
The north star every loop iteration serves. When two options conflict,
the earlier pillar wins.
## The four pillars (in priority order)
1. **Rock-stable.** A workstation you never fight. Everything is a NixOS/HM
generation — atomic, rollbackable, never partial. `nix flake check` is
green at every commit on `main`. Regressions are caught by the VM-test
suite (`checks.*`), not by users. `v1` only ever advances after human
on-hardware QA.
2. **Reproducible, with zero hidden state.** The downstream flake checkout
*is* the machine. All user-settable config is menu-writable into the
git-tracked state file (`theme-state.json` `settings.*`) — never
`~/.local/state`, never `~/.config` side files. Re-cloning your flake
reproduces the machine, settings and all.
3. **Effortless to configure.** The user never has to learn Nix. Every
common knob is reachable from `nomarchy-menu` (SUPER+M); the menu is an
ergonomic writer for the flake. Where a toggle can take effect without a
rebuild, it must (`--no-switch` + flip the running service).
4. **Beautiful.** One JSON themes the entire desktop coherently — Hyprland,
Waybar, Ghostty, btop, rofi, GTK/Qt, boot splash, greeter. Every new
surface follows the palette. Informative, self-gating Waybar modules
(they hide when irrelevant). No unthemed corner survives contact with
the theme switcher.
## Quality bars (non-negotiable)
- **The honesty rule** (docs/TESTING.md): for anything visual, "the Nix
evaluates" is not "it renders". Verify at the highest tier you can reach
(see LOOP.md's verification ladder) and *state the tier you reached*.
Never claim a check you didn't run.
- **Parity rule:** any module added to the generated Waybar config must
also be added to the summer-day/night `waybar.jsonc` whole-swaps.
- **Menu placement:** new menu entries go in the right category submenu
(Tools / System ), with a direct `SUPER+CTRL+<mnemonic>` bind and
self-gating where applicable. The root picker stays six entries.
- **Opt-in features** ship a commented example in
`templates/downstream/{home,system}.nix`.
- **Option surface discipline:** a toggle exists only when there is real
config behind it. Bare package installs go in the template's
`home.packages` (opt-out = delete the line), never a `nomarchy.apps.*`.
## Non-goals (do not drift into these)
- **No binary cache.** Compile-from-source is a deliberate values call;
automation targets the config/lock channel, not artifact distribution.
- **No second theming pipeline.** The dispatcher owns menu structure; the
renderer (rofi) stays swappable. No GTK4 launcher.
- **No nixpkgs major bump on `main`.** Lock updates stay within the pinned
release branch; a release jump is a deliberate `v2`, hand-edited by the
maintainer (the last attempt was discarded over a Hyprland OOM — see
agent/MEMORY.md).
- **No repo-wide reformat.** The `.nix` files use deliberate aligned
hand-formatting; adopting a formatter is an open maintainer decision
(BACKLOG.md § Decisions), not a cleanup.
- **No multi-DE.** Hyprland is the desktop.

310
agent/HARDWARE-QUEUE.md Normal file
View File

@@ -0,0 +1,310 @@
# Hardware queue — V3 checks only a human can run
Everything shipped at V1/V2 whose final verification needs real hardware.
Agents **append** (newest at the bottom of a section) with exact steps;
Bernardo runs them and either checks off (`[x]` + date + verdict) or files
the failure as a NOW bug in BACKLOG.md. Machines: the **AMD dev box**
(Ryzen AI laptop: AMD + fingerprint + NPU), the **Latitude 5410** (Intel
QA machine), the **T14s** (webcam case).
## Any machine (dev box is fine)
- [ ] **Battery charge limit re-apply on unplug** — `sudo nixos-rebuild
switch` with `power.batteryChargeLimit = 80`, physically unplug/replug
AC, confirm `charge_control_end_threshold` re-reads 80. (udev trigger
already VM-verified.)
- [ ] **SSH_AUTH_SOCK for GUI clients** — after relogin, launch a GUI git
client (or `rofi`-launched terminal-less app) and confirm it reaches
gpg-agent's SSH socket without an interactive shell parent.
- [x] **Night-light full cycle** — first menu enable rebuilds + hyprsunset
starts; later toggles are instant (no rebuild); an *off* survives
reboot (ExecCondition); stopping hyprsunset restores gamma.
— 2026-07-04 Bernardo: PASS enable/disable on hardware
(off-survives-reboot + gamma-restore not explicitly checked).
- [x] **Auto-timezone** — enable from the menu; confirm geoclue finds the
zone, `/etc/localtime` updates, and the Waybar clock refreshes
(SIGUSR2 watcher) — also after a manual `timedatectl set-timezone`.
— 2026-07-04 Bernardo: PASS — enabled from the menu, `timedatectl`
shows Europe/London (the manual set-timezone re-trigger wasn't
exercised).
- [ ] **Keyboard layout cycle bind** — with a comma layout (e.g.
`nomarchy.keyboard.layout = "us,de"`), SUPER+SHIFT+K cycles the
focused keyboard's layout, the Waybar `󰌌` indicator follows, and
the row shows in the SUPER+? cheatsheet. — 2026-07-04 attempt:
no comma layout was configured, so the bind wasn't rendered (the
gate working as designed, not a failure) — retest after setting a
comma layout + rebuild + relogin. The SUPER+? no-op found en
route IS a real bug → BACKLOG item 26 (fix shipped — see the
dedicated SUPER+? entry below).
- [ ] **SUPER+? opens the cheatsheet** (item 32 re-fix — supersedes the
item 26 attempt) — the bind is now `$mod SHIFT, slash` (the BASE
keysym: Hyprland 0.55 resolves the sym with Shift consumed, so the
old `question` keysym never matched while Shift was down — same
pattern as the working `$mod SHIFT, 1` workspace binds). After
`home-update` + relogin (or `hyprctl reload`), SUPER+? must open the
keybindings cheatsheet, whose row still reads `SUPER + ?` (not
`SUPER + SHIFT + ?`).
- [ ] **swaync readable on summer-day** (item 25 fix) — on summer-day
after `home-update`: `notify-send "title" "body text"` shows
readable body text; open the control centre (SUPER+N), hover a
notification row and check the Clear button — all text legible
(body/buttons now @text on tinted chips, not subtext/surface).
- [ ] **Bar + rofi legible on flexoki-light** (item 27 fix) — switch to
flexoki-light: every Waybar module readable (window title, tray
row, dimmed inactive workspaces / muted volume visible-but-dim),
rofi inputbar/alternate rows show text. Spot-check one dark theme
(e.g. tokyo-night) for no visual regression in the same spots.
- [ ] **Stub-bar themes got the generated bar** (item 28, 90a5104) —
switch to catppuccin, lumon, nord and retro-82: each now shows
the generated styled bar in its own palette (workspace pill,
padded right cluster) instead of the old raw default Waybar.
Their rofi menus should look unchanged.
- [ ] **Back-audit spot-check** (item 24) — the keybinds cheatsheet
(SUPER+? / root menu Keybindings) now ends in ↩ Back and
returns to the root picker; spot a couple of submenus (Display,
VPN Tailscale) still Back correctly after the audit pass.
- [ ] **Network menu has no blank rows** (item 22 fix, needs Wi-Fi) —
after `home-update`, open Tools Network: no empty separator
rows between the ethernet/wifi/VPN sections (compact = True).
KNOWN RESIDUAL: if exactly one nameless wifi row remains showing
only security+bars, that's a hidden-SSID AP — config can't filter
it; report it and the follow-up is a small source patch skipping
empty-name APs in create_ap_list.
- [ ] **Keyboard hotplug picker (re-verify after in-flake graduation)** —
plug an external keyboard post-login, pick a layout in rofi, confirm
it applies per-device only, persists in `settings.keyboard.devices`,
and graduates into a `device{}` block on the next rebuild.
- [x] **Snapshots restore/rollback** — ⚠ PRECONDITION: update to main ≥
a47aa3a and RELOGIN first (the polkit agent starts with the session).
Bernardo's 2026-07-04 Latitude findings 5/6 ("btrfs-assistant still
crashes", "menu snapshots shows nothing") match the PRE-fix behavior
exactly: no agent → pkexec fails silently (= menu does nothing), and
a direct unprivileged run crashes (= the libbtrfsutil bug). If either
still reproduces ON THE FIXED BUILD after relogin, reopen BACKLOG
item 4 as [stuck]. Then: `nomarchy-menu snapshot` now opens
the **btrfs-assistant GUI**: a *themed* polkit prompt must appear
(hyprpolkitagent — first on-hardware outing) and the GUI must open
as root. Also exercise the fzf fallback in a terminal
(`sudo nomarchy-snapshots`): browse/diff, restore a file
(`undochange`), and a root-config rollback behind the typed-`yes`
gate. Bonus check: any other pkexec flow now prompts instead of
silently failing. — 2026-07-04 Bernardo: PASS on the fixed build —
polkit prompt appears, GUI opens as root, fzf fallback works.
Restore/rollback not exercised → residual item below.
- [ ] **Snapshots restore + rollback exercise** (residual from the item
above — the GUI/polkit half passed 2026-07-04): in
`sudo nomarchy-snapshots`, restore a single file (`undochange`)
and walk a root-config rollback up to (or through) the
typed-`yes` gate.
- [ ] **Caffeine toggle (item 13 slice)** — click 󰾪 in the bar → turns
󰅶 (warm tint on the generated bar) and hypridle must NOT lock /
blank while it's on (wait past the 5-min lock); click again
releases. Also present on both summer bars now.
- [ ] **Screen recording (item 12)** — Tools Capture: Record region
(slurp) and Record screen produce a playable .mp4 in
~/Videos/Recordings; the red ⏺ REC appears in the bar instantly
and CLICKING IT stops + notifies the path; the + audio variants
carry sound. While recording, the Capture menu offers only
"■ Stop recording". On the AMD box wl-screenrec (VAAPI) should
do the work; if it dies at start the wf-recorder fallback kicks
in silently — check the notification says which. Summer bars
have the same ⏺ (parity).
- [ ] **Doctor (item 10)** — menu System Doctor opens the sheet in
a terminal; `nomarchy-doctor` over SSH shows the same minus user
units. On a healthy machine everything is ✔/ (dev-box run
2026-07-04 correctly flagged a genuinely failed user unit).
- [ ] **Rollback menu (item 9b)** — after a couple of theme changes,
menu System Rollback: recent desktop generations listed
(newest marked current); picking an older one opens a terminal,
activates it, and the theme visibly reverts; picking the newer one
again rolls forward. The two System rows: Snapshots opens the
snapshot flow, "boot an older generation (how)" fires an
instruction notification.
- [ ] **Open-a-file smoke (viewers + mime defaults, item 8)** — after
`home-update`: from yazi/Thunar, open a PDF (→ zathura, themed to
the palette), an image (→ imv, NOT GIMP), a video (→ mpv);
`xdg-open .` on a directory → Thunar. With no browser installed,
clicking a link must still fall through to *something sane* (the
firefox.desktop entries are inert); if you've uncommented a
browser, links go there.
- [ ] **Update awareness** — with `nomarchy.updates.enable`, let the timer
fire (or start the unit): indicator appears only when inputs are
behind, notification only on count growth, click opens the upgrade
flow.
- [ ] **VPN menu live paths** — import a real WireGuard `.conf` and an
`.ovpn` via System → VPN, toggle up/down (● / ○ state), and the
Tailscale block: up/down + exit-node without sudo (operator grant).
- [ ] **Printer menu** — with `nomarchy.services.printing`, System →
Printers opens system-config-printer; add a printer, test page.
- [ ] **GRUB UEFI ISO theme render** — boot the ISO on UEFI hardware:
composed splash background, palette menu in the lower third, accent
timeout bar.
- [ ] **Visual theme pass** — live ISO: all six identity themes (bars) +
the four authored rofi `.rasi` (nord/retro-82/lumon/kanagawa) look
right, not just parse.
- [x] **Auto-commit on a real machine** — System Auto-commit toggles on
(row shows state, notification fires); a theme apply from SUPER+T then
shows a `nomarchy: apply theme …` commit in `~/.nomarchy` (`git log`);
unrelated dirty files in the checkout stay uncommitted; toggle off is
itself the last commit. Also: the "Auto timezone (on/off)" row label
now reflects the real state (the `= true` comparison fix).
— 2026-07-04 Bernardo: PASS — theme changes committed and the
toggle self-committed.
- [ ] **Display profiles, slice a (item 15)** — declare two
`nomarchy.displayProfiles` (e.g. docked disables eDP-1 + arranges
the externals; undocked re-enables it), `home-update`, then:
System Display Profiles lists them (● marks active); applying
is instant (no rebuild) and survives a relogin (the baked
overlay); "Base layout" restores; a menu resolution pick made
earlier must NOT re-enable a profile-disabled panel after the
next rebuild.
- [ ] **Display profiles workspace pins, slice c (item 15)** — give the
docked profile `workspaces = { "1" = "<ext>"; "9" = "eDP-1"; }`:
applying the profile moves open workspaces 1/9 to those outputs
immediately, new visits land there too, and the pins survive a
relogin (baked `workspace` rules). Switching to a profile without
pins leaves workspaces where they are (stale session pins are
expected until reload/rebuild — noted in the applier).
- [ ] **Display profiles auto-switch, slice b (item 15)** — same setup,
then menu Profiles Auto-switch on: plugging the dock/monitor
applies the matching profile within ~3s (toast names it);
unplugging switches back; Auto-switch off stops that; with two
profiles naming the same outputs, no flapping (ties do nothing).
- [ ] **P2 retunes eyeball (item 28b)** — spot-check on top of the P1
entry below: gruvbox/nord/lumon/everforest/retro-82/white dimmed
text (inactive workspaces, muted volume) now visible-but-dim;
latte + rose-pine(dawn) + summer-day warn/battery tint reads on
the light bases; flexoki-light statuses are the deeper canonical
600s; latte selected-row/alt-accent pink darker. Anything that
lost its "dim" feel → reopen 28b.
- [ ] **Retuned palettes eyeball (item 28b P1)** — switch through
summer-day, flexoki-light, kanagawa, miasma: chips/menus/toasts
now draw on a *raised* surface (summer-day's was slate-on-cream,
kanagawa's near-black, miasma's pure black), secondary text
(tooltips, fastfetch labels) is visible on summer-day +
flexoki-light, kanagawa floats are lighter than the bg (upstream
sumiInk4). Anything that reads worse than before → reopen 28b.
- [ ] **Clock zone tooltip (LATER item)** — hover the bar clock: the
tooltip's first line shows the zone ("BST (UTC+0100)") above the
calendar, on the generated AND summer bars; with auto-timezone
on, a zone change updates it (the SIGUSR2 reload).
- [ ] **Doctor bar tripwire (LATER item)** — with everything healthy
the bar shows nothing; `systemctl --user start doomed`-style
induced failure → within ~5 min a red appears (tooltip lists
the ✖ lines), clicking opens the sheet in a terminal;
reset-failed → it disappears on the next poll. Also visible on
both summer bars.
- [ ] **OCR region (LATER item)** — Tools Capture "OCR region →
clipboard": select a region with visible text → toast reports
the word count and `wl-paste` yields the text; Esc in slurp
cancels silently; a text-free region toasts "No text recognized"
without clobbering the clipboard.
- [ ] **Look & Feel submenu (item 19)** — root menu: "Look & Feel"
(replacing Theme, root still six rows) → Theme grid opens, Next
wallpaper cycles instantly, Night light toggles (and is GONE
from System). SUPER+T / SUPER+SHIFT+T direct binds unchanged.
- [ ] **Launch-or-focus (item 17)** — uncomment the template's
`nomarchy.launchOrFocus` firefox example (with firefox
installed), `home-update` + reload: SUPER+B launches firefox
when closed, FOCUSES the existing window when open (also from
another workspace); with firefox removed the bind fires a
"not installed" toast; the row shows under SUPER+?.
- [ ] **Themed greeter + console (item 16)** — after `sys-rebuild` +
logout: tuigreet renders in theme colors (dark container, accent
border, themed prompt/time) and a raw tty (CTRL+ALT+F2) shows the
theme's ANSI palette; on a LUKS machine the passphrase prompt
follows too. Then switch theme + `sys-rebuild` + logout → the
greeter follows the new palette. (tuigreet can't run under the
VM harness — see MEMORY.md — so rendering is hardware-tier.)
- [ ] **Color picker (item 13, final slice)** — SUPER+CTRL+P (or Tools
Color picker): hyprpicker's zoom loupe appears; click a pixel →
toast shows the hex and `wl-paste` yields it; Esc cancels with no
toast. Row present in the SUPER+? cheatsheet.
- [ ] **Low-battery toasts (item 13 slice)** — on battery, drain past
25%: one "Battery low" toast; past 10%: a critical "Battery
critical" toast that stays up until dismissed (swaync); plug in,
drain again → it re-notifies; no repeat toasts while it just keeps
draining. (Crossing logic VM-verified — this checks the real
swaync rendering in a session.)
- [ ] **Battery charge-limit toggle** (iteration #55) — control-center
(menu System Control Center System Toggles Battery Limit):
the preset picker (Off / 80% / 90% / 60% / Custom…) writes the value
(`nomarchy-theme-sync get settings.power.batteryChargeLimit` reflects
the pick); after a `sys-rebuild` the sysfs
`charge_control_end_threshold` reads it. (UX-only over the already
VM-verified writer — a session sanity pass, not a deep check.)
- [ ] **Waybar hides under fullscreen video** (item 30) — after
`home-update` + relogin, put a browser video (YouTube) into
fullscreen (F): the bar is now *covered* by the video, not drawn
on top; exit fullscreen → the bar returns. Normal tiling
unchanged (the bar still reserves its space). Accepted trade-off
of `layer: bottom`: a floating window dragged over the top strip
can now overlap the bar — confirm that's the only regression.
- [ ] **Battery limit in rofi System** (iteration #64, item 36a) — after
`home-update`: menu System shows a "Battery limit" row (this laptop
exposes the threshold node); picking a preset (80/90/60/Off/Custom)
writes it (`nomarchy-theme-sync get settings.power.batteryChargeLimit`
reflects the pick) and after a `sys-rebuild` the sysfs
`charge_control_end_threshold` matches. The gum control-center no
longer lists Battery Limit (moved, not duplicated).
- [ ] **Config dialogs float** (iteration #63, item 41 cut) — after
`home-update` + relogin: open Bluetooth (blueman-manager) and, if
printing is on, System ▸ Printers (system-config-printer) — each
opens floating + centered, not tiled. If either still tiles, run
`hyprctl clients` while it's open, read the real `class`, and fix the
regex. While there, capture the polkit prompt's and a GTK file
dialog's class for the next 41 slice.
- [ ] **Right-click volume → floating mixer** (iteration #62, item 35) —
after `home-update` + relogin: right-click the Waybar volume module
opens pwvucontrol as a floating, centered window (not tiled);
left-click still mutes. Verify across a whole-swap theme too
(summer/boreal/executive-slate) since the jsoncs got the same wiring.
- [ ] **Window focus on arrows** (iteration #61) — after `home-update` +
relogin: SUPER+←/→/↑/↓ move focus between tiled windows; SUPER+H/J/K/L
no longer move focus; SUPER+? cheatsheet shows the arrow glyphs.
- [ ] **Audio opens in Amberol** (iteration #60, item 37) — after
`home-update`: double-click an mp3/flac/ogg (or `xdg-open song.mp3`)
→ it opens in Amberol, not mpv; video files still open in mpv.
(`xdg-mime query default audio/mpeg` → io.bassi.Amberol.desktop.)
- [ ] **Capture-to-file keybinds** (iteration #59, item 38) — after
`home-update` + relogin: SHIFT+Print prompts a region select then
saves a PNG under ~/Pictures/Screenshots (toast shows the path);
CTRL+Print saves the whole screen the same way; bare Print still
copies a region to the clipboard. Both new rows appear in SUPER+?.
Also (iteration #66): Tools ▸ Capture now shows the matching dim key
hint on the Region → clipboard / Region → file / Full screen → file
rows (Print / SHIFT + Print / CTRL + Print) — pango renders, no raw
`<span>` leaks.
- [ ] **rofi menu polish** (iteration #58, item 39 + #56/#57 34/40) —
after `home-update`: (a) menu System Power profile shows a
colored icon per profile (performance/balanced/power-saver), not
just text — icons *render* (not blank); (b) every submenu's ↩ Back
shows a single arrow, no double; (c) on this single-monitor box,
System Display → pick a resolution → Back returns to System
(not back into the same resolution list).
## AMD dev box only
- [ ] **AMD runtime bits** — VA-API (`vainfo` → radeonsi), amd-pstate EPP
active and PPD switching governors; opt-ins: ROCm (`rocminfo`, a GPU
PyTorch/Ollama smoke) and the XDNA NPU driver loading.
- [ ] **Fingerprint** — `fprintd-enroll` + (opt-in PAM) login/sudo.
## Latitude 5410 only
- [x] **Waybar theme-switch resilience** (finding #1 re-test, needs main ≥
the supervisor commit + relogin) — switch themes repeatedly (incl.
summer-day/night whole-swaps): the bar restarts cleanly each time;
if anything kills it, it's back within ~a second. `pgrep -f
nomarchy-waybar` shows the supervisor. — 2026-07-04 Bernardo:
PASS — theme switches clean; pkill respawn so fast the bar never
visibly disappears.
- [x] **Media keys + gestures** (from dccceb4) — volume/brightness keys
drive the OSD; touchpad gestures work. — 2026-07-04 Bernardo:
PASS, volume/brightness keys and touchpad gestures all working.
- [ ] **v1 QA batch on-hardware pass** (583708d batch was QEMU-verified) —
general smoke before the next `main → v1` promotion.
## T14s only
- [ ] **Webcam IR-hide end-to-end on Nomarchy** — installer detects the
RGB+IR pair, bakes `hardware.camera.hideIrSensor`; `wpctl status`
shows one colour source; an app picker lists one camera; Howdy-style
direct `/dev/video2` reads still work.

1253
agent/JOURNAL.md Normal file

File diff suppressed because it is too large Load Diff

147
agent/LOOP.md Normal file
View File

@@ -0,0 +1,147 @@
# The loop — autonomous iteration protocol
How an AI agent works on Nomarchy unattended. One **iteration** = pick one
task, do it, verify it, commit it, record it. The protocol is
runner-agnostic; the same iteration works under any of:
- **Interactive `/loop`** in a Claude Code session in this repo — the agent
self-paces iterations until stopped.
- **Headless** (`claude -p`, cron/systemd-timer) — one invocation runs one
iteration (or a small fixed number) and exits.
- **A fresh manual session** — a human says "do a loop iteration"; the
files below carry all the state, so any session can pick up where the
last left off.
All loop state lives in this directory, git-tracked. There is no state
outside the checkout (the distro's own philosophy, applied to its agents).
## The files
| File | Role | Who writes it |
|---|---|---|
| `GOALS.md` | North star + quality bars + non-goals | Human (agents propose edits) |
| `BACKLOG.md` | Prioritized task queue (NOW/NEXT/LATER/PROPOSED/DECISIONS) | Both — see its header rules |
| `JOURNAL.md` | Append-only iteration log | Agents |
| `MEMORY.md` | Curated durable lessons/gotchas | Agents (curated, not append-only) |
| `HARDWARE-QUEUE.md` | Pending on-hardware checks only Bernardo can run | Agents append, human checks off |
| `CONVENTIONS.md` | Repo/design conventions to follow while coding | Human (agents propose edits) |
## Model & token economy
Spend expensive tokens on judgment, not mechanics.
- **Plan and reason on the strong model.** Orientation, task selection,
design, debugging, Nix eval semantics, verification judgment, and
anything that would land in a commit unreviewed stay with the
top-tier model running the loop (Fable 5).
- **Delegate mechanical subtasks to cheaper models.** When a subtask is
fully specified and needs no design judgment — grep/audit sweeps,
README-option-table reconciliation, a repeated edit applied across
files, summarizing long logs or check output — hand it to a subagent
with a `model` override: `haiku` for search/summarize/audit, `sonnet`
for routine well-specified edits. The strong model writes the spec,
reviews the result, and owns the commit. Only delegate when writing
the spec is cheaper than doing the work — a spawned agent starts cold
and must re-derive context.
- **Read narrowly.** Step 0's list is the whole orientation read (the
*last 35 entries* of the journal, never the full file). Read large
files by section, don't re-read what's already in context, and tail
build/check logs instead of dumping them.
- **Write tersely.** Journal entries follow the template and no more;
commit bodies state what/why/tier, not a narrative.
- **Headless runners** may run whole low-stakes iterations (QA sweeps,
docs-drift passes) on a cheaper `--model`; iterations touching
`modules/` or `pkgs/` behavior keep the strong model.
## One iteration, step by step
### 0. Orient
1. Read `GOALS.md`, `CONVENTIONS.md`, `MEMORY.md`, the **last 35 entries**
of `JOURNAL.md`, and `BACKLOG.md`.
2. `git pull --ff-only` (skip silently if offline). Confirm you are on
`main` with a clean tree. **A dirty tree you didn't create → stop and
report; never stash or discard someone else's work.**
3. Sanity baseline: if the last journal entry reports a red
`nix flake check`, or you have any reason to suspect breakage, run
`nix flake check --no-build` first. **A red baseline preempts the
backlog — fixing it *is* this iteration's task.**
### 1. Pick exactly one task
- Take the **topmost actionable** item: NOW before NEXT; never LATER
unless NOW and NEXT are empty or all blocked.
- *Actionable* means: not `[blocked:hw]` (those wait in
`HARDWARE-QUEUE.md`), not `[human]` (decisions), and small enough to
finish + verify in one iteration. If the top item is too big, **split
it in BACKLOG.md** (that edit is part of the iteration) and take the
first slice.
- Never implement anything from **PROPOSED** — those await human triage.
- If nothing is actionable, do a **QA sweep** instead: run the full check
suite, hunt drift (README option tables vs the live `nomarchy.*`
surface, template drift, dead code), deepen a VM test, or research and
write up a PROPOSED item. An iteration that only improves the backlog
is a valid iteration. If even that yields nothing, journal it and stop
— do not manufacture churn.
### 2. Work
- Keep the diff focused on the task. Unrelated fixes you trip over become
PROPOSED/NOW entries, not scope creep.
- Mechanical, fully-specified sub-steps go to a cheaper model
(see *Model & token economy* above); design and review stay here.
- Follow `CONVENTIONS.md`. Match the surrounding hand-formatting; never
run a formatter.
- New gotcha discovered the hard way → one line in `MEMORY.md` now, while
it's fresh.
### 3. Verify — the ladder
Climb as high as the change warrants and your environment allows; **record
the tier reached** in the commit body and journal entry.
| Tier | What | When required |
|---|---|---|
| **V0** | `nix flake check --no-build` (+ `bash -n` / `py_compile` for scripts) | Every change, no exceptions |
| **V1** | Build the touched output: `system.build.toplevel`, the HM generation, the ISO, or the package | Anything beyond docs/comments |
| **V2** | VM: a `checks.*` runNixOSTest (add one if the change is guardable), or boot `tools/test-live-iso.sh` / `tools/test-install.sh` | Behavioral changes — services, boot, installer, session |
| **V3** | Real hardware | Cannot be done by the agent → append to `HARDWARE-QUEUE.md` with exact test steps |
The honesty rule governs: a visual/interactive change verified only to V1
is **not done** — it ships as "V1-verified, V2/V3 pending" with the pending
check queued. Prefer *adding a permanent `checks.*` test* over a one-off
manual VM poke when the behavior is testable headlessly (see MEMORY.md for
the reusable recipes).
### 4. Commit + push
- Style: match the log — `feat(scope): …`, `fix(scope): …`,
`test(scope): …`, `docs(scope): …`. Body explains what/why + the
verification tier reached and what remains.
- Include the `agent/` bookkeeping updates (backlog/journal/memory/queue)
**in the same commit** as the change they describe.
- Commit directly on `main` and `git push` (Bernardo's standing workflow).
- **Never:** force-push; touch the `v1` branch or any branch/tag you
didn't create; commit secrets or binaries; run `nix flake update`
unless the task is explicitly a lock bump; delete themes, wallpapers,
or user-facing assets without the backlog saying so.
### 5. Record
1. Mark the task in `BACKLOG.md` (move to its ✓ line or delete, per that
file's rules).
2. Append a `JOURNAL.md` entry (template in that file).
3. Queue any V3 checks in `HARDWARE-QUEUE.md`.
### 6. Pace (self-paced runners only)
Under `/loop`, continue to the next iteration while tasks remain
actionable and checks stay green. Stop the loop when: nothing is
actionable, the same task has failed twice (journal the failure analysis
and mark the item `[stuck]`), or a `[human]` decision blocks everything
remaining.
## Stop-and-escalate conditions (any runner)
Write a journal entry + a BACKLOG note, then stop, when:
- A fix would require touching `v1`, force-pushing, or a nixpkgs release
jump.
- The working tree contains uncommitted work you didn't create.
- A task turns out to need a design decision Bernardo hasn't made → move
it to **Decisions** in BACKLOG.md with the options laid out.
- Two consecutive iterations failed on the same task (`[stuck]`).
- Anything would delete or rewrite user data, git history, or the state
file schema in a non-backward-compatible way.

102
agent/MEMORY.md Normal file
View File

@@ -0,0 +1,102 @@
# Memory — durable lessons, learned the hard way
Curated, not append-only: one line per fact, newest at the top of its
section; delete entries that stop being true. Details usually live in a
docs/ROADMAP.md decision record — pointer given as (§ item). Add a fact
here the moment a debugging session teaches you something a future
iteration would otherwise rediscover.
## Testing & VM recipes
- **tuigreet dies silently under runNixOSTest** (even bare, no theme
flag: greetd sits as "(greetd)" with no child, nothing in the
journal — its stderr goes to the VT) — nixpkgs' own greetd test uses
agreety instead. Greeter *rendering* is interactive-ISO/hardware
tier; don't burn another session on a checks.greeter VM test.
- In VM tests `pgrep -f PATTERN` can match the test backdoor's own
`bash -c` wrapper (the pattern is in its cmdline) — use `pgrep -x`
or a `[t]uigreet`-style bracket pattern.
- A checks.* fixture CANNOT be a writeText/toFile state file read at
eval time ("path … is not valid" — flake check's eval store won't
realise it): extract the logic into a pure importable file and
unit-test THAT (monitor-rules.nix / checks.display-profiles is the
pattern).
- CI (`.gitea/workflows/check.yml`) is **eval-tier only**: the act_runner
is a docker container (no systemd, no /dev/kvm). Container gotchas are
documented in the workflow header (single-user Nix + nixbld users,
`sandbox=false` for Stylix IFD, Nix pinned 2.31.5 vs lazy-trees, no JS
actions past node20) — learned over the legacy repo's 57 runs; read
them before touching the workflow.
- The Gitea instance is **1.25.4**`on: schedule` workflows are
supported; bump.yml assumes the Actions token can push to `main`
(standard Gitea behaviour, but unconfirmed until the first run lands).
- The git server is **Gitea** (gitea/act_runner via docker-compose), NOT
Forgejo — workflows are read from `.gitea/workflows/` (or `.github/`),
never `.forgejo/workflows/` (a whole push cycle was lost to that).
- Reusable headless VM harness: `checks.*` via runNixOSTest — existing
examples to crib from: `distro-id` (boots + `switch-to-configuration
dry-activate`), `hardware-toggles` (kernel cmdline/PAM assertions),
`battery-charge-limit` (fake Mains adapter via `test_power`, real udev
uevent, `InvocationID` change proves the restart).
- Themed-desktop screenshots work headlessly: software-GL Hyprland
(`LIBGL_ALWAYS_SOFTWARE` on virtio-gpu) + `machine.screenshot()` QMP
dump — prototyped 2026-06-19, kept as the fallback for theme previews
(§ Visual theme picker).
- Hyprland/Ghostty need guest GL (`virtio-vga-gl`, `gl=on`) in
interactive QEMU or the session won't start; black screen ≈ missing GL
(docs/TESTING.md § gotchas).
- No KVM = slow, not broken; don't read slowness as failure.
## Known-broken / watchlist
- **btrfs-assistant "segfault" was unprivileged-only** (re-diagnosed
2026-07-04): libbtrfsutil's unprivileged subvolume iteration crashes on
btrfs-progs 6.17.1 (upstream-fixed after); **as root it works**, and the
pkexec launcher runs it as root. The real distro bug was **no polkit
agent in the session** (every pkexec failed silently) — hyprpolkitagent
now ships (hyprland.nix exec-once). `checks.snapshot-gui` guards the
root path. Lesson: before "app X is broken", check WHO it runs as — and
whether polkit prompts can render at all (§ Snapshot browse/restore).
- **NixOS release bump is a trap:** the discarded attempt
(branch deleted 2026-06-22) hit a Hyprland OOM blocker; a redo is a
deliberate `v2`, never part of routine lock bumps.
- `theme-state.json` is git-tracked inside an 86 MB flake tree, so every
state write re-copies the source before eval — the wallpapers-artifact
split (BACKLOG LATER) is the decided fix (§ Faster switches).
## Gotchas (cost a debugging session once)
- Waybar `layer: top` renders above **even real-fullscreen windows** — the
bar draws over a fullscreen video. `layer: bottom` lets the fullscreen
surface cover it while the exclusive zone still reserves the bar's space
in normal tiling (trade-off: floating windows can now overlap the bar
strip). Set in both waybar.nix and every whole-swap jsonc (item 30).
- Hyprland binds match the exact modmask: a shifted keysym (`question`)
needs SHIFT in `mods` or the bind never fires — the keypress falls
through to the focused window (§ item 26; caught on hardware, invisible
to eval-tier tests).
- Never kill a Wayland session-lock client (hyprlock): its crash
failsafe drops to a tty instead of unlocking (§ Hibernate
double-unlock).
- rofi `element-icon size` is one value = a square cell; `WxH` silently
collapses and non-square icons letterbox — pre-crop images square at
build (§ Visual theme picker).
- WirePlumber 0.5 monitor rules can only early-match `device.api`;
`device.product.name` etc. bind *after* the rule runs — surgical
libcamera scoping is impossible (§ Webcam).
- `hyprctl switchxkblayout` is a *global* layout flip; per-device isolation
needs `device[<name>]:kb_layout` keywords (§ Keyboard layouts).
- Waybar's clock captures the timezone at construction — a zone change
needs SIGUSR2 (watcher in `timezone.nix`) (§ Automatic timezone).
- Waybar `persistent_workspaces` (underscore) is dead syntax silently
ignored; the hyphen form is honoured and renders phantom workspaces
(§ Waybar shows non-existent workspaces).
- GTK4/libadwaita/Qt6 read light/dark from the portal's
`org.freedesktop.appearance color-scheme` (dconf), not Stylix polarity
(§ GTK/Qt ignore the theme's mode).
- Update order matters downstream: `sys-update` (lock) before
`home-update`, or desktop changes are silently skipped against the old
lock (README § 3).
- grub `loadfont`s every `.pf2` in a theme dir — reuse a bundled DejaVu
rather than shipping fonts (§ Distro branding).
- The Claude Code harness drops machine-local files into the repo
(`.claude/skills/…`) mid-session — never `git add -A` blindly; check
`git status --short` for strangers first (they're gitignored now, but
the next surprise won't be) (§ loop hygiene).

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# nomarchy-docs-keybindings
#
# Regenerates docs/KEYBINDINGS.md from the Hyprland binding files. Run from the
# repo root or anywhere — paths are resolved relative to this script.
#
# nomarchy-docs-keybindings # write to stdout
# nomarchy-docs-keybindings --out docs/KEYBINDINGS.md
#
# Source files in render order. Each entry is "<repo-relative path>|<title>".
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
sources=(
"core/home/config/nomarchy/default/hypr/bindings/utilities.conf|Utilities"
"core/home/config/nomarchy/default/hypr/bindings/tiling-v2.conf|Tiling"
"core/home/config/nomarchy/default/hypr/bindings/clipboard.conf|Clipboard"
"core/home/config/nomarchy/default/hypr/bindings/media.conf|Media keys"
"features/desktop/hyprland/config/bindings.conf|Apps & web shortcuts"
)
prettify_key() {
case "$1" in
code:10) echo "1" ;; code:11) echo "2" ;; code:12) echo "3" ;;
code:13) echo "4" ;; code:14) echo "5" ;; code:15) echo "6" ;;
code:16) echo "7" ;; code:17) echo "8" ;; code:18) echo "9" ;;
code:19) echo "0" ;;
XF86AudioRaiseVolume) echo "Volume Up" ;;
XF86AudioLowerVolume) echo "Volume Down" ;;
XF86AudioMute) echo "Mute" ;;
XF86AudioMicMute) echo "Mic Mute" ;;
XF86AudioPlay) echo "Play/Pause" ;;
XF86AudioStop) echo "Stop" ;;
XF86AudioNext) echo "Next Track" ;;
XF86AudioPrev) echo "Previous Track" ;;
XF86MonBrightnessUp) echo "Brightness Up" ;;
XF86MonBrightnessDown) echo "Brightness Down" ;;
XF86KbdBrightnessUp) echo "Kbd Brightness Up" ;;
XF86KbdBrightnessDown) echo "Kbd Brightness Down" ;;
XF86KbdLightOnOff) echo "Kbd Backlight" ;;
*) echo "$1" ;;
esac
}
trim() { sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'; }
render_section() {
local file="$1" title="$2"
[[ ! -f "$repo_root/$file" ]] && return
local rows
rows=$(grep -E '^[[:space:]]*bind[a-z]*[[:space:]]*=' "$repo_root/$file" || true)
[[ -z "$rows" ]] && return
local body=""
while IFS= read -r line; do
# Strip the "bindXXX =" prefix.
local rhs="${line#*=}"
local mods key desc
IFS=',' read -r mods key desc _ <<<"$rhs"
mods=$(printf '%s' "${mods:-}" | trim)
key=$(printf '%s' "${key:-}" | trim)
desc=$(printf '%s' "${desc:-}" | trim)
[[ -z "$desc" ]] && continue # skip non-descriptive bindings
[[ -z "$mods" ]] && mods="—"
key=$(prettify_key "$key")
body+=$(printf '| %s | %s | %s |\n' "$mods" "$key" "$desc")
body+=$'\n'
done <<<"$rows"
[[ -z "$body" ]] && return
printf '\n## %s\n\n' "$title"
printf '_Source: `%s`_\n\n' "$file"
printf '| Modifiers | Key | Action |\n'
printf '| --- | --- | --- |\n'
printf '%s' "$body"
}
main() {
cat <<'HEADER'
# Nomarchy Keybindings
Auto-generated from the Hyprland binding files. **Do not edit by hand.**
Re-run the generator after changing any `bindings/*.conf`:
```bash
./bin/utils/nomarchy-docs-keybindings --out docs/KEYBINDINGS.md
```
`SUPER` is the Meta / Win key. `code:NN` keys (X11 digit keycodes) are
shown as the digit they correspond to. Media keys (`XF86Audio*`,
`XF86MonBrightness*`, …) are prettified.
HEADER
for entry in "${sources[@]}"; do
render_section "${entry%|*}" "${entry#*|}"
done
}
out=""
if [[ "${1:-}" == "--out" ]]; then
out="${2:?--out needs a path}"; shift 2
fi
if [[ -n "$out" ]]; then
main >"$out"
else
main
fi

View File

@@ -1,288 +0,0 @@
#!/usr/bin/env bash
# Generator tolerates "no matches" exit codes from grep | sort.
# pipefail and -e off; -u stays.
set -u
# Deterministic collation regardless of the caller's locale. Without this
# `sort` orders rows and caller lists by the ambient LC_COLLATE (UTF-8 on a
# dev box, C in the CI container), so the committed doc and CI's regenerated
# copy disagree on ordering and the drift check fails on pure noise.
export LC_ALL=C
# nomarchy-docs-scripts
#
# Regenerates docs/SCRIPTS.md from the repo state. Produces:
# 1. Header + status legend + regen instructions.
# 2. Table of every nomarchy-* script (location, callers, status).
# 3. Table of every menu entry in features/scripts/utils/nomarchy-menu
# (submenu, label, target command, status).
# 4. Snapshot list of orphaned references (called somewhere, no script).
#
# Status heuristic in Phase A:
# kept — file exists AND is called from at least one *.nix / *.conf /
# shell file outside its own directory.
# unused? — file exists but no caller found. Phase B decides delete-dead
# vs intentional public API.
# missing — referenced but no file. Phase B decides port-from-omarchy
# vs delete-dead vs stub-with-notify.
#
# nomarchy-docs-scripts # write to stdout
# nomarchy-docs-scripts --out docs/SCRIPTS.md
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"
# --- Inventory -------------------------------------------------------------
# Where scripts live, in render order.
declare -A loc_label=(
["features/scripts/utils"]="features/scripts/utils"
["core/system/scripts"]="core/system/scripts"
["themes/engine/scripts"]="themes/engine/scripts"
)
script_dirs=(features/scripts/utils core/system/scripts themes/engine/scripts)
# Build name → location map (associative array of basename → repo-relative dir).
declare -A script_loc
for dir in "${script_dirs[@]}"; do
[[ -d "$dir" ]] || continue
while IFS= read -r f; do
script_loc["$(basename "$f")"]="$dir"
done < <(find "$dir" -maxdepth 1 -type f -name 'nomarchy-*')
done
# Find every nomarchy-* token referenced anywhere outside the script dirs.
# (We exclude the script files themselves so they don't list themselves as
# their own caller.)
# File types we search for references. *.md catches docs and README;
# branding/hook/extension files have varied or no extensions.
# *.lua catches elephant providers; *.ini catches mako on-button-* hooks;
# *.desktop catches MimeType-registered URL handlers.
grep_includes=(
--include='*.nix' --include='*.conf' --include='*.sh' --include='*.md'
--include='nomarchy-*' --include='*.jsonc' --include='*.json'
--include='*.toml' --include='*.ini' --include='*.lua'
--include='*.desktop' --include='*.txt' --include='*.sample'
)
search_dirs=(core features themes installer hosts bin lib README.md)
# Files whose mentions of nomarchy-* are documentation about the scripts,
# not real callers. Excluded from caller discovery so they don't promote
# every script to `kept`.
self_refs=(docs/SCRIPTS.md docs/ROADMAP.md docs/AGENT.md)
ref_files_per_cmd() {
local cmd="$1"
local self_pattern
self_pattern=$(IFS='|'; echo "${self_refs[*]}")
grep -rlE "\\b${cmd}\\b" \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE "^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/${cmd}$" \
| grep -vE "^(${self_pattern})$" \
| sort -u
}
# All distinct nomarchy-* tokens we see anywhere in the repo.
# Final char must be alphanumeric — dropping trailing-dash matches like
# `nomarchy-pkg-` that come from glob references (`for c in nomarchy-pkg-*`).
# Restrict to grep_includes so binaries / tmpfiles don't pollute the set.
# The first `grep -vE` drops lines where `nomarchy-*` is a derivation /
# tmp file / sudoers basename / systemd unit / flake output / docker
# container identifier rather than a shell invocation.
# The second `grep -vE` is a token-level safety net for prefix-only
# tokens left over from wildcards/expansions (e.g. `nomarchy-pkg-*`).
all_refs=$(grep -rhE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE \
-e '(pname|name)[[:space:]]*=[[:space:]]*"nomarchy-' \
-e '/tmp/nomarchy-' \
-e '/etc/sudoers\.d/[^"[:space:]]*nomarchy-' \
-e 'nixosConfigurations\.nomarchy-' \
-e 'packages\.[^.]+\.nomarchy-' \
-e '\./result/bin/run-nomarchy-' \
-e 'mktemp[[:space:]]+[^|]*-t[[:space:]]+nomarchy-' \
-e '(TIMER_NAME|NOPASSWD_FILE|UNIT_NAME)=.*nomarchy-' \
-e '\.services\.nomarchy-' \
-e 'docker[[:space:]]+[^|]*nomarchy-' \
| grep -oE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \
| grep -vE '^(nomarchy-launch|nomarchy-brightness|nomarchy-cmd|nomarchy-pkg|nomarchy-restart|nomarchy-toggle|nomarchy-theme|nomarchy-webapp-handler|nomarchy-font-selector|nomarchy-theme-selector|nomarchy-wallpaper-selector|nomarchy-setup|nomarchy-refresh|nomarchy-scripts|nomarchy-system-scripts|nomarchy-theme-engine-scripts)$' \
| grep -vE '^(nomarchy-plymouth|nomarchy-sddm-theme|nomarchy-live|nomarchy-rev|nomarchy-windows)$' \
| grep -vE '^(nomarchy-eval-matrix|nomarchy-docs-scripts|nomarchy-docs-keybindings)$' \
| sort -u)
# The second denylist covers identifiers whose ambiguity survives the line
# filter: `nomarchy-plymouth` / `nomarchy-sddm-theme` are Nix derivation
# names referenced as bare idents in `[...]` lists, `nomarchy-live` is an
# ISO label that shows up in comments, `nomarchy-rev` is `/etc/nomarchy-rev`
# (written by the ISO), and `nomarchy-windows` is a docker container name
# in compose heredocs.
# The third denylist: `nomarchy-eval-matrix`, `nomarchy-docs-scripts` (this
# generator), and `nomarchy-docs-keybindings` are bin/utils repo tools that
# name themselves in their own headers / AGENT.md — not user-facing scripts
# that ship in nomarchy-system-scripts.
# --- Render: header --------------------------------------------------------
main() {
cat <<'HEADER'
# Nomarchy Script & Menu Audit
Auto-generated table for [Pillar 3 of the roadmap](ROADMAP.md#3-pillar-script--menu-audit).
**Do not edit by hand.** Regenerate after script or menu changes:
```bash
./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md
```
The status column uses a Phase A heuristic — `kept` / `unused?` / `missing`.
Phase B (per-batch PRs) refines those into `port-from-omarchy`,
`delete-dead`, or `stub-with-notify` and updates the rows.
## Status legend
- `kept` — script exists and is called from somewhere outside its own directory.
- `unused?` — script exists but no caller was found. Could be dead, could be
intentional public API. Phase B triage decides.
- `missing` — referenced from code but no script file exists. Phase B triage
decides whether to port from Omarchy upstream, delete the caller, or stub
with `notify-send`.
- `port-from-omarchy` — Phase B verdict: lift the upstream Omarchy script,
rewrite for NixOS paths.
- `delete-dead` — Phase B verdict: remove and update callers.
- `stub-with-notify` — Phase B verdict: temporary `notify-send` stub.
HEADER
# --- Render: scripts table ----------------------------------------------
printf '## Scripts (%d)\n\n' "${#script_loc[@]}"
printf '| Script | Location | Callers | Status | Notes |\n'
printf '| --- | --- | --- | --- | --- |\n'
# Sort scripts by name.
for name in $(printf '%s\n' "${!script_loc[@]}" | sort); do
local dir="${script_loc[$name]}"
local callers status callers_str
callers=$(ref_files_per_cmd "$name")
if [[ -z "$callers" ]]; then
status='`unused?`'
callers_str='—'
else
status='`kept`'
# Trim caller list to 2 entries + count.
local count
count=$(printf '%s\n' "$callers" | wc -l)
if (( count > 2 )); then
callers_str=$(printf '%s\n' "$callers" | head -2 | paste -sd, -)
callers_str="$callers_str, +$((count - 2)) more"
else
callers_str=$(printf '%s\n' "$callers" | paste -sd, -)
fi
fi
printf '| `%s` | `%s` | %s | %s | |\n' \
"$name" "$dir" "$callers_str" "$status"
done
echo
# --- Render: missing references -----------------------------------------
printf '## Missing references\n\n'
printf 'Tokens grepped from `core/`, `features/`, `themes/`, `installer/`, `hosts/`, `bin/`, `lib/` that have no matching script file.\n\n'
printf '| Token | Referenced in | Status |\n'
printf '| --- | --- | --- |\n'
while IFS= read -r token; do
[[ -z "$token" ]] && continue
[[ -n "${script_loc[$token]:-}" ]] && continue
local refs
self_pattern=$(IFS='|'; echo "${self_refs[*]}")
refs=$(grep -rlE "\\b${token}\\b" \
"${grep_includes[@]}" \
"${search_dirs[@]}" 2>/dev/null \
| grep -vE "^(${self_pattern})$" \
| sort -u)
[[ -z "$refs" ]] && continue
local count refs_str
count=$(printf '%s\n' "$refs" | wc -l)
if (( count > 2 )); then
refs_str=$(printf '%s\n' "$refs" | head -2 | paste -sd, -)
refs_str="$refs_str, +$((count - 2)) more"
else
refs_str=$(printf '%s\n' "$refs" | paste -sd, -)
fi
printf '| `%s` | %s | `missing` |\n' "$token" "$refs_str"
done <<<"$all_refs"
echo
# --- Render: menu items -------------------------------------------------
printf '## Menu items\n\n'
printf 'Walked from `features/scripts/utils/nomarchy-menu`. Each `case` arm in a `show_*_menu` function becomes one row.\n\n'
printf '| Submenu | Entry | Calls | Status |\n'
printf '| --- | --- | --- | --- |\n'
awk '
/^show_[a-z_]+_menu\(\) {/ { sub(/\(\) {/, ""); current=$1; in_func=1; next }
/^[a-z_]+\(\) {/ && !/^show_/ { current=""; in_func=0; next }
/^}$/ { current=""; in_func=0; next }
!in_func { next }
/^ case \$\(menu / {
# extract the menu title between the first pair of double quotes
match($0, /menu "[^"]+" "[^"]+"/);
if (RSTART == 0) next;
title=substr($0, RSTART, RLENGTH);
# second quoted string is the option list
n=split(title, parts, "\"");
title=parts[2];
options=parts[4];
# split options on \n
split(options, opts, "\\\\n");
pending_submenu=current;
pending_title=title;
for (i=1;i<=length(opts);i++) pending_opts[i]=opts[i];
pending_count=length(opts);
next
}
/^ \*[A-Za-z]/ {
# case arm — extract pattern between the first * and the closing )
match($0, /\*[^)]*\)/);
if (RSTART == 0) next;
arm=substr($0, RSTART, RLENGTH);
gsub(/[*)]/, "", arm);
gsub(/^[[:space:]]+|[[:space:]]+$/, "", arm);
# action follows the )
rest=substr($0, RSTART+RLENGTH);
sub(/^[[:space:]]+/, "", rest);
sub(/[[:space:]]*;;[[:space:]]*$/, "", rest);
# match the first nomarchy-* token in the action
cmd=""
if (match(rest, /nomarchy-[a-z0-9-]+/)) {
cmd=substr(rest, RSTART, RLENGTH);
}
printf "%s|%s|%s\n", pending_submenu, arm, cmd;
}
' features/scripts/utils/nomarchy-menu > /tmp/nomarchy-menu-rows.$$
while IFS='|' read -r submenu entry cmd; do
[[ -z "$entry" ]] && continue
[[ "$entry" =~ ^\) ]] && continue
status='`kept`'
if [[ -n "$cmd" ]]; then
if [[ -z "${script_loc[$cmd]:-}" ]]; then
status='`missing`'
fi
else
cmd='_(inline)_'
fi
printf '| `%s` | %s | `%s` | %s |\n' "$submenu" "$entry" "$cmd" "$status"
done < /tmp/nomarchy-menu-rows.$$
rm -f /tmp/nomarchy-menu-rows.$$
echo
}
out=""
if [[ "${1:-}" == "--out" ]]; then
out="${2:?--out needs a path}"; shift 2
fi
if [[ -n "$out" ]]; then
main >"$out"
else
main
fi

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env bash
# nomarchy-eval-matrix
#
# Evaluates the opt-in nomarchy.* surface that the per-output flake check
# never touches. flake check only evaluates the default configs, so a bug
# that only fires when a toggle is flipped — a renamed option, a failed
# assertion, a stale reference — sails straight through. Two such bugs
# shipped to main before this existed (the vscode option rename and the
# impermanence systemd-stage-1 assertion); both would have been caught
# here.
#
# Cost matters: this runs in CI on a small self-hosted runner, and the
# first cut (one full `extendModules` eval per individual toggle, plus one
# per palette) did ~39 whole-system evaluations and ran ~3h. So:
#
# * Compatible toggles are COMBINED into a few configs (a failure means
# re-run the offending toggle alone to pinpoint it — see the trace hint
# at the end). laptop vs desktop formFactor conflict, so they split;
# impermanence's multi-disk variant gets its own config for the alt
# mainLuksName.
# * Palettes are checked DIRECTLY via the lib — the per-palette risk is
# the system-side Plymouth base00 → RGB math (fromHexString of 2-char
# slices in themes/engine/plymouth.nix), which is pure and needs no
# module-system instantiation. Forcing the whole palette attrset also
# catches a missing/garbled palette.
# * The standalone homeConfigurations aren't re-checked here — the
# per-output flake-check step already forces both.
#
# Net: a handful of full evals instead of ~39. Results come from
# builtins.tryEval so one failure doesn't mask the rest.
#
# nomarchy-eval-matrix # run the matrix, table + exit code
#
# Add a new opt-in option's coverage by dropping it into the relevant
# combined scenario in the `scenarios` attrset below.
set -u
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"
NIX=(nix --extra-experimental-features 'nix-command flakes')
read -r -d '' EXPR <<'NIXEOF' || true
let
f = builtins.getFlake (toString ./.);
lib = f.inputs.nixpkgs.lib;
nl = import ./lib { inherit lib; };
default = f.nixosConfigurations.default;
forced = x: (builtins.tryEval (builtins.seq x true)).success;
# Combined opt-in scenarios. Each enables as many compatible toggles as
# possible so CI does ~3 full-system evals, not one per toggle. Home-side
# options go under home-manager.users.nomarchy.nomarchy.*.
scenarios = {
# laptop formFactor + every compatible system/home toggle + impermanence.
"laptop-stack" = {
nomarchy.system = {
formFactor = "laptop";
laptop.enable = true;
accessibility.enable = true;
gaming.enable = true;
features.hybridGPU = true;
hibernation.enable = true;
virtualization.docker.enable = true;
impermanence.enable = true;
};
nomarchy.hardware.fwupd = true;
home-manager.users.nomarchy.nomarchy = {
accessibility.enable = true;
gaming.enable = true;
overrides.enable = true;
panelPosition = "bottom";
keymap = { layout = "de"; variant = "nodeadkeys"; };
toggles = { waybar = false; idle = false; nightlight = false; };
};
};
# desktop preset — conflicts with the laptop formFactor, so its own eval.
"desktop-stack" = {
nomarchy.system = { formFactor = "desktop"; desktop.enable = true; };
};
# impermanence multi-disk variant (alternate mainLuksName).
"impermanence-multi" = {
nomarchy.system.impermanence = { enable = true; mainLuksName = "crypted_main"; };
};
};
evalToplevel = mod:
forced (default.extendModules { modules = [ mod ]; }).config.system.build.toplevel.drvPath;
# base00 → the three byte values the Plymouth template substitutes; plus
# the whole palette attrset, so a missing/garbled palette is caught.
paletteOk = name:
let
p = nl.getPalette name;
b = p.base00;
bytes = [
(lib.fromHexString (lib.substring 0 2 b))
(lib.fromHexString (lib.substring 2 2 b))
(lib.fromHexString (lib.substring 4 2 b))
];
in forced (builtins.deepSeq [ p bytes ] true);
in {
scenarios = builtins.mapAttrs (_: evalToplevel) scenarios;
palettes = builtins.listToAttrs (map (n: { name = n; value = paletteOk n; }) nl.themeNames);
}
NIXEOF
echo "Evaluating combined toggle scenarios + per-palette colour math..."
json="$("${NIX[@]}" eval --impure --json --expr "$EXPR" 2>/tmp/eval-matrix.err)"
status=$?
if [[ $status -ne 0 || -z "$json" ]]; then
echo "ERROR: the matrix evaluation aborted before producing results." >&2
echo "This usually means an uncatchable error (abort/infinite recursion) in one" >&2
echo "scenario, or a syntax error in the expression. Full output:" >&2
cat /tmp/eval-matrix.err >&2
exit 1
fi
# Render each section and tally failures.
fails=0
render() {
local section="$1"
echo
echo "=== $section ==="
while IFS=$'\t' read -r name ok; do
if [[ "$ok" == "true" ]]; then
printf ' ok %s\n' "$name"
else
printf ' FAIL %s\n' "$name"
fails=$((fails + 1))
fi
done < <(echo "$json" | jq -r --arg s "$section" '.[$s] | to_entries[] | "\(.key)\t\(.value)"' | sort)
}
render scenarios
render palettes
echo
if [[ $fails -gt 0 ]]; then
echo "$fails scenario(s) failed to evaluate. A combined scenario bundles several"
echo "toggles — re-run the suspects individually for the trace, e.g.:"
echo " nix eval --impure --show-trace --expr 'let f = builtins.getFlake (toString ./.); in (f.nixosConfigurations.default.extendModules { modules = [ { <the toggle> } ]; }).config.system.build.toplevel.drvPath'"
exit 1
fi
echo "All scenarios evaluated cleanly."

View File

@@ -1,16 +0,0 @@
# About Nomarchy
Nomarchy is a highly curated, NixOS-based distribution designed for power users.
It features a customized Hyprland desktop environment with a declarative
theming engine and a suite of integrated utility scripts.
Built on a foundation of:
- NixOS (Linux)
- Hyprland (Window Manager)
- Waybar (Status Bar)
- Walker (Application Launcher & Menu)
- Stylix (Theming Engine)
Version: 2026.05.04
Docs: https://github.com/nomarchy/nomarchy/docs
Manual: nomarchy-manual (Command)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
sodipodi:docname="icon.svg"
inkscape:export-filename="logo.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.82803284"
inkscape:cx="215.57116"
inkscape:cy="452.27675"
inkscape:window-width="1025"
inkscape:window-height="1012"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g4"
transform="translate(3.1953242,-22.686801)"
style="fill:#000088;fill-opacity:1"><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 25.136891,85.823024 25.557592,210.77125 88.452409,174.38061 67.417351,160.49747 57.110174,166.38729 V 105.80633 Z"
id="path1" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 67.728991,112.41131 182.54178,185.60757 153.16137,202.85259 67.830432,148.17947 Z"
id="path2" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 139.74857,145.88014 140.00405,110.4959 54.800856,56.333749 25.675926,73.32329 Z"
id="path3" /><path
style="fill:#000088;stroke-width:0.264583;fill-opacity:1"
d="M 182.2863,172.70573 V 48.286069 l -62.59305,36.406166 20.82177,13.668277 10.21927,-5.74834 0.12774,60.165978 z"
id="path4" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,34 +0,0 @@
${2},,,
${1},, ${2},,,,,,
${1},,,,,,,, ${2},,,,,,,,,,
${1},,,,,,,,,,,,,, ${2}.,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,,. ${2},,,,,,,,,,,,,,,,,,,
${1}.,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,
${1}, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,,
${1},,, ${1},,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,, ${1}.,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,. ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,, ${2}.,,,,,,,,,,,,
${1},,,,,,,,,,,, ${1}.,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,. ${2},,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},, ${1},,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,, ${1},,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,, ${1},,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,, ${1},,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,, ${1},,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,, ${1},,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,, ${1},,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,, ${1},, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,, ${2}.,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,, ${2},,,
${1},,,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,,,,, ${1},,,,,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,,,,, ${1}.,,,,,,,,,,,,,,,,,,,
${1},,,,,,,,,,,,, ${1},,,,,,,,,,,,,,
${1},,,,,,,,,, ${1},,,,,,,,,.
${1},,,,,, ${1},,
${1},,,,

View File

@@ -1,34 +0,0 @@
,,,
,, ,,,,,,
,,,,,,,, ,,,,,,,,,,
,,,,,,,,,,,,,, .,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,
.,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,
, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,
,,, ,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,, .,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,, ,,, .,,,,,,,,,,,,
,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,
,,,,,,,,,,,,, ,, ,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,, ,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,, ,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,, ,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,, ,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,, ,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,, ,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,
,,,,,,,,,,,,, ,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,
,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,, .,,,,,
,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,, ,,,
,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,, ,,,,,,,,,,,,,,
,,,,,,,,,, ,,,,,,,,,.
,,,,,, ,,
,,,,

View File

@@ -1,11 +0,0 @@
# Nomarchy Screensaver Configuration
Nomarchy uses hyprlock for locking and hypridle for idle management.
To configure the screensaver/lock screen visuals, edit:
~/.config/hypr/hyprlock.conf
To configure idle timeouts and actions, edit:
~/.config/hypr/hypridle.conf
You can also toggle the screensaver/idle management via the Nomarchy Menu:
Trigger > Toggle > Idle Lock

View File

@@ -1,5 +0,0 @@
{ ... }:
{
imports = [ ./system ];
}

View File

@@ -1,28 +0,0 @@
{ config, lib, ... }:
let
cfg = config.nomarchy.accessibility;
in
{
config = lib.mkIf cfg.enable {
# Hyprland-side accessibility extras. The system preset
# (core/system/accessibility.nix) covers AT-SPI2 + Orca on PATH +
# XCURSOR_SIZE; this module adds the bits Hyprland reads directly.
#
# Loaded via extraConfig (mkAfter) so it merges with — and overrides
# — the templated input.conf for the repeat-rate / repeat-delay
# fields. The Orca keybinding is additive.
wayland.windowManager.hyprland.extraConfig = lib.mkAfter ''
# Accessibility slower key-repeat so holding a key isn't a
# runaway machine-gun for users with low-mobility hands.
input {
repeat_rate = 25
repeat_delay = 1000
}
# Launch the Orca screen reader. The system preset puts `orca`
# on PATH when nomarchy.system.accessibility.enable = true.
bindd = SUPER ALT, S, Launch Orca, exec, orca
'';
};
}

View File

@@ -1,39 +0,0 @@
{ config, lib, ... }:
{
programs.bash = {
enable = true;
bashrcExtra = ''
if [[ -f ~/.config/nomarchy/default/bash/rc ]]; then
source ~/.config/nomarchy/default/bash/rc
fi
'';
shellAliases = lib.mkDefault {
# File system
lsa = "ls -a";
# Directories
".." = "cd ..";
"..." = "cd ../..";
"...." = "cd ../../..";
# Tools
c = "opencode";
d = "docker";
r = "rails";
t = "tmux attach || tmux new -s Work";
# Git
g = "git";
gcm = "git commit -m";
gcam = "git commit -a -m";
gcad = "git commit -a --amend";
# NixOS commands
sys-update = "nomarchy-sys-update";
env-update = "nomarchy-env-update";
};
};
}

View File

@@ -1,4 +0,0 @@
--ozone-platform=wayland
--ozone-platform-hint=wayland
--enable-features=TouchpadOverscrollHistoryNavigation
--load-extension=~/.config/nomarchy/default/chromium/extensions/copy-url

View File

@@ -1,4 +0,0 @@
--ozone-platform=wayland
--ozone-platform-hint=wayland
--enable-features=TouchpadOverscrollHistoryNavigation
--load-extension=~/.config/nomarchy/default/chromium/extensions/copy-url

View File

@@ -1,137 +0,0 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "file",
"source": "~/.config/nomarchy/branding/icon.txt",
"color": { "1": "green", "2": "blue" },
"padding": {
"top": 2,
"right": 6,
"left": 2
}
},
"modules": [
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Hardware──────────────────────┐"
},
{
"type": "host",
"key": " PC",
"keyColor": "green"
},
{
"type": "cpu",
"key": "│ ├",
"showPeCoreCount": true,
"keyColor": "green"
},
{
"type": "gpu",
"key": "│ ├",
"detectionMethod": "pci",
"keyColor": "green"
},
{
"type": "display",
"key": "│ ├󱄄",
"keyColor": "green"
},
{
"type": "disk",
"key": "│ ├󰋊",
"keyColor": "green"
},
{
"type": "memory",
"key": "│ ├",
"keyColor": "green"
},
{
"type": "swap",
"key": "└ └󰓡 ",
"keyColor": "green"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌──────────────────────Software──────────────────────┐"
},
{
"type": "os",
"key": "\ue900 OS",
"keyColor": "blue"
},
{
"type": "kernel",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "wm",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "de",
"key": " DE",
"keyColor": "blue"
},
{
"type": "terminal",
"key": "│ ├",
"keyColor": "blue"
},
{
"type": "packages",
"key": "│ ├󰏖",
"keyColor": "blue"
},
{
"type": "wmtheme",
"key": "│ ├󰉼",
"keyColor": "blue"
},
{
"type": "command",
"key": "│ ├󰸌",
"keyColor": "blue",
"text": "theme=$(cat ~/.config/nomarchy/current/theme.name); echo -e \"$theme \\e[38m●\\e[37m●\\e[36m●\\e[35m●\\e[34m●\\e[33m●\\e[32m●\\e[31m●\""
},
{
"type": "terminalfont",
"key": "└ └",
"keyColor": "blue"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break",
{
"type": "custom",
"format": "\u001b[90m┌────────────────Age / Uptime / Update───────────────┐"
},
{
"type": "command",
"key": "󱦟 OS Age",
"keyColor": "magenta",
"text": "echo $(( ($(date +%s) - $(stat -c %W /)) / 86400 )) days"
},
{
"type": "uptime",
"key": "󱫐 Uptime",
"keyColor": "magenta"
},
{
"type": "custom",
"format": "\u001b[90m└────────────────────────────────────────────────────┘"
},
"break"
]
}

View File

@@ -1,2 +0,0 @@
TriggerKey=
PastePrimaryKey=

View File

@@ -1 +0,0 @@
Allow Overriding System XKB Settings=False

View File

@@ -1,79 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<match target="pattern">
<test name="family" qual="any">
<string>sans-serif</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>Liberation Sans</string>
</edit>
</match>
<match target="pattern">
<test name="family" qual="any">
<string>serif</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>Liberation Serif</string>
</edit>
</match>
<match target="pattern">
<test name="family" qual="any">
<string>monospace</string>
</test>
<edit name="family" mode="assign" binding="strong">
<string>JetBrainsMono Nerd Font</string>
</edit>
</match>
<alias>
<family>system-ui</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>ui-monospace</family>
<default>
<family>monospace</family>
</default>
</alias>
<alias>
<family>-apple-system</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>BlinkMacSystemFont</family>
<prefer>
<family>Liberation Sans</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
<alias>
<family>serif</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
<alias>
<family>monospace</family>
<accept>
<family>Noto Color Emoji</family>
</accept>
</alias>
</fontconfig>

View File

@@ -1,28 +0,0 @@
# See https://git-scm.com/docs/git-config
[alias]
co = checkout
br = branch
ci = commit
st = status
[init]
defaultBranch = master
[pull]
rebase = true # Rebase (instead of merge) on pull
[push]
autoSetupRemote = true # Automatically set upstream branch on push
[diff]
algorithm = histogram # Clearer diffs on moved/edited lines
colorMoved = plain # Highlight moved blocks in diffs
mnemonicPrefix = true # More intuitive refs in diff output
[commit]
verbose = true # Include diff comment in commit message template
[column]
ui = auto # Output in columns when possible
[branch]
sort = -committerdate # Sort branches by most recent commit first
[tag]
sort = -version:refname # Sort version numbers as you would expect
[rerere]
enabled = true # Record and reuse conflict resolutions
autoupdate = true # Apply stored conflict resolutions automatically

View File

@@ -1,13 +0,0 @@
[binds]
# Print the current image file
<Ctrl+p> = exec lp "$imv_current_file"
# Delete the current image and quit the viewer
<Ctrl+x> = exec rm "$imv_current_file"; quit
# Delete the current image and move to the next one
<Ctrl+Shift+X> = exec rm "$imv_current_file"; close
# Rotate the currently open image by 90 degrees
<Ctrl+r> = exec mogrify -rotate 90 "$imv_current_file"

View File

@@ -1,84 +0,0 @@
import os
import shutil
from gi import require_version
require_version("Nautilus", "4.1")
from gi.repository import GObject, Gio, Nautilus
class SendViaLocalSendAction(GObject.GObject, Nautilus.MenuProvider):
def _launch_localsend(self, paths):
command = self._resolve_command()
if not command:
return
if command[-1] == "@@":
command = command + paths + ["@@"]
else:
command = command + paths
Gio.Subprocess.new(command, Gio.SubprocessFlags.NONE)
def _resolve_command(self):
localsend = shutil.which("localsend")
if localsend:
return [localsend, "--headless", "send"]
flatpak = shutil.which("flatpak")
if flatpak and self._has_flatpak_app(flatpak, "org.localsend.localsend_app"):
return [
flatpak,
"run",
"--file-forwarding",
"org.localsend.localsend_app",
"@@",
]
return None
def _has_flatpak_app(self, flatpak, app_id):
process = Gio.Subprocess.new(
[flatpak, "info", app_id],
Gio.SubprocessFlags.STDOUT_SILENCE | Gio.SubprocessFlags.STDERR_SILENCE,
)
return process.wait_check()
def _selected_paths(self, files):
paths = []
for file in files:
location = file.get_location()
if not location:
continue
path = location.get_path()
if path and path not in paths:
paths.append(path)
return paths
def _make_item(self, paths):
label = (
"Send via LocalSend" if len(paths) == 1 else "Send selected via LocalSend"
)
item = Nautilus.MenuItem(
name="LocalSendNautilus::send_via_localsend",
label=label,
icon="localsend",
)
item.connect("activate", self._on_activate, paths)
return item
def _on_activate(self, _menu, paths):
self._launch_localsend(paths)
def get_file_items(self, *args):
files = args[0] if len(args) == 1 else args[1]
paths = self._selected_paths(files)
if not paths or not self._resolve_command():
return []
return [self._make_item(paths)]

View File

@@ -1,399 +0,0 @@
---
name: nomarchy
description: >
REQUIRED for end-user customization of Linux desktop, window manager, or system config.
Use when editing ~/.config/hypr/, ~/.config/waybar/, ~/.config/walker/,
~/.config/alacritty/, ~/.config/kitty/, ~/.config/ghostty/, ~/.config/mako/,
or ~/.config/nomarchy/. Triggers: Hyprland, window rules, animations, keybindings,
monitors, gaps, borders, blur, opacity, waybar, walker, terminal config, themes,
wallpaper, night light, idle, lock screen, screenshots, layer rules, workspace
settings, display config, and user-facing nomarchy commands. Excludes Nomarchy
source development in ~/.local/share/nomarchy/ and repo-internal workflows.
---
# Nomarchy Skill
Manage [Nomarchy](https://nomarchy.org/) Linux systems - a beautiful, modern, opinionated Arch Linux distribution with Hyprland.
This skill is for end-user customization on installed systems.
It is not for contributing to Nomarchy source code.
## When This Skill MUST Be Used
**ALWAYS invoke this skill for end-user requests involving ANY of these:**
- Editing ANY file in `~/.config/hypr/` (window rules, animations, keybindings, monitors, etc.)
- Editing ANY file in `~/.config/waybar/`, `~/.config/walker/`, `~/.config/mako/`
- Editing terminal configs (alacritty, kitty, ghostty)
- Editing ANY file in `~/.config/nomarchy/`
- Window behavior, animations, opacity, blur, gaps, borders
- Layer rules, workspace settings, display/monitor configuration
- Themes, wallpapers, fonts, appearance changes
- User-facing `nomarchy-*` commands (`nomarchy-theme-*`, `nomarchy-refresh-*`, `nomarchy-restart-*`, etc.)
- Screenshots, screen recording, night light, idle behavior, lock screen
**If you're about to edit a config file in ~/.config/ on this system, STOP and use this skill first.**
**Do NOT use this skill for Nomarchy development tasks** — editing files in `~/.local/share/nomarchy/` or modifying repo internals.
## Critical Safety Rules
**For end-user customization tasks, NEVER modify anything in `~/.local/share/nomarchy/`** - but READING is safe and encouraged.
This directory contains Nomarchy's source files managed by git. Any changes will be:
- Lost on next `nomarchy-update`
- Cause conflicts with upstream
- Break the system's update mechanism
```
~/.local/share/nomarchy/ # READ-ONLY - NEVER EDIT (reading is OK)
├── bin/ # Source scripts (symlinked to PATH)
├── config/ # Default config templates
├── themes/ # Stock themes
├── default/ # System defaults
├── migrations/ # Update migrations
└── install/ # Installation scripts
```
**Reading `~/.local/share/nomarchy/` is SAFE and useful** - do it freely to:
- Understand how nomarchy commands work: `cat $(which nomarchy-theme-set)`
- See default configs before customizing: `cat ~/.local/share/nomarchy/config/fastfetch/config.jsonc`
- Check stock theme files to copy for customization
- Reference default hyprland settings: `cat ~/.config/nomarchy/default/hypr/*`
**Always use these safe locations instead:**
- `~/.config/` - User configuration (safe to edit)
- `~/.config/nomarchy/themes/<custom-name>/` - Custom themes (must be real directories)
- `~/.config/nomarchy/hooks/` - Custom automation hooks
If the request is to develop Nomarchy itself, this skill is out of scope. Follow repository development instructions instead of this skill.
## System Architecture
Nomarchy is built on:
| Component | Purpose | Config Location |
|-----------|---------|-----------------|
| **Arch Linux** | Base OS | `/etc/`, `~/.config/` |
| **Hyprland** | Wayland compositor/WM | `~/.config/hypr/` |
| **Waybar** | Status bar | `~/.config/waybar/` |
| **Walker** | App launcher | `~/.config/walker/` |
| **Alacritty/Kitty/Ghostty** | Terminals | `~/.config/<terminal>/` |
| **Mako** | Notifications | `~/.config/mako/` |
| **SwayOSD** | On-screen display | `~/.config/swayosd/` |
## Command Discovery
Nomarchy provides ~145 commands following `nomarchy-<category>-<action>` pattern.
```bash
# List all nomarchy commands
compgen -c | grep -E '^nomarchy-' | sort -u
# Find commands by category
compgen -c | grep -E '^nomarchy-theme'
compgen -c | grep -E '^nomarchy-restart'
# Read a command's source to understand it
cat $(which nomarchy-theme-set)
```
### Command Categories
| Prefix | Purpose | Example |
|--------|---------|---------|
| `nomarchy-refresh-*` | Reset config to defaults (backs up first) | `nomarchy-refresh-waybar` |
| `nomarchy-restart-*` | Restart a service/app | `nomarchy-restart-waybar` |
| `nomarchy-toggle-*` | Toggle feature on/off | `nomarchy-toggle-nightlight` |
| `nomarchy-theme-*` | Theme management | `nomarchy-theme-set <name>` |
| `nomarchy-install-*` | Install optional software | `nomarchy-install-docker-dbs` |
| `nomarchy-launch-*` | Launch apps | `nomarchy-launch-browser` |
| `nomarchy-cmd-*` | System commands | `nomarchy-cmd-screenshot` |
| `nomarchy-pkg-*` | Package management | `nomarchy-pkg-add <pkg>` |
| `nomarchy-setup-*` | Initial setup tasks | `nomarchy-setup-fingerprint` |
| `nomarchy-update-*` | System updates | `nomarchy-update` |
## Configuration Locations
### Hyprland (Window Manager)
```
~/.config/hypr/
├── hyprland.conf # Main config (sources others)
├── bindings.conf # Keybindings
├── monitors.conf # Display configuration
├── input.conf # Keyboard/mouse settings
├── looknfeel.conf # Appearance (gaps, borders, animations)
├── envs.conf # Environment variables
├── autostart.conf # Startup applications
├── hypridle.conf # Idle behavior (screen off, lock, suspend)
├── hyprlock.conf # Lock screen appearance
└── hyprsunset.conf # Night light / blue light filter
```
**Key behaviors:**
- Hyprland auto-reloads on config save (no restart needed for most changes)
- Use `hyprctl reload` to force reload
- Use `nomarchy-refresh-hyprland` to reset to defaults
### Waybar (Status Bar)
```
~/.config/waybar/
├── config.jsonc # Bar layout and modules (JSONC format)
└── style.css # Styling
```
**Waybar does NOT auto-reload.** You MUST run `nomarchy-restart-waybar` after any config changes.
**Commands:** `nomarchy-restart-waybar`, `nomarchy-refresh-waybar`, `nomarchy-toggle-waybar`
### Terminals
```
~/.config/alacritty/alacritty.toml
~/.config/kitty/kitty.conf
~/.config/ghostty/config
```
**Command:** `nomarchy-restart-terminal`
### Other Configs
| App | Location |
|-----|----------|
| btop | `~/.config/btop/btop.conf` |
| fastfetch | `~/.config/fastfetch/config.jsonc` |
| lazygit | `~/.config/lazygit/config.yml` |
| starship | `~/.config/starship.toml` |
| git | `~/.config/git/config` |
| walker | `~/.config/walker/config.toml` |
## Safe Customization Patterns
### Pattern 1: Edit User Config Directly
For simple changes, edit files in `~/.config/`:
```bash
# 1. Read current config
cat ~/.config/hypr/bindings.conf
# 2. Backup before changes
cp ~/.config/hypr/bindings.conf ~/.config/hypr/bindings.conf.bak.$(date +%s)
# 3. Make changes with Edit tool
# 4. Apply changes
# - Hyprland: auto-reloads on save (no restart needed)
# - Waybar: MUST restart with nomarchy-restart-waybar
# - Walker: MUST restart with nomarchy-restart-walker
# - Terminals: MUST restart with nomarchy-restart-terminal
```
### Pattern 2: Make a new theme
1. Create a directory under ~/.config/nomarchy/themes.
2. See how an existing theme is done via ~/.local/share/nomarchy/themes/catppuccin.
3. Download a matching background (or several) from the internet and put them in ~/.config/nomarchy/themes/[name-of-new-theme]
4. When done with the theme, run nomarchy-theme-set "Name of new theme"
### Pattern 3: Use Hooks for Automation
Create scripts in `~/.config/nomarchy/hooks/` to run automatically on events:
```bash
# Available hooks (see samples in ~/.config/nomarchy/hooks/):
~/.config/nomarchy/hooks/
├── theme-set # Runs after theme change (receives theme name as $1)
├── font-set # Runs after font change
└── post-update # Runs after nomarchy-update
```
Example hook (`~/.config/nomarchy/hooks/theme-set`):
```bash
#!/bin/bash
THEME_NAME=$1
echo "Theme changed to: $THEME_NAME"
# Add custom actions here
```
### Pattern 4: Reset to Defaults -- ALWAYS SEEK USER CONFIRMATION BEFORE RUNNING
When customizations go wrong:
```bash
# Reset specific config (creates backup automatically)
nomarchy-refresh-waybar
nomarchy-refresh-hyprland
# The refresh command:
# 1. Backs up current config with timestamp
# 2. Copies default from ~/.local/share/nomarchy/config/
# 3. Restarts the component
```
## Common Tasks
### Themes
```bash
nomarchy-theme-list # Show available themes
nomarchy-theme-current # Show current theme
nomarchy-theme-set <name> # Apply theme (use "Tokyo Night" not "tokyo-night")
nomarchy-theme-next # Cycle to next theme
nomarchy-theme-bg-next # Cycle wallpaper
nomarchy-theme-install <url> # Install from git repo
nomarchy-theme-remove <name> # Remove an installed extra theme
nomarchy-theme-refresh # Re-apply current theme from templates
nomarchy-theme-bg-install # Open backgrounds dir to drop in custom images
```
### Keybindings
Edit `~/.config/hypr/bindings.conf`. Format:
```
bind = SUPER, Return, exec, xdg-terminal-exec
bind = SUPER, Q, killactive
bind = SUPER SHIFT, E, exit
```
View current bindings: `nomarchy-menu-keybindings --print`
**IMPORTANT: When re-binding an existing key:**
1. First check existing bindings: `nomarchy-menu-keybindings --print`
2. If the key is already bound, you MUST add an `unbind` directive BEFORE your new `bind`
3. Inform the user what the key was previously bound to
Example - rebinding SUPER+F (which is bound to fullscreen by default):
```
# Unbind existing SUPER+F (was: fullscreen)
unbind = SUPER, F
# New binding for file manager
bind = SUPER, F, exec, nautilus
```
Always tell the user: "Note: SUPER+F was previously bound to fullscreen. I've added an unbind directive to override it."
### Display/Monitors
Edit `~/.config/hypr/monitors.conf`. Format:
```
monitor = eDP-1, 1920x1080@60, 0x0, 1
monitor = HDMI-A-1, 2560x1440@144, 1920x0, 1
```
List monitors: `hyprctl monitors`
### Window Rules
**CRITICAL: Hyprland window rules syntax changes frequently between versions.**
Before writing ANY window rules, you MUST fetch the current documentation from the official Hyprland wiki:
- https://github.com/hyprwm/hyprland-wiki/blob/main/content/Configuring/Window-Rules.md
DO NOT rely on cached or memorized window rule syntax. The format has changed multiple times and using outdated syntax will cause errors or unexpected behavior.
Window rules go in `~/.config/hypr/hyprland.conf` or a sourced file. Always verify the current syntax from the wiki first.
### Fonts
```bash
nomarchy-font-list # Available fonts
nomarchy-font-current # Current font
nomarchy-font-set <name> # Change font
```
### System
```bash
nomarchy-update # Full system update
nomarchy-version # Show Nomarchy version
nomarchy-debug --no-sudo --print # Debug info (ALWAYS use these flags)
nomarchy-lock-screen # Lock screen
nomarchy-system-shutdown # Shutdown
nomarchy-system-reboot # Reboot
nomarchy-sudo-passwordless-toggle # Toggle 15-min passwordless sudo
nomarchy-sudo-reset # Clear sudo lockout / faillock
nomarchy-restart-trackpad # Reload intel_quicki2c (fixes dead THC trackpad)
```
**IMPORTANT:** Always run `nomarchy-debug` with `--no-sudo --print` flags to avoid interactive sudo prompts that will hang the terminal.
### Custom App Launchers
```bash
nomarchy-webapp-install # Add a web app launcher (interactive)
nomarchy-webapp-remove [name...] # Remove web apps (interactive if no name)
nomarchy-webapp-remove-all # Bulk-remove every web app
nomarchy-tui-install # Add a TUI launcher for a terminal program
nomarchy-tui-remove [name...] # Remove TUI launchers
nomarchy-tui-remove-all # Bulk-remove every TUI launcher
```
Both families write `.desktop` files into `~/.local/share/applications/` so they appear in the app launcher (walker / rofi).
### Virtualization
```bash
nomarchy-windows-vm install # Provision a Windows VM via docker-compose
nomarchy-windows-vm launch # Connect to the VM (auto-stop on disconnect)
nomarchy-windows-vm launch -k # Connect, keep VM running after disconnect
nomarchy-windows-vm stop # Shut the VM down
nomarchy-windows-vm status # Show current state
```
Requires KVM (`/dev/kvm`) and Docker (enable via `nomarchy.system.virtualization.docker`).
## Troubleshooting
```bash
# Get debug information (ALWAYS use these flags to avoid interactive prompts)
nomarchy-debug --no-sudo --print
# Upload logs for support
nomarchy-upload-log
# Reset specific config to defaults
# Examples: nomarchy-refresh-fastfetch, nomarchy-refresh-hyprland, nomarchy-refresh-waybar
nomarchy-refresh-<app>
# Refresh specific config file
# config-file path is relative to ~/.config/; the stock copy must exist under
# ~/.local/share/nomarchy/config/ (i.e. a core/home/config item).
# eg. nomarchy-refresh-config fastfetch/config.jsonc refreshes ~/.config/fastfetch/config.jsonc
nomarchy-refresh-config <config-file>
# Full reinstall of configs (nuclear option)
nomarchy-reinstall
```
## Decision Framework
When user requests system changes:
1. **Is it a stock nomarchy command?** Use it directly
2. **Is it a config edit?** Edit in `~/.config/`, never `~/.local/share/nomarchy/`
3. **Is it a theme customization?** Create a NEW custom theme directory
4. **Is it automation?** Use hooks in `~/.config/nomarchy/hooks/`
5. **Is it a package install?** Use `nomarchy-pkg-add` (adds to `user-packages.json`; rebuild applies it). The AUR does not exist on NixOS — search nixpkgs with `nix search nixpkgs <name>`.
6. **Unsure if command exists?** Search with `compgen -c | grep nomarchy`
## Out of Scope
This skill intentionally does not cover Nomarchy source development. Do not use this skill for:
- Editing files in `~/.local/share/nomarchy/` (`bin/`, `config/`, `default/`, `themes/`, `migrations/`, etc.)
- Creating or editing migrations
- Modifying Nomarchy's own source tree
## Example Requests
- "Change my theme to catppuccin" -> `nomarchy-theme-set catppuccin`
- "Add a keybinding for Super+E to open file manager" -> Check existing bindings first, add `unbind` if needed, then add `bind` in `~/.config/hypr/bindings.conf`
- "Configure my external monitor" -> Edit `~/.config/hypr/monitors.conf`
- "Make the window gaps smaller" -> Edit `~/.config/hypr/looknfeel.conf`
- "Set up night light to turn on at sunset" -> `nomarchy-toggle-nightlight` or edit `~/.config/hypr/hyprsunset.conf`
- "Customize the catppuccin theme colors" -> Create `~/.config/nomarchy/themes/catppuccin-custom/` by copying from stock, then edit
- "Run a script every time I change themes" -> Create `~/.config/nomarchy/hooks/theme-set`
- "Reset waybar to defaults" -> `nomarchy-refresh-waybar`

View File

@@ -1,11 +0,0 @@
[colors.primary]
background = "0x000000"
[colors.cursor]
cursor = "0x000000"
[font]
size = 18.0
[window]
opacity = 1.0

View File

@@ -1,57 +0,0 @@
# File system
if command -v eza &> /dev/null; then
alias ls='eza -lh --group-directories-first --icons=auto'
alias lsa='ls -a'
alias lt='eza --tree --level=2 --long --icons --git'
alias lta='lt -a'
fi
if [[ "$TERM" == "xterm-kitty" ]]; then
alias ff="fzf --preview 'case \$(file --mime-type -b {}) in image/*) kitty icat --clear --transfer-mode=memory --stdin=no --place=\${FZF_PREVIEW_COLUMNS}x\${FZF_PREVIEW_LINES}@0x0 {} ;; *) bat --style=numbers --color=always {} ;; esac'"
else
alias ff="fzf --preview 'bat --style=numbers --color=always {}'"
fi
alias eff='$EDITOR "$(ff)"'
sff() { if [ $# -eq 0 ]; then echo "Usage: sff <destination> (e.g. sff host:/tmp/)"; return 1; fi; local file; file=$(find . -type f -printf '%T@\t%p\n' | sort -rn | cut -f2- | ff) && [ -n "$file" ] && scp "$file" "$1"; }
if command -v zoxide &> /dev/null; then
alias cd="zd"
zd() {
if (( $# == 0 )); then
builtin cd ~ || return
elif [[ -d $1 ]]; then
builtin cd "$1" || return
else
if ! z "$@"; then
echo "Error: Directory not found"
return 1
fi
printf "\U000F17A9 "
pwd
fi
}
fi
open() (
xdg-open "$@" >/dev/null 2>&1 &
)
# Directories
alias ..='cd ..'
alias ...='cd ../..'
alias ....='cd ../../..'
# Tools
alias c='opencode'
alias cx='printf "\033[2J\033[3J\033[H" && claude --allow-dangerously-skip-permissions'
alias d='docker'
alias r='rails'
alias t='tmux attach || tmux new -s Work'
n() { if [ "$#" -eq 0 ]; then command nvim . ; else command nvim "$@"; fi; }
# Git
alias g='git'
alias gcm='git commit -m'
alias gcam='git commit -a -m'
alias gcad='git commit -a --amend'

View File

@@ -1,6 +0,0 @@
# Editor used by CLI
export SUDO_EDITOR="$EDITOR"
export BAT_THEME=ansi
# Duplicated from .config/uwsm/env so SSH works too
export PATH=$PATH:$HOME/.local/bin

View File

@@ -1,3 +0,0 @@
# Compression
compress() { tar -czf "${1%/}.tar.gz" "${1%/}"; }
alias decompress="tar -xzf"

View File

@@ -1,59 +0,0 @@
# Write iso file to sd card
iso2sd() {
if (( $# < 1 )); then
echo "Usage: iso2sd <input_file> [output_device]"
echo "Example: iso2sd ~/Downloads/ubuntu-25.04-desktop-amd64.iso /dev/sda"
return 1
fi
local iso="$1"
local drive="$2"
if [[ -z $drive ]]; then
local available_sds=$(lsblk -dpno NAME | grep -E '/dev/sd')
if [[ -z $available_sds ]]; then
echo "No SD drives found and no drive specified"
return 1
fi
drive=$(nomarchy-drive-select "$available_sds")
if [[ -z $drive ]]; then
echo "No drive selected"
return 1
fi
fi
sudo dd bs=4M status=progress oflag=sync if="$iso" of="$drive"
sudo eject "$drive"
}
# Format an entire drive for a single partition using exFAT
format-drive() {
if (( $# != 2 )); then
echo "Usage: format-drive <device> <name>"
echo "Example: format-drive /dev/sda 'My Stuff'"
echo -e "\nAvailable drives:"
lsblk -d -o NAME -n | awk '{print "/dev/"$1}'
else
echo "WARNING: This will completely erase all data on $1 and label it '$2'."
read -rp "Are you sure you want to continue? (y/N): " confirm
if [[ $confirm =~ ^[Yy]$ ]]; then
sudo wipefs -a "$1"
sudo dd if=/dev/zero of="$1" bs=1M count=100 status=progress
sudo parted -s "$1" mklabel gpt
sudo parted -s "$1" mkpart primary 1MiB 100%
sudo parted -s "$1" set 1 msftdata on
partition="$([[ $1 == *"nvme"* ]] && echo "${1}p1" || echo "${1}1")"
sudo partprobe "$1" || true
sudo udevadm settle || true
sudo mkfs.exfat -n "$2" "$partition"
echo "Drive $1 formatted as exFAT and labeled '$2'."
fi
fi
}

View File

@@ -1,20 +0,0 @@
# SSH Port Forwarding Functions
fip() {
(( $# < 2 )) && echo "Usage: fip <host> <port1> [port2] ..." && return 1
local host="$1"
shift
for port in "$@"; do
ssh -f -N -L "$port:localhost:$port" "$host" && echo "Forwarding localhost:$port -> $host:$port"
done
}
dip() {
(( $# == 0 )) && echo "Usage: dip <port1> [port2] ..." && return 1
for port in "$@"; do
pkill -f "ssh.*-L $port:localhost:$port" && echo "Stopped forwarding port $port" || echo "No forwarding on port $port"
done
}
lip() {
pgrep -af "ssh.*-L [0-9]+:localhost:[0-9]+" || echo "No active forwards"
}

View File

@@ -1,97 +0,0 @@
# Create a Tmux Dev Layout with editor, ai, and terminal
# Usage: tdl <c|cx|codex|other_ai> [<second_ai>]
tdl() {
[[ -z $1 ]] && { echo "Usage: tdl <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdl."; return 1; }
local current_dir="${PWD}"
local editor_pane ai_pane ai2_pane
local ai="$1"
local ai2="$2"
# Use TMUX_PANE for the pane we're running in (stable even if active window changes)
editor_pane="$TMUX_PANE"
# Name the current window after the base directory name
tmux rename-window -t "$editor_pane" "$(basename "$current_dir")"
# Split window vertically - top 85%, bottom 15% (target editor pane explicitly)
tmux split-window -v -p 15 -t "$editor_pane" -c "$current_dir"
# Split editor pane horizontally - AI on right 30% (capture new pane ID directly)
ai_pane=$(tmux split-window -h -p 30 -t "$editor_pane" -c "$current_dir" -P -F '#{pane_id}')
# If second AI provided, split the AI pane vertically
if [[ -n $ai2 ]]; then
ai2_pane=$(tmux split-window -v -t "$ai_pane" -c "$current_dir" -P -F '#{pane_id}')
tmux send-keys -t "$ai2_pane" "$ai2" C-m
fi
# Run ai in the right pane
tmux send-keys -t "$ai_pane" "$ai" C-m
# Run nvim in the left pane
tmux send-keys -t "$editor_pane" "$EDITOR ." C-m
# Select the nvim pane for focus
tmux select-pane -t "$editor_pane"
}
# Create multiple tdl windows with one per subdirectory in the current directory
# Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]
tdlm() {
[[ -z $1 ]] && { echo "Usage: tdlm <c|cx|codex|other_ai> [<second_ai>]"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tdlm."; return 1; }
local ai="$1"
local ai2="$2"
local base_dir="$PWD"
local first=true
# Rename the session to the current directory name (replace dots/colons which tmux disallows)
tmux rename-session "$(basename "$base_dir" | tr '.:' '--')"
for dir in "$base_dir"/*/; do
[[ -d $dir ]] || continue
local dirpath="${dir%/}"
if $first; then
# Reuse the current window for the first project
tmux send-keys -t "$TMUX_PANE" "cd '$dirpath' && tdl $ai $ai2" C-m
first=false
else
local pane_id=$(tmux new-window -c "$dirpath" -P -F '#{pane_id}')
tmux send-keys -t "$pane_id" "tdl $ai $ai2" C-m
fi
done
}
# Create a multi-pane swarm layout with the same command started in each pane (great for AI)
# Usage: tsl <pane_count> <command>
tsl() {
[[ -z $1 || -z $2 ]] && { echo "Usage: tsl <pane_count> <command>"; return 1; }
[[ -z $TMUX ]] && { echo "You must start tmux to use tsl."; return 1; }
local count="$1"
local cmd="$2"
local current_dir="${PWD}"
local -a panes
tmux rename-window -t "$TMUX_PANE" "$(basename "$current_dir")"
panes+=("$TMUX_PANE")
while (( ${#panes[@]} < count )); do
local new_pane
local split_target="${panes[-1]}"
new_pane=$(tmux split-window -h -t "$split_target" -c "$current_dir" -P -F '#{pane_id}')
panes+=("$new_pane")
tmux select-layout -t "${panes[0]}" tiled
done
for pane in "${panes[@]}"; do
tmux send-keys -t "$pane" "$cmd" C-m
done
tmux select-pane -t "${panes[0]}"
}

View File

@@ -1,53 +0,0 @@
# Transcode a video to a good-balance 1080p that's great for sharing online
transcode-video-1080p() {
ffmpeg -i "$1" -vf scale=1920:1080 -c:v libx264 -preset fast -crf 23 -c:a copy "${1%.*}-1080p.mp4"
}
# Transcode a video to a good-balance 4K that's great for sharing online
transcode-video-4K() {
ffmpeg -i "$1" -c:v libx265 -preset slow -crf 24 -c:a aac -b:a 192k "${1%.*}-optimized.mp4"
}
# Transcode any image to JPG image that's great for shrinking wallpapers
img2jpg() {
img="$1"
shift
magick "$img" "$@" -quality 85 -strip "${img%.*}-converted.jpg"
}
# Transcode any image to a small JPG (max 1080px wide)
img2jpg-small() {
img="$1"
shift
magick "$img" "$@" -resize 1080x\> -quality 85 -strip "${img%.*}-small.jpg"
}
# Transcode any image to a 4K JPG (max 2160px wide)
img2jpg-medium() {
img="$1"
shift
magick "$img" "$@" -resize 2160x\> -quality 85 -strip "${img%.*}-medium.jpg"
}
# Transcode any image to a 6K JPG (max 3160px wide)
img2jpg-large() {
img="$1"
shift
magick "$img" "$@" -resize 3160x\> -quality 85 -strip "${img%.*}-large.jpg"
}
# Transcode any image to compressed-but-lossless PNG
img2png() {
img="$1"
shift
magick "$img" "$@" -strip -define png:compression-filter=5 \
-define png:compression-level=9 \
-define png:compression-strategy=1 \
-define png:exclude-chunk=all \
"${img%.*}-optimized.png"
}

View File

@@ -1,36 +0,0 @@
# Create a new worktree and branch from within current git directory.
ga() {
if [[ -z "$1" ]]; then
echo "Usage: ga [branch name]"
return 1
fi
local branch="$1"
local base="$(basename "$PWD")"
local wt_path="../${base}--${branch}"
git worktree add -b "$branch" "$wt_path"
mise trust "$wt_path"
cd "$wt_path"
}
# Remove worktree and branch from within active worktree directory.
gd() {
if gum confirm "Remove worktree and branch?"; then
local cwd base branch root worktree
cwd="$(pwd)"
worktree="$(basename "$cwd")"
# split on first `--`
root="${worktree%%--*}"
branch="${worktree#*--}"
# Protect against accidentally nuking a non-worktree directory
if [[ "$root" != "$worktree" ]]; then
cd "../$root"
git worktree remove "$cwd" --force || return 1
git branch -D "$branch"
fi
fi
}

View File

@@ -1 +0,0 @@
for f in $HOME/.config/nomarchy/default/bash/fns/*; do source "$f"; done

View File

@@ -1,24 +0,0 @@
if command -v mise &> /dev/null; then
eval "$(mise activate bash)"
fi
if command -v starship &> /dev/null; then
eval "$(starship init bash)"
fi
if command -v zoxide &> /dev/null; then
eval "$(zoxide init bash)"
fi
if command -v try &> /dev/null; then
eval "$(SHELL=/bin/bash command try init ~/Work/tries)"
fi
if command -v fzf &> /dev/null; then
if [[ -f /usr/share/fzf/completion.bash ]]; then
source /usr/share/fzf/completion.bash
fi
if [[ -f /usr/share/fzf/key-bindings.bash ]]; then
source /usr/share/fzf/key-bindings.bash
fi
fi

View File

@@ -1,47 +0,0 @@
set meta-flag on
set input-meta on
set output-meta on
set convert-meta off
set completion-ignore-case on
set completion-prefix-display-length 2
set show-all-if-ambiguous on
set show-all-if-unmodified on
# Arrow keys match what you've typed so far against your command history
"\e[A": history-search-backward
"\e[B": history-search-forward
"\e[C": forward-char
"\e[D": backward-char
# Immediately add a trailing slash when autocompleting symlinks to directories
set mark-symlinked-directories on
# Do not autocomplete hidden files unless the pattern explicitly begins with a dot
set match-hidden-files off
# Show all autocomplete results at once
set page-completions off
# If there are more than 200 possible completions for a word, ask to show them all
set completion-query-items 200
# Show extra file information when completing, like `ls -F` does
set visible-stats on
# Be more intelligent when autocompleting by also looking at the text after
# the cursor. For example, when the current line is "cd ~/src/mozil", and
# the cursor is on the "z", pressing Tab will not autocomplete it to "cd
# ~/src/mozillail", but to "cd ~/src/mozilla". (This is supported by the
# Readline used by Bash 4.)
set skip-completed-text on
# Coloring for Bash 4 tab completions.
set colored-stats on
# Cycle forward and backward through completion candidates (tab/shift+tab)
# (completion listing and display behavior configured above)
TAB: menu-complete
"\e[Z": menu-complete-backward
# On first Tab, complete the common prefix before cycling candidates
set menu-complete-display-prefix on

View File

@@ -1,6 +0,0 @@
source ~/.config/nomarchy/default/bash/envs
source ~/.config/nomarchy/default/bash/shell
source ~/.config/nomarchy/default/bash/aliases
source ~/.config/nomarchy/default/bash/functions
source ~/.config/nomarchy/default/bash/init
[[ $- == *i* ]] && bind -f ~/.config/nomarchy/default/bash/inputrc

View File

@@ -1,13 +0,0 @@
# History control
shopt -s histappend
HISTCONTROL=ignoreboth
HISTSIZE=32768
HISTFILESIZE="${HISTSIZE}"
# Autocompletion
if [[ ! -v BASH_COMPLETION_VERSINFO && -f /usr/share/bash-completion/bash_completion ]]; then
source /usr/share/bash-completion/bash_completion
fi
# Ensure command hashing is off for mise
set +h

View File

@@ -1,21 +0,0 @@
chrome.commands.onCommand.addListener((command) => {
if (command === 'copy-url') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
chrome.scripting.executeScript({
target: { tabId: currentTab.id },
func: () => {
navigator.clipboard.writeText(window.location.href);
}
}).then(() => {
chrome.notifications.create({
type: 'basic',
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==',
title: ' URL copied to clipboard',
message: ''
});
});
});
}
});

View File

@@ -1 +0,0 @@
../../../../icon.png

View File

@@ -1,19 +0,0 @@
{
"manifest_version": 3,
"name": "Copy URL",
"version": "1.0",
"description": "Copy current URL to clipboard, this extension is installed by Nomarchy",
"permissions": ["activeTab", "scripting", "notifications"],
"icons": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"commands": {
"copy-url": {
"suggested_key": {"default": "Alt+Shift+L"},
"description": "Copy URL"
}
},
"background": {"service_worker": "background.js"}
}

View File

@@ -1,3 +0,0 @@
window-padding-x = 0
window-padding-y = 0
window-padding-color = "extend-always"

View File

@@ -1,7 +0,0 @@
keyserver hkps://keyserver.ubuntu.com
keyserver hkps://pgp.surfnet.nl
keyserver hkps://keys.mailvelope.com
keyserver hkps://keyring.debian.org
keyserver hkps://pgp.mit.edu
connect-quick-timeout 4

View File

@@ -1,19 +0,0 @@
# App-specific tweaks. All rules are class- or title-gated, so sourcing
# unconditionally is harmless when the app isn't installed or running.
source = ~/.config/nomarchy/default/hypr/apps/1password.conf
source = ~/.config/nomarchy/default/hypr/apps/bitwarden.conf
source = ~/.config/nomarchy/default/hypr/apps/browser.conf
source = ~/.config/nomarchy/default/hypr/apps/davinci-resolve.conf
source = ~/.config/nomarchy/default/hypr/apps/geforce.conf
source = ~/.config/nomarchy/default/hypr/apps/hyprshot.conf
source = ~/.config/nomarchy/default/hypr/apps/localsend.conf
source = ~/.config/nomarchy/default/hypr/apps/moonlight.conf
source = ~/.config/nomarchy/default/hypr/apps/pip.conf
source = ~/.config/nomarchy/default/hypr/apps/qemu.conf
source = ~/.config/nomarchy/default/hypr/apps/retroarch.conf
source = ~/.config/nomarchy/default/hypr/apps/steam.conf
source = ~/.config/nomarchy/default/hypr/apps/system.conf
source = ~/.config/nomarchy/default/hypr/apps/telegram.conf
source = ~/.config/nomarchy/default/hypr/apps/terminals.conf
source = ~/.config/nomarchy/default/hypr/apps/walker.conf
source = ~/.config/nomarchy/default/hypr/apps/webcam-overlay.conf

View File

@@ -1,2 +0,0 @@
windowrulev2 = noscreenshare, class:^(1[p|P]assword)$
windowrulev2 = tag +floating-window, class:^(1[p|P]assword)$

View File

@@ -1,6 +0,0 @@
windowrulev2 = noscreenshare, class:^(Bitwarden)$
windowrulev2 = tag +floating-window, class:^(Bitwarden)$
# Bitwarden Chrome Extension
windowrulev2 = noscreenshare, class:chrome-nngceckbapebfimnlniiiahkandclblb-Default
windowrulev2 = tag +floating-window, class:chrome-nngceckbapebfimnlniiiahkandclblb-Default

View File

@@ -1,16 +0,0 @@
# Browser types
windowrulev2 = tag +chromium-based-browser, class:((google-)?[cC]hrom(e|ium)|[bB]rave-browser|[mM]icrosoft-edge|Vivaldi-stable|helium)
windowrulev2 = tag +firefox-based-browser, class:([fF]irefox|zen|librewolf)
windowrulev2 = tag -default-opacity, tag:chromium-based-browser
windowrulev2 = tag -default-opacity, tag:firefox-based-browser
# Video apps: remove chromium browser tag so they don't get opacity applied
windowrulev2 = tag -chromium-based-browser, class:(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)
windowrulev2 = tag -default-opacity, class:(chrome-youtube.com__-Default|chrome-app.zoom.us__wc_home-Default)
# Force chromium-based browsers into a tile to deal with --app bug
windowrulev2 = tile, tag:chromium-based-browser
# Only a subtle opacity change, but not for video sites
windowrulev2 = opacity 1.0 0.97, tag:chromium-based-browser
windowrulev2 = opacity 1.0 0.97, tag:firefox-based-browser

View File

@@ -1,2 +0,0 @@
# Focus floating DaVinci Resolve dialog windows
windowrulev2 = stayfocused, class:.*[Rr]esolve.*, float:1

View File

@@ -1 +0,0 @@
windowrulev2 = idleinhibit fullscreen, class:^(GeForceNOW)$

View File

@@ -1,2 +0,0 @@
# Remove 1px border around hyprshot screenshots
layerrule = noanim, namespace:selection

View File

@@ -1,4 +0,0 @@
# Float LocalSend and fzf file picker
windowrulev2 = float, class:(Share|localsend)
windowrulev2 = center, class:(Share|localsend)
windowrulev2 = size 1100 700, class:localsend

View File

@@ -1,2 +0,0 @@
windowrulev2 = fullscreen, class:^(com.moonlight_stream.Moonlight)$
windowrulev2 = idleinhibit fullscreen, class:^(com.moonlight_stream.Moonlight)$

View File

@@ -1,10 +0,0 @@
# Picture-in-picture overlays
windowrulev2 = tag +pip, title:(Picture.?in.?[Pp]icture)
windowrulev2 = tag -default-opacity, tag:pip
windowrulev2 = float, tag:pip
windowrulev2 = pin, tag:pip
windowrulev2 = size 600 338, tag:pip
windowrulev2 = keepaspectratio, tag:pip
windowrulev2 = bordersize 0, tag:pip
windowrulev2 = opacity 1 1, tag:pip
windowrulev2 = move (monitor_w-window_w-40) (monitor_h*0.04), tag:pip

View File

@@ -1,2 +0,0 @@
windowrulev2 = tag -default-opacity, class:qemu
windowrulev2 = opacity 1 1, class:qemu

View File

@@ -1,4 +0,0 @@
windowrulev2 = fullscreen, class:com.libretro.RetroArch
windowrulev2 = tag -default-opacity, class:com.libretro.RetroArch
windowrulev2 = opacity 1 1, class:com.libretro.RetroArch
windowrulev2 = idleinhibit fullscreen, class:com.libretro.RetroArch

View File

@@ -1,8 +0,0 @@
# Float Steam
windowrulev2 = float, class:steam
windowrulev2 = center, class:steam, title:Steam
windowrulev2 = tag -default-opacity, class:steam.*
windowrulev2 = opacity 1 1, class:steam.*
windowrulev2 = size 1100 700, class:steam, title:Steam
windowrulev2 = size 460 800, class:steam, title:Friends List
windowrulev2 = idleinhibit fullscreen, class:steam

View File

@@ -1,23 +0,0 @@
# Floating windows
windowrulev2 = float, tag:floating-window
windowrulev2 = center, tag:floating-window
windowrulev2 = size 875 600, tag:floating-window
windowrulev2 = tag +floating-window, class:(org.nomarchy.bluetui|org.nomarchy.impala|org.nomarchy.wiremix|org.nomarchy.btop|org.nomarchy.terminal|org.nomarchy.bash|org.gnome.NautilusPreviewer|org.gnome.Evince|com.gabm.satty|Nomarchy|About|TUI.float|imv|mpv)
windowrulev2 = tag +floating-window, class:(xdg-desktop-portal-gtk|sublime_text|DesktopEditors|org.gnome.Nautilus), title:^(Open.*Files?|Open [F|f]older.*|Save.*Files?|Save.*As|Save|All Files|.*wants to [open|save].*|[C|c]hoose.*)
windowrulev2 = float, class:org.gnome.Calculator
# Fullscreen screensaver
windowrulev2 = fullscreen, class:org.nomarchy.screensaver
windowrulev2 = float, class:org.nomarchy.screensaver
windowrulev2 = animation slide, class:org.nomarchy.screensaver
# No transparency on media windows
windowrulev2 = tag -default-opacity, class:^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$
windowrulev2 = opacity 1 1, class:^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$
# Popped window rounding
windowrulev2 = rounding 8, tag:pop
# Prevent idle while open
windowrulev2 = idleinhibit always, tag:noidle

View File

@@ -1,2 +0,0 @@
# Prevent Telegram from stealing focus on new messages
windowrulev2 = focusonactivate off, class:org.telegram.desktop

View File

@@ -1,4 +0,0 @@
# Define terminal tag to style them uniformly
windowrulev2 = tag +terminal, class:(Alacritty|kitty|com.mitchellh.ghostty)
windowrulev2 = tag -default-opacity, tag:terminal
windowrulev2 = opacity 0.97 0.9, tag:terminal

View File

@@ -1,2 +0,0 @@
# Application-specific animation
layerrule = noanim, namespace:walker

View File

@@ -1,6 +0,0 @@
# Webcam overlay for screen recording
windowrulev2 = float, title:WebcamOverlay
windowrulev2 = pin, title:WebcamOverlay
windowrulev2 = noinitialfocus, title:WebcamOverlay
windowrulev2 = nodim, title:WebcamOverlay
windowrulev2 = move (monitor_w-window_w-40) (monitor_h-window_h-40), title:WebcamOverlay

View File

@@ -1,17 +0,0 @@
exec-once = uwsm-app -- hypridle
exec-once = uwsm-app -- mako
exec-once = nomarchy-welcome
# exec-once = uwsm-app -- waybar
# fcitx5 is autostarted by NixOS's i18n.inputMethod when
# nomarchy.system.inputMethod.enable = true; no manual exec-once needed.
# swaybg is started as a systemd user service (nomarchy-wallpaper.service)
# so failures surface in logs and the wallpaper survives Hyprland restarts.
exec-once = uwsm-app -- swayosd-server
exec-once = nomarchy-on-boot
# Slow app launch fix -- set systemd vars
exec-once = systemctl --user import-environment $(env | cut -d'=' -f 1)
exec-once = dbus-update-activation-environment --systemd --all
# Network Manager
exec-once = uwsm-app 'nm-applet --indicator'

View File

@@ -1,5 +0,0 @@
# Copy / Paste
bindd = SUPER, C, Universal copy, sendshortcut, CTRL, Insert,
bindd = SUPER, V, Universal paste, sendshortcut, SHIFT, Insert,
bindd = SUPER, X, Universal cut, sendshortcut, CTRL, X,
bindd = SUPER CTRL, V, Clipboard manager, exec, nomarchy-launch-walker -m clipboard

View File

@@ -1,28 +0,0 @@
# Only display the OSD on the currently focused monitor
$osdclient = swayosd-client --monitor "$(hyprctl monitors -j | jq -r '.[] | select(.focused == true).name')"
# Laptop multimedia keys for volume and LCD brightness (with OSD)
bindeld = ,XF86AudioRaiseVolume, Volume up, exec, $osdclient --output-volume raise
bindeld = ,XF86AudioLowerVolume, Volume down, exec, $osdclient --output-volume lower
bindeld = ,XF86AudioMute, Mute, exec, $osdclient --output-volume mute-toggle
bindeld = ,XF86AudioMicMute, Mute microphone, exec, $osdclient --input-volume mute-toggle
bindeld = ,XF86MonBrightnessUp, Brightness up, exec, nomarchy-brightness-display +5%
bindeld = ,XF86MonBrightnessDown, Brightness down, exec, nomarchy-brightness-display 5%-
bindeld = ,XF86KbdBrightnessUp, Keyboard brightness up, exec, nomarchy-brightness-keyboard up
bindeld = ,XF86KbdBrightnessDown, Keyboard brightness down, exec, nomarchy-brightness-keyboard down
bindld = ,XF86KbdLightOnOff, Keyboard backlight cycle, exec, nomarchy-brightness-keyboard cycle
# Precise 1% multimedia adjustments with Alt modifier
bindeld = ALT, XF86AudioRaiseVolume, Volume up precise, exec, $osdclient --output-volume +1
bindeld = ALT, XF86AudioLowerVolume, Volume down precise, exec, $osdclient --output-volume -1
bindeld = ALT, XF86MonBrightnessUp, Brightness up precise, exec, nomarchy-brightness-display +1%
bindeld = ALT, XF86MonBrightnessDown, Brightness down precise, exec, nomarchy-brightness-display 1%-
# Requires playerctl
bindld = , XF86AudioNext, Next track, exec, $osdclient --playerctl next
bindld = , XF86AudioPause, Pause, exec, $osdclient --playerctl play-pause
bindld = , XF86AudioPlay, Play, exec, $osdclient --playerctl play-pause
bindld = , XF86AudioPrev, Previous track, exec, $osdclient --playerctl previous
# Switch audio output with Super + Mute
bindld = SUPER, XF86AudioMute, Switch audio output, exec, nomarchy-cmd-audio-switch

View File

@@ -1,128 +0,0 @@
# Close windows
bindd = SUPER, W, Close window, killactive,
bindd = CTRL ALT, DELETE, Close all windows, exec, nomarchy-hyprland-window-close-all
# Control tiling
bindd = SUPER, J, Toggle window split, layoutmsg, togglesplit
bindd = SUPER, P, Pseudo window, pseudo, # dwindle
bindd = SUPER, T, Toggle window floating/tiling, togglefloating,
bindd = SUPER, F, Full screen, fullscreen, 0
bindd = SUPER CTRL, F, Tiled full screen, fullscreenstate, 0 2
bindd = SUPER ALT, F, Full width, fullscreen, 1
bindd = SUPER, O, Pop window out (float & pin), exec, nomarchy-hyprland-window-pop
bindd = SUPER, L, Toggle workspace layout, exec, nomarchy-hyprland-workspace-layout-toggle
# Move focus with SUPER + arrow keys
bindd = SUPER, LEFT, Move window focus left, movefocus, l
bindd = SUPER, RIGHT, Move window focus right, movefocus, r
bindd = SUPER, UP, Move window focus up, movefocus, u
bindd = SUPER, DOWN, Move window focus down, movefocus, d
# Switch workspaces with SUPER + [1-9; 0]
bindd = SUPER, code:10, Switch to workspace 1, workspace, 1
bindd = SUPER, code:11, Switch to workspace 2, workspace, 2
bindd = SUPER, code:12, Switch to workspace 3, workspace, 3
bindd = SUPER, code:13, Switch to workspace 4, workspace, 4
bindd = SUPER, code:14, Switch to workspace 5, workspace, 5
bindd = SUPER, code:15, Switch to workspace 6, workspace, 6
bindd = SUPER, code:16, Switch to workspace 7, workspace, 7
bindd = SUPER, code:17, Switch to workspace 8, workspace, 8
bindd = SUPER, code:18, Switch to workspace 9, workspace, 9
bindd = SUPER, code:19, Switch to workspace 10, workspace, 10
# Move active window to a workspace with SUPER + SHIFT + [1-9; 0]
bindd = SUPER SHIFT, code:10, Move window to workspace 1, movetoworkspace, 1
bindd = SUPER SHIFT, code:11, Move window to workspace 2, movetoworkspace, 2
bindd = SUPER SHIFT, code:12, Move window to workspace 3, movetoworkspace, 3
bindd = SUPER SHIFT, code:13, Move window to workspace 4, movetoworkspace, 4
bindd = SUPER SHIFT, code:14, Move window to workspace 5, movetoworkspace, 5
bindd = SUPER SHIFT, code:15, Move window to workspace 6, movetoworkspace, 6
bindd = SUPER SHIFT, code:16, Move window to workspace 7, movetoworkspace, 7
bindd = SUPER SHIFT, code:17, Move window to workspace 8, movetoworkspace, 8
bindd = SUPER SHIFT, code:18, Move window to workspace 9, movetoworkspace, 9
bindd = SUPER SHIFT, code:19, Move window to workspace 10, movetoworkspace, 10
# Move active window silently to a workspace with SUPER + SHIFT + ALT + [1-9; 0]
bindd = SUPER SHIFT ALT, code:10, Move window silently to workspace 1, movetoworkspacesilent, 1
bindd = SUPER SHIFT ALT, code:11, Move window silently to workspace 2, movetoworkspacesilent, 2
bindd = SUPER SHIFT ALT, code:12, Move window silently to workspace 3, movetoworkspacesilent, 3
bindd = SUPER SHIFT ALT, code:13, Move window silently to workspace 4, movetoworkspacesilent, 4
bindd = SUPER SHIFT ALT, code:14, Move window silently to workspace 5, movetoworkspacesilent, 5
bindd = SUPER SHIFT ALT, code:15, Move window silently to workspace 6, movetoworkspacesilent, 6
bindd = SUPER SHIFT ALT, code:16, Move window silently to workspace 7, movetoworkspacesilent, 7
bindd = SUPER SHIFT ALT, code:17, Move window silently to workspace 8, movetoworkspacesilent, 8
bindd = SUPER SHIFT ALT, code:18, Move window silently to workspace 9, movetoworkspacesilent, 9
bindd = SUPER SHIFT ALT, code:19, Move window silently to workspace 10, movetoworkspacesilent, 10
# Control scratchpad
bindd = SUPER, S, Toggle scratchpad, togglespecialworkspace, scratchpad
bindd = SUPER ALT, S, Move window to scratchpad, movetoworkspacesilent, special:scratchpad
# TAB between workspaces
bindd = SUPER, TAB, Next workspace, workspace, e+1
bindd = SUPER SHIFT, TAB, Previous workspace, workspace, e-1
bindd = SUPER CTRL, TAB, Former workspace, workspace, previous
# Move workspaces to other monitors
bindd = SUPER SHIFT ALT, LEFT, Move workspace to left monitor, movecurrentworkspacetomonitor, l
bindd = SUPER SHIFT ALT, RIGHT, Move workspace to right monitor, movecurrentworkspacetomonitor, r
bindd = SUPER SHIFT ALT, UP, Move workspace to up monitor, movecurrentworkspacetomonitor, u
bindd = SUPER SHIFT ALT, DOWN, Move workspace to down monitor, movecurrentworkspacetomonitor, d
# Swap active window with the one next to it with SUPER + SHIFT + arrow keys
bindd = SUPER SHIFT, LEFT, Swap window to the left, swapwindow, l
bindd = SUPER SHIFT, RIGHT, Swap window to the right, swapwindow, r
bindd = SUPER SHIFT, UP, Swap window up, swapwindow, u
bindd = SUPER SHIFT, DOWN, Swap window down, swapwindow, d
# Cycle through applications on active workspace
bindd = ALT, TAB, Cycle to next window, cyclenext
bindd = ALT SHIFT, TAB, Cycle to prev window, cyclenext, prev
bindd = ALT, TAB, Reveal active window on top, bringactivetotop
bindd = ALT SHIFT, TAB, Reveal active window on top, bringactivetotop
# Resize active window
bindd = SUPER, code:20, Expand window left, resizeactive, -100 0 # - key
bindd = SUPER, code:21, Shrink window left, resizeactive, 100 0 # = key
bindd = SUPER SHIFT, code:20, Shrink window up, resizeactive, 0 -100
bindd = SUPER SHIFT, code:21, Expand window down, resizeactive, 0 100
# Scroll through existing workspaces with SUPER + scroll
bindd = SUPER, mouse_down, Scroll active workspace forward, workspace, e+1
bindd = SUPER, mouse_up, Scroll active workspace backward, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindmd = SUPER, mouse:272, Move window, movewindow
bindmd = SUPER, mouse:273, Resize window, resizewindow
# Toggle groups
bindd = SUPER, G, Toggle window grouping, togglegroup
bindd = SUPER ALT, G, Move active window out of group, moveoutofgroup
# Join groups
bindd = SUPER ALT, LEFT, Move window to group on left, moveintogroup, l
bindd = SUPER ALT, RIGHT, Move window to group on right, moveintogroup, r
bindd = SUPER ALT, UP, Move window to group on top, moveintogroup, u
bindd = SUPER ALT, DOWN, Move window to group on bottom, moveintogroup, d
# Navigate a single set of grouped windows
bindd = SUPER ALT, TAB, Next window in group, changegroupactive, f
bindd = SUPER ALT SHIFT, TAB, Previous window in group, changegroupactive, b
# Window navigation for grouped windows
bindd = SUPER CTRL, LEFT, Move grouped window focus left, changegroupactive, b
bindd = SUPER CTRL, RIGHT, Move grouped window focus right, changegroupactive, f
# Scroll through a set of grouped windows with SUPER + ALT + scroll
bindd = SUPER ALT, mouse_down, Next window in group, changegroupactive, f
bindd = SUPER ALT, mouse_up, Previous window in group, changegroupactive, b
# Activate window in a group by number
bindd = SUPER ALT, code:10, Switch to group window 1, changegroupactive, 1
bindd = SUPER ALT, code:11, Switch to group window 2, changegroupactive, 2
bindd = SUPER ALT, code:12, Switch to group window 3, changegroupactive, 3
bindd = SUPER ALT, code:13, Switch to group window 4, changegroupactive, 4
bindd = SUPER ALT, code:14, Switch to group window 5, changegroupactive, 5
# Cycle monitor scaling
bindd = SUPER, Slash, Cycle monitor scaling, exec, nomarchy-hyprland-monitor-scaling-cycle

View File

@@ -1,59 +0,0 @@
# Menus
bindd = SUPER, SPACE, Launch apps, exec, nomarchy-launch-walker
bindd = SUPER CTRL, E, Emoji picker, exec, nomarchy-menu symbols
bindd = SUPER CTRL, C, Capture menu, exec, nomarchy-menu capture
bindd = SUPER CTRL, O, Toggle menu, exec, nomarchy-menu toggle
bindd = SUPER ALT, SPACE, Toggle top bar, exec, nomarchy-toggle-waybar
bindd = SUPER, ESCAPE, System menu, exec, nomarchy-menu system
bindld = , XF86PowerOff, Power menu, exec, nomarchy-menu system
bindd = SUPER, K, Show key bindings, exec, nomarchy-menu-keybindings
bindd = , XF86Calculator, Calculator, exec, gnome-calculator
# Aesthetics
bindd = SUPER SHIFT, SPACE, Nomarchy menu, exec, nomarchy-menu
bindd = SUPER CTRL, SPACE, Theme background menu, exec, nomarchy-menu background
bindd = SUPER SHIFT CTRL, SPACE, Theme menu, exec, nomarchy-menu theme
bindd = SUPER, BACKSPACE, Toggle window transparency, exec, nomarchy-hyprland-active-window-transparency-toggle
bindd = SUPER SHIFT, BACKSPACE, Toggle window gaps, exec, nomarchy-hyprland-window-gaps-toggle
bindd = SUPER CTRL, BACKSPACE, Toggle single-window square aspect, exec, nomarchy-hyprland-window-single-square-aspect-toggle
# Notifications
bindd = SUPER, COMMA, Dismiss last notification, exec, makoctl dismiss
bindd = SUPER SHIFT, COMMA, Dismiss all notifications, exec, makoctl dismiss --all
bindd = SUPER CTRL, COMMA, Toggle silencing notifications, exec, nomarchy-toggle-notification-silencing
bindd = SUPER ALT, COMMA, Invoke last notification, exec, makoctl invoke
bindd = SUPER SHIFT ALT, COMMA, Restore last notification, exec, makoctl restore
# Toggles
bindd = SUPER CTRL, I, Toggle locking on idle, exec, nomarchy-toggle-idle
bindd = SUPER CTRL, N, Toggle nightlight, exec, nomarchy-toggle-nightlight
# Control Apple Display brightness
bindd = CTRL, F1, Apple Display brightness down, exec, nomarchy-brightness-display-apple -5000
bindd = CTRL, F2, Apple Display brightness up, exec, nomarchy-brightness-display-apple +5000
bindd = SHIFT CTRL, F2, Apple Display full brightness, exec, nomarchy-brightness-display-apple +60000
# Captures
bindd = , PRINT, Screenshot, exec, nomarchy-cmd-screenshot
bindd = ALT, PRINT, Screenrecording, exec, nomarchy-menu screenrecord
bindd = SUPER, PRINT, Color picker, exec, pkill hyprpicker || hyprpicker -a
# File sharing
bindd = SUPER CTRL, S, Share, exec, nomarchy-menu share
# Waybar-less information
bindd = SUPER CTRL ALT, T, Show time, exec, notify-send -u low " $(date +"%A %H:%M · %d %B %Y · Week %V")"
bindd = SUPER CTRL ALT, B, Show battery remaining, exec, notify-send -u low "$(nomarchy-battery-status)"
# Control panels
bindd = SUPER CTRL, A, Audio controls, exec, nomarchy-launch-audio
bindd = SUPER CTRL, B, Bluetooth controls, exec, nomarchy-launch-bluetooth
bindd = SUPER CTRL, W, Wifi controls, exec, nomarchy-launch-wifi
bindd = SUPER CTRL, T, Activity, exec, nomarchy-launch-tui btop
# Zoom
bindd = SUPER CTRL, Z, Zoom in, exec, hyprctl keyword cursor:zoom_factor $(hyprctl getoption cursor:zoom_factor -j | jq '.float + 1')
bindd = SUPER CTRL ALT, Z, Reset zoom, exec, hyprctl keyword cursor:zoom_factor 1
# Lock system
bindd = SUPER CTRL, L, Lock system, exec, nomarchy-lock-screen

View File

@@ -1,12 +0,0 @@
# Hyprland Color Configuration (Visual)
# This file contains only color-related settings.
# It's sourced AFTER theme colors are applied, allowing theme-specific overrides.
# These are placeholder values - actual colors come from the active theme
# via ~/.config/nomarchy/current/theme/hyprland.conf
# Border colors (set by theme-loader based on active theme)
# The theme's hyprland.conf will define $activeBorderColor and $inactiveBorderColor
# Group bar colors (set by theme)
# Themes can override group bar colors in their hyprland.conf

View File

@@ -1,29 +0,0 @@
# Cursor size
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
# Force all apps to use Wayland
env = GDK_BACKEND,wayland,x11,*
env = QT_QPA_PLATFORM,wayland;xcb
env = QT_STYLE_OVERRIDE,kvantum
env = SDL_VIDEODRIVER,wayland,x11
env = MOZ_ENABLE_WAYLAND,1
env = ELECTRON_OZONE_PLATFORM_HINT,wayland
env = OZONE_PLATFORM,wayland
env = XDG_SESSION_TYPE,wayland
# Allow better support for screen sharing (Google Meet, Discord, etc)
env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_DESKTOP,Hyprland
xwayland {
force_zero_scaling = true
}
# Use XCompose file
env = XCOMPOSEFILE,~/.XCompose
# Don't show update on first launch
ecosystem {
no_update_news = true
}

View File

@@ -1,146 +0,0 @@
# Refer to https://wiki.hyprland.org/Configuring/Variables/
# Variables
$activeBorderColor = rgba(33ccffee) rgba(00ff99ee) 45deg
$inactiveBorderColor = rgba(595959aa)
# https://wiki.hyprland.org/Configuring/Variables/#general
general {
gaps_in = 5
gaps_out = 10
border_size = 2
# https://wiki.hyprland.org/Configuring/Variables/#variable-types for info about colors
col.active_border = $activeBorderColor
col.inactive_border = $inactiveBorderColor
# Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
layout = dwindle
}
# https://wiki.hyprland.org/Configuring/Variables/#decoration
decoration {
rounding = 0
shadow {
enabled = true
range = 2
render_power = 3
color = rgba(1a1a1aee)
}
# https://wiki.hyprland.org/Configuring/Variables/#blur
blur {
enabled = true
size = 2
passes = 2
special = true
brightness = 0.60
contrast = 0.75
}
}
# https://wiki.hypr.land/Configuring/Variables/#group
group {
col.border_active = $activeBorderColor
col.border_inactive = $inactiveBorderColor
col.border_locked_active = -1
col.border_locked_inactive = -1
groupbar {
font_size = 12
font_family = monospace
font_weight_active = ultraheavy
font_weight_inactive = normal
indicator_height = 0
indicator_gap = 5
height = 22
gaps_in = 5
gaps_out = 0
text_color = rgb(ffffff)
text_color_inactive = rgba(ffffff90)
col.active = rgba(00000040)
col.inactive = rgba(00000020)
gradients = true
gradient_rounding = 0
gradient_round_only_edges = false
}
}
# https://wiki.hyprland.org/Configuring/Variables/#animations
animations {
enabled = yes
# Default animations, see https://wiki.hyprland.org/Configuring/Animations/ for more
bezier = easeOutQuint,0.23,1,0.32,1
bezier = easeInOutCubic,0.65,0.05,0.36,1
bezier = linear,0,0,1,1
bezier = almostLinear,0.5,0.5,0.75,1.0
bezier = quick,0.15,0,0.1,1
animation = global, 1, 10, default
animation = border, 1, 5.39, easeOutQuint
animation = windows, 1, 4.79, easeOutQuint
animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
animation = windowsOut, 1, 1.49, linear, popin 87%
animation = fadeIn, 1, 1.73, almostLinear
animation = fadeOut, 1, 1.46, almostLinear
animation = fade, 1, 3.03, quick
animation = layers, 1, 3.81, easeOutQuint
animation = layersIn, 1, 4, easeOutQuint, fade
animation = layersOut, 1, 1.5, linear, fade
animation = fadeLayersIn, 1, 1.79, almostLinear
animation = fadeLayersOut, 1, 1.39, almostLinear
animation = workspaces, 0, 0, ease
animation = specialWorkspace, 1, 4, easeOutQuint, slidevert
}
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
force_split = 2 # Always split on the right
}
# See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hyprland.org/Configuring/Variables/#misc
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
disable_scale_notification = true
focus_on_activate = true
}
# https://wiki.hypr.land/Configuring/Variables/#cursor
cursor {
hide_on_key_press = true
warp_on_change_workspace = 1
}
# Auto toggle scratchpad on switching workspace from scratchpad
binds {
hide_special_on_workspace_change = true
}
# Style Gum confirm to match terminal theme
env = GUM_CONFIRM_PROMPT_FOREGROUND,6 # Cyan
env = GUM_CONFIRM_SELECTED_FOREGROUND,0 # Black
env = GUM_CONFIRM_SELECTED_BACKGROUND,2 # Green
env = GUM_CONFIRM_UNSELECTED_FOREGROUND,7 # White
env = GUM_CONFIRM_UNSELECTED_BACKGROUND,8 # Dark grey
# Dark grey

View File

@@ -1,15 +0,0 @@
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# Hyprland 0.53+ syntax
#windowrulev2 = suppressevent maximize, class:.*
# Tag all windows for default opacity (apps can override with -default-opacity tag)
#windowrulev2 = tag +default-opacity, class:.*
# Fix some dragging issues with XWayland
#windowrulev2 = nofocus, class:^$, title:^$, xwayland:1, float:1, fullscreen:0, pin:0
# App-specific tweaks (may remove default-opacity tag)
source = ~/.config/nomarchy/default/hypr/apps.conf
# Apply default opacity after apps have had a chance to opt out
#windowrulev2 = opacity 0.97 0.9, tag:default-opacity

View File

@@ -1,37 +0,0 @@
anchor=top-right
default-timeout=5000
width=420
outer-margin=20
padding=10,15
border-size=2
max-icon-size=32
font=sans-serif 14px
[app-name=Spotify]
invisible=1
[mode=do-not-disturb]
invisible=true
[mode=do-not-disturb app-name=notify-send]
invisible=false
[urgency=critical]
default-timeout=0
layer=overlay
[summary~="Setup Wi-Fi"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Setup Wi-Fi"; nomarchy-launch-wifi'
on-click=exec sh -c 'nomarchy-notification-dismiss "Setup Wi-Fi"; nomarchy-launch-wifi'
[summary~="Update System"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Update System"; nomarchy-launch-floating-terminal-with-presentation nomarchy-update'
on-click=exec sh -c 'nomarchy-notification-dismiss "Update System"; nomarchy-launch-floating-terminal-with-presentation nomarchy-update'
[summary~="Learn Keybindings"]
on-button-left=exec sh -c 'nomarchy-notification-dismiss "Learn Keybindings"; nomarchy-menu-keybindings'
on-click=exec sh -c 'nomarchy-notification-dismiss "Learn Keybindings"; nomarchy-menu-keybindings'
[summary~="Screenshot copied & saved"]
max-icon-size=80
format=<b>%s</b>\n%b

View File

@@ -1,3 +0,0 @@
[Service]
Restart=always
RestartSec=2

View File

@@ -1,156 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"></requires>
<object class="GtkWindow" id="Window">
<style>
<class name="window"></class>
</style>
<property name="resizable">true</property>
<property name="title">Walker</property>
<child>
<object class="GtkBox" id="BoxWrapper">
<style>
<class name="box-wrapper"></class>
</style>
<property name="width-request">644</property>
<property name="overflow">hidden</property>
<property name="orientation">horizontal</property>
<property name="valign">center</property>
<property name="halign">center</property>
<child>
<object class="GtkBox" id="Box">
<style>
<class name="box"></class>
</style>
<property name="orientation">vertical</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
<property name="spacing">10</property>
<child>
<object class="GtkBox" id="SearchContainer">
<style>
<class name="search-container"></class>
</style>
<property name="overflow">hidden</property>
<property name="orientation">horizontal</property>
<property name="halign">fill</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
<child>
<object class="GtkEntry" id="Input">
<style>
<class name="input"></class>
</style>
<property name="halign">fill</property>
<property name="hexpand-set">true</property>
<property name="hexpand">true</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="ContentContainer">
<style>
<class name="content-container"></class>
</style>
<property name="orientation">horizontal</property>
<property name="spacing">10</property>
<property name="vexpand">true</property>
<property name="vexpand-set">true</property>
<child>
<object class="GtkLabel" id="ElephantHint">
<style>
<class name="elephant-hint"></class>
</style>
<property name="hexpand">true</property>
<property name="height-request">100</property>
<property name="label">Waiting for elephant...</property>
</object>
</child>
<child>
<object class="GtkLabel" id="Placeholder">
<style>
<class name="placeholder"></class>
</style>
<property name="label">No Results</property>
<property name="yalign">0.0</property>
<property name="hexpand">true</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="Scroll">
<style>
<class name="scroll"></class>
</style>
<property name="hexpand">true</property>
<property name="can_focus">false</property>
<property name="overlay-scrolling">true</property>
<property name="max-content-width">600</property>
<property name="max-content-height">300</property>
<property name="min-content-height">0</property>
<property name="propagate-natural-height">true</property>
<property name="propagate-natural-width">true</property>
<property name="hscrollbar-policy">automatic</property>
<property name="vscrollbar-policy">automatic</property>
<child>
<object class="GtkGridView" id="List">
<style>
<class name="list"></class>
</style>
<property name="max_columns">1</property>
<property name="can_focus">false</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="Preview">
<style>
<class name="preview"></class>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="Keybinds">
<property name="hexpand">true</property>
<property name="margin-top">10</property>
<style>
<class name="keybinds"></class>
</style>
<child>
<object class="GtkBox" id="GlobalKeybinds">
<property name="spacing">10</property>
<style>
<class name="global-keybinds"></class>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="ItemKeybinds">
<property name="hexpand">true</property>
<property name="halign">end</property>
<property name="spacing">10</property>
<style>
<class name="item-keybinds"></class>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel" id="Error">
<style>
<class name="error"></class>
</style>
<property name="xalign">0</property>
<property name="visible">false</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -1,117 +0,0 @@
@import "../../../../../../../.config/nomarchy/current/theme/walker.css";
* {
all: unset;
}
* {
font-family: monospace;
font-size: 18px;
color: @text;
}
scrollbar {
opacity: 0;
}
.normal-icons {
-gtk-icon-size: 16px;
}
.large-icons {
-gtk-icon-size: 32px;
}
.box-wrapper {
background: alpha(@base, 0.95);
padding: 20px;
border: 2px solid @border;
}
.preview-box {
}
.box {
}
.search-container {
background: @base;
padding: 10px;
}
.input placeholder {
opacity: 0.5;
}
.input {
}
.input:focus,
.input:active {
box-shadow: none;
outline: none;
}
.content-container {
}
.placeholder {
}
.scroll {
}
.list {
}
child,
child > * {
}
child:hover .item-box {
}
child:selected .item-box {
background-color: @selected-background;
}
child:selected .item-box * {
color: @selected-text;
}
.item-box {
padding-left: 14px;
}
.item-text-box {
all: unset;
padding: 14px 0;
}
.item-text {
}
.item-subtext {
font-size: 0px;
min-height: 0px;
margin: 0px;
padding: 0px;
}
.item-image {
margin-right: 14px;
-gtk-icon-transform: scale(0.9);
}
.current {
font-style: italic;
}
.keybind-hints {
background: @background;
padding: 10px;
margin-top: 10px;
}
.preview {
}

View File

@@ -1,7 +0,0 @@
[Desktop Entry]
Name=Walker
Comment=Walker Service
Exec=walker --gapplication-service
StartupNotify=false
Terminal=false
Type=Application

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
if pgrep -x hypridle >/dev/null; then
echo '{"text": ""}'
else
echo '{"text": "󱫖", "tooltip": "Idle lock disabled", "class": "active"}'
fi

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
if makoctl mode | grep -q 'do-not-disturb'; then
echo '{"text": "󰂛", "tooltip": "Notifications silenced", "class": "active"}'
else
echo '{"text": ""}'
fi

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
if pgrep -f "^gpu-screen-recorder" >/dev/null; then
echo '{"text": "󰻂", "tooltip": "Stop recording", "class": "active"}'
else
echo '{"text": ""}'
fi

View File

@@ -1,18 +0,0 @@
## Use software volume control for all ALSA devices.
## This prevents hardware mixer quirks (like muffled audio on Realtek codecs)
## and provides consistent volume behavior across all hardware.
monitor.alsa.rules = [
{
matches = [
{
device.name = "~alsa_card.*"
}
]
actions = {
update-props = {
api.alsa.soft-mixer = true
}
}
}
]

View File

@@ -1,29 +0,0 @@
include "%L"
# Emoji
<Multi_key> <m> <s> : "😄" # smile
<Multi_key> <m> <c> : "😂" # cry
<Multi_key> <m> <l> : "😍" # love
<Multi_key> <m> <v> : "✌️" # victory
<Multi_key> <m> <h> : "❤️" # heart
<Multi_key> <m> <y> : "👍" # yes
<Multi_key> <m> <n> : "👎" # no
<Multi_key> <m> <f> : "🖕" # fuck
<Multi_key> <m> <w> : "🤞" # wish
<Multi_key> <m> <r> : "🤘" # rock
<Multi_key> <m> <k> : "😘" # kiss
<Multi_key> <m> <e> : "🙄" # eyeroll
<Multi_key> <m> <d> : "🤤" # droll
<Multi_key> <m> <m> : "💰" # money
<Multi_key> <m> <x> : "🎉" # xellebrate
<Multi_key> <m> <1> : "💯" # 100%
<Multi_key> <m> <t> : "🥂" # toast
<Multi_key> <m> <p> : "🙏" # pray
<Multi_key> <m> <i> : "😉" # wink
<Multi_key> <m> <o> : "👌" # OK
<Multi_key> <m> <g> : "👋" # greeting
<Multi_key> <m> <a> : "💪" # arm
<Multi_key> <m> <b> : "🤯" # blowing
# Typography
<Multi_key> <space> <space> : "—"

View File

@@ -1,20 +0,0 @@
# Overwrite parts of the nomarchy-menu with user-specific submenus.
# See $OMARCHY_PATH/bin/nomarchy-menu for functions that can be overwritten.
#
# WARNING: Overwritten functions will obviously not be updated when Nomarchy changes.
#
# Example of minimal system menu:
#
# show_system_menu() {
# case $(menu "System" " Lock\n󰐥 Shutdown") in
# *Lock*) nomarchy-lock-screen ;;
# *Shutdown*) nomarchy-system-shutdown ;;
# *) back_to show_main_menu ;;
# esac
# }
#
# Example of overriding just the about menu action: (Using zsh instead of bash (default))
#
# show_about() {
# exec nomarchy-launch-or-focus-tui "zsh -c 'fastfetch; read -k 1'"
# }

View File

@@ -1,10 +0,0 @@
#!/bin/bash
# This hook is called with the current battery percentage when the low battery
# notification is sent. To put it into use, remove .sample from the name.
SOUND_FILE="/usr/share/sounds/freedesktop/stereo/dialog-warning.oga"
if nomarchy-cmd-present mpv && [[ -f $SOUND_FILE ]]; then
mpv --no-video "$SOUND_FILE" >/dev/null 2>&1
fi

View File

@@ -1,7 +0,0 @@
#!/bin/bash
# This hook is called with the snake-cased name of the font that has just been set.
# To put it into use, remove .sample from the name.
# Example: Show the name of the theme that was just set.
# notify-send -u low "New font" "Your new font is $1"

View File

@@ -1,9 +0,0 @@
#!/bin/bash
# This hook is called after the nomarchy-welcome wizard finishes successfully.
# Use it to run any custom first-boot logic (e.g. cloning your dotfiles repo,
# setting up SSH keys, or launching specific initial apps).
# Example: Open the manual and show a notification
# nomarchy-manual
# notify-send "Welcome home" "Your Nomarchy system is ready to use."

View File

@@ -1,7 +0,0 @@
#!/bin/bash
# This hook is called after an Nomarchy system update has been performed.
# To put it into use, remove .sample from the name.
# Example: Show notification after the system has been updated.
# notify-send -u low "Update Performed" "Your system is now up to date"

View File

@@ -1,7 +0,0 @@
#!/bin/bash
# This hook is called with the snake-cased name of the theme that has just been set.
# To put it into use, remove .sample from the name.
# Example: Show the name of the theme that was just set.
# notify-send -u low "New theme" "Your new theme is $1"

View File

@@ -1,86 +0,0 @@
# Nomarchy User Template Example: Alacritty
#
# This is a sample template file demonstrating how to create custom themed
# configurations. Templates use placeholder variables that get replaced with
# your current theme's colors when you switch themes.
#
# HOW TO USE:
# 1. Rename this file to: alacritty.toml.tpl (remove the .sample extension)
# 2. Customize it as needed
# 3. Change your theme to apply the template
#
# User templates in ~/.config/nomarchy/themed/ take priority over the built-in
# templates in the Nomarchy default/themed/ directory. This lets you override
# any built-in template or create templates for applications not included
# by default.
#
# AVAILABLE VARIABLES:
# {{ background }} - Main background color (e.g., "#1a1b26")
# {{ foreground }} - Main foreground/text color
# {{ cursor }} - Cursor color
# {{ accent }} - Theme accent color
# {{ selection_background }} - Selection highlight background
# {{ selection_foreground }} - Selection highlight foreground
#
# {{ color0 }} through {{ color15 }} - Standard 16-color terminal palette
# color0-7: Normal colors (black, red, green, yellow, blue, magenta, cyan, white)
# color8-15: Bright variants
#
# VARIABLE MODIFIERS:
# {{ variable_strip }} - Hex color without the # prefix (e.g., "1a1b26")
# {{ variable_rgb }} - Decimal RGB values (e.g., "26,27,38")
#
# Example using modifiers:
# background = "{{ background }}" -> background = "#1a1b26"
# background = "{{ background_strip }}" -> background = "1a1b26"
# background = "rgb({{ background_rgb }})" -> background = "rgb(26,27,38)"
#
# ---------------------------------------------------------------------------
[colors.primary]
background = "{{ background }}"
foreground = "{{ foreground }}"
[colors.cursor]
text = "{{ background }}"
cursor = "{{ cursor }}"
[colors.vi_mode_cursor]
text = "{{ background }}"
cursor = "{{ cursor }}"
[colors.search.matches]
foreground = "{{ background }}"
background = "{{ color3 }}"
[colors.search.focused_match]
foreground = "{{ background }}"
background = "{{ color1 }}"
[colors.footer_bar]
foreground = "{{ background }}"
background = "{{ foreground }}"
[colors.selection]
text = "{{ selection_foreground }}"
background = "{{ selection_background }}"
[colors.normal]
black = "{{ color0 }}"
red = "{{ color1 }}"
green = "{{ color2 }}"
yellow = "{{ color3 }}"
blue = "{{ color4 }}"
magenta = "{{ color5 }}"
cyan = "{{ color6 }}"
white = "{{ color7 }}"
[colors.bright]
black = "{{ color8 }}"
red = "{{ color9 }}"
green = "{{ color10 }}"
yellow = "{{ color11 }}"
blue = "{{ color12 }}"
magenta = "{{ color13 }}"
cyan = "{{ color14 }}"
white = "{{ color15 }}"

View File

@@ -1,32 +0,0 @@
add_newline = true
command_timeout = 200
format = "[$directory$git_branch$git_status]($style)$character"
[character]
error_symbol = "[✗](bold cyan)"
success_symbol = "[](bold cyan)"
[directory]
truncation_length = 2
truncation_symbol = "…/"
repo_root_style = "bold cyan"
repo_root_format = "[$repo_root]($repo_root_style)[$path]($style)[$read_only]($read_only_style) "
[git_branch]
format = "[$branch]($style) "
style = "italic cyan"
[git_status]
format = '[$all_status]($style)'
style = "cyan"
ahead = "⇡${count} "
diverged = "⇕⇡${ahead_count}⇣${behind_count} "
behind = "⇣${count} "
conflicted = " "
up_to_date = " "
untracked = "? "
modified = " "
stashed = ""
staged = ""
renamed = ""
deleted = ""

View File

@@ -1,13 +0,0 @@
# Changes require a restart to take effect.
# Install other terminals via Install > Terminal
export TERMINAL=xdg-terminal-exec
# Use code for VSCode
export EDITOR=nvim
# Use a custom directory for screenshots (remember to make the directory!)
# export OMARCHY_SCREENSHOT_DIR="$HOME/Pictures/Screenshots"
# Use a custom directory for screenrecordings (remember to make the directory!)
# export OMARCHY_SCREENRECORD_DIR="$HOME/Videos/Screencasts"

Some files were not shown because too many files have changed in this diff Show More