From d362978974b85895cb6935f1911275e22f207b39 Mon Sep 17 00:00:00 2001 From: GraysonCAdams Date: Mon, 13 Apr 2026 21:46:01 -0500 Subject: [PATCH 1/2] fix: land on activity page when dismissing a clip opened from it Activity notifications navigate to /?clip=X which pushed one feed entry plus the overlay's own entry, so closing the overlay only popped one level (back to the feed) and required a second press to return to the activity list. Now the activity page tags the link with from=activity; the overlay reads it and, on dismiss, pops the extra feed entry too so the user lands directly back where they tapped the notification. --- src/lib/components/ClipOverlay.svelte | 9 +++++++++ src/routes/(app)/+page.svelte | 6 ++++++ src/routes/(app)/activity/+page.svelte | 7 +++++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/lib/components/ClipOverlay.svelte b/src/lib/components/ClipOverlay.svelte index ee8210d..9884ed9 100644 --- a/src/lib/components/ClipOverlay.svelte +++ b/src/lib/components/ClipOverlay.svelte @@ -25,6 +25,7 @@ autoScroll, gifEnabled = false, openComments = false, + fromActivity = false, ondismiss }: { clipId: string; @@ -33,6 +34,7 @@ autoScroll: boolean; gifEnabled?: boolean; openComments?: boolean; + fromActivity?: boolean; ondismiss: () => void; } = $props(); @@ -67,6 +69,13 @@ console.log('[ClipOverlay] handleDismiss called for:', clipId); dismissed = true; ondismiss(); + if (fromActivity) { + // Opened from /activity → /?clip=X (pushed feed entry) plus the + // overlay's own pushed state. ondismiss() unmounts the overlay; + // the overlay history cleanup pops one entry (back to /). Pop one + // more so the user lands directly on /activity. + setTimeout(() => history.back(), 0); + } } // Load clip data diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index f60904e..390946b 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -61,6 +61,7 @@ // Clip overlay (dedicated single-clip view) let overlayClipId = $state(null); let overlayOpenComments = $state(false); + let overlayFromActivity = $state(false); const overlayActive = $derived(overlayClipId !== null); let pullDistance = $state(0); @@ -703,6 +704,7 @@ function handleOverlayDismiss() { overlayClipId = null; overlayOpenComments = false; + overlayFromActivity = false; // Refresh feed to reflect any changes made in the overlay loadInitialClips(); fetchUnwatchedCount(); @@ -802,11 +804,13 @@ const params = new URLSearchParams(window.location.search); const deepClipId = params.get('clip'); const deepComments = params.get('comments') === 'true'; + const deepFromActivity = params.get('from') === 'activity'; if (deepClipId) { // Clean URL without triggering navigation const clean = new URL(window.location.href); clean.searchParams.delete('clip'); clean.searchParams.delete('comments'); + clean.searchParams.delete('from'); history.replaceState(null, '', clean.pathname + clean.search || '/'); // Clear the notification stash so the layout's visibilitychange handler // doesn't double-process this same deep-link. @@ -818,6 +822,7 @@ // pushState throws if called before the router is ready (cold start). setTimeout(() => { overlayOpenComments = deepComments; + overlayFromActivity = deepFromActivity; clipOverlaySignal.set(deepClipId); }, 0); } @@ -1010,6 +1015,7 @@ {autoScroll} {gifEnabled} openComments={overlayOpenComments} + fromActivity={overlayFromActivity} ondismiss={handleOverlayDismiss} /> {/if} diff --git a/src/routes/(app)/activity/+page.svelte b/src/routes/(app)/activity/+page.svelte index c181ff4..45e3229 100644 --- a/src/routes/(app)/activity/+page.svelte +++ b/src/routes/(app)/activity/+page.svelte @@ -93,8 +93,11 @@ function handleNotificationClick(e: Event, n: Notification) { e.preventDefault(); const shouldOpenComments = n.type !== 'reaction'; - const q = shouldOpenComments ? `clip=${n.clipId}&comments=true` : `clip=${n.clipId}`; - goto(resolve(`/?${q}`)); + const base = shouldOpenComments ? `clip=${n.clipId}&comments=true` : `clip=${n.clipId}`; + // `from=activity` tells the feed/overlay the user arrived from the + // activity page, so a single back press pops both the feed entry and + // the overlay entry to land them back here. + goto(resolve(`/?${base}&from=activity`)); } function clipLabel(n: Notification): string { From d0ed8b53a7dc2de49a01ca1859c29866a4b62fbc Mon Sep 17 00:00:00 2001 From: GraysonCAdams Date: Mon, 13 Apr 2026 21:46:09 -0500 Subject: [PATCH 2/2] fix: keep reel scroll position when closing nested sheets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MeReelView's mount effect re-attached its overlay-history entry whenever a nested sheet (e.g. CommentsSheet) pushed/popped state, and the tick() scroll-to-startIndex call ran every time — snapping the reel back to the clip the user originally tapped. Guard the initial scroll with a one-shot flag so only the first mount positions the reel; later effect re-runs leave scroll alone. --- src/lib/components/MeReelView.svelte | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/components/MeReelView.svelte b/src/lib/components/MeReelView.svelte index 39d6d26..659ea6e 100644 --- a/src/lib/components/MeReelView.svelte +++ b/src/lib/components/MeReelView.svelte @@ -49,12 +49,21 @@ beforeNavigate(overlay.onBeforeNavigate); // Mount: push history state, scroll to start index + // Guard the initial scroll so subsequent effect re-runs (e.g. after a + // nested CommentsSheet pushes/pops state) don't yank the reel back to + // startIndex and make it look like the feed jumped to the top. + let didInitialScroll = false; $effect(() => { const cleanupHistory = overlay.attach(close); - tick().then(() => { - if (reelContainer) reelContainer.scrollTop = startIndex * reelContainer.clientHeight; - }); + if (!didInitialScroll) { + tick().then(() => { + if (reelContainer) { + reelContainer.scrollTop = startIndex * reelContainer.clientHeight; + didInitialScroll = true; + } + }); + } return cleanupHistory; });