refactor(#107): theme-state.json → state.json, theme-sync → state-sync
Some checks failed
Check / eval (push) Has been cancelled

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.
This commit is contained in:
2026-07-15 11:26:59 +01:00
parent 013403deb4
commit d8e1a13d50
62 changed files with 463 additions and 369 deletions

View File

@@ -169,7 +169,7 @@ ryzenadj)**, **no Secure Boot**, and the **stateVersion override**.
nomarchy.hardware.amd.enable = true; # amd-pstate + radeonsi VA-API
# nomarchy.hardware.amd.rocm.enable = true; # opt-in GPU compute (multi-GB)
# Auto-login is NOT set here: it lives in theme-state.json
# Auto-login is NOT set here: it lives in state.json
# (settings.greeter.autoLogin) so System Auto-login can move it — a line
# here would outrank the state and pin it. Turn it on after the first boot
# with `nomarchy-autologin on` (what nomarchy-install seeds on encrypted
@@ -206,9 +206,9 @@ emacs setup).
}
```
### `theme-state.json`
### `state.json`
Copy the template's `theme-state.json` (or let `nomarchy-menu theme` write
Copy the template's `state.json` (or let `nomarchy-menu theme` write
it after the switch). Your old `nomarchy-state.nix` prototype (schema
`nomarchy.theme = "nord"` …) is **retired** — the current distro uses this
JSON. `nord` is a shipped Nomarchy theme, so you lose nothing.
@@ -247,7 +247,7 @@ radeonsi), display brightness.
You should also get a one-shot **You're set** toast (menu / themes /
keys). If it never appears: `systemctl --user status nomarchy-first-boot`
and re-try with
`nomarchy-theme-sync set settings.firstBootShown false --no-switch`
`nomarchy-state-sync set settings.firstBootShown false --no-switch`
then log out/in.
If anything is wrong: **reboot → old generation.** Nothing is committed as
@@ -261,7 +261,7 @@ default yet.
powerprofile icons open the power menu. Your ryzenadj scripts are gone;
if you miss a specific TDP behaviour, that's a followup, not a blocker.
- **Theme:** `nomarchy-menu theme` → pick a preset (writes
`theme-state.json`).
`state.json`).
- **Snapshots:** `nomarchy-menu` → System → Snapshots should see your
existing `@snapshots` subvolume.
- **Fingerprint:** `fingerprint.enable = true` only starts **fprintd**
@@ -446,6 +446,25 @@ just reports that no swap is configured.
---
## State file rename (`theme-state.json` → `state.json`, #107)
The machine flake's git-tracked state file is **`state.json`** (appearance +
menu settings). Older checkouts may still have `theme-state.json`.
- **Eval:** `lib.mkFlake` and the reader accept either name (prefer
`state.json`).
- **Write:** `nomarchy-state-sync` (and the menu) always write `state.json`
and remove a leftover `theme-state.json` so you never have two sources.
- **CLI:** `nomarchy-state-sync` is the real name; `nomarchy-theme-sync`
remains a symlink for scripts and muscle memory. Drop the alias after the
next stable release notes call it out.
No action required on pull: the next theme apply or menu write migrates you.
To migrate by hand: `mv theme-state.json state.json && git add state.json`
(and `git rm theme-state.json` if it was tracked).
---
## TuringMachine — the decisions, at a glance
| Item | Choice |

View File

@@ -81,11 +81,11 @@ Both distros reskin everything from one place. In Nomarchy:
- **`SUPER+T`** (or `nomarchy-menu theme`) — pick from 24 presets with live
previews; it rebuilds and applies to Hyprland, Waybar, the terminal,
btop and GTK/Qt at once.
- Or from a shell: `nomarchy-theme-sync apply <slug>` (e.g. `gruvbox`,
- Or from a shell: `nomarchy-state-sync apply <slug>` (e.g. `gruvbox`,
`rose-pine`, `nord`).
- Tweak gaps / rounding / fonts / opacity / icon pack without leaving the
theme system — see [OVERRIDES.md](OVERRIDES.md). One git-tracked JSON
(`theme-state.json`) is the single source of truth.
(`state.json`) is the single source of truth.
---

View File

@@ -9,16 +9,16 @@ rule of thumb:
## 1. Appearance (gaps, colors, rounding, fonts, opacity) — use the CLI
Everything that defines the *look* flows from `theme-state.json`, the single
source of truth. Change it with `nomarchy-theme-sync`, which writes the JSON
Everything that defines the *look* flows from `state.json`, the single
source of truth. Change it with `nomarchy-state-sync`, which writes the JSON
and rebuilds — one generation, applied to Hyprland, Waybar, Kitty, btop and
Stylix at once:
```sh
nomarchy-theme-sync set ui.gapsOut 16 # gaps, borders, rounding, opacity
nomarchy-theme-sync set ui.rounding 0
nomarchy-theme-sync set fonts.mono "FiraCode Nerd Font"
nomarchy-theme-sync apply gruvbox # whole palette
nomarchy-state-sync set ui.gapsOut 16 # gaps, borders, rounding, opacity
nomarchy-state-sync set ui.rounding 0
nomarchy-state-sync set fonts.mono "FiraCode Nerd Font"
nomarchy-state-sync apply gruvbox # whole palette
```
### Icon pack
@@ -28,7 +28,7 @@ icon pack shipped by default (Papirus alone is ~1 GiB, so extra packs are
opt-in rather than a cost every install pays). To switch to another pack:
```sh
nomarchy-theme-sync set icons "Tela-dark" # or "" to return to Papirus-by-mode
nomarchy-state-sync set icons "Tela-dark" # or "" to return to Papirus-by-mode
```
Only the pack you name is pulled into your system (the first switch downloads
@@ -55,11 +55,11 @@ Turn it on from **Look & Feel Auto theme** (`SUPER+M`): toggle it, pick the
day and night themes, and set the sunrise/sunset times. The same from a shell:
```sh
nomarchy-theme-sync set settings.autoTheme.day summer-day --no-switch
nomarchy-theme-sync set settings.autoTheme.night summer-night --no-switch
nomarchy-theme-sync set settings.autoTheme.sunset 20:00 --no-switch
nomarchy-theme-sync set settings.autoTheme.enable true --no-switch
nomarchy-theme-sync auto --force # one rebuild: installs the timer + applies now
nomarchy-state-sync set settings.autoTheme.day summer-day --no-switch
nomarchy-state-sync set settings.autoTheme.night summer-night --no-switch
nomarchy-state-sync set settings.autoTheme.sunset 20:00 --no-switch
nomarchy-state-sync set settings.autoTheme.enable true --no-switch
nomarchy-state-sync auto --force # one rebuild: installs the timer + applies now
```
State lives in `settings.autoTheme`:
@@ -67,14 +67,14 @@ State lives in `settings.autoTheme`:
| Field | Meaning |
|---|---|
| `enable` | on / off |
| `day` / `night` | theme slugs — e.g. `summer-day` / `summer-night` (see `nomarchy-theme-sync list`) |
| `day` / `night` | theme slugs — e.g. `summer-day` / `summer-night` (see `nomarchy-state-sync list`) |
| `sunrise` / `sunset` | switch times, `"HH:MM"` (24-hour) |
A timer re-checks every ~15 minutes: the day theme applies around sunrise, the
night theme around sunset — but it only rebuilds when the active theme
actually needs to change, so most checks do nothing. Enabling rebuilds once
(to install the timer); disabling is instant. Preview the current decision
without switching with `nomarchy-theme-sync auto --which`.
without switching with `nomarchy-state-sync auto --which`.
These values are deliberately kept at normal priority in the modules, so they
stay owned by the theme system. If you *insist* on pinning one in `home.nix`
@@ -197,7 +197,7 @@ value is theme-owned at normal priority — either change it via the CLI (§1) o
| You want to… | Do this |
|---|---|
| Change gaps / colors / rounding / fonts | `nomarchy-theme-sync set …` or `apply` |
| Change gaps / colors / rounding / fonts | `nomarchy-state-sync set …` or `apply` |
| Change input / misc / monitor / animations / terminal chrome | plain assignment in `home.nix` |
| Arrange monitors declaratively | `nomarchy.monitors` (values via `nwg-displays`) |
| Add keybinds / autostarts | add to the `bind` / `exec-once` list (concatenates) |

View File

@@ -24,7 +24,7 @@ home-manager generations # newest first, one per theme/HM change
The same picker lives in the menu: **System Rollback** lists the
recent desktop generations — pick one and it activates.
Or simply apply a theme you know is good: `nomarchy-theme-sync apply
Or simply apply a theme you know is good: `nomarchy-state-sync apply
boreal` (or any preset). If a switch failed halfway, the state file is written
*before* the rebuild — fix the cause and re-run
`home-manager switch --flake ~/.nomarchy` (or `nomarchy-home`).

View File

@@ -19,7 +19,7 @@ Design/decision records and a running log of shipped work (items marked
## Roadmap
- **Menu system** (apps launcher + theme switching + system actions), built
on rofi 2.0 (native Wayland on 26.05) — its `.rasi` theme is baked from
theme-state.json like every other app, with rich per-element styling:
state.json like every other app, with rich per-element styling:
- ✓ shipped: `modules/home/rofi.nix` (per-element theme generated from
the palette, `themes/<slug>/rofi.rasi` whole-swap) and the
`nomarchy-menu` dispatcher: root picker (no args) · `power`
@@ -197,18 +197,18 @@ Design/decision records and a running log of shipped work (items marked
(confirmed): `themes/` is 86 MB and `backgrounds/` is **all** of it — the
palette JSONs + per-theme overrides are only ~208 KB, and the wallpapers are
**never read at Nix eval** (only the Python `swww` path uses them). But
`theme-state.json` is git-tracked, so every `apply` rewrites it → the flake
`state.json` is git-tracked, so every `apply` rewrites it → the flake
tree changes → Nix re-copies the whole 86 MB source before `home-manager
switch` can evaluate. You pay an 86 MB copy to change a 1 KB file.
**Decided approach (deferred — don't want the extra moving part yet):**
Option 1, a **separate pinned wallpapers artifact** — a `Nomarchy-wallpapers`
repo or release tarball, pulled once via a flake input / `fetchurl` (pinned
by hash → content-addressed, never re-copied on a state write), with
`nomarchy-theme-sync` reading wallpapers from that stable store path (the
`nomarchy-state-sync` reading wallpapers from that stable store path (the
`NOMARCHY_DEFAULT_THEMES` env hook already anticipates external theme
assets). Keeps eval **pure**; the live ISO still bakes them in (fetched at
build). A state write then re-copies only ~208 KB. Rejected alternative:
moving `theme-state.json` out to `~/.config` + `--impure` eval (one repo, no
moving `state.json` out to `~/.config` + `--impure` eval (one repo, no
second artifact, but trades away the in-tree-pinned-state reproducibility).
Follow-on if `home-manager switch` itself is still the bottleneck after the
copy is gone: **pre-built theme variants** (build each theme's generation
@@ -302,7 +302,7 @@ Design/decision records and a running log of shipped work (items marked
- ✓ **Auto time-of-day theme (#79, `VISION § D`):** an automatic light/dark
theme switch through the **same** engine as a manual apply — no second
pipeline. `settings.autoTheme = {enable, day, night, sunrise, sunset}`
drives `nomarchy-theme-sync auto`, which picks day vs night by the local
drives `nomarchy-state-sync auto`, which picks day vs night by the local
clock and applies the preset **only if it differs** (idempotent).
`modules/home/autotheme.nix` installs a user timer
(`OnStartupSec` + `OnCalendar=*:0/15` + `Persistent`, gated on the state
@@ -329,7 +329,7 @@ Design/decision records and a running log of shipped work (items marked
`settings.{bluetooth,printing}.enable` and printed "requires rebuild", and
the rebuild changed nothing, for as long as those toggles had existed.
**The pattern, now uniform:** the option declares a *static* default and
the implementing module reads the state via `modules/theme-state-read.nix`
the implementing module reads the state via `modules/state-read.nix`
(fails closed on bad JSON) and `mkDefault`s it —
`lib.mkIf (stateX != null) (lib.mkDefault stateX)`, so an absent key leaves
the option default as the single source of the fallback and a hand-set
@@ -427,6 +427,11 @@ Design/decision records and a running log of shipped work (items marked
default `$TERMINAL` / SUPER+Return, doctor + calendar + what-changed use
`kitty --class=com.nomarchy.*`. Ghostty module and install-time GL probe
removed. V3: Acer SUPER+Return + doctor float.
- ✓ **State file rename (#107):** `theme-state.json` → `state.json`; CLI
`nomarchy-theme-sync` → `nomarchy-state-sync` (old binary name kept as a
symlink). Eval/read shims accept the legacy file name; the next write
migrates. Documented in MIGRATION.md. Drop the CLI alias after the next
stable release notes call it out.
- ✓ **Installed default browser path (#94 residual):** live half closed by
#103 + `checks.live-baseline-apps`. Installed path V2 (2026-07-15): re-boot
of offline `test-install` disk — `chromium --version` on PATH (150.x),
@@ -1112,7 +1117,7 @@ Design/decision records and a running log of shipped work (items marked
`settings.nightlight.on` (runtime on/off). **Off by default.** The *first*
enable from the menu writes `installed` and rebuilds to create the unit (the
one accepted rebuild); **every toggle after is instant** —
`nomarchy-theme-sync set settings.nightlight.on … --no-switch` (atomic +
`nomarchy-state-sync set settings.nightlight.on … --no-switch` (atomic +
`git add -N`, no rebuild) plus a `systemctl` start/stop. Splitting the sticky
flag from the on/off is what avoids a **decay** bug: an instant-off writes
only `on`, so an unrelated later rebuild (e.g. `sys-update`) never drops the
@@ -1143,7 +1148,7 @@ Design/decision records and a running log of shipped work (items marked
(opt-in, off by default) — geoclue + `services.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,
keyboard philosophy): the flag is `settings.autoTimezone` in state.json,
git-tracked; the System-menu entry (`nomarchy-menu autotimezone` →
`nomarchy-autotimezone`) writes it and rebuilds. **Not instant like
night-light** — it's a *system* service (not a user unit you can start/stop),
@@ -1318,10 +1323,10 @@ Design/decision records and a running log of shipped work (items marked
- ✓ **Opt-in auto-commit of state mutations (in-flake state, Phase 4):**
`settings.autoCommit` (menu: **System Auto-commit**, self-gated on the
flake being a git repo) makes every `apply`/`set` also `git commit`
theme-state.json in the downstream flake. Design decisions: the flag is
state.json in the downstream flake. Design decisions: the flag is
**live-read by the tool** on each write — nothing in Nix consumes it, so
the toggle is instant, no rebuild, no option-surface addition; the commit
is **pathspec-limited** (`git commit -- theme-state.json`) so unrelated
is **pathspec-limited** (`git commit -- 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 instead of
staying forever-dirty; a same-value `set` no-ops (diff against HEAD); a
@@ -1347,7 +1352,7 @@ Design/decision records and a running log of shipped work (items marked
the real binary: sweep, no-op on clean/off/non-repo).
- **"nomarchy" control center:** a single TUI/GUI front-end over the common
toggles — theme, power profile, opt-in services, display, DND — built on
the same `nomarchy-theme-sync` / `nomarchy.*` surface the menu already
the same `nomarchy-state-sync` / `nomarchy.*` surface the menu already
uses. Plus a first-boot welcome that lands new installs in a guided "pick
your theme / essentials" flow (ties into the branding work).
@@ -1390,11 +1395,11 @@ Design/decision records and a running log of shipped work (items marked
hardware QA, 2026-07-04): exec-once has no supervisor, so any crash
orphaned the session until relogin — and the switch path itself was
crash-prone: `reload_style_on_change` (inotify on style.css) and
theme-sync's SIGUSR2 both fired while the HM symlinks flipped, a
state-sync's SIGUSR2 both fired while the HM symlinks flipped, a
double-reload race in waybar's in-place reload. Fix: (1) the bar runs
under a `nomarchy-waybar` supervisor (waybar.nix) — ANY exit respawns
it, with a crash-loop guard (5 fast exits → critical notification,
stop); (2) theme-sync now prefers a clean `pkill -x waybar` when it
stop); (2) state-sync now prefers a clean `pkill -x waybar` when it
sees the supervisor (a restart with fresh config+style — no reload
code path at all), keeping SIGUSR2 only as the fallback for
unsupervised/custom bars; `reload_style_on_change` stays for manual

View File

@@ -72,10 +72,10 @@ Work through these in order; each one exercises a different layer.
| 2 | Waybar shows at top, themed (blue accent on dark) | HM waybar module, palette baking |
| 3 | `SUPER+Return` opens Kitty with the default theme colors (Boreal) | terminal default, ANSI palette |
| 4 | `btop` in the terminal is themed | per-theme asset baking |
| 5 | `nomarchy-theme-sync list` prints 24 presets | package, baked themes dir |
| 6 | `nomarchy-theme-sync apply boreal` (or re-apply the **current** theme) offline → switch succeeds. Applying a *different* preset may need a network — the ISO pins the default theme's generation, not every preset (#113) | state write + offline rebuild contract |
| 5 | `nomarchy-state-sync list` prints 24 presets | package, baked themes dir |
| 6 | `nomarchy-state-sync apply boreal` (or re-apply the **current** theme) offline → switch succeeds. Applying a *different* preset may need a network — the ISO pins the default theme's generation, not every preset (#113) | state write + offline rebuild contract |
| 7 | `SUPER+SHIFT+T` cycles wallpapers instantly (try `tokyo-night` for 4, or `boreal` for its set) | the runtime wallpaper path |
| 8 | `nomarchy-theme-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
| 8 | `nomarchy-state-sync apply summer-night` → after the switch the bar has its own identity (light bar, different styling) | whole-swap waybar.css assets |
| 9 | Open a GTK app — dark theme matching the palette | Stylix layer |
| 10 | `home-manager generations` lists one generation per theme change; activating an older one rolls the theme back | atomicity / rollback story |
@@ -137,7 +137,7 @@ Hyprland's headless backend otherwise aborts in a VM-only zero-output state.
- Session/login problems: `journalctl -b -u greetd`, then `journalctl
--user -b`.
- Theme switch failures: run `nomarchy-theme-sync apply <x>` from a
- Theme switch failures: run `nomarchy-state-sync apply <x>` from a
terminal — the home-manager output streams there. The state file is
written *before* the rebuild, so after fixing you can just re-run
`home-manager switch --flake ~/.nomarchy`.

View File

@@ -72,7 +72,7 @@ Reference: [HARDWARE.md](HARDWARE.md) §4§5, §11 (doctor).
| Idea | Intent | Notes |
|------|--------|-------|
| **Silent first-week card** ✓ | One dismissible “youre set” (menu, themes, keys, network) — not a wizard | ✓ shipped #81`settings.firstBootShown` + notify; live ISO keeps its own toast |
| **Boreal as default** ✓ | Identity on first boot | ✓ seed `theme-state.json` + install path use Boreal (2026-07-09) |
| **Boreal as default** ✓ | Identity on first boot | ✓ seed `state.json` + install path use Boreal (2026-07-09) |
| **Generation readability** ✓ | “What changed last rebuild” in plain language | ✓ shipped #82 — nvd toast + System What changed? |
| **Post-install hints** | One-shot MOTD/notify for fwupd / fprintd when relevant | Partial: MOTD + control-center tips; still no self-gated *first-session* toast when hardware is present (see BACKLOG PROPOSED) |