From abd4574ee3d2c701c78f227db62e6109f0e390a3 Mon Sep 17 00:00:00 2001 From: Ricky Stretch Date: Sat, 28 Mar 2026 10:00:52 -0400 Subject: [PATCH] swipe bug fix --- frontend/src/components/ImageLightbox.jsx | 5 +++-- frontend/src/main.jsx | 4 +++- frontend/src/pages/Chat.jsx | 17 ++++++++++++----- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ImageLightbox.jsx b/frontend/src/components/ImageLightbox.jsx index cb9604d..8d011af 100644 --- a/frontend/src/components/ImageLightbox.jsx +++ b/frontend/src/components/ImageLightbox.jsx @@ -4,15 +4,16 @@ import { createPortal } from 'react-dom'; export default function ImageLightbox({ src, onClose }) { const overlayRef = useRef(null); - // Close on Escape + // Close on Escape; signal global pinch handler to stand down while open useEffect(() => { const handler = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handler); - // Prevent body scroll while open document.body.style.overflow = 'hidden'; + document.documentElement.dataset.lightboxOpen = '1'; return () => { window.removeEventListener('keydown', handler); document.body.style.overflow = ''; + delete document.documentElement.dataset.lightboxOpen; }; }, [onClose]); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index ad840d8..267355f 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -59,7 +59,9 @@ if ('serviceWorker' in navigator) { document.addEventListener('touchmove', function (e) { if (e.touches.length === 2 && pinchStartDist !== null) { - // Two-finger pinch: scale fonts, not viewport + // Two-finger pinch: scale fonts, not viewport. + // Skip when a lightbox is open — let the browser handle pinch natively there. + if (document.documentElement.dataset.lightboxOpen) return; e.preventDefault(); const ratio = getTouchDist(e) / pinchStartDist; const newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchStartScale * ratio)); diff --git a/frontend/src/pages/Chat.jsx b/frontend/src/pages/Chat.jsx index 426551f..f56331f 100644 --- a/frontend/src/pages/Chat.jsx +++ b/frontend/src/pages/Chat.jsx @@ -455,11 +455,15 @@ export default function Chat() { setUnreadGroups(prev => { const next = new Map(prev); next.delete(id); return next; }); }; - // Establish one history sentinel on mount (mobile only) so back gestures are - // always interceptable without accumulating extra entries. + // Establish two history entries on mount (mobile only): + // floor — marks the true exit point; always stays below the sentinel + // sentinel — intercepted by handlePopState on every back gesture + // Two entries are required so that iOS fires popstate (same-document navigation) + // before exiting, giving the handler a chance to push a new sentinel. useEffect(() => { if (window.innerWidth < 768) { - window.history.replaceState({ rc: 'chat' }, ''); + window.history.replaceState({ rc: 'floor' }, ''); + window.history.pushState({ rc: 'chat' }, ''); } }, []); @@ -486,8 +490,11 @@ export default function Chat() { return; } - // Already at root (Messages list, no chat open) — let the browser handle - // it so the next gesture actually exits the PWA. Don't re-push. + // Already at root (Messages list, no chat open) — we just popped the sentinel + // and are now on the floor entry. Step one more back so the browser exits the + // PWA (or navigates to the previous URL). Without this explicit go(-1), iOS + // leaves the user stranded on the invisible floor state. + window.history.go(-1); }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState);