fix(portal): teach the phone the receipt shape and say what actually broke - #79
Merged
Conversation
…broke
The founder paired a phone to a real halted run and could not tell what to do.
The decision view asked "is the live application ready for OpenAdapt to verify
and continue?" and offered three answers, and it said neither what broke nor
what any of the three would do.
BLOCKING: the receipt shape. `app.js` read `body.status`/`body.message`. Flow
PR #290 replaces that reply with the closed `HumanDecisionReceiptV1`, which has
`state`/`reason_code` and deliberately no message, so every terminal outcome
fell through to the else branch and rendered as "That decision was refused" --
a real `halted` was observed doing exactly that. Every `(state, reason_code)`
pair now maps to its own copy, and a pair this build does not recognise is
reported as an unknown outcome rather than a refusal, because showing a real
terminal outcome as some other outcome is the failure this table prevents. A
runner still returning the older decision record is mapped through Flow's own
status table so it renders identically; Flow #290 is no longer blocked on
Desktop.
Say what broke. Flow's local decision detail carries a typed halt category, the
step's ordinal, its action kind, the target's role, and the target's label only
when the engine proved that label is static control chrome. The shell composes
the sentence -- "Step 1 of 6 could not start: OpenAdapt could not find the
button labelled Open on the screen, so it did not click anything" -- because
the wire carries closed enums and no prose, which is what keeps a typed value
off it. A withheld label degrades to the role noun, so a TYPE step reads "could
not find the text field". It also lists which resolution rungs were tried and
what continuing will re-check.
Distinguish the three answers by consequence, which is what could not be told
apart: check and continue is this run only and the same drift stops the next
run; teach the correction changes future runs and continues nothing now; needs
more help leaves the run paused and untouched.
Two cosmetic defects, both with established causes. The action bar stayed
visible after a terminal receipt because `.actions { display: grid }` outranks
the user-agent `[hidden] { display: none }` rule, so setting the attribute did
nothing; an explicit `.actions[hidden]` fixes it. The outcome text sat under
the fixed bar because the page reserved a constant 200px, which three
two-line buttons exceed; the bar's real height is now measured and released
when it is hidden.
Also: a fetch that never comes back is now status 0 -- "the result is
uncertain" -- instead of an unhandled rejection leaving "Submitted, waiting"
on screen forever.
`src/portalShell.test.ts` drives the shipped `app.js` in a DOM rather than
grepping it, because every existing source-text assertion passed while the
surface a human reads showed every outcome as a refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The founder paired a phone to a real halted run and could not tell what to do. The decision view asked "Is the live application ready for OpenAdapt to verify and continue?", offered
I fixed it, verify and continue/Teach the correction/Needs more help, and said neither what broke nor what any of the three answers would do. The engine knew: that pause carriedcategory: resolutionand "could not resolve target for step_000 (click 'Open'): all resolution rungs failed".BLOCKING: the receipt shape
engine/portal/shell/app.jsreadbody.status/body.message. openadapt-flow PR #290 replaces that reply with the closedHumanDecisionReceiptV1, which carriesstate/reason_codeand deliberately no message. Every terminal outcome therefore fell through to the else branch and rendered as "That decision was refused" — a realhaltedwas observed doing exactly that.Every
(state, reason_code)pair Flow can emit now maps to its own operator copy inRECEIPT_COPY. A pair this build does not recognise is reported as an unknown outcome, never as a refusal: showing a real terminal outcome as some other outcome is the whole failure this table exists to prevent. A runner still returning the older decision record is mapped through Flow's own_RECEIPT_STATEtable so it renders identically, and the engine's free-textmessageis never rendered even when present.openadapt-flow#290 is unblocked by this PR.
Say what broke, and what "fixed" means
Flow's local decision detail (
presentation.halt) carries closed values only: the typed halt category, the step's ordinal, its action kind, the target's role, and the target's label only when the engine proved that label is static control chrome rather than record content. The shell composes the sentence, because the wire carries no prose — that is what keeps a typed value off it:A withheld label degrades to the role noun alone, so a TYPE step reads "could not find the text field", never the value. The view also lists which resolution rungs were tried and their verdicts (rungs with no recorded evidence are shown as never attempted, not as failures), and what a continue will re-check before anything happens.
Distinguish the three answers by consequence
Which is what could not be told apart. Each button carries its consequence, and the card states it in full:
The two cosmetic defects, causes established
.actions { display: grid }outranks the user-agent[hidden] { display: none }rule, soactionBar.hidden = trueset an attribute that nothing acted on. Fixed with an explicit.actions[hidden] { display: none }.200px, which three two-line buttons exceed. The bar's real height is now measured into--action-bar-heightand released when it is hidden.Also: a fetch that never returns is now status
0— "the result is uncertain" — instead of an unhandled rejection leaving "Submitted, waiting…" on screen forever.Verification
src/portalShell.test.tsdrives the shippedapp.jsin a DOM rather than grepping it, because every existing source-text assertion passed while the surface a human reads showed every outcome as a refusal. 53 frontend tests, 737 Python tests,tsc && vite build, andruff checkpass locally; the loop was also driven end to end against a real halt over the live tunnel from a mobile device profile.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM