fix(openworlds): wire the stuck-recovery 'Try again' to actually retry (Closes #344) - #346
Conversation
Closes #344) #343 added a 90s 'stuck' recovery that re-enables the action bar and relabels Declare to 'Try again' when the DM stalls. But the button was wired to sendAction, which reads input.trim() — and the first submit clears the input box (setInput('')). So by the time the bar re-opens stuck, the box is empty, sendAction early-returns, and the click is a silent no-op. The #324-v2 veteran (vet1) hit exactly this: DM timed out on turn 1, 'Try again' did nothing. Fix (viewer-only; engine stays sole writer; no /move or wire-contract change): - Capture the in-flight move (already-neutralized move object + label + actionId) in a lastMoveRef on every postMove. - New retryStuck() handler: if the player typed NEW text into the re-opened box, send that (the 'or rephrase' path, which already worked); otherwise re-POST the exact stalled move. Either way postMove → armPending re-arms the narrating state + the 90s recovery / 12-min backstop timers. - The Declare button + Enter key route through onDeclareClick (pendingStuck ? retryStuck() : sendAction()), so non-stuck behavior is unchanged. The other #344 finding — Table nav still blocked from Map/Journal during narration — did NOT reproduce: headless CDP repro against the real JSX shows the nav rail fully interactive during an active pending turn (all 6 buttons enabled, pointer-events:auto, hit-test uncovered) and a clean Table→Map→Table and Table→Journal→Table round-trip with the in-flight turn preserved. #341's app-level/pending-agnostic nav holds. qa/ui_audit_health.sh --quick --axe: axe total 0 across all 17 screens.
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughScreenTable now persists the last successfully posted move and implements a "Try again" recovery flow. When stuck, it either resubmits new user input through the normal path or re-posts the stalled move verbatim from storage. The Declare UI (Enter key and button) routes through a unified handler that chooses the appropriate behavior. ChangesStuck DM Recovery Flow
Possibly related PRs
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Closes #344
Validates the two #324-v2 veteran (vet1) findings filed against current main (post-#343), then fixes the one that genuinely reproduces. Viewer-only; engine stays the sole writer — no
/moveor wire-contract change. One file:viewer/openworlds/screen-table.jsx(+39/−2).Finding 1 — the "Try again" recovery button is DEAD → REAL, fixed
Repro (headless CDP against the real JSX, no engine): drive a live surface → type a
do, click Declare (fires/move, input clears + disables, narrating beat shown, 90s+12min timers armed) → fire the 90s recovery → bar re-opensstuck, Declare relabels to "Try again", input is enabled but empty → click "Try again".Root cause: #343 relabels the Declare button to "Try again" when a turn goes
stuck, but it stayed wired tosendAction, which doesconst text = input.trim(); if (!text) return;. The first submit clears the box (setInput("")), so by the time the bar re-opens stuck the box is empty →sendActionearly-returns → the click is a silent no-op. (#343's own evidence missed this because that repro left/re-typed text in the box; the veteran's real turn-1 box was empty.)Fix:
moveobject +label+actionId) in alastMoveRefon everypostMove.retryStuck(): if the player typed new text into the re-opened box, send that (the "or rephrase" path — already worked); otherwise re-POST the exact stalled move. Either waypostMove → armPendingre-arms the narrating state + the 90s recovery / 12-min backstop.onDeclareClick = pendingStuck ? retryStuck() : sendAction(), so non-stuck behavior is byte-for-byte unchanged.Both branches verified: empty-box "Try again" → re-sends the stalled move; new-text "Try again" → sends the new text (last move = the rephrase).
Finding 2 — Table nav still blocked from Map/Journal during narration → NOT reproducible
Repro (headless CDP, real JSX): arm an active pending turn on Table (
input.disabled=true, placeholder "The Dungeon Master is narrating…"), then exercise the nav rail.disabled:false,pointer-events:auto, hit-test uncovered (the button is the topmost element at its center).Table → Map → Table: returns to Table, in-flight turn preserved (input.disabledstill true on return — [playtest][P1] DM narration SILENTLY DROPPED when player navigates away mid-turn — lost turn / story hole #340's lifted state working).Table → Journal → Table: same clean round-trip.This matches #341: the nav rail / tab bar live at the App level and are pending-agnostic; the one churn hazard (the narrating dots'
transform: scale) is already opacity-only. The veteran's nav complaint was most likely that now-fixed continuous-motion actionability artifact. No nav change made.Validation
qa/ui_audit_health.sh --quick --axe→ axe total: 0 violations across all 17 screens; health summary PASS.babel-standalone-7.29.0).WebSocketdriver over the real JSX) confirm before→after for finding 1 and not-repro for finding 2.Evidence
Verbatim CDP repro snapshots (real JSX, the patched build) — the load-bearing evidence is the
/movecall count, which a screenshot can't show:Rephrase branch (type new text, then "Try again"):
moves = [ {"text":"I sneak past the guard"}, {"text":"I bribe the guard instead"} ]— the new text is sent.A local screenshot of the stuck state (toast "The Dungeon Master seems stuck" + the stuck beat in the chronicle + the re-enabled bar with the "Try again" button) was also captured during the repro.
Please admin-merge on green — do not auto-merge.
Summary by CodeRabbit
Bug Fixes
UI/UX