From d8e1a13d509284172e212a4b8317116cc2864ceb Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Wed, 15 Jul 2026 11:26:59 +0100 Subject: [PATCH] =?UTF-8?q?refactor(#107):=20theme-state.json=20=E2=86=92?= =?UTF-8?q?=20state.json,=20theme-sync=20=E2=86=92=20state-sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The machine flake's git-tracked settings file is system state, not "theme" only — rename it to state.json. CLI becomes nomarchy-state-sync with a nomarchy-theme-sync symlink for scripts and muscle memory. Eval (mkFlake, doctor, lifecycle) still accepts theme-state.json; the next write migrates to state.json and removes the legacy file. Documented in MIGRATION.md; drop the CLI alias after release notes. --- README.md | 42 ++++----- agent/BACKLOG.md | 20 +--- agent/CONVENTIONS.md | 2 +- agent/GOALS.md | 2 +- agent/HARDWARE-QUEUE.md | 2 +- agent/JOURNAL.md | 35 ++++--- agent/MEMORY.md | 4 +- docs/MIGRATION.md | 29 +++++- docs/OMARCHY.md | 4 +- docs/OVERRIDES.md | 30 +++--- docs/RECOVERY.md | 2 +- docs/ROADMAP.md | 31 ++++--- docs/TESTING.md | 8 +- docs/VISION.md | 2 +- flake.nix | 92 ++++++++++--------- hosts/live.nix | 10 +- lib.nix | 21 +++-- modules/home/autotheme.nix | 4 +- modules/home/default.nix | 6 +- modules/home/first-boot.nix | 2 +- modules/home/hyprland.nix | 10 +- modules/home/idle.nix | 2 +- modules/home/keybinds.nix | 2 +- modules/home/kitty.nix | 2 +- modules/home/options.nix | 14 +-- modules/home/osd.nix | 2 +- modules/home/rofi.nix | 78 ++++++++-------- modules/home/stylix.nix | 2 +- modules/home/swaync.nix | 4 +- modules/home/theme.nix | 24 ++--- modules/home/timezone.nix | 2 +- modules/home/waybar.nix | 8 +- modules/home/yazi.nix | 2 +- modules/nixos/default.nix | 12 +-- modules/nixos/greeter.nix | 8 +- modules/nixos/hardware.nix | 12 +-- modules/nixos/options.nix | 14 +-- modules/nixos/plymouth.nix | 6 +- modules/nixos/power.nix | 4 +- modules/nixos/services.nix | 6 +- modules/nixos/timezone.nix | 8 +- .../{theme-state-read.nix => state-read.nix} | 28 +++--- .../nomarchy-control-center.sh | 14 +-- pkgs/nomarchy-doctor/nomarchy-doctor.sh | 32 +++++-- pkgs/nomarchy-first-boot/default.nix | 2 +- .../nomarchy-first-boot.sh | 10 +- pkgs/nomarchy-install/default.nix | 4 +- pkgs/nomarchy-install/nomarchy-install.sh | 6 +- pkgs/nomarchy-install/patch-template.py | 6 +- pkgs/nomarchy-lifecycle/default.nix | 12 ++- .../default.nix | 19 ++-- .../nomarchy-state-sync.py} | 89 +++++++++++++----- .../downstream/theme-state.json => state.json | 0 templates/downstream/README.md | 10 +- templates/downstream/home.nix | 4 +- .../downstream/state.json | 0 tools/capture-float-classes.nix | 6 +- tools/check-template-sot.py | 4 +- tools/doctor-float.nix | 2 +- tools/import-palettes.py | 2 +- tools/monitor-fallback.nix | 2 +- tools/theme-shot.nix | 10 +- 62 files changed, 463 insertions(+), 369 deletions(-) rename modules/{theme-state-read.nix => state-read.nix} (68%) rename pkgs/{nomarchy-theme-sync => nomarchy-state-sync}/default.nix (61%) rename pkgs/{nomarchy-theme-sync/nomarchy-theme-sync.py => nomarchy-state-sync/nomarchy-state-sync.py} (90%) rename templates/downstream/theme-state.json => state.json (100%) rename theme-state.json => templates/downstream/state.json (100%) diff --git a/README.md b/README.md index 3bd4534..0840d2f 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ partial. ``` ┌──────────────────────────────────────────────────────────────────────────┐ -│ theme-state.json (single source of truth) │ +│ state.json (single source of truth) │ │ lives INSIDE your flake checkout, git-tracked │ └───────────────────────────────────┬──────────────────────────────────────┘ │ - nomarchy-theme-sync apply gruvbox + nomarchy-state-sync apply gruvbox 1. merges the preset into the JSON (atomic write) 2. runs `home-manager switch` (no sudo, no system rebuild) │ @@ -35,7 +35,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. . ├── flake.nix # inputs + the downstream API (exports below) ├── lib.nix # nomarchy.lib.mkFlake — one-call downstream wrapper -├── theme-state.json # ★ THE single source of truth (git-tracked!) +├── state.json # ★ THE single source of truth (git-tracked!) ├── themes/ # 24 presets: .json + optional / assets │ ├── nord.json # palette (required, works alone) │ └── nord/ # assets (optional, fixed filenames) @@ -72,10 +72,10 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) ├── pkgs/ -│ ├── nomarchy-theme-sync/ # state writer + rebuild dispatcher (Python) +│ ├── nomarchy-state-sync/ # state writer + rebuild dispatcher (Python) │ ├── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake) │ ├── nomarchy-doctor/ # read-only health sheet, one command per failure -│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-theme-sync (gum) +│ ├── nomarchy-control-center/ # TUI settings front-end over nomarchy-state-sync (gum) │ └── nomarchy-battery-notify/ # low-battery toast watcher backing battery-notify.nix ├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch) ├── docs/ # human docs — map: docs/README.md @@ -161,7 +161,7 @@ Nomarchy in place, reusing your existing `hardware-configuration.nix`: **[docs/MIGRATION.md](docs/MIGRATION.md)**. You own two files day-to-day: `system.nix` and `home.nix` (plus -`theme-state.json`, written by the CLI). Your `flake.nix` is set up once — +`state.json`, written by the CLI). Your `flake.nix` is set up once — later by the installer — and never hand-edited; it's a single call: ```nix @@ -229,7 +229,7 @@ Legacy aliases still work: `sys-update` → pull+rebuild, `sys-rebuild` → `nomarchy-rebuild`, `home-update` → `nomarchy-home`. Override anything via the `nomarchy.*` surface or plain NixOS/HM options: -appearance (gaps/colors/fonts) changes through `nomarchy-theme-sync`, +appearance (gaps/colors/fonts) changes through `nomarchy-state-sync`, behaviour (input/misc/monitor/chrome) is `mkDefault` so a plain `home.nix` assignment wins, and bind/exec-once lists concatenate. Full guide with examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. @@ -243,7 +243,7 @@ two tables below are split along exactly that line. | Option | Default | Purpose | |---|---|---| -| `nomarchy.stateFile` | — (required) | Path to your theme-state.json | +| `nomarchy.stateFile` | — (required) | Path to your state.json | | `nomarchy.terminal` | `"kitty"` | Terminal for keybinds and `$TERMINAL` (Kitty is the only shipped/themed emulator) | | `nomarchy.kitty.enable` | `true` | Nomarchy's Kitty config (palette/font/opacity from theme-state) | | `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) | @@ -275,7 +275,7 @@ two tables below are split along exactly that line. | `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. Profiles that disable the laptop panel use the same safe ordering as Dock mode (external on → workspace handoff → internal off). The menu's Auto-switch row (`settings.displayProfileAuto`) applies the matching profile from Hyprland hotplug events. Workspace pins (`workspaces = { "1" = "DP-3"; }`) are moved instantly and baked as rules | | `nomarchy.launchOrFocus` | `[]` | Launch-or-focus binds: `SUPER+` 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 | +| `nomarchy.package` | overlay's `nomarchy-state-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 @@ -290,7 +290,7 @@ option, e.g. `services.easyeffects.enable = lib.mkForce false;` — see | 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.stateFile` | `null` | state.json for the system-side consumers (the Plymouth tint); `lib.mkFlake` wires it for you — set it only when composing the modules by hand | | `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) | | `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet | | `nomarchy.system.greeter.autoLogin` | state | Auto-login this user into Hyprland. Owned by System › Auto-login (`settings.greeter.autoLogin`; installer seeds it on LUKS machines) — set it here only to pin it against the menu | @@ -367,13 +367,13 @@ The trap with "read a mutable file from Nix" is pure evaluation: flakes cannot read arbitrary `$HOME` paths without `--impure` (the old prototype required it — never again). Nomarchy's convention: **the state file lives inside the consuming flake** and is wired via -`nomarchy.stateFile = ./theme-state.json;`. Reading it is pure — it's flake -source. It must be git-tracked (`nomarchy-theme-sync` runs +`nomarchy.stateFile = ./state.json;`. Reading it is pure — it's flake +source. It must be git-tracked (`nomarchy-state-sync` runs `git add --intent-to-add` after every write as a safety net). ### One change = one generation -`nomarchy-theme-sync apply ` merges the preset into the JSON and runs +`nomarchy-state-sync apply ` merges the preset into the JSON and runs `home-manager switch` (override the command with `$NOMARCHY_REBUILD`, or pass `--no-switch` to only write). Everything is baked: Hyprland, Waybar, Kitty, btop, and — via Stylix, mapped onto base16 roles — GTK, Qt, cursors and @@ -404,7 +404,7 @@ gates the unit, the first enable rebuilds) and `settings.nightlight.on` (the instant on/off). Expect more `nomarchy.*` toggles to migrate to this pattern. **Auto-commit (opt-in):** System › Auto-commit makes every `apply`/`set` -mutation also `git commit` theme-state.json in your flake — *only* that +mutation also `git commit` state.json in your flake — *only* that file, so unrelated dirty work is never swept up — turning your settings history into `git log`. Off by default; the toggle is instant (nothing in Nix consumes the flag), the off-write is itself committed so history stays @@ -432,12 +432,12 @@ asset probe). ## 5. Day-to-day ```sh -nomarchy-theme-sync list # 24 presets (nord, gruvbox, rose-pine, …) -nomarchy-theme-sync apply kanagawa # whole desktop, one generation (~a switch) -nomarchy-theme-sync set ui.gapsOut 16 # tweak one knob (also a switch) -nomarchy-theme-sync bg next # cycle wallpapers — instant, no rebuild -nomarchy-theme-sync bg auto # back to the theme's default wallpaper -nomarchy-theme-sync get colors.accent +nomarchy-state-sync list # 24 presets (nord, gruvbox, rose-pine, …) +nomarchy-state-sync apply kanagawa # whole desktop, one generation (~a switch) +nomarchy-state-sync set ui.gapsOut 16 # tweak one knob (also a switch) +nomarchy-state-sync bg next # cycle wallpapers — instant, no rebuild +nomarchy-state-sync bg auto # back to the theme's default wallpaper +nomarchy-state-sync get colors.accent nomarchy-pull # flake input update (no rebuild) nomarchy-rebuild # rebuild the system, current lock nomarchy-home # rebuild just the desktop layer @@ -484,7 +484,7 @@ reload # exec zsh (reload the shell) - **New theme:** drop a JSON into `themes/` (schema = any existing preset), plus an optional `themes//` assets directory. -- **New themed value:** add the key to `theme-state.json` and consume it in +- **New themed value:** add the key to `state.json` and consume it in the Nix modules. One place — there is no second renderer to keep in sync. - **Importing more old-distro palettes:** `tools/import-palettes.py themes/` (roles are first-class: diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index 2d12df6..f4b1f2b 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -226,7 +226,7 @@ always — the same on-AC question `modules/home/idle.nix`'s suspend listener already answers with `${onAc} ||`. In-flake state like the rest (`settings.*`), menu-writable, and read via -`theme-state-read.nix` on the NixOS side — NOT `config.nomarchy.settings`, +`state-read.nix` on the NixOS side — NOT `config.nomarchy.settings`, which does not exist there (ROADMAP § *NixOS-side state bridges (#116)*; copy the `bluetooth.enable` shape in `modules/nixos/default.nix` and add a case to `checks.state-bridges`). Pass = the toggle survives a rebuild, @@ -234,22 +234,6 @@ a suspended machine hibernates after the configured delay and resumes, and a machine without hibernate support hides the row instead of offering a suspend that never wakes. -### 107. Rename `theme.json` to reflect that it is the system state - -The state file long ago stopped being about themes: it carries night-light, -keyboard memory, display resolution and profiles, auto-commit, services and -more, so `theme.json` now misnames its own contents and misleads anyone -reading the flake. Rename it to `state.json` (Bernardo's call, 2026-07-14 — -settled, do not revisit), and carry the `nomarchy-theme-sync` tool name along -with it. - -Standalone task — do not fold it into a feature. Ships with a compatibility -shim that keeps reading an existing `theme.json` so downstream checkouts do -not break on a pull, plus a migration note. Pass = a fresh install and an -existing downstream checkout both work, every in-repo reference (modules, -tools, template, docs) uses the new name, and the shim is documented with the -release it can be dropped in. - ### 104. Runtime Airplane mode Add Airplane mode under System connectivity controls. It must disable Wi-Fi @@ -280,7 +264,7 @@ read, so *these two toggles have never actually worked for any user*. That cuts both ways: "preserve existing behavior" is not a reason to keep them (there is no behavior to preserve), but the state keys and their bridges are real now and outlive the TUI — dropping the rows must not drop the keys, or a -machine whose theme-state.json already says `bluetooth.enable = false` will +machine whose state.json already says `bluetooth.enable = false` will silently flip back on at the next rebuild. `checks.state-bridges` covers those two keys and will fail loudly if the bridges go with the tool; keep it green, and if a key is deliberately retired, remove its case in the same commit. diff --git a/agent/CONVENTIONS.md b/agent/CONVENTIONS.md index 9a8f3f1..00e3362 100644 --- a/agent/CONVENTIONS.md +++ b/agent/CONVENTIONS.md @@ -24,7 +24,7 @@ those, it probably shouldn't exist. ## Feature design - **In-flake state:** any user-settable config gets a menu writer that - lands it in `theme-state.json` (`settings.*`), git-tracked. No + lands it in `state.json` (`settings.*`), git-tracked. No `~/.local/state`, no side files. Instant-effect where possible (`--no-switch` + flip the service; the service reads the *live* working tree at start — the night-light `ExecCondition` pattern). Rebuild-baked diff --git a/agent/GOALS.md b/agent/GOALS.md index 32b6cb9..492521c 100644 --- a/agent/GOALS.md +++ b/agent/GOALS.md @@ -14,7 +14,7 @@ confidence, default identity, release bar) live in on-hardware QA. 2. **Reproducible, with zero hidden state.** The downstream flake checkout *is* the machine. All user-settable config is menu-writable into the - git-tracked state file (`theme-state.json` `settings.*`) — never + git-tracked state file (`state.json` `settings.*`) — never `~/.local/state`, never `~/.config` side files. Re-cloning your flake reproduces the machine, settings and all. 3. **Effortless to configure.** The user never has to learn Nix. Every diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index 4a791b6..ca46972 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -237,7 +237,7 @@ the **T14s** (webcam case). open Ghostty, so the hand `btop.theme` assets were only guest-file asserted (main_bg / inactive_fg keys) + desktop/bar rendered for rose-pine, everforest, summer-night, vantablack, catppuccin. On a - real session: `nomarchy-theme-sync apply ` then `btop` and + real session: `nomarchy-state-sync apply ` then `btop` and confirm backgrounds/text match the theme (esp. rose-pine Dawn light bg `#faf4ed`, vantablack near-black `#0d0d0d`, catppuccin Mocha `#1E1E2E`). One pass cycling those five is enough. diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index b0525ae..cd8a329 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -19,6 +19,17 @@ Template: --- +## 2026-07-15 — #107 state.json + nomarchy-state-sync +- **Task:** BACKLOG #107 — rename misnamed system state file and CLI. +- **Did:** `theme-state.json` → `state.json` (template, root, docs, modules); + package/CLI `nomarchy-theme-sync` → `nomarchy-state-sync` with symlink + alias; `modules/state-read.nix`; mkFlake/doctor/lifecycle read legacy + name; writes always land on `state.json` and remove the old file. +- **Verified:** py_compile; legacy-file unit migrate (write removes + theme-state.json); V0 flake check pending in same push. +- **Pending:** drop `nomarchy-theme-sync` alias after release notes. +- **Next suggestion:** #104 airplane, or #98 with a screenshot. + ## 2026-07-15 — Close #94 installed chromium + kill Hyprland update-news - **Task:** Residual #94 (chromium on installed path) + first-boot polish seen while probing. @@ -373,7 +384,7 @@ Template: (`modules/home/options.nix:412`), so `or ` had been swallowing a missing-attribute error on the NixOS side forever. Options now declare a *static* default and the implementing module reads the state via - `theme-state-read.nix` + `lib.mkIf (state != null) (lib.mkDefault state)` — + `state-read.nix` + `lib.mkIf (state != null) (lib.mkDefault state)` — done for `bluetooth.enable` (default.nix) and `services.printing.enable` (services.nix). `batteryChargeLimit`'s read is simply deleted: power.nix's oneshot reads the key with `jq` at *runtime*, which is why that menu worked. @@ -421,7 +432,7 @@ Template: Making both state-owned surfaced two real bugs: `greeter.autoLogin` defaulted from `config.nomarchy.settings…`, an attribute that exists ONLY on the HM side, and `or null` swallowed it — the state path never worked on - any machine ever built; and `theme-sync get` printed Python's `None` for a + any machine ever built; and `state-sync get` printed Python's `None` for a JSON null, so any `case … null)` would miss. - **Verified:** **V1** — flake check, installer-safety, option-docs pass. Proved by eval/build: a state carrying autoLogin yields greetd @@ -580,14 +591,14 @@ Template: - **Next suggestion:** wait on round 7 before touching the dock path again. ## 2026-07-13 — #99 live-seed git-init; theme-switch failure filed as #113 (this commit) -- **Task:** BACKLOG #99 — false "theme-state.json NOT git-tracked" doctor/ +- **Task:** BACKLOG #99 — false "state.json NOT git-tracked" doctor/ Waybar warning in the live session; resolve the held question from Fable. - **Did:** hosts/live.nix `nomarchy-seed-flake` now `git init`+`add`+`commit`s the seeded `~/.nomarchy` (like nomarchy-install.sh:654), so the live checkout is a committed repo. Also committed #94's live-posture comment in flake.nix (separate commit). - **Verified:** V2 — focused serial-log VM run (`/tmp/nomarchy-v2-theme-99/`): - doctor prints `✔ theme-state.json is git-tracked` + `✔ flake checkout is + doctor prints `✔ state.json is git-tracked` + `✔ flake checkout is clean`, `GIT_REPO_OK`/`TRACKED_OK` markers. Held question RESOLVED: the same run's offline gruvbox switch failed with a 1176-drv source rebuild, but a local drv-equivalence test proved the failing @@ -885,14 +896,14 @@ Template: ## 2026-07-12 — auto-commit sweep before pull/rebuild/home (interactive) - **Task:** Bernardo's request (no BACKLOG item): with autoCommit on, hand edits to `~/.nomarchy` (his had 4) stayed forever-dirty — - theme-sync's commit is pathspec-limited to theme-state.json by design. + state-sync's commit is pathspec-limited to state.json by design. - **Did:** New internal `nomarchy-autocommit` in nomarchy-lifecycle: live-reads `settings.autoCommit`, commits *everything* dirty with message `nomarchy: auto-commit before