fix(client): touch BACK affordance on multiplayer check-in (#88) #136
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/88-checkin-touch-back"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Resolves the mobile-keyboard-trap audit (#88). The audit's full enumeration is posted as a comment on #88 (the audit deliverable, AC1). This PR implements the single fix the audit confirmed at source.
The audit, in one line
Enumerated all 12 client screens/phases for keyboard-Esc vs touch-back symmetry, verified each flag at source (the sub-agent enumeration was hypothesis-form; per-item source-verify is what shrinks-or-grows the batch). Result: exactly one genuine mobile-keyboard-trap —
checkin.checkin(name entry): touch-reachable via VERSUS, but Esc-back was keyboard-onlyHIGH SCORESoverlay — keyboard-only-reachable (Lkey, no touch open affordance), so a touch user can't enter it → can't be trapped; symmetric in the way that mattersmatched(committed-post-match soft lock),countdown(input-frozen focus beat, #110)The fix
checkinis reached by tapping VERSUS (main.ts) but its only back-out wasEsc → welcome(#79). A phone has no Esc key, and every tap on the screen otherwise just re-summons the keyboard (#66), so a touch player was trapped at name-entry. Added a touch BACK mirroring #85'sLOBBY_BACK_BTNexactly:CHECKIN_BACK_BTNmutable-layout rect, recomputed each frame indrawCheckinfrom liveCANVAS_W+ the check-in box geometry, painted via the sharedbutton()helper. Centered below the box+hint (clear of the #66 DOM input that overlays the box, and of the portrait top-left audio toolbar).inCheckinBackButtonreads the same live rect → the tap lands where it's drawn in both orientations.focusNameInputcatch-all (which would otherwise swallow the tap), transitioning to'title'exactly like the Esc handler; the render loop hides+blurs the input on the screen change → no keyboard-trap.Verification
npx tsc --noEmitclean; fullnav.spec.tsgreen (38 tests).screen → 'title').inCheckinBackButtonhit-test branch reds the test — the tap falls through to the keyboard re-summon andscreenstays'checkin'. Restored precisely (re-edit, not checkout).Design call (implementer-latitude, pattern was clear)
Placed BACK below the content (same as the #85 lobby BACK) rather than a top-corner affordance — the portrait audio toolbar (#btn-mute) reflows over the top-left, and the centered check-in content reflows nowhere, so below-content is clear in both orientations. A top-bar back would collide with the toolbar in portrait.
What this PR does NOT do
matched/countdown— intentional input locks, not traps.Closes #88
🤖 Generated with Claude Code
✅ APPROVED — checkin touch BACK (#88)
Reviewed at head
54784e7(on current main0ede7e0). The one genuine mobile-keyboard-trap the audit confirmed — cleanly fixed, mirroring the #85 pattern.The fix is correct, and the ordering is the load-bearing part
The
inCheckinBackButtonhit-test is wired first, before thefocusNameInputre-summon catch-all:That ordering is exactly what makes it work — without the early hit-test+return, the catch-all would swallow the BACK tap and re-summon the keyboard (the trap).
screen='title'then triggers the render loop's hide+blur of the #66 DOM input, so no lingering keyboard. Mirrors the keyboard Esc-back (#79) on the touch path.Mirrors #85 + placement verified
CHECKIN_BACK_BTNis the same mutable-layout-export pattern asLOBBY_BACK_BTN(rect recomputed each frame in drawCheckin from liveCANVAS_W+ box geometry;inCheckinBackButtonreads the same live rect → tap lands where drawn in both orientations). Render-capture confirms it: BACK sits centered below the box+hint in both portrait and landscape — clear of the box that the #66 DOM input overlays (box.y+130vs the input atbox.y..box.y+h), so a tap reaches the canvas handler, not the input. That's the geometry that matters.Verification
#88: touch BACK → welcomerow mirrors the #85 touch-back test).focusNameInput→ screen stayscheckin→ the test reds. Revert clean.Audit discipline — exemplary
The #88 deliverable (12 screens, each verified at source) correctly fires the filed-rootcause-is-hypothesis discipline: the sub-agent's HIGH SCORES "2nd trap" was verified and excluded (keyboard-only-reachable, no touch open path → not a trap), and the genuinely-separable latent finding (the desktop HIGH SCORES click-through) was filed as #137 rather than dropped or scope-crept into this PR. I confirmed #137 exists + is well-specified. Scope is honest end to end.
Clean, minimal, pattern-consistent, mutation-proven. Closes #88. Merge-ready → Bosun.