refactor(#107): theme-state.json → state.json, theme-sync → state-sync
Some checks failed
Check / eval (push) Has been cancelled
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:
92
flake.nix
92
flake.nix
@@ -75,12 +75,12 @@
|
||||
# ─── Downstream API ────────────────────────────────────────────
|
||||
# A user's machine flake imports these and layers its own
|
||||
# system.nix / home.nix on top (see templates/downstream).
|
||||
# Their theme-state.json lives in THEIR flake and is wired up via
|
||||
# Their state.json lives in THEIR flake and is wired up via
|
||||
# the `nomarchy.stateFile` option — reading it stays pure.
|
||||
|
||||
overlays.default = final: prev: {
|
||||
nomarchy-theme-sync = final.callPackage ./pkgs/nomarchy-theme-sync {
|
||||
# Presets baked into the package, so `nomarchy-theme-sync list`
|
||||
nomarchy-state-sync = final.callPackage ./pkgs/nomarchy-state-sync {
|
||||
# Presets baked into the package, so `nomarchy-state-sync list`
|
||||
# works on machines that don't check out this repo.
|
||||
themesDir = ./themes;
|
||||
};
|
||||
@@ -127,7 +127,9 @@
|
||||
};
|
||||
|
||||
packages.${system} = {
|
||||
nomarchy-theme-sync = pkgs.nomarchy-theme-sync;
|
||||
nomarchy-state-sync = pkgs.nomarchy-state-sync;
|
||||
# #107 compat alias — same derivation; drop after release notes say so.
|
||||
nomarchy-theme-sync = pkgs.nomarchy-state-sync;
|
||||
nomarchy-install = nomarchyInstall;
|
||||
# Overlay tools exported for `nix build .#nomarchy-doctor` etc.
|
||||
# (maintainer/CI convenience — modules install them via the overlay).
|
||||
@@ -138,7 +140,7 @@
|
||||
nomarchy-first-boot = pkgs.nomarchy-first-boot;
|
||||
nomarchy-what-changed = pkgs.nomarchy-what-changed;
|
||||
nomarchy-detect-hw = pkgs.nomarchy-detect-hw;
|
||||
default = pkgs.nomarchy-theme-sync;
|
||||
default = pkgs.nomarchy-state-sync;
|
||||
};
|
||||
|
||||
# ─── Checks ────────────────────────────────────────────────────
|
||||
@@ -155,11 +157,11 @@
|
||||
hardwareProfile = "framework-13-7040-amd"; # exercises the mapping
|
||||
};
|
||||
|
||||
# theme-state.json fixtures for the state-bridge checks below,
|
||||
# state.json fixtures for the state-bridge checks below,
|
||||
# built off the real shipped template so they stay honest about
|
||||
# what a machine actually carries.
|
||||
baseState = builtins.fromJSON
|
||||
(builtins.readFile ./templates/downstream/theme-state.json);
|
||||
(builtins.readFile ./templates/downstream/state.json);
|
||||
stateFileOf = name: overrides: pkgs.writeText name
|
||||
(builtins.toJSON (nixpkgs.lib.recursiveUpdate baseState overrides));
|
||||
# The shipped template carries none of these keys — the
|
||||
@@ -236,7 +238,7 @@
|
||||
throw ''
|
||||
State bridge broken — a settings.* key no longer reaches the config:
|
||||
${nixpkgs.lib.concatMapStringsSep "\n" (c: " ✗ ${c.n}") failed}
|
||||
Read the state via modules/theme-state-read.nix and mkDefault it
|
||||
Read the state via modules/state-read.nix and mkDefault it
|
||||
in the module — `config.nomarchy.settings` does NOT exist on the
|
||||
NixOS side, and `or <fallback>` swallows that silently.
|
||||
See ROADMAP § "NixOS-side state bridges (#116)".''
|
||||
@@ -330,53 +332,53 @@
|
||||
# reports broken fixtures by field with a fix, good state
|
||||
# passes, and an invalid `set` is refused BEFORE the write —
|
||||
# the on-disk file must be byte-identical after the attempt.
|
||||
theme-sync-validate = pkgs.runCommand "nomarchy-theme-sync-validate"
|
||||
state-sync-validate = pkgs.runCommand "nomarchy-state-sync-validate"
|
||||
{ nativeBuildInputs = [ pkgs.python3 pkgs.jq ]; }
|
||||
''
|
||||
set -eu
|
||||
tool=${./pkgs/nomarchy-theme-sync/nomarchy-theme-sync.py}
|
||||
tool=${./pkgs/nomarchy-state-sync/nomarchy-state-sync.py}
|
||||
export NOMARCHY_DEFAULT_THEMES=${./themes}
|
||||
|
||||
mkdir good && cp ${./templates/downstream/theme-state.json} good/theme-state.json
|
||||
chmod +w good/theme-state.json
|
||||
mkdir good && cp ${./templates/downstream/state.json} good/state.json
|
||||
chmod +w good/state.json
|
||||
NOMARCHY_PATH=$PWD/good python3 "$tool" validate
|
||||
|
||||
mkdir syntax && printf '{ "slug": "x", }' > syntax/theme-state.json
|
||||
mkdir syntax && printf '{ "slug": "x", }' > syntax/state.json
|
||||
if NOMARCHY_PATH=$PWD/syntax python3 "$tool" validate 2>err; then
|
||||
echo "FAIL: syntax fixture accepted"; exit 1; fi
|
||||
grep -q "syntax error at line" err
|
||||
|
||||
mkdir badhex && jq '.colors.accent = "7aa2f7"' \
|
||||
good/theme-state.json > badhex/theme-state.json
|
||||
good/state.json > badhex/state.json
|
||||
if NOMARCHY_PATH=$PWD/badhex python3 "$tool" validate 2>err; then
|
||||
echo "FAIL: bad hex accepted"; exit 1; fi
|
||||
grep -q "colors.accent" err && grep -q "fix:" err
|
||||
|
||||
mkdir badtype && jq '.ui.gapsOut = "12"' \
|
||||
good/theme-state.json > badtype/theme-state.json
|
||||
good/state.json > badtype/state.json
|
||||
if NOMARCHY_PATH=$PWD/badtype python3 "$tool" validate 2>err; then
|
||||
echo "FAIL: bad type accepted"; exit 1; fi
|
||||
grep -q "ui.gapsOut" err
|
||||
|
||||
mkdir unknown && jq '.extraKey = 1' \
|
||||
good/theme-state.json > unknown/theme-state.json
|
||||
good/state.json > unknown/state.json
|
||||
NOMARCHY_PATH=$PWD/unknown python3 "$tool" validate \
|
||||
| grep -q "unknown top-level key"
|
||||
|
||||
cp good/theme-state.json before.json
|
||||
cp good/state.json before.json
|
||||
if NOMARCHY_PATH=$PWD/good python3 "$tool" --quiet \
|
||||
set ui.gapsOut '"12"' --no-switch 2>err; then
|
||||
echo "FAIL: invalid set was written"; exit 1; fi
|
||||
grep -q "refusing to write" err
|
||||
cmp good/theme-state.json before.json
|
||||
cmp good/state.json before.json
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
||||
# The lifecycle "sweep before switch": with settings.autoCommit
|
||||
# on, nomarchy-pull/-rebuild/-home first commit every dirty file
|
||||
# in the machine flake (theme-sync's own auto_commit stays
|
||||
# pathspec-limited to theme-state.json). Exercises the real
|
||||
# in the machine flake (state-sync's own auto_commit stays
|
||||
# pathspec-limited to state.json). Exercises the real
|
||||
# helper binary against sandbox repos: flag on → one commit,
|
||||
# clean tree, fallback identity, file list in the body; clean
|
||||
# tree / flag off / no repo → untouched, never an error.
|
||||
@@ -388,7 +390,7 @@
|
||||
export HOME=$PWD # no global git config → fallback identity
|
||||
|
||||
mkdir flake && git -C flake init -q
|
||||
printf '{ "settings": { "autoCommit": true } }' > flake/theme-state.json
|
||||
printf '{ "settings": { "autoCommit": true } }' > flake/state.json
|
||||
echo base > flake/system.nix
|
||||
git -C flake add -A
|
||||
git -C flake -c user.name=T -c user.email=t@t commit -qm init
|
||||
@@ -408,7 +410,7 @@
|
||||
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
||||
|
||||
# Flag off → dirty stays dirty, no commit
|
||||
printf '{ "settings": { "autoCommit": false } }' > flake/theme-state.json
|
||||
printf '{ "settings": { "autoCommit": false } }' > flake/state.json
|
||||
echo more >> flake/system.nix
|
||||
NOMARCHY_PATH=$PWD/flake $tool rebuild
|
||||
[ "$(git -C flake rev-list --count HEAD)" = 2 ]
|
||||
@@ -416,7 +418,7 @@
|
||||
|
||||
# Not a git repo → silent no-op, exit 0
|
||||
mkdir plain
|
||||
printf '{ "settings": { "autoCommit": true } }' > plain/theme-state.json
|
||||
printf '{ "settings": { "autoCommit": true } }' > plain/state.json
|
||||
NOMARCHY_PATH=$PWD/plain $tool rebuild
|
||||
|
||||
touch $out
|
||||
@@ -1391,7 +1393,7 @@
|
||||
modules = [
|
||||
self.homeModules.nomarchy
|
||||
{
|
||||
nomarchy.stateFile = ./templates/downstream/theme-state.json;
|
||||
nomarchy.stateFile = ./templates/downstream/state.json;
|
||||
nomarchy.nightlight = {
|
||||
enable = true;
|
||||
latitude = "52.52";
|
||||
@@ -1482,14 +1484,14 @@
|
||||
# First-session welcome toast gate (#81): fires once when
|
||||
# settings.firstBootShown is unset, writes the marker via
|
||||
# theme-sync, silent on re-run; skips hostname nomarchy-live.
|
||||
# notify-send + theme-sync shimmed on PATH (same pattern as
|
||||
# notify-send + state-sync shimmed on PATH (same pattern as
|
||||
# battery-notify). Real toast rendering stays session/V3.
|
||||
first-boot = pkgs.testers.runNixOSTest {
|
||||
name = "nomarchy-first-boot";
|
||||
nodes.machine = { ... }: {
|
||||
environment.systemPackages = [
|
||||
pkgs.nomarchy-first-boot
|
||||
pkgs.nomarchy-theme-sync
|
||||
pkgs.nomarchy-state-sync
|
||||
pkgs.jq
|
||||
];
|
||||
};
|
||||
@@ -1499,8 +1501,8 @@
|
||||
# Writable flake checkout with a valid theme-state (template).
|
||||
machine.succeed(
|
||||
"mkdir -p /tmp/fake-nomarchy && "
|
||||
"cp ${./templates/downstream/theme-state.json} "
|
||||
"/tmp/fake-nomarchy/theme-state.json"
|
||||
"cp ${./templates/downstream/state.json} "
|
||||
"/tmp/fake-nomarchy/state.json"
|
||||
)
|
||||
|
||||
machine.succeed(
|
||||
@@ -1521,7 +1523,7 @@
|
||||
assert "SUPER+M" in out and "SUPER+T" in out, f"key pointers missing:\n{out}"
|
||||
assert "Network" in out or "Wi" in out, f"network pointer missing:\n{out}"
|
||||
shown = machine.succeed(
|
||||
f"{env} nomarchy-theme-sync get settings.firstBootShown"
|
||||
f"{env} nomarchy-state-sync get settings.firstBootShown"
|
||||
).strip()
|
||||
assert shown == "true", f"marker not written: {shown!r}"
|
||||
|
||||
@@ -1532,7 +1534,7 @@
|
||||
|
||||
# Live ISO hostname: skip even with marker cleared.
|
||||
machine.succeed(
|
||||
f"{env} nomarchy-theme-sync --quiet set settings.firstBootShown false --no-switch"
|
||||
f"{env} nomarchy-state-sync --quiet set settings.firstBootShown false --no-switch"
|
||||
)
|
||||
machine.succeed("rm -f /tmp/notifications")
|
||||
machine.succeed(f"{env} hostname nomarchy-live || true")
|
||||
@@ -1810,7 +1812,7 @@
|
||||
|
||||
# Auto time-of-day theme (#79, VISION § D): simulate crossing
|
||||
# sunset and sunrise by moving the VM clock, and assert
|
||||
# `nomarchy-theme-sync auto` switches the active theme accordingly
|
||||
# `nomarchy-state-sync auto` switches the active theme accordingly
|
||||
# — day stays day (no needless rebuild), sunset flips to night,
|
||||
# a later tick is idempotent, sunrise flips back. The actual
|
||||
# `home-manager switch` is stubbed via NOMARCHY_REBUILD (that
|
||||
@@ -1819,7 +1821,7 @@
|
||||
auto-theme = pkgs.testers.runNixOSTest {
|
||||
name = "nomarchy-auto-theme";
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
environment.systemPackages = [ pkgs.nomarchy-theme-sync ];
|
||||
environment.systemPackages = [ pkgs.nomarchy-state-sync ];
|
||||
time.timeZone = "UTC"; # naive HH:MM == the clock we set
|
||||
};
|
||||
testScript = ''
|
||||
@@ -1830,7 +1832,7 @@
|
||||
# that records each invocation instead of running home-manager.
|
||||
machine.succeed("mkdir -p /root/.nomarchy")
|
||||
machine.succeed(
|
||||
"echo '{\"slug\":\"summer-day\"}' > /root/.nomarchy/theme-state.json"
|
||||
"echo '{\"slug\":\"summer-day\"}' > /root/.nomarchy/state.json"
|
||||
)
|
||||
machine.succeed(
|
||||
"printf '#!/bin/sh\\necho switch >> /root/rebuilds\\n' > /root/rebuild"
|
||||
@@ -1847,34 +1849,34 @@
|
||||
"settings.autoTheme.night summer-night",
|
||||
"settings.autoTheme.sunrise 07:00",
|
||||
"settings.autoTheme.sunset 20:00"]:
|
||||
machine.succeed(env + "nomarchy-theme-sync --quiet set " + kv + " --no-switch")
|
||||
machine.succeed(env + "nomarchy-state-sync --quiet set " + kv + " --no-switch")
|
||||
|
||||
def slug():
|
||||
return machine.succeed(env + "nomarchy-theme-sync get slug").strip()
|
||||
return machine.succeed(env + "nomarchy-state-sync get slug").strip()
|
||||
def rebuilds():
|
||||
return int(machine.succeed("wc -l < /root/rebuilds 2>/dev/null || echo 0").strip())
|
||||
|
||||
# Before sunset: already the day theme → no switch, no rebuild.
|
||||
machine.succeed("date -s '2026-01-02 19:55:00'")
|
||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
||||
machine.succeed(env + "nomarchy-state-sync auto")
|
||||
assert slug() == "summer-day", f"pre-sunset should stay day, got {slug()}"
|
||||
assert rebuilds() == 0, f"no needless rebuild, got {rebuilds()}"
|
||||
|
||||
# Cross sunset → night theme, exactly one rebuild triggered.
|
||||
machine.succeed("date -s '2026-01-02 20:05:00'")
|
||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
||||
machine.succeed(env + "nomarchy-state-sync auto")
|
||||
assert slug() == "summer-night", f"post-sunset should be night, got {slug()}"
|
||||
assert rebuilds() == 1, f"sunset should trigger one rebuild, got {rebuilds()}"
|
||||
|
||||
# Another tick still after sunset → idempotent, no extra rebuild.
|
||||
machine.succeed("date -s '2026-01-02 20:06:00'")
|
||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
||||
machine.succeed(env + "nomarchy-state-sync auto")
|
||||
assert slug() == "summer-night"
|
||||
assert rebuilds() == 1, f"idempotent tick must not rebuild, got {rebuilds()}"
|
||||
|
||||
# Cross sunrise the next morning → back to day, second rebuild.
|
||||
machine.succeed("date -s '2026-01-03 07:30:00'")
|
||||
machine.succeed(env + "nomarchy-theme-sync auto")
|
||||
machine.succeed(env + "nomarchy-state-sync auto")
|
||||
assert slug() == "summer-day", f"post-sunrise should be day, got {slug()}"
|
||||
assert rebuilds() == 2, f"sunrise should trigger a rebuild, got {rebuilds()}"
|
||||
'';
|
||||
@@ -1946,12 +1948,12 @@
|
||||
|
||||
# ─── Reference host ────────────────────────────────────────────
|
||||
# Manually wired rather than via lib.mkFlake: the repo-root
|
||||
# theme-state.json is load-bearing (live ISO + in-repo CLI dev),
|
||||
# state.json is load-bearing (live ISO + in-repo CLI dev),
|
||||
# which doesn't match mkFlake's src-layout convention.
|
||||
# Deliberately split rebuild paths:
|
||||
# system → sudo nixos-rebuild switch --flake .#nomarchy (rare)
|
||||
# desktop → home-manager switch --flake .#nomarchy (no sudo;
|
||||
# this is what `nomarchy-theme-sync apply` runs)
|
||||
# this is what `nomarchy-state-sync apply` runs)
|
||||
# Theme changes never touch the system layer, so they never need
|
||||
# root and never rebuild the world.
|
||||
nixosConfigurations.nomarchy = nixpkgs.lib.nixosSystem {
|
||||
@@ -1959,7 +1961,7 @@
|
||||
specialArgs = { inherit username; };
|
||||
modules = [
|
||||
self.nixosModules.nomarchy
|
||||
{ nomarchy.system.stateFile = ./theme-state.json; }
|
||||
{ nomarchy.system.stateFile = ./state.json; }
|
||||
./hosts/default/configuration.nix
|
||||
];
|
||||
};
|
||||
@@ -1971,7 +1973,7 @@
|
||||
{
|
||||
# The single source of truth — pure read: the file is part
|
||||
# of this flake's source.
|
||||
nomarchy.stateFile = ./theme-state.json;
|
||||
nomarchy.stateFile = ./state.json;
|
||||
home = {
|
||||
inherit username;
|
||||
homeDirectory = "/home/${username}";
|
||||
@@ -2004,7 +2006,7 @@
|
||||
useUserPackages = true;
|
||||
users.${username} = { pkgs, ... }: {
|
||||
imports = [ self.homeModules.nomarchy ];
|
||||
nomarchy.stateFile = ./theme-state.json;
|
||||
nomarchy.stateFile = ./state.json;
|
||||
|
||||
# Live-session baseline apps (ROADMAP § live-ISO baseline
|
||||
# apps, #103). Until now the
|
||||
@@ -2045,7 +2047,7 @@
|
||||
};
|
||||
};
|
||||
|
||||
# The standalone CLI that `nomarchy-theme-sync apply` runs for
|
||||
# The standalone CLI that `nomarchy-state-sync apply` runs for
|
||||
# theme switching, from the same pinned input — plus the
|
||||
# guided installer.
|
||||
environment.systemPackages = [
|
||||
|
||||
Reference in New Issue
Block a user