From 6bdc937861e3e9b79ccce82c31b062e8abe55d8d Mon Sep 17 00:00:00 2001 From: lorentz Date: Thu, 7 May 2026 22:10:43 -0400 Subject: [PATCH] fix(08-02): preserve engagement list scroll across profile navigation (D-04) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mobile shell's
is overflow-y-auto, so Next.js's built-in scrollRestoration (window-only) doesn't restore the list's inner scroll when returning from /mobile/engagement/[userId]. Persist the scroll position to sessionStorage on scroll (rAF-throttled) and restore once after the first users page loads. Restoration is gated to the first load only, so changing period/sort doesn't yank the viewport — and uses a single sessionStorage key, so returning to the list later still lands where you were. --- app/mobile/engagement/page.tsx | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/mobile/engagement/page.tsx b/app/mobile/engagement/page.tsx index f682f75..6008c67 100644 --- a/app/mobile/engagement/page.tsx +++ b/app/mobile/engagement/page.tsx @@ -172,6 +172,38 @@ export default function MobileEngagementPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortKey]); + // D-04 fallback: the mobile shell's
is overflow-y-auto, so Next.js's + // built-in scrollRestoration (which targets window) doesn't help. Persist the + // inner scroll position across navigations via sessionStorage so back from + // /mobile/engagement/[userId] restores the row the user tapped. + const restoredScrollRef = useRef(false); + useEffect(() => { + const main = document.querySelector('main'); + if (!main) return; + let raf = 0; + const onScroll = () => { + if (raf) return; + raf = requestAnimationFrame(() => { + raf = 0; + sessionStorage.setItem('mobile-engagement-list-scroll', String(main.scrollTop)); + }); + }; + main.addEventListener('scroll', onScroll, { passive: true }); + return () => { + main.removeEventListener('scroll', onScroll); + if (raf) cancelAnimationFrame(raf); + }; + }, []); + useEffect(() => { + if (restoredScrollRef.current) return; + if (usersLoading || users.length === 0) return; + const main = document.querySelector('main'); + if (!main) return; + const saved = sessionStorage.getItem('mobile-engagement-list-scroll'); + if (saved) main.scrollTop = parseInt(saved, 10); + restoredScrollRef.current = true; + }, [usersLoading, users.length]); + // IntersectionObserver — D-18 (mirrors Phase 4/6 pattern, rootMargin '200px') const sentinelRef = useRef(null); useEffect(() => {