fix(mobile): suppress double-tap-zoom on rapid touch-control taps (#64) #65
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/64-rapid-tap-zoom"
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
Fixes #64. After PR #61 (#60) killed the canvas long-press loupe, the operator's follow-up device pass found one remaining edge: occasional double-tap-zoom / magnifier while dashing the touch buttons quickly. Pure CSS (11 lines,
client/index.html).Root cause — three gaps a naive "touch-action on buttons" fix would miss
.tc-btnalready hadtouch-action: manipulation, so the obvious fix was a no-op. The real failure mode:touch-actiondoes not inherit — #60's#appdeclaration (it set user-select/callout, not touch-action) never reached the buttons' containers anyway.touch-action—#touch-controlspadding + the.tc-rowflex-gaps between buttons defaulted toauto, so a rapid tap landing on a gap fired the double-tap-zoom heuristic.user-scalable=nois ignored by modern iOS Safari — the viewport meta looks like a backstop but isn't one (so tracker fix-direction 4 wouldn't have helped).Fix
touch-action: manipulationdeclared explicitly on#app,#touch-controls, and.tc-row(the gaps — the real miss), kept on.tc-btn.-webkit-touch-callout: noneadded explicitly on the buttons +#touch-controls(defense-in-depth against the magnifier, not relying on the#appinheritance chain — per tracker fix-directions 1+3). Canvas keepstouch-action: none(its swipe handling) — #60's suppression untouched (AC5).manipulationremoves double-tap-zoom + the legacy 300ms delay without blocking pointer/click events, so single taps still register as game inputs (AC3).Verification
Playwright iPhone-13 (
/tmp/cb64-verify.js, ephemeral):touch-action: manipulationcomputed on#app/#touch-controls/.tc-row/.tc-btn;canvasstaystouch-action:none;#appuser-select:none(AC5 — #60 intact); 7 touch buttons present; no JS errors.tsc --noEmitclean.Honest boundary: the actual iOS zoom/magnifier behavior is Safari-only — Chromium can't reproduce the gesture heuristics, so the probe proves the mechanism is wired + scoped + #60 untouched; the behavioral confirmation is the operator device loop (same instrument-vs-confirmation split as #60/#58). AC1/AC2/AC4 are the device pass.
Scope / compose
Standalone S-size CSS (not folded into #63) to preserve #63's clean review state + keep the hot device-iteration loop fed. Branched off main (has #60). Composes with #63 (#57 portrait layout) — #63 touches different index.html rules (tc-hidden + portrait media query); trivial rebase whichever merges first.
🤖 Generated with Claude Code
Surveyor review — APPROVE ✅
Verified at head
2fdb6e0ca401965491f870674f94e4aab0a27e66, merge-base = current main tip0b4480e(clean, on current main). CSS-only, +11.Mechanism is correct.
touch-actiondoes not inherit, so suppressing double-tap-zoom requires an explicit declaration on each interactive container — and that's exactly what this does:manipulationon#app,#touch-controls,.tc-row(.tc-btnalready had it). The root-cause framing checks out: taps landing in the bar's padding or a row's flex-gap hit a container whose defaulttouch-actionwasauto, so the gesture fired.AC5 verified —
canvasis untouched. Thecanvas { … touch-action: none }rule (line 61) is not in the diff (the only "canvas" mention is the explanatory comment). #60's swipe handling + suppression survive intact.Verified (iPhone-13 probe, on bytes I confirmed were actually served):
appTA / tcTA / rowTA / btnTAall computemanipulation;canvasTAnone(AC5);#app userSelectnone(#61 suppression intact under composition); 7 buttons; zero page errors. PASS.vite buildclean; all 4touch-action: manipulationdeclarations present in the built bundle.Composition verified (this is a cross-PR situation):
#app userSelect:noneintact +touch-actionco-resides — probe-confirmed.0940651, approved, not yet merged — both touch index.html): I built the 3-way (cherry-picked #65 onto #63's rebased head) → clean auto-merge, zero conflicts, all fourtouch-action+ both #63 portrait blocks + canvasnonesurvive. They compose. As long as neither branch is force-pushed before merge, both APPROVED stamps stay valid and Forgejo's merge-time 3-way is pre-verified clean; only a rebase-merge (head-rewriting) of whichever lands second would need a re-pin.must-fix: none. should-consider: none beyond the familiar uncommitted-probe note (
cb64-verify.js).nit (harmless redundancy): the added
-webkit-touch-callout: noneon#touch-controlsand.tc-btnis redundant — that property inherits, so both already receivenonefrom#app(#61). It doesn't hurt (defensive explicitness), but the load-bearing change here is thetouch-actiondeclarations; the callout re-declares are belt-and-suspenders, not the fix.Clean to merge. Stamping APPROVED below, pinned to the verified head.
APPROVED at head
2fdb6e0ca401965491f870674f94e4aab0a27e66(on current main). CSS-only. touch-action:manipulation correctly declared per-container (#app/#touch-controls/.tc-row; .tc-btn pre-existing) since touch-action doesn't inherit. AC5 verified: canvas touch-action:none untouched (#60 swipe intact). iPhone-13 probe PASS on freshly-served bytes (all containers manipulation, canvas none, #app userSelect none, 7 btns, 0 errors). Composition with #61 (merged) probe-confirmed + with #63 (0940651) built clean (zero conflicts, all effects survive). nit: redundant -webkit-touch-callout re-declares (that prop inherits) — harmless. Substance in issue-comment above. Clean to merge.