Commit Graph

9 Commits

Author SHA1 Message Date
fea60814c2 Size the alarm's type in pixels, and gate every screen on fitting 768px
Every font here was a positive number handed to Tk, which reads that as
*points*: "WAKE UP!" at 48 rendered around a third larger than the 48px
intended, and the challenge code at 72 larger still. LockConfig.font()
applies Tk's sign convention, so the sizes now come from the design-system
roles (with an explicit scale where a challenge is deliberately oversized)
and the paddings from the spacing scale.

The alarm places a centred frame, which shears equal amounts off the top AND
the bottom when it outgrows the display -- on this screen that means losing
"WAKE UP!" and the answer entry at once, on a window whose whole purpose is
to be answered before it will stop. scripts/verify_screen_fits.py renders
every challenge kind plus the escape hatch against a real 1366x768 X server
and fails if any overflows; it runs as a pre-commit hook. Each kind is
measured separately rather than one at random -- the flash kind draws its
code half again as large as the rest, so a random sample would have passed
or failed by dice roll.

Fixed while sweeping: maybe_offer() reached for a `config` that was never in
its scope, so revealing the escape button raised NameError. Its own tests
caught it.

gatelock is pinned to v0.4.0, which stops the shared viewport scrolling
itself to follow the app's own focus_set() (2026-08-03, screen-locker).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsXnTHyuuCUZsqetTaajcF
2026-08-03 16:04:27 +02:00
1e465e0d2c Let a half-awake keyboard user actually reach Submit
The escape-hatch form was a keyboard dead end. Tk traps Tab inside a tk.Text
(inserts a literal tab, refocuses the widget) and binds Shift-Tab to nothing,
so the only exits are the undocumented Ctrl+Tab / Ctrl+Shift+Tab. Unlike
diet_guard's equivalent box, which at least binds <Return>, this one bound
nothing at all -- so a keyboard-only user who tabbed into the justification
field at 6am could not get back out to Submit.

Enter now activates Submit / Back / the escape button too. X11 Tk binds only
<space> on Button, so the key a user actually reaches for did nothing.

Escape returns to the alarm from the escape FORM. To be explicit, because this
app's whole point is resisting dismissal: this does not weaken the gating. The
alarm still requires DISMISS_ROUNDS_REQUIRED correct answers or the budgeted
escape hatch. The absent global <Escape>, the overrideredirect + grab_set_global
+ VT lock, and the hidden-until-earned escape button are deliberate adversarial
design and are left alone. What changed is that the *sanctioned* paths are now
reachable without a pointer.

Focus rings everywhere: the dismiss-code entry, the escape button, the
justification Text and the three escape-form fields all kept Tk's default 1px
BLACK ring, invisible against #211D1B. On a screen with no other window and a
global grab, that ring is the only feedback that a keystroke landed anywhere.

_alarm_view.py imported gatelock only under TYPE_CHECKING, so bind_activate
would have raised NameError at runtime while the module still imported cleanly
-- an import smoke test would have missed it.

phone_app is Android-only, so 1366x768 does not apply; its risk is landscape.
home_screen and settings_screen were bare Columns that cannot scroll, so
content overflowed with the stripe pattern painted over the primary control:
phone landscape leaves ~360px of body height for ~405px of content. Both now
scroll inside a SafeArea. The status line also rendered an unbounded raw
'Error: $e' with no maxLines, which added 100-150px and overflowed even in
portrait; it is capped at four lines.

The gatelock pin moves to v0.3.0, which is where the shared Tk-default
workarounds live.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 16:27:39 +02:00
ad2e725d52 Pin gatelock 0.2.1 for the recovery-loop fail-open fix
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
0.2.0's recovery loop scheduled the next tick after running it, so any
exception out of a tick permanently ended all re-assertion of coverage, grab
and VT for the rest of the lock -- silently, since Tk logs and swallows
exceptions from `after` callbacks. 0.2.1 moves the reschedule into a
`finally`.

Also in 0.2.1: `holds_grab` no longer reads a grab held by one of our own
transient children (a posted menu) as "the grab was lost" and takes it back.
wake-alarm has no popup widgets on its lock surfaces, so that half is
insurance rather than a fix here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:19:19 +02:00
36330ed2d3 Point wake_alarm at gatelock v0.2.0
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Three pins, all of which have to move together or the gate lies: the
runtime dependency in pyproject.toml, the mirror of it in requirements.txt,
and -- easy to miss -- the copy inside the pylint hook's own isolated
environment in .pre-commit-config.yaml. With only the first two bumped,
pre-commit still resolved 0.1.1 and reported the v0.2.0 API as
no-name-in-module.
2026-07-25 15:42:21 +02:00
71175690a7 Bump gatelock pin to released v0.1.1, regenerate launcher icon
Picks up gatelock's warm-tinted unified-design-system palette + the new
on_fill token. wake_alarm's own UI wasn't audited for the on-fill
contrast bug this session (out of the 3-pilot scope that found it in
diet-guard/screen-locker/dufs-cloud) -- worth a follow-up pass.

Verified: wake_alarm pytest 300/300 @ 100% coverage against the new pin.
2026-07-21 21:18:02 +02:00
d3e81174ea Fix package discovery so install.sh gets past step 1
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Adding the Flutter phone_app/ directory gave setuptools two top-level
candidates in a flat layout, so it refused to build:

    error: Multiple top-level packages discovered in a flat-layout:
    ['phone_app', 'wake_alarm']

install.sh runs under `set -euo pipefail` and does the editable install
first, so every run since has aborted at step 1 — silently skipping the
systemd units, the sleep hook, the fan script and the shutdown wrapper.
That is why /usr/local/bin/shutdown was still the pre-token-fallback
version: not a forgotten reinstall, a broken one.

Declare the build backend explicitly and restrict discovery to
wake_alarm*, so phone_app is ignored. All 9 install steps now run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 08:28:04 +02:00
87d2629e88 Fire the alarm at the phone's time via a per-minute timer
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
The PC is no longer shut down at night, which broke the scheduling model
outright. The wake time used to be decided once, at hibernate, and baked
into the RTC by shutdown-wrapper.sh. With no shutdown there is no resume,
so sleep-hook.sh never ran and nothing fired the alarm at all.

Worse, wake-alarm.service was WantedBy=graphical-session.target and
enabled by install.sh, while _should_run_alarm() gates on day / dismissed
/ workout-logged but has no *time* gate. On an always-on machine that
target is reached on every login, so the alarm fired at login time on any
alarm day — which is every day.

Replace both with a systemd user timer ticking once a minute:

- _sync.py reads the alarm the phone pushed to kuhyx/syncs, mirroring
  shutdown-wrapper.sh's jq parse so the two readers cannot drift. Caches
  each read, then degrades fresh cache -> fetch -> stale cache ->
  FALLBACK_ALARM_HOUR, so a network blip can never cost a wake-up.
  stdlib-only: the units run /usr/bin/python, so a dependency here is a
  production install-path hazard.
- _trigger.py fires morning-routine.service (never wake-alarm.service —
  that skips the workout lock) when local time is inside
  [alarm, alarm + TRIGGER_CATCHUP_MINUTES). The catch-up window covers a
  suspended or just-booted machine; starting an already-active unit is a
  no-op, so an alarm on screen needs no extra dedupe state.
- wake-alarm.service loses its [Install]; install.sh disables it.

A timer rather than a resident scheduler: each tick is a fresh process,
so there is no daemon to die silently and no wall-clock jump to handle
across suspend/resume. Ticks are per-minute for exact-minute firing, but
only hit the network when the cache is older than 5 minutes — ~288
GitHub reads/day, and phone changes land within ~5 minutes.

Degraded reads log at INFO, not WARNING: at one tick a minute a
persistent condition would be 1440 identical journal lines a day. The one
WARNING kept is _trigger firing on a non-phone alarm time.

Token resolution gains a fallback to the sibling apps' repo-scoped
sync_token, in both readers, so one PAT covers every app on kuhyx/syncs.

Note this surfaced that wake-alarm-sync/ does not exist in kuhyx/syncs at
all: the phone has never completed a push since the crdt_sync migration,
so the hibernate path had been silently using the +8h fallback. Fixing
that is a phone-side action (reconnect GitHub with repo scope, then one
"Set Alarm & Sync"). Alarms edited directly in the Clock app still do not
reach the sync — that needs an ACTION_NEXT_ALARM_CLOCK_CHANGED receiver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 20:21:17 +02:00
643c55e204 Point gatelock at the consolidated utils monorepo
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
gatelock was folded into github.com/kuhyx/utils as a subdirectory;
update the pip git URL across requirements.txt, pyproject.toml, and
.pre-commit-config.yaml to the new location with the renamed tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 14:01:43 +02:00
407d7cbf8f Extract wake_alarm from testsAndMisc as a standalone repo
Rewrites python_pkg.wake_alarm imports to wake_alarm, vendors the
shared configure_logging helper, drops the monorepo PYTHONPATH from
install.sh and the systemd unit (package is now pip-installed), and
untracks wake_state.json (runtime HMAC state, now gitignored). Scaffolds
standalone lint/test config copied from the already-corrected diet_guard
scaffold (pylint --fail-under=10 with tests excluded and the
use-implicit-booleaness/consider-using-with disables, mypy's actual
disabled-error-code set, ruff ALL, bandit, 100% branch coverage), plus
the wave.Wave_write generated-members fix this package's _audio.py needs.
2026-06-22 12:31:40 +02:00