docs: mark M3 done
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+38
-7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user