fix(dashboard): self-clear mobile send/stop dedupe ref so subsequent taps work
The previous fix introduced a regression: handledMobileSendRef / handledMobileActionRef was set in onPointerDown/onTouchStart but only cleared in onClick. preventDefault() on pointer/touch events suppresses the synthesized click on iOS Safari, so the ref stayed true forever after the first tap — every subsequent tap (quick OR long press) hit the new dedupe guard and silently bailed. Now both handlers schedule a 500ms setTimeout to self-clear the ref alongside their action. That covers the full pointerdown/touchstart/ click burst from one tap while still letting the next user tap go through. Applied to: ChatView send button, QuickChat send button, QuickChat stop button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1998,22 +1998,21 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
if (typeof window === "undefined" || window.innerWidth > 768) return;
|
||||
event.preventDefault();
|
||||
if (event.pointerType && event.pointerType !== "mouse") {
|
||||
// On mobile, both onPointerDown and onTouchStart fire
|
||||
// for a quick tap. Without this guard handleSend runs
|
||||
// twice — the second call closes the first's stream
|
||||
// (useChat.ts: streamRef.current.close()) and the
|
||||
// server-side beginGeneration aborts the in-flight
|
||||
// generation, leaving the chat with no output. A
|
||||
// long-press doesn't fire both events the same way,
|
||||
// which is why holding the button "fixes" sending.
|
||||
// Dedupe across the pointerdown/touchstart/click burst
|
||||
// from a single tap. preventDefault() on pointer/touch
|
||||
// can suppress the synthesized click that previously
|
||||
// cleared this ref, so we self-clear it via a short
|
||||
// timeout — otherwise once set the next tap silently
|
||||
// bails (long press stops working too).
|
||||
if (handledMobileSendRef.current) return;
|
||||
handledMobileSendRef.current = true;
|
||||
markPreserveComposerFocus();
|
||||
focusComposerInput();
|
||||
void handleSend();
|
||||
window.setTimeout(() => {
|
||||
handledMobileSendRef.current = false;
|
||||
preserveComposerFocusRef.current = false;
|
||||
}, 1500);
|
||||
}, 500);
|
||||
}
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
@@ -2025,8 +2024,9 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
focusComposerInput();
|
||||
void handleSend();
|
||||
window.setTimeout(() => {
|
||||
handledMobileSendRef.current = false;
|
||||
preserveComposerFocusRef.current = false;
|
||||
}, 1500);
|
||||
}, 500);
|
||||
}}
|
||||
onMouseDown={(event) => {
|
||||
if (typeof window === "undefined" || window.innerWidth > 768) return;
|
||||
|
||||
@@ -2474,6 +2474,9 @@ export function QuickChatFAB({
|
||||
if (handledMobileActionRef.current) return;
|
||||
handledMobileActionRef.current = true;
|
||||
stopStreaming();
|
||||
window.setTimeout(() => {
|
||||
handledMobileActionRef.current = false;
|
||||
}, 500);
|
||||
}
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
@@ -2482,6 +2485,9 @@ export function QuickChatFAB({
|
||||
if (handledMobileActionRef.current) return;
|
||||
handledMobileActionRef.current = true;
|
||||
stopStreaming();
|
||||
window.setTimeout(() => {
|
||||
handledMobileActionRef.current = false;
|
||||
}, 500);
|
||||
}}
|
||||
onMouseDown={(event) => {
|
||||
if (typeof window === "undefined" || window.innerWidth > QUICK_CHAT_DESKTOP_BREAKPOINT) return;
|
||||
@@ -2507,17 +2513,18 @@ export function QuickChatFAB({
|
||||
if (typeof window === "undefined" || window.innerWidth > QUICK_CHAT_DESKTOP_BREAKPOINT) return;
|
||||
event.preventDefault();
|
||||
if (event.pointerType && event.pointerType !== "mouse") {
|
||||
// Guard against the same touch firing both
|
||||
// onPointerDown and onTouchStart, which would call
|
||||
// handleSendMessage twice — the second call closes
|
||||
// the first's stream and the server-side
|
||||
// beginGeneration aborts the in-flight generation,
|
||||
// producing no output. (See ChatView send button.)
|
||||
// Dedupe across pointerdown/touchstart/click for one
|
||||
// tap; self-clear via setTimeout because preventDefault
|
||||
// can suppress the synthesized click that previously
|
||||
// cleared this ref. (See ChatView send button.)
|
||||
if (handledMobileActionRef.current) return;
|
||||
handledMobileActionRef.current = true;
|
||||
markPreserveComposerFocus();
|
||||
focusComposerInput();
|
||||
void handleSendMessage();
|
||||
window.setTimeout(() => {
|
||||
handledMobileActionRef.current = false;
|
||||
}, 500);
|
||||
}
|
||||
}}
|
||||
onTouchStart={(event) => {
|
||||
@@ -2528,6 +2535,9 @@ export function QuickChatFAB({
|
||||
markPreserveComposerFocus();
|
||||
focusComposerInput();
|
||||
void handleSendMessage();
|
||||
window.setTimeout(() => {
|
||||
handledMobileActionRef.current = false;
|
||||
}, 500);
|
||||
}}
|
||||
onMouseDown={(event) => {
|
||||
if (typeof window === "undefined" || window.innerWidth > QUICK_CHAT_DESKTOP_BREAKPOINT) return;
|
||||
|
||||
Reference in New Issue
Block a user