Commit Graph

39 Commits

Author SHA1 Message Date
0cd6212b9c tools: preview the plymouth splash unprivileged; #137 attempt 1 reverted
All checks were successful
Check / eval (push) Successful in 3m49s
Answering "how do I give you root": you don't. tools/plymouth-preview.sh
renders the splash — LUKS dialog included — into X11 windows in ~10s with
no reboot, no root and no DRM:

- unshare -rm = user + mount namespace; every mount dies with it.
- /run/plymouth is BOTH the socket dir and the compiled-in plugin path
  (`strings plymouthd` → /run/plymouth/plugins/), so we bind a writable dir
  over it AND recreate its symlinks — miss those and plymouth can't find
  x11.so, silently skips X11 and reaches for a tty.
- never pass --tty (main.c:2133 takes the X11 path only while default_tty
  is unset); /dev/dri is masked so a preview physically cannot mode-set a
  real output — which matters on a docked session (#127).
- PLY_CREATE_FAKE_MULTI_HEAD_SETUP fakes 800x600 + 640x480 heads.
- debug wants plymouth.debug=stream: — `file:` buffers and yields nothing.

What it proved immediately: with a STABLE two-head canvas the current theme
centres correctly on both heads. So the geometry is fine and #137 really is
the timing bug (positions frozen at parse time vs a canvas that resizes
when a head arrives) — recorded in the item.

And it caught my own fix: hoisting positions into layout() rendered the
background on both heads and nothing else — no logo, no bar — with NO parse
error logged. Reverted rather than shipped, because that script draws the
passphrase box on an encrypted machine. #137 carries the failure, the prime
suspect (plymouth script scope: assignment lands in `local` unless the name
already exists) and the fast edit/render loop for the next attempt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 12:42:31 +01:00
d8e1a13d50 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.
2026-07-15 11:26:59 +01:00
e6be5a5770 fix: disk picker fd0 (#112), menu Back guard (#106), offline theme (#113)
Some checks failed
Check / eval (push) Has been cancelled
- #112: list_installable_disks drops fd/loop/sr/zram/&lt;8GiB, largest-first
  so OVMF no longer offers /dev/fd0 as the default wipe target
- #106: tools/check-menu-back.py + checks.menu-back (proved to fail)
- #113: offline theme-switch contract documented (default/pinned only);
  theme-sync run_switch adds offline-oriented hint when no network

Verified: V0 flake check; installer-safety; menu-back; py_compile.
2026-07-15 10:02:19 +01:00
545e40f4d9 feat(terminal): Kitty only — drop Ghostty (#95)
Some checks failed
Check / eval (push) Has been cancelled
Ghostty's OpenGL 4.3 floor broke the Acer (HD 4000 / 4.2). Dual-terminal
support (default Ghostty + install-time Kitty fallback) was more complexity
than it was worth.

- Remove ghostty.nix / nomarchy.ghostty.enable
- Kitty always installed and themed from theme-state
- Default nomarchy.terminal = kitty; SUPER+Return, doctor, calendar,
  what-changed use kitty --class=com.nomarchy.*
- Drop install-time glxinfo probe and mesa-demos on the installer
- Docs/REQUIREMENTS no longer list OpenGL 4.3 as a terminal floor

Verified: V0 flake check; option-docs; installer-safety; template HM has
kitty, no ghostty; menu/calendar emit classed kitty launches.
2026-07-15 09:51:27 +01:00
bec826baf0 fix(docking): the undock keyword is inert, not raced; escalate to reload
All checks were successful
Check / eval (push) Successful in 3m10s
Round 7 FAILED: 5-6 consecutive unplugs on the dev box, the panel never came
back. Round 6's retry could not have worked, because round 6's diagnosis was
wrong — and so was the "phantom re-add" theory I brought to this session.

With ZERO enabled outputs — panel disabled by the dock, external gone —
Hyprland 0.55.4 accepts `hyprctl keyword monitor eDP-1,preferred,auto,1`,
prints `ok`, exits 0, and never flushes it: the rule waits for a DRM event
that is not coming. It is inert, not raced. Retrying it 25x/poll for 6 polls
bought 30s of black screen and nothing else.

What made this survive two rounds is worth more than the fix: every
`transition=undock result=ok` in the round-6 journal was Bernardo plugging
the cable back in because the screen was black. His hotplug flushed the
queued rule and the next poll took the credit. Success was indistinguishable
from the user working around the failure, so the logs confirmed whichever
story we brought to them. Ten minutes of probing on hardware killed both.

Probed live 2026-07-14 (dev box, cable out): keyword inert across 4s and 5s
in two runs; `dispatch forcerendererreload` inert too; only `hyprctl reload`
escapes — 99ms and 289ms. So: issue the keyword (enough whenever another
output is still enabled, e.g. the menu's Dock mode), and escalate to reload
only when `monitors` proves it inert. After a reload, re-assert the rule so a
config that parks the panel off cannot undo the undock, and restore per-device
keyboard layouts — a reload re-reads the config and drops the runtime
`device[<name>]:kb_layout` an external board depends on. The menu's `enable`
now proves itself against `monitors` too, instead of cheering for an exit code.

Verified V3 (partial): the fixed transition driven through a real unplug —
panel on, workspaces 1-3 home, 1.8s, `keyword=inert escalate=reload` ->
`enable=via-reload` -> `result=ok`. V2: nix flake check --no-build; docking-ux
+ dock-audio; monitor-fallback.nix; shellcheck clean on display-transition.

NOT proven, and queued as HARDWARE-QUEUE round 8: the watcher-driven path
(exec-once + baked store path = relogin required, the round-5 trap) and
repetition. The keyboard-restore path is untested — the dev box's keyboard
hangs off the dock's hub, so it leaves with the cable; round 8 adds a check
with a keyboard in the laptop. The VM harness still cannot reach any of this:
QEMU aborts Hyprland if the last active output is deleted while the DRM output
is disabled, which is the same zero-output degeneracy from the other side.

New BACKLOG #114 (PROPOSED): tuigreet ignores per-device keyboard layouts —
a VT has one keymap, so the greeter cannot honour `device[]:kb_layout`.
Bernardo hit it logging out while docked; not a regression, a design gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:28:06 +01:00
44ecc9094e fix(docking): retry the undock; a lost keyword left the panel black
Some checks failed
Check / eval (push) Has been cancelled
Round 6 passed on the plug side but unplugging the cable left the laptop
panel black until it was re-docked. The journal named it: across three
undocks, two logged result=enable-timeout and one result=ok — a race, not
a logic error.

Two halves, both needed:

  - `hyprctl keyword monitor eDP-1,preferred,auto,1` is accepted (exit 0)
    while Hyprland is still tearing down the departing external, then
    silently dropped. Re-issue it on every poll for 5s and keep `monitors`
    listing the panel — not hyprctl's exit code — as the only proof.

  - The watcher fired the undock once off the removal event and marked the
    departure handled regardless (`|| true` + awaiting_lid_open=1). Since
    known_outputs had already moved, the set-changed guard never reopened
    and nothing retried, so one lost keyword cost the panel until re-dock.
    The undock is now a queued invariant driven on the 1s tick, bounded at
    6 attempts so a genuinely unenableable panel can't pin the inhibitor.

The VM harness cannot reach this race: QEMU's headless backend aborts
Hyprland if the last active output is deleted while the DRM output is
disabled, so the test must undock before removing DP-1 and never collides
the two. Noted there, and V3 queued as round 7 — unplug 5+ times, since
the old failure only hit ~2 in 3.

Verified: V2 — nix flake check --no-build; monitor-fallback.nix exit 0 in
57s; shellcheck clean. The fix itself is V3 pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 11:35:12 +01:00
d09c11d872 fix(install): constrain keyboard selection to XKB catalog
All checks were successful
Check / eval (push) Successful in 3m17s
Make keyboard typing filter-only with Gum strict mode, validate interactive and unattended choices against the pinned XKB catalog before any disk mutation, and show resolved human-readable choices in review.

Verified: V1 flake eval, focused installer builds, shell/Python/diff checks; current-ISO KVM boot proved the packaged new flow while the pre-destructive interaction pass continued.
2026-07-13 17:07:08 +01:00
9d0abe5422 fix(install): normalize empty keyboard variants
All checks were successful
Check / eval (push) Successful in 3m18s
Treat the picker-only (none) row as an empty XKB variant at the shared input boundary and again in the template patcher. Guard the real downstream output and console keymap, and carry the sentinel through the offline installer VM.

Verified: V0 full flake evaluation plus shell/Python/Nix/diff checks; V1 installer-keyboard, installer-safety, and template-SoT builds; V2 full KVM offline LUKS+swap install and themed first boot. No V3 required.
2026-07-13 15:01:51 +01:00
2a34c7398b fix(docking): make workspace and lid transitions atomic
All checks were successful
Check / eval (push) Successful in 3m19s
Restore the old flake's external-only workspace handoff, protect closed-lid cable removal with a verified low-level logind inhibitor, and use monitor-added as the settled audio reprobe boundary.

Verified: V2 — nix flake check --no-build; docking-ux, option-docs, and template-sot checks; KVM tools/monitor-fallback.nix lifecycle test.

V3 pending: closed-lid BenQ round 4 in agent/HARDWARE-QUEUE.md.
2026-07-13 13:56:54 +01:00
7ebfab4bd4 fix(docking): make clamshell hotplug safe and discoverable
All checks were successful
Check / eval (push) Successful in 3m32s
Real dock QA exposed pointer-bound menus, an absent default rescue watcher, the unsafe zero-output panel-off path, missing keyboard/audio hotplug coverage, and unpainted new outputs.

Route Rofi to the focused output; replace panel disable with safe workspace migration; always run and reconnect the display watcher and repaint hotplugged outputs; expose a default per-device keyboard picker; handle audio card availability transitions.

Verified: V1 — nix flake check --no-build; checks.docking-ux real Home Manager artifact build; option-docs/template-sot; Nix parse, py_compile, and git diff --check. V2 unavailable: no readable KVM. V3 pending in agent/HARDWARE-QUEUE.md.
2026-07-13 11:53:01 +01:00
53e3c32852 removing the ISO eval from the gitea workflow
All checks were successful
Check / eval (push) Successful in 3m9s
2026-07-12 20:07:13 +01:00
67400b07dc fix(ci): memory-bounded eval — one output per nix process
Some checks failed
Check / eval (push) Failing after 7m59s
check.yml has been permanently red since the runner container was
capped at 2 GB (uncapped, it OOM'd the whole 4 GB VPS). Root cause,
measured with GNU time (eval-cache off): `nix flake check --no-build`
walks every output in ONE evaluator process and peaks at ~6.0 GB RSS
on this repo — the checks.* suite alone is ~15 runNixOSTests, each a
full NixOS eval, all accumulating in one heap.

New tools/ci-eval.sh: identical coverage (all checks.* drvPaths, both
nixosConfigurations toplevels, the HM activationPackage — enumerated
dynamically, no drift), one fresh nix process per output so the heap is
freed between outputs. Cold per-output peaks: worst check 0.99 GB
(hardware-toggles), nomarchy toplevel 0.78 GB, HM 0.60 GB; the one
outlier is nomarchy-live at 2.69 GB (live-ISO eval), which fits the
2 GB cap only via the container's swap allowance (docker's default
--memory-swap is 2x memory). check.yml and bump.yml both call the
script now; bump.yml also gains max-jobs=1/cores=2 (it had no limits,
so its V1 build gate could spawn parallel builders in the capped
container). ROADMAP's stale "no CI today" corrected: bump.yml landed
8fded63 on schedule 2026-07-06.

Verified: V1+ — ci-eval.sh green locally end-to-end (3m22s, tree peak
2.75 GB = nomarchy-live's process); per-output peaks measured
individually. The decisive proof is the next Actions run on the VPS
itself — watch the nomarchy-live step; if it OOMs there, the runner
needs swap allowed: --memory=2g --memory-swap=6g.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 19:39:46 +01:00
c840202018 feat(display): blackout rescue — re-enable a disabled panel on undock
Some checks failed
Check / eval (push) Failing after 1m40s
Bernardo's question: with "Laptop screen off" active, does the panel
come back when the dock is yanked? VM answer (new maintainer harness
tools/monitor-fallback.nix, softGL desktop): no — Hyprland 0.55.4
leaves the session with zero active outputs and never re-enables a
soft-disabled monitor. rescue_blackout in the display hotplug watcher
now re-enables every disabled output (immediate, retried, idempotent
keyword + toast) whenever a monitorremoved leaves nothing active;
independent of the profile auto-switch and its settings gate.

Sharp edge documented rather than hidden: the zero-output state itself
crashes Hyprland in the VM ~4/5 runs (aquamarine CBackend::dispatchIdle
ABRT within ~0.5s — faster than any userland rescue; a control run
removing the external with the panel ACTIVE survives 5/5, so the state
is the trigger, not headless removal; crash reproduced with the rescue
provably inert, so not caused by it). Rescue ships as defense-in-depth:
it recovers every surviving case, and the worst case on real DRM is
session-to-greeter on a re-lit panel instead of a black brick. Rider
fix: `grep -c .` exits 1 at count zero — the ||-guard on that pipeline
silently disabled the rescue exactly when it mattered.

Verified: hazard V2 (VM reproduces the dead state); control
discriminator 5/5; flake check green. V3 pending: real-dock
undock-while-off in HARDWARE-QUEUE (run with nothing important open;
crash on real DRM = rework the row to mirror or wait for a Hyprland
bump).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:09:17 +01:00
Bernardo Magri
073adf743d test(doctor): V2 float harness for System › Doctor
Some checks failed
Check / eval (push) Has been cancelled
Headless softGL Hyprland harness (tools/doctor-float.nix): launches
nomarchy-menu doctor, asserts class/floating/center via hyprctl, dumps
before/after screenshots. Ran boreal + summer-night — both PASS
(floating centered sheet; Ghostty keeps ~800×600). MEMORY recipe.

Verified: V2 — THEME=boreal and THEME=summer-night doctor-float green;
screenshots viewed (desktop-before vs doctor-float under both themes).
2026-07-11 10:16:34 +01:00
1e1d568947 fix(hyprland): stay_focused not stayfocused (0.55)
Some checks failed
Check / eval (push) Has been cancelled
Hyprland 0.55 renamed the effect; stayfocused 1 → invalid field type
on line 158 (polkit rules). Keep float/center/workspace current.
2026-07-10 13:38:54 +01:00
Bernardo Magri
3eeba0611d test(install): fall back to nixpkgs OVMF.fd for headless ISO tests
All checks were successful
Check / eval (push) Successful in 2m59s
2026-07-10 09:40:55 +01:00
Bernardo Magri
a95cf49ddf test(install): #74 wire matrix flags into go.sh + first-boot
Some checks failed
Check / eval (push) Has been cancelled
Generate unattended go.sh from NOMARCHY_TEST_SWAP_GB / NO_LUKS; skip
LUKS passphrase typing on first boot when cleartext.
2026-07-10 09:40:38 +01:00
Bernardo Magri
3760e39a54 test(install): #74 unattended matrix env flags (swap / no-LUKS)
Some checks failed
Check / eval (push) Has been cancelled
NOMARCHY_TEST_SWAP_GB and NOMARCHY_TEST_NO_LUKS drive go.sh for the
headless install harness. Document default / no-swap / no-LUKS matrix.
First-boot path skips LUKS typing when NO_LUKS=1.
2026-07-10 09:40:25 +01:00
Bernardo Magri
3a874dccc8 test(install): #72 installer ↔ template SoT parity check
Guard that nomarchy-install share/template ships byte-identical
flake.nix/system.nix/home.nix/theme-state.json to templates/downstream
(the single machine-seed SoT; default.nix copy list).

- tools/check-template-sot.py — share vs SoT file-set + content
- checks.template-sot — pure python + package path (theme-contrast pattern)
- BACKLOG #72 removed; JOURNAL entry

Verified (V0):
  python3 tools/check-template-sot.py <share/template> templates/downstream
  nix build .#checks.x86_64-linux.template-sot
2026-07-10 09:39:10 +01:00
Bernardo Magri
34362d6a92 fix(tools): identity audit exemptions + import hierarchy (#69 #70)
#69: audit-theme-design tags hue/CVD/ANSI-family noise on white,
vantablack, lumon, hackerman, matte-black, miasma as [identity] with a
legend — do not retune identity palettes into traffic lights.

#70: import-palettes keeps surface≠overlay when color0==color8, derives
light UI chips from base (ANSI-black trap), gentler light mantle; roles
are first-class. No bulk re-import of shipped theme JSON.

Verified: V0 (py_compile + synthetic import fixtures + audit report).
2026-07-10 09:39:10 +01:00
Bernardo Magri
14558ad296 test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s
- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
  05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
  asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
  capture-float-classes.nix harness (softGL empty); residual portal
  class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.

Verified: live-install-entry + windowrule-syntax green.
2026-07-10 09:20:15 +01:00
Bernardo Magri
f09042adbe feat(themes): #62 whole-swap CSS self-containment CI gate
Some checks failed
Check / eval (push) Has been cancelled
Add tools/check-theme-wholeswap.py and checks.theme-wholeswap: every
themes/*/waybar.css must @define-color each @name it references (raw
readFile, no palette prepend — neon-glass class), and every preset needs
preview.png + non-empty backgrounds/. README: four waybar.css themes
(drop quarantined neon-glass). Close #62.

Verified: V0 (script + negative fixture + checks.theme-wholeswap + flake check --no-build).
2026-07-10 08:29:30 +01:00
Bernardo Magri
11d6a3df0f feat(install): #61 review Source line tracks offline/online
Some checks failed
Check / eval (push) Has been cancelled
Gate the Review panel Source wording on the same OFFLINE flag as the
cache.nixos.org probe (ISO-only vs may use network binary caches).
Extend installer-safety pure contract so both phrases and $SOURCE_NET
cannot regress.

Also promote Bernardo-accepted A/B/C batch → NEXT #55–#63 and close #61.

Verified: V0 (check-install-safety + bash -n + checks.installer-safety).
2026-07-10 08:28:20 +01:00
Bernardo Magri
9aa8f250d6 feat: close #54 V2 install safety, #33 rofi scrollbar, #43 MOTD tip
All checks were successful
Check / eval (push) Successful in 3m0s
#54: permanent checks.installer-safety; test-install-safety harness with
offline compose-fail, LUKS-signature disk warn, and HM-hint FORCE paths.
#33: enable themed rofi scrollbars (generated + all whole-swap .rasi).
#43: MOTD + first-boot tip for System › Firmware (V3 LVFS stays hw-queue).

V2: compose-fail/disk-warn/hm-hint logs green offline; theme-shot
boreal+summer-night menus OK. V0 flake/installer-safety.
2026-07-09 23:02:05 +01:00
Bernardo Magri
be4efd38ea fix(waybar): #51 doctor glyph renders + #52 V2 bake asserts
All checks were successful
Check / eval (push) Successful in 3m6s
Doctor tripwire was self-hiding under waybar (PATH miss / weak glyph).
Call nomarchy-doctor by store path, strip ANSI tooltips, use 󰀨 + format
+ signal 10 (generated and whole-swap). theme-shot asserts class:bad,
guest btop.theme, and pokes RTMIN+10 before desktop shots.

V2: theme-shot green for rose-pine, summer-night, catppuccin, everforest,
vantablack; bar crops show the red alert-circle. btop TUI visual is
softGL-blocked (Ghostty) → HARDWARE-QUEUE. Closes #51 and #52.
2026-07-09 20:56:51 +01:00
Bernardo Magri
e01303851d test(checks): pure-contract guards batch (#49)
All checks were successful
Check / eval (push) Successful in 3m6s
Four new no-VM checks.* + CI py_compile expansion:
- hardware-db-modules: installer DB module names ∈ pinned
  nixos-hardware.nixosModules (a lock bump can rename a module and break
  profiled installs on just the matching DMI, invisible to any VM).
- installer-compose-lock: offline lock-composer contract on fixtures.
- installer-disko: pure Nix assert — swapSize "0"/"0G" → no @swap, "2G"
  → sized, withLuks wraps root (permanently guards the #46 install fix).
- windowrule-syntax: builds the generated hyprland.conf and fails on the
  pre-0.53 grammar / windowrulev2 keyword (guards ed7fd93).
CI py_compile now covers all tracked *.py; docs/TESTING.md §1 synced.

Implemented by a worktree agent; diff reviewed. Verified V0 (flake check)
+ V1 (each checks.x86_64-linux.* built on main, exit 0); agent's negative
tests confirm each guard fails on a regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:10:45 +01:00
Bernardo Magri
41cd350a52 feat(theme): capture Boreal picker preview + V2-verify default
All checks were successful
Check / eval (push) Successful in 3m6s
Close the honesty gap on #45. Rendered the full Boreal desktop
headlessly (tools/theme-shot.nix, softGL 1920x1080) and confirmed the
session reads as Boreal — aurora wallpaper, Waybar violet accent
(#B79BE8) workspace pill, GeistMono clock, rofi launcher violet
prompt/selection — not Tokyo Night. Added themes/boreal/preview.png
(480x270, matches the existing 21) so the picker row is no longer
plain-name.

theme-shot: park the cursor bottom-right before the desktop shot so the
frame (which doubles as the preview source) is clean; reusable for the
other two missing previews.

Verified: V0 flake check; V2 theme-shot desktop/menu shots viewed;
contrast 24x7 pass. Preview is a headless bare-desktop capture (not the
floating-terminal composition of the others) — hardware recapture noted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 18:29:27 +01:00
Bernardo Magri
351b7adb8e feat(theme): make Boreal the distro default
Some checks failed
Check / eval (push) Has been cancelled
Seed theme-state.json and the downstream template from themes/boreal.json
(including border + empty settings). Point eval/schema fallbacks at Boreal
(theme.nix preset, plymouth and live-ISO splash colors, theme-shot, sync
error examples). Docs/TESTING and RECOVERY follow the new default.

Verified: state matches boreal palette; theme-contrast all pass.
preview.png for boreal still missing (picker plain-name; V3 capture).
2026-07-09 09:57:32 +01:00
Bernardo Magri
39cfe0fb12 feat(tools): seed guest state at boot so captures render wallpapers (item 28c)
All checks were successful
Check / eval (push) Successful in 3m8s
theme-shot guests had no ~/.nomarchy, so the session's exec-once
`nomarchy-theme-sync wallpaper` died on missing state and captures
showed a bare base-color backdrop. Seed theme-state.json via tmpfiles
before greetd starts the session — the real exec-once path now paints.
(A post-boot testScript seed is too late for exec-once, and a manual
re-run fails without WAYLAND_DISPLAY: awww's client socket is
display-named.)

V2: summer-night capture renders wallpaper + inverted cream bar +
menu + placeholder dim, all coherent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 22:09:52 +01:00
Bernardo Magri
fb75c3dedf feat(tools): 1080p themed-desktop capture harness + first polish findings (item 28c)
All checks were successful
Check / eval (push) Successful in 3m2s
tools/theme-shot.nix: boots the full themed desktop headlessly
(software-GL Hyprland, THEME env picks the slug) at 1920x1080 and
QMP-dumps desktop.png + menu.png (rofi root open) — the review
artifact for slice (c). Two fixes over the 2026-06-19 spike:
home-manager.useGlobalPkgs (the HM submodule otherwise misses the
overlay) and the menu shot via hyprctl dispatch exec. Maintainer tool,
deliberately not a checks.* gate. Known gap in the header: the
wallpaper needs ~/.nomarchy seeded in the guest.

First full-res review (tokyo-night) added to the item-28 punch list:
rofi root menu is ~760px of mostly empty row space (candidate:
right-aligned SUPER+CTRL hints from keybinds.nix), full-color Papirus
menu icons vs the all-monochrome bar glyphs is a [human] identity
call, bar right-cluster group margins confirmed at full res, and the
bar's alpha(text,.5) dim states should become the @muted role now
that 28b floors guarantee its legibility. Whole-swap bar module
parity verified textually — no drift.

Verified: harness end-to-end green (screenshots read back); evals
from tools/ via the relative flake path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 20:00:28 +01:00
Bernardo Magri
44d5516191 fix(themes): P2 contrast floors retuned + gated across 9 themes (item 28b done)
All checks were successful
Check / eval (push) Successful in 3m10s
18 hex retunes, hue-preserving OKLCH minimal raises except where the
upstream palette defines a canonical value: muted lifted to >= 2.0 on
lumon/white/retro-82/everforest (solved), gruvbox (bg4 #7c6f64), nord
(#616e88, the community comment-brightening), catppuccin-latte
(overlay1 #8c8fa1); text-on-surface >= 4.5 via surface nudges
(ristretto darker, latte lighter); accentAlt >= 3.0 (latte, summer-day
darkened); warn >= 2.5 — the bar's battery tint — darkened on the
light bases (latte, rose-pine dawn, summer-day) and flexoki-light's
four status colors moved to their canonical light-mode 600 series.

check-theme-contrast.py now gates all four adopted floors (7 pairings
x 21 themes, green). good/bad stay audit-only — identity themes sit at
2.0-2.3 there by design, so no exemption mechanism was needed. The
design audit's contrast findings drop 20 -> 1 (miasma's earthy `bad`,
kept as identity). Item 28 slice (b) complete.

Verified: V0, checks.theme-contrast green, flake check green,
re-audit. Rendering is V3 -> HARDWARE-QUEUE eyeball entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:44:46 +01:00
Bernardo Magri
47c8b6f997 fix(themes): P1 palette retunes from the design audit + gated subtext floor (item 28b)
All checks were successful
Check / eval (push) Successful in 3m0s
The audit's P1s, fixed at the palette level (items 25/27 previously
patched the consumers around these):
- summer-day: surface was the TEXT color (slate #5c6a72 on cream) and
  subtext was the BASE (ratio 1.0, invisible) — now a raised cream
  surface #efe7d0 and a slate-teal subtext #6e828a in the theme's
  family.
- flexoki-light: surface/overlay/muted were all the text black and
  subtext the paper — retuned to canonical upstream Flexoki tones
  (ui #E6E4D9, #B7B5AC, tx-2 #6F6E69, base-500 #878580).
- miasma: pure-black surface under a #222 base → raised #2e2e2e.
- kanagawa: surface was sumiInk0 (darker than the bg) → upstream's
  raised sumiInk4 #2a2a37.
check-theme-contrast.py gains the adopted rule: subtext on base >= 3.0
(all 21 themes pass), so the invisible-secondary-text class cannot
ship again. Model note in the backlog: `overlay` is a mid-tone role
(catppuccin semantics), not a third bg tier.

Verified: V0 + checks.theme-contrast green (21x3); re-audit shows all
four P1 findings gone; a scratch downstream with summer-day active
builds and the new surface hex lands in the rendered ghostty config.
Remains: V3 four-theme eyeball (HARDWARE-QUEUE) and 28b's P2 floors +
exemption list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:55:44 +01:00
Bernardo Magri
abc953ea84 feat(tools): theme design audit + item 28 punch list (slice a)
All checks were successful
Check / eval (push) Successful in 3m0s
tools/audit-theme-design.py (report-only, maintainer-run): for every
themes/*.json it checks OKLCH lightness architecture (raised bg stack,
fg hierarchy), WCAG contrast beyond the guarded pairs, status-color
hue families, accent harmony, protanopia/deuteranopia
distinguishability (Machado matrices), and ANSI slot semantics the
greeter/tty now rely on. Ran it over all 21 palettes + a visual pass
over representative previews; findings curated identity-aware (lumon/
vantablack/white/hackerman exemptions are identity, not defects) into
the P1-P3 punch list now recorded in BACKLOG item 28: two palettes
with literally invisible subtext (summer-day, flexoki-light — the
item-25/27 trap at its source), two inverted bg stacks (miasma,
kanagawa), muted/warn floors, a systemic "status is never color-only"
rule, the light-theme ANSI convention decision, and bar grouping
rhythm notes.

Verified: V0 (py_compile; the tool is report-only, deliberately not a
checks.* gate until slice b adopts rules + exemptions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 18:45:56 +01:00
Bernardo Magri
56f1cc3fa9 docs(options): README tables reconciled + permanent option-docs check (item 6a)
All checks were successful
Check / eval (push) Successful in 2m59s
Slice (a) of the docs review: 9 undocumented nomarchy.* options got
README rows or inline .leaf mentions (autoTimezone, camera IR-hide,
intel.guc, amd.pstate/vaapi, package, system.stateFile, restic.paths).
The diff is now a flake check: option names are eval-walked from the
four option-declaring modules (config halves stay lazy) and compared
to the tables by tools/check-option-docs.py — undocumented options and
stale table rows both fail. Negative-tested against the pre-fix README
(reports exactly the 9 gaps). Item 6 split into slices a–d in BACKLOG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:31:20 +01:00
Bernardo Magri
fd6e5f60e9 fix(theme): generated waybar/rofi CSS invisible on on-surface palettes (item 27)
All checks were successful
Check / eval (push) Successful in 2m53s
Follow-on to the swaync fix (a643391): the generated Waybar CSS colored
the module row and #window @subtext and dim states @muted, and the
generated rofi theme put @fg text on @surface chips — invisible where
palettes use those roles as "on-surface" (flexoki-light: subtext==base,
surface==text; gruvbox: muted/base = 1.27). flexoki-light has no
whole-swap, so it ran this CSS. Generated surfaces only — the summer
whole-swaps are hand-authored and untouched.

- waybar: secondary text → alpha(@text, 0.85); dim states (inactive
  workspaces, DND bell, muted volume) → alpha(@text, 0.5).
- rofi: @dim → text+"80", @surface → text+"1a" (#RRGGBBAA fg tints).
- check-theme-contrast.py pairings/docs now cover all three generated
  surfaces; status glyphs (good/warn/bad on base, worst 2.05) are
  deliberately not held to a text ratio — raising them is palette
  design, a human decision.

Verified: V0; V1 (checks.theme-contrast green over 21 themes;
downstream-template-home builds; rendered waybar style has zero
@subtext/@muted/@surface; rendered custom.rasi shows fg-derived
tints). Honesty: NOT visually verified — V3 queued (flexoki-light
eyeball + dark-theme regression spot-check).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:08:18 +01:00
Bernardo Magri
a643391d3d fix(swaync): palette-safe pairings — text was invisible on summer-day (item 25)
All checks were successful
Check / eval (push) Successful in 2m52s
Palette roles aren't uniform: summer-day and flexoki-light use subtext
as text-ON-surface (== base) and surface as a dark chip (== text), so
swaync's @subtext-on-@base body text and @surface/@text buttons were
self-colored (caught on the Latitude).

- body/summary: @text on @base — the one text pairing that passes in
  every palette (survey: worst WCAG ratio 5.18 across all 21 themes;
  subtext/base bottomed at 1.00 twice, muted/base at 1.27).
- hover rows + widget-title buttons: alpha(@text, 0.1) tints —
  contrast by construction; subtext/surface defines dropped.
- NEW checks.theme-contrast (tools/check-theme-contrast.py): asserts
  the hex-on-hex text pairings across themes/*.json, cheap (no VM).

The audit also found the generated waybar/rofi CSS shares the bug
class, live on flexoki-light (no whole-swap) → BACKLOG item 27.

Verified: V0; V1 (checks.theme-contrast builds green, 21 themes;
rendered services.swaync.style has zero @subtext/@surface). Honesty:
NOT visually verified — V3 queued (notify-send on summer-day).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:53:01 +01:00
Bernardo Magri
6763bef73e fix(install): pre-activation, writable home.nix, keyboard/locale selection
- Pre-activation failure ("desktop pre-activation failed"): the in-chroot
  HM build hard-coded `--option substituters ""`, which only works on
  OFFLINE installs (where the ISO store is copied in). Online installs
  (now common — wifi works) had no pins and built from source. Fix: build
  the generation in the live env (its store always has the pins), nix copy
  the closure to the target, chroot only activates. In-chroot build kept
  as fallback. Uses path: (not git+file:) to sidestep libgit2 ownership.
- Read-only home.nix: templates come out of the store mode 0444 and cp
  preserves it — chmod -R u+w after staging so the user can edit it.
- Keyboard + locale selection: gum prompts (localectl layouts/variants +
  curated UTF-8 locale list) and NOMARCHY_LOCALE/KB_LAYOUT/KB_VARIANT for
  unattended. One choice flows everywhere — services.xserver.xkb (source
  of truth) + console.useXkbConfig (tty/greeter) + boot.initrd.systemd
  (so the LUKS passphrase prompt uses the layout; script initrd can't) +
  the generated home.nix's nomarchy.keyboard.* for the Hyprland session.
- test-install.sh: NOMARCHY_OVMF env override for sandboxed OVMF paths.

Verified end-to-end in QEMU: offline LUKS install completes, boots to a
themed desktop (no autogenerated-config banner), Plymouth renders the LUKS
prompt on the theme-tinted background.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 07:38:31 +01:00
Bernardo Magri
a50e9793ea feat(tools): commit the VM test harness + one-command installer regression
Promotes the throwaway harness that verified the installer into tools/:
- tools/vm/qmp.py        QMP keystroke injection / typing / quit
- tools/vm/vncshot.py    GL-safe screenshots via VNC readback (QMP
                         screendump shows "no surface" with virtio-vga-gl)
- tools/vm/gap-analysis.py  drv-graph diff that converged the offline
                         pin set; run it when offline installs build
                         from source
- tools/test-install.sh  the full offline regression: build ISO, boot
                         offline, unattended LUKS+swap install via a
                         config disk (typed long commands drop keys),
                         wait for poweroff, boot the installed disk,
                         screenshot the first boot for visual verdict

docs/TESTING.md §4 now points at the script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:05:13 +01:00
Bernardo Magri
f211ef0d09 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>
2026-06-10 10:59:13 +01:00