The default theme's clock#date wired on-click to `kitty calcurse`, but
kitty isn't a default-installed app (alacritty is the terminal) and
calcurse isn't installed anywhere — so clicking the date did nothing.
The default config.jsonc uses xdg-terminal-exec/alacritty for its
clicks and summer-day's date has no on-click. Drop the dead action
(tooltip: false), matching summer-day.
Found during the Pillar 9 Component 6 (apps) pass, which confirmed
btop/ghostty/kitty/lazygit/tmux ship config-only by design (the
installer/user picks the binary) — not a bug. Logged two follow-ups:
summer-day's wofi --term=kitty launcher, and shipping a calendar app.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The summer-night waybar config defined a custom/battery module
(exec nomarchy-battery-status) but never listed it in any modules-*
slot, so it was orphaned and never rendered. summer-night is the
default theme, so a laptop on a stock install had no battery
indicator — the default fallback config and the summer-day theme
both place battery in modules-right; only summer-night omitted it.
Add custom/battery to summer-night's modules-right. The formFactor
filter (laptopOnlyModules) already lists custom/battery, so it shows
on laptop and is dropped on desktop. Found during the Pillar 9
Component 9 (form-factor split) verification pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found by driving the menu's observable actions live in a VM with the real
session env imported. All three are also bound to keybindings.
1. nomarchy-hyprland-window-single-square-aspect-toggle (1-Window Ratio,
SUPER CTRL BACKSPACE): read/set layout:single_window_aspect_ratio, which
is "no such option" in Hyprland 0.52.1 — the option lives under dwindle:.
The getoption returned nothing and the keyword set was silently dropped,
so the toggle did nothing. Point it at dwindle:single_window_aspect_ratio.
2. nomarchy-hyprland-workspace-layout-toggle (Workspace Layout, SUPER L):
read .tiledLayout off `hyprctl activeworkspace -j` (no such key → always
null) and switched to "scrolling" (not a built-in layout — hyprctl
layouts lists only dwindle/master), so it silently no-op'd. Rewrite to
toggle the real general:layout between dwindle and master.
3. show_setup_menu offered Power Profile unconditionally, but powerprofilesctl
is absent everywhere (power-profiles-daemon is force-off on laptop in
favour of TLP and never enabled on desktop), so the entry died with
"command not found". Gate it behind `command -v powerprofilesctl`.
Verified (1) and (2) against live Hyprland in the VM: aspect flips
[0,0]<->[1,1], layout flips dwindle<->master.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All three confirmed real on inspection.
1. nomarchy-refresh-config was dead on every system: it read
/etc/nixos/nomarchy/{core/home/config,features} (the source tree never
lands there — only /etc/nomarchy on VM-guest/live ISO) and fell back to
~/.local/share/nomarchy/config, which nothing deployed despite SKILL.md
documenting it. Deploy the pristine core/home/config tree to
~/.local/share/nomarchy/config via xdg.dataFile and read from there, so it
works without a source checkout; its live caller nomarchy-refresh-fastfetch
now succeeds. Fix two stale SKILL.md examples (waybar/, hypr/) that pointed
at non-existent stock paths.
2. nomarchy-docs-scripts shipped a stale divergent copy in
features/scripts/utils/ (in the user nomarchy-system-scripts package)
beside the canonical bin/utils dev tool — it errored outside the repo.
Grounding it surfaced the identical bug in nomarchy-docs-keybindings.
Delete both duplicates, add them to the generator's self-reference
denylist so they don't show as false `missing`, regenerate docs/SCRIPTS.md.
3. Home ~/.config/nomarchy/state.json was never seeded on non-installer
systems, so nomarchy-installed-summary rendered "—" for theme/font/panel.
Add an idempotent home-manager activation seed in core/home/state.nix that
backfills the resolved values (defaults * existing — user choices and
runtime-only keys like welcome_done always win).
Verified: flake check + full eval matrix clean; refresh-config happy/error
paths and the seed deep-merge proven by hand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "mkDefault everywhere user might override" rule is what caused the
home.packages bug. Clarify: mkDefault is for scalars; on list/attrset
options that other modules contribute to, a mkDefault def is dropped
entirely (filterOverrides) rather than merged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same footgun as the home.packages fix, found by sweeping the tree for
`mkDefault` on list options that other modules contribute to at normal
priority (so filterOverrides discards the mkDefault def entirely):
* themes/engine/sddm.nix — environment.systemPackages = mkDefault
[ nomarchy-sddm-theme ] was dropped, so the SDDM greeter theme package
was never installed (unthemed login).
* themes/engine/plymouth.nix — boot.kernelParams = mkDefault [ quiet
splash loglevel=3 … ] was dropped, so boots weren't quiet/clean.
* features/desktop/waybar/default.nix — home.packages = mkDefault
[ font-awesome ] was dropped, so waybar's icon font was missing.
Verified via eval: nomarchy-sddm-theme now in systemPackages, "quiet" in
kernelParams, font-awesome in home.packages. Left the genuinely-safe
single-definition mkDefaults alone (plymouth.themePackages,
resolved.fallbackDns, hyprsunset.extraArgs) and the hybridGPU videoDrivers
mkDefault (it outranks nixpkgs' mkOptionDefault on real hardware).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chasing a "DBus.Error … name is not activatable" from notify-send (mako),
the root cause turned out to be far bigger: home.packages in
features/default.nix was wrapped in lib.mkDefault. home.packages is a
list, and the module system's filterOverrides keeps only the
highest-priority definitions — so the moment any other module sets
home.packages at normal priority (features/scripts/default.nix's
`[ nomarchy-scripts ]`, the installer's profile packages), this entire
mkDefault list was DROPPED. Every install was silently missing firefox,
thunar, imv, mpv, swww, mako, hyprlock, rofi, etc. — which broke
notifications (mako never installed → exec-once fails → notify-send dead)
and the lock screen (hyprlock missing), and hid a stale `rofi-wayland`
reference (merged into `rofi` upstream) that only errored once the list
was live again. Remove the mkDefault so the list merges (home.packages =
52 pkgs, up from 31; mako/hyprlock/firefox/… now present) and fix
rofi-wayland -> rofi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pillar 9 VM test: after the menu-registration fix, the walker background
selector menu opened but still showed "No Results". Its GetEntries ran
`find <dir> -type f`, but home-manager deploys the per-theme background
images as symlinks into the nix store, and `-type f` doesn't follow
symlinks. Add -L (the sibling nomarchy_themes.lua already uses `find -L`).
Verified in the VM: `find -type f` -> 0, `find -L` -> the backgrounds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pillar 9 VM test: the walker theme picker (and background selector)
returned "No Results". Root cause: nomarchy_themes.lua and
nomarchy_background_selector.lua (elephant lua menu providers named
"nomarchythemes" / "nomarchyBackgroundSelector") were deployed via the
bulk nomarchy config to ~/.config/nomarchy/default/elephant/ — outside
elephant's provider search path, so elephant never registered them and
`nomarchy-launch-walker -m menus:nomarchythemes` (used by nomarchy-theme,
nomarchy-wallpaper, and nomarchy-menu Style submenu) had no backing menu.
Move them into the elephant config (features/apps/elephant/config/menus/)
so they deploy to ~/.config/elephant/menus/. Verified on a fresh VM boot:
`elephant listproviders` now lists menus:nomarchythemes +
menus:nomarchyBackgroundSelector, and the walker theme picker renders all
21 palettes with per-theme preview images.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pillar 9 VM script sweep: nomarchy-haptic-touchpad failed with
"env: 'python3': No such file or directory". It's a python3 script
(#!/usr/bin/env python3) wired as systemd.services.nomarchy-haptic-touchpad
in core/system/hardware.nix (Dell XPS haptic feedback), run from the
nomarchy-system-scripts wrapper — but python3 wasn't in systemScriptDeps,
so the wrapper's PATH lacked it and the service died on every XPS. Add
python3 (stdlib-only script, plain pkgs.python3 suffices). Verified the
rebuilt wrapper resolves python3 and the script runs to its clean
"no device found" exit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New pillar to drive the distro running in a VM and verify what actually
works vs. what only looks right in source: run every nomarchy-* command,
walk every menu entry, switch through all 22 palettes and eyeball
rendering, fire every keybinding, drive the installer. Documents the
method (build -> boot headless -> screendump/sendkey) proven in the first
VM-boot pass, the per-component checklist, and the deliverable format. The
old "Pillar 8 runtime verification" Now-board punch-list folds into it.
AGENT.md's verify step now points at the headless VM screenshot flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Booting the VM peeled back two more layers of Hyprland config errors after
the geforce/moonlight fix:
* pip.conf used underscored rule names with stray values —
`keep_aspect_ratio on` and `border_size 0`. Hyprland's rules are
`keepaspectratio` (boolean, no value) and `bordersize 0`.
* retroarch/steam/system.conf wrote `idleinhibit, <mode>` with a comma,
so Hyprland read <mode> as a selector ("Invalid value: always/
fullscreen"). The mode is space-separated: `idleinhibit always`.
Verified clean in a rebuilt VM — the boot-time config-error overlay is
gone. All Omarchy-era rule-syntax holdovers, exposed once Component 5
wired apps.conf to source every app rule file.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Booting the VM showed "line 31: compgen: command not found" from
nomarchy-installed-summary on first boot. compgen is a bash
programmable-completion builtin, and the non-interactive bash the script
gets wrapped with (via makeWrapper + patchShebangs) is compiled without
progcomp. Beyond the visible error, the battery-presence check silently
failed to the else branch, so a laptop was always reported as "desktop".
Replace `compgen -G` with a nullglob array (a shopt, always available).
The installer's identical check runs under the ISO's interactive bash, so
it was unaffected — this script is the only compgen user.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Booting the desktop VM surfaced Hyprland config errors on every start:
"config option <windowrule:name> does not exist" from apps/geforce.conf
and apps/moonlight.conf. Both used an invalid `windowrule { name=…;
class:=…; idle_inhibit=… }` block form (an Omarchy-era holdover) that
Hyprland rejects line-by-line — exposed once Component 5 wired apps.conf
to source all 17 app rule files. Rewrite as single-line windowrulev2
rules matching every other apps/*.conf: idle-inhibit on fullscreen for
GeForceNOW; open-fullscreen + idle-inhibit for Moonlight.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ten state-mutating sites across seven scripts wrote through a predictable,
world-writable temp path (`/tmp/state.json`, `/tmp/system-state.json`)
before the atomic `sudo mv`. Because the `>` redirect runs as the invoking
user (sudo doesn't cover redirects), the shared fixed path is a symlink/
TOCTOU target and collides if two of these run concurrently. Switch each
to a per-invocation `mktemp`; the atomic rename into place is unchanged.
nomarchy-theme-set already used a temp var for its home-state write — this
makes its system-state write consistent and cleans the temp up on failure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These three home options (Hyprland inner/outer gaps + active-border width,
read from state.json, wired in features/desktop/hyprland/default.nix) were
the only real options missing from OPTIONS.md — a guardrail violation
(every nomarchy.* option must be documented). Found via a full diff of the
live option tree against the doc's headings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI's drift check failed on pure ordering noise: the generator's `sort`
collated rows and caller lists by the ambient locale (en_GB.UTF-8 on the
dev box that committed the file, C in the CI container), so `-` vs `/` and
`-` vs `s` ordered differently (e.g. theme-update vs themes-prebuild,
caller-list order). Force LC_ALL=C in the generator and regenerate
docs/SCRIPTS.md in that collation, so the committed copy and any
regeneration — dev box or CI — are byte-identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-toggle + per-palette matrix did ~39 full-system evaluations (16
toggle toplevels + 21 palettes each via a full extendModules eval + 2
home) and ran ~2h55m on the small self-hosted runner — long enough to hit
act_runner's 3h container lifetime (sleep 10800) and get killed.
Redesign:
* Combine compatible toggles into 3 configs (laptop-stack, desktop-stack,
impermanence-multi) instead of one eval per toggle. A failure means
re-run the suspect toggle alone; the trace hint covers that.
* Check palettes directly via the lib — the per-palette risk is the pure
base00 -> RGB hex math in plymouth.nix, which needs no module-system
instantiation. Forcing the palette attrset also catches a missing one.
* Drop the home configs; the per-output flake-check step already forces
both homeConfigurations.
~39 full evals -> 3, plus a near-instant palette pass. Runs in ~1m locally
(warm). AGENT.md updated for the new combined-scenario structure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`nix flake check --no-build` also evaluates packages.allThemeVariants, a
linkFarm whose drvPath forces all 22 home generations into memory at once
— the runner OOM-killed there ("Killed" after allThemeVariants). Replace
it with an explicit per-output eval that forces all 4 system closures
(incl. assertions), both standalone home generations, the installer VM
package, the overlay and the app, and skips only allThemeVariants/default.
Each tryEval is GC'd before the next, so peak memory is one config rather
than twenty-two. The eval matrix already validates every palette
one-at-a-time, so dropping that package from CI loses no real coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diagnosed the persistent "path '…-source' is not valid": it's walker's
flake reading its own source at eval time — importTOML
resources/config.toml in its home-manager module and lib.fileset.toSource
for the package src. On a cold store the walker input source isn't
materialized, and neither --no-build flake check nor `nix flake archive`
makes the fileset-derived src readable. Replace the archive step with a
`nix build` of walker's src attribute, which eagerly realizes it (and the
input it reads from) so the subsequent flake check / eval matrix find
every path valid.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flake check kept failing on a cold store with "path '…-source' is not
valid" while evaluating Stylix's import-from-derivation in the
home-manager fontconfig text. Nix keeps flake-input sources in a git
cache and only materializes the -source store paths lazily; IFD readFile
needs them as real store paths. Works locally only because prior builds
already materialized them. Add a `nix flake archive` step that copies
every transitive input into the store before the check (and before the
eval matrix, which has the same need).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>