From b097598c2a82516c19fd299b13b7eb72166da3dc Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sun, 20 Sep 2026 06:04:11 -0400 Subject: [PATCH] feat(mobile): take the session domain's three routers through the handoff seam (OTA phase C, C7.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inside the page a screen is one document standing in for one screen, and `useRouteHandoff` is the only thing that knows which targets the page keeps and which it hands back to the app. The three holders here are the workspace-missing bounce, the file-tap preview push, and the pane-tap param consume. The domain's census is narrower than the two landed ones because it has to be: eight of its hooks take `useFocusEffect` and two take `useLocalSearchParams`, neither of which can navigate, so the rule is a closed list of names rather than a ban on any value import — which also catches expo-router's module-singleton `router`, a spelling a `useRouter` rule would have read as clean. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb --- mobile/src/session/use-mobile-file-tap-handlers.ts | 4 ++-- mobile/src/session/use-mobile-session-foundation.ts | 5 +++-- mobile/src/session/use-notification-pane-navigation.ts | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mobile/src/session/use-mobile-file-tap-handlers.ts b/mobile/src/session/use-mobile-file-tap-handlers.ts index 9c3019f3073..ad0386aa422 100644 --- a/mobile/src/session/use-mobile-file-tap-handlers.ts +++ b/mobile/src/session/use-mobile-file-tap-handlers.ts @@ -1,5 +1,5 @@ import { useCallback, useLayoutEffect, useRef, type MutableRefObject } from 'react' -import { useRouter } from 'expo-router' +import { useRouteHandoff } from '../navigation/route-handoff' import { triggerSelection } from '../platform/haptics' import { openMobileFileTap, type FileTapSessionTab } from './mobile-file-tap-open' import { openMobileNativeChatFileTap } from './mobile-native-chat-open-file' @@ -58,7 +58,7 @@ export function useMobileFileTapHandlers( worktreeId, worktreeName } = options - const router = useRouter() + const router = useRouteHandoff() const routerRef = useRef(router) const optionsRef = useRef(options) const activationSeqRef = useRef(0) diff --git a/mobile/src/session/use-mobile-session-foundation.ts b/mobile/src/session/use-mobile-session-foundation.ts index 80a67a35702..0ca3d3b93a2 100644 --- a/mobile/src/session/use-mobile-session-foundation.ts +++ b/mobile/src/session/use-mobile-session-foundation.ts @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { useLocalSearchParams, useRouter } from 'expo-router' +import { useLocalSearchParams } from 'expo-router' +import { useRouteHandoff } from '../navigation/route-handoff' import { HOST_DOCK_MIN_WIDTH } from '../storage/preferences' import { useHostClient, useForceReconnect } from '../transport/client-context' import { @@ -33,7 +34,7 @@ export function useMobileSessionFoundation() { const isFolderWorkspaceRoute = worktreeId.startsWith('folder:') // Synthetic ids have no repo scope. // Why: the floating sentinel has no repo/worktree, so repo-backed surfaces hide. const isFloatingWorkspaceRoute = isFloatingWorkspaceWorktreeId(worktreeId) - const router = useRouter() + const router = useRouteHandoff() const insets = useSafeAreaInsets() // Why: shared client per host owned by RpcClientProvider (docs/mobile-shared-client-per-host.md). const { client, clientId, state: connState } = useHostClient(hostId) diff --git a/mobile/src/session/use-notification-pane-navigation.ts b/mobile/src/session/use-notification-pane-navigation.ts index f6d3bb15cd4..55af7089c2f 100644 --- a/mobile/src/session/use-notification-pane-navigation.ts +++ b/mobile/src/session/use-notification-pane-navigation.ts @@ -1,5 +1,6 @@ import { useEffect } from 'react' -import { useLocalSearchParams, useRouter } from 'expo-router' +import { useLocalSearchParams } from 'expo-router' +import { useRouteHandoff } from '../navigation/route-handoff' import { parsePaneKey } from '../../../src/shared/stable-pane-id' import type { MobileSessionTab } from './mobile-session-route-types' @@ -25,7 +26,7 @@ export function useNotificationPaneNavigation({ switchSessionTab: (tab: MobileSessionTab) => void }) { const { paneKey } = useLocalSearchParams<{ paneKey?: string }>() - const router = useRouter() + const router = useRouteHandoff() useEffect(() => { if (!terminalsLoaded || typeof paneKey !== 'string' || !paneKey) { return