Files
Nomarchy/modules/home/autotheme.nix
Bernardo Magri f658b0087e
All checks were successful
Check / eval (push) Successful in 4m14s
feat(autotheme): fire at the configured sunrise/sunset, not a 15-min poll
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>
2026-07-18 22:31:02 +01:00

59 lines
2.7 KiB
Nix

# Auto time-of-day theme switch (BACKLOG #79, VISION § D). A user timer
# 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 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, ... }:
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 = {
Type = "oneshot";
# The rebuild (`home-manager switch`) and its tools resolve from the
# system + per-user profiles — same PATH shape nomarchy-updates uses.
Environment = "PATH=/run/current-system/sw/bin:/etc/profiles/per-user/${config.home.username}/bin";
ExecStart = "${pkgs.nomarchy-state-sync}/bin/nomarchy-state-sync auto";
};
};
systemd.user.timers.nomarchy-auto-theme = {
Unit.Description = "Day/night theme switch at sunrise and sunset";
Timer = {
OnStartupSec = "1min"; # settle on the right theme shortly after login
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" ];
};
}