From ef39227895faec634e5e48a554e8a81d586f8d24 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Sun, 30 Aug 2026 23:20:49 -0400 Subject: [PATCH] fix(mobile): preserve hosted resume and reconnect --- mobile/app/h/[hostId]/session/[worktreeId].tsx | 16 ++++++++++++---- .../h/[hostId]/session/[worktreeId].tsx | 7 +++++++ mobile/host-web-app/index.tsx | 7 +------ ...ile-web-session-screen-binding-source.test.ts | 3 +++ ...mobile-web-host-screen-binding-source.test.ts | 1 + 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx index dcccf8c4a44..880619eff59 100644 --- a/mobile/app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx @@ -316,6 +316,9 @@ type SessionScreenProps = { sessionChatPendingDeliveryOperations?: HostSessionChatPendingDeliveryOperations connectionState?: ConnectionState nativeHostBinding?: boolean + reconnect?: () => Promise + reconnectAttempts?: number + lastConnectedAt?: number | null } export { SessionScreen as default } @@ -334,7 +337,10 @@ export function SessionScreen({ sessionChatDraftOperations: sessionChatDraftOperationsProp, sessionChatPendingDeliveryOperations: sessionChatPendingDeliveryOperationsProp, connectionState: connectionStateProp, - nativeHostBinding = true + nativeHostBinding = true, + reconnect: reconnectProp, + reconnectAttempts: reconnectAttemptsProp, + lastConnectedAt: lastConnectedAtProp }: SessionScreenProps = {}) { const { hostId, @@ -441,8 +447,10 @@ export function SessionScreen({ }, [sessionDeviceOperations] ) - const reconnectAttempts = useReconnectAttempt(hostId) - const lastConnectedAt = useLastConnectedAt(hostId) + const nativeReconnectAttempts = useReconnectAttempt(hostId) + const nativeLastConnectedAt = useLastConnectedAt(hostId) + const reconnectAttempts = reconnectAttemptsProp ?? nativeReconnectAttempts + const lastConnectedAt = lastConnectedAtProp ?? nativeLastConnectedAt const relayRecovery = useRelayRecoveryStatus(hostId) const forceReconnectHost = useForceReconnect() const { name: worktreeName, resolution: worktreeResolution } = useLiveWorktreeName({ @@ -3973,7 +3981,7 @@ export function SessionScreen({ disabled={!showConnectionRetry} onPress={() => { if (hostId) { - void forceReconnectHost(hostId) + void (reconnectProp ? reconnectProp() : forceReconnectHost(hostId)) } }} accessibilityRole={showConnectionRetry ? 'button' : undefined} diff --git a/mobile/host-web-app/h/[hostId]/session/[worktreeId].tsx b/mobile/host-web-app/h/[hostId]/session/[worktreeId].tsx index 47036c59e68..7f5d40a7382 100644 --- a/mobile/host-web-app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/host-web-app/h/[hostId]/session/[worktreeId].tsx @@ -101,6 +101,13 @@ export default function HostMobileWebSessionRoute() { sessionChatPendingDeliveryOperations={sessionChatPendingDeliveryOperations} connectionState={connectionState} nativeHostBinding={false} + reconnect={() => + shell.client + ? shell.client.navigationReconnect().then(() => undefined) + : Promise.reject(new Error('Native shell channel unavailable')) + } + reconnectAttempts={shell.reconnectAttempts} + lastConnectedAt={shell.lastConnectedAt} /> ) } diff --git a/mobile/host-web-app/index.tsx b/mobile/host-web-app/index.tsx index 8a8c820873b..ee044ed7e55 100644 --- a/mobile/host-web-app/index.tsx +++ b/mobile/host-web-app/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import { usePathname, useRouter } from 'expo-router' import { HostScreen } from '../app/h/[hostId]/index' import { HostProtocolGatesProvider } from '../src/components/host-protocol-gates-context' @@ -16,11 +16,6 @@ export default function HostMobileWebRoute() { const shell = useMobileWebNativeShell() const router = useRouter() const pathname = usePathname() - useEffect(() => { - if (shell.context) { - shell.rememberRoute({ kind: 'workspaceList' }) - } - }, [shell.context, shell.rememberRoute]) const workspaceOperations = useMemo( () => (shell.client ? webHostWorkspaceOperations(shell.client) : undefined), [shell.client] diff --git a/mobile/src/session/mobile-web-session-screen-binding-source.test.ts b/mobile/src/session/mobile-web-session-screen-binding-source.test.ts index fefb1347553..f1d47c2eb8c 100644 --- a/mobile/src/session/mobile-web-session-screen-binding-source.test.ts +++ b/mobile/src/session/mobile-web-session-screen-binding-source.test.ts @@ -40,6 +40,9 @@ describe('mobile web session screen binding', () => { expect(hostedSessionRoute).toContain( 'sessionNativeChatOperations={sessionNativeChatOperations}' ) + expect(hostedSessionRoute).toContain('reconnect={() =>') + expect(hostedSessionRoute).toContain('reconnectAttempts={shell.reconnectAttempts}') + expect(hostedSessionRoute).toContain('lastConnectedAt={shell.lastConnectedAt}') expect(hostedSessionRoute).toContain(' { expect(hostedRouteSource).toContain('nativeHostBinding={false}') expect(hostedRouteSource).toContain('useWebHostStatusGates({') expect(hostedRouteSource).toContain('value={hostStatusGates}') + expect(hostedRouteSource).not.toContain("rememberRoute({ kind: 'workspaceList' })") expect(hostedRouteSource).not.toContain('hostCapabilities: []') expect(hostedRouteSource).not.toContain('floatingWorkspaceEnabled: false') expect(hostedRouteSource).not.toContain('hostDisplayName=')