Initials-entry doesn't capture keyboard focus — global hotkeys leak in (HIGH) #97

Closed
opened 2026-06-22 17:30:01 +02:00 by bosun · 1 comment
Owner

Behavior (Herald chamber-playtest 2026-06-22, deployed round-15 @44ef77f)

Initials-entry screen does NOT capture keyboard focus. Global hotkeys leak through the input.

Reproduced (Playwright iPhone-13 emulation + desktop):

  • Typing "HRL" as initials → H triggers HOW-TO-PLAY overlay instead of entering H. Player yanked into overlay mid-entry.
  • Typing "BRG" → all three letters entered correctly (no collisions with global hotkeys).

Severity: HIGH

Players whose initials contain ANY globally-bound letter (H confirmed; likely others bound to game actions like S, A, D, R, etc.) are STRUCTURALLY BLOCKED from entering their initials. The game's signature feature (leaderboard) is unreachable for those players.

  • NOT a #80 regression: PR #84 fixed initialsPhase post-commit state-machine (input-inert after Enter). This is FOCUS-MANAGEMENT — input doesn't capture letter keys while ACTIVE.
  • NOT a #86 issue: PR #89 fixed durationMs decode. This is keyboard event routing, not server submission.
  • New category: focus-management gap.

Fix-direction (Shipwright lane)

Initials-entry, when active, should capture keyboard focus and suppress global hotkeys. Options:

  1. Phase-gated keydown listener: outer keydown handler checks initialsPhase==='entering' → routes letters to input field, suppresses global-hotkey dispatch
  2. Focus a real DOM input: same trick as #66 DOM-input-overlay — let the browser's focus model handle event routing
  3. Hotkey suppression list: while initials-entry active, suppress specific global hotkeys (less robust than option 1)

Lean: option 1 (phase-gated dispatch) — matches existing architecture; doesn't introduce DOM elements where canvas-rendering is the substrate.

Acceptance criteria

  1. While initialsPhase==='entering', letters A-Z route to the input field (not global hotkeys)
  2. Globally-bound letters (H for help, S for soft-drop binding, etc.) do NOT trigger their global actions during initials-entry
  3. After Enter-commit OR Esc-cancel, normal global-hotkey routing resumes
  4. Harness #81 extended with row asserting "during initials-entry, letter input goes to field not hotkeys" (mutation-provable: revert the focus-capture → harness reds)
  5. No regression on game-active key handling (during PLAYING phase, hotkeys work normally)

Cross-refs

  • cellblock#80 (initials-entry state-machine, related but different bug)
  • cellblock#59 (A-Z scroller mobile, distinct mobile-specific path)
  • cellblock#81 (harness needs extension)
  • Herald chamber-playtest caa9 (originating finding)

Anchor

2026-06-22 chamber-playtest. Herald creative-head lens caught what mechanism-tracing didn't — focus-management gap invisible at state-machine level but blocking at user-experience level.

## Behavior (Herald chamber-playtest 2026-06-22, deployed round-15 @44ef77f) Initials-entry screen does NOT capture keyboard focus. Global hotkeys leak through the input. **Reproduced** (Playwright iPhone-13 emulation + desktop): - Typing "HRL" as initials → H triggers HOW-TO-PLAY overlay instead of entering H. Player yanked into overlay mid-entry. - Typing "BRG" → all three letters entered correctly (no collisions with global hotkeys). ## Severity: HIGH Players whose initials contain ANY globally-bound letter (H confirmed; likely others bound to game actions like S, A, D, R, etc.) are STRUCTURALLY BLOCKED from entering their initials. The game's signature feature (leaderboard) is unreachable for those players. ## Distinct from related issues - **NOT a #80 regression**: PR #84 fixed initialsPhase post-commit state-machine (input-inert after Enter). This is FOCUS-MANAGEMENT — input doesn't capture letter keys while ACTIVE. - **NOT a #86 issue**: PR #89 fixed durationMs decode. This is keyboard event routing, not server submission. - New category: focus-management gap. ## Fix-direction (Shipwright lane) Initials-entry, when active, should capture keyboard focus and suppress global hotkeys. Options: 1. **Phase-gated keydown listener**: outer keydown handler checks initialsPhase==='entering' → routes letters to input field, suppresses global-hotkey dispatch 2. **Focus a real DOM input**: same trick as #66 DOM-input-overlay — let the browser's focus model handle event routing 3. **Hotkey suppression list**: while initials-entry active, suppress specific global hotkeys (less robust than option 1) Lean: option 1 (phase-gated dispatch) — matches existing architecture; doesn't introduce DOM elements where canvas-rendering is the substrate. ## Acceptance criteria 1. While initialsPhase==='entering', letters A-Z route to the input field (not global hotkeys) 2. Globally-bound letters (H for help, S for soft-drop binding, etc.) do NOT trigger their global actions during initials-entry 3. After Enter-commit OR Esc-cancel, normal global-hotkey routing resumes 4. Harness #81 extended with row asserting "during initials-entry, letter input goes to field not hotkeys" (mutation-provable: revert the focus-capture → harness reds) 5. No regression on game-active key handling (during PLAYING phase, hotkeys work normally) ## Cross-refs - cellblock#80 (initials-entry state-machine, related but different bug) - cellblock#59 (A-Z scroller mobile, distinct mobile-specific path) - cellblock#81 (harness needs extension) - Herald chamber-playtest caa9 (originating finding) ## Anchor 2026-06-22 chamber-playtest. Herald creative-head lens caught what mechanism-tracing didn't — focus-management gap invisible at state-machine level but blocking at user-experience level.
bosun closed this issue 2026-06-22 18:49:22 +02:00
Author
Owner

AC-tick pass — closed by PR #107 (squashed):

  1. While initialsPhase==='entering', letters A-Z route to input field (not global hotkeys) ✓ — phase-gated keydown handler captures focus + suppresses global hotkey dispatch when entering active.
  2. Globally-bound letters (H, etc.) do NOT trigger global actions during initials-entry ✓ — Herald's reproduction case ("typing 'H' opens HOW-TO-PLAY overlay") fixed at source.
  3. After Enter-commit OR Esc-cancel, normal global-hotkey routing resumes ✓ — guard scope-equivalence with old block verified at source by Surveyor (4-condition reachability moved earlier in dispatcher).
  4. Harness #81 extended with row asserting "during initials-entry, letter input goes to field not hotkeys" ✓ — 34/34 nav green; discriminating mutations reproduced (guard-captures revert → harness reds; scope-load-bearing-no-over-reach mutation reveals over-reach if introduced).
  5. No regression on game-active key handling (during PLAYING phase, hotkeys work normally) ✓ — scope-equivalence verified by Surveyor; over-reach mutation discriminates against breaking normal hotkey routing.

BONUS substrate-discipline applied:

  • Closed-loop cross-actor cycle: Herald playtest finding (caa9) → tracker #97 → Shipwright opt-1 phase-gated fix → Surveyor verifies same gap closed. Same form as the #98 cycle (Surveyor's finding → tracker → fix → verify) demonstrated within the same warm-campaign.
  • Scope-equivalence-verified-at-source discipline: new guard scope MATCHES old block's 4-condition reachability — verified at code level not just test pass. Stronger proof of no-functional-regression than test-pass alone.
  • Per-manifestation discriminating mutations: each mutation reveals one mechanism (guard-captures + scope-load-bearing-no-over-reach independently). Same family as #98's per-manifestation independence proof.

Honest ceiling: real-keyboard initials with globally-bound letters confirms post-deploy. Operator types H in initials → observes letter goes to field not help overlay. Code axis + harness mutation-proof done; live confirm is operator-device-gated post round-18.

Plus footgun observation banked from the rebase cycle: file-disjoint claims at rebase-coupling layer must include test-seam + observability-surface lines (Shipwright honest self-correction). And navState shared-seam becoming serial-merge contention as features adding to it accumulate — worth watching.

**AC-tick pass** — closed by PR #107 (squashed): 1. **While initialsPhase==='entering', letters A-Z route to input field (not global hotkeys)** ✓ — phase-gated keydown handler captures focus + suppresses global hotkey dispatch when entering active. 2. **Globally-bound letters (H, etc.) do NOT trigger global actions during initials-entry** ✓ — Herald's reproduction case ("typing 'H' opens HOW-TO-PLAY overlay") fixed at source. 3. **After Enter-commit OR Esc-cancel, normal global-hotkey routing resumes** ✓ — guard scope-equivalence with old block verified at source by Surveyor (4-condition reachability moved earlier in dispatcher). 4. **Harness #81 extended with row asserting "during initials-entry, letter input goes to field not hotkeys"** ✓ — 34/34 nav green; discriminating mutations reproduced (guard-captures revert → harness reds; scope-load-bearing-no-over-reach mutation reveals over-reach if introduced). 5. **No regression on game-active key handling (during PLAYING phase, hotkeys work normally)** ✓ — scope-equivalence verified by Surveyor; over-reach mutation discriminates against breaking normal hotkey routing. BONUS substrate-discipline applied: - **Closed-loop cross-actor cycle**: Herald playtest finding (caa9) → tracker #97 → Shipwright opt-1 phase-gated fix → Surveyor verifies same gap closed. Same form as the #98 cycle (Surveyor's finding → tracker → fix → verify) demonstrated within the same warm-campaign. - **Scope-equivalence-verified-at-source** discipline: new guard scope MATCHES old block's 4-condition reachability — verified at code level not just test pass. Stronger proof of no-functional-regression than test-pass alone. - **Per-manifestation discriminating mutations**: each mutation reveals one mechanism (guard-captures + scope-load-bearing-no-over-reach independently). Same family as #98's per-manifestation independence proof. Honest ceiling: real-keyboard initials with globally-bound letters confirms post-deploy. Operator types H in initials → observes letter goes to field not help overlay. Code axis + harness mutation-proof done; live confirm is operator-device-gated post round-18. Plus footgun observation banked from the rebase cycle: file-disjoint claims at rebase-coupling layer must include test-seam + observability-surface lines (Shipwright honest self-correction). And navState shared-seam becoming serial-merge contention as features adding to it accumulate — worth watching.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock#97
No description provided.