Submit Anthropic OAuth manual codes on the first mobile tap instead of requiring keyboard dismissal first. - Add a reusable touch action gesture hook that handles touch/pointer activation before synthetic clicks. - Wire the OAuth manual code Submit button to invoke submission on the first touch while preventing duplicate click handling. - Cover the mobile double-tap regression and document the UI bug pattern for future fixes. Files changed: .../oauth-manual-code-mobile-double-tap-submit.md | 60 +++++++++++ .../app/components/OAuthManualCodeForm.tsx | 31 +++++- .../__tests__/OAuthManualCodeForm.test.tsx | 110 +++++++++++++++++++++ .../hooks/__tests__/useTouchActionGesture.test.ts | 110 +++++++++++++++++++++ .../dashboard/app/hooks/useTouchActionGesture.ts | 89 +++++++++++++++++ 5 files changed, 399 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7953 Fusion-Task-Lineage: d387cdbd-25a7-4b7d-add6-27a1ded5cbea Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
5.7 KiB
title, date, category, module, problem_type, component, symptoms, root_cause, resolution_type, severity, related_components, tags
| title | date | category | module | problem_type | component | symptoms | root_cause | resolution_type | severity | related_components | tags | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| OAuth manual code mobile double-tap submit | 2026-07-14 | ui-bugs | packages/dashboard/app/components/OAuthManualCodeForm | ui_bug | frontend_auth_onboarding |
|
touch_event_ordering | code_fix | medium |
|
|
OAuth manual code mobile double-tap submit
Problem
On mobile/touch viewports, OAuthManualCodeForm's "Submit code" button relied solely on a plain onClick={onSubmit} handler. When the user typed or pasted a manual OAuth code (Anthropic subscription OAuth, or any other provider using manualCodeConfigs) and tapped "Submit code" while the textarea still held focus, some mobile browsers consumed that first physical tap solely to blur the focused textarea and dismiss the on-screen keyboard — suppressing or delaying the resulting synthetic click event. The button's onClick handler did not fire until a second, separate tap landed on the same button, so users had to tap "Submit code" twice to actually submit their auth code.
This is the same mobile-web event-ordering bug class already solved for the chat Send/Stop button (useStandardChatActionGesture + StandardChatActionButton in StandardChatSurface.tsx), but OAuthManualCodeForm had not been wired up to that pattern.
Solution
Extracted the proven gesture-handling logic from useStandardChatActionGesture into a new, generic, non-chat-coupled hook, useTouchActionGesture() (packages/dashboard/app/hooks/useTouchActionGesture.ts), and wired it into OAuthManualCodeForm's submit button:
onPointerDown(touch pointer types only, viaevent.pointerType !== "mouse") andonTouchStartboth callevent.preventDefault(), guard withbeginTouchActionGesture()(a same-tick re-entrancy guard so a single physical tap that dispatches bothpointerdownandtouchstartonly fires the action once), thenmarkHandledSendTouch()and invokeonSubmit()immediately (when notdisabled) — this is what makes the first tap submit instead of only dismissing the keyboard.onMouseDown={(event) => event.preventDefault()}avoids an equivalent mouse-driven blur race.onClickcallsconsumeHandledSendTouch()first; if it returnstruethe tap was already handled via the touch path above, so the synthetic click that follows is skipped — preventing a doubleonSubmitinvocation for one physical tap. Otherwise (a genuine non-touch/mouse click, or a touch environment where the touch handlers didn't fire) it callsonSubmit()directly when notdisabled.style={{ touchAction: "manipulation" }}on the button avoids double-firing from the browser's native double-tap-to-zoom gesture handling, matchingStandardChatActionButton.
StandardChatSurface.tsx and its existing useStandardChatActionGesture/StandardChatActionButton were left untouched — the new hook is a standalone extraction so non-chat consumers can reuse the same fix without depending on the chat component, keeping blast radius isolated to auth-code submission. Because the fix lives in the shared OAuthManualCodeForm component (rendered by both AuthenticationSection.tsx's Settings → Authentication tab and ModelOnboardingModal.tsx's onboarding "Connect AI providers" flow), every manual-OAuth-code provider inherits the single-tap fix without any caller changes.
Regression coverage
OAuthManualCodeForm.test.tsx covers the invariant, not just the reported repro:
- Mobile single-tap:
matchMediamocked mobile, textarea focused, then the real mobile browser sequence (touchstart→bluron the textarea →click) is dispatched once andonSubmitis asserted to have fired exactly once. - Duplicate event dispatch for one physical tap:
pointerdown(pointerType: "touch") andtouchstartboth fired for the same tap, followed byclick, still yields exactly oneonSubmitcall — proving the same-tick re-entrancy guard prevents double-firing when a browser dispatches both event families for one gesture. - Desktop/non-touch regression: mobile
matchMedianot matched, a plainclickwith no preceding touch/pointer events still submits exactly once — proving mouse-driven desktop submission is unaffected. disabledstate: the same touch/pointer/click sequence never invokesonSubmitwhiledisabledis true, from any path.- The two pre-existing
scrollIntoViewviewport-assist tests continue to pass unmodified, proving the mobile keyboard viewport-assist behavior is untouched by this fix.
useTouchActionGesture.test.ts unit-tests the hook directly: beginTouchActionGesture() returns true once and false on a same-tick re-entrant call (with fake timers proving it clears on the next tick); markHandledSendTouch() sets a flag that consumeHandledSendTouch() reads and clears exactly once; the handled flag auto-expires after its ~700ms timeout; and the pending timer is cleared on unmount.