The PC shut down mid-session. This is the entry point for picking the work back up without the transcript: what is done, what is left in what order, the decisions already settled, and the traps that actually cost time. Points at ~/.claude/plans/wake-alarm-still-does-witty-toucan.md for detail rather than duplicating it, so the two cannot drift apart. Every factual claim in it was re-checked against the tree, not written from memory -- including the one that matters most: _reactivate.py is finished and tested but is called by NOTHING (it appears only in a _startup.py docstring), so item 6 is not actually fixed at runtime yet. That is step 1 for the next session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EckWmyfetYq3Mi8MAnUudh
2.2 KiB
wake_alarm architecture
Reference for the module layout after the 2026-08-26 split, and the two failure modes that motivated it.
Where behaviour lives
_alarm.py owns the window and the dismiss flow and nothing else. When looking
for behaviour, start here:
| Question | Module |
|---|---|
| Should the alarm run at all today? | _gating (no time gate — that is _trigger) |
| What should it sound like right now? | _escalation (pure), driven by _beep_loop |
| Why did it go quiet? | _escalation — the failsafe at FAILSAFE_SILENCE_SECONDS |
| Why is there no sound on HDMI? | _sinks + _reactivate (see below) |
| Challenge presentation / rounds | _rounds, _countdown, _dismiss_view |
| Recurring timers | _alarm_timers (a mixin; they reschedule via root.after) |
| Screen flash | _screen_flash (~1.3 Hz, deliberately under the 3 Hz threshold) |
| Startup hardware | _startup (display, brightness, plug + pre-roll) |
| Structured logs | _logging_setup, _log_sink, _log_dedupe, _correlation |
The audio failure this fixed
_activate_all_outputs() ran once, at startup, and _parse_profile_line
drops any profile reporting available: no. At wake time the monitor has not
finished its HDMI handshake, so the profile is skipped — and while a card's
profile is off its sink does not exist, so _play_on_all_sinks's
per-cycle re-enumeration can never find it. _reactivate re-runs activation on
a decaying schedule and logs the enumerated sink list each pass.
A powered-off speaker is not detectable from software — the analog sink is
present and IDLE either way. That is a hardware problem; see
DOCS-speaker-recommendation.md.
Timing constants that must not collide
FAILSAFE_SILENCE_SECONDS (300) silences audio only; the lock, challenge
and workout gate persist. Two invariants are locked in by tests in
test_escalation.py, because breaking either silently guts the alarm:
PHASE_MEDIUM_END * 60 < FAILSAFE_SILENCE_SECONDS— otherwise the escalation never leaves its softest rung before going quiet.ESCAPE_OFFER_AFTER_SECONDS(900)> FAILSAFE_SILENCE_SECONDS— otherwise the escape hatch appears exactly as the alarm stops being annoying.