diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 16e1546..3df8b19 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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