feat(autotheme): fire at the configured sunrise/sunset, not a 15-min poll
All checks were successful
Check / eval (push) Successful in 4m14s
All checks were successful
Check / eval (push) Successful in 4m14s
The timer's OnCalendar now bakes the configured times (falling back like _hhmm_to_min on a hand-edited state), so the switch lands at the configured minute instead of up to 15 minutes late. The original poll's robustness case is covered without it: Persistent catches transitions missed while powered off, systemd fires elapsed calendar timers on resume, and OnStartupSec settles the theme after login. Baked times mean a time edit must rebuild: the menu's Sunrise/Sunset writes now run `auto --force` when enabled (one rebuild re-bakes the timer and lands on the right theme, same as the enable flow). Day and night slugs stay live reads — but with no poll to catch them up, editing the slot currently on screen applies immediately via a plain `auto`. New eval check `auto-theme-timer` asserts the bake state-bridges-style: configured times reach OnCalendar, garbage falls back to 07:00/20:00, disabled installs no unit. Docs + hardware-queue entry updated to the new semantics. V2: checks.auto-theme (KVM VM, passed) covers the auto engine the timer ticks; checks.auto-theme-timer (eval, passed) covers the unit; OnCalendar strings systemd-analyze-validated; the rebuilt menu script passes bash -n with the new branches in place. V3 pending: live timer firing at the exact configured minute on hardware (existing "Auto-theme pair flip" queue entry, refreshed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,37 @@
|
||||
# Auto time-of-day theme switch (BACKLOG #79, VISION § D). A user timer
|
||||
# periodically runs `nomarchy-state-sync auto`, which reads
|
||||
# settings.autoTheme = { enable, day, night, sunrise, sunset } from the
|
||||
# state file and applies the day or night preset for the current clock —
|
||||
# through the SAME one engine as a manual `apply` (no second pipeline).
|
||||
# runs `nomarchy-state-sync auto` at the configured sunrise and sunset,
|
||||
# which reads settings.autoTheme = { enable, day, night, sunrise, sunset }
|
||||
# from the state file and applies the day or night preset for the current
|
||||
# clock — through the SAME one engine as a manual `apply` (no second
|
||||
# pipeline).
|
||||
#
|
||||
# The command self-gates (no-op when disabled) and is idempotent (it only
|
||||
# rebuilds when the active theme actually needs to change), so the timer
|
||||
# can tick freely. Install is gated on the state flag (like nomarchy.updates
|
||||
# gates on its enable), so a machine not using the feature carries no timer;
|
||||
# enabling it from the menu (slice 3) writes the flag + rebuilds, which is
|
||||
# what brings the timer into being.
|
||||
# The trigger times are BAKED into OnCalendar at rebuild (originally this
|
||||
# was a 15-min poll; exact times won on wasted wakeups — Bernardo
|
||||
# 2026-07-18), so a time edit must rebuild: the menu's Sunrise/Sunset
|
||||
# writes run `auto --force`, whose apply is that rebuild. Missed
|
||||
# transitions are covered without polling: Persistent=true catches ones
|
||||
# that pass while powered off, systemd fires elapsed OnCalendar timers on
|
||||
# resume from suspend, and OnStartupSec settles the theme just after
|
||||
# login. The command self-gates (no-op when disabled) and is idempotent
|
||||
# (it only rebuilds when the active theme actually needs to change), so
|
||||
# every extra firing is cheap.
|
||||
#
|
||||
# Install is gated on the state flag (like nomarchy.updates gates on its
|
||||
# enable), so a machine not using the feature carries no timer; enabling
|
||||
# it from the menu (slice 3) writes the flag + rebuilds, which is what
|
||||
# brings the timer into being.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf (config.nomarchy.settings.autoTheme.enable or false) {
|
||||
let
|
||||
at = config.nomarchy.settings.autoTheme or { };
|
||||
# "HH:MM" or the fallback — mirrors _hhmm_to_min in nomarchy-state-sync,
|
||||
# so a hand-edited state can't bake an OnCalendar systemd rejects.
|
||||
hhmm = v: fallback:
|
||||
if builtins.isString v
|
||||
&& builtins.match "([01][0-9]|2[0-3]):[0-5][0-9]" v != null
|
||||
then v else fallback;
|
||||
in
|
||||
lib.mkIf (at.enable or false) {
|
||||
systemd.user.services.nomarchy-auto-theme = {
|
||||
Unit.Description = "Apply the day/night theme for the current time";
|
||||
Service = {
|
||||
@@ -25,10 +44,13 @@ lib.mkIf (config.nomarchy.settings.autoTheme.enable or false) {
|
||||
};
|
||||
|
||||
systemd.user.timers.nomarchy-auto-theme = {
|
||||
Unit.Description = "Periodic day/night theme check";
|
||||
Unit.Description = "Day/night theme switch at sunrise and sunset";
|
||||
Timer = {
|
||||
OnStartupSec = "1min"; # settle on the right theme shortly after login
|
||||
OnCalendar = "*:0/15"; # re-check every 15 min; auto no-ops if unchanged
|
||||
OnCalendar = [
|
||||
"*-*-* ${hhmm (at.sunrise or null) "07:00"}:00" # sunrise → day
|
||||
"*-*-* ${hhmm (at.sunset or null) "20:00"}:00" # sunset → night
|
||||
];
|
||||
Persistent = true; # catch a transition missed while powered off
|
||||
};
|
||||
Install.WantedBy = [ "timers.target" ];
|
||||
|
||||
Reference in New Issue
Block a user