currentInput() answered for Converting, Waiting and Converted as well as Ready. Those three
arms were unreachable by tapping Convert -- the button renders only in the Ready branch --
but they were reachable through the POST_NOTIFICATIONS *result*, which ConverterScreen.kt:91
wires to convert() rather than to the button.
Reaching one enqueued a SECOND job over a live one: activeWorkId was overwritten, and the
first job kept running with its foreground notification orphaned and nothing left holding
its id to cancel it.
#202 decided to narrow rather than to test it as it stood, because a test written against
the old shape would have frozen the double-enqueue as intended behaviour -- the F1/F5 failure
mode docs/coverage-read-findings.md names. currentInput() is now
(_state.value as? ConversionState.Ready)?.input, which is what JoinViewModel.join() has been
all along; the two screens are the same shape and only one of them was over-general.
Four cold refusal arms come with it, all reached the same way -- a system callback arriving
after the screen has moved on, which is what a result redelivered after process death does:
ConversionViewModel.kt:513 currentInput() ?: return
ConversionViewModel.kt:600 pendingSave() ?: return
JoinViewModel.kt:316 (as? Ready)?.inputs ?: return
JoinViewModel.kt:390 pendingSave() ?: return
One fixture note worth keeping: the second case needs a real staged file in the worker's
output Data. A SUCCEEDED job with no output path maps to Failed rather than Converted, so
Data.EMPTY never reaches the state the case is about -- which cost a timed-out awaitState
before it was spotted.
Mutations, all run and restored:
restore the over-general four-arm when 1 red <- the defect this change fixes
currentInput()!! at :513 2 red
pendingSave()!! in save() 1 red
drop both join guards 1 red
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>