Go to file
Krzysztof kuhy Rudnicki 758b2976b8 Gate the free-day rules against the Python package, and fix two picker paths
showDatePicker asserts that initialDate satisfies selectableDayPredicate,
so with today already free the picker asserted in debug and, in release,
pre-selected a disabled day. The cursor now starts on the first non-free
day. And the reason dialog had no Cancel: a barrier tap marked the day
anyway. Both covered by widget tests; the first fails without the fix.

The cross-language check that proved the Dart port was a one-off probe,
deleted after it ran. It is now scripts/free_days_interop/check.sh, a
pre-commit hook on both inputs: Dart writes one pool through every policy
branch, Python replays the same edits into a scratch pool, and every
semantic reading has to agree. The Python package is the oracle -- Dart's
own count compared to what Python reads from Dart's file cannot see a
drift in what Dart writes. Proved by dropping the sticky consumed flag
from release: remaining dart=33 python=32.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019pBgaXSJKyY3DarYpDatDF
2026-09-11 13:53:56 +02:00
.github/workflows Bring Flutter, flutter_secure_storage and ruff up to latest stable 2026-09-11 12:37:33 +02:00
bottles Pin freedays v0.2.0 2026-09-07 15:58:35 +02:00
docs/explanations Add change explanation: phone-driven-alarm-timer 2026-07-20 09:17:12 +02:00
phone_app Gate the free-day rules against the Python package, and fix two picker paths 2026-09-11 13:53:56 +02:00
prompts Add the removal marker to every TODO file 2026-08-24 15:56:24 +02:00
scripts Gate the free-day rules against the Python package, and fix two picker paths 2026-09-11 13:53:56 +02:00
wake_alarm Do not ring at all on a shared free day 2026-09-07 15:28:54 +02:00
.fvmrc Bring Flutter, flutter_secure_storage and ruff up to latest stable 2026-09-11 12:37:33 +02:00
.gitignore Bring Flutter, flutter_secure_storage and ruff up to latest stable 2026-09-11 12:37:33 +02:00
.pre-commit-config.yaml Gate the free-day rules against the Python package, and fix two picker paths 2026-09-11 13:53:56 +02:00
.versioncode-offset Read the versionCode offset from the repo root, not the app dir 2026-08-31 11:55:45 +02:00
AGENTS.md Add the free-day picker, and stop the phone ringing on a free day 2026-09-11 13:45:42 +02:00
CLAUDE.md Phase 0: fix dependency pin mismatch, stale docs, add speaker research 2026-08-26 22:53:01 +02:00
DOCS-free-days.md Gate the free-day rules against the Python package, and fix two picker paths 2026-09-11 13:53:56 +02:00
DOCS-phone-alarm.md Sync the NFC tags and home location so a reinstall gets them back 2026-09-11 12:34:50 +02:00
DOCS-vmbox.md Retire the PC's waking role; keep the trigger 2026-08-30 15:24:02 +02:00
install.sh Don't let the retired-cleanup abort a working install 2026-08-30 15:38:30 +02:00
pyproject.toml Pin freedays v0.2.0 2026-09-07 15:58:35 +02:00
README.md Retire the PC's waking role; keep the trigger 2026-08-30 15:24:02 +02:00
requirements.txt Bring Flutter, flutter_secure_storage and ruff up to latest stable 2026-09-11 12:37:33 +02:00
TODO-file-length-250.md Add the removal marker to every TODO file 2026-08-24 15:56:24 +02:00
TODO-hardening-continue.md Retire the PC's waking role; keep the trigger 2026-08-30 15:24:02 +02:00
uninstall.sh Retire the PC's waking role; keep the trigger 2026-08-30 15:24:02 +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

The phone is the alarm. phone_app/ arms and rings it, and getting past it means walking between two NFC tags and answering three questions. This repo's Python half no longer wakes anyone: all it does is read the alarm time the phone published and start the morning routine (the workout lock) at that time.

The dismiss sequence

Strict, ordered, and latched — every stage required, and a stage once passed never reverts:

tag 1  ->  25 steps + 2 min  ->  tag 2  ->  3 challenges

The two tags must be distinct and live in different rooms; the same id in both slots is refused at registration. Answers are cumulative, so a wrong one costs the attempt and nothing else.

One fallback, deliberately narrow. After 5 minutes of ringing — or immediately if NFC is off or fewer than two tags are registered — the two tag stages are skipped and the challenge count rises to 6. The walk is never skipped. Without a hatch of some kind, a dead tag at 06:00 leaves an alarm that cannot be switched off at all.

Details, and the things that are a hard crash or a silent no-ring: DOCS-phone-alarm.md.

The PC half

A per-minute systemd user timer (wake-alarm-trigger.timer) reads the alarm the phone published through the shared crdt_sync transport, and at that time starts morning-routine.service, which runs the workout lock. It records a once-per-day latch so the rest of the catch-up window does not restart it.

That is all it does. There is no alarm window, no audio, no smart plug, no rtcwake, and no shutdown wrapper.

Install

bash install.sh      # trigger timer
bash uninstall.sh    # removes it, and any retired waking machinery

install.sh installs the package into system Python's user site-packages (the units run /usr/bin/python directly, not a venv — see CLAUDE.md) and enables the timer. It also clears the retired pieces an older install left behind, notably /usr/local/bin/shutdown, which is root-owned and would otherwise keep turning shutdown now into a hibernate forever.

Reading the phone's alarm uses the shared ~/.config/crdt-sync/firebase.json. The GitHub mirror (fallback only) needs a repo-scoped token at ~/.config/wake_alarm/sync.json as {"token": "..."}.

Usage

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

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
cd phone_app && flutter test && flutter analyze

The dev venv lacks gatelock, so anything importing _state (including _trigger) needs /usr/bin/python3, not .venv/bin/python.

See CLAUDE.md for the sync resolution chain, the cross-repo coupling to morning_routine, and production deployment gotchas.