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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
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.
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.
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.
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.
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.
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.
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.
#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).
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.
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.
Installer: disko @snapshots → /.snapshots + first-boot nested
/home/.snapshots. Migration machines may use top-level
@home-snapshots (TuringMachine); either works for snapper.
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).
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).
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).
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.
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.
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).
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).
#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.
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.
#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>
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>
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>
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>
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>
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>
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>
#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>
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>
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>
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>
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>
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>
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>
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).
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).
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.
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.
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.
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.
- 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.
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).
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.
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
Leveraged Nano Banana to generate a 4k functional minimalist wallpaper. Added the new preset with a solid, high-legibility waybar and flat rofi launcher.
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.
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.
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>
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>
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>
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>
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>
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>
The gate was >=3 appearance entries and it's met: the root's Theme row
becomes "Look & Feel" (pango-escaped & — 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
.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>
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>
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>
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>
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>
'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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
? 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>
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>
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>
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>
- 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>
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>
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>
- 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>
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>
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>
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>
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>
.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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
`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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
`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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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
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
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.
**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/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`.
### 2. Build the Installer ISO
To install on physical hardware, generate your own bootable image:
| `username` | — (required) | Login name; flows into `system.nix` and names the HM config |
| `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.)
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
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:
```bash
sys-update # Rebuilds the NixOS system (Runs: sudo nixos-rebuild switch --flake .#default --impure)
env-update # Reloads your Home Manager environment (Runs: home-manager switch --flake .#default --impure)
*Built with ❤️ using NixOS, Hyprland, Stylix, and the Nomarchy Community.*
**Where each option goes.**`nomarchy.system.*`, `nomarchy.hardware.*`, and
`nomarchy.services.*` are NixOS options — set them in `system.nix`. Everything
else under `nomarchy.*` is a Home Manager option — set it in `home.nix`. The
two tables below are split along exactly that line.
**`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.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.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.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.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.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.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`) |
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 3–5 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 3–5 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
| [../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.
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)
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:
- "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`
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 = tag -default-opacity, class:^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.