Go to file
Krzysztof kuhy Rudnicki c98055f8a2 Migrate the alarm to gatelock v0.2.0 and give it an escape hatch
Per-output locking: the alarm now paints one full challenge per live
monitor instead of one window on the primary. `_ViewGroup` presents the N
copies as one -- every label update fans out, and because all entries share
a single root-mastered StringVar, answering on whichever screen you are
actually looking at dismisses the alarm everywhere.

The escape hatch is the reason this repo could not stay unmigrated. The
alarm was the only locker with no way out but four correct rounds against a
30-second refresh, which is a lot to ask of someone who has just woken up
and nothing at all to fall back on if the alarm misfires at the wrong hour.
It is deliberately exactly as hard as sick mode: hidden for the first five
minutes so it is never the easy first move, gone entirely once a rolling
budget is spent, and costing a written justification plus a wait that
doubles with each recent use. It grants no workout skip.

`EscapeHatch` owns the whole flow so open and close stay symmetric. That
symmetry is the fix for a real bug: `_open_escape` used to pack_forget the
challenge widgets and cancel only flipped a bool, so "Back to the alarm"
left a dead form on a blank screen, leaked the dialog frame, and made the
next open sweep the dialog instead of the challenge. pack_forget does not
remember its options, so the challenge now lives in one `challenge_frame`
that is forgotten and re-packed as a unit.

Tests: the single-output default made every "on every monitor" assertion
degenerate into 1 == 1, and a shared MagicMock return_value made N surfaces
N references to one widget -- a loop cropped to containers[:1] passed the
whole suite. A `dual_output` fixture plus distinct-identity mocks now cover
all five fan-out loops; each was mutation-checked by cropping it and
confirming the suite goes red. The suite also no longer reads the real
escape history or signs with the production HMAC key.

326 tests, 100% branch coverage, pre-commit clean, no lint suppressions.
2026-07-25 15:35:44 +02:00
.github/workflows Extract wake_alarm from testsAndMisc as a standalone repo 2026-06-22 12:31:40 +02:00
docs/explanations Add change explanation: phone-driven-alarm-timer 2026-07-20 09:17:12 +02:00
phone_app phone_app: warm-tint the unified-design-system palette 2026-07-22 20:10:27 +02:00
scripts Add CI-mirror pre-push gate + requirements-sync check 2026-07-11 17:51:16 +02:00
wake_alarm Migrate the alarm to gatelock v0.2.0 and give it an escape hatch 2026-07-25 15:35:44 +02:00
.gitignore Add CI-mirror pre-push gate + requirements-sync check 2026-07-11 17:51:16 +02:00
.pre-commit-config.yaml Bump gatelock pin to released v0.1.1, regenerate launcher icon 2026-07-21 21:18:02 +02:00
CLAUDE.md Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
DESIGN_AUDIT_TODO.md Add a design-rules audit against the new safe-design-rules checklist 2026-07-21 15:25:20 +02:00
install.sh Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
MIGRATION.md MIGRATION.md: correct the pinned crdt_sync_dart tag to v0.2.0 2026-07-20 08:59:00 +02:00
pyproject.toml Bump gatelock pin to released v0.1.1, regenerate launcher icon 2026-07-21 21:18:02 +02:00
README.md Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
requirements.txt Bump gatelock pin to released v0.1.1, regenerate launcher icon 2026-07-21 21:18:02 +02:00
shutdown-wrapper.sh Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
sleep-hook.sh Extract wake_alarm from testsAndMisc as a standalone repo 2026-06-22 12:31:40 +02:00
wake-alarm-fans.sh Extract wake_alarm from testsAndMisc as a standalone repo 2026-06-22 12:31:40 +02:00
wake-alarm-trigger.service Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
wake-alarm-trigger.timer Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00
wake-alarm.service Fire the alarm at the phone's time via a per-minute timer 2026-07-19 20:21:17 +02:00

wake_alarm

A phone-driven wake alarm for an always-on PC. The alarm time is whatever the companion phone app last synced (via the shared crdt_sync transport over a private GitHub repo); a per-minute systemd user timer fires at exactly that time, showing a fullscreen, challenge-dismissed alarm with fan ramp and optional TP-Link Tapo P110 smart-plug control.

Change the alarm on the phone at any hour and the PC follows within ~5 minutes. If the PC does happen to be powered down, a shutdown wrapper hibernates with rtcwake set to the same synced time, as a fallback.

Install

bash install.sh

This installs the package + dependencies into system Python's user site-packages (the systemd units run /usr/bin/python directly, not a venv — see CLAUDE.md), installs and enables the per-minute trigger timer, the systemd-sleep resume hook, the shutdown wrapper that hibernates with rtcwake as a fallback, the fan-ramp script, and (optionally) python-kasa for smart-plug control.

Reading the phone's alarm needs a GitHub token with repo scope, at ~/.config/wake_alarm/sync.json as {"token": "..."}. If that file is absent, a sibling app's ~/.config/{diet_guard,screen_locker}/sync_token is used instead, so one token covers every app syncing through kuhyx/syncs.

Usage

python -m wake_alarm._alarm --demo     # test the alarm window (safe, closeable)
python -m wake_alarm._sync --show      # what alarm time the PC resolves
python -m wake_alarm._trigger --dry-run  # what the next tick would do
journalctl --user -fu wake-alarm-trigger.service  # watch it live

The alarm fires automatically once installed; no manual invocation is needed in normal operation.

Development

python -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/pre-commit install && .venv/bin/pre-commit install --hook-type pre-push
.venv/bin/python -m pytest wake_alarm/tests/ --cov=wake_alarm --cov-branch --cov-fail-under=100

See CLAUDE.md for scheduling details, the hibernate/rtcwake mechanism, the cross-repo workout_log.json read, and production deployment gotchas.