Compare commits

...

375 Commits

Author SHA1 Message Date
Bernardo Magri
4ebd6770ec feat(nightlight): geo mode — lat/long-computed sunset via wlsunset
All checks were successful
Check / eval (push) Successful in 4m20s
Set BOTH nomarchy.nightlight.latitude/.longitude and the night light
switches backing unit: wlsunset computes sunrise/sunset from the
location daily (the fixed .sunrise/.sunset are ignored;
.temperature feeds the night temp). Everything user-facing is
unchanged — same nomarchy-nightlight toggle script, Waybar moon, and
live-state ExecCondition gate; the unit name is a single let-binding
so all three follow together. Declarative-only on purpose: coordinates
are machine config like the keyboard layout, so home.nix (commented
template example), not a menu writer.

checks.nightlight-geo guards the wiring at pure eval: rendered
wlsunset ExecStart carries the coords + night temperature, the
ExecCondition gate landed on the swapped unit, hyprsunset stays off in
geo mode.

Verification: V1 — nix flake check --no-build exit 0 (evals the new
asserts + option-docs sync); built downstream-template-home (default
hyprsunset path unregressed). V3 pending: visible warm shift at the
location's night + instant toggle/off-persistence on the swapped unit
(HARDWARE-QUEUE § Any machine).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:30:52 +01:00
Bernardo Magri
4c656b1e73 chore(agent): sync-sweep rule on task completion + apply it; zram user docs
All checks were successful
Check / eval (push) Successful in 4m45s
New standing rule (LOOP.md §5 step 4, echoed in AGENTS.md): when a task
ships, grep its number/feature name across agent/ and docs/ and update
or delete every cross-reference it made stale, in the same commit —
including pruning checked-off [x] HARDWARE-QUEUE entries whose outcome
is recorded (journal/ROADMAP; git history is the archive).

First application: BACKLOG PROPOSED groomed (the auto time-of-day theme
pitch was stale — shipped as #79; shipped/promoted placeholder lines
removed; v1.0 pointer rewritten; FUTURE promoted-stub dropped);
HARDWARE-QUEUE pruned of 29 checked entries (363→253 lines) with
dangling references reworded and the prune rule documented in its
header.

Also closes the zram user-doc gap: README §3 now documents the zram
default (zstd/50%/prio 100), hibernation working out of the box on new
installs, and the MIGRATION.md runbook for existing machines.

Verification: V0 (docs/meta only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:20:54 +01:00
Bernardo Magri
5ef66c242e chore(agent): drop .claude/skills — AGENTS.md carries the routing
All checks were successful
Check / eval (push) Successful in 4m44s
The skill shims became redundant once the auto-loaded entry point
(CLAUDE.md → AGENTS.md) explicitly instructs reading VERIFICATION.md /
THEME-DESIGN.md; their only remaining value was explicit /nomarchy and
/theme invocation. The adapter is now permissions + subagent defs only.
References updated (AGENTS.md, agent/README.md, docs/README.md, README
tree, MEMORY.md hygiene note).

Verification: V0 (docs/meta only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:06:32 +01:00
Bernardo Magri
dafa83e922 docs(agent): AGENTS.md explicitly requires reading VERIFICATION.md first
Some checks failed
Check / eval (push) Has been cancelled
The enforcement rules must not depend on any harness's skill/trigger
mechanism — the auto-loaded entry point itself now instructs every
agent to read agent/VERIFICATION.md before its first change (and
THEME-DESIGN.md / TESTING.md where relevant), instead of merely
pointing at them.

Verification: V0 (docs-only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:04:14 +01:00
Bernardo Magri
c0fc16e25c docs(agent): vendor-neutral agent docs — AGENTS.md entry, agent/ SoT, .claude shims
All checks were successful
Check / eval (push) Successful in 4m28s
The repo is maintained by agents from multiple vendors, so agent
instructions move out of vendor-specific locations into shared,
git-tracked markdown:

- AGENTS.md is the new entry point for any harness; CLAUDE.md becomes a
  symlink to it (Claude Code keeps working unchanged).
- Skill bodies relocate to agent/: VERIFICATION.md (the enforcement
  rules, ex .claude/skills/nomarchy), DELEGATION.md (capability tiers
  light/standard/frontier, scout/runner role contracts, token economy,
  parallel fan-out — consolidates the CLAUDE.md model table, LOOP.md's
  economy section, and skill §6.5 into one place; vendor model names
  survive only in the per-harness mapping table), THEME-DESIGN.md
  (ex .claude/skills/theme, which previously lacked frontmatter).
- .claude/ shrinks to a thin Claude Code adapter: settings, subagent
  defs, and skill shims that route into agent/.
- Maps updated: agent/README.md (instructions vs state vs adapters),
  docs/README.md, README.md layout tree.

Cleanup: stale old_distro .gitignore entry dropped (dir long gone);
local result*/__pycache__ artifacts removed; JOURNAL.md rotated (29
recent entries kept, 120 older moved to agent/JOURNAL-ARCHIVE.md,
rotation rule documented in the header).

Verification: V0 (docs/meta only) — nix flake check --no-build exit 0
with the tracked symlink; grep sweep confirms no vendor model names
outside DELEGATION.md's mapping table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 08:58:25 +01:00
Bernardo Magri
37615c85a4 test(hibernate): #76 checks.hibernate-swapfile — verify swapfile+offset in a VM
All checks were successful
Check / eval (push) Successful in 4m18s
Push #76's swapfile-resume residual into a VM as far as headlessly possible.
A full hibernate→resume from a RUNTIME swapfile turned out not to be
headless-reachable: systemd reports CanHibernate=no for a swap that isn't
declared in the boot config, and a runtime-computed resume_offset can't be
fed into the boot cmdline (the genuine chicken-and-egg). So the check now
verifies, bounded and deterministically, what the partition test
(checks.hibernate) can't and what a VM CAN prove: the installer's btrfs
@swap swapfile is built correctly and its offset is valid — NOCOW
mkswapfile, file-type swapon, a good `map-swapfile -r` offset the kernel
accepts (/sys/power/resume_offset), and zram (prio 100) above it.

The literal encrypted-swapfile power-cycle + LUKS-initrd unlock stays a V3
laptop check (the dev box runs exactly this layout, /proc/cmdline verified).

(First cut had an unbounded wait_for_shutdown that hung on the CanHibernate
refusal; rewritten with a thread-bounded wait + a CanHibernate pre-check,
which gave the clean verdict. Device is /dev/vdb without useNixStoreImage.)

Verification: V2 PASS (nix build .#checks.x86_64-linux.hibernate-swapfile,
fresh, exit 0); flake check --no-build green. #76 residual narrowed in
BACKLOG/ROADMAP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 08:34:17 +01:00
Bernardo Magri
2fa5231215 fix(theme): #79 review fixes — wrap-around schedule + HH:MM guard
All checks were successful
Check / eval (push) Successful in 4m6s
Two low-severity edge bugs from a fresh-eyes review of the session batch:

- cmd_auto: an inverted schedule (sunrise > sunset, i.e. a day window that
  crosses midnight) stayed perpetually on the day theme — the old
  `not between` shortcut is an empty interval when sunrise > sunset.
  Replaced with a real wrap-around: daytime = mins >= sunrise or
  mins < sunset, so both directions switch. Normal sunrise < sunset path
  is unchanged (checks.auto-theme still holds).
- rofi autotheme HH:MM validator: `[0-2][0-9]` accepted 24:00–29:59.
  Tightened the case pattern to `[01][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9]`
  so out-of-range hours are rejected (they'd otherwise skew the boundary).

(Two other review findings dismissed: papirus DOES ship breeze/breeze-dark
so that iconPacks prefix is correct; the doctor RAM-unknown row is cosmetic
since /proc/meminfo is always readable.)

Verification: py_compile; boundary truth-table (normal + wrap-around);
HH:MM case table (23:00/19:59/20:00 accept, 24/25/30:00 reject); auto
--which normal-case unchanged; menu bash -n; flake check green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 08:19:05 +01:00
Bernardo Magri
808990592d docs(overrides): user guide for auto day/night theme (#79)
All checks were successful
Check / eval (push) Successful in 3m51s
Add an "Auto theme (day/night)" subsection to docs/OVERRIDES.md § 1
(Appearance), after the Icon pack section. Covers what it does (light/dark
switch on a schedule, same one engine), how to turn it on (Look & Feel ›
Auto theme, or the shell set/auto sequence), the settings.autoTheme
fields, and behavior (periodic ~15-min re-check, idempotent rebuilds,
instant disable). Names/fields/flags verified against rofi.nix,
nomarchy-theme-sync.py, and the icon section's style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:31:21 +01:00
Bernardo Magri
49e0061dbb test(auto-theme): #79 V2 — simulate sunset in a VM (was mis-framed V3)
All checks were successful
Check / eval (push) Successful in 3m53s
Bernardo rightly pushed back on deferring #79 to a real session: a
simulated sunset IS VM-testable. Add checks.auto-theme, a runNixOSTest
that installs nomarchy-theme-sync, points NOMARCHY_DEFAULT_THEMES at the
presets, stubs the rebuild via NOMARCHY_REBUILD (a marker), configures
the day/night pair + schedule, then WALKS THE VM CLOCK across the
boundary: 19:55 -> 20:05 -> 20:06 -> next-day 07:30, asserting the active
theme goes summer-day -> night (1 rebuild) -> night (idempotent, still 1)
-> day (2 rebuilds).

This isolates the auto decision + apply + rebuild-trigger; the actual
home-manager switch is stubbed (that generic apply path is exercised by
every manual theme change). The only on-hardware residual is the user
timer firing on its OnCalendar schedule, which systemd-analyze validates.

#79 drops its [blocked:hw] tag — it's V2-verified. ROADMAP updated;
lesson recorded in MEMORY (don't default timer/session features to V3:
stub the generic step, simulate the clock).

Verification: V2 PASS (nix build .#checks.x86_64-linux.auto-theme, fresh,
exit 0); V0 flake check green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:04:38 +01:00
Bernardo Magri
860c70466f feat(menu): #79 slice 3 — Look & Feel › Auto theme (completes #79)
All checks were successful
Check / eval (push) Successful in 3m46s
Adds an "Auto theme (on/off)" row to Look & Feel and an `autotheme`
submenu: toggle the feature, pick the day/night themes (from
`nomarchy-theme-sync list`), and set the sunrise/sunset times (HH:MM,
validated). This is the menu surface over settings.autoTheme, completing
the auto time-of-day theme feature (slices: CLI / timer / menu).

Enabling writes the flag and defaults the summer-day/summer-night pair
--no-switch, then runs `auto --force`, so a single rebuild both installs
the timer and applies the correct theme for now. Disabling is instant
(--no-switch): the flag flips and `auto` self-gates, so the lingering
timer no-ops until the next rebuild. Day/night/time writes are live
--no-switch (read by `auto` at tick time).

Verification: V1 — HM generation builds (bash -n on nomarchy-menu at
build) + bash -n on the built menu. Logic test with stubbed
rofi/theme-sync/notify: selecting the toggle from off runs exactly `set
day/night/enable ... --no-switch` then `auto --force` — the intended
one-rebuild path. V0 flake check green. All three #79 slices shipped;
the live timer switch across sunset is a real-session (V3) check on a
Nomarchy machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:53:37 +01:00
Bernardo Magri
c44616aeb0 feat(autotheme): #79 slice 2 — systemd timer runs auto
All checks were successful
Check / eval (push) Successful in 3m53s
modules/home/autotheme.nix installs a user service + timer
(nomarchy-auto-theme) that periodically runs `nomarchy-theme-sync auto`,
so the day/night theme is applied on the current schedule through the
same one engine. Install gates on settings.autoTheme.enable (read from
state, the way updates.nix gates on its enable), so a machine not using
the feature carries no timer.

Timer: OnStartupSec=1min (settle after login) + OnCalendar=*:0/15 +
Persistent (catch a transition missed while off). Periodic rather than
exact sunrise/sunset timers — robust to suspend/DST, and `auto` is
idempotent (no rebuild unless the theme must change) so ticks are cheap.

Verification: V1 — real-config eval (state temporarily enabled, then
restored) confirms the units land when enabled (OnCalendar *:0/15,
ExecStart `... nomarchy-theme-sync auto`) and are absent when disabled;
systemd-analyze validates the schedule. V0 flake check green. The live
timer -> home-manager switch on a real Nomarchy session is pending a
Nomarchy machine (same blocker as #76).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:34:56 +01:00
Bernardo Magri
429de59b52 feat(theme-sync): #79 slice 1 — auto day/night theme primitive
All checks were successful
Check / eval (push) Successful in 3m58s
First slice of the auto time-of-day theme pair (VISION § D, split into
CLI / timer / menu). `nomarchy-theme-sync auto` reads
settings.autoTheme.{enable,day,night,sunrise,sunset}, picks day vs night
from the local clock, and applies the matching preset — but only if it
differs from the current one, so the slice-2 timer can fire freely
without triggering needless rebuilds. `--which` prints the decision
without switching; `--force` and `--no-switch` included.

cmd_apply's core is factored into apply_named() so `auto` drives the
exact same one engine (no second pipeline). apply behavior is unchanged.

Verification: V1 (package builds) + functional tests on the built binary
against scratch state — wide-day window → day slug, a 00:00–00:01 day
span → night, disabled → no-op, missing day/night → clear die, auto
--no-switch writes state, a re-run no-ops as "already on", and plain
apply still writes (refactor intact). V0 py_compile + flake check +
checks.theme-sync-validate green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:20:55 +01:00
Bernardo Magri
79d73cd623 docs(omarchy): #78 Omarchy → Nomarchy migrant one-pager
All checks were successful
Check / eval (push) Successful in 3m47s
Add docs/OMARCHY.md for users coming from Omarchy (VISION § F). Leads
with the one real difference — imperative dotfiles vs a declarative flake
built via the menu, with rollback through NixOS generations — then maps
the deltas: install (ISO/nomarchy-install or MIGRATION.md), a familiar
keybindings table, theming (SUPER+T / nomarchy-theme-sync), installing
software (home.packages / nix shell, no AUR), and update+rollback
(nomarchy-pull/-rebuild/-home, boot-menu generations, nomarchy-doctor).
Registered in docs/README.md; VISION § F marked shipped.

The Nomarchy side is grounded in the code: keybindings pulled from
keybinds.nix, and every cited command (7), the nsearch alias, and the
named theme slugs (gruvbox/rose-pine/nord) verified to exist. Omarchy-side
claims kept to known facts (Arch, dotfiles, pacman/AUR).

Verification: V0 (docs-only); commands/slugs/binds checked against the tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:15:36 +01:00
Bernardo Magri
e841251399 feat(doctor): #77 hibernate/sleep section (VISION § C)
All checks were successful
Check / eval (push) Successful in 3m41s
nomarchy-doctor gains a read-only hibernate/sleep section (the highest-ROI
Day-2 gap, a natural #76 follow-on): resume device on the kernel cmdline
(+ resume_offset for a swapfile), disk swap >= RAM, zram active, and any
suspend/hibernate error in the previous boot's kernel log. Self-gates
(skip on swap=0) and emits only ok/warn/skip, so it never fails the sheet.
Adds `hibernate` to the checks.doctor probe list so the row is asserted.

Fixes a set -euo pipefail hazard the first VM run caught (exit 1, no
verdict): a no-match grep inside $(...) and a `cond && action` both abort
under set -e — now guarded with || true / || echo 0 / if. Recorded in
MEMORY as a reusable gotcha for writeShellScriptBin scripts.

Verification: V2 PASS — checks.doctor VM test green (fresh build, exit 0;
asserts the hibernate row renders, healthy run stays 0). V1 — ran on real
LUKS+@swap hardware: all three ok rows render (resume set, swap 34G >= RAM,
zram 16G) and the sheet reaches its verdict. V0 bash -n + flake check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:06:45 +01:00
Bernardo Magri
cf97f5605d docs(agent): clean QA sweep + VISION-gap pass (3 PROPOSED)
All checks were successful
Check / eval (push) Successful in 3m44s
QA/drift sweep: deterministic checks + bash -n/py_compile all pass
(the 4 "needs-args" scripts pass via their flake derivations); drift
recon found none (dead code, stale refs, TODOs, changed-area
contradictions, counts all clean — 24 presets/previews consistent);
eval sweep green. No fixes needed.

VISION-gap pass: verified candidates against the tree first, which
killed two false gaps (nomarchy-rebuild/home already tail logs + point
at doctor on failure = §A; nvd diff = §B; Firmware menu already exists).
Filed three verified-open PROPOSED items: doctor hibernate/sleep section
(§C, natural #76 follow-on), Omarchy migrant one-pager (§F), auto
time-of-day theme pair (§D, big). Await human triage.

Verification: V0 (agent-notes/docs only); flake check --no-build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:51:36 +01:00
Bernardo Magri
dc33694a56 docs(overrides): user guide for opt-in icon packs
All checks were successful
Check / eval (push) Successful in 3m24s
The only doc for the icon-pack override was a comment in the downstream
template. Add an "Icon pack" subsection to docs/OVERRIDES.md § Appearance
(the user-facing appearance-customization doc): the
`nomarchy-theme-sync set icons "<name>"` command, why only Papirus ships
by default (~1 GiB; extra packs opt-in), a known-packs table, and that
the choice is sticky across `apply` (presets carry no icon field).

Example names verified by building all four packs and listing their real
theme directories — corrected Qogir to its capitalized Qogir-Dark/Light
(a lowercase name silently falls back to hicolor). Hibernation needs no
new doc (README swapfile note + MIGRATION.md runbook + Power-menu row).

Verification: V0 (docs-only); command + variant names checked live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:34:00 +01:00
Bernardo Magri
7eb9b8ad4f feat(theme): opt-in cross-pack icon overrides, no default bloat
All checks were successful
Check / eval (push) Successful in 3m39s
theme.nix now resolves nomarchy.theme.iconThemePackage from a small
icon-pack registry (Papirus/Tela/Qogir/Reversal/Numix, matched by name
prefix). Default themes name Papirus-*, so the resolved package is the
plain papirus-icon-theme derivation — byte-identical closure, zero added
MB. Only when a theme's `icons` names a set from another pack is that
pack (and only it) symlinkJoined alongside papirus. stylix.nix consumes
it via mkDefault. Adding a pack is one iconPacks row.

Deliberately NOT shipping more packs by default: papirus alone is already
~1 GiB and each extra pack adds ~hundreds of MB, so variety is opt-in per
install (set `icons` in theme-state.json), not a default closure cost.
Documented in templates/downstream/home.nix + ROADMAP § Icon themes.

Verification: V1 (HM activationPackage builds). Eval proofs — default
resolves to plain papirus (zero delta); icons="Tela-dark" ->
nomarchy-icon-themes symlinkJoin (papirus+tela) with the name flowing,
without building tela; all 5 pack attrs exist in the pinned nixpkgs;
template-sot + option-docs + flake check green. No default visual change
(same papirus), so no screenshot; the opt-in render is downstream.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 20:12:52 +01:00
Bernardo Magri
aff50e6070 docs(agent): correct #76 V3 scope — dev box isn't a Nomarchy install
All checks were successful
Check / eval (push) Successful in 4m20s
The dev box runs Bernardo's own NixOS (TuringMachine, 25.11), not
Nomarchy — so hibernating it tests that config's hibernate, not
Nomarchy's oom.nix/patch-template/rofi wiring, and can't close #76's V3.
Its layout (LUKS cryptroot + btrfs @ + /swap/swapfile + resume_offset)
does resume, which corroborates the MIGRATION.md runbook, but nothing
more. Rescope the two #76 HARDWARE-QUEUE entries to require a machine
actually running Nomarchy (a Latitude install, or the dev box after
migration), and fix the matching reference in the BACKLOG #76 stub.

Verification: V0 (docs/agent-notes only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:53:23 +01:00
Bernardo Magri
ba8963a385 test(hibernate): #76 slice 3 — V2 hibernate→resume VM check (PASS)
All checks were successful
Check / eval (push) Successful in 4m11s
Add checks.hibernate: a runNixOSTest modeled on nixpkgs' hibernate.nix,
wired to the actual risk #76 introduces — coexistence with the zram swap
that oom.nix now enables by default. It imports oom.nix, puts the disk
swap at a lower priority than zram with an explicit boot.resumeDevice,
and uses systemd-initrd. The test drives a full
hibernate -> power-off -> resume -> crash cycle: a volatile ramfs marker
survives resume but NOT a crash-boot (proving genuine resume), and it
asserts zram sits at priority 100 while the disk swap is lower, so the
hibernate image lands on disk rather than volatile zram.

What QEMU genuinely can't do — feed a boot-time resume_offset for a btrfs
swapfile (offset only exists after first-boot allocation) or drive a
LUKS-initrd unlock — stays a V3 laptop check (HARDWARE-QUEUE, dev box
already runs that exact layout), plus the no-swap notify in a real
session. With this, all V0/V1/V2 work on #76 is done; #76 collapses to a
[blocked:hw] stub and the design record moves to ROADMAP.

Verification: V2 PASS — nix build .#checks.x86_64-linux.hibernate built
the driver fresh and exited 0. nix flake check --no-build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:45:47 +01:00
Bernardo Magri
2d0cb48d5c feat(menu): #76 slice 2 — Hibernate notifies on failure
All checks were successful
Check / eval (push) Successful in 4m7s
Per the settled #76 UX call: keep the Power-menu Hibernate row
unconditional, but when hibernate fails (typically a swap=0 machine with
no disk swap), surface a notification pointing at the enable-hibernation
runbook instead of a silent no-op. On success `systemctl hibernate`
blocks until resume, so the notify only fires on a real failure.

Verification: V1 — homeConfigurations.nomarchy.activationPackage builds
(rebuilds nomarchy-menu; writeShellScriptBin runs bash -n). Control-flow
proved on the arm extracted from the built script: failing systemctl ->
notify fires, succeeding -> no notify. flake check --no-build green.
V2 hibernate->resume VM test + V3 laptop remain open (#76 slice 3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:36:52 +01:00
Bernardo Magri
8c048a2692 docs(migration): #76 slice 1 — enable-hibernation runbook
Some checks failed
Check / eval (push) Has been cancelled
Add a self-contained "Enabling hibernation on an existing machine (no
reinstall)" section to docs/MIGRATION.md, per the settled #76 call to
document migration rather than ship a tool. Covers: @swap subvolume
creation (subvolid=5), btrfs mkswapfile (NOCOW), reading the resume
offset via map-swapfile, and wiring fileSystems."/swap" + swapDevices +
resumeDevice + resume_offset into system.nix. Flags the /swap mount as
required on the hand-edit path (a fresh install inherits it from
disko-generated hardware-config). Notes the zram-priority reservation,
a no-LUKS variant, and the swap=0 opt-out.

Verification: V0. All read-only commands run live against this machine's
LUKS(cryptroot)+btrfs(@)+/swap/swapfile layout; nix flake check
--no-build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:34:14 +01:00
Bernardo Magri
858362cfcc docs(agent): #76 hibernation design calls settled
All checks were successful
Check / eval (push) Successful in 4m8s
Bernardo's three design calls on the hibernation half of #76:
- swap sizing = exactly RAM (installer default, no change)
- migration for existing machines = docs runbook (MIGRATION.md), not a tool
- no-swap Hibernate = keep menu row, notify on failure

Recon confirmed new installs are already hibernation-ready (installer +
patch-template resume wiring + Power-menu Hibernate row). Folded decisions
into BACKLOG #76; re-sliced remainder to MIGRATION.md runbook + notify-on-
failure menu; recorded in Decisions.

Verification: V0 (docs-only) nix flake check --no-build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 19:28:26 +01:00
Bernardo Magri
2055842c33 feat(memory): enable zram compressed-RAM swap by default (#76 slice)
All checks were successful
Check / eval (push) Successful in 4m1s
First slice of BACKLOG #76 (hibernation + zram, split): zramSwap on by
default in modules/nixos/oom.nix — the memory-pressure layer before
earlyoom. zstd @ 50% RAM (nixpkgs defaults, explicit for reproducibility),
priority 100 so day-to-day paging fills zram first and any disk swapfile
stays reserved for #76's hibernate image (hibernation can't resume from
volatile zram). Follows the earlyoom mkDefault precedent — no toggle.

Adds checks.zram-swap runNixOSTest importing oom.nix, asserting the device
is an active swap, uses [zstd], and carries priority 100.

Verified: V2 checks.zram-swap PASS (/dev/zram0 in /proc/swaps, zstd,
prio 100); V1 full distro toplevel builds; V0 flake check --no-build green.
#76 remainder (disk swapfile default + resume + installer) still open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:07:55 +01:00
Bernardo Magri
59975a6a59 docs(agent): close #41 — OpenFile portal float confirmed on hardware
All checks were successful
Check / eval (push) Successful in 3m1s
Bernardo verified the GTK file-chooser (OpenFile) portal floats on real
hardware; the xdg-desktop-portal-gtk float/center rule already shipped in
hyprland.nix. Drop the "OpenFile not re-probed / optional residual"
caveats in HARDWARE-QUEUE and journal the V3 close. Bookkeeping only, no
code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:58:36 +01:00
5c5e1f797c docs(agent): #76 require VM hibernate verify; LUKS+@swap reference
All checks were successful
Check / eval (push) Successful in 2m58s
Agent must V2-test hibernation in the VM (encrypted BTRFS @swap
swapfile). Reference layout from live Latitude: no zram yet; swap
inside LUKS. MEMORY pointer for implementers.
2026-07-10 14:51:59 +01:00
6356f9b408 docs(agent): NEXT #76 hibernation default + zram
All checks were successful
Check / eval (push) Successful in 2m51s
Promote hibernation+zram product intent to the queue head; [big] with
design/module/installer/V3 slices. FUTURE stub points at #76.
2026-07-10 14:46:26 +01:00
f0f568f8d5 docs(agent): FUTURE formatter; hibernation intent + zram note
Some checks failed
Check / eval (push) Has been cancelled
Formatter yes-but-later. Hibernation wanted by default (disk swap);
zram compatible alongside for live pressure, not as sole swap.
2026-07-10 14:43:36 +01:00
ac3b75b018 feat(template): ship Chromium by default; record product decisions
All checks were successful
Check / eval (push) Successful in 2m49s
- home.packages: chromium active; mime → chromium-browser.desktop
- Decisions: markdown docs stay in-repo; PPD default confirmed
- Formatter + zram still open (explained in session, not closed)

V0: flake check --no-build green.
2026-07-10 14:37:52 +01:00
b37a50c779 docs(agent): complete FUTURE #20 section; fix nomarchy-pull README line
All checks were successful
Check / eval (push) Successful in 2m54s
FUTURE block with #20 deferred decision; README day-to-day list
matches pull = flake inputs only.
2026-07-10 14:29:52 +01:00
d5ee59fcb1 docs(agent): defer #20 KVM CI to FUTURE; keep eval-only VPS
Some checks failed
Check / eval (push) Has been cancelled
Standing decision: IONOS Gitea/act_runner docker stays eval-tier.
vm-checks needs a separate KVM host later — not the 4G compose box.
2026-07-10 14:29:37 +01:00
8002757ca3 docs(agent): tick HARDWARE-QUEUE for Latitude 5310 2026-07-10 QA
All checks were successful
Check / eval (push) Successful in 2m57s
Mark PASSes from the session: charge Custom type, bar/menus/capture,
polkit, combined power, mixer, arrows, SUPER+?, caffeine, doctor,
network blanks, lifecycle CLIs. Leave residual theme/ISO/greeter/etc.
2026-07-10 14:20:30 +01:00
c41c8abaa7 fix(cli): lifecycle tools in HM so stale pull can self-heal
All checks were successful
Check / eval (push) Successful in 2m57s
Extract nomarchy-pull/rebuild/home to pkgs/nomarchy-lifecycle; install
via system + home. Home profile wins on PATH so home-manager switch
installs the fixed pull (skip git when no upstream) without waiting
for a system rebuild that needed a working pull first.

V0: flake check --no-build green.
2026-07-10 14:13:00 +01:00
45c584db26 fix(power): Dell charge limit needs Custom type, not Adaptive
All checks were successful
Check / eval (push) Successful in 2m58s
charge_control_end_threshold was written (80) but charge_types stayed
[Adaptive], which ignores the cap while still reporting it — battery
kept charging past 80% on Latitude. Oneshot now selects Custom when a
limit is set, Adaptive/Standard when off; sets start hysteresis; doctor
warns if type ≠ Custom while limited.

V0: flake check --no-build green. V3: re-apply limit, confirm
charge_types shows [Custom] and status stops Charging above cap.
2026-07-10 14:01:19 +01:00
44264428af feat(hyprland): float GTK portal file chooser; close #41
All checks were successful
Check / eval (push) Successful in 2m50s
Class xdg-desktop-portal-gtk (+ Nix .…-wrapped) from package desktop/
libexec. Polkit already V3 PASS on Latitude. NEXT only #20 remains.

V0: flake check --no-build green.
2026-07-10 13:57:22 +01:00
84c145467e docs(agent): clock tooltip V3 PASS on Latitude
Some checks failed
Check / eval (push) Has been cancelled
2026-07-10 13:54:35 +01:00
0834038072 fix(waybar): clock tooltip uses single chrono format block
All checks were successful
Check / eval (push) Successful in 2m57s
Two bare `{:%…}` specs emptied the hover tooltip (box with no text).
One strftime block with an embedded newline shows date + zone; ensure
tooltip label colors on generated + summer bars.
2026-07-10 13:50:15 +01:00
869ca6b16c fix(cli): nomarchy-pull skips git when no upstream
All checks were successful
Check / eval (push) Successful in 2m54s
Machine flakes often have no remote (local auto-commits only). Distro
updates come from nix flake update of the nomarchy input. Only git pull
--ff-only when @{u} is set; always update the lock; print nomarchy rev.
2026-07-10 13:41:34 +01:00
1e1d568947 fix(hyprland): stay_focused not stayfocused (0.55)
Some checks failed
Check / eval (push) Has been cancelled
Hyprland 0.55 renamed the effect; stayfocused 1 → invalid field type
on line 158 (polkit rules). Keep float/center/workspace current.
2026-07-10 13:38:54 +01:00
d7427ae5da docs: note SUPER+D launcher bind dropped
All checks were successful
Check / eval (push) Successful in 2m56s
2026-07-10 13:33:12 +01:00
e2ede3d813 refactor(binds): app launcher is SUPER+Space only
Some checks failed
Check / eval (push) Has been cancelled
Drop the duplicate SUPER+D drun bind; menu Apps hint shows SUPER+Space.
2026-07-10 13:33:06 +01:00
3d5cb21302 feat(cli): nomarchy-pull / nomarchy-rebuild / nomarchy-home
Some checks failed
Check / eval (push) Has been cancelled
Split lifecycle into three clear commands (user pick):
  nomarchy-pull     git pull (if checkout) + flake.lock update
  nomarchy-rebuild  system switch on current lock (snap + nvd)
  nomarchy-home     Home Manager switch

Full upgrade: pull && rebuild && home. Legacy sys-update / sys-rebuild /
home-update remain as wrappers. nixos-rebuild-snap uses NOMARCHY_PATH.

V0: flake check --no-build green.
2026-07-10 13:32:50 +01:00
910f357f08 fix(desktop): hardware findings — charge limit, polkit ws, bar deps
All checks were successful
Check / eval (push) Successful in 3m3s
Latitude 5310 QA:
- Clock tooltip: drop empty {calendar} popup; plain date+zone
- Charge limit: restart oneshot via polkit + dual write/delay for
  Dell unplug race; toast verifies sysfs; looser menu match
- pwvucontrol ships with waybar (right-click volume)
- Print/region→clipboard toasts success
- Polkit (and pinentry) open on current workspace + stayfocused

V0: flake check --no-build green. V3: retest A3/A4/B1/tooltip/polkit.
2026-07-10 13:29:44 +01:00
3bc8a46927 fix(menu): case-insensitive filter on all rofi -dmenu menus
All checks were successful
Check / eval (push) Successful in 2m53s
dmenu mode is case-sensitive by default (dmenu compat), so config.rasi
case-sensitive=false fixed apps/drun but not nomarchy-menu pickers.
rofi_menu helper always passes -i; networkmanager_dmenu gets rofi -i.

V0: flake check --no-build green. V3: type sys in root Menu → System.
2026-07-10 13:08:33 +01:00
79630314aa docs(agent): mark calendar V3 PASS on Latitude
Some checks failed
Check / eval (push) Has been cancelled
2026-07-10 13:06:52 +01:00
b5263bcef8 fix(desktop): tighter bar icon pad + case-insensitive rofi match
Some checks failed
Check / eval (push) Has been cancelled
- Double-space pad on volume (esp. high), battery, keyboard layout glyphs
- rofi: matching=normal,fuzzy; case-smart off (ste finds Steam)
- Queue: unfree+calendar PASS; retest spacing + rofi search

V0: nix flake check --no-build green. V3: bar/rofi on session.
2026-07-10 13:06:47 +01:00
d547393e7b fix(desktop): unfree CLI default, bar icon spacing, unified clock
All checks were successful
Check / eval (push) Successful in 3m0s
Hardware findings on Latitude: nix-shell blocked unfree; whole-swap
bars collided icon+percentage; clock needed calcurse + one module.

- Export NIXPKGS_ALLOW_UNFREE=1 + ~/.config/nixpkgs/config.nix
- Mono Nerd face + trailing spaces on battery/volume icons (all bars)
- Single clock `HH:MM · Day DD Mon`; calcurse ships with waybar
- V3 queue entries for laptop re-check

Verification: V0 — nix flake check --no-build green; V2/V3 pending on
session (user-visible bar + CLI unfree).
2026-07-10 12:58:37 +01:00
Bernardo Magri
eb951569cf feat: live charge limit, identity retunes, btop assets (product calls)
All checks were successful
Check / eval (push) Successful in 3m0s
- Charge limit: udev GROUP=users on threshold; menu applies sysfs live
  and persists state; boot/AC oneshot prefers live theme-state.json.
- Look & Feel: Reset wallpaper (auto); Theme/next/night light already there.
- NVIDIA first-class wrappers deferred past v1 (keep #59 comments).
- Identity taste: white, lumon, hackerman, matte-black, miasma.
- Hand btop.theme for identity + boreal/executive-slate/neon-glass.

Verified: V0 contrast; V1 battery-charge-limit VM. V3 live sysfs on laptop.
2026-07-10 12:12:13 +01:00
Bernardo Magri
099d214529 feat(themes): summer pair JSON SoT + status CSS + recapture previews
All checks were successful
Check / eval (push) Successful in 2m58s
Align summer-day/night waybar tokens with JSON roles (hierarchy split
on night; day overlay≠muted), unify bar font 13px + battery 25/10, add
VPN pill and status states (.on/.available/.recording/…). Recapture
executive-slate and neon-glass picker previews from theme-shot.

Verified: V0 contrast+wholeswap; V2 theme-shot four slugs.
2026-07-10 12:03:51 +01:00
Bernardo Magri
2fff3ad628 docs(agent): close #74 matrix + #75 V2 install (themed first boot)
All checks were successful
Check / eval (push) Successful in 3m8s
Unattended env matrix flags shipped earlier; default arm V2: live ISO
rebuild + offline install + first-boot themed Boreal desktop.
2026-07-10 10:09:06 +01:00
Bernardo Magri
3eeba0611d test(install): fall back to nixpkgs OVMF.fd for headless ISO tests
All checks were successful
Check / eval (push) Successful in 2m59s
2026-07-10 09:40:55 +01:00
Bernardo Magri
a95cf49ddf test(install): #74 wire matrix flags into go.sh + first-boot
Some checks failed
Check / eval (push) Has been cancelled
Generate unattended go.sh from NOMARCHY_TEST_SWAP_GB / NO_LUKS; skip
LUKS passphrase typing on first boot when cleartext.
2026-07-10 09:40:38 +01:00
Bernardo Magri
3760e39a54 test(install): #74 unattended matrix env flags (swap / no-LUKS)
Some checks failed
Check / eval (push) Has been cancelled
NOMARCHY_TEST_SWAP_GB and NOMARCHY_TEST_NO_LUKS drive go.sh for the
headless install harness. Document default / no-swap / no-LUKS matrix.
First-boot path skips LUKS typing when NO_LUKS=1.
2026-07-10 09:40:25 +01:00
Bernardo Magri
5b93b97191 docs(agent): close NEXT #64–#73 after parallel land
Some checks failed
Check / eval (push) Has been cancelled
Landed worktree batch: migration docs, installer chown, theme-state
reader, theme fidelity, audit/import tools, IR docs, template-sot check,
hardware MOTD tips. Verified contrast + template-sot + flake check.
2026-07-10 09:39:59 +01:00
Bernardo Magri
ad6b76e1eb feat(hints): #73 fingerprint + doctor MOTD/first-boot tips
#43 shipped Firmware MOTD + control-center --first-boot tip. Residual:
- MOTD always lists nomarchy-doctor; Fingerprint line only when
  nomarchy.hardware.fingerprint.enable (no nag without a reader).
- first-boot tips self-gate on fprintd-list / nomarchy-doctor (same
  gum style as Firmware). No dismiss state file — first-boot is once.

V0 flake check --no-build + bash -n; V1 build control-center + eval
MOTD with/without fingerprint.enable. V3 enroll stays hardware-queue.
2026-07-10 09:39:10 +01:00
Bernardo Magri
3a874dccc8 test(install): #72 installer ↔ template SoT parity check
Guard that nomarchy-install share/template ships byte-identical
flake.nix/system.nix/home.nix/theme-state.json to templates/downstream
(the single machine-seed SoT; default.nix copy list).

- tools/check-template-sot.py — share vs SoT file-set + content
- checks.template-sot — pure python + package path (theme-contrast pattern)
- BACKLOG #72 removed; JOURNAL entry

Verified (V0):
  python3 tools/check-template-sot.py <share/template> templates/downstream
  nix build .#checks.x86_64-linux.template-sot
2026-07-10 09:39:10 +01:00
Bernardo Magri
82776d7da4 docs(webcam): portal/Flatpak IR gap is v4l2-only (#71)
Ship the recommended document-only path: WirePlumber hideIrSensor acts
on the v4l2 monitor only; libcamera / xdg-desktop-portal / Flatpak
camera pickers can still list the internal IR sensor.

- HARDWARE.md §7: dual-sensor subsection (scope, workaround, deferred)
- ROADMAP § Webcam: ✓ #71; (b)/(c) stay hardware-gated
- README option row + HARDWARE-QUEUE T14s check for (b)/(c)
- Close BACKLOG #71; no libcamera rules

Verified: V0 (docs only).
2026-07-10 09:39:10 +01:00
Bernardo Magri
34362d6a92 fix(tools): identity audit exemptions + import hierarchy (#69 #70)
#69: audit-theme-design tags hue/CVD/ANSI-family noise on white,
vantablack, lumon, hackerman, matte-black, miasma as [identity] with a
legend — do not retune identity palettes into traffic lights.

#70: import-palettes keeps surface≠overlay when color0==color8, derives
light UI chips from base (ANSI-black trap), gentler light mantle; roles
are first-class. No bulk re-import of shipped theme JSON.

Verified: V0 (py_compile + synthetic import fixtures + audit report).
2026-07-10 09:39:10 +01:00
Bernardo Magri
a640de4fd4 fix(theme): #66 fail-closed theme-state errors at mkFlake/read
Missing, empty, or non-object theme-state.json now throws a short
message pointing at the template and `nomarchy-theme-sync validate`
instead of a raw readFile/fromJSON stack. Shared reader:
modules/theme-state-read.nix — wired in theme.nix, plymouth/timezone/
hardware, and lib.mkFlake (seq-forced early gate). Field-level schema
in theme.nix kept; unknown border roles still fail there; resolveColor
no longer falls through on missing palette keys. Defaults added for
settings.displayProfile / displayProfileAuto.

Verified: V0 nix flake check --no-build; negative evals (missing,
empty, [], bad border role); good-path template mode + displayProfile
defaults.
2026-07-10 09:39:10 +01:00
Bernardo Magri
bd6d94f973 fix(themes): fidelity hierarchy nits + #52 residual ANSI (#67, #68)
#67 hierarchy: nord subtext dimmer than text; tokyo-night btop Night
blues; ethereal surface raise + muted darker than overlay; retro-82
surface raised; ristretto subtext distinct + btop main_bg align;
flexoki-light distinct bright-black; gruvbox overlay/subtext ladder
(no rename).

#68 residual ANSI: vantablack ansi[8]=muted; osaka-jade ansi[3]=warn.

Verified: python3 tools/check-theme-contrast.py themes/ → 24×7 all pass.
2026-07-10 09:39:03 +01:00
Bernardo Magri
3d324982b9 fix(install): chown flake dir with real install-user uid/gid (#65)
Hard-coded 1000:100 assumed the first free normal uid and users(100).
After nixos-install, resolve id -u/-g for USERNAME on the target and
chown FLAKE_DIR (and the HM-fail hint file) to those ids.
2026-07-10 09:39:03 +01:00
Bernardo Magri
61d9ee1577 docs(migration): clarify installer vs @home-snapshots layout (#64)
Installer: disko @snapshots → /.snapshots + first-boot nested
/home/.snapshots. Migration machines may use top-level
@home-snapshots (TuringMachine); either works for snapper.
2026-07-10 09:39:03 +01:00
Bernardo Magri
cb659ebb4c docs(agent): PROPOSED breadcrumbs for #64–#75 promotion
All checks were successful
Check / eval (push) Successful in 2m56s
2026-07-10 09:29:34 +01:00
Bernardo Magri
6b488d69ef docs(agent): promote agent-actionable PROPOSED → NEXT #64–#75
Some checks failed
Check / eval (push) Has been cancelled
Category 1 (no hw/human): MIGRATION, chown, mkFlake errors, theme
fidelity nits, residual ANSI, audit exemptions, import pipeline, IR
docs, SoT CI, hardware-hints residual. Category 2 (KVM): unattended
matrix + SoT V2 install. Queue head is #64.
2026-07-10 09:29:01 +01:00
Bernardo Magri
1d6b25b96a docs(agent): clean stale PROPOSED backlog residue
All checks were successful
Check / eval (push) Successful in 2m59s
Drop shipped #52 high-ROI theme bullets, neon-glass quarantine, empty
installer audit sections, resolved template seed drift, and #50 soft
gaps. PROPOSED is open work only (day-2, installer follow-ups, theme
polish residuals, tooling).
2026-07-10 09:25:15 +01:00
Bernardo Magri
14558ad296 test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s
- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
  05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
  asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
  capture-float-classes.nix harness (softGL empty); residual portal
  class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.

Verified: live-install-entry + windowrule-syntax green.
2026-07-10 09:20:15 +01:00
Bernardo Magri
5f3124d160 docs(agent): note A/B/C batch #55–#63 shipped
All checks were successful
Check / eval (push) Successful in 2m57s
2026-07-10 08:45:41 +01:00
Bernardo Magri
a102dff508 feat(rebuild): #56 human rebuild errors point at doctor
Some checks failed
Check / eval (push) Has been cancelled
sys-update / sys-rebuild / home-update tee output and on failure print
the last 40 log lines plus nomarchy-doctor + RECOVERY.md. theme-sync HM
switch failure notify/die text matches. Close #56.

Verified: V1 (HM rebuild; failure-footer shell smoke).
2026-07-10 08:45:33 +01:00
Bernardo Magri
e81529c1bb feat(menu): #55 System › Fingerprint enroll/list/PAM
Self-gated System › Fingerprint when fprintd is on PATH: enroll, list,
verify, delete-all (terminal), and Use for login writing
settings.fingerprint.pam (hardware.nix default from theme-state). Close #55.

Verified: V1 (HM menu rebuild). V3 pending: real-reader enroll (HARDWARE-QUEUE).
2026-07-10 08:45:33 +01:00
Bernardo Magri
2ef0a8b710 feat(hw): #58 nomarchy-detect-hw post-install re-probe CLI
All checks were successful
Check / eval (push) Successful in 2m52s
Package the installer’s pure hardware-db probe as nomarchy-detect-hw:
prints suggested hardwareProfile and system.nix snippets (or --raw
protocol lines). Does not rewrite the flake. On PATH via the distro
module; docs/HARDWARE.md §8 updated. Close #58.

Verified: V1 (build + run on AMD laptop with fingerprint/NPU/IR cam).
2026-07-10 08:41:18 +01:00
Bernardo Magri
35699f170f feat(live): #57 Install Nomarchy desktop entry + Tools row
Some checks failed
Check / eval (push) Has been cancelled
Always-visible install surface on the live ISO: xdg.desktopEntries
(Terminal=true) plus Tools › Install Nomarchy (self-gated on
nomarchy-install on PATH). Welcome toast mentions the app. Close #57.

Verified: V1 (live eval + HM menu rebuild). V2 pending: ISO smoke.
2026-07-10 08:39:47 +01:00
Bernardo Magri
a504f35b41 feat(waybar): #63 generated bar logo + powermenu
Some checks failed
Check / eval (push) Has been cancelled
Add custom/nomarchy (left, Nomarchy monogram → nomarchy-menu) and
custom/powermenu (right → nomarchy-menu power) to the generated bar so
default-theme users match whole-swap affordances. Pin font-family:
Nomarchy; use literal UTF-8 glyphs (Nix has no \u escapes).

Verified: V2 (THEME=tokyo-night theme-shot — monogram + power glyph).
Close #63.
2026-07-10 08:38:45 +01:00
Bernardo Magri
c962d07841 fix(power): #60 name-agnostic system-battery scan
All checks were successful
Check / eval (push) Successful in 2m53s
Replace BAT* globs with type=Battery + !scope=Device (same filter as
nomarchy-battery-notify) in charge-limit oneshot, menu gates, Waybar
power-profile self-gate, doctor, and install chassis detection. CMB0-
style names get charge-cap and laptop modules consistently.

V3 pending: confirm on non-BAT* hardware (HARDWARE-QUEUE). Close #60.

Verified: V1 (option-docs, hardware-db-modules, battery-charge-limit VM).
2026-07-10 08:32:24 +01:00
Bernardo Magri
09388196a9 feat(install): #59 NVIDIA commented system.nix guidance
Some checks failed
Check / eval (push) Has been cancelled
When hardwareProfile includes common-gpu-nvidia, emit a comment-only
plain NixOS block (PRIME / powerManagement / open-module) in the
installer region of system.nix — same pattern as ROCm/NPU opt-ins.
HARDWARE.md §6 product direction marked shipped. Close #59.

Verified: V0 (patch-template smoke + negative + bash -n).
2026-07-10 08:30:51 +01:00
Bernardo Magri
f09042adbe feat(themes): #62 whole-swap CSS self-containment CI gate
Some checks failed
Check / eval (push) Has been cancelled
Add tools/check-theme-wholeswap.py and checks.theme-wholeswap: every
themes/*/waybar.css must @define-color each @name it references (raw
readFile, no palette prepend — neon-glass class), and every preset needs
preview.png + non-empty backgrounds/. README: four waybar.css themes
(drop quarantined neon-glass). Close #62.

Verified: V0 (script + negative fixture + checks.theme-wholeswap + flake check --no-build).
2026-07-10 08:29:30 +01:00
Bernardo Magri
11d6a3df0f feat(install): #61 review Source line tracks offline/online
Some checks failed
Check / eval (push) Has been cancelled
Gate the Review panel Source wording on the same OFFLINE flag as the
cache.nixos.org probe (ISO-only vs may use network binary caches).
Extend installer-safety pure contract so both phrases and $SOURCE_NET
cannot regress.

Also promote Bernardo-accepted A/B/C batch → NEXT #55–#63 and close #61.

Verified: V0 (check-install-safety + bash -n + checks.installer-safety).
2026-07-10 08:28:20 +01:00
Bernardo Magri
9aa8f250d6 feat: close #54 V2 install safety, #33 rofi scrollbar, #43 MOTD tip
All checks were successful
Check / eval (push) Successful in 3m0s
#54: permanent checks.installer-safety; test-install-safety harness with
offline compose-fail, LUKS-signature disk warn, and HM-hint FORCE paths.
#33: enable themed rofi scrollbars (generated + all whole-swap .rasi).
#43: MOTD + first-boot tip for System › Firmware (V3 LVFS stays hw-queue).

V2: compose-fail/disk-warn/hm-hint logs green offline; theme-shot
boreal+summer-night menus OK. V0 flake/installer-safety.
2026-07-09 23:02:05 +01:00
Bernardo Magri
be4efd38ea fix(waybar): #51 doctor glyph renders + #52 V2 bake asserts
All checks were successful
Check / eval (push) Successful in 3m6s
Doctor tripwire was self-hiding under waybar (PATH miss / weak glyph).
Call nomarchy-doctor by store path, strip ANSI tooltips, use 󰀨 + format
+ signal 10 (generated and whole-swap). theme-shot asserts class:bad,
guest btop.theme, and pokes RTMIN+10 before desktop shots.

V2: theme-shot green for rose-pine, summer-night, catppuccin, everforest,
vantablack; bar crops show the red alert-circle. btop TUI visual is
softGL-blocked (Ghostty) → HARDWARE-QUEUE. Closes #51 and #52.
2026-07-09 20:56:51 +01:00
Bernardo Magri
d8a796b6ee docs(agent): record #43 V2 (Firmware menu render) + #44 close
All checks were successful
Check / eval (push) Successful in 3m2s
#43: theme-shot render confirms the System submenu renders themed and
self-gates correctly; the Firmware row's presence on a real install is
verified via /sw/bin/fwupdmgr existing in the live toplevel (same
mechanism as the working Bluetooth row). A filter render didn't surface
the row in the minimal capture VM — render artifact, not a bug. V3 (real
LVFS flash) stays queued.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:11:23 +01:00
Bernardo Magri
f606c78fcf test(checks): #44 doctor hardware section — headless guard
All checks were successful
Check / eval (push) Successful in 3m24s
Extend checks.doctor: the bare test VM lacks every probe tool, so the
healthy nomarchy-doctor run drives all six hardware checks in skip mode.
Assert the self-gated skip rows appear (NetworkManager, fingerprint,
fwupd, VA-API) and the sheet still exits 0 — proving the section runs
without erroring under set -euo pipefail. No node changes.

Verified V2: nix build .#checks.x86_64-linux.doctor passes. Closes #44.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:59:32 +01:00
Bernardo Magri
c40c74e640 feat(themes): #53 last previews + quarantine neon-glass broken bar
All checks were successful
Check / eval (push) Successful in 2m58s
Capture executive-slate + neon-glass preview.png (480x270, theme-shot) —
all 24 themes now have picker previews. Quarantine neon-glass's broken
whole-swap: its waybar.css referenced @accent/@base/… with no
@define-color block, and waybar.nix reads a whole-swap css raw (no palette
prepend), so applying it rendered broken bar colours. Removing it falls
back to the generated bar from the theme's JSON palette.

Verified: V2 theme-shot renders viewed — executive-slate clean; neon-glass
bar now renders correctly, no config-error banner. V0 flake check green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:52:09 +01:00
Bernardo Magri
f94772de2c docs(skill): track the theme designer skill
All checks were successful
Check / eval (push) Successful in 2m59s
First commit of .claude/skills/theme/SKILL.md (skills were un-ignored in
7d52d4b); content unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:45:35 +01:00
Bernardo Magri
40c38dc4f5 fix(install): #54 safety guards + recovery hint
All checks were successful
Check / eval (push) Successful in 3m7s
Offline compose-lock failure now fail-closes (reusing the cache.nixos.org
probe) instead of falling back to a network `nix flake lock`; a disk-review
warn fires when the target already carries Windows/BitLocker/NTFS/LUKS
(before the wipe); account password requires ≥8 chars like LUKS; on HM
pre-activate failure the installer drops a NOMARCHY-DESKTOP-NOT-THEMED.txt
recovery hint in the target home so first login still shows the fix.

Worktree agent; diff reviewed + cherry-picked. V0 flake check + V1
nomarchy-install build. Password ≥8 is V0-complete; the offline/NTFS/
pre-activate scenarios are V2-pending (crafted install runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:39:08 +01:00
Bernardo Magri
def6e9dcbe fix(themes): #52 broken/high-ROI fidelity fixes
Some checks failed
Check / eval (push) Has been cancelled
Six per-theme defects: rose-pine btop rewritten to Dawn (was Main-on-Dawn,
invisible hi_fg); everforest + summer-night btop inactive_fg off main_bg;
vantablack role inversion (surface/overlay/muted + btop selection);
osaka-jade text↔subtext swap + warn→yellow; catppuccin-latte
mantle/accentAlt/ANSI axis; catppuccin Mocha btop (was Frappé). accentAlt
uses Latte mauve #8839ef, not the audit's pink #ea76cb (which fails the
3.0 contrast floor).

Worktree agent; diff reviewed + contrast conflict adjudicated. V0:
check-theme-contrast 24x7 all pass + flake check green. btop V2 render
pending (batched). Residual ansi[8]/ansi[3] drift noted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:36:58 +01:00
Bernardo Magri
4a99b64532 docs(skill): fan-out token-economy guidance + track nomarchy skill
Some checks failed
Check / eval (push) Has been cancelled
Add a "Fanning out worktree agents" subsection to §6.5: match model to
task (sonnet for spec'd work, opus for judgment), point briefs at the
backlog spec, disjoint file lanes, worktree isolation with the parent
owning landing, lean on scriptable checks as evidence, batch V2 in one
VM pass. First commit of the tracked skill (skills were un-ignored in
7d52d4b). MEMORY note updated: skills are now tracked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:35:01 +01:00
Bernardo Magri
75d76fabd7 fix(waybar,menu): #51 doctor indicator glyph + CC rebuild hints + scroll
Some checks failed
Check / eval (push) Has been cancelled
Doctor Waybar indicator now emits a non-empty glyph (󰒡) + class:bad when
unhealthy (was "text":"" → self-hidden, so the tripwire never showed);
Control Center appearance toggles (blur/gaps) print "(requires rebuild)"
matching the other CC toggles; summer-day workspace scroll e±1 → r±1 to
match every other bar.

Worktree agent; diff reviewed + cherry-picked. V0 flake check + V1
(HM generation, nomarchy-control-center build). V2 render folded into #44.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:32:31 +01:00
Bernardo Magri
f6975a9797 docs(agent): promote V2-capable PROPOSED items to NEXT #51–54
All checks were successful
Check / eval (push) Successful in 2m57s
Bernardo-directed triage (VM now free post-#46): #51 Waybar/menu
behaviour bugs, #52 theme fidelity Broken/high-ROI fixes, #53 missing
previews + neon-glass whole-swap, #54 installer safety + polish. Promoted
pitches removed from PROPOSED (breadcrumbs left); non-selected V2 items
stay in PROPOSED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:22:38 +01:00
Bernardo Magri
7a5284b15a feat(menu,install): #50 one-liners + record #46 V2 install pass
All checks were successful
Check / eval (push) Successful in 2m51s
#50: Bluetooth menu row + handler self-gate on `command -v
blueman-manager` (no-op when bluetooth is off, like Printers); Control
Center rollback list /tmp/nomarchy-gens → mktemp; usbutils on the
nomarchy-install PATH (fingerprint lsusb probe); export
nomarchy-doctor/control-center/battery-notify as flake packages.
Verified V0 (flake check) + V1 (packages, installer, HM generation build).

Also records #46: the swap=0 + unattended-LUKS fail-closed contracts
(208b8d4) V2-verified via a headless two-variant install — fail-closed
exits before wipe with the disk blank; swap=0 installs with no @swap
subvol and boots to a themed desktop. Both items removed from NEXT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:16:17 +01:00
Bernardo Magri
e01303851d test(checks): pure-contract guards batch (#49)
All checks were successful
Check / eval (push) Successful in 3m6s
Four new no-VM checks.* + CI py_compile expansion:
- hardware-db-modules: installer DB module names ∈ pinned
  nixos-hardware.nixosModules (a lock bump can rename a module and break
  profiled installs on just the matching DMI, invisible to any VM).
- installer-compose-lock: offline lock-composer contract on fixtures.
- installer-disko: pure Nix assert — swapSize "0"/"0G" → no @swap, "2G"
  → sized, withLuks wraps root (permanently guards the #46 install fix).
- windowrule-syntax: builds the generated hyprland.conf and fails on the
  pre-0.53 grammar / windowrulev2 keyword (guards ed7fd93).
CI py_compile now covers all tracked *.py; docs/TESTING.md §1 synced.

Implemented by a worktree agent; diff reviewed. Verified V0 (flake check)
+ V1 (each checks.x86_64-linux.* built on main, exit 0); agent's negative
tests confirm each guard fails on a regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:10:45 +01:00
Bernardo Magri
97bf26a23f docs: factual-drift pass + i2c option docs (#47, #48)
Some checks failed
Check / eval (push) Has been cancelled
Fix documentation drift (each claim verified against code): theme count
21→24; waybar whole-swap lists (waybar.css=5 incl. neon-glass vs
waybar.jsonc parity=4 + boreal); idle "suspend 30"→"15 min battery-only";
Tailscale sudo→operator; hosts/live.nix "No installer yet" header; link
docs/MIGRATION.md + fix its placeholder URL; README layout tree
(+doctor/control-center/battery-notify); OVERRIDES swappy→satty; document
always-on home pieces. String fixes: keybind cheatsheet
nmtui→networkmanager_dmenu; complete nomarchy-menu usage string
(+doctor/firmware/controlcenter/rollback). Add README rows for
nomarchy.hardware.i2c.* (#48) — closes checks.option-docs.

Implemented by a worktree agent; diff reviewed and cherry-picked.
Verified V0 (flake check) + checks.option-docs green + V1 (HM build).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:09:00 +01:00
Bernardo Magri
1ee17f6799 docs(agent): promote V0/V1 PROPOSED items to NEXT #47–50
All checks were successful
Check / eval (push) Successful in 3m3s
Bernardo-directed triage: bump the no-VM (V0/V1) PROPOSED items into NEXT
so parallel worktree agents can take them without contending for the VM.
Grouped into #47 docs+string drift pass, #48 i2c option-docs, #49
pure-contract checks.* batch, #50 small code one-liners. Removed the
promoted pitches from PROPOSED (breadcrumbs left); V2-wanting items stay
in PROPOSED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:55:29 +01:00
Bernardo Magri
eba7924b0f feat(doctor): read-only hardware health section (#44)
All checks were successful
Check / eval (push) Successful in 3m4s
Six self-gating hardware checks in nomarchy-doctor, same ok/bad/warn/skip
+ one-fix-command contract: NetworkManager connectivity, default PipeWire
sink, VA-API/OpenGL smoke (only if vainfo/glxinfo present), fprintd unit +
enroll nudge, fwupd daemon + a soft warn when get-updates is non-empty,
and the laptop charge_control_end_threshold when a limit is set. Each
skips cleanly when its tool/service/device is absent, so the section
shrinks to the machine and never hard-fails on a missing feature.

Implemented by a worktree-isolated agent in parallel with the #46 install
VM; diff reviewed and cherry-picked here. Verified V0 (flake check) + V1
(nix build .#…pkgs.nomarchy-doctor — build-time bash -n + shellcheck),
re-run on main after cherry-pick; plus a read-only run on real hardware
(all six checks fired + self-gated). V2 (checks.doctor fakes) pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:50:37 +01:00
Bernardo Magri
c89cace149 feat(menu): System ▸ Firmware — fwupd/LVFS update surface (#43)
All checks were successful
Check / eval (push) Successful in 3m3s
Add a self-gated Firmware row to the System submenu (present whenever
fwupdmgr is on PATH, i.e. services.fwupd.enable — default-on), its
dispatch, and a `firmware` handler that opens a terminal and runs
fwupdmgr refresh → get-updates → y/N confirm → fwupdmgr update. Never
auto-flashes: fwupdmgr update confirms each device and prompts for the
reboot a capsule needs (pillar 1 — no silent BIOS writes). HARDWARE.md
§4 updated to "shipped".

Verified V0 (nix flake check --no-build) + V1 (HM generation builds;
generated nomarchy-menu passes bash -n; firmware handler emitted
correctly). V2 (menu render in a session) queued in #43 and V3 (real
LVFS flash) in HARDWARE-QUEUE — the VM was held by the #46 install test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:43:46 +01:00
Bernardo Magri
41cd350a52 feat(theme): capture Boreal picker preview + V2-verify default
All checks were successful
Check / eval (push) Successful in 3m6s
Close the honesty gap on #45. Rendered the full Boreal desktop
headlessly (tools/theme-shot.nix, softGL 1920x1080) and confirmed the
session reads as Boreal — aurora wallpaper, Waybar violet accent
(#B79BE8) workspace pill, GeistMono clock, rofi launcher violet
prompt/selection — not Tokyo Night. Added themes/boreal/preview.png
(480x270, matches the existing 21) so the picker row is no longer
plain-name.

theme-shot: park the cursor bottom-right before the desktop shot so the
frame (which doubles as the preview source) is clean; reusable for the
other two missing previews.

Verified: V0 flake check; V2 theme-shot desktop/menu shots viewed;
contrast 24x7 pass. Preview is a headless bare-desktop capture (not the
floating-terminal composition of the others) — hardware recapture noted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:29:27 +01:00
7d52d4b5e4 Update .gitignore
All checks were successful
Check / eval (push) Successful in 3m6s
2026-07-09 18:25:45 +01:00
Bernardo Magri
ed7fd93e16 fix(hyprland): migrate window rules to Hyprland 0.53+ syntax
Some checks failed
Check / eval (push) Has been cancelled
The default desktop showed a red Hyprland config-error banner on every
boot, occluding the Waybar. Root cause: Hyprland 0.53 rewrote window
rules — the `windowrulev2` keyword is a hard error and the old
rule-first order (`float, class:^…$`) no longer parses, so the float
rules were not applying either. Confirmed against the Hyprland 0.55.4
source (`handleWindowrule`): each comma field is now `<keyword> <value>`,
so effects carry a value and matchers take a `match:` prefix.

Migrated the 7-rule block to `float 1, match:class ^…$` form.

Verified: V0 nix flake check --no-build; built the generated
hyprland.conf; V2 theme-shot re-render — banner gone, themed Waybar
renders. Journal + MEMORY gotcha added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:24:55 +01:00
Bernardo Magri
5c875542d0 docs(agent): NEXT #45–46 for VM verification of recent V0 work
All checks were successful
Check / eval (push) Successful in 2m55s
Queue headless-incomplete checks for a VM-capable agent: Boreal default
(live/HM apply, preview.png, flake check) and install contracts
(swap=0 + unattended LUKS fail-closed).
2026-07-09 09:58:08 +01:00
Bernardo Magri
351b7adb8e feat(theme): make Boreal the distro default
Some checks failed
Check / eval (push) Has been cancelled
Seed theme-state.json and the downstream template from themes/boreal.json
(including border + empty settings). Point eval/schema fallbacks at Boreal
(theme.nix preset, plymouth and live-ISO splash colors, theme-shot, sync
error examples). Docs/TESTING and RECOVERY follow the new default.

Verified: state matches boreal palette; theme-contrast all pass.
preview.png for boreal still missing (picker plain-name; V3 capture).
2026-07-09 09:57:32 +01:00
Bernardo Magri
70501b566a docs(agent): promote v1.0 slices 42–44 to NEXT
All checks were successful
Check / eval (push) Successful in 2m55s
Boreal default theme, System › Firmware (fwupd), and doctor hardware
section — human triage from PROPOSED for agents to execute.
2026-07-09 09:45:31 +01:00
Bernardo Magri
208b8d4444 fix(install): swap=0 and unattended LUKS fail-closed
All checks were successful
Check / eval (push) Successful in 3m4s
Pass bare swapSize "0" when the user wants no swap (was "0G", which
still created @swap). Accept "0"/"0G" in disko-config. Unattended
installs require NOMARCHY_LUKS_PASSPHRASE or explicit NOMARCHY_NO_LUKS=1
instead of silently installing cleartext.

Verified: bash -n; nix-instantiate subvolume sets for 0/0G/16G.
VISION § v1.0 install golden path (workflow test iteration).
2026-07-09 09:40:07 +01:00
Bernardo Magri
02d7baeb7c docs: VISION for v1.0 + clarify docs/ vs agent/ vs .claude
All checks were successful
Check / eval (push) Successful in 3m7s
Add docs/VISION.md (product themes, v1.0 bar, agent slice hints) and
README maps under docs/ and agent/. Point ROADMAP, BACKLOG, LOOP,
CLAUDE, HARDWARE, and the root README at the split: VISION = intent,
BACKLOG = executable queue, ROADMAP = design history, .claude = harness
only. Mark v1.0 track in BACKLOG PROPOSED for human triage.
2026-07-09 09:33:51 +01:00
Bernardo Magri
caaac88da9 feat(install): copy templates/downstream and patch install values
All checks were successful
Check / eval (push) Successful in 3m4s
Make the downstream template the single source of truth for machine
flakes: nomarchy-install copies flake/system/home/theme-state and
patch-template.py only fills hostname, user, keyboard, detected
hardware, snapper, resume, and password. Install and flake-init users
now share the same commented opt-ins and starter home.packages.

Also: BACKLOG boreal-as-default proposal; installer audit notes;
HARDWARE.md from prior work if uncommitted.
2026-07-09 09:23:50 +01:00
Bernardo Magri
896b41faa3 docs: hardware support guide + product backlog
All checks were successful
Check / eval (push) Successful in 3m10s
Add docs/HARDWARE.md (layers, install path, firmware/fingerprint/NVIDIA
deep dives, unsupported machines, contributing a DMI line). Link from
README and MIGRATION; queue firmware UI, doctor checks, detect-hw CLI,
and related PROPOSED items in BACKLOG.
2026-07-09 09:04:27 +01:00
Bernardo Magri
d09c978b9b docs(agent): backlog codebase exam + per-theme design audit
All checks were successful
Check / eval (push) Successful in 3m16s
Record PROPOSED findings from a full tree exam (installer, modules,
docs drift, CI) and a 24-theme design pass (contrast green; btop
fidelity, identity semantics, whole-swap gaps). No code changes —
await human triage into NOW/NEXT.
2026-07-09 08:46:13 +01:00
Bernardo Magri
5273493c20 fix(hyprland): use windowrulev2 for class matching and update docs
All checks were successful
Check / eval (push) Successful in 3m0s
- Fixed invalid `windowrule` syntax when matching by class regex
  which caused Hyprland parse errors.
- Documented IPC socket watcher for display profiles in README.
- Updated agent/JOURNAL.md.
2026-07-08 23:07:51 +01:00
Bernardo Magri
9c21aa64b1 docs: document AppArmor and Kernel panic defaults
All checks were successful
Check / eval (push) Successful in 3m3s
2026-07-08 22:50:48 +01:00
Bernardo Magri
6b7f2b4ce8 docs: add new keybinds to README
All checks were successful
Check / eval (push) Successful in 3m5s
2026-07-08 22:47:20 +01:00
Bernardo Magri
2ef56eae88 docs: sync ROADMAP with shipped features
Some checks failed
Check / eval (push) Has been cancelled
2026-07-08 22:44:47 +01:00
Bernardo Magri
d6b5b344fa feat(desktop): complete theme UI review
All checks were successful
Check / eval (push) Successful in 3m0s
2026-07-08 22:37:25 +01:00
Bernardo Magri
60c7878a6a feat(desktop): workstation polish pack (apparmor, kernel panic, hyprpicker)
All checks were successful
Check / eval (push) Successful in 3m5s
2026-07-08 22:30:36 +01:00
Bernardo Magri
8a5714f330 feat(desktop): overhaul multi-monitor docking UX
Some checks failed
Check / eval (push) Has been cancelled
2026-07-08 22:29:44 +01:00
Bernardo Magri
05c7c7b54f ci: add V1 build tier to automated lock bumps
All checks were successful
Check / eval (push) Successful in 2m55s
2026-07-08 22:25:42 +01:00
Bernardo Magri
3322db7caf feat(system): enable ddcci for external monitor brightness control
Some checks failed
Check / eval (push) Has been cancelled
2026-07-08 22:24:49 +01:00
Bernardo Magri
fbd4e0503e feat(system): enable smartd for drive health monitoring
All checks were successful
Check / eval (push) Successful in 3m1s
2026-07-08 22:21:17 +01:00
Bernardo Magri
f97acda158 feat(home): enable easyeffects for microphone noise cancellation
All checks were successful
Check / eval (push) Successful in 3m2s
2026-07-08 22:16:50 +01:00
Bernardo Magri
ff76781a97 feat(home): enable udiskie for zero-click USB automounting
All checks were successful
Check / eval (push) Successful in 2m52s
2026-07-08 22:09:04 +01:00
Bernardo Magri
856445c505 feat(nixos): add file-roller as GUI backend for thunar archive plugin 2026-07-08 22:08:27 +01:00
Bernardo Magri
c6b759e19e feat(nixos): enable hardware security key support (pcscd)
Some checks failed
Check / eval (push) Has been cancelled
2026-07-08 22:07:12 +01:00
Bernardo Magri
eb8f9fad88 feat(nixos): enable Magic SysRq keys for safe crash recovery
Some checks failed
Check / eval (push) Failing after 57s
2026-07-08 22:06:04 +01:00
Bernardo Magri
d41e5c18d2 docs(agent): propose 3 even deeper-dive workstation tasks
All checks were successful
Check / eval (push) Successful in 2m56s
- [usefulness] Screen Sharing for Legacy X11 Apps (xwaylandvideobridge)
- [stability] Magic SysRq Keys for Safe Recovery (kernel.sysrq)
- [usefulness] File Manager Archive Backend (file-roller)
2026-07-08 22:01:25 +01:00
Bernardo Magri
452bb9d75f docs(agent): propose 3 more deep-dive workstation tasks
All checks were successful
Check / eval (push) Successful in 3m2s
- [usefulness] Hardware security key support (pcscd + u2f)
- [usefulness] Microphone noise cancellation (easyeffects)
- [stability] Drive health monitoring (smartd)
2026-07-08 21:56:59 +01:00
Bernardo Magri
2bad7c524f docs(agent): propose three new stability and usefulness tasks
All checks were successful
Check / eval (push) Successful in 2m51s
- [stability] BTRFS auto-scrub
- [usefulness] Automount removable media (udiskie)
- [usefulness] External monitor brightness control (ddcutil)
2026-07-08 21:53:12 +01:00
Bernardo Magri
1d8c1a4314 feat(home): add satty for screenshot annotation
All checks were successful
Check / eval (push) Successful in 3m1s
Added satty (Wayland-native screenshot annotation tool) and integrated it with the Capture flow.
Wired `$mod SHIFT, Print` to slurp a region and open satty.
Added "Annotate region" to the rofi Capture menu (Tools > Capture).
Added satty's configuration via home-manager to inject the active theme's palette colors into its UI.

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

Verified: V1 (flake check --no-build, home activation package build).
Pending: nothing.
2026-07-08 21:44:31 +01:00
Bernardo Magri
b1a9d2ea66 docs(agent): stability+usefulness survey #71 — 2 PROPOSED items
All checks were successful
Check / eval (push) Successful in 2m58s
Research iteration (LOOP §1). Findings: the state-file path is robust;
the real stability hole is the eval-only automated lock-bump gate (#14)
with no binary cache, whose true fix is the KVM-blocked #20. Usefulness:
the capture suite lacks a screenshot annotate step. Filed 2 PROPOSED —
an interim build-tier lock gate (no KVM) and a satty annotate step.
V0 (docs-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:31:34 +01:00
Bernardo Magri
db48bb85d5 docs(agent): journal QA sweep #70 — clean tree, no actionable drift
All checks were successful
Check / eval (push) Successful in 2m52s
Headless-actionable backlog is empty (NEXT is all human/hw/V3-visual).
Ran the full scripted suite: flake check green; option-docs and
theme-contrast checks built+passed; theme-design audit shows only
pre-curated identity findings; templates in sync with the live surface.
No churn manufactured. V0-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:20:07 +01:00
Bernardo Magri
6bd03747c9 fix(waybar): inhibited notification state reads by shape, not color alone
All checks were successful
Check / eval (push) Successful in 2m59s
Item 28 color-only accessibility sweep. Swept every Waybar status
indicator for shape/glyph redundancy: all already carry non-color
distinction (updates shows a count + self-hides; vpn/doctor/nightlight/
recording self-gate; battery/idle/muted are glyph-distinct) except the
swaync notification module — its `inhibited-*` states reused the normal
bells (󰂜/󰂚), so an app-inhibited (suppressed) state was distinguished
from a normal one by color alone.

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 21:07:10 +01:00
Bernardo Magri
850dc310df added skills
All checks were successful
Check / eval (push) Successful in 3m0s
2026-07-08 20:50:14 +01:00
Bernardo Magri
565f66372a docs(migration): guide for migrating an existing NixOS machine to Nomarchy
Some checks failed
Check / eval (push) Has been cancelled
Bernardo asked whether TuringMachine (NixOS 25.11) could adopt Nomarchy
without a reinstall, keeping all files. It can — it's a config switch that
reuses the existing hardware-configuration.nix, not a reformat. Adds
docs/MIGRATION.md: a generic "existing NixOS → Nomarchy" walkthrough with
TuringMachine as the worked example — candidacy checklist, the two
data-safety rules (never bump system.stateVersion; /home untouched), a
reversible Phase 0→4 (btrfs snapshot → build-only gate → nixos-rebuild
test → reconcile → cutover) and a triple rollback net. Bakes his decisions:
Nomarchy PPD (drop all ryzenadj), no Secure Boot (drop lanzaboote), full
cutover. Flake/system/home examples verified against lib.nix mkFlake +
templates/downstream, not guessed.

Also files a PROPOSED note: home.stateVersion should be mkDefault so
migrating users don't need mkForce.

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 20:19:30 +01:00
Bernardo Magri
2954283e23 docs(backlog): add item 42 — synced system calendar on the clock click
All checks were successful
Check / eval (push) Successful in 3m2s
Bernardo wants clicking the Waybar date module to open a calendar showing
events synced from the user's accounts, opening instantly (not Thunderbird-
slow). Investigated (2026-07-08): recommend the split model — khal (TUI in
a floating, palette-themed ghostty window) + vdirsyncer for CalDAV/Google
sync — over calcurse (local-first, weaker sync) or GNOME Calendar (GUI,
best Google onboarding but not instant). Flags the two design wrinkles:
credentials are the one legit out-of-flake secret, and Google OAuth needs a
self-registered client_id. Placed in NEXT as [big] with slices (a) viewer
+ click wiring, (b) sync + accounts, (c) events-in-tooltip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:56:47 +01:00
Bernardo Magri
ce83a8e655 docs(backlog): record PPD-vs-TLP default power backend as a Decision
All checks were successful
Check / eval (push) Successful in 3m13s
Bernardo asked whether TLP would be more power-efficient and worth making
the default. Logged in § Decisions with the evidence gathered 2026-07-08:
TLP's gain is idle-only and unquantified (its own maintainer won't cite
numbers), the sibling distro Omarchy tried TLP-for-battery and reverted to
PPD, and TLP-default would rework the powerprofilesctl-based profile UX.
Agent recommendation: keep PPD, chase battery via targeted low-risk tweaks
+ keep TLP the one-line opt-in. Decision itself stays with Bernardo.

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:16:45 +01:00
Bernardo Magri
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
747 changed files with 25146 additions and 21166 deletions

View File

@@ -0,0 +1,28 @@
---
name: nomarchy-runner
description: Mechanical execution of the Nomarchy headless test harness. Use PROACTIVELY when a build or VM run just needs to be executed and its artifacts collected — nix build, tools/test-live-iso.sh, tools/test-install.sh, screenshot capture via tools/vm/vncshot.py and tools/vm/qmp.py, and the scripted checks (check-theme-contrast.py, audit-theme-design.py, check-option-docs.py). Never use for interpreting ambiguous failures, judging visual quality, or deciding what to test.
tools: Read, Bash, Glob, Grep
model: haiku
---
You are a deterministic test executor for the Nomarchy repository. You run
exactly the commands you are asked to run, headlessly, and hand back the
evidence. You do not decide what to test, do not interpret ambiguous
results, and do not judge whether something "looks fine".
Rules:
- Run everything headless and unattended. Never launch a graphical VM
window; never wait on human input. Wrap long-running commands in
timeouts as instructed by the caller, and treat a timeout as a recorded
failure, not something to silently retry.
- Collect artifacts to the working directory the caller specifies: full
command lines, exit codes, the tail of stdout/stderr (plus the complete
logs as files), serial console output, and screenshot file paths.
- Report format: for each command — command, exit code, wall time,
artifact paths, and verbatim error lines if the exit code was nonzero.
Nothing else. No summaries of what the results "mean".
- Never mark anything as passed or verified. You return evidence; the
caller makes the verification claim.
- If a command fails in a way that prevents collecting artifacts, report
exactly what happened and stop — do not improvise recovery steps beyond
what the caller authorized.

View File

@@ -0,0 +1,20 @@
---
name: nomarchy-scout
description: Read-only reconnaissance inside the Nomarchy repo. Use PROACTIVELY for any task that is pure information gathering — locating where an option/module/theme is defined, mapping which files touch a subsystem, scanning build logs or serial-console output for errors, checking docs against code for drift, summarizing a directory. Never use for anything requiring judgment about design, correctness, or visual quality.
tools: Read, Grep, Glob
model: haiku
---
You are a fast, cheap scout for the Nomarchy NixOS distribution repository.
Your only job is to find things and report facts. You never edit, never
judge design quality, and never draw conclusions beyond what the files
literally say.
Rules:
- Report file paths and line numbers, quote the minimum relevant snippet.
- If asked to scan logs, extract the error/warning lines verbatim with
enough surrounding context to locate them, and note timestamps.
- If you cannot find something, say so plainly — never guess or infer that
something "probably" exists.
- Keep output terse and structured: the caller pays for every token you
produce. Facts first, no prose padding, no recommendations.

88
.claude/settings.json Normal file
View File

@@ -0,0 +1,88 @@
{
"permissions": {
"allow": [
"Read",
"Edit",
"Write",
"Glob",
"Grep",
"Bash(ls *)",
"Bash(cat *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(grep *)",
"Bash(rg *)",
"Bash(find *)",
"Bash(tree *)",
"Bash(wc *)",
"Bash(file *)",
"Bash(stat *)",
"Bash(du *)",
"Bash(df *)",
"Bash(which *)",
"Bash(diff *)",
"Bash(jq *)",
"Bash(sha256sum *)",
"Bash(mkdir *)",
"Bash(cp *)",
"Bash(mv *)",
"Bash(touch *)",
"Bash(ln -s *)",
"Bash(tar *)",
"Bash(nix *)",
"Bash(nix-store *)",
"Bash(nix-instantiate *)",
"Bash(./tools/test-live-iso.sh *)",
"Bash(./tools/test-install.sh *)",
"Bash(./tools/audit-theme-design.py *)",
"Bash(./tools/check-option-docs.py *)",
"Bash(./tools/check-theme-contrast.py *)",
"Bash(./tools/import-palettes.py *)",
"Bash(./tools/vm/qmp.py *)",
"Bash(./tools/vm/vncshot.py *)",
"Bash(./tools/vm/gap-analysis.py *)",
"Bash(python3 tools/*)",
"Bash(bash tools/*)",
"Bash(qemu-system-x86_64 *)",
"Bash(qemu-img *)",
"Bash(git status *)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git show *)",
"Bash(git add *)",
"Bash(git commit *)",
"Bash(git branch *)",
"Bash(git switch *)",
"Bash(git checkout *)",
"Bash(git restore *)",
"Bash(git stash *)",
"Bash(git worktree *)",
"Bash(systemctl --user status *)",
"Bash(journalctl --user *)",
"Bash(pgrep *)",
"Bash(pkill -f qemu*)"
],
"ask": [
"Bash(git push *)",
"Bash(rm *)",
"Bash(git reset --hard *)",
"Bash(git clean *)",
"Bash(nix-collect-garbage *)",
"Bash(curl *)",
"Bash(wget *)"
],
"deny": [
"Bash(sudo *)",
"Bash(git push --force *)",
"Bash(git push -f *)"
],
"defaultMode": "default"
}
}

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

@@ -0,0 +1,97 @@
# 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` (eval tier) followed by a V1 build
# (home-manager activation package + nixos toplevel) to catch compilation errors.
# (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 and compilation, 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: Gate — V1 build (toplevel + home-manager)
if: steps.update.outputs.changed == '1'
run: |
nix build .#homeConfigurations.nomarchy.activationPackage --no-link
nix build .#nixosConfigurations.nomarchy.config.system.build.toplevel --no-link
- 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,48 @@ 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 (all tracked scripts)
# Stand-in for `nix flake check --no-build`. That also evaluates # Every tracked *.py — theme-sync, the installer composers
# packages.allThemeVariants — a linkFarm whose drvPath forces all 22 # (compose-lock, patch-template) and the maintainer tools
# home generations into memory at once, OOM-killing the runner. The # (tools/, tools/vm/). Via nix shell so the step doesn't depend on
# eval matrix below already validates every palette one-at-a-time, so # the runner image preinstalling python3.
# 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 ./.); bash -c 'git ls-files "*.py" | xargs -r python3 -m py_compile'
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 — FUTURE #20, not NEXT) ─────────────────────
run: | # Eval-only CI is the standing decision: Gitea act_runner stays docker
# Regenerate to a temp file and compare. If different, the # compose (no /dev/kvm). Full checks.* VMs need a *separate* host with
# contributor forgot to run the generator (or skipped the # nix + KVM (label nix-kvm); see agent/BACKLOG.md FUTURE #20. Do NOT
# pre-commit hook). Fail loudly and tell them the fix. # uncomment until that label is online — Gitea queues forever otherwise.
./bin/utils/nomarchy-docs-scripts --out /tmp/SCRIPTS.regen.md #
if ! diff -q docs/SCRIPTS.md /tmp/SCRIPTS.regen.md >/dev/null; then # vm-checks:
echo "::error::docs/SCRIPTS.md is stale." # runs-on: nix-kvm
echo "Run: ./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md" # timeout-minutes: 120
echo "Then commit the regenerated file." # steps:
echo "--- diff ---" # - uses: actions/checkout@v4
diff -u docs/SCRIPTS.md /tmp/SCRIPTS.regen.md || true # - run: nix flake check # builds + runs the VM tests
exit 1 # - run: nix build .#nixosConfigurations.nomarchy.config.system.build.toplevel --no-link
fi # - 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

16
.gitignore vendored
View File

@@ -1,15 +1,9 @@
# ---> Nix # Nix build artifacts
# Ignore build outputs from performing a nix-build or `nix build` command
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
# VM and ISO artifacts
*.qcow2
*.qcow
*.iso

65
AGENTS.md Normal file
View File

@@ -0,0 +1,65 @@
# 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.
This file is the entry point for **any** AI coding agent, whatever the
vendor or harness. Everything agents need lives in vendor-neutral,
git-tracked markdown (`agent/`, `docs/`); harness-specific config is a
thin *adapter* (see bottom) and never holds shared content.
## Where things live (read this first)
| Path | Role |
|------|------|
| **`agent/`** | Agent instructions + loop state — **only executable queue is `BACKLOG.md`** |
| **`agent/README.md`** | Map of the agent files |
| **`docs/VISION.md`** | Product themes toward **v1.0** (not a queue) |
| **`docs/ROADMAP.md`** | Design history + shipped log |
| **`docs/README.md`** | Full documentation map |
| **`.claude/`** | Claude Code adapter only (permissions, subagent defs) — not the backlog |
## If you're here to work autonomously (the loop)
Follow **`agent/LOOP.md`** — one iteration: orient → pick one BACKLOG
item → work → verify → commit+push on `main` → record. All loop state is
git-tracked in `agent/`.
## Rules that apply to every session, loop or not
- **Before your first change, read `agent/VERIFICATION.md`** and follow
it — every change, however small, climbs its V0V3 ladder. For theme
or visual work, also read `agent/THEME-DESIGN.md`; before any VM test,
`docs/TESTING.md`.
- **Honesty rule** (`agent/VERIFICATION.md`, `docs/TESTING.md`): for
visual/interactive changes, evaluation is not rendering — state the
tier you reached. Cheap first: `nix flake check --no-build`.
- **Conventions** (`agent/CONVENTIONS.md`): in-flake state, menu
placement, Waybar parity with whole-swaps, toggle-vs-package discipline,
template as SoT for opt-in comments (`templates/downstream`).
- **Sync sweep on completion** (`agent/LOOP.md` §5): when a task ships,
update or delete every cross-reference it made stale — BACKLOG
pitches/pointers, HARDWARE-QUEUE entries, docs — in the same commit.
No stale items, no unsynced information.
- **Git:** direct commits on `main`, pushed; **`v1` is human-only** —
never touch it. Never `nix flake update` unless the task is a lock bump.
No formatter — match aligned hand-formatting.
- Layout: `hosts/` machine · `modules/` distro · `themes/` data ·
`pkgs/` code · `tools/` maintainer · `agent/` loop · `docs/` human docs.
## Delegation & escalation
Push mechanical work down to cheaper models; keep judgment on the
strongest model available. Capability tiers, standing roles (scout /
runner), and per-harness model mappings: **`agent/DELEGATION.md`**.
Work above your tier? Return it — don't burn tokens.
## Harness adapters
One directory per harness, holding only what that harness *requires* in
its own format (permissions, subagent/skill registration). Adapters point
into `agent/`; they never carry policy, queue items, or vision text.
| Harness | Adapter |
|---------|---------|
| Claude Code | `.claude/``CLAUDE.md` is a symlink to this file |
| others | add a sibling dir + a row here; keep it a thin shim |

1
CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
AGENTS.md

565
README.md
View File

@@ -1,118 +1,497 @@
# 👑 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/ # 24 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
│ ├── battery-notify.nix # low-battery toasts at the bar's thresholds
│ ├── 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)
│ ├── nomarchy-doctor/ # read-only health sheet, one command per failure
│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-theme-sync (gum)
│ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix
├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch)
├── docs/ # human docs — map: docs/README.md
│ ├── VISION.md # product themes toward v1.0 (not a queue)
│ ├── ROADMAP.md # design/decision records + shipped log
│ ├── HARDWARE.md # firmware, profiles, drivers
│ ├── TESTING.md · RECOVERY.md · OVERRIDES.md · MIGRATION.md
├── agent/ # agent instructions + loop state — map: agent/README.md
│ # BACKLOG (executable queue), LOOP, VERIFICATION, …
├── AGENTS.md # agent entry, any vendor (CLAUDE.md symlinks here)
├── .claude/ # Claude Code adapter: permissions + subagent defs
└── 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 — see **[docs/HARDWARE.md](docs/HARDWARE.md)**),
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.)
Already running NixOS on this machine? No reinstall needed — adopt
Nomarchy in place, reusing your existing `hardware-configuration.nix`:
**[docs/MIGRATION.md](docs/MIGRATION.md)**.
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: nomarchy-pull # nix flake update — refresh inputs (nomarchy, nixpkgs, …);
# optional git pull only if ~/.nomarchy tracks a remote
```bash nomarchy-rebuild # system rebuild against the CURRENT lock (sudo inside;
sys-update # Rebuilds the NixOS system (Runs: sudo nixos-rebuild switch --flake .#default --impure) # BTRFS snapshot first when snapper is on; nvd diff after)
env-update # Reloads your Home Manager environment (Runs: home-manager switch --flake .#default --impure) nomarchy-home # home-manager switch — desktop only, no sudo, no lock bump
``` ```
--- `~/.nomarchy` is **your machine flake** (`system.nix` / `home.nix`). Newer
Nomarchy code arrives when `flake.lock` updates the `nomarchy` input
(`nomarchy-pull`), not by `git pull` of that directory (many installs have
no remote there — only local commits from auto-commit).
## 🚀 Commands & Keybindings | When | Run |
|---|---|
| Pull newer Nomarchy / nixpkgs / inputs | `nomarchy-pull` |
| You changed `system.nix` (or after pull) | `nomarchy-rebuild` |
| You changed `home.nix` / theme / desktop | `nomarchy-home` |
| Full upgrade (inputs + both layers) | `nomarchy-pull && nomarchy-rebuild && nomarchy-home` |
The full list lives in [`docs/KEYBINDINGS.md`](docs/KEYBINDINGS.md) (auto-generated from the Hyprland configs). A few highlights: **Order matters for distro updates.** `nomarchy-home` rebuilds against the
**current** `flake.lock` — it never updates inputs. A new Nomarchy revision
lands only when the lock is updated (`nomarchy-pull`). So for an upstream
desktop change: **pull → rebuild → home**. Home before pull rebuilds against
the old inputs and silently skips new home-side changes. After a home-side
keybind/config change, also `hyprctl reload` (or relogin) so the session
re-reads it.
| Keybinding | Action | Legacy aliases still work: `sys-update` → pull+rebuild, `sys-rebuild`
| :--- | :--- | `nomarchy-rebuild`, `home-update``nomarchy-home`.
| `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 Override anything via the `nomarchy.*` surface or plain NixOS/HM options:
Nomarchy includes dozens of productivity scripts available in your PATH. Some highlights: appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`,
- `nomarchy-sync push <repo>`: Backup your setup to Git. behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix`
- `nomarchy-theme-bg-next`: Cycle to the next wallpaper in the current theme. assignment wins, and bind/exec-once lists concatenate. Full guide with
- `nomarchy-menu`: The central hub for all utilities and pickers. examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
--- **Where each option goes.** `nomarchy.system.*`, `nomarchy.hardware.*`, and
*Built with ❤️ using NixOS, Hyprland, Stylix, and the Nomarchy Community.* `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.
**`home.nix`** (Home Manager — the desktop):
| Option | Default | Purpose |
|---|---|---|
| `nomarchy.stateFile` | — (required) | Path to your theme-state.json |
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
| `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; set BOTH `.latitude`/`.longitude` for **geo mode** (wlsunset computes sunrise/sunset from your location; the fixed times are ignored); 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 15 min — battery-only) |
| `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 instantly (driven by a dedicated Hyprland IPC socket watcher, no polling). 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 |
**Always-on, no toggle by design:** `services.cliphist`, `services.udiskie`
(automount + safe-removal toasts) and `services.easyeffects` (mic noise
cancellation) ship unconditionally — small, low-risk pieces with no
Nomarchy-specific config behind them (the toggle-vs-package rule in
`agent/CONVENTIONS.md`). Disable one from `home.nix` with the plain HM
option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see
**[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
**`system.nix`** (NixOS — the machine):
| 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's **v4l2** path 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. Does **not** hide IR from libcamera / portal / Flatpak pickers — see [HARDWARE.md §7](docs/HARDWARE.md) |
| `nomarchy.hardware.i2c.enable` | `false` | I2C devices support — access to `/dev/i2c-*` (RGB controllers, sensors, DDC/CI monitor control) |
| `nomarchy.hardware.i2c.ddcci` | `false` (distro default: **`true`**) | the ddcci-driver kernel module, exposing external monitors as standard backlight devices via DDC/CI so brightness keys and swayosd natively control them |
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN — the login user is made the operator, so `tailscale up/down/set` and the System VPN menu work without sudo |
| `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) and enforces
baseline security/stability defaults. A few worth calling out:
**`security.apparmor.enable`** is on by default for mandatory access
control confinement. **Kernel panics** are configured to auto-reboot the
machine after 10 seconds (`panic=10 oops=panic`) rather than hanging
indefinitely. **`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). Full hardware
story (profiles, fingerprint, NVIDIA, unsupported machines, contributing
a DMI line): **[docs/HARDWARE.md](docs/HARDWARE.md)**. 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;`.
**`zramSwap`** is also on by default (zstd, 50% of RAM, priority 100):
day-to-day memory pressure pages into compressed RAM instead of the
disk, keeping the disk swapfile free for **hibernation** — the installer
creates a RAM-sized encrypted swapfile and wires `resume`, so
Power Hibernate works out of the box (swap size 0 at install = no
swap). Opt out with `zramSwap.enable = false;`; to add hibernation to a
machine installed before it existed, follow
**[docs/MIGRATION.md](docs/MIGRATION.md)** § Enabling hibernation.
## 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 |
Four themes ship a `waybar.css` identity (summer-day, summer-night,
executive-slate, boreal). 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 # 24 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
nomarchy-pull # flake input update (no rebuild)
nomarchy-rebuild # rebuild the system, current lock
nomarchy-home # 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+Space` 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+SHIFT+C` color picker (hyprpicker) · `SUPER+Q`
close · `SUPER+1..9` workspaces · `SUPER+ALT+arrow` move workspace to monitor · `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 (lifecycle: nomarchy-pull / nomarchy-rebuild / nomarchy-home — full names)
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
nfu nfc # nix flake update · check (prefer nomarchy-pull for day-to-day)
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/` (roles are first-class:
when ANSI color0==color8 the tool derives an overlay step; light themes
do not use dark ANSI black as surface — hand-tune after import; do not
bulk-reimport shipped JSON without a hierarchy pass).
- **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
| Doc | Role |
|-----|------|
| **[agent/BACKLOG.md](agent/BACKLOG.md)** | What to do next (agent queue) |
| **[docs/VISION.md](docs/VISION.md)** | Product themes toward **v1.0** |
| **[docs/ROADMAP.md](docs/ROADMAP.md)** | Design history + shipped log |
| **[docs/README.md](docs/README.md)** · **[agent/README.md](agent/README.md)** | Maps |
| **[agent/LOOP.md](agent/LOOP.md)** | Autonomous iteration protocol |
Kept out of the README body so this stays a focused entry point.

151
agent/BACKLOG.md Normal file
View File

@@ -0,0 +1,151 @@
# Backlog — the prioritized task queue
**This is the only executable work list for agents.** Product themes and
v1.0 intent live in [`docs/VISION.md`](../docs/VISION.md); design history
in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in
[`docs/README.md`](../docs/README.md) and [`agent/README.md`](README.md).
**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 (and/or a note in VISION)
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 (include
`VISION § …` or `ROADMAP § …` when relevant); only Bernardo moves items
*out* of PROPOSED into the tiers.
---
## NOW
*(empty — NEXT's top item is the queue head)*
## NEXT
### 76. Hibernation default — all agent work shipped `[blocked:hw]`
Hibernation + zram on by default (product intent, Bernardo 2026-07-10).
**All V0/V1/V2 slices done** (2026-07-10); only the V3 check remains — on a
machine **actually running Nomarchy** (the dev box runs Bernardo's own NixOS,
not Nomarchy, so it can't close it). Steps in HARDWARE-QUEUE. Design record in
**ROADMAP § Hibernation + zram by default**. Shipped:
- zram default (`modules/nixos/oom.nix`, zstd/50%/prio 100) — `checks.zram-swap`.
- Installer/template already default new installs to a RAM-sized encrypted
`@swap` swapfile + `resume_offset` wiring; `swapSize=0` = no-swap opt-out.
- `docs/MIGRATION.md` enable-hibernation runbook (existing machines).
- `rofi.nix` Power → Hibernate: kept unconditional, **notifies on failure**.
- `checks.hibernate` VM test (V2 PASS) — hibernate→resume→crash round-trip
(swap partition); zram (prio 100) doesn't swallow the image, disk carries it.
- `checks.hibernate-swapfile` VM test (V2 PASS) — the installer's btrfs `@swap`
**swapfile** setup: NOCOW `mkswapfile`, file-type swapon, a valid
`map-swapfile -r` offset the kernel accepts (`/sys/power/resume_offset`),
zram above it. (Bounded proof of the swapfile+offset the partition test
can't reach; a full runtime-swapfile hibernate isn't headless-reachable —
systemd `CanHibernate=no` for undeclared swap.)
**V3 remaining (narrowed):** the literal encrypted-swapfile power-cycle —
`Hibernate → power off → unlock LUKS → resume session` on the LUKS+@swap
layout — plus the no-swap notify in a real session. QEMU can't feed a
runtime-computed `resume_offset` into the boot cmdline, and the dev box runs
exactly this layout (`/proc/cmdline` verified) but isn't Nomarchy. Steps in
HARDWARE-QUEUE. Close #76 on PASS.
## 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.
- **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).
## FUTURE (decided deferred — not the agent queue head)
Work we **intend** someday but explicitly **not** NEXT. Agents do not
pick these unless Bernardo promotes one into NEXT/NOW.
### 20. KVM runner → VM suite in CI `[human]`
**Status (2026-07-10):** keep **eval-only** CI on the current Gitea
stack (act_runner in docker-compose on the 4c/4GB IONOS VPS). Nested
KVM + RAM headroom on that host are a poor fit next to Gitea; full
`checks.*` VMs stay local / promotion-time until a **separate**
KVM-capable machine exists.
**When ready:** register a second runner (host-mode nix + `/dev/kvm`,
label `nix-kvm` — not the existing docker eval runner), then uncomment
the `vm-checks` job in `.gitea/workflows/check.yml` (`runs-on: nix-kvm`,
`nix flake check` + toplevel/HM builds). Do not enable the job until
that label is online (Gitea queues forever otherwise).
### Formatter — adopt later `[human]`
**Intent:** add a Nix formatter (likely `nixfmt-rfc-style`) in a dedicated
pass: reformat the tree once, document in CONVENTIONS, optional CI
check. **Not** the queue head — no drive-by reformats until that pass.
## 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.*
*Open work only. Shipped exam/AC items (#47#63, #14, #52 theme
high-ROI, etc.) live in the journal + ROADMAP — not here.*
### Product / day-2
- **NVIDIA first-class options** — **deferred past v1** (Bernardo
2026-07-10). Keep #59 commented install guidance; no
`nomarchy.hardware.nvidia.*` until a hybrid maintainer + queue.
### v1.0 pointer
See **VISION**. Open PROPOSED: NVIDIA wrappers deferred past v1; IR
portal (b)/(c) need T14s hardware (HARDWARE-QUEUE § T14s). Standing
calls: browser default = Chromium; power backend = PPD.
## Decisions `[human]`
Open calls only Bernardo can make; agents add options/evidence but never
decide. **Resolved** entries stay for history; agents treat them as closed.
### Resolved (2026-07-10)
- **Docs site vs Markdown-in-repo** — **markdown in-repo for now**
(`docs/`, README). A rendered docs site is FUTURE if wanted.
- **Default browser** — **ship Chromium** in
`templates/downstream/home.nix`; mime → `chromium-browser.desktop`.
Opt out: delete the line / override mime.
- **Default power backend** — **keep PPD** (`nomarchy.system.power.backend`
default). TLP remains the one-line opt-in. Rationale: stability + live
profile API for menu/Waybar; Omarchys TLP experiment reverted.
### Resolved (2026-07-10, more)
- **Formatter adoption** — **yes, but not now.** Tracked as FUTURE
(below). Nix-source style only (`nixfmt-rfc-style` or similar); one
bulk reformat + CI/check when promoted. Until then: hand-aligned
style per CONVENTIONS.
- **Hibernation** — **want by default** (product intent). Needs a
disk-backed swap (file or partition) sized for resume; not zram alone.
Implementation is a future/NEXT item when designed (installer +
resume device + encrypted-root story).
### Resolved (2026-07-10, #76 design)
- **Swap sizing** — **exactly RAM** (installer default, unchanged). Hibernate
image ≤ RAM; zram takes day-to-day paging. **`swapSize=0`** stays no-swap.
- **Migration** — **docs runbook** (`docs/MIGRATION.md`), not a tool.
- **No-swap Hibernate** — keep the menu row; **notify on failure**.

72
agent/CONVENTIONS.md Normal file
View File

@@ -0,0 +1,72 @@
# 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`. That template is the
single source of truth for machine files: `nomarchy-install` copies it
and patches install-time values only (never a thinner second catalog).
- **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,
executive-slate and boreal (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.

112
agent/DELEGATION.md Normal file
View File

@@ -0,0 +1,112 @@
# Delegation — capability tiers, roles, token economy
How to spend model capacity in this repo, for **any** agent harness.
Tasks are matched to capability *tiers*, not vendor model names; each
harness maps tiers to its own models (table at the bottom). GOALS says
what to build, CONVENTIONS how to write it — this says who does which
part.
## The dividing line: evidence vs judgment
Cheap models gather evidence; they never make verification claims or
design decisions. The following always stay on the **frontier** tier and
are never delegated downward:
- deciding what to test and the regression scope (VERIFICATION.md)
- interpreting an ambiguous or flaky failure
- viewing screenshots and judging visual quality — aesthetic judgment is
exactly what small models do badly, and it's load-bearing here
- writing non-trivial Nix (module structure, overlays, cross-cutting
refactors)
- the final diff review and report
Product calls ("finish vs quarantine", promoting PROPOSED items) sit
above even that: they belong to the human.
## Tiers
| Tier | Best for | Delegate to it? |
|------|----------|-----------------|
| **light** | bulk mechanical: search, summarize, audit sweeps, running the harness | freely — never for judgment |
| **standard** | well-specified scoped edits, routine research | when the spec is already written |
| **frontier** | design, novel code, ambiguous failures, taste | this is the loop's own tier |
Reasoning effort: default moderate; go maximum only for the hardest
calls. A frontier parent may spawn a frontier child for one hard call.
Work above your tier gets returned, not attempted.
## Standing roles
Two mechanical roles exist for any harness that supports subagents
(Claude Code implementations: `.claude/agents/`; other harnesses
implement the same contracts in their own format):
- **scout** (light, read-only): locate where things are defined, map
which files touch a subsystem, scan build logs / serial output for
error lines, docs-vs-code drift sweeps. Reports facts with paths and
line numbers, quotes the minimum snippet, says "not found" plainly —
never guesses, never recommends.
- **runner** (light, executes): builds, VM boots, screenshot capture,
the scripted `tools/` checks. Headless and unattended, every wait
bounded by a timeout. Returns commands, exit codes, wall time, and
artifact paths — never marks anything passed or verified; the caller
makes the verification claim.
Use them for pure information-gathering or pure execution instead of
pulling bulk (log files, wide scans) into the main context. When a
result surprises you, spot-check it yourself before building on it —
cheap models are allowed to be wrong about hard things, which is
precisely why they're not allowed to make claims.
## Economy rules
- **Only delegate when writing the spec is cheaper than doing the
work** — a spawned agent starts cold and must re-derive context. A
one-file read is cheaper done directly.
- **Brief every child cold; point at the spec, don't restate it.** "The
spec is in `agent/BACKLOG.md` #NN — implement it" plus only the
*constraints* (scope files, branch, no-VM, no-push).
- **Match the tier to the task, not the prestige.** An item whose spec
is already written (exact files, exact fixes) is standard-tier work;
reserve frontier children for genuine multi-step reasoning. This is
the single biggest saving.
- The strong model writes the spec, reviews the result, and owns the
commit.
## Fanning out parallel work (V0/V1, no VM)
When several NEXT items are independent and don't need the VM, spread
them across worktree-isolated subagents in parallel:
- **Disjoint file lanes.** Partition items so no two agents touch the
same file (map the touched files first). If two items must share a
file (README, flake.nix, rofi.nix), give both to one agent or keep
one for yourself.
- **Isolation + you own landing.** Each agent works in an isolated
worktree, commits to its own branch, and **never pushes or touches
`main`/`v1`**. You review each diff, cherry-pick onto `main`, and do
the bookkeeping — a single landing agent can't race itself. Clean up
worktrees and branches after landing.
- **Lean on scriptable checks as primary evidence.** Where a
deterministic `tools/` check or `checks.*` guard already proves the
property, that near-free run *is* the V0/V1 evidence.
- **Batch V2 at the end, once.** Delegated visual/behavioural items come
back "V2 pending"; collect the landed changes and do **one** VM pass
covering all of them — the VM render + screenshot review is the most
expensive step in the loop; amortise it.
- **Re-verify on `main`, but leanly.** After landing, confirm the
agent's V0/V1 on the merged tree with a targeted build, not a full
re-run. Trust-but-spot-check scales; blind re-running doesn't.
The judgment list above still holds: *you* review every diff before it
lands.
## Per-harness model mapping
| Tier | Claude Code |
|------|-------------|
| light | `haiku` |
| standard | `sonnet` |
| frontier | the session's top model (`opus` and up) |
Other harnesses: add a column when one is actually used on this repo.

60
agent/GOALS.md Normal file
View File

@@ -0,0 +1,60 @@
# Goals — what "done" looks like for Nomarchy
The north star every loop iteration serves. When two options conflict,
the earlier pillar wins. Product *themes* toward a **v1.0** ship (day-2
confidence, default identity, release bar) live in
[`docs/VISION.md`](../docs/VISION.md) — still subordinate to these pillars.
## 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.

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

@@ -0,0 +1,266 @@
# 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. Checked-off entries are **pruned**
on the next sync sweep (LOOP.md §5) — outcomes live in the journal/ROADMAP;
git history is the archive. Machines: the **AMD dev box** (Ryzen AI
laptop: AMD + fingerprint + NPU), the **Latitude 5310/5410** (Intel QA),
the **T14s** (webcam case).
## Any machine (dev box is fine)
- [ ] **#55 fingerprint enroll on real reader** — with
`nomarchy.hardware.fingerprint.enable` and a physical reader: System
Fingerprint Enroll a finger; List shows it; Verify succeeds; optional
Use for login (on) → `nomarchy-rebuild` → login/sudo accept fingerprint.
Menu surface is V1/V2 without a reader (self-gate + dry paths).
- [ ] **#60 non-BAT* battery name (if available)** — on a machine whose
system battery is **not** named `BAT*` (e.g. `CMB0`): confirm
charge-limit oneshot writes the threshold, System Battery limit
and Power profile rows appear, Waybar power-profile module shows,
doctor charge-limit section runs. On `BAT0`-only machines this is
a no-op (charge-limit re-apply passed on `BAT0` — Latitude
2026-07-10).
- [ ] **btop theme fidelity (#52 residual)** — softGL theme-shot cannot
open Ghostty, so the hand `btop.theme` assets were only guest-file
asserted (main_bg / inactive_fg keys) + desktop/bar rendered for
rose-pine, everforest, summer-night, vantablack, catppuccin. On a
real session: `nomarchy-theme-sync apply <slug>` then `btop` and
confirm backgrounds/text match the theme (esp. rose-pine Dawn light
bg `#faf4ed`, vantablack near-black `#0d0d0d`, catppuccin Mocha
`#1E1E2E`). One pass cycling those five is enough.
- [ ] **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.
- [ ] **Notification inhibited-state glyph** (item 28 color-only sweep,
iteration #69) — have an app hold a notification inhibitor (e.g. run
a fullscreen video, or `busctl --user call org.freedesktop.Notifications
… Inhibit`); the Waybar bell must switch to the muted **bell-off** 󰂛
(same glyph as DND), NOT the normal bell 󰂜/󰂚. Then release it → the
bell returns. Repeat on a whole-swap bar (summer-day/night use their
own bell-off glyph, executive-slate/boreal use 󰂛). The glyph itself
already renders (DND uses it); this only confirms swaync emits the
`inhibited-*` class and the bar routes it.
- [ ] **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 was a real bug → BACKLOG item 26 (fix shipped; SUPER+?
cheatsheet PASS on Latitude 2026-07-10).
- [ ] **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.
- [ ] **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.
- [ ] **Snapshots restore + rollback exercise** (residual — the
GUI/polkit half passed on hardware 2026-07-04, a47aa3a): in
`sudo nomarchy-snapshots`, restore a single file (`undochange`)
and walk a root-config rollback up to (or through) the
typed-`yes` gate.
- [ ] **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
`nomarchy-home`: 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; a link → **Chromium**
(`chromium-browser.desktop` — template default as of 2026-07-10).
- [ ] **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.
- [ ] **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.
- [ ] **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.)
- [ ] **Audio opens in Amberol** (iteration #60, item 37) — after
`nomarchy-home`: 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.)
- [ ] **rofi menu polish** (iteration #58, item 39 + #56/#57 34/40) —
after `nomarchy-home`: (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).
- [ ] **Screenshot annotation (satty)** (iteration #73) — after `nomarchy-home`:
hit SUPER+SHIFT+Print (or Tools ▸ Capture ▸ Annotate region) → a region
select (slurp) appears, then the `satty` UI opens in fullscreen with the
screenshot loaded. Check that the UI draws on the current theme palette
(tools colored properly) and hitting save places the screenshot in
`~/Pictures/Screenshots/` while hitting copy places it in the clipboard.
- [ ] **#76 hibernate→resume on a real Nomarchy install** — the V2 VM test
proves the resume mechanism + zram coexistence, but QEMU can't feed a
boot-time `resume_offset` for a btrfs *swapfile* nor drive the LUKS
initrd unlock, so this triple needs hardware. **Requires a machine
actually running Nomarchy** (installer default swap = RAM), NOT the dev
box — that box runs Bernardo's own NixOS (TuringMachine, 25.11); its
layout resumes fine (verified 2026-07-10) but that only corroborates
the *layout*, not Nomarchy's oom.nix/patch-template/rofi wiring.
Candidate: a fresh Nomarchy install on a Latitude, or the dev box
*after* migrating to Nomarchy (docs/MIGRATION.md).
1. Confirm the wiring on the Nomarchy box: `swapon --show` lists
`/swap/swapfile` (Type file) **and** `/dev/zram0` at priority 100;
`cat /proc/cmdline` shows `resume_offset=…` + `resume=` the btrfs UUID.
2. Open apps, note an unsaved in-RAM state. `nomarchy-menu → Power →
Hibernate` (or `systemctl hibernate`). Machine writes image, **powers
off**.
3. Power on: unlock LUKS **once** in the themed prompt; land back in the
**same session** — state restored, not a fresh login.
4. **Pass** = session restored after full power-off + single unlock.
Fail (fresh boot / stuck initrd) → NOW bug in BACKLOG (capture
`journalctl -b -1 | grep -i -E 'hibernat|resume|swap'`).
- [ ] **#76 no-swap Hibernate notify** — on a **Nomarchy** machine installed
with `swap = 0` (or temporarily `sudo swapoff -a` on one): `nomarchy-menu
→ Power → Hibernate` must surface a desktop notification ("Couldn't
hibernate — likely no swap is configured. See docs/MIGRATION.md →
Enabling hibernation."), **not** a silent no-op. (`swapon` after, if you
swapoff'd for the test.)
- [ ] **Night-light geo mode (2026-07-11)** — in `home.nix` set
`nomarchy.nightlight = { enable = true; latitude = "<lat>";
longitude = "<long>"; }` (your real coordinates), `nomarchy-home`,
relogin. Then: `systemctl --user status wlsunset` is running (and
`hyprsunset` is NOT); after local sunset the screen visibly warms
(or test by setting coordinates where it's currently night); the
Waybar moon + menu toggle still flip it instantly and an *off*
survives relogin (the ExecCondition gate on the swapped unit —
the eval check proves the wiring, not the runtime gate). **Pass** =
warm shift at the location's night + toggle/persistence intact.
## 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.
- [ ] **System ▸ Firmware menu on real LVFS hardware** (item #43,
`nomarchy-menu firmware`) — on a machine whose firmware/SSD/dock is
on LVFS: open Menu ▸ System ▸ **Firmware**; confirm the terminal runs
`fwupdmgr refresh` → lists real `get-updates` → the y/N confirm gates
correctly → `fwupdmgr update` applies and prints the reboot note when a
capsule needs one. Verify **no** flash happens on "N"/cancel. (VM only
proves the menu row + flow renders; a real capsule write is
hardware-only.)
## Latitude 5310 / 5410 only
- [ ] **v1 QA batch on-hardware pass** (583708d batch was QEMU-verified) —
general smoke before the next `main → v1` promotion (broader than
the 2026-07-10 session; include theme/ISO/greeter leftovers).
## 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.
- [ ] **Portal/Flatpak libcamera IR (b)/(c)** — after #71 docs: on hardware,
confirm a Flatpak/portal picker still lists the internal IR node;
only then investigate (b) WirePlumber libcamera GREY-only monitor
rule or (c) libcamera/udev-layer hide. Do not ship either without a
live dual-sensor check (see HARDWARE.md §7 / ROADMAP § Webcam).

1884
agent/JOURNAL-ARCHIVE.md Normal file

File diff suppressed because it is too large Load Diff

531
agent/JOURNAL.md Normal file
View File

@@ -0,0 +1,531 @@
# Journal — append-only iteration log
One entry per iteration, **newest first**. This is the loop's short-term
memory: the next session reads the last 35 entries to orient. Keep an
entry under ~15 lines; durable lessons go to MEMORY.md, not here. When
this file grows past ~25 entries, rotate the tail to
[JOURNAL-ARCHIVE.md](JOURNAL-ARCHIVE.md) (history, never edited).
Template:
```markdown
## YYYY-MM-DD — <task title> (<commit sha, or "no commit">)
- **Task:** which BACKLOG item (or QA sweep / bootstrap / escalation)
- **Did:** what changed, in 24 lines
- **Verified:** tier reached (V0/V1/V2) + the actual commands/checks run
- **Pending:** V3 queued? follow-ups filed? anything [stuck]?
- **Next suggestion:** what the following iteration should probably take
```
---
## 2026-07-11 — Night-light geo mode (lat/long → wlsunset)
- **Task:** BACKLOG LATER "Night-light geo mode" (human-picked).
- **Did:** `nomarchy.nightlight.latitude`/`.longitude` (both set = geo mode):
`services.wlsunset` (location-computed sunrise/sunset, `.temperature`
night temp) replaces hyprsunset; the toggle script's `unit=` and the
live-state ExecCondition follow via one `unit` let-binding. Template
example lines, README row, ROADMAP ✓. New `checks.nightlight-geo`:
pure-eval asserts on the rendered units (coords + temp in ExecStart,
ExecCondition present, hyprsunset off). Declarative-only (no menu writer —
coords are machine config like keyboard layout).
- **Verified:** V1 — `nix flake check --no-build` exit 0 (evals the new
asserts + option-docs); built `downstream-template-home` (default
hyprsunset path unregressed) + the check drv. Caught en route: HM renders
`ExecStart` as a list (`toString` needed in eval asserts).
- **Pending:** V3 queued (HARDWARE-QUEUE § Any machine): visible warm shift
at location night + toggle/persistence on the swapped unit.
- **Next suggestion:** wallpapers artifact split (LATER, decided).
## 2026-07-11 — Sync-sweep rule + grooming + zram user docs (human-requested)
- **Task:** Bernardo: groom stale items and codify the check so they don't
recur; close the zram user-doc gap.
- **Did:** LOOP.md §5 gains step 4 **Sync sweep** (grep item/feature across
agent/+docs/ on ship; prune recorded `[x]` queue entries) + an AGENTS.md
rule line. Applied it: BACKLOG PROPOSED pruned (auto-theme pitch was
stale — shipped as #79; shipped/promoted placeholders removed; v1.0
pointer rewritten; FUTURE promoted-stub dropped). HARDWARE-QUEUE pruned
29 checked `[x]` entries (363→~250 lines), dangling refs reworded,
prune rule in header. README §3 now documents the zram default +
hibernation out-of-the-box + MIGRATION pointer.
- **Verified:** V0 (docs/meta only) — grep sweep for dangling refs clean.
- **Pending:** night-light geo mode next (same session).
- **Next suggestion:** wallpapers artifact split (LATER, decided).
## 2026-07-11 — Vendor-neutral agent docs restructure (human-requested)
- **Task:** Bernardo: de-vendor the agent instructions (multi-model repo),
clean stale files, better docs/agent/.claude structure.
- **Did:** New root `AGENTS.md` entry (CLAUDE.md → symlink). Skill bodies
moved to `agent/`: VERIFICATION.md (enforcement, ex-nomarchy skill),
DELEGATION.md (tiers light/standard/frontier + scout/runner contracts +
fan-out; consolidates CLAUDE.md table + LOOP §economy + skill §6.5),
THEME-DESIGN.md (ex-theme skill, now with frontmatterd shim). `.claude/`
is a thin adapter (shims route into `agent/`). Maps updated (agent/,
docs/, README tree). Stale: `.gitignore` old_distro entry dropped; local
result*/__pycache__ removed. JOURNAL rotated (29 kept, 120 →
JOURNAL-ARCHIVE.md).
- **Verified:** V0 — `nix flake check --no-build` exit 0 with the symlink
tracked; vendor-name grep clean outside DELEGATION's mapping table.
- **Pending:** nothing; other harnesses add their own adapter row when used.
- **Next suggestion:** #76/#79 remain [blocked:hw]; otherwise groom PROPOSED.
## 2026-07-11 — #76 checks.hibernate-swapfile (VM-verify the swapfile+offset)
- **Task:** Bernardo — push #76's swapfile-resume residual into a VM (like
the auto-theme sunset trick).
- **Did:** Tried a full runtime-swapfile hibernate→resume test; it hit a
bounded, definitive **`CanHibernate=no`** — systemd won't hibernate to a
swap that isn't declared in the boot config, and a runtime-computed
`resume_offset` can't reach the boot cmdline (the genuine chicken-and-egg).
First cut also had an **unbounded `wait_for_shutdown`** that hung → killed
it, bounded the wait in a thread (180s) + a `CanHibernate` pre-check, which
is what surfaced the clean verdict. Pivoted `checks.hibernate-swapfile` to
what a VM *can* prove deterministically and the partition test can't: the
installer swapfile **build + offset** — NOCOW `mkswapfile`, file-type
swapon, valid `map-swapfile -r` offset accepted by `/sys/power/resume_offset`,
zram above it. (Device fix: `/dev/vdb` once `useNixStoreImage` was dropped.)
- **Verified:** **V2 PASS** (fresh build, exit 0). BACKLOG/ROADMAP updated;
#76 residual narrowed to the literal encrypted power-cycle (dev box runs it).
- **Lesson:** the sunset-trick spirit applies but not always the full cycle —
when systemd/kernel gates the behaviour (CanHibernate), verify the
*construction* deterministically and leave only the physical cycle to HW.
Always bound `wait_for_shutdown` (no timeout param → thread + join).
- **Next suggestion:** #76/#79 both `[blocked:hw]`; QA / PROPOSED otherwise.
## 2026-07-11 — Code-review fixes (parallel review of the session batch)
- **Task:** Bernardo asked for parallel work; spawned a fresh-eyes opus
review of `2055842..HEAD` (static). It found the batch sound — 2 real LOW
edge bugs, 1 false positive, 1 cosmetic.
- **Did:** (1) `cmd_auto` — inverted sunrise/sunset (sunrise > sunset) went
perpetually day; replaced the `not between` shortcut with a real
wrap-around `mins >= sunrise or mins < sunset`, so a cross-midnight day
window switches both ways. (2) `rofi.nix` autotheme HH:MM guard tightened
`[0-2][0-9]``[01][0-9]|2[0-3]` so 2429 are rejected. Dismissed the
`breeze` finding (papirus DOES ship breeze/breeze-dark — verified) and the
cosmetic doctor-RAM one.
- **Verified:** py_compile; boundary truth-table (normal + wrap-around);
HH:MM case table (23:00/19:59/20:00 accept, 24/25/30:00 reject); `auto
--which` normal-case unchanged; menu bash -n; flake check green. Normal
case unchanged so `checks.auto-theme` still holds.
- **Pending:** hibernate-swapfile VM test still running (uncommitted flake.nix).
- **Next suggestion:** land hibernate-swapfile once its VM test returns.
## 2026-07-10 — #79 V2: checks.auto-theme (simulated sunset) — was mis-framed V3
- **Task:** Bernardo challenged the "V3 real-session" framing — a simulated
sunset IS VM-testable. He's right; I'd conflated "needs a real desktop"
with "needs the generic home-manager switch."
- **Did:** Added `checks.auto-theme` runNixOSTest: installs nomarchy-theme-sync,
points `NOMARCHY_DEFAULT_THEMES` at presets, stubs the switch via
`NOMARCHY_REBUILD` (a marker), configures the pair+schedule, then **walks
the VM clock** 19:55→20:05→20:06→next-day 07:30 asserting slug goes
summer-day → night (1 rebuild) → night (idempotent, still 1) → day (2). The
self-gating I relied on: presets via env, wallpaper/notify no-op headless,
run_switch honors NOMARCHY_REBUILD + waybar nudge is check=False.
- **Verified:** **V2 PASS**`nix build .#checks…auto-theme` fresh, exit 0.
Dropped #79's `[blocked:hw]`; it's done (V2). Updated ROADMAP.
- **Lesson:** don't default "timer/session feature" to V3 — stub only the
generic step (the real HM switch, covered elsewhere) and simulate the rest
(clock) headlessly. Only the literal timer-fires-on-OnCalendar is on-HW,
and its schedule is systemd-analyze-validated.
- **Next suggestion:** NEXT head #76 `[blocked:hw]`; else QA / PROPOSED.
## 2026-07-10 — #79 slice 3: Look & Feel Auto theme menu (#79 complete)
- **Task:** BACKLOG #79 slice 3 — the menu; completes the feature.
- **Did:** `rofi.nix` — "Auto theme (on/off)" row in Look & Feel + an
`autotheme)` submenu: toggle, pick day/night (from
`nomarchy-theme-sync list`), set sunrise/sunset (HH:MM input, validated).
Enable writes the flag + defaults the summer pair `--no-switch`, then
`exec auto --force` — one rebuild installs the timer AND applies the right
theme. Disable is instant `--no-switch` (`auto` self-gates; the lingering
timer no-ops). Day/night/times are live `--no-switch`.
- **Verified:** **V1** — HM generation builds (`writeShellScriptBin` bash -n
on nomarchy-menu passes) + bash -n on the built menu. **Logic test**
(stubbed rofi/theme-sync/notify): selecting "Auto theme (off)" runs exactly
`set day summer-day`, `set night summer-night`, `set enable true`
(all `--no-switch`), then `auto --force` — the intended one-rebuild path.
Disable path is static-clear (set false `--no-switch`, no `auto`); a
stateful-stub dynamic test would loop (stub doesn't reflect the flip). V0
flake check green. **#79 collapsed to `[blocked:hw]`** (all 3 slices done;
only live-session switch remains, needs a Nomarchy machine).
- **Pending:** V3 real-session (timer switches day↔night across sunset).
- **Next suggestion:** NEXT head #76/#79 both `[blocked:hw]`; QA/PROPOSED.
## 2026-07-10 — #79 slice 2: auto-theme systemd timer
- **Task:** BACKLOG #79 slice 2 — the timer/hook.
- **Did:** `modules/home/autotheme.nix` (imported in home default.nix):
`systemd.user.{service,timer}.nomarchy-auto-theme`, install gated on
`config.nomarchy.settings.autoTheme.enable or false` (state-driven, like
updates.nix gates on its enable). Service runs `nomarchy-theme-sync auto`
with a PATH for `home-manager`; timer `OnStartupSec=1min` +
`OnCalendar=*:0/15` + `Persistent`. Chose periodic over exact
sunrise/sunset timers — robust to suspend/DST, and `auto` is idempotent so
ticks are cheap (rebuild only at the transition).
- **Verified:** **V1** — real-config eval (state temp-enabled, restored):
`settings.autoTheme.enable`→true, timer `OnCalendar`=`*:0/15`, service
ExecStart=`… nomarchy-theme-sync auto`; disabled→no units (isolation eval
condition=false). `systemd-analyze calendar '*:0/15'` valid. **V0** flake
check green. (Gotcha: a new .nix file is invisible to the flake until
`git add` — flake eval only sees tracked files.)
- **Pending:** live timer→`home-manager switch` on a real Nomarchy session
(needs a Nomarchy machine, like #76). Slice 3 (menu) next.
- **Next suggestion:** #79 slice 3 — Look & Feel Auto theme menu.
## 2026-07-10 — #79 slice 1: auto time-of-day theme CLI primitive
- **Task:** Split BACKLOG #79 (`VISION § D`, `[big]`) into 3 slices (CLI /
timer / menu); took slice 1.
- **Did:** `nomarchy-theme-sync auto` — reads
`settings.autoTheme.{enable,day,night,sunrise,sunset}`, computes day/night
by the local clock, and applies the matching preset **only if different**
(idempotent → the slice-2 timer won't rebuild needlessly). `--which`
prints the decision without switching; `--force`/`--no-switch` too.
Factored `cmd_apply`'s core into `apply_named` so `auto` reuses the exact
same one engine (GOALS: no second pipeline).
- **Verified:** **V1** package builds; functional tests on the built bin
against scratch state: wide-day→day slug, 00:0000:01 window→night,
disabled→no-op, missing day/night→die, `auto --no-switch` writes state,
re-run→"already on" no-op, plain `apply` still writes (refactor intact).
**V0** py_compile + flake check + `checks.theme-sync-validate` green.
- **Pending:** slices 2 (systemd sunrise/sunset timers + login apply, V2)
and 3 (Look & Feel menu) remain under #79.
- **Next suggestion:** #79 slice 2 — the timer/hook (V2 runNixOSTest).
## 2026-07-10 — #78 Omarchy migrant doc (docs/OMARCHY.md)
- **Task:** BACKLOG #78 (promoted from PROPOSED) — Omarchy→Nomarchy
one-pager (`VISION § F`).
- **Did:** New `docs/OMARCHY.md`: leads with the config-model shift
(imperative dotfiles → declarative flake + menu, rollback via
generations), then install (ISO/`nomarchy-install` or MIGRATION.md),
a familiar-bindings table, theming (`SUPER+T`/`nomarchy-theme-sync`),
packages (home.packages / `nix shell`, no AUR), update+rollback
(`nomarchy-pull`/`-rebuild`/`-home`, boot-menu generations,
`nomarchy-doctor`). Added to `docs/README.md` map + VISION § F ✓.
- **Verified:** V0 (docs). Grounded Nomarchy side in code: pulled real
binds from `keybinds.nix`; confirmed all 7 cited commands, the `nsearch`
alias, and the named slugs (gruvbox/rose-pine/nord) exist. Kept
Omarchy-side claims to known facts (Arch, dotfiles, pacman/AUR).
- **Pending:** none.
- **Next suggestion:** NEXT head #76 `[blocked:hw]`; last open PROPOSED is
auto time-of-day theme pair (`§D`, `[big]` — needs splitting).
## 2026-07-10 — #77 doctor hibernate/sleep section (V2 PASS)
- **Task:** BACKLOG #77 (promoted from PROPOSED by Bernardo) — read-only
hibernate section in nomarchy-doctor (`VISION § C`, #76 follow-on).
- **Did:** Added a hibernate/sleep row group to `nomarchy-doctor.sh`: resume
device on cmdline (+ `resume_offset` for a swapfile), disk swap ≥ RAM,
zram active, previous-boot suspend/hibernate error. Self-gates (`skip` on
swap=0); only ok/warn/skip → never fails the sheet. Added `hibernate` to
the `checks.doctor` probe list.
- **Verified:** **V2 PASS**`checks.doctor` VM test green (fresh build,
exit 0; asserts the hibernate row renders + healthy run still 0). **V1**
ran on this box's real LUKS+@swap layout: all three `ok` rows render
(resume set, swap 34G ≥ RAM, zram 16G) and the sheet reaches its verdict.
**V0** bash -n + flake check.
- **Gotcha (fixed + MEMORY):** the doctor runs `set -euo pipefail`; my first
cut aborted before the verdict because a no-match `grep` inside `$(…)` and
a `cond && action` both trip set -e. Fixed with `|| true` / `|| echo 0` and
an `if`. First VM run caught it (exit 1, no verdict) — the test earned its keep.
- **Pending:** none — fully agent-verifiable (read-only checks, no V3).
- **Next suggestion:** NEXT head is #76 `[blocked:hw]`; remaining PROPOSED
(Omarchy doc §F, auto time-of-day theme §D) await triage.
## 2026-07-10 — QA sweep (clean) + VISION-gap pass → 3 PROPOSED
- **Task:** Bernardo — QA/drift sweep, then a VISION-gap pass with proposals.
- **Did (sweep):** Fanned out two subagents (Bernardo OK'd spawning for
efficiency): nomarchy-runner ran the deterministic checks + `bash -n`/
`py_compile` (all PASS; the 4 "needs-args" scripts pass via their flake
derivations); nomarchy-scout hunted fuzzy drift (dead code, stale refs,
TODOs, changed-area contradictions, count/version) — **all clean**.
Spot-checked scout's count claim myself: 24 theme JSONs / 24 previews /
README "24 presets" — consistent. Eval sweep green. **No fixes needed.**
- **Did (VISION):** Verified candidate gaps against the tree before
proposing (killed two false gaps — nomarchy-rebuild/home already tail logs
+ point at doctor on failure = §A shipped; `nvd diff` = §B covered; System
Firmware menu already exists). Filed 3 **PROPOSED** (verified open):
doctor hibernate/sleep section (§C, top Day-2 pick, #76 follow-on),
Omarchy migrant one-pager (§F), auto time-of-day theme pair (§D, `[big]`).
- **Verified:** V0 (agent-notes/docs only). Sweep evidence above.
- **Pending:** none. Proposals await Bernardo's triage into a tier.
- **Next suggestion:** if a PROPOSED is promoted, the doctor hibernate
section is the cleanest next build; else NEXT #76 stays `[blocked:hw]`.
## 2026-07-10 — Icon overrides: user-facing docs (OVERRIDES.md)
- **Task:** Bernardo asked whether the session's features need user docs.
- **Did:** Hibernation already covered (README swapfile line, MIGRATION.md
runbook, discoverable Power-menu row) — no gap. Icons had one: the only
doc was the template comment. Added an "Icon pack" subsection to
`docs/OVERRIDES.md` § Appearance — `nomarchy-theme-sync set icons "<name>"`,
the known-packs table, the ~1 GiB/opt-in rationale, and that the choice is
sticky across `apply` (presets carry no icon field; verified `deep_merge`).
- **Verified:** command path confirmed (`cmd_set` writes any dotted key;
`icons` is a schema key). **Built all 4 packs and listed real theme dirs**
to fix the examples — caught Qogir using capital `Qogir-Dark`/`Light`
(lowercase would silently fall back to hicolor). Docs-only (V0).
- **Pending:** none.
- **Next suggestion:** NEXT #76 `[blocked:hw]`; LATER/QA-sweep next.
## 2026-07-10 — Icon themes: opt-in cross-pack overrides (no default bloat)
- **Task:** LATER "per-theme icon overrides / more icon packs". Recon found
papirus is already ~1 GiB and ships only Papirus/Dark/Light (no free
variety); real variety needs extra packs at ~hundreds of MB each. Bernardo
chose the **opt-in, no-default-bloat** shape (over shipping packs).
- **Did:** `theme.nix` resolves `nomarchy.theme.iconThemePackage` from a
small `iconPacks` registry (papirus·tela·qogir·reversal·numix, by name
prefix). Default themes name `Papirus-*` → resolves to the *plain* papirus
derivation (no symlinkJoin, identical closure); only a theme naming another
pack's set symlinkJoins that one pack in. `stylix.nix` uses it
(mkDefault). Opt-in note in `templates/downstream/home.nix`; ROADMAP §
Icon themes updated (incl. the deliberate no-default-packs decision).
- **Verified:** **V1** — HM activationPackage builds (exit 0). Eval proofs:
default `iconThemePackage` = plain `papirus-icon-theme-*` (zero delta);
with `icons="Tela-dark"``nomarchy-icon-themes` symlinkJoin (papirus+tela)
and name flows, **without building tela**. All 5 pack attrs exist in the
pinned nixpkgs. `template-sot` + `option-docs` + `flake check` green.
- **Pending:** none. No default visual change (identical papirus), so no
screenshot needed; the opt-in render is the downstream user's to see.
- **Next suggestion:** NEXT still #76 `[blocked:hw]`; LATER/QA-sweep next.
## 2026-07-10 — #76 slice 3: hibernate VM test (V2 PASS) → #76 agent-complete
- **Task:** BACKLOG #76 slice 3 — the mandatory V2 hibernate test + V3 queue.
- **Did:** Added `checks.hibernate` (flake.nix), modeled on nixpkgs'
hibernate.nix but wired to Nomarchy's #76 risk: imports oom.nix (zram
default-on), disk swap at lower priority + explicit `boot.resumeDevice`,
systemd-initrd. Test drives hibernate→power-off→resume→crash: ramfs marker
survives resume, a crash-boot loses it (real resume, not a survivor), and
asserts zram@100 + disk-swap<100 so the image lands on disk not volatile
zram. Queued the V3 laptop steps (real LUKS+@swap+offset resume + no-swap
notify) in HARDWARE-QUEUE. Collapsed #76 to a `[blocked:hw]` stub; design
record → ROADMAP § "Hibernation + zram by default".
- **Verified:** **V2 PASS**`nix build .#checks.x86_64-linux.hibernate`
built the driver fresh and exited 0 (all assertions held). `nix flake
check --no-build` green.
- **Pending:** only V3 (dev box / laptop) — #76's last check; close on PASS.
- **Next suggestion:** #76 is now blocked:hw; NEXT head falls to LATER/
PROPOSED triage or a QA sweep — pick per LOOP.md orientation next session.
## 2026-07-10 — #76 slice 2: Hibernate notify-on-failure
- **Task:** BACKLOG #76 remaining slice 2 — per the settled "keep row, notify
on failure" call.
- **Did:** `modules/home/rofi.nix` power menu Hibernate arm now runs
`systemctl hibernate 2>/dev/null || notify-send "Hibernate" "…see
docs/MIGRATION.md"`. On success the call blocks until resume, so the notify
only fires on a genuine failure (e.g. swap=0 no-swap machine). Row stays
unconditional (no hiding).
- **Verified:** **V1**`nix build …homeConfigurations.nomarchy.activationPackage`
green (rebuilds `nomarchy-menu.drv`; `writeShellScriptBin` runs bash -n at
build). Control-flow proof on the arm extracted from the built script:
failing `systemctl` → notify fires; succeeding → no notify. `nix flake
check --no-build` green.
- **Pending:** slice 3 = the V2 hibernate→resume VM test + V3 laptop
(Hibernate→power off→resume on the LUKS+@swap machine; plus confirm the
no-swap notify surfaces in a real session). Both remain open on #76.
- **Next suggestion:** slice 3 — add a `checks.*` hibernate VM test (as far
as QEMU allows) and queue the V3 laptop steps in HARDWARE-QUEUE.
## 2026-07-10 — #76 slice 1: MIGRATION.md hibernation runbook
- **Task:** BACKLOG #76 remaining slice 1 — docs runbook for enabling
hibernation on an existing machine (per the settled "docs not tool" call).
- **Did:** Added § "Enabling hibernation on an existing machine (no reinstall)"
to `docs/MIGRATION.md`: create `@swap` subvol (subvolid=5) + `mkswapfile`
(NOCOW), read `map-swapfile -r` offset, wire `fileSystems."/swap"` +
`swapDevices` + `resumeDevice` + `resume_offset` into system.nix. Flagged
the mount as required on the hand-edit path (installer gets it from
disko-gen hardware-config). Notes zram-priority reservation, no-LUKS
variant, `swap=0` opt-out. Worked example = this dev machine.
- **Verified:** **V0** — read-only commands run live against this machine's
real LUKS(`cryptroot`)+btrfs(`@`)+`/swap/swapfile` layout (RAM 31G, fsuuid,
`findmnt SOURCE`, `mkswapfile` flags, zram@100 vs swapfile@-1 confirms the
design intent). `nix flake check --no-build` green.
- **Pending:** slice 2 — Hibernate row notify-on-failure (V1/V2); then the
V2 hibernate VM test + V3 laptop.
- **Next suggestion:** slice 2 (menu notify-on-failure).
## 2026-07-10 — #76 hibernation design calls (no commit yet → this bookkeeping)
- **Task:** BACKLOG #76 design slice — Bernardo made the open calls.
- **Did:** Recon showed the hibernation half is **already built for new
installs** (installer defaults swap=RAM hibernation-ready `@swap` swapfile;
`patch-template.py` writes swapDevices/resumeDevice/resume_offset; Power
menu Hibernate row + LUKS/hyprlock handling exist). Settled 3 calls:
(1) swap sizing = **exactly RAM** (no change); (2) migration = **docs
runbook in MIGRATION.md**, not a tool; (3) no-swap Hibernate = **keep row,
notify on failure**. Folded into BACKLOG #76 (design ✓, re-sliced remaining:
MIGRATION.md runbook + notify-on-failure menu) and Decisions.
- **Verified:** **V0**`nix flake check --no-build` green (docs-only).
- **Pending:** two remaining slices below; V2 hibernate VM test + V3 laptop.
- **Next suggestion:** slice 1 — `docs/MIGRATION.md` hibernation-enable runbook
(V0), then slice 2 — Hibernate notify-on-failure (V1/V2).
## 2026-07-10 — #76 zram slice: compressed-RAM swap default
- **Task:** BACKLOG #76 (`[big]`) — split; took the low-risk zram-default slice.
- **Did:** `zramSwap` on by default in `modules/nixos/oom.nix` (zstd, 50% RAM,
priority 100) as the memory-pressure layer before earlyoom, following the
earlyoom mkDefault precedent (no toggle). High priority keeps day-to-day
paging in zram so a disk swapfile stays reserved for #76's hibernate image.
Added `checks.zram-swap` runNixOSTest (imports oom.nix). Split #76 in
BACKLOG → remainder is the hibernation half (swapfile/resume/installer).
- **Verified:** **V2**`nix build .#checks…zram-swap` PASS: /dev/zram0 active
in /proc/swaps, `[zstd]` comp_algorithm, priority 100. **V1** full distro
toplevel builds. **V0** flake check --no-build green.
- **Pending:** hibernation half of #76 still open (needs hibernate→resume VM
test + V3 laptop). No V3 for zram (under-load tuning is secondary).
- **Next suggestion:** #76 hibernation half — design default swapSize + the
resume/installer wiring; it's the boot-critical, V2-hibernate + V3 part.
## 2026-07-10 — #41 hardware-confirmed: OpenFile portal floats (V3 close)
- **Task:** BACKLOG #41 — final on-hardware confirmation (Bernardo reported PASS).
- **Did:** #41 already out of NEXT + portal rule shipped
(`hyprland.nix` xdg-desktop-portal-gtk float/center). Bernardo verified the
GTK OpenFile file-picker floats on real hardware; dropped the "OpenFile not
re-probed / optional residual" caveats in HARDWARE-QUEUE — #41 fully closed.
- **Verified:** **V3** (Bernardo, Latitude). No code change. Bookkeeping only;
QA-sweep guards run green this session: `nix flake check --no-build`,
option-docs/template-sot/theme-sync-validate builds, contrast 24×7.
- **Pending:** none for #41. NEXT head is #76 (hibernation + zram, `[big]`).
- **Next suggestion:** split #76 — take the low-risk **zram-default** slice
(V2-testable headlessly) before the boot-critical hibernate/resume work.
## 2026-07-10 — Close #41 GTK portal float rule
- **Task:** BACKLOG #41 residual (GTK file-chooser portal class).
- **Did:** Added float+center for `xdg-desktop-portal-gtk` /
`.xdg-desktop-portal-gtk-wrapped` (desktop + libexec name from package).
Polkit already PASS on Latitude. Removed #41 from NEXT; only #20 remains.
- **Verified:** **V0** windowrule-syntax check path unchanged; class from
package `xdg-desktop-portal-gtk.desktop` / community configs. SoftGL
still cannot materialize portal dialogs.
- **Pending:** optional hardware spot-check OpenFile floats (HARDWARE-QUEUE).
- **Next suggestion:** #20 is `[human]` only — or LATER/PROPOSED triage.
## 2026-07-10 — Product calls: charge live, identity, btop, NVIDIA skip
- **Task:** Bernardo decisions on remaining PROPOSED.
- **Did:** (1) Instant charge-limit: udev GROUP=users on threshold,
menu/powermgmt live sysfs + state, oneshot reads live theme-state +
AC re-apply. (2) Look & Feel already had Theme/wallpaper/night light;
added Reset wallpaper (auto). (3) NVIDIA first-class deferred past v1
(HARDWARE.md). (4) Identity retunes: white L-steps, lumon subtext,
hackerman surface+UI warn/bad, matte-black subtext, miasma bad.
(5) Hand `btop.theme` for 9 identity/whole-swap slugs from palette map.
- **Verified:** **V0** contrast 24×7; **V1** battery-charge-limit VM
(udev restart). **V3** live charge write on real laptop (HARDWARE-QUEUE).
- **Next suggestion:** only #20/#41 blocked, or Decisions.
## 2026-07-10 — Summer pair polish + status CSS + preview recapture
- **Task:** finish agent-able PROPOSED theme leftovers.
- **Did:** summer-day/night — JSON hierarchy (night subtext/muted/overlay
ladder; day overlay≠muted); waybar CSS tokens track JSON roles; font
13px both; day battery 25/10; VPN pill + full status states
(.recording/.available/.on/.activated/battery warn). Recaptured
executive-slate + neon-glass `preview.png` via theme-shot desktop
(480×270). Closed those PROPOSED lines.
- **Verified:** **V0** contrast 24×7 + wholeswap; **V2** theme-shot
summer-day/night/executive-slate/neon-glass — viewed bar crops +
previews. Identity taste retunes stay PROPOSED.
- **Next suggestion:** only blocked NEXT (#20/#41) or product Decisions.
## 2026-07-10 — #74 matrix flags + #75 V2 install (default arm)
- **Task:** NEXT #74 unattended matrix · #75 SoT V2 install after ISO.
- **Did:** `NOMARCHY_TEST_SWAP_GB` / `NOMARCHY_TEST_NO_LUKS` drive
`tools/test-install.sh` go.sh + first-boot LUKS skip; nixpkgs OVMF
fallback. Full offline install V2 (SWAP=2 LUKS) after live ISO rebuild
with starter packages — first-boot themed Boreal, no red banner.
Closed #74/#75. Other matrix arms scripted (not full-ISO re-run).
- **Verified:** **V2** — install powered off ~460s; first-boot.png
viewed (`/tmp/nomarchy-vm-75/first-boot.png`): themed wallpaper +
waybar (logo, clock, modules), doctor glyph, no autogen banner.
- **Next suggestion:** NEXT only #20 [human] + #41 [blocked:hw].
## 2026-07-10 — Loop batch land #64#73 (parallel worktrees)
- **Task:** agent-actionable NEXT #64#73 via worktree agents; land on main.
- **Did:** #64 MIGRATION snapshots · #65 real-uid chown · #66 theme-state
fail-closed reader · #67/#68 theme fidelity+ANSI · #69/#70 audit+import
tools · #71 IR portal docs · #72 template-sot check · #73 MOTD/first-boot
fingerprint+doctor tips. Agents did not push; parent cherry-picked
(stripped agent BACKLOG races). Closed #64#73.
- **Verified:** **V0/V1** — theme-contrast 24×7 pass; template-sot green;
bash -n install; flake check --no-build green.
- **Next suggestion:** #74 unattended matrix + #75 V2 install (KVM).
## 2026-07-10 — Promote agent-actionable PROPOSED → NEXT #64#75
- **Task:** Bernardo: bump non-hw/non-human PROPOSED categories 1+2.
- **Did:** **#64** MIGRATION snapshots · **#65** installer chown ·
**#66** mkFlake theme-state errors · **#67** theme fidelity nits ·
**#68** #52 residual ANSI · **#69** audit identity exemptions ·
**#70** import-pipeline hierarchy · **#71** IR portal docs (a) ·
**#72** template SoT CI · **#73** hardware hints residual · **#74**
unattended install matrix · **#75** SoT V2 install. Breadcrumbs in
PROPOSED; actionable items sit above #20/#41 in NEXT.
- **Verified:** V0 (backlog only).
- **Next suggestion:** queue head **#64** (docs) or batch #67/#68 themes.
## 2026-07-10 — PROPOSED cleanup (stale exam residue)
- **Task:** Bernardo: clean stale PROPOSED items after A/B/C + #52 ship.
- **Did:** Rewrote PROPOSED to open work only — dropped #52 shipped
high-ROI bullets, neon-glass quarantine, empty/shipped installer
audit sections, template seed drift (keys already present), soft
gaps fixed by #50, and “no action” kanagawa/boreal notes. Kept
product day-2, installer follow-ups, theme polish residuals, tooling.
- **Verified:** V0 (backlog hygiene); spot-checked template keys,
latte/mocha #52 fixes, usbutils, package exports.
- **Next suggestion:** triage the lean PROPOSED list or HARDWARE-QUEUE.
## 2026-07-10 — Pending closeout: #14, #57 V2, #41 slice
- **Task:** pending V2/CI/float leftovers after A/B/C batch.
- **Did:** **#14 closed** — first scheduled lock bump landed
`8fded63` (2026-07-06 Mon 05:20 UTC) as `nomarchy-bump` /
`chore(lock): scheduled upstream bump` (Actions token push works).
**#57 V2** — `checks.live-install-entry` asserts live HM gen has
`nomarchy-install.desktop` (Name/Terminal/Exec) + installer bin +
Tools menu gate in `nomarchy-menu`. **#41 slice** — float/center rules
for hyprpolkitagent + pinentry-qt from package app-id strings;
`tools/capture-float-classes.nix` harness (softGL empty clients);
residual GTK portal stays NEXT `#41 [blocked:hw]`. HARDWARE-QUEUE
float confirm steps added.
- **Verified:** **V2** live-install-entry green; windowrule-syntax green.
SoftGL class capture inconclusive (empty clients). **V3** float confirm
+ fingerprint/#60 still HARDWARE-QUEUE. **#20** still `[human]`.
- **Next suggestion:** Bernardo: #20 KVM runner, or HARDWARE-QUEUE pass.
## 2026-07-10 — #55 fingerprint menu + #56 human rebuild errors
- **Task:** NEXT #55 (System Fingerprint) + #56 (rebuild failure UX).
- **Did:** #55 — System row self-gated on fprintd-list; enroll/list/verify/
delete-all terminal flows; Use for login writes
`settings.fingerprint.pam` (hardware.nix default from stateFile);
HARDWARE.md §5 + HARDWARE-QUEUE V3 enroll. #56 — sys-update /
sys-rebuild / home-update tee last 40 lines + nomarchy-doctor pointer;
theme-sync run_switch notify/die text. Closed #55/#56.
- **Verified:** **V1** — HM template rebuild (menu + theme-sync);
option-docs green; failure-footer shell smoke. **V3 pending:** real
fingerprint enroll (queue). **V2 pending:** forced bad sys-rebuild in
VM (footer text pure/logic covered).
- **Next suggestion:** queue was AC; remaining NEXT is blocked items
(#14/#20/#41) or PROPOSED triage.
## 2026-07-10 — #58 nomarchy-detect-hw CLI
- **Task:** NEXT #58 — post-install re-probe package (HARDWARE.md §8).
- **Did:** `pkgs/nomarchy-detect-hw` wrapping installers hardware-db;
human report + snippets + `--raw` protocol; overlay + systemPackages;
HARDWARE.md §8 updated. Closed #58.
- **Verified:** **V1** — package builds; ran on this AMD laptop: correct
MODULE/NOMARCHY lines (amd cpu/gpu, laptop, ssd, fingerprint, IR cam,
NPU) and printable system.nix snippets.
- **Next suggestion:** #55 fingerprint menu or #56 human rebuild errors.
## 2026-07-10 — #57 live Install Nomarchy affordance
- **Task:** NEXT #57 — durable live-ISO install surface (not only toast).
- **Did:** `xdg.desktopEntries.nomarchy-install` (Terminal=true) on live
HM; Tools Install Nomarchy self-gated on `command -v nomarchy-install`;
welcome toast wording updated. Closed #57.
- **Verified:** **V1** — live config eval exposes entry name/exec;
`downstream-template-home` rebuilds with menu gate. **V2 pending:**
ISO rebuild + smoke that the .desktop appears in the session.
- **Next suggestion:** #58 `nomarchy-detect-hw` CLI.
## 2026-07-10 — #63 generated Waybar logo + powermenu
- **Task:** NEXT #63 — reverse parity: generated bar lacked whole-swap
logo → menu and power icon → power menu.
- **Did:** `custom/nomarchy` (left, Nomarchy.ttf U+F000) + `custom/powermenu`
(right, U+F011 → `nomarchy-menu power`) in generated settings/style.
Fixed Nix `\uf000` pitfall (no `\u` escapes — use literal UTF-8).
Whole-swaps already had both; no JSON/CSS edits. Closed #63.
- **Verified:** **V2**`THEME=tokyo-night` theme-shot: left monogram N,
right power glyph, accent styling; config modules-left/right correct.
Crop paths under `/tmp/theme-shot-tn2-*.png`.
- **Next suggestion:** product path #55 fingerprint menu (or #57 live install).

161
agent/LOOP.md Normal file
View File

@@ -0,0 +1,161 @@
# 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
harness- and vendor-agnostic; the same iteration works under any of:
- **An interactive self-paced loop** in any agent harness (e.g. `/loop`
in Claude Code) — the agent iterates until stopped.
- **Headless** (a one-shot CLI invocation, 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) |
Instructions live next to the state: `VERIFICATION.md` (enforcement),
`DELEGATION.md` (tiers/roles/economy), `GOALS.md`, `THEME-DESIGN.md`.
## Model & token economy
Spend expensive tokens on judgment, not mechanics. Tiers, roles, and the
full delegation rules are in **`DELEGATION.md`**; the loop-specific
habits:
- **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
frontier-tier model running the loop.
- **Delegate mechanical subtasks down.** 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
light/standard-tier subagent per `DELEGATION.md`. The strong model
writes the spec, reviews the result, and owns the commit.
- **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`. If the top task is product-shaped
(UX, release bar, day-2 confidence), also read the matching section of
**`docs/VISION.md`** — do not invent work from VISION; only execute
BACKLOG items. Map of docs vs agent state: `docs/README.md`,
`agent/README.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`.
4. **Sync sweep.** Grep the item's number and feature name across
`agent/` and `docs/` and update or delete every cross-reference the
ship made stale: the PROPOSED pitch that spawned it (and BACKLOG's
v1.0 pointer), HARDWARE-QUEUE entries it supersedes, ROADMAP/README/
docs mentions of the old behavior. While there, prune checked-off
`[x]` HARDWARE-QUEUE entries whose outcome is already recorded
(journal/ROADMAP — git history is the archive). A shipped item must
leave no stale pointer behind; the sweep rides in the same commit.
### 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.

170
agent/MEMORY.md Normal file
View File

@@ -0,0 +1,170 @@
# 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
- **theme-shot softGL cannot start Ghostty** — `btop.png` is best-effort
(usually identical to desktop). Guest asserts on
`~/.config/btop/themes/nomarchy.theme` prove baking; the TUI look is
hardware/GL tier (HARDWARE-QUEUE).
- **Waybar `custom/doctor` tripwire:** status helper must invoke
`nomarchy-doctor` by **absolute store path** (waybar's env can miss
system PATH → `command -v … || exit 0` self-hides forever); empty
`"text"` also self-hides; strip ANSI before packing the tooltip; use
signal 10 + `format = "{}"`. theme-shot asserts class:bad + glyph and
pokes RTMIN+10 before the desktop shot.
- **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.
- **Don't default a "timer/session" feature to V3 — most of it is VM-testable.**
A scheduled/session behaviour usually decomposes into a *generic* step
already covered elsewhere (e.g. `home-manager switch`, exercised by every
theme apply) and a *specific* decision (which theme, when). Stub the generic
step (`NOMARCHY_REBUILD=<marker>` for theme-sync) and **simulate time by
moving the VM clock** (`date -s`, `timedatectl set-ntp false`, `time.timeZone
= "UTC"`), then assert the decision + state change headlessly. `checks.auto-theme`
does exactly this for #79's sunset/sunrise. Only the literal
timer-fires-on-`OnCalendar` is truly on-hardware, and `systemd-analyze
calendar` validates that schedule. (I first mis-framed #79 as V3 — it's V2.)
- **`writeShellScriptBin` scripts run `set -euo pipefail`** (nomarchy-doctor,
the menu, lifecycle CLIs). So a **no-match `grep` inside `$(…)`** (grep exits
1 → command-sub fails → abort) and a **standalone `cond && action`** (false
cond → abort) both kill the script mid-run — the tell is output that stops
before the final/verdict line with no error. Guard: `… | grep … || true`
inside `$()`, `cmd 2>/dev/null || echo 0` for captures, and `if` instead of
`&& action`. (#77 doctor hibernate section; caught by the checks.doctor VM
test on first run.)
- 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).
- **Hibernation reference (Latitude / Newton, BACKLOG #76):** LUKS whole
root BTRFS; `@swap``/swap`; file `/swap/swapfile`;
`boot.resumeDevice` = LUKS root UUID; `resume_offset` from
`btrfs inspect-internal map-swapfile -r`. Swap is encrypted with root
(not a cleartext partition). No zram on that box yet — zram is additive
for live pressure only. Installer already creates this when swapSize>0.
- CI (`.gitea/workflows/check.yml`) is **eval-tier only** (standing
decision 2026-07-10): act_runner docker-compose on the Gitea VPS; no
KVM there. Full VM suite is BACKLOG **FUTURE #20**, not NEXT — needs a
separate nix+/dev/kvm runner. Container gotchas are 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).
- 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).
- **Friendly theme-state load** (`modules/theme-state-read.nix`, #66):
`builtins.tryEval` does **not** catch `readFile`/`fromJSON` failures —
gate with `pathExists` + empty/non-object checks before `fromJSON`.
Subtle JSON syntax errors still surface from nlohmann (line/col);
field schema stays in `theme.nix`. mkFlake must `builtins.seq` the
check onto the whole return set or lazy attr access skips it.
## Design invariants
- **Waybar status is never color-only** (item 28 sweep, iteration #69):
every status module must distinguish its states by SHAPE (glyph) or
presence (self-hide), never color alone — good/warn/bad collapse under
color-blindness. When adding a state, give it a distinct glyph or gate
the module on it; a new `class` that only recolors an existing glyph is
a regression. Suppressed notification states (DND *and* app-inhibited)
all use the bell-off glyph + @muted.
- **Identity themes are not traffic lights** (#69): white, vantablack,
lumon, hackerman, matte-black, miasma — monochrome / mono-hue / earthy
status by design. `audit-theme-design.py` tags their hue/CVD/ANSI-family
findings `[identity]`; do not "fix" them into R/Y/G.
- **Import hierarchy ≠ ANSI** (#70): `import-palettes.py` must not set
surface==overlay when color0==color8; light color0 is often ANSI black
(not a chip). Roles are first-class — never bulk-reimport shipped JSON
without a hierarchy pass.
## 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: `nomarchy-pull` (lock) then
`nomarchy-rebuild` then `nomarchy-home`, or desktop changes are silently
skipped against the old
lock (README § 3).
- Hyprland 0.55 renames `stayfocused``stay_focused` (and similar
underscore effects); `stayfocused 1` is `invalid field type` at parse
(§ polkit workspace rules, 2026-07-10 hardware).
- Hyprland 0.53 rewrote window rules: `windowrulev2` is a hard error and
the old rule-first `float, class:^…$` no longer parses — both surface a
red config-error banner on the default desktop. Hyprlang legacy form is
now `<effect> <value>, match:<prop> ^…$` (e.g. `float 1, match:class ^…$`);
effects carry a value, matchers take `match:` (§ windowrule migration).
- grub `loadfont`s every `.pf2` in a theme dir — reuse a bundled DejaVu
rather than shipping fonts (§ Distro branding).
- Agent instructions live vendor-neutrally in `agent/` (VERIFICATION,
DELEGATION, THEME-DESIGN; entry AGENTS.md) — `.claude/` is a thin
adapter (permissions + subagent defs only; skills were removed
2026-07-11). Never `git add -A` blindly: check `git status --short`
for genuine strangers first (`settings.local.json`, harness-dropped
files) and commit with explicit pathspecs (§ loop hygiene).

54
agent/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Agent instructions + loop state
Everything an AI agent needs to work on Nomarchy, vendor-neutral and
git-tracked. Protocol: **[LOOP.md](LOOP.md)**. Entry point for every
harness: repo-root **[AGENTS.md](../AGENTS.md)**.
## Instructions (how to work)
| File | Who writes | Role |
|------|------------|------|
| [LOOP.md](LOOP.md) | Human | One-iteration protocol (orient → pick → work → verify → commit → record) + the V0V3 ladder |
| [VERIFICATION.md](VERIFICATION.md) | Human (agents propose) | Enforcement: preflight, honesty rules, visual protocol, hardware-blocked checks, reporting |
| [DELEGATION.md](DELEGATION.md) | Human (agents propose) | Capability tiers, scout/runner roles, token economy, parallel fan-out |
| [GOALS.md](GOALS.md) | Human (agents propose) | Pillars, quality bars, non-goals |
| [CONVENTIONS.md](CONVENTIONS.md) | Human (agents propose) | How to write code/menu/state while shipping |
| [THEME-DESIGN.md](THEME-DESIGN.md) | Human (agents propose) | Theme/visual design instructions |
## State (what's happening)
| File | Who writes | Role |
|------|------------|------|
| [BACKLOG.md](BACKLOG.md) | Both | **Prioritized queue** — only executable work list |
| [JOURNAL.md](JOURNAL.md) | Agents | Append-only iteration log (read last 35 entries; older → [JOURNAL-ARCHIVE.md](JOURNAL-ARCHIVE.md)) |
| [MEMORY.md](MEMORY.md) | Agents | Curated durable gotchas |
| [HARDWARE-QUEUE.md](HARDWARE-QUEUE.md) | Agents append, human checks | On-hardware V3 tests only Bernardo can run |
## Product / design docs (not a queue)
| File | Role |
|------|------|
| [../docs/VISION.md](../docs/VISION.md) | v1.0 product themes — agents slice into BACKLOG PROPOSED |
| [../docs/ROADMAP.md](../docs/ROADMAP.md) | Design history + shipped log |
| [../docs/README.md](../docs/README.md) | Full docs map |
## Harness adapters (vendor-specific, thin)
Shared content never lives in an adapter — adapters only register/route
into the files above, in whatever format their harness requires.
| Path | Harness | Role |
|------|---------|------|
| [../AGENTS.md](../AGENTS.md) | any | Entry point (`CLAUDE.md` is a symlink to it) |
| [../.claude/settings.json](../.claude/settings.json) | Claude Code | Tool permissions |
| [../.claude/agents/](../.claude/agents/) | Claude Code | `nomarchy-scout` / `nomarchy-runner` role defs (contracts in [DELEGATION.md](DELEGATION.md)) |
Do **not** put backlog items, vision text, or policy under an adapter
directory — it is not shared with other agent runners.
## Rules of thumb
1. **Execute** from BACKLOG only (NOW → NEXT; never PROPOSED without human triage).
2. **Orient** with GOALS + CONVENTIONS + MEMORY + last journal + BACKLOG; when the task is product-shaped, also read the relevant **VISION §**.
3. **Record** lasting design in ROADMAP ✓ when something ships that future humans should know; delete the BACKLOG line.
4. **v1 branch** is human-only — never advance from an agent session.

49
agent/THEME-DESIGN.md Normal file
View File

@@ -0,0 +1,49 @@
# Theme design — instructions for visual work
For creating, updating, refining, or troubleshooting Nomarchy themes and
visual design. Act as an experienced UI/UX designer and Linux ricing
expert for Wayland environments: themes here must be cohesive,
intentional, and visually striking — aesthetics are a load-bearing
feature of this distro (`agent/GOALS.md` pillar 4).
## Context & architecture
All theme data lives in `themes/` at the repo root — one JSON palette per
theme plus per-app assets (whole-swap `waybar.jsonc`/CSS, btop themes,
wallpapers). Before generating any new configuration, read the existing
files there: learn how current themes are structured, how the syntax is
formatted for each application, and how they integrate into the NixOS/HM
modules. Always match the established pattern — there is no second
theming pipeline (`agent/GOALS.md` non-goals), so a new visual surface
consumes the palette from the state JSON, never a side file.
## Design responsibilities
1. **Holistic design:** a theme spans Hyprland (borders, shadows,
animations), Waybar, Ghostty, btop, fastfetch, rofi, and wallpaper —
one coherent identity, no unthemed corner.
2. **Color theory:** create or adapt advanced palettes. Draw inspiration
from established aesthetics (Everforest, Nord, Gruvbox) or r/unixporn
trends, but innovate. Ensure harmony between background, foreground,
accents, and warning/error colors.
3. **Typography & iconography:** pair UI and monospace fonts and icon
themes to the specific vibe of the palette.
4. **Accessibility:** high contrast for text readability; status
information is never color-only (see `agent/MEMORY.md` design
invariants — glyph/shape carries state, and identity themes are
deliberately not traffic-lights).
## Process
1. Read `themes/` to understand the current structure.
2. State the "vibe", the primary palette (hex codes), and typography
choices of the proposed design before implementing.
3. Implement by generating or updating files within the `themes/`
structure, following `agent/CONVENTIONS.md` (Waybar parity rule:
whole-swap `waybar.jsonc` files stay in sync with the generated
config).
4. Verify per `agent/VERIFICATION.md` §3 — scripted checks first
(`tools/check-theme-contrast.py`, `tools/audit-theme-design.py`), then
headless before/after screenshots you actually view, under at least
two themes. New themes are imported via `tools/import-palettes.py` and
round-tripped through the theme switcher.

178
agent/VERIFICATION.md Normal file
View File

@@ -0,0 +1,178 @@
# Verification — the enforcement rules
Nomarchy's promise to its user: a rock-stable, fully functional, beautiful
workstation that is reproducible, easy to recover, and never requires the
user to become a Nix expert. Every rule below exists to protect that
promise. A change that works but degrades stability, aesthetics, or
user-simplicity is a regression, not a feature.
This document is an **enforcer**, not the workflow itself: the ladder and
iteration protocol live in `agent/LOOP.md`, VM instructions and gotchas in
`docs/TESTING.md`. It applies to **every** change to this repo — features,
fixes, theming, module changes, lock bumps, docs, backlog grooming.
"Small" or "obvious" changes are exactly where verification gets skipped,
so they trigger it too. If this file and those docs ever disagree, fix the
discrepancy in the same or a follow-up commit so they can't disagree
twice.
## 1. Preflight (once per session)
Before starting work, establish what verification tier this environment
can reach, so you never promise verification you can't deliver:
1. Linux x86_64 host? `/dev/kvm` present and readable?
2. Enough free disk for an image/ISO build (multi-GB)?
3. Network access for a cold Nix store?
If the environment cannot reach V2 (no KVM, no disk, etc.): say so
immediately, do the V0/V1 work honestly, mark the change **"V2 pending"**
exactly as you would mark a hardware-blocked change "V3 pending" (§4),
and stop short of claiming the change is done. Never simulate, guess, or
describe what a VM test "would" show.
## 2. The verification ladder (enforcement rules)
Climb the V0V3 ladder as defined in `agent/LOOP.md`. Four non-negotiable
enforcement rules on top:
1. **V2 is mandatory for anything user-visible.** If a user of the
installed system could perceive the change — behavior, layout, colors,
keybinds, timing, error messages — it must be exercised in the local
VM before commit. Docs-only, comment-only, or agent-notes changes may
stop at the tier LOOP.md assigns them; user-visible changes may not.
2. **Every "done" report names the tier reached and shows the evidence.**
Evidence means: the command run and its relevant output, the checklist
items exercised, and for visual work the screenshots viewed (§3). "It
builds" is a V1 claim, not a V2 claim. Never let a report imply a
higher tier than was actually reached.
3. **A failed or flaky test is a result, not an obstacle.** Distinguish
real failures from environment flakes using the known-gotchas section
of `docs/TESTING.md` (e.g. no-KVM slowness, missing guest GL). If you
cannot confidently classify a failure, report it as unresolved — do
not retry until green and report only the green run.
4. **VM runs are headless and unattended.** Use the repo's headless
harness — `tools/test-live-iso.sh` and `tools/test-install.sh` for
boot/install runs, `tools/vm/qmp.py` for programmatic VM control and
`tools/vm/vncshot.py` for screen capture — never a graphical VM window
or any flow that needs a human at the console. The human is not part
of the test loop: do not pause mid-run to ask them to look at the VM,
click something, or confirm what is on screen. A run must complete on
its own and leave auditable artifacts behind (logs, serial console
output, exit codes, screenshots), with every wait bounded by a timeout
so a hang becomes a recorded failure instead of a stalled session.
Prefer scripted assertions (process up, file exists, service/D-Bus
state, the checks in `tools/`) over eyeballing; where judgment is
genuinely needed — visual quality — *you* view the captured
screenshots (§3), not the human. The human reviews evidence in the
final report, never the live run.
### Regression scope after a change
Re-running the full checklist for every change wastes VM time; running
nothing invites regressions. Default rule:
- Always: the session-sanity items (boot to session, bar renders).
- Plus: every checklist item touching the layer you changed.
- Plus: the theming end-to-end item whenever theming plumbing changed,
even indirectly (palette generation, symlinks, reload hooks).
- Lock bumps and toolchain changes: run the full checklist — their blast
radius is unknowable by construction.
## 3. Visual verification protocol
Visual quality is a core feature of Nomarchy, so "it probably looks fine"
is never verification. A visual/UI change is not V2-verified until all of
the following are true:
1. **Before/after screenshots** of the changed surface were captured
headlessly — `tools/theme-shot.nix` for reproducible theme renders,
`tools/vm/vncshot.py` (driven via `tools/vm/qmp.py`) for captures from
a running VM. No VM window, no human interaction. Capture the "before"
from the base branch or prior generation, not from memory.
2. **Scripted checks first**: run `tools/check-theme-contrast.py` and
`tools/audit-theme-design.py` against the affected theme(s) before any
eyeballing — machine-checkable legibility/design violations should
never survive to the judgment stage. Use `tools/vm/gap-analysis.py`
where it applies.
3. **Two themes**: repeat the "after" capture under at least two themes,
one with a generated palette and one whole-swap theme (e.g.
summer-night). These exercise different code paths in the bar/launcher
theming; a change that looks right under one can silently break the
other.
4. **You actually viewed the images** — open the screenshot files and
look at them. State concretely what you inspected: alignment, spacing,
contrast/legibility against the palette, icon rendering, no clipped or
overlapping elements, and that the change looks intentional next to
the "before".
5. Keep the screenshots in the run's working area and reference their
paths in the report, so the human can audit the same evidence.
If the VM cannot render the surface faithfully (known GL/compositor gaps
in the guest — see `docs/TESTING.md`), that specific visual aspect is
hardware-blocked: verify everything the VM *can* show, and queue the rest
per §4.
## 4. Hardware-blocked checks
Some checks genuinely require real hardware (GPU behavior, multi-monitor
hotplug, audio devices, power/suspend, firmware). For those:
1. Add an entry to `agent/HARDWARE-QUEUE.md` with: what changed, **exact**
reproduction steps a human can follow verbatim, the expected
observation (what "pass" looks like), and the commit hash once known.
2. Mark the commit body **"V3 pending: <one-line summary>"**.
3. Say it plainly in your report. A hardware-blocked check is not a
failure and not something to hide — hiding it is the failure.
4. When the human reports back, close the queue entry in the next commit
and record the outcome; if it failed on hardware, that's a new bug at
the top of the backlog.
Do not use the hardware queue as an escape hatch: if a check *can* be
done in the VM, it must be. "The VM is slow" does not qualify.
## 5. Maintenance work
Maintenance follows the same ladder:
- **Flake lock bumps**: treat as maximum-blast-radius changes. Build,
boot the VM, run the full regression checklist, and do a visual
spot-check of the session (themes can shift with upstream package
changes). Never merge a lock bump on "it evaluates".
- **Theme imports / new themes**: import via `tools/import-palettes.py`,
then the full §3 visual protocol; additionally verify the theme-switch
round trip (into the new theme and back out).
- **Docs drift**: run `tools/check-option-docs.py` after any change that
adds or modifies options, and fix drift in the same commit as the code
change that created it. Doc-only fixes are V0 — but verify any command
you document by actually running it.
- **Backlog grooming / agent-notes**: V0; keep entries consistent with
the conventions in `agent/`.
## 6. Guarding the philosophy
Before committing, check the change against the distro's promises
(`agent/GOALS.md` is the full statement):
- **User is not a Nix expert.** If the change requires the user to write
or read Nix to use the feature day-to-day, redesign it. Configuration
the user touches must stay in the simple, documented surface the repo
defines.
- **Rock-stable and recoverable.** Prefer boring, reproducible mechanisms
over clever ones. Any change that could break boot or the session must
have an obvious rollback story (NixOS generations count, but say so).
- **Aesthetics are load-bearing.** A functionally correct but visually
regressive change fails review by definition — that's what §3 is for.
When a requested change conflicts with these promises, stop and raise the
conflict instead of implementing it quietly.
## 7. Reporting format
End every unit of work with a short report containing:
1. What changed (one paragraph, plain language).
2. Verification tier reached, with evidence (commands + key output,
checklist items run, screenshot paths viewed).
3. Anything pending: "V2 pending" (environment) or "V3 pending"
(hardware, with queue entry reference).
4. Follow-ups added to the backlog, if any.

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> : "—"

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