test(state): #133 — pin the #107 legacy-name shim (and its fail-closed gate)
All checks were successful
Check / eval (push) Successful in 4m9s

mkFlake prefers state.json and still accepts the legacy theme-state.json,
because a machine only migrates on its next menu write. Nothing evaluated a
checkout carrying ONLY the legacy name, so the shim could rot with every
check green — breaking rebuilds for precisely the users who have not opened
the menu since #107, i.e. the ones least likely to see it coming.

checks.state-legacy-name evaluates two fixtures: legacy-only must resolve,
and no-state-at-all must still throw. The gate is half the shim's contract
(it turns "no state file" into one readable error instead of a readFile
stack from inside the module system) and was equally unpinned.

Cheap by construction: mkFlake wraps its return in `builtins.seq
_themeState`, so forcing the attrset forces the state read and nothing else
— no module system, no home.nix. Hence a one-file fixture and no measurable
cost (flake check still ~35s). That file is a SYMLINK to the shipped
template's state.json: a copy would drift and the check would quietly start
testing a fossil. The fixture also asserts it has no state.json, or it would
pass while testing nothing.

Both assertions proven by breaking lib.nix — deleting the fallback, then
defeating the gate — and watching each fail by name. Delete this check
together with the shim and the nomarchy-theme-sync alias.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 14:04:16 +01:00
parent 3cc644edd9
commit a6f86cf575
5 changed files with 67 additions and 12 deletions

View File

@@ -153,18 +153,6 @@ whether Nomarchy holds itself higher — darker label, or a darker accent mix
for light palettes — and note `tools/check-theme-contrast.py` covers palette
pairings only, never GTK widget surfaces. `[human]` for the aesthetic call.
### 133. No check pins the #107 legacy-name compat shim
`lib.mkFlake`, doctor and lifecycle all still accept `theme-state.json`, and
`nomarchy-state-sync` migrates it on write — but nothing in `checks.*`
evaluates a checkout that has *only* the legacy name, so the shim can rot
silently while every check stays green. It is load-bearing until every
existing machine has taken one menu write. Add a cheap eval check: a fixture
dir with `theme-state.json` only → `mkFlake` resolves it; plus the write-side
migration (state.json created + git-staged, legacy `git rm`'d). Proven by
hand 2026-07-15 (drvPaths identical across the rename) — this just keeps it
proven. Delete together with the shim + the `nomarchy-theme-sync` alias.
### 143. Rofi keeps the selected *row number* when the search changes, so the highlight lands on an unrelated entry
Bernardo 2026-07-16, and his repro is exact: open the app menu, type `ca`, move

View File

@@ -0,0 +1 @@
../../../templates/downstream/state.json

View File

@@ -0,0 +1,5 @@
Deliberately empty: a checkout with **neither** `state.json` nor the legacy
`theme-state.json`. `checks.state-legacy-name` asserts `mkFlake` fails closed
here — the fail-closed gate in `lib.nix` is what turns "you have no state file"
into one readable error instead of a raw `readFile` stack from deep inside the
module system.

View File

@@ -555,6 +555,22 @@ Design/decision records and a running log of shipped work (items marked
`%26`, `%3F`, `%23`, `caf%C3%A9`. **Unverified on purpose:** whether each
vendor's page fills the box or auto-submits — testing that means posting into
Bernardo's own accounts.
- ✓ **The #107 compat shim is pinned (#133, 2026-07-16):** `mkFlake` prefers
`state.json` and still accepts the legacy `theme-state.json`, because a
machine only migrates on its next menu write — but nothing evaluated a
checkout carrying **only** the legacy name, so the shim could rot with every
check green, breaking rebuilds for exactly the users who have not touched the
menu since #107 (i.e. the least likely to notice). `checks.state-legacy-name`
now evaluates two fixtures: legacy-only must resolve, and **no state at all
must still throw** — the fail-closed gate is half the shim's contract and was
equally unpinned. Cheap by construction: `mkFlake` wraps its return in
`builtins.seq _themeState`, so forcing the attrset forces the state read and
nothing else — no module system, no `home.nix` — which is why the fixture is
one file and the check costs no measurable time. That file is a **symlink** to
the shipped template's `state.json`: a copy would drift and the check would
quietly start testing a fossil. Both assertions were proven by breaking
`lib.nix` — deleting the fallback, then defeating the gate — and watching each
fail by name. Delete it with the shim and the `nomarchy-theme-sync` alias.
- ✓ **The VM's verdict on menu geometry is worthless — and so is measuring
(#132, 2026-07-16):** the guest draws the picker with icons at ~¼ of
`ui.iconSize` and rows short enough to clip a 6-row root, so screenshots are

View File

@@ -1511,6 +1511,51 @@
in
assert ok; pkgs.runCommand "nomarchy-rofi-text-width" { } "touch $out";
# The #107 rename shipped a compat shim: mkFlake prefers state.json
# and still accepts the legacy theme-state.json (lib.nix), because a
# machine only migrates on its next menu write. Nothing evaluated a
# checkout carrying ONLY the legacy name, so the shim could rot with
# every check green — and it is load-bearing for exactly the users who
# have not touched the menu since #107, i.e. the ones least likely to
# notice before their rebuild breaks.
#
# Cheap by construction: mkFlake wraps its return in
# `builtins.seq _themeState`, so forcing the attrset forces the state
# read and nothing else — no module system, no home.nix. Which is also
# why the fixture needs only that one file.
#
# Delete this check together with the shim and the
# `nomarchy-theme-sync` alias.
state-legacy-name =
let
inherit (nixpkgs) lib;
expect = cond: msg: lib.assertMsg cond "state-legacy-name: ${msg}";
legacy = ./checks/fixtures/legacy-state;
none = ./checks/fixtures/no-state;
evalsOk = src:
(builtins.tryEval
(builtins.isAttrs (self.lib.mkFlake { inherit src; username = "fixture"; })))
.success;
ok =
# Fixture drift: if someone adds a state.json here the check
# would still pass, while testing nothing at all.
expect (builtins.pathExists (legacy + "/theme-state.json"))
"fixture drift: checks/fixtures/legacy-state has no theme-state.json"
&& expect (! builtins.pathExists (legacy + "/state.json"))
("fixture drift: checks/fixtures/legacy-state also has a state.json, "
+ "so it no longer exercises the legacy-only path")
&& expect (evalsOk legacy)
("mkFlake cannot evaluate a checkout carrying only theme-state.json "
+ "the #107 compat shim is broken and every pre-#107 machine "
+ "fails to rebuild")
# The other half of the shim's contract: no state at all must
# still fail, and fail here rather than deep in a module.
&& expect (! evalsOk none)
("mkFlake accepted a checkout with no state file at all the "
+ "fail-closed gate in lib.nix is gone");
in
assert ok; pkgs.runCommand "nomarchy-state-legacy-name" { } "touch $out";
# Docking artifact guard: default/no-profile Home Manager output
# must still launch both hotplug watchers, put Rofi on the focused
# monitor, expose the manual keyboard picker, and never ship the