docs: record the two release-blockers and the reported trap
Development log entry per WORK_CYCLE step 6, covering what the three explorations found beyond the bug that was reported -- including that ExportHost has almost certainly never been visible to anyone, and that the app-lock entry's "setting a PIN locked you out" fix closed the common ordering but not the race. Next action and blockers named at the end, as the file's own rules ask. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ff3cbe89ad
commit
7447156cbc
|
|
@ -32,6 +32,90 @@ written and stay true. It is exempt from review for the same reason a receipt is
|
||||||
|
|
||||||
## Entries
|
## Entries
|
||||||
|
|
||||||
|
### 2026-08-20 (evening) — Two release-blockers, and the trap that was reported
|
||||||
|
|
||||||
|
Kaspa reported one bug — *"on the settings page, if you go to app lock you can't
|
||||||
|
unnavigate out of it"* — and asked for biometrics-or-PIN, back arrows, unified
|
||||||
|
code, error handling, notifications, and blind spots covered. Three read-only
|
||||||
|
explorations of settings navigation, the app lock, and the reminder pipeline
|
||||||
|
turned up considerably more than the report, including two release-blockers
|
||||||
|
nobody had noticed.
|
||||||
|
|
||||||
|
**Batch 12 · P0 — "Ended" recorded the start of a period.** The period-end
|
||||||
|
check-in asks *"Is your period over?"* and offers **Ended** / **Still going**.
|
||||||
|
`ReminderWorker` attached writes to buttons *by position* — index 0 to "started",
|
||||||
|
index 1 to "not yet" — for every kind of reminder, so "Ended" inserted a new
|
||||||
|
period starting today, in the middle of the one it was asking about, and "Still
|
||||||
|
going" filed a censoring observation against a forecast that had already
|
||||||
|
arrived. It corrupted the health record and every forecast built on it. The
|
||||||
|
handler had no test at all, which is how it survived; it owns the only two writes
|
||||||
|
reachable from a locked phone. A button is now one thing carrying both halves,
|
||||||
|
`ENDED` closes the period through `setPeriodEnd`, and `STILL_GOING` deliberately
|
||||||
|
writes nothing.
|
||||||
|
|
||||||
|
**Batch 11 · P0 — Change PIN never checked the current PIN.** The screen fired
|
||||||
|
the check and advanced to "Choose a PIN" in the same breath, without waiting for
|
||||||
|
the answer. Any four digits reached the replacement screen and overwrote the PIN,
|
||||||
|
so anyone holding the unlocked phone could lock the owner out of her own history
|
||||||
|
permanently — under the no-recovery policy the only way back is to erase
|
||||||
|
everything. Fixed at both altitudes: the step advances inside the verified
|
||||||
|
callback, and the ViewModel refuses a replacement no successful check authorised.
|
||||||
|
|
||||||
|
**The reported trap had three causes, all real.** The bottom nav saved and
|
||||||
|
restored per-tab stacks, so tapping Settings from App lock restored
|
||||||
|
`[settings, settings/lock]` and landed back on App lock — and the saved stack
|
||||||
|
survived visiting other tabs, pinning the Settings tab there for the rest of the
|
||||||
|
process. No screen in the app had a back arrow at all: a grep for `TopAppBar`,
|
||||||
|
`navigationIcon`, `BackHandler` and `popBackStack` across `app/`, `core/` and
|
||||||
|
`domain/` returned nothing, and App lock had a headline styled like a bar without
|
||||||
|
being one. And setting a PIN could still lose the race the log recorded as fixed
|
||||||
|
in the app-lock entry: the fix lived in the scope the race destroys.
|
||||||
|
|
||||||
|
Settings is now a nested graph, leaving it never saves its stack, re-tapping a
|
||||||
|
tab returns it to its root, and one `SettingsSubpage` carries the bar for all
|
||||||
|
three children. App lock's steps are remembered state rather than destinations,
|
||||||
|
so its back is step-aware and the arrow and the gesture agree. `setPin` unlocks
|
||||||
|
before it writes, which makes the bad pair unobservable.
|
||||||
|
|
||||||
|
**`ExportHost` has almost certainly never been visible.** It was emitted as a
|
||||||
|
sibling *before* the `Scaffold`, inside a `Box`, where later siblings draw on top
|
||||||
|
— and the Scaffold paints an opaque container. It is the Scaffold's top bar now.
|
||||||
|
The export's on-device checks were never run either, which is why nobody noticed.
|
||||||
|
|
||||||
|
**The lock now fails towards opening.** `hasPin` had no `catch` and is collected
|
||||||
|
on the startup path in two places that cannot handle a throw, so a corrupt
|
||||||
|
`app_lock` file crashed the app before any UI existed. It reads as *no lock* now,
|
||||||
|
and the store has a corruption handler — without one, a single bad write leaves
|
||||||
|
the lock unable to be set *and* the "Forgot your PIN?" erase unable to run.
|
||||||
|
Separately, a wrong PIN whose counter could not be signed used to cost nothing;
|
||||||
|
the unwritten counter is held in memory and read back when it is the longer wait.
|
||||||
|
|
||||||
|
**And the app can now stop asking.** §30's stopping rule was unreachable: the
|
||||||
|
check-in count was reset by a flow whose first emission always passes, in a
|
||||||
|
coordinator started in every process — including the one WorkManager spawns for
|
||||||
|
the reminder, moments before it reads the count. The count now carries the row id
|
||||||
|
of the period it was asked about, so a new period starts it over by arithmetic
|
||||||
|
and the reset chain is deleted.
|
||||||
|
|
||||||
|
Compose UI tests run on the JVM under Robolectric now; nothing in this project
|
||||||
|
could assert a navigation behaviour before. Every fix was proved with
|
||||||
|
`scripts/prove-guard.sh` — one red each, except removing the change-PIN
|
||||||
|
write-site guard, which reddens three because that is the whole of its surface.
|
||||||
|
|
||||||
|
**16 issues filed** across two new milestones: `Batch 11 — Settings and the App
|
||||||
|
Lock` (#60-#67) and `Batch 12 — Reminders that tell the truth` (#68-#75). Five
|
||||||
|
are closed.
|
||||||
|
|
||||||
|
**Next action:** #71 — a denied notification permission is ignored, the toggle
|
||||||
|
stays on, and the check-in counter still advances on reminders that were never
|
||||||
|
posted, so the app can "stop asking" having never asked once. Then #72, where
|
||||||
|
changing the reminder time does not move it (verified against WorkManager's
|
||||||
|
bytecode: `UPDATE` keeps the old `lastEnqueueTime`).
|
||||||
|
|
||||||
|
**Blockers:** none. Note that a flaky 5-second test budget was raised to 30s
|
||||||
|
after one red in a parallel run — each PIN in that test costs a real
|
||||||
|
210,000-iteration derivation.
|
||||||
|
|
||||||
### 2026-08-20 (later) — Batch 09: the learning loop, measured
|
### 2026-08-20 (later) — Batch 09: the learning loop, measured
|
||||||
|
|
||||||
Started from a question — *how well does this app learn a period?* — and
|
Started from a question — *how well does this app learn a period?* — and
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue