docs: mark M3 done

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-11 16:05:58 +02:00
co-authored by Claude Opus 5
parent a522d68098
commit 12da168cc5
+38 -7
View File
@@ -10,12 +10,14 @@ Status legend: ✅ done · 🚧 in progress · ⬜ not started
## Current state (one line)
✅ **M2 done.** Clockula owns its storage: a Room database of four tables with
the schema exported and committed, plain-Kotlin domain models behind four
Flow-based repository interfaces, DataStore preferences, Hilt wiring, and the
first `ARCHITECTURE.md`. **M3** (the alarm engine — next-fire resolution over
repeat masks and DST, `AlarmScheduler`, the receivers, and the ringing service)
is next, and is the hard one.
✅ **M3 done.** Clockula's alarms ring. Next-fire resolution is a pure function
over a repeat mask and a zone, DST-correct in both directions; skip and snooze
each have a persisted watermark; the engine registers only the single next alarm
via `setAlarmClock`, is driven by five thin receivers, and rings through a
foreground service that survives process death and reboot and never degrades to
silence. Schema v2 adds `alarm_states`, and M2's reboot blind spot is closed.
**M4** (the app shell — navigation host, four tabs, the live pill, and the ring
screen's UI) is next.
---
@@ -121,7 +123,7 @@ The whole storage stack, headless. No UI.
each other's edits or persist a torn record. 151 JVM tests over fake DAOs and
injected fake clocks; the migration test is the only thing needing a device.
### ⬜ M3 — Alarm engine
### ✅ M3 — Alarm engine
The hard part (`PLAN.md` §4), still headless apart from the ring screen's
plumbing.
- Next-fire resolution: local time-of-day + repeat mask → next instant in the
@@ -137,6 +139,33 @@ plumbing.
- **Tests:** DST spring-forward and fall-back, every repeat configuration, skip,
snooze-vs-next-occurrence. These are the tests the app lives or dies on.
✅ 326 JVM tests, 172 of them new. The engine is a plain Kotlin class that
reaches Android through four interfaces it does not implement, so the
behaviour the app lives or dies on is pinned without an emulator —
`ArchitectureRulesTest` fails the build if `android.*` ever appears in
`domain/`, `AlarmEngine.kt` or `system/`. Occurrences are generated by walking
local dates through `ZonedDateTime.of` rather than by adding 24 hours to an
instant, which is the whole DST story: a 02:30 alarm rings at 03:30 on a
spring-forward night instead of vanishing, and once rather than twice on a
fall-back night. Asserted for Berlin and New York and across all 128 repeat
masks. Two rules keep constant re-resolution honest: a two-minute grace window
so a late fire still counts, and a `handled_occurrence` watermark that can
suppress a re-fire but — because suppression also requires the candidate to be
at-or-before now — can never silence the future. Skip needed a watermark of its
own, since a bare flag eats a second alarm once the skipped occurrence passes.
At most one alarm rings and a newly-firing one takes over, so closing a cycle
touches the shared ring and auto-silence slots only when that alarm is the one
ringing; every write to `alarm_states` runs under one mutex, because a cold
start triggered by the fire broadcast otherwise races its own reschedule pass.
The "never to silence" chain is a testable invariant, not a hope: full-screen
intent, else a heads-up notification that still rings, else the default
ringtone, else forced vibration. Schema v2 adds `alarm_states`, and the boot id
closes M2's blind spot for running timers **and** the stopwatch — a scope move
from M7, recorded in `ARCHITECTURE.md` §5. Knowingly open: the migration and
the other 12 instrumentation tests compile but have never run on hardware, as
no device was attached; an auto-silenced alarm is silently missed, with no
missed-alarm notification until someone asks for one.
### ⬜ M4 — App shell
- Navigation host, four tabs, M3 navigation bar, adaptive rail on wide layouts.
- The **live pill** (`PLAN.md` §9) — shared running-state surface, pause + stop
@@ -159,6 +188,8 @@ Elapsed-realtime anchored.
Start/stop/reset, laps with splits and cumulative times, best/worst lap emphasis.
Foreground service so it survives backgrounding; laps persist across process
death. This is where the big-readout typography gets settled for the whole app.
The post-reboot repair is **not** here — M3 took it (`ARCHITECTURE.md` §5);
M7 owns only how the paused result is presented.
### ⬜ M8 — World clock
IANA zone list with search, ICU-localised city and zone display names, offset and