From ebbd0fd125c9517fb9fce5daf69f0a10955ee1a6 Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Sat, 25 Jul 2026 15:24:05 +0700 Subject: [PATCH] Fix Sasquatch panel opening then instantly closing on mobile On touch devices, the bubble's pointerup opened the panel and unmounted the bubble; the synthesized "ghost click" from the same tap then landed on the panel's minimize/close button (which renders in roughly the same bottom-left spot), instantly re-closing it. Fix: preventDefault/stopPropagation on pointerup to suppress the ghost click, guard releasePointerCapture against unmount throws, and add a 400ms "just opened" lock so stray close/minimize events from the same tap are ignored. Co-authored-by: shakespeare.diy --- src/components/SasquatchGuide.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/SasquatchGuide.tsx b/src/components/SasquatchGuide.tsx index 8324025..88ac9e3 100644 --- a/src/components/SasquatchGuide.tsx +++ b/src/components/SasquatchGuide.tsx @@ -365,6 +365,10 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) { const dragRef = useRef(null); const dragStart = useRef<{ mx: number; my: number; bx: number; by: number } | null>(null); const dragMoved = useRef(false); + // Timestamp of the last panel-open via tap. Used to ignore stray + // close/minimize events that fire from the same tap on mobile. + const justOpenedRef = useRef(0); + const recentlyOpened = () => Date.now() - justOpenedRef.current < 400; // Dragging handlers const onPointerDown = useCallback((e: React.PointerEvent) => { @@ -389,13 +393,27 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) { }, [setDragPos]); const onPointerUp = useCallback((e: React.PointerEvent) => { - if (dragRef.current) dragRef.current.releasePointerCapture(e.pointerId); + // Suppress the synthesized mouse "click" that follows a touch pointerup. + // Without this, on mobile WebViews the ghost click lands on whatever element + // is now under the finger (the panel unmounts the bubble the instant we + // open) and can immediately re-close the panel — the "opens then closes" + // bug. + e.preventDefault(); + e.stopPropagation(); + try { + if (dragRef.current) dragRef.current.releasePointerCapture(e.pointerId); + } catch { + // releasePointerCapture can throw if the element is unmounting — ignore. + } const wasDrag = dragMoved.current; dragStart.current = null; dragMoved.current = false; setTimeout(() => setIsDragging(false), 50); if (!wasDrag) { - // Always open the panel — even on pages without a context message + // Always open the panel — even on pages without a context message. + // Set a short "just opened" lock so a stray follow-up event from the same + // tap can't immediately minimize/close it. + justOpenedRef.current = Date.now(); setIsMinimized(false); setIsVisible(true); } @@ -739,7 +757,7 @@ Instructions:
{/* Minimize — shrinks to draggable bubble */} {/* Close — hides the panel entirely */}