docs: mark M9 done
This commit is contained in:
+104
-15
@@ -10,20 +10,20 @@ Status legend: ✅ done · 🚧 in progress · ⬜ not started
|
||||
|
||||
## Current state (one line)
|
||||
|
||||
✅ **M8 done.** The World clock tab is real, and **every tab now has its own
|
||||
content** — no empty state is left anywhere in the app. A hero analog face shows
|
||||
your home zone, and its dial morphs between a circle and a sun with the hour
|
||||
there, so a glance answers "is it the middle of the night" without a line of
|
||||
text saying so — the app's one deliberate `MaterialShapes` showpiece, spent on
|
||||
information rather than decoration. Cities come from the device's own tzdata,
|
||||
named and localised through ICU behind a seam, searched by word-prefix over
|
||||
folded text so ASCII finds São Paulo; each row carries its time, its offset and
|
||||
its day difference **relative to home**, all read at the instant so both sides
|
||||
of a daylight-saving change are right on the day. The list reorders by drag or
|
||||
by a move-up/move-down action a screen reader can reach, and the home zone is
|
||||
set by hand or left following the device. **M9** (system interop — the full
|
||||
`android.provider.AlarmClock` contract and its hostile-input validation) is
|
||||
next.
|
||||
✅ **M9 done.** Every app on the device — an assistant, an automation app, a
|
||||
watch companion, a shell script — can now drive Clockula through
|
||||
`android.provider.AlarmClock`, and no extra it sends can make Clockula write
|
||||
something the user did not ask for. All **seven** of the contract's actions are
|
||||
answered through **one** exported, permission-guarded, window-less door, behind
|
||||
which everything that decides is pure Kotlin: an hour of 25 is dropped rather
|
||||
than clamped to 23, a snooze of 1 000 minutes is clamped rather than refused, a
|
||||
`file://` ringtone inherits the default instead of blocking the alarm, and an
|
||||
intent hostile in every extra at once still leaves exactly one alarm and the
|
||||
user looking at it. A voice-set alarm is **transient** — the schema went to v3
|
||||
for it — so "wake me at 6:30" every morning no longer leaves a graveyard of dead
|
||||
06:30 rows. And the status-bar alarm icon finally opens the alarms list instead
|
||||
of a ring screen for an alarm that is not ringing. **M10** (settings, JSON
|
||||
backup and the self-check screen) is next.
|
||||
|
||||
---
|
||||
|
||||
@@ -491,12 +491,101 @@ deliberate showpiece.
|
||||
in the gate but have **never run on hardware**, as no device was attached —
|
||||
the same caveat M5's seven, M6's four and M7's four carry.
|
||||
|
||||
### ⬜ M9 — System interop
|
||||
### ✅ M9 — System interop
|
||||
The full `android.provider.AlarmClock` contract (`PLAN.md` §6): `SET_ALARM`,
|
||||
`SET_TIMER`, `SHOW_ALARMS`, `SHOW_TIMERS`, `DISMISS_ALARM`, `SNOOZE_ALARM`, plus
|
||||
next-alarm publishing. Hostile-input validation at the intent boundary.
|
||||
- **Tests:** extra validation, including malformed and out-of-range input.
|
||||
|
||||
✅ 1 420 JVM tests, 281 of them new, and **still no Robolectric** — kept now
|
||||
for a *boundary*, which is the last place a project usually gives in, because
|
||||
"it is an `Intent`" sounds like it needs a device. It does not: the door is
|
||||
split so that everything which decides is pure (`AlarmClockRequests`,
|
||||
`AlarmMatching`, `IntentExtras`, `InteropText`, `InteropDeepLinks`) and the
|
||||
Android half is two policy-free files — a `Bundle` reader and an `Intent`
|
||||
builder. `AlarmClockHandler` orchestrates with **no `android.*` import**, as
|
||||
all three engines do, and is tested over both **real** engines through a new
|
||||
`InteropHarness` — deliberately *not* a composition of the existing harnesses,
|
||||
since each builds a DataStore over the same file name and DataStore refuses a
|
||||
second instance over a live one. Four new `ArchitectureRulesTest` rules and a
|
||||
new `ManifestRulesTest`, because the manifest is where this milestone could be
|
||||
silently wrong and none of it fails a compiler. The five new instrumentation
|
||||
tests bring the compiled total to 45.
|
||||
|
||||
`ACTION_DISMISS_TIMER` — the seventh action, named neither in this checklist
|
||||
nor in `PLAN.md` §6's table — was built anyway: "the full contract" is the
|
||||
milestone's own first sentence, and shipping six of seven and calling it full
|
||||
would be the kind of quiet gap this loop exists to avoid.
|
||||
|
||||
The decisions worth knowing: **one range rule settles every extra** — a value
|
||||
that decides when something will ring in the *future* is **dropped** out of
|
||||
range, a value that modifies what the user is doing *now* is **clamped**, so
|
||||
hour 25 never becomes an enabled 23:00 alarm while a 1 000-minute snooze
|
||||
becomes the 60 minutes `ClockPrefs` would have allowed; **a `Bundle` is
|
||||
modelled honestly** as `Map<String, Any?>` with four *total* accessors,
|
||||
because hostile input is an `Int` where a `String` was documented far more
|
||||
often than it is an out-of-range number, and the platform's typed getters
|
||||
cannot tell "absent" from "wrong type"; **`SKIP_UI` never means skip
|
||||
validation** — an incomplete `SET_ALARM` creates the row from the extras that
|
||||
*were* valid, at the next whole hour, and lands the user in its editor, since
|
||||
M5 locked that the editor edits a persisted row and nothing is ever written
|
||||
and hidden; **`ALARM_SEARCH_MODE_TIME` matches exactly, never "nearest"**, and
|
||||
an ambiguous 0..12 hour with no `IS_PM` carries *both* readings as candidates
|
||||
so the question only reaches the user when the data cannot settle it;
|
||||
**`ALARM_SEARCH_MODE_ALL` never asks** — taken literally the contract's "show
|
||||
the results" would make the mode useless — while every other search with two
|
||||
or more matches opens the milestone's one new surface, an M3 basic dialog with
|
||||
a `ListItem` list where tapping a row *is* the answer.
|
||||
|
||||
**A correctness find, fixed here:** the `AlarmClockInfo` **`showIntent`** — the
|
||||
thing the system opens when the user taps the status-bar alarm icon or the
|
||||
lockscreen's next-alarm line — pointed at the *ring screen* with
|
||||
`CLEAR_TASK`, so tapping "my next alarm" opened a ring screen for an alarm
|
||||
that was not ringing, which resolved to `Finished` and closed itself. It now
|
||||
opens `MainActivity` on the Alarms tab, and a build rule fails the build if
|
||||
`AndroidAlarmScheduler` ever names `AlarmRingActivity` again. That intent is
|
||||
literally what "next-alarm publishing" means to a user.
|
||||
|
||||
**The schema went to v3**: `delete_after_use` on `alarms` and on `timers`, two
|
||||
`ALTER TABLE`s in `MIGRATION_2_3`, `3.json` exported and committed, `1.json`
|
||||
and `2.json` untouched. It is not optional polish — the contract says in two
|
||||
places that a `SKIP_UI` alarm or timer should be removed once dismissed, and
|
||||
without it a daily "wake me at 6:30" turns the Alarms tab into a graveyard.
|
||||
The flag is set only on the `SKIP_UI` create path and honoured inside the two
|
||||
engines, under their locks.
|
||||
|
||||
Four things outside the checklist were changed anyway, and are recorded in
|
||||
`ARCHITECTURE.md`: **`AlarmEngine` gained `dismissUpcoming(id)` and a
|
||||
`minutesOverride` on `snooze`**, because both move the ring slot, the
|
||||
auto-silence backstop and the next-alarm registration, and a handler calling
|
||||
three repositories in a row would race a fire broadcast; **`TimerEngine` gained
|
||||
`dismissAllExpired()` and `dismissExpired(id)`** — the first is the "stop all"
|
||||
the UI still deliberately does not offer, because that rule was about a thumb
|
||||
on a ring and a programmatic request has stated exactly what it is discarding;
|
||||
**`domain/text/TextFolding` was extracted** and `ZoneSearch` delegates to it,
|
||||
with `ZoneSearchTest` passing unmodified as the proof; and **the shell's
|
||||
`openTab` became `openRequest: ShellRequest?`**, since three of the contract's
|
||||
outcomes are not "select a tab".
|
||||
|
||||
**No new `<uses-permission>`**: the one new permission string is
|
||||
`android:permission` *on the door*, a requirement on the caller. **No new
|
||||
dependency coordinate** and **no floret-kit change** — the kit stays at 0.6.0,
|
||||
untouched, because the one new surface is material3's own `AlertDialog`.
|
||||
|
||||
Knowingly open: no voice-interaction follow-on flows
|
||||
(`VoiceInteractor.CompleteVoiceRequest`/`PickOptionRequest`), so
|
||||
`android.intent.category.VOICE` is deliberately not declared and Clockula
|
||||
accepts a `clockula://` deeplink while publishing none; no "most closely
|
||||
matched" time search; no toast on a `SKIP_UI` write — the status-bar icon is
|
||||
the platform's own receipt; no `delete_after_use` surface in either editor, so
|
||||
an assistant-set alarm the user edits stays transient, as it does in Google
|
||||
Clock; no `EXTRA_MESSAGE` carried into the timer setup panel when no length
|
||||
was given; and `AlarmScheduler.systemNextAlarm()` is still caller-less — it
|
||||
belongs to M10's self-check screen. The five new instrumentation tests and the
|
||||
two new migration cases compile in the gate but have **never run on
|
||||
hardware**, as no device was attached — the same caveat M5's seven, M6's four,
|
||||
M7's four and M8's four carry.
|
||||
|
||||
### ⬜ M10 — Settings, backup, and the self-check
|
||||
- Settings composed from kit components: theme, dynamic colour, language,
|
||||
alarm/timer/clock defaults, about + crash reporting.
|
||||
|
||||
Reference in New Issue
Block a user