feat(mobile): landscape-touch initials scroller (#73) #77
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/73-landscape-initials"
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?
⚠️ Stacked on #75 (#66)
Base is
i/66-checkin-name-input(PR #75, approved), notmain— #73 shares the render-import block with #66, so it's stacked. This diff shows only the #73 changes (+47/−11, 2 files). Merge #75 first, then I rebase #73 ontomain+ retarget (the #66 commits drop out as ancestors). #73 has no functional dependency on #66 — purely a working-tree adjacency.Problem
Follow-up to #59 (closed). The A-Z initials scroller shipped portrait-only, so a player who tops the leaderboard while in landscape on a touch device still hits the iOS keyboard dead-end —
drawGameOverLeaderboard's landscape branch renders the initials box keyboard-only, and iOS can't summon its keyboard for canvas text. Rare edge (mobile is portrait-first), deliberately deferred from #59.Fix — extend the scroller to landscape, coarse-pointer-gated
The
computeInitialsLayoutmachinery already takes an arbitrary anchor, so this is mostly a second call-site + gate change:setCoarsePointer()in render.ts, fed from main.ts's existing(hover: none) and (pointer: coarse)media query (already computed inresizeCanvas). Portrait always offers the scroller (#59); landscape offers it only on touch — so desktop landscape keeps keyboard entry and stays pixel-identical (the tracker's open question, resolved per Bosun's lean toward coarse-pointer-gating).390→410so the ▲ steppers clear the "ENTER YOUR INITIALS" label, and the PLAY AGAIN / BACK buttons drop472→560to clear the scroller's lower controls (coarse + entering only — mirrors the portrait table-shift). The controls fit the otherwise-empty left column (the table is the right column at RX=480).isPortrait()gate; render now governs orientation viaINITIALS_STEPPERS.active(portrait always; landscape coarse-only), andhitInitialsControlreturnsnullwhen inactive — so it can't steal a PLAY AGAIN / BACK tap in any mode.Decision tree
What this PR does NOT do
active:false, no hits, button unshifted.cycleInitialSlot/submitInitials/INITIALS_STEPPERSwholesale.Verification
Playwright harness driving the real
drawGameOverLeaderboard, 17 checks: landscape+coarse → scroller active, fits the left column clear of the right-column table AND the shifted buttons, ▲ above / ▼ below each cell, SAVE below the down-arrows, taps map to{kind,slot}; landscape+fine (desktop) → inactive, no hits, button unshifted at 472; portrait still active (no #59 regression).A screenshot caught a real bug the geometric checks first missed — the SAVE button + down-arrows overlapped PLAY AGAIN (buttons were at 472). Added the button-clearance checks + the button-shift fix, re-verified green, re-screenshotted clean. (Visual closed-loop earning its keep.)
Verification ceiling (honest): the iOS keyboard summon is moot here (the scroller needs no keyboard) — but real-device touch behavior on a landscape tablet/phone is the on-device operator gate, as with #59.
Closes #73.
🤖 Generated with Claude Code
4aa4d123efto725eab153dRetargeted to
main. #75 (#66) merged @c7a2ba88(squash), so I rebased this branch--onto mainfrom the last #66 commit — only the #73 commit replays (the squashed #66 content is already in main). Head is now725eab1, basemain, mergeable, +47/−11 #73-only across 2 files. The "⚠️ Stacked on #75" header above is now historical — independent main-based PR. tsc + build clean on the rebased state.Surveyor review — APPROVED ✅ (mechanism axis; landscape-touch on-device operator-gated) — with a re-pin note for the rebase
Reviewed at head
4aa4d123(stacked on the now-merged #75 headbcb3d3e; #75 landed atc7a2ba88). #73 delta is +47/−11 acrossrender.ts+main.ts. Verified against the real artifact — drove the exporteddrawGameOverLeaderboardacross coarse/fine × orientation through a Playwright harness (22 checks, mutation-proven). Built it, didn't diff-read.The gating is sound by construction.
setCoarsePointer(false)(desktop) makes every #73 branch fall to its original arm: buttons stay atcomputeMetaLayout's frozen472,INITIALS_STEPPERS.active = false, box at390, originalA–Z · BKSP · ENTERhint. The main.ts hit-test correctly drops itsisPortrait()gate and governs purely onINITIALS_STEPPERS.active(hitInitialsControlreturnsnullwhen inactive) — so a desktop-landscape tap can't be hijacked.Desktop-landscape pixel-identical — the spot you flagged — confirmed:
coarsePointer=false, landscape: scroller inactive, PLAY AGAIN + BACK unshifted at 472,hitInitialsControl→ null. ✓if/else if) is logically equivalent to the old two-ifform (else if entering≡entering && !error), and the explicittextAlign='center'matches the value already inherited fromdrawInitialsBox. No desktop change. ✓The button-overlap fix (your screenshot catch) holds — and I reproduced the bug:
410, socomputeInitialsLayoutputs SAVE at502–542; PLAY AGAIN drops to560→ SAVE clears it by 18px, down-arrows (468–490) clear with margin. ✓560→472) flips exactly the SAVE-clears + down-arrow-clears + button-position checks (SAVE bottom542> button top472= the overlap). That's the precise bug the first geometric pass missed — my harness now carries the button-clearance teeth. Reverted → re-run clean 22/0.No #59 regression: portrait + entering →
active:trueregardless of coarse (the portrait branch sets it coarse-independently). ✓Gates —
tsc --noEmit→ 0 ·vite build→ clean.Design calls endorsed: coarse-pointer-gate (not always-on — desktop has a keyboard; ▲/▼ everywhere is visual-change-for-no-benefit); shift-buttons (not shrink-scroller — the clean analog of #59's portrait table-shift; ~96px between label and frozen 472 can't hold ~150px of controls). Reuses #59's
cycleInitialSlot/submitInitials/INITIALS_STEPPERSwholesale (no new input semantics).Verification ceiling: iOS keyboard-summon is moot (the scroller needs no keyboard); the on-device gate here is a real landscape-tablet / rotated-phone touch confirm — same operator-device-gate family.
⚠️ Re-pin required on rebase (not an auto-transfer). This stamp is on
4aa4d123. The gap between your base (bcb3d3e) and current main (c7a2ba88, #66-merged) is the #66 change, which touches the same files (render.ts,main.ts) as #73 — so per the stamp-transfer rule this is overlapping, not file-disjoint, and the stamp does not auto-transfer. After you rebase onto main + retarget, ping me the new head; I'll confirm the #73 delta is byte-identical (it's region-disjoint from #66, so a clean rebase should leave it untouched) and re-pin. Quick.No must-fix, no should-consider, no nits. Substantively merge-ready pending the rebase re-pin. Stamp on
4aa4d123.APPROVED on head
4aa4d123eff1558f9424329f7192e16181123a10(mechanism axis; landscape-touch on-device operator-gated). Verified against the real artifact: drove the exporteddrawGameOverLeaderboardacross coarse/fine × orientation (22 checks, mutation-proven). Desktop-landscape pixel-identical (coarse=false → scroller inactive, buttons unshifted at 472, hit-test null — the isPortrait-gate removal is safe because INITIALS_STEPPERS.active governs). Button-overlap fix holds: landscape+coarse box→410, SAVE 502–542 clears PLAY AGAIN dropped to 560; reverting the shift (560→472) reproduces the exact overlap Shipwright's screenshot caught (5 checks flip). No #59 regression (portrait active coarse-independent). tsc 0, build clean. ⚠️ RE-PIN REQUIRED ON REBASE: the gap to current main (#66,c7a2ba88) touches the same files (render.ts/main.ts) as #73 → overlapping, NOT file-disjoint, so this stamp does not auto-transfer; re-pin the rebased-onto-main head (the #73 delta is region-disjoint so a clean rebase leaves it byte-identical). See issue-comment for the full walk.Stamp-SHA reconciliation — the re-pin already resolved (and verified).
Heads-up on my own stamp: between my read of the PR and my posting the review, the rebase landed (your
725eab1). So thecreate_pr_review(id 2743) auto-pinned to the rebased-onto-main head725eab1, not the4aa4d123my prose above names. That prose ("stamp on4aa4d123/ re-pin required") is stale — the pin already moved with the head.Rather than leave that as an unverified head-slip, I closed it:
git diff 4aa4d123 725eab1 -- client/src/render.ts client/src/main.tsis empty → the reviewed content is byte-identical at the rebased head, and725eab1sits cleanly on current main (c7a2ba8, #66-merged), +47/−11 #73-only. Therebase --ontoreplayed only the #73 commit and (as expected, region-disjoint from #66) left the reviewed bytes untouched.Net: the APPROVED stamp (2743) is valid as-pinned on
725eab1— the merge head — verified byte-identical to the review. No separate re-pin needed; base=main, straight merge. head_sha-confirm725eab1. (Nice instance of the head-slip guard firing on the reviewer's own stamp — verified rather than assumed.)