From a1fb2dfd1aa184a31dcf2e1ec48814dc53eeb38e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 30 Apr 2026 05:58:13 -0700 Subject: [PATCH] mobile: preserve dashboard scroll on back; allow iOS edge-swipe Two related mobile UX fixes: - Dashboard now persists window.scrollY to sessionStorage (keyed by dongleId) and restores on remount. Closing a route via the X button or via OS/browser back returns the user to where they were in the list, instead of jumping to the top. - PullDownReload's document-level passive:false touchstart was swallowing the iOS system edge-swipe-back gesture. Skip touches that start within 30px of either screen edge so the OS gesture is preserved while pull-to-reload still works in the middle. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/Dashboard/index.jsx | 24 +++++++++++++++++++++++- src/components/utils/PullDownReload.jsx | 8 ++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/Dashboard/index.jsx b/src/components/Dashboard/index.jsx index 1bd585ef..2efd0e03 100644 --- a/src/components/Dashboard/index.jsx +++ b/src/components/Dashboard/index.jsx @@ -1,4 +1,4 @@ -import React, { lazy, Suspense } from 'react'; +import React, { lazy, Suspense, useEffect } from 'react'; import { connect } from 'react-redux'; import Obstruction from 'obstruction'; @@ -18,7 +18,29 @@ const DashboardLoading = () => ( ); +// Persist the dashboard's scroll position across navigation so closing a route +// returns the user to where they were. Stored in sessionStorage keyed by +// dongleId so different devices don't stomp each other. +const useDashboardScrollRestore = (dongleId) => { + useEffect(() => { + if (!dongleId) return undefined; + const key = `dashboard-scroll-${dongleId}`; + + const saved = sessionStorage.getItem(key); + if (saved !== null) { + // Defer to next frame so children have a chance to lay out before we scroll. + requestAnimationFrame(() => window.scrollTo(0, parseInt(saved, 10))); + } + + const onScroll = () => sessionStorage.setItem(key, String(window.scrollY)); + window.addEventListener('scroll', onScroll, { passive: true }); + return () => window.removeEventListener('scroll', onScroll); + }, [dongleId]); +}; + const Dashboard = ({ primeNav, device, dongleId }) => { + useDashboardScrollRestore(primeNav ? null : dongleId); + if (!device || !dongleId) { return ; } diff --git a/src/components/utils/PullDownReload.jsx b/src/components/utils/PullDownReload.jsx index 51015daf..7579a616 100644 --- a/src/components/utils/PullDownReload.jsx +++ b/src/components/utils/PullDownReload.jsx @@ -60,6 +60,14 @@ class PullDownReload extends Component { return; } + // Don't capture touches that start near the screen edges — those are + // iOS' system back-swipe gestures and we shouldn't preventDefault on them. + const x = ev.touches[0].pageX; + const edgeWidth = 30; + if (x < edgeWidth || x > window.innerWidth - edgeWidth) { + return; + } + this.setState({ startY: ev.touches[0].pageY }); }