diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 857d1ff1..0a226970 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -605,17 +605,7 @@ export function useSmartRelayNavigation() { // Build contextual URL based on current page const contextualUrl = buildRelayUrl(relayUrl, currentPrimaryPage) - if (isSmallScreen) { - // Use primary note view on mobile - window.history.pushState(null, '', contextualUrl) - setPrimaryNoteView( - suspensePrimaryPage(), - 'relay' - ) - } else { - // Desktop: always use secondary routing (will be rendered in drawer in single-pane, side panel in double-pane) - pushSecondaryPage(contextualUrl) - } + pushSecondaryPage(contextualUrl) } return { navigateToRelay } @@ -642,15 +632,7 @@ export function useSmartRelayNavigationOptional() { url.match(/\/relays\/(.+)$/) const relayUrl = relayUrlMatch ? decodeURIComponent(relayUrlMatch[relayUrlMatch.length - 1]) : decodeURIComponent(url.replace(/.*\/relays\//, '')) const contextualUrl = buildRelayUrl(relayUrl, currentPrimaryPage) - if (isSmallScreen) { - window.history.pushState(null, '', contextualUrl) - setPrimaryNoteView( - suspensePrimaryPage(), - 'relay' - ) - } else { - pushSecondaryPage(contextualUrl) - } + pushSecondaryPage(contextualUrl) } return { navigateToRelay } } @@ -1242,6 +1224,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { // Drawer handlers const [drawerInitialEvent, setDrawerInitialEvent] = useState(null) const openDrawer = useCallback((noteId: string, initialEvent?: Event) => { + noteStatsService.setBackgroundStatsPaused(true) + client.interruptBackgroundQueries() setDrawerNoteId(noteId) setDrawerInitialEvent(initialEvent ?? null) setDrawerOpen(true) @@ -1990,6 +1974,10 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { const navigatePrimaryPageStable = useEventCallback(navigatePrimaryPage) const goBack = () => { + if (primaryViewType === 'relay') { + setPrimaryNoteView(null) + return + } if (primaryViewType === 'settings-sub') { navigatePrimaryPage('settings') return @@ -2050,6 +2038,9 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { } recentSecondaryPushRef.current = { url, at: now } + noteStatsService.setBackgroundStatsPaused(true) + client.interruptBackgroundQueries() + // Small screens render either the primary overlay OR the secondary stack — not both. // Clear overlays (e.g. full-screen note) so pushes from Seen-on, settings deep links, etc. show the target page. if (isSmallScreen && primaryNoteView) { @@ -2282,8 +2273,10 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { setSinglePaneSheetOpen(shouldBeOpen) }, [panelMode, isSmallScreen, secondaryStack.length, drawerOpen]) - const primaryFrozen = - secondaryStack.length > 0 && (isSmallScreen || panelMode === 'double') + const primaryObscured = + secondaryStack.length > 0 || drawerOpen || primaryNoteView != null + + const primaryFrozen = primaryObscured useLayoutEffect(() => { noteStatsService.setBackgroundStatsPaused(primaryFrozen) @@ -2298,7 +2291,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { navigate: navigatePrimaryPageStable, current: currentPrimaryPage, currentPageProps, - display: isSmallScreen ? secondaryStack.length === 0 : true, + /** Double-pane keeps the feed visible (frozen); single-pane / mobile unmount primary while a panel is open. */ + display: panelMode === 'double' || !primaryObscured, frozen: primaryFrozen }), [ @@ -2306,7 +2300,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { currentPrimaryPage, currentPageProps, isSmallScreen, - secondaryStack.length, + panelMode, + primaryObscured, primaryFrozen ] ) diff --git a/src/contexts/primary-page-context.tsx b/src/contexts/primary-page-context.tsx index 258ffc4e..34eedfaa 100644 --- a/src/contexts/primary-page-context.tsx +++ b/src/contexts/primary-page-context.tsx @@ -13,13 +13,13 @@ export type PrimaryPageContextValue = { /** Props passed to the current primary page (e.g. `{ spell: 'discussions' }` for spells). */ currentPageProps: object | undefined /** - * False on small screens while the secondary stack is open (primary feed unmounted). - * True on desktop double-pane so the left column stays visible. + * False while a note drawer, secondary page, or mobile overlay covers the feed (primary unmounted). + * True on desktop double-pane so the left column stays visible (but {@link frozen} pauses it). */ display: boolean /** - * True while a secondary panel is open: pause primary feed timelines / background stats - * and preserve scroll position until the panel closes. + * True while any secondary panel, note drawer, or mobile overlay is open: pause primary feed + * timelines / background stats and abort non-foreground relay queries. */ frozen: boolean } diff --git a/src/layouts/SecondaryPageLayout/index.tsx b/src/layouts/SecondaryPageLayout/index.tsx index 03ee63ee..1200d89a 100644 --- a/src/layouts/SecondaryPageLayout/index.tsx +++ b/src/layouts/SecondaryPageLayout/index.tsx @@ -50,6 +50,9 @@ const SecondaryPageLayout = forwardRef( enabled: mobileSwipeActive }) + const shouldRenderTitlebar = + titlebar != null || (title != null && title !== '') || !hideBackButton + useImperativeHandle( ref, () => ({ @@ -99,15 +102,16 @@ const SecondaryPageLayout = forwardRef( paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)' }} > - {title && ( + {shouldRenderTitlebar ? ( - )} + ) : null} {children} {displayScrollToTopButton && } @@ -118,7 +122,7 @@ const SecondaryPageLayout = forwardRef( return (
- {title && ( + {shouldRenderTitlebar ? ( - )} + ) : null}
@@ -176,18 +187,20 @@ function SecondaryPageTitlebar({ } return (
{hideBackButton ? ( -
- {title} -
+ title ? ( +
+ {title} +
+ ) : null ) : (
- {title} + {title ?? t('back')}
)}
diff --git a/src/lib/sheet-dismiss-guard.ts b/src/lib/sheet-dismiss-guard.ts index 2669e8c5..85a49f43 100644 --- a/src/lib/sheet-dismiss-guard.ts +++ b/src/lib/sheet-dismiss-guard.ts @@ -17,6 +17,17 @@ function eventComposedPath(e: RadixOutsideEvent): EventTarget[] { return [] } +function pathIncludesPortaledOverlay(path: EventTarget[]): boolean { + return path.some((node) => { + if (!(node instanceof HTMLElement)) return false + if (node.tagName.toLowerCase() === 'bc-modal') return true + if (node.hasAttribute('data-radix-dialog-content')) return true + if (node.hasAttribute('data-radix-dialog-overlay')) return true + if (node.getAttribute('role') === 'dialog' && node.closest('[data-radix-portal]')) return true + return false + }) +} + export function preventRadixSheetCloseForPortaledOverlay(e: RadixOutsideEvent): void { if (typeof document === 'undefined') return if (document.body.classList.contains('yarl__no_scroll')) { @@ -24,10 +35,11 @@ export function preventRadixSheetCloseForPortaledOverlay(e: RadixOutsideEvent): return } const path = eventComposedPath(e) - const inBitcoinConnectModal = path.some( - (node) => node instanceof HTMLElement && node.tagName.toLowerCase() === 'bc-modal' - ) - if (inBitcoinConnectModal) { + if (pathIncludesPortaledOverlay(path)) { + e.preventDefault() + return + } + if (document.querySelector('[data-radix-dialog-content][data-state="open"]')) { e.preventDefault() } } diff --git a/src/pages/secondary/NotFoundPage/index.tsx b/src/pages/secondary/NotFoundPage/index.tsx index 5f43e95a..5ea8d0e0 100644 --- a/src/pages/secondary/NotFoundPage/index.tsx +++ b/src/pages/secondary/NotFoundPage/index.tsx @@ -2,12 +2,20 @@ import NotFound from '@/components/NotFound' import { RefreshButton } from '@/components/RefreshButton' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { forwardRef, useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' const NotFoundPage = forwardRef(({ index }: { index?: number }, ref) => { + const { t } = useTranslation() const [contentKey, setContentKey] = useState(0) const bump = useCallback(() => setContentKey((k) => k + 1), []) return ( - }> + } + >
diff --git a/src/pages/secondary/RelayPage/index.tsx b/src/pages/secondary/RelayPage/index.tsx index 879fcfe4..f1000829 100644 --- a/src/pages/secondary/RelayPage/index.tsx +++ b/src/pages/secondary/RelayPage/index.tsx @@ -35,6 +35,7 @@ const RelayPage = forwardRef(({ url, index, hideTitlebar = false }: { url?: stri ref={ref} index={index} title={hideTitlebar ? undefined : title} + hideBackButton={false} controls={hideTitlebar ? undefined : } displayScrollToTopButton > diff --git a/src/pages/secondary/RelayReviewsPage/index.tsx b/src/pages/secondary/RelayReviewsPage/index.tsx index bb20c1d8..064713bf 100644 --- a/src/pages/secondary/RelayReviewsPage/index.tsx +++ b/src/pages/secondary/RelayReviewsPage/index.tsx @@ -81,6 +81,7 @@ const RelayReviewsPage = forwardRef(({ url, index, hideTitlebar = false }: { url ref={ref} index={index} title={hideTitlebar ? undefined : title} + hideBackButton={false} controls={hideTitlebar ? undefined : } displayScrollToTopButton > diff --git a/src/services/navigation.service.ts b/src/services/navigation.service.ts index 4ce2a645..d8f077f0 100644 --- a/src/services/navigation.service.ts +++ b/src/services/navigation.service.ts @@ -119,7 +119,7 @@ export class ComponentFactory { } static createRelayPage(relayUrl: string): ReactNode { - return React.createElement(SecondaryRelayPage, { url: relayUrl, index: 0, hideTitlebar: true }) + return React.createElement(SecondaryRelayPage, { url: relayUrl, index: 0 }) } static createProfilePage(profileId: string): ReactNode {