From 49e0061dbbf3c0556af632d2f68e72af0d467b17 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Fri, 10 Jul 2026 22:04:38 +0100 Subject: [PATCH] =?UTF-8?q?test(auto-theme):=20#79=20V2=20=E2=80=94=20simu?= =?UTF-8?q?late=20sunset=20in=20a=20VM=20(was=20mis-framed=20V3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bernardo rightly pushed back on deferring #79 to a real session: a simulated sunset IS VM-testable. Add checks.auto-theme, a runNixOSTest that installs nomarchy-theme-sync, points NOMARCHY_DEFAULT_THEMES at the presets, stubs the rebuild via NOMARCHY_REBUILD (a marker), configures the day/night pair + schedule, then WALKS THE VM CLOCK across the boundary: 19:55 -> 20:05 -> 20:06 -> next-day 07:30, asserting the active theme goes summer-day -> night (1 rebuild) -> night (idempotent, still 1) -> day (2 rebuilds). This isolates the auto decision + apply + rebuild-trigger; the actual home-manager switch is stubbed (that generic apply path is exercised by every manual theme change). The only on-hardware residual is the user timer firing on its OnCalendar schedule, which systemd-analyze validates. #79 drops its [blocked:hw] tag — it's V2-verified. ROADMAP updated; lesson recorded in MEMORY (don't default timer/session features to V3: stub the generic step, simulate the clock). Verification: V2 PASS (nix build .#checks.x86_64-linux.auto-theme, fresh, exit 0); V0 flake check green. Co-Authored-By: Claude Opus 4.8 --- agent/BACKLOG.md | 17 ------------ agent/JOURNAL.md | 19 +++++++++++++ agent/MEMORY.md | 10 +++++++ docs/ROADMAP.md | 9 ++++-- flake.nix | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 19 deletions(-) diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index fc558d3..3a76e5f 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -28,23 +28,6 @@ in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in ## NEXT -### 79. Auto time-of-day theme pair — all agent work shipped `[blocked:hw]` -Auto light/dark theme switch through the existing engine, one pipeline -(`VISION § D`). All 3 slices shipped 2026-07-10; design record in ROADMAP. -Only the real-session check remains (needs a Nomarchy machine, like #76): -- **CLI** — `nomarchy-theme-sync auto` (idempotent day/night apply; - `--which`/`--force`/`--no-switch`); `cmd_apply` core → `apply_named`. -- **Timer** — `modules/home/autotheme.nix`: user service+timer, install - gated on `settings.autoTheme.enable`, `OnStartupSec=1min` + - `OnCalendar=*:0/15` + `Persistent`. -- **Menu** — Look & Feel › Auto theme: toggle, pick day/night, set times - (enable rebuilds to install the timer + `auto --force`; disable is - instant `--no-switch`; day/night/times live `--no-switch`). - -**V3 remaining:** on a real Nomarchy session — enable from the menu, confirm -the timer switches day↔night across the sunset/sunrise boundary (and the -live `home-manager switch` from the timer succeeds). - ### 76. Hibernation default — all agent work shipped `[blocked:hw]` Hibernation + zram on by default (product intent, Bernardo 2026-07-10). **All V0/V1/V2 slices done** (2026-07-10); only the V3 check remains — on a diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index 6a26628..18035e0 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -17,6 +17,25 @@ Template: --- +## 2026-07-10 — #79 V2: checks.auto-theme (simulated sunset) — was mis-framed V3 +- **Task:** Bernardo challenged the "V3 real-session" framing — a simulated + sunset IS VM-testable. He's right; I'd conflated "needs a real desktop" + with "needs the generic home-manager switch." +- **Did:** Added `checks.auto-theme` runNixOSTest: installs nomarchy-theme-sync, + points `NOMARCHY_DEFAULT_THEMES` at presets, stubs the switch via + `NOMARCHY_REBUILD` (a marker), configures the pair+schedule, then **walks + the VM clock** 19:55→20:05→20:06→next-day 07:30 asserting slug goes + summer-day → night (1 rebuild) → night (idempotent, still 1) → day (2). The + self-gating I relied on: presets via env, wallpaper/notify no-op headless, + run_switch honors NOMARCHY_REBUILD + waybar nudge is check=False. +- **Verified:** **V2 PASS** — `nix build .#checks…auto-theme` fresh, exit 0. + Dropped #79's `[blocked:hw]`; it's done (V2). Updated ROADMAP. +- **Lesson:** don't default "timer/session feature" to V3 — stub only the + generic step (the real HM switch, covered elsewhere) and simulate the rest + (clock) headlessly. Only the literal timer-fires-on-OnCalendar is on-HW, + and its schedule is systemd-analyze-validated. +- **Next suggestion:** NEXT head #76 `[blocked:hw]`; else QA / PROPOSED. + ## 2026-07-10 — #79 slice 3: Look & Feel › Auto theme menu (#79 complete) - **Task:** BACKLOG #79 slice 3 — the menu; completes the feature. - **Did:** `rofi.nix` — "Auto theme (on/off)" row in Look & Feel + an diff --git a/agent/MEMORY.md b/agent/MEMORY.md index 8e3567a..2b5c9cb 100644 --- a/agent/MEMORY.md +++ b/agent/MEMORY.md @@ -25,6 +25,16 @@ iteration would otherwise rediscover. - In VM tests `pgrep -f PATTERN` can match the test backdoor's own `bash -c` wrapper (the pattern is in its cmdline) — use `pgrep -x` or a `[t]uigreet`-style bracket pattern. +- **Don't default a "timer/session" feature to V3 — most of it is VM-testable.** + A scheduled/session behaviour usually decomposes into a *generic* step + already covered elsewhere (e.g. `home-manager switch`, exercised by every + theme apply) and a *specific* decision (which theme, when). Stub the generic + step (`NOMARCHY_REBUILD=` for theme-sync) and **simulate time by + moving the VM clock** (`date -s`, `timedatectl set-ntp false`, `time.timeZone + = "UTC"`), then assert the decision + state change headlessly. `checks.auto-theme` + does exactly this for #79's sunset/sunrise. Only the literal + timer-fires-on-`OnCalendar` is truly on-hardware, and `systemd-analyze + calendar` validates that schedule. (I first mis-framed #79 as V3 — it's V2.) - **`writeShellScriptBin` scripts run `set -euo pipefail`** (nomarchy-doctor, the menu, lifecycle CLIs). So a **no-match `grep` inside `$(…)`** (grep exits 1 → command-sub fails → abort) and a **standalone `cond && action`** (false diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 07a8c6b..f4bc57a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -298,8 +298,13 @@ Design/decision records and a running log of shipped work (items marked reuses it verbatim. Periodic (not exact sunrise/sunset timers) for robustness to suspend/DST; the idempotent `auto` keeps ticks cheap. Enable rebuilds once (installs the timer + `auto --force`); disable is instant - (the flag flips and `auto` self-gates). Live timer→switch is a real-session - (V3) check. + (the flag flips and `auto` self-gates). **V2:** `checks.auto-theme` moves + the VM clock across a configured sunset/sunrise and asserts the theme flips + day→night→day with the right rebuild count (idempotent between) — the + `home-manager switch` itself is stubbed via `NOMARCHY_REBUILD` (that + generic apply path is exercised by every manual theme change). The only + on-hardware residual is the live user timer firing on its `OnCalendar` + schedule (the schedule is `systemd-analyze`-validated). - **Nicer shell out of the box:** ✓ zsh is the default login shell, with a starship prompt themed from the JSON, autosuggestions + syntax highlighting, and modern-CLI ergonomics — `cat`→bat (theme "ansi", so it diff --git a/flake.nix b/flake.nix index ce53222..619f246 100644 --- a/flake.nix +++ b/flake.nix @@ -752,6 +752,78 @@ ''; }; + # 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 + # — 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 + # generic apply path is exercised by every manual theme change); + # this isolates the auto decision + apply + rebuild-trigger. + auto-theme = pkgs.testers.runNixOSTest { + name = "nomarchy-auto-theme"; + nodes.machine = { pkgs, ... }: { + environment.systemPackages = [ pkgs.nomarchy-theme-sync ]; + time.timeZone = "UTC"; # naive HH:MM == the clock we set + }; + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.succeed("timedatectl set-ntp false || true") + + # Writable flake state (start on the day theme) + a rebuild stub + # 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" + ) + machine.succeed( + "printf '#!/bin/sh\\necho switch >> /root/rebuilds\\n' > /root/rebuild" + " && chmod +x /root/rebuild" + ) + + env = ("NOMARCHY_PATH=/root/.nomarchy " + "NOMARCHY_DEFAULT_THEMES=${./themes} " + "NOMARCHY_REBUILD=/root/rebuild ") + + # Pair + schedule (setup writes only, no rebuild). + for kv in ["settings.autoTheme.enable true", + "settings.autoTheme.day summer-day", + "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") + + def slug(): + return machine.succeed(env + "nomarchy-theme-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") + 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") + 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") + 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") + assert slug() == "summer-day", f"post-sunrise should be day, got {slug()}" + assert rebuilds() == 2, f"sunrise should trigger a rebuild, got {rebuilds()}" + ''; + }; + # Snapshot GUI canary: btrfs-assistant crashes in libbtrfsutil's # UNPRIVILEGED subvolume iteration (btrfs-progs 6.17.1, fixed # upstream after 6.17.1) but runs fine as root — which is how the