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:
null 2026-08-20 21:59:52 -05:00
parent ff3cbe89ad
commit 7447156cbc
1 changed files with 84 additions and 0 deletions

View File

@ -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