Skip to content

fix(openworlds): wire the stuck-recovery 'Try again' to actually retry (Closes #344) - #346

Merged
100yenadmin merged 1 commit into
mainfrom
fix/344-343-residual
May 30, 2026
Merged

fix(openworlds): wire the stuck-recovery 'Try again' to actually retry (Closes #344)#346
100yenadmin merged 1 commit into
mainfrom
fix/344-343-residual

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 30, 2026

Copy link
Copy Markdown
Member

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 /move or 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-opens stuck, Declare relabels to "Try again", input is enabled but empty → click "Try again".

move count before "Try again" after
Before fix 1 1 — no retry fired (dead button)
After fix 1 2 — the stalled turn is re-POSTed

Root cause: #343 relabels the Declare button to "Try again" when a turn goes stuck, but it stayed wired to sendAction, which does const 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 → sendAction early-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:

  • Capture the in-flight move (already-neutralized move object + label + actionId) in a lastMoveRef on every postMove.
  • New 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 way postMove → armPending re-arms the narrating state + the 90s recovery / 12-min backstop.
  • Declare button + Enter route through 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.

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 --axeaxe total: 0 violations across all 17 screens; health summary PASS.
  • Both edited/affected JSX files transpile cleanly via the vendored in-browser Babel (babel-standalone-7.29.0).
  • Targeted headless CDP repros (zero-dependency Node WebSocket driver 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 /move call count, which a screenshot can't show:

# after first Declare (turn in flight)
declareBtn={"text":"Narrating…","disabled":true}  input={"disabled":true,"ph":"The Dungeon Master is narrating…"}  moves=[{"kind":"do","text":"I search the room for traps"}]
# after the 90s recovery fires (stuck)
declareBtn={"text":"Try again","disabled":false}  input={"value":"","disabled":false,"ph":"The DM seemed stuck — try again."}  stuckBeat=true  moves=[ …1 move… ]
# after clicking "Try again" (empty box) — PATCHED
declareBtn={"text":"Narrating…","disabled":true}  moves=[ …, {"kind":"do","text":"I search the room for traps"} ]   ← 2 moves: the stalled turn was re-POSTed
>>> VERDICT: moves 1 → 2  (before fix: 1 → 1, dead button)

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

    • Improved recovery when the system becomes unresponsive: players can now retry their last action or submit a new one to recover from a stuck state.
  • UI/UX

    • Unified the Declare button behavior to handle both new submissions and retry attempts through a consistent interface.

Review Change Stack

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

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

ScreenTable 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.

Changes

Stuck DM Recovery Flow

Layer / File(s) Summary
Move state capture for retry
viewer/openworlds/screen-table.jsx
Add lastMoveRef ref to store move details. Update postMove to capture the neutralized move object, rendered label, and actionId whenever a move is successfully posted.
Stuck recovery handler and routing
viewer/openworlds/screen-table.jsx
Implement retryStuck to either submit new typed input via sendAction or re-post the stalled move from lastMoveRef, guarded by toast when nothing is available. Add onDeclareClick handler to route Declare actions to retry when pendingStuck is true, otherwise send normally.
Declare input and button wiring
viewer/openworlds/screen-table.jsx
Wire the input's Enter key and the Declare button's click handler to onDeclareClick, ensuring stuck-retry behavior is used instead of the previous sendAction-only flow.

Possibly related PRs

  • electricsheephq/WorldOS#343: Modifies the same stuck-narration recovery state and UI gating in screen-table.jsx; both PRs work on the DM stuck recovery flow in tandem.
  • electricsheephq/WorldOS#328: Adjacent changes to the same ScreenTable move submission and pending state machine, including action-bar disablement and DM-narration affordances.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 When the DM gets stuck, don't despair,
We've stashed the last move with loving care!
Press "Try again" with a hop and a bound,
Or retype your command—we've got your back, sound! 🎲

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(openworlds): wire the stuck-recovery Try again to actually retry (Closes #344)' directly describes the primary change: fixing the non-functional recovery button by wiring it to retry the stalled move.
Description check ✅ Passed The PR description provides comprehensive detail: context of the two findings, the root cause analysis of the dead button, the implementation fix, validation evidence, and a clear assessment of both issues. While the CLA checklist is not explicitly marked, the description is substantive and complete for understanding the changes.
Linked Issues check ✅ Passed The PR fully addresses the linked issue #344's coding objectives: validates and fixes Finding 1 (the dead 'Try again' button by capturing the move in lastMoveRef and routing through retryStuck), confirms Finding 2 is not reproducible, and provides headless CDP validation evidence that the fixes work without engine wire-contract changes.
Out of Scope Changes check ✅ Passed All changes remain strictly scoped to the stated fix: one file (viewer/openworlds/screen-table.jsx, +39/−2) addressing the stuck-recovery retry logic and UI routing. No out-of-scope changes to engine, wire-contracts, or unrelated functionality are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@100yenadmin
100yenadmin merged commit d1ada0f into main May 30, 2026
6 of 7 checks passed
@100yenadmin
100yenadmin deleted the fix/344-343-residual branch May 30, 2026 00:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[playtest][P1] VALIDATE #343 completeness — veteran hit dead 'Try again' button + Table nav still blocked from Map/Journal

1 participant