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.
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).
'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>
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>
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>
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>
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>
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>
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>