feat: Nomarchy ground-up rewrite on NixOS 26.05

Full replacement of the previous iteration, rebuilt around three ideas:

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-10 10:59:13 +01:00
commit f211ef0d09
131 changed files with 4844 additions and 0 deletions

69
modules/home/theme.nix Normal file
View File

@@ -0,0 +1,69 @@
# Nomarchy theming engine.
#
# nomarchy.stateFile (theme-state.json, inside the consuming flake) is
# ingested at evaluation time — pure, because flake files are store
# paths — and exposed to every other module as `config.nomarchy.theme`.
#
# Theme changes are fully Home Manager managed: `nomarchy-theme-sync
# apply <theme>` writes the JSON and runs `home-manager switch`, baking
# everything (Hyprland, Waybar, Ghostty, btop, Stylix) into one
# read-only generation. No runtime patching, no partial states; theme
# history is generation history.
#
# The one runtime exception is the wallpaper (swww is imperative by
# nature): applied at session start, after every switch (hook below),
# and cycled instantly with `nomarchy-theme-sync bg next`.
{ config, lib, pkgs, ... }:
let
cfg = config.nomarchy;
themeState = builtins.fromJSON (builtins.readFile cfg.stateFile);
# Defaults guarantee evaluation succeeds on a sparse or older state
# file (e.g. one written before a schema field was added). The shipped
# Tokyo Night preset provides the color/ansi fallbacks; the path is
# relative to this module, so it resolves inside Nomarchy's own flake
# source even when consumed downstream.
preset = builtins.fromJSON (builtins.readFile ../../themes/tokyo-night.json);
defaults = preset // {
fonts = {
mono = "JetBrainsMono Nerd Font";
ui = "Inter";
size = 11;
};
ui = {
gapsIn = 5;
gapsOut = 12;
borderSize = 2;
rounding = 10;
activeOpacity = 1.0;
inactiveOpacity = 0.95;
terminalOpacity = 0.96;
blur = true;
shadow = true;
};
};
in
{
config = {
nomarchy.theme = lib.recursiveUpdate defaults themeState;
nomarchy.lib = {
# "#7aa2f7" -> "rgb(7aa2f7)" (Hyprland color syntax)
rgb = c: "rgb(${lib.removePrefix "#" c})";
# "#16161e" -> "rgba(16161eaa)" (Hyprland color + hex alpha)
rgba = c: alpha: "rgba(${lib.removePrefix "#" c}${alpha})";
};
home.packages = [ cfg.package ];
# After a switch the new theme's wallpaper should show without
# logging out. Best-effort: outside a graphical session swww isn't
# running and this is a no-op.
home.activation.nomarchyWallpaper = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run ${lib.getExe cfg.package} --quiet wallpaper || true
'';
};
}