Name reattach's third outcome, which this KDoc denied existed

The determinism argument said no coroutine had a `_state` write left in
flight, because `reattach` "has either returned on its `_state.value !is Idle`
guard or found nothing". There is a third outcome: `pruneWork()` is async, so
`reattach` can find an unpruned job, pass that guard, and start an `observe()`
that is a live coroutine with writes ahead of it.

The conclusion survives, by a mechanism the paragraph did not mention.
`reattach` reads `ownership.current` before its query and hands that token to
`observe`, while `onInputPicked` calls `ownership.claim()` synchronously on the
pick -- so once a detail row exists that observation is superseded and every
emission returns at `stillHeldBy` before it writes. The claim is therefore
"every write in flight is landed or superseded", not "no other coroutine
started".

This is the KDoc a future reader opens to learn why the test cannot flake, and
CLAUDE.md records the same failure mode twice already -- E1/E3, and #226's KDoc
that described a draft rather than the code. A correct test with an incomplete
explanation is its own defect.

Comment only; no test logic changed. Committed with --no-verify on the repo
owner's explicit say-so: the gate's cache is keyed on the `app/src` tree hash,
so a comment costs a full 33-36 sweep, and CI is already green on the parent
commit. ktlintCheck, detekt and compileDebugAndroidTestKotlin were run by hand
first and pass -- those are what a comment edit can actually break.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 16:19:24 -05:00
co-authored by Claude Opus 5
parent 9fd96d08fd
commit b0b8b66d31
@@ -826,11 +826,22 @@ class SafPickerRoundTripTest {
*
* A detail row can only be composed from that second write, because `FileCard` renders the rows
* exclusively under `input.probe != null`. So once one exists, both of `onInputPicked`'s writes
* have landed and been laid out, and no coroutine in the ViewModel has a `_state` write left in
* flight: `reattach` has either returned on its `_state.value !is Idle` guard or found nothing
* (teardown prunes finished work), and `observe` is not started until `convert()` runs. **The
* card cannot change height again before the tap**, which is a different claim from waiting
* longer.
* have landed and been laid out, and every `_state` write still in flight is either landed or
* superseded.
*
* `reattach` has **three** outcomes here, not two. It returns on its `_state.value !is Idle`
* guard; or it finds nothing; or — because `pruneWork()` is async and can leave a finished job
* unpruned — it passes that guard and starts an `observe()`. This paragraph used to name only
* the first two, which was wrong rather than merely incomplete: the third is a live coroutine
* with writes ahead of it.
*
* It is still harmless, and by a different mechanism than the guard. `reattach` reads
* `ownership.current` *before* its query and hands that token to `observe`, while
* `onInputPicked` calls `ownership.claim()` synchronously on the pick — so by the time a
* detail row exists the observation is superseded, and every emission returns at
* `stillHeldBy` before it writes. Outside that path `observe` is not started until
* `convert()` runs. **The card cannot change height again before the tap**, which is a
* different claim from waiting longer.
*
* The `Container` row specifically, rather than a new "probing finished" tag in `main`, because
* this fixture is an MP4 video and that row is already what