docs: mark M7 done
This commit is contained in:
+85
-10
@@ -10,15 +10,18 @@ Status legend: ✅ done · 🚧 in progress · ⬜ not started
|
||||
|
||||
## Current state (one line)
|
||||
|
||||
✅ **M6 done.** The Timers tab is real: several timers at once, a keypad and
|
||||
saved presets that set one in a couple of taps, pause/reset/"+1 min" from the
|
||||
row, the live pill *and* a notification whose countdown the system draws, a
|
||||
foreground service alive exactly while something is active, and expiry that
|
||||
rings through M3's audio path — waiting its turn when an alarm is already
|
||||
ringing rather than talking over it. Every countdown is anchored on the device's
|
||||
uptime, so moving the system clock cannot move a timer in either direction. Two
|
||||
tabs are still empty states: **M7** (stopwatch — start/stop/reset, laps with
|
||||
splits, and the app-wide big-readout typography settled at last) is next.
|
||||
✅ **M7 done.** The Stopwatch tab is real: start, pause and reset, a readout to
|
||||
hundredths whose fraction is drawn smaller than its seconds, laps with their
|
||||
split and their running total newest-first with the fastest and slowest marked,
|
||||
the lap in progress as its own counting row, and a silent `specialUse`
|
||||
foreground service whose Lap and Pause work with the app closed — its ticking
|
||||
drawn by the system, so it costs no battery to keep accurate. A stopwatch that
|
||||
meets a reboot comes back **paused at what it banked**, with every lap intact,
|
||||
rather than inventing the time it lost; a clock change cannot move it at all.
|
||||
M0's promise that the big-readout typography would be settled against a working
|
||||
stopwatch is kept, and every readout in the app now sits in fixed columns. One
|
||||
tab is still an empty state: **M8** (world clock — the zone list, ICU names,
|
||||
offsets and the analog face) is next.
|
||||
|
||||
---
|
||||
|
||||
@@ -329,13 +332,85 @@ Elapsed-realtime anchored.
|
||||
§5.10 #11 opens a second DataStore over a live file in the same scope, which
|
||||
DataStore refuses by design — as `SettingsPrefsTest` itself documents.
|
||||
|
||||
### ⬜ M7 — Stopwatch
|
||||
### ✅ M7 — Stopwatch
|
||||
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.
|
||||
|
||||
✅ 968 JVM tests, 125 of them new, and **still no Robolectric** — kept now for
|
||||
a third screen and a third service. Everything the tab decides is a pure
|
||||
object (`StopwatchFormat`, `StopwatchReadings`, `LapStats`, `StopwatchLaps`,
|
||||
`StopwatchNotificationPolicy`, `StopwatchRows`) or a method on the engine or
|
||||
the ViewModel, tested over the **real** `StopwatchRepositoryImpl` across the
|
||||
real `appendLap` transaction and a real DataStore under a `@TempDir`.
|
||||
`StopwatchService` holds no policy and **no state at all** — not even the
|
||||
timers' one `alreadyAlerted` flag, because nothing here ever alerts — and it
|
||||
has no `delay`. Two new `ArchitectureRulesTest` rules and two extended; the
|
||||
four new instrumentation tests bring the compiled total to 36.
|
||||
|
||||
The decisions worth knowing: the foreground service is **`specialUse`**, not
|
||||
`systemExempted`, because a stopwatch is not alarm functionality — it
|
||||
schedules nothing, wakes nothing and rings nothing, and claiming the type
|
||||
would have the app tell the platform something untrue; it holds **no wake
|
||||
lock, ever**, since the elapsed value is derived from a persisted monotonic
|
||||
anchor at read time; the notification is the **platform chronometer counting
|
||||
up**, with its base `now - elapsed` **derived at post time and never stored**,
|
||||
which is how the run record keeps its "no wall-clock field whatsoever" rule;
|
||||
its channel is `IMPORTANCE_LOW` and silent, because nothing here ever
|
||||
finishes, so there is no alert-once rule and no session window anywhere in the
|
||||
path; the readout is hundredths and it is **two strings**, so the
|
||||
fastest-changing glyphs are not the loudest ones; best and worst need **three**
|
||||
laps, ties go to the earliest, and all-equal means neither; laps are capped at
|
||||
**999**, guarded in the engine over one `COUNT(*)` so appending never
|
||||
materialises the rows; and the tab ticks at ~60 Hz **only while running**, so
|
||||
a paused or idle stopwatch costs nothing.
|
||||
|
||||
Three things the checklist did not name were changed anyway, and are recorded
|
||||
in `ARCHITECTURE.md`: **the live pill's stopwatch actions now go through
|
||||
`StopwatchEngine`** (M6 recorded this as knowingly open), with the pill's own
|
||||
contract unchanged and `LivePillSelectorTest` passing **unmodified** as the
|
||||
proof; **`LivePillSelector` was re-pointed at a new pure `StopwatchReadings`**,
|
||||
the same extraction M6 made for timers, so the pill, the shade and the tab
|
||||
cannot answer "what does this read right now" three different ways; and
|
||||
**`ui/theme/Type.kt` stopped being `Typography()`** — tabular figures on the
|
||||
display, headline and title roles, app-wide, with no call-site change
|
||||
anywhere, plus `ClockulaReadoutDefaults` naming the three roles a readout uses.
|
||||
|
||||
The correctness find: a run that met a reboot mid-lap had `accumulated = 0`
|
||||
while its laps still recorded forty seconds, so the honest reading is
|
||||
**floored at the last lap's total** as well as at zero — a readout sitting
|
||||
below a lap the same run printed is not one anyone can believe, and the laps
|
||||
are real measurements that are never deleted to tidy the arithmetic. Recorded
|
||||
in `ARCHITECTURE.md` §5. The review found the other half of it: the floor has
|
||||
to be **banked, not only displayed** — resuming writes
|
||||
`accumulated = max(accumulated, lastLapCumulative)`, and pausing and lapping
|
||||
bank the same floored reading — or the record ticks from below its own last
|
||||
lap, the shade's free-running chronometer counts while the tab sits frozen,
|
||||
and the next lap records a cumulative below the previous one. The same review
|
||||
pass moved the engine's verbs onto **the reading's** mode rather than the
|
||||
stored row's, so a stale record's Resume button resumes instead of hitting a
|
||||
silent no-op. `pauseAfterReboot` itself is still untouched.
|
||||
|
||||
No schema change (the database stays at v2; `LapDao` gains one `COUNT(*)` and
|
||||
nothing else), **one** new permission — `FOREGROUND_SERVICE_SPECIAL_USE`, the
|
||||
one that names what the service actually does — and **no floret-kit change**:
|
||||
the kit stays at 0.6.0, untouched.
|
||||
|
||||
Knowingly open: no lap export, sharing, deletion or editing, and no "previous
|
||||
run" history; no Lap button on the live pill (it has two controls by design);
|
||||
no user-configurable lap cap or readout precision; no reordering, grouping or
|
||||
filtering of laps; no countdown-to-start, split-lap alarm, sound or vibration
|
||||
from the stopwatch (a build rule enforces the silence); no analog stopwatch
|
||||
face and no `MaterialShapes` showpiece, which `PLAN.md` §8 reserves for the
|
||||
world-clock face, the ring screen and timer progress. `StopwatchReading.anchorIsStale`
|
||||
is still not surfaced, the same answer M6 gave for timers. Of `AlarmClock`'s
|
||||
intent contract only the in-process hook `ShellNavigation.tabForAction` grew a
|
||||
second action; M9 owns the rest. The four new instrumentation tests compile in
|
||||
the gate but have **never run on hardware**, as no device was attached — the
|
||||
same caveat M5's seven and M6's four carry.
|
||||
|
||||
### ⬜ M8 — World clock
|
||||
IANA zone list with search, ICU-localised city and zone display names, offset and
|
||||
day-difference relative to home, reorder, home-zone handling. The analog face
|
||||
|
||||
Reference in New Issue
Block a user