From aa07d5891a402a2f98cc1e56d22a2b30d4f15542 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 18 Oct 2026 04:00:24 -0700 Subject: [PATCH] fix(mobile): break the transport and terminal-document import cycles, and gate cycles after mobile's install (#30290) * fix(mobile): break the transport and terminal-document import cycles and gate them after mobile's install client-context re-exported the host-client hooks, which import client-context back: the re-export barrel is gone and importers (and their test mocks) name host-client-hooks directly. In the terminal document, the cell-metric getters move to a leaf cell-metrics module and select-mode entry/exit to selection-mode, so fit, transform, overlay and range no longer import each other. pr.yml lints imports before mobile's install, when mobile's tsconfig can't load, so Mobile verify now runs the same import rules over mobile after its install with zero warnings allowed. * fix(mobile): the browser-pane render harness imports useHostClient from host-client-hooks Its route source is a template string, so no typecheck saw the removed client-context re-export; the mobile web bundle check did. * fix(ci): the e2e typecheck ratchet counts the same with or without mobile's install Specs reach mobile/src/transport, whose expo-crypto and async-storage imports resolve only after mobile's install and then load React Native's globals (numeric timers, a required NODE_ENV), adding errors in unrelated specs. An empty stub pins both, so the static job reads 232 either way. --- .github/workflows/mobile.yml | 7 ++++ config/e2e-typecheck-mobile-native-stub.ts | 2 ++ .../mobile-release-check-scope.test.mjs | 8 ++++- ...obile-web-app-browser-pane-render.test.mjs | 2 +- config/tsconfig.e2e.json | 7 +++- mobile/app/connection-log.tsx | 3 +- mobile/app/h/[hostId]/edit.tsx | 2 +- mobile/app/pair-confirm.tsx | 2 +- mobile/app/pair-scan.tsx | 2 +- mobile/src/accounts/MobileAccountsScreen.tsx | 2 +- ...obile-accounts-screen-reset-credit.test.ts | 2 +- ...t-history-current-host-capability.test.tsx | 2 +- ...-history-pending-resume-ownership.test.tsx | 2 +- .../use-mobile-agent-history-state.ts | 2 +- .../src/components/HostProtocolGate.test.ts | 2 +- mobile/src/components/HostProtocolGate.tsx | 2 +- .../use-mobile-web-bundle-probe.ts | 2 +- .../src/files/MobileFileExplorerPanel.test.ts | 2 +- mobile/src/files/MobileFileExplorerPanel.tsx | 2 +- mobile/src/files/MobileFilePreviewScreen.tsx | 2 +- .../files/mobile-file-preview-retry.test.tsx | 2 +- mobile/src/home/MobileHomeScreen.tsx | 2 +- .../home/use-mobile-home-host-connections.ts | 2 +- .../src/host-edit-route-accessibility.test.ts | 2 +- mobile/src/host-edit-save-flow.test.ts | 2 +- .../host-screen/use-host-screen-controller.ts | 6 +++- .../host-screen/use-host-worktree-actions.ts | 2 +- .../MobileWebShellScreen.test.tsx | 2 +- .../catch-all-page-route-states.test.tsx | 2 +- .../host-area-layout-class-change.test.tsx | 2 +- .../host-area-leave-session.test.tsx | 2 +- .../host-area-sidebar-matrix.test.tsx | 2 +- .../host-area-two-hosts.test.tsx | 2 +- ...ile-web-shell-agent-history-route.test.tsx | 2 +- ...e-web-shell-failed-update-fallback.test.ts | 2 +- .../mobile-web-shell-files-route.test.tsx | 2 +- ...web-shell-keyboard-overlay-screen.test.tsx | 2 +- ...-web-shell-offline-grant-staleness.test.ts | 2 +- ...mobile-web-shell-safe-area-screen.test.tsx | 2 +- .../mobile-web-shell-screen-test-mocks.tsx | 2 +- .../mobile-web-shell-session-route.test.tsx | 2 +- .../shell-route-first-frame.test.tsx | 2 +- .../use-mobile-web-shell-bridge.test.ts | 2 +- .../use-mobile-web-shell-bridge.ts | 2 +- .../use-mobile-web-shell-session.test.ts | 2 +- .../use-mobile-web-shell-session.ts | 2 +- .../src/navigation/workspace-route.test.tsx | 2 +- mobile/src/navigation/workspace-route.tsx | 2 +- .../session/MobileDiffReviewRouteScreen.tsx | 2 +- .../session/use-mobile-session-foundation.ts | 2 +- .../MobileGitHistoryList.test.tsx | 2 +- .../source-control/MobileGitHistoryList.tsx | 2 +- .../use-mobile-source-control-state.ts | 2 +- mobile/src/tasks/mobile-tasks-dependencies.ts | 2 +- mobile/src/terminal/document/cell-geometry.ts | 3 +- mobile/src/terminal/document/cell-metrics.ts | 31 ++++++++++++++++ mobile/src/terminal/document/fit-scale.ts | 14 ++------ .../terminal/document/host-message-router.ts | 2 +- .../document/keyboard-avoidance-metrics.ts | 2 +- .../src/terminal/document/mouse-click-drag.ts | 3 +- .../terminal/document/mouse-report-cell.ts | 3 +- .../document/native-document-bundle.test.ts | 4 +-- .../document/normal-buffer-smooth-scroll.ts | 4 +-- .../document/selection-menu-buttons.ts | 2 +- .../src/terminal/document/selection-mode.ts | 36 +++++++++++++++++++ .../terminal/document/selection-overlay.ts | 3 +- .../src/terminal/document/selection-range.ts | 31 ---------------- .../document/selection-state-and-eviction.ts | 2 +- .../document/surface-touch-gestures.ts | 5 +-- mobile/src/terminal/document/tap-dispatch.ts | 2 +- mobile/src/terminal/document/terminal-init.ts | 2 +- mobile/src/terminal/document/text-scaling.ts | 4 +-- mobile/src/terminal/document/viewport-cell.ts | 3 +- .../terminal/document/viewport-transform.ts | 25 +------------ mobile/src/terminal/document/wheel-scroll.ts | 3 +- mobile/src/transport/client-context.test.ts | 4 +-- mobile/src/transport/client-context.tsx | 9 ----- mobile/src/transport/client-context.web.tsx | 9 ----- .../src/transport/host-open-recovery.test.tsx | 4 +-- .../settings-host-client-lifecycle.test.ts | 3 +- mobile/src/transport/use-workspace-client.ts | 2 +- mobile/web-entry/web-overrides.json | 2 +- 82 files changed, 176 insertions(+), 170 deletions(-) create mode 100644 config/e2e-typecheck-mobile-native-stub.ts create mode 100644 mobile/src/terminal/document/cell-metrics.ts create mode 100644 mobile/src/terminal/document/selection-mode.ts diff --git a/.github/workflows/mobile.yml b/.github/workflows/mobile.yml index fbf7f45c9c1..b855a5d717c 100644 --- a/.github/workflows/mobile.yml +++ b/.github/workflows/mobile.yml @@ -32,6 +32,7 @@ on: - '.github/workflows/mobile.yml' - 'config/mobile-ci.vitest.config.mjs' - 'config/scripts/run-mobile-ci-tests.mjs' + - 'config/oxlint-code-quality-native-plugins.json' - 'config/.bun-version' - '.github/actions/install-node-dependencies/**' - '.github/actions/restore-pnpm-verification/**' @@ -154,6 +155,12 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + # Why here: pr.yml runs these import rules before mobile's install, when mobile's tsconfig + # (which extends expo's) can't load, so its import cycles were never seen. Zero tolerance. + - name: Lint mobile imports with mobile dependencies installed + working-directory: . + run: node node_modules/oxlint/bin/oxlint --config config/oxlint-code-quality-native-plugins.json mobile --deny-warnings + # Call installed tools so pnpm's dependency refresh cannot race between checks. - name: Typecheck id: production-types diff --git a/config/e2e-typecheck-mobile-native-stub.ts b/config/e2e-typecheck-mobile-native-stub.ts new file mode 100644 index 00000000000..c0cddc285b8 --- /dev/null +++ b/config/e2e-typecheck-mobile-native-stub.ts @@ -0,0 +1,2 @@ +// Stands in for mobile's native-only packages in config/tsconfig.e2e.json (see its `paths`). +export {} diff --git a/config/scripts/mobile-release-check-scope.test.mjs b/config/scripts/mobile-release-check-scope.test.mjs index 28b1aaf92fe..7195be14d45 100644 --- a/config/scripts/mobile-release-check-scope.test.mjs +++ b/config/scripts/mobile-release-check-scope.test.mjs @@ -96,7 +96,13 @@ it('gates Ruby independently and retains mobile static validation', () => { for (const step of gated) { expect(step.if).toBe("steps.ruby-scope.outputs.should_run != 'false'") } - for (const name of ['Typecheck', 'Typecheck tests (ratchet)', 'Lint', 'Check formatting']) { + for (const name of [ + 'Lint mobile imports with mobile dependencies installed', + 'Typecheck', + 'Typecheck tests (ratchet)', + 'Lint', + 'Check formatting' + ]) { expect(steps.find((step) => step.name === name)?.if).toBeUndefined() expect(steps.some((step) => step.name === name)).toBe(true) } diff --git a/config/scripts/mobile-web-app-browser-pane-render.test.mjs b/config/scripts/mobile-web-app-browser-pane-render.test.mjs index e30b16a7dd4..1ae4d7e7a74 100644 --- a/config/scripts/mobile-web-app-browser-pane-render.test.mjs +++ b/config/scripts/mobile-web-app-browser-pane-render.test.mjs @@ -82,7 +82,7 @@ const OVER_CAP_FRAME = { width: 2400, height: 2160 } * because the capability probe is the session screen's to make and C7's to prove. */ const ROUTE_SOURCE = ` -import { useHostClient } from '../../../src/transport/client-context' +import { useHostClient } from '../../../src/transport/host-client-hooks' import { MobileBrowserPane } from '../../../src/browser/MobileBrowserPane' const TAB = { diff --git a/config/tsconfig.e2e.json b/config/tsconfig.e2e.json index cfad2852b92..e6ef77eb68e 100644 --- a/config/tsconfig.e2e.json +++ b/config/tsconfig.e2e.json @@ -20,7 +20,12 @@ "types": ["node"], "paths": { "@renderer/*": ["../src/renderer/src/*"], - "@/*": ["../src/renderer/src/*"] + "@/*": ["../src/renderer/src/*"], + // Why: specs reach mobile/src/transport, whose native imports resolve only once mobile is + // installed and then load React Native's globals (numeric timers, a required NODE_ENV). + // An empty stub keeps the error count the same with or without mobile's install. + "expo-crypto": ["./e2e-typecheck-mobile-native-stub.ts"], + "@react-native-async-storage/async-storage": ["./e2e-typecheck-mobile-native-stub.ts"] } } } diff --git a/mobile/app/connection-log.tsx b/mobile/app/connection-log.tsx index f7a1e80c231..ef2fe3840ca 100644 --- a/mobile/app/connection-log.tsx +++ b/mobile/app/connection-log.tsx @@ -4,7 +4,8 @@ import { useLocalSearchParams, useRouter } from 'expo-router' import * as Clipboard from 'expo-clipboard' import { loadHosts } from '../src/transport/host-store' import { connectionLogStore } from '../src/transport/persisted-connection-log-store' -import { useHostClient, useRpcClientContext } from '../src/transport/client-context' +import { useRpcClientContext } from '../src/transport/client-context' +import { useHostClient } from '../src/transport/host-client-hooks' import { useConnectionPathStatus, useReconnectAttempt diff --git a/mobile/app/h/[hostId]/edit.tsx b/mobile/app/h/[hostId]/edit.tsx index 90e9bc6c167..acd631549f4 100644 --- a/mobile/app/h/[hostId]/edit.tsx +++ b/mobile/app/h/[hostId]/edit.tsx @@ -17,7 +17,7 @@ import { colors, radii, spacing, typography } from '../../../src/theme/mobile-th import { loadHosts, updateHostNameAndEndpoint } from '../../../src/transport/host-store' import { displayHostEndpoint } from '../../../src/transport/host-endpoint' import { resolveHostEndpointEdit } from '../../../src/transport/host-endpoint-edit' -import { usePrimeHosts, useRefreshHostClient } from '../../../src/transport/client-context' +import { usePrimeHosts, useRefreshHostClient } from '../../../src/transport/host-client-hooks' import type { HostProfile } from '../../../src/transport/types' import { hostOs } from '../../../src/platform/host-os' diff --git a/mobile/app/pair-confirm.tsx b/mobile/app/pair-confirm.tsx index be790427088..d700ea83f42 100644 --- a/mobile/app/pair-confirm.tsx +++ b/mobile/app/pair-confirm.tsx @@ -9,7 +9,7 @@ import { type PreProfilePairingAttempt } from '../src/transport/pre-profile-pairing-coordinator' import type { ConnectionLogEntry } from '../src/transport/types' -import { useRefreshHostClient } from '../src/transport/client-context' +import { useRefreshHostClient } from '../src/transport/host-client-hooks' import { colors, spacing, radii, typography } from '../src/theme/mobile-theme' import { ConnectionLog } from '../src/components/ConnectionLog' import { diff --git a/mobile/app/pair-scan.tsx b/mobile/app/pair-scan.tsx index 61e99f19f2a..5b4c17b2501 100644 --- a/mobile/app/pair-scan.tsx +++ b/mobile/app/pair-scan.tsx @@ -17,7 +17,7 @@ import { type PreProfilePairingAttempt } from '../src/transport/pre-profile-pairing-coordinator' import type { ConnectionLogEntry, PairingOffer } from '../src/transport/types' -import { useRefreshHostClient } from '../src/transport/client-context' +import { useRefreshHostClient } from '../src/transport/host-client-hooks' import { colors, spacing } from '../src/theme/mobile-theme' import { TextInputModal } from '../src/components/TextInputModal' import { ConnectionLog } from '../src/components/ConnectionLog' diff --git a/mobile/src/accounts/MobileAccountsScreen.tsx b/mobile/src/accounts/MobileAccountsScreen.tsx index 1df0d0dd74a..3f2c74a865b 100644 --- a/mobile/src/accounts/MobileAccountsScreen.tsx +++ b/mobile/src/accounts/MobileAccountsScreen.tsx @@ -4,7 +4,7 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { useFocusEffect, useLocalSearchParams } from 'expo-router' import { ChevronLeft, Check, RefreshCw, User } from 'lucide-react-native' import { loadHosts } from '../transport/host-store' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import { colors, spacing } from '../theme/mobile-theme' import { styles } from './mobile-accounts-screen-styles' import { useNow } from '../hooks/use-now' diff --git a/mobile/src/accounts/mobile-accounts-screen-reset-credit.test.ts b/mobile/src/accounts/mobile-accounts-screen-reset-credit.test.ts index 2cbbb62e6ab..d960f7a081b 100644 --- a/mobile/src/accounts/mobile-accounts-screen-reset-credit.test.ts +++ b/mobile/src/accounts/mobile-accounts-screen-reset-credit.test.ts @@ -60,7 +60,7 @@ vi.mock('lucide-react-native', () => ({ vi.mock('../transport/host-store', () => ({ loadHosts: dependencies.loadHosts })) -vi.mock('../transport/client-context', () => { +vi.mock('../transport/host-client-hooks', () => { const client = { sendRequest: async (method: string, params?: unknown, options?: unknown) => { if (method === 'status.get') { diff --git a/mobile/src/agent-history/agent-history-current-host-capability.test.tsx b/mobile/src/agent-history/agent-history-current-host-capability.test.tsx index 51d4a91f955..c35fa7af497 100644 --- a/mobile/src/agent-history/agent-history-current-host-capability.test.tsx +++ b/mobile/src/agent-history/agent-history-current-host-capability.test.tsx @@ -10,7 +10,7 @@ const connection = vi.hoisted((): { client: RpcClient | null; state: ConnectionS client: null, state: 'connected' })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => connection, useForceReconnect: () => vi.fn() })) diff --git a/mobile/src/agent-history/agent-history-pending-resume-ownership.test.tsx b/mobile/src/agent-history/agent-history-pending-resume-ownership.test.tsx index ecfdd4bae9c..f9fbd5c7f67 100644 --- a/mobile/src/agent-history/agent-history-pending-resume-ownership.test.tsx +++ b/mobile/src/agent-history/agent-history-pending-resume-ownership.test.tsx @@ -51,7 +51,7 @@ vi.mock('../components/MobileAgentIcon', () => ({ MobileAgentIcon: () => null }) vi.mock('../navigation/route-handoff', () => ({ useRouteHandoff: () => ({ push: notifications.push, back: vi.fn(), canGoBack: () => false }) })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: state.client, state: state.connection }), useForceReconnect: () => vi.fn() })) diff --git a/mobile/src/agent-history/use-mobile-agent-history-state.ts b/mobile/src/agent-history/use-mobile-agent-history-state.ts index 00bd377990a..1973ecf631e 100644 --- a/mobile/src/agent-history/use-mobile-agent-history-state.ts +++ b/mobile/src/agent-history/use-mobile-agent-history-state.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import type { RpcClient } from '../transport/rpc-client' import { connectionRetryAction } from '../transport/connection-retry-action' diff --git a/mobile/src/components/HostProtocolGate.test.ts b/mobile/src/components/HostProtocolGate.test.ts index 12dfa6e72ed..0e0220694ee 100644 --- a/mobile/src/components/HostProtocolGate.test.ts +++ b/mobile/src/components/HostProtocolGate.test.ts @@ -35,7 +35,7 @@ vi.mock('expo-router', () => ({ const hostClient = vi.hoisted(() => ({ current: { client: null as RpcClient | null, state: 'disconnected' as string } })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => hostClient.current })) // The wall's release offer: the native hook imports the update checker module. diff --git a/mobile/src/components/HostProtocolGate.tsx b/mobile/src/components/HostProtocolGate.tsx index 3202530c17d..64f06523286 100644 --- a/mobile/src/components/HostProtocolGate.tsx +++ b/mobile/src/components/HostProtocolGate.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, type ReactNode } from 'react' import { ActivityIndicator, StyleSheet, View } from 'react-native' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import { useHostStatusGates } from '../transport/host-status-gates' import { colors } from '../theme/mobile-theme' import { HostStatusGatesContext } from './host-protocol-gates-context' diff --git a/mobile/src/diagnostics/use-mobile-web-bundle-probe.ts b/mobile/src/diagnostics/use-mobile-web-bundle-probe.ts index aeebb4573a5..6ae2487208c 100644 --- a/mobile/src/diagnostics/use-mobile-web-bundle-probe.ts +++ b/mobile/src/diagnostics/use-mobile-web-bundle-probe.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useState } from 'react' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import { fetchMobileWebBundle } from '../transport/mobile-web-bundle-fetch' import { readMobileWebBundleErrorCode } from '../transport/mobile-web-bundle-operations' import { startDiagnosticFetchTimeout } from './diagnostic-fetch-timeout' diff --git a/mobile/src/files/MobileFileExplorerPanel.test.ts b/mobile/src/files/MobileFileExplorerPanel.test.ts index 8033464ffa4..47db7d64656 100644 --- a/mobile/src/files/MobileFileExplorerPanel.test.ts +++ b/mobile/src/files/MobileFileExplorerPanel.test.ts @@ -81,7 +81,7 @@ vi.mock('../platform/haptics', () => ({ triggerSelection: vi.fn() })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useForceReconnect: () => mockTransport.forceReconnect, useHostClient: () => ({ client: mockTransport.client, diff --git a/mobile/src/files/MobileFileExplorerPanel.tsx b/mobile/src/files/MobileFileExplorerPanel.tsx index c8d22755fe9..a6078ec7510 100644 --- a/mobile/src/files/MobileFileExplorerPanel.tsx +++ b/mobile/src/files/MobileFileExplorerPanel.tsx @@ -10,7 +10,7 @@ import { import { SafeAreaView } from 'react-native-safe-area-context' import { ChevronLeft, X } from 'lucide-react-native' import { useWorkspaceRouteHandoff } from '../navigation/workspace-route-handoff' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import { connectionRetryAction } from '../transport/connection-retry-action' import { getWorktreeLabel } from '../session/worktree-label' diff --git a/mobile/src/files/MobileFilePreviewScreen.tsx b/mobile/src/files/MobileFilePreviewScreen.tsx index 973751491a2..afc490abcb3 100644 --- a/mobile/src/files/MobileFilePreviewScreen.tsx +++ b/mobile/src/files/MobileFilePreviewScreen.tsx @@ -5,7 +5,7 @@ import { ChevronLeft, Save } from 'lucide-react-native' import { useWorkspaceRouteHandoff } from '../navigation/workspace-route-handoff' import { getWorktreeLabel } from '../session/worktree-label' import { colors, spacing } from '../theme/mobile-theme' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import { connectionRetryAction } from '../transport/connection-retry-action' import { diff --git a/mobile/src/files/mobile-file-preview-retry.test.tsx b/mobile/src/files/mobile-file-preview-retry.test.tsx index 54d626d8283..7db1218ae8f 100644 --- a/mobile/src/files/mobile-file-preview-retry.test.tsx +++ b/mobile/src/files/mobile-file-preview-retry.test.tsx @@ -38,7 +38,7 @@ vi.mock('../components/ConfirmModal', () => ({ ConfirmModal: () => null })) vi.mock('./MobileFileMarkdownPreview', () => ({ MobileFileMarkdownPreview: () => null })) vi.mock('./MobileFilePreviewSourceText', () => ({ MobileFilePreviewSourceText: () => null })) // A host the shell has not reached by default: no client, so the screen settles on `waiting`. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useForceReconnect: () => transport.forceReconnect, useHostClient: () => ({ ...transport.connection, clientId: null }) })) diff --git a/mobile/src/home/MobileHomeScreen.tsx b/mobile/src/home/MobileHomeScreen.tsx index 7bf9089f77e..b36835614a8 100644 --- a/mobile/src/home/MobileHomeScreen.tsx +++ b/mobile/src/home/MobileHomeScreen.tsx @@ -17,7 +17,7 @@ import { useDisconnectHostClient, useForceReconnect, useForgetHostClient -} from '../transport/client-context' +} from '../transport/host-client-hooks' import { hostEndpointLabel } from '../transport/host-endpoint-label' import { resolveHomeHostConnectionState } from '../transport/home-host-auto-connect' import { removeHostAndCloseClient } from '../transport/host-removal-lifecycle' diff --git a/mobile/src/home/use-mobile-home-host-connections.ts b/mobile/src/home/use-mobile-home-host-connections.ts index 989583f11ab..cbd77919d0c 100644 --- a/mobile/src/home/use-mobile-home-host-connections.ts +++ b/mobile/src/home/use-mobile-home-host-connections.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { decodeAccountsSnapshot } from '../components/AccountUsage' import { subscribeToDesktopNotifications } from '../notifications/mobile-notifications' -import { usePrimeHosts } from '../transport/client-context' +import { usePrimeHosts } from '../transport/host-client-hooks' import { createHostConnectRefetchGate } from '../transport/host-connect-refetch-gate' import { selectHomeAutoConnectHostIds } from '../transport/home-host-auto-connect' import type { RpcClient } from '../transport/rpc-client' diff --git a/mobile/src/host-edit-route-accessibility.test.ts b/mobile/src/host-edit-route-accessibility.test.ts index 12d4ae230e1..3526db9b1b4 100644 --- a/mobile/src/host-edit-route-accessibility.test.ts +++ b/mobile/src/host-edit-route-accessibility.test.ts @@ -41,7 +41,7 @@ vi.mock('./transport/host-store', () => ({ updateHostNameAndEndpoint: dependencies.updateHostNameAndEndpoint })) -vi.mock('./transport/client-context', () => ({ +vi.mock('./transport/host-client-hooks', () => ({ usePrimeHosts: () => dependencies.primeHosts, useRefreshHostClient: () => dependencies.refreshHostClient })) diff --git a/mobile/src/host-edit-save-flow.test.ts b/mobile/src/host-edit-save-flow.test.ts index 0c55da0cb0f..4ca9a356116 100644 --- a/mobile/src/host-edit-save-flow.test.ts +++ b/mobile/src/host-edit-save-flow.test.ts @@ -42,7 +42,7 @@ vi.mock('./transport/host-store', () => ({ updateHostNameAndEndpoint: dependencies.updateHostNameAndEndpoint })) -vi.mock('./transport/client-context', () => ({ +vi.mock('./transport/host-client-hooks', () => ({ usePrimeHosts: () => dependencies.primeHosts, useRefreshHostClient: () => dependencies.refreshHostClient })) diff --git a/mobile/src/host-screen/use-host-screen-controller.ts b/mobile/src/host-screen/use-host-screen-controller.ts index d2661f09668..e79cf96c148 100644 --- a/mobile/src/host-screen/use-host-screen-controller.ts +++ b/mobile/src/host-screen/use-host-screen-controller.ts @@ -8,7 +8,11 @@ import { resolveHostRouteActionState } from '../host-route-action-state' import { useActiveWorktreeScroll } from '../hooks/use-active-worktree-scroll' import { useNow } from '../hooks/use-now' import { useResponsiveLayout } from '../layout/responsive-layout' -import { useForgetHostClient, useForceReconnect, useHostClient } from '../transport/client-context' +import { + useForgetHostClient, + useForceReconnect, + useHostClient +} from '../transport/host-client-hooks' import { useLastConnectedAt, useReconnectAttempt, diff --git a/mobile/src/host-screen/use-host-worktree-actions.ts b/mobile/src/host-screen/use-host-worktree-actions.ts index d2ae2b906e1..66b4b2ce6c2 100644 --- a/mobile/src/host-screen/use-host-worktree-actions.ts +++ b/mobile/src/host-screen/use-host-worktree-actions.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react' import type { useRouter } from 'expo-router' import { floatingWorkspaceSessionPath } from '../session/floating-workspace' import { savePinnedIds } from '../storage/preferences' -import type { useForgetHostClient } from '../transport/client-context' +import type { useForgetHostClient } from '../transport/host-client-hooks' import { removeHostAndCloseClient } from '../transport/host-removal-lifecycle' import { isPageHostRemovalUnavailable } from '../transport/page-host-removal-refusal' import type { RpcClient } from '../transport/rpc-client' diff --git a/mobile/src/mobile-web-shell/MobileWebShellScreen.test.tsx b/mobile/src/mobile-web-shell/MobileWebShellScreen.test.tsx index 1b7976259ed..e13ed72038d 100644 --- a/mobile/src/mobile-web-shell/MobileWebShellScreen.test.tsx +++ b/mobile/src/mobile-web-shell/MobileWebShellScreen.test.tsx @@ -20,7 +20,7 @@ vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context' vi.mock('expo-router', mocks['expo-router']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) vi.mock('./use-mobile-web-shell-session', mocks['./use-mobile-web-shell-session']) diff --git a/mobile/src/mobile-web-shell/catch-all-page-route-states.test.tsx b/mobile/src/mobile-web-shell/catch-all-page-route-states.test.tsx index 7dbae2341b8..fa42ad9b426 100644 --- a/mobile/src/mobile-web-shell/catch-all-page-route-states.test.tsx +++ b/mobile/src/mobile-web-shell/catch-all-page-route-states.test.tsx @@ -117,7 +117,7 @@ vi.mock('../../modules/orca-mobile-web-shell/src', async () => { vi.mock('../app-update/use-wall-app-update', () => ({ useWallAppUpdate: () => null })) -vi.mock('../transport/client-context', () => ({ useHostClient: () => ({ client: null }) })) +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: null }) })) vi.mock('./use-page-host-snapshot', () => ({ usePageHostSnapshot: () => ({ snapshot: SNAPSHOT, diff --git a/mobile/src/mobile-web-shell/host-area-layout-class-change.test.tsx b/mobile/src/mobile-web-shell/host-area-layout-class-change.test.tsx index 964a06d88c5..9ffbf5892e9 100644 --- a/mobile/src/mobile-web-shell/host-area-layout-class-change.test.tsx +++ b/mobile/src/mobile-web-shell/host-area-layout-class-change.test.tsx @@ -57,7 +57,7 @@ vi.mock('../transport/mobile-endpoint-supervisor-support', () => ({ vi.mock('../components/host-protocol-gates-context', () => ({ useHostProtocolGates: () => doubles.gates })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: {}, state: 'connected' }) })) vi.mock('../transport/rpc-operation', () => ({ diff --git a/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx b/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx index eddf863c588..def7f3e679e 100644 --- a/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx +++ b/mobile/src/mobile-web-shell/host-area-leave-session.test.tsx @@ -36,7 +36,7 @@ vi.mock('lucide-react-native', mocks['lucide-react-native']) vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) vi.mock('./use-mobile-web-shell-session', mocks['./use-mobile-web-shell-session']) diff --git a/mobile/src/mobile-web-shell/host-area-sidebar-matrix.test.tsx b/mobile/src/mobile-web-shell/host-area-sidebar-matrix.test.tsx index ef3211e5889..08f74e23976 100644 --- a/mobile/src/mobile-web-shell/host-area-sidebar-matrix.test.tsx +++ b/mobile/src/mobile-web-shell/host-area-sidebar-matrix.test.tsx @@ -75,7 +75,7 @@ vi.mock('lucide-react-native', mocks['lucide-react-native']) vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) // The screen's session is the reducer's, as `sessionFor` carried it. vi.mock('./use-mobile-web-shell-session', () => ({ diff --git a/mobile/src/mobile-web-shell/host-area-two-hosts.test.tsx b/mobile/src/mobile-web-shell/host-area-two-hosts.test.tsx index ab3b64de09e..23996e97777 100644 --- a/mobile/src/mobile-web-shell/host-area-two-hosts.test.tsx +++ b/mobile/src/mobile-web-shell/host-area-two-hosts.test.tsx @@ -53,7 +53,7 @@ vi.mock('lucide-react-native', mocks['lucide-react-native']) vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) // Each host route's session as the reducer leaves it under a declaring page on a wide layout. vi.mock('./use-mobile-web-shell-session', () => ({ diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-agent-history-route.test.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-agent-history-route.test.tsx index 9bfe6bafcb6..c5837839d26 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-agent-history-route.test.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-agent-history-route.test.tsx @@ -28,7 +28,7 @@ vi.mock('react-native', () => ({ })) // The workspace route reads the desktop's connection to decide whether a server is reachable. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: null, state: 'disconnected' }) })) vi.mock('../transport/workspace-server', () => ({ diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-failed-update-fallback.test.ts b/mobile/src/mobile-web-shell/mobile-web-shell-failed-update-fallback.test.ts index 3b473e1bfa1..afeeac0e2c6 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-failed-update-fallback.test.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-failed-update-fallback.test.ts @@ -54,7 +54,7 @@ vi.mock('../transport/mobile-endpoint-supervisor-support', () => ({ vi.mock('../components/host-protocol-gates-context', () => ({ useHostProtocolGates: () => doubles.gates })) -vi.mock('../transport/client-context', () => ({ useHostClient: () => doubles.connection })) +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => doubles.connection })) vi.mock('../transport/rpc-operation', () => ({ defineRpcOperation: (definition: unknown) => definition, runRpcOperation: async () => { diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-files-route.test.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-files-route.test.tsx index 0e74320be27..53644e78c51 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-files-route.test.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-files-route.test.tsx @@ -28,7 +28,7 @@ vi.mock('react-native', () => ({ })) // The workspace route reads the desktop's connection to decide whether a server is reachable. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: null, state: 'disconnected' }) })) vi.mock('../transport/workspace-server', () => ({ diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-keyboard-overlay-screen.test.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-keyboard-overlay-screen.test.tsx index 2303b0b9e47..917b4faaecc 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-keyboard-overlay-screen.test.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-keyboard-overlay-screen.test.tsx @@ -20,7 +20,7 @@ vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context' vi.mock('expo-router', mocks['expo-router']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) vi.mock('./use-mobile-web-shell-session', mocks['./use-mobile-web-shell-session']) diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts b/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts index 627dbf0d129..db56073fe39 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts +++ b/mobile/src/mobile-web-shell/mobile-web-shell-offline-grant-staleness.test.ts @@ -45,7 +45,7 @@ vi.mock('../transport/mobile-endpoint-supervisor-support', () => ({ vi.mock('../components/host-protocol-gates-context', () => ({ useHostProtocolGates: () => doubles.gates })) -vi.mock('../transport/client-context', () => ({ useHostClient: () => doubles.connection })) +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => doubles.connection })) vi.mock('../transport/rpc-operation', () => ({ defineRpcOperation: (definition: unknown) => definition, runRpcOperation: async () => { diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-safe-area-screen.test.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-safe-area-screen.test.tsx index 678253ce146..71e970786c8 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-safe-area-screen.test.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-safe-area-screen.test.tsx @@ -20,7 +20,7 @@ vi.mock('react-native-safe-area-context', mocks['react-native-safe-area-context' vi.mock('expo-router', mocks['expo-router']) vi.mock('../../modules/orca-mobile-web-shell/src', mocks['../../modules/orca-mobile-web-shell/src']) vi.mock('../app-update/use-wall-app-update', mocks['../app-update/use-wall-app-update']) -vi.mock('../transport/client-context', mocks['../transport/client-context']) +vi.mock('../transport/host-client-hooks', mocks['../transport/host-client-hooks']) vi.mock('./use-page-host-snapshot', mocks['./use-page-host-snapshot']) vi.mock('./use-mobile-web-shell-session', mocks['./use-mobile-web-shell-session']) diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-screen-test-mocks.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-screen-test-mocks.tsx index 2f62e2e01ef..0bcd3e95338 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-screen-test-mocks.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-screen-test-mocks.tsx @@ -150,7 +150,7 @@ export function screenModuleMocks(dependencies: ScreenDependencies) { '../app-update/use-wall-app-update': () => ({ useWallAppUpdate: () => null }), // The real bridge hook runs, so the props it owns are the ones the view is handed here; only the // client lookup is stubbed, because reaching it imports the Expo runtime this test does not have. - '../transport/client-context': () => ({ + '../transport/host-client-hooks': () => ({ useHostClient: () => ({ client: dependencies.client }) }), // Reaching the real one imports the host store and expo-secure-store, whose module touches an Expo diff --git a/mobile/src/mobile-web-shell/mobile-web-shell-session-route.test.tsx b/mobile/src/mobile-web-shell/mobile-web-shell-session-route.test.tsx index 4e5b38aa559..dea4f920d26 100644 --- a/mobile/src/mobile-web-shell/mobile-web-shell-session-route.test.tsx +++ b/mobile/src/mobile-web-shell/mobile-web-shell-session-route.test.tsx @@ -31,7 +31,7 @@ vi.mock('react-native', () => ({ })) // The workspace route reads the desktop's connection to decide whether a server is reachable. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: null, state: 'disconnected' }) })) vi.mock('../transport/workspace-server', () => ({ diff --git a/mobile/src/mobile-web-shell/shell-route-first-frame.test.tsx b/mobile/src/mobile-web-shell/shell-route-first-frame.test.tsx index 32e793cf18c..1f692e5ffe3 100644 --- a/mobile/src/mobile-web-shell/shell-route-first-frame.test.tsx +++ b/mobile/src/mobile-web-shell/shell-route-first-frame.test.tsx @@ -37,7 +37,7 @@ vi.mock('react-native', () => ({ })) // The workspace route reads the desktop's connection to decide whether a server is reachable. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: null, state: 'disconnected' }) })) vi.mock('../transport/workspace-server', () => ({ diff --git a/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.test.ts b/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.test.ts index 13876aa0d53..bc7453dd86e 100644 --- a/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.test.ts +++ b/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.test.ts @@ -30,7 +30,7 @@ import type { FakeRpcClient } from './bridge-host-test-fakes' const doubles = vi.hoisted((): { client: FakeRpcClient | null } => ({ client: null })) // Reaching the real one imports the Expo runtime this test does not have; the hook reads one field. -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: doubles.client }) })) diff --git a/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.ts b/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.ts index b640887706e..db216269345 100644 --- a/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.ts +++ b/mobile/src/mobile-web-shell/use-mobile-web-shell-bridge.ts @@ -3,7 +3,7 @@ import type { MobileWebShellBridgeMessagePayload, OrcaMobileWebShellViewHandle } from '../../modules/orca-mobile-web-shell/src' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import { createBridgeDiagnosticReporter } from './bridge-diagnostic-log' import { bridgeDiagnosticLine, frameWithoutHostLine } from './shell-bridge-log-lines' import { recordShellLifecycleLine } from './shell-lifecycle-line-recorder' diff --git a/mobile/src/mobile-web-shell/use-mobile-web-shell-session.test.ts b/mobile/src/mobile-web-shell/use-mobile-web-shell-session.test.ts index 2e915d7f9d2..46591c7ccec 100644 --- a/mobile/src/mobile-web-shell/use-mobile-web-shell-session.test.ts +++ b/mobile/src/mobile-web-shell/use-mobile-web-shell-session.test.ts @@ -77,7 +77,7 @@ vi.mock('../transport/mobile-endpoint-supervisor-support', () => ({ vi.mock('../components/host-protocol-gates-context', () => ({ useHostProtocolGates: () => doubles.gates })) -vi.mock('../transport/client-context', () => ({ useHostClient: () => doubles.connection })) +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => doubles.connection })) vi.mock('../transport/rpc-operation', () => ({ defineRpcOperation: (definition: unknown) => definition, runRpcOperation: async (client: unknown) => { diff --git a/mobile/src/mobile-web-shell/use-mobile-web-shell-session.ts b/mobile/src/mobile-web-shell/use-mobile-web-shell-session.ts index 05cfba5dd53..0d837ef0445 100644 --- a/mobile/src/mobile-web-shell/use-mobile-web-shell-session.ts +++ b/mobile/src/mobile-web-shell/use-mobile-web-shell-session.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { MobileWebShellFailureReason } from '../../modules/orca-mobile-web-shell/src/load-state' import { useHostProtocolGates } from '../components/host-protocol-gates-context' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import type { GenerationStore } from './generation-store' import { deriveHostCacheKey } from './host-cache-key' import { download, openCache, readManifest } from './mobile-web-shell-session-effects' diff --git a/mobile/src/navigation/workspace-route.test.tsx b/mobile/src/navigation/workspace-route.test.tsx index 888571faf75..b29cc2c2bf4 100644 --- a/mobile/src/navigation/workspace-route.test.tsx +++ b/mobile/src/navigation/workspace-route.test.tsx @@ -36,7 +36,7 @@ vi.mock('react-native', () => ({ Text: 'Text', Pressable: 'Pressable' })) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useHostClient: () => ({ client: route.client, state: 'connected' }) })) // The server's status is read for real; only the desktop's persisted records are stubbed. diff --git a/mobile/src/navigation/workspace-route.tsx b/mobile/src/navigation/workspace-route.tsx index 8453053b932..c4e22259c98 100644 --- a/mobile/src/navigation/workspace-route.tsx +++ b/mobile/src/navigation/workspace-route.tsx @@ -8,7 +8,7 @@ import { useOptionalHostProtocolGates } from '../components/host-protocol-gates-context' import { PageRouteUnavailableScreen } from '../mobile-web-shell/PageRouteUnavailableScreen' -import { useHostClient } from '../transport/client-context' +import { useHostClient } from '../transport/host-client-hooks' import { WorkspaceServerContext } from '../transport/use-workspace-client' import { useWorkspaceServer } from '../transport/workspace-server' diff --git a/mobile/src/session/MobileDiffReviewRouteScreen.tsx b/mobile/src/session/MobileDiffReviewRouteScreen.tsx index ddca209789c..a3c83a316af 100644 --- a/mobile/src/session/MobileDiffReviewRouteScreen.tsx +++ b/mobile/src/session/MobileDiffReviewRouteScreen.tsx @@ -4,7 +4,7 @@ import { MobileDiffReviewScreenView } from '../components/MobileDiffReviewScreen import { firstReviewParam, normalizeReviewFilterParam } from './mobile-diff-review-screen-model' import { normalizeReviewAreaParam } from './mobile-diff-review-positioning' import { useMobileDiffReviewController } from './use-mobile-diff-review-controller' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import { useHostProtocolGates } from '../components/host-protocol-gates-context' import { useWorkspaceRouteHandoff } from '../navigation/workspace-route-handoff' diff --git a/mobile/src/session/use-mobile-session-foundation.ts b/mobile/src/session/use-mobile-session-foundation.ts index 2b99af3e73b..287a12e958f 100644 --- a/mobile/src/session/use-mobile-session-foundation.ts +++ b/mobile/src/session/use-mobile-session-foundation.ts @@ -3,7 +3,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useLocalSearchParams } from 'expo-router' import { useRouteHandoff } from '../navigation/route-handoff' import { HOST_DOCK_MIN_WIDTH } from '../storage/preferences' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import { useLastConnectedAt, diff --git a/mobile/src/source-control/MobileGitHistoryList.test.tsx b/mobile/src/source-control/MobileGitHistoryList.test.tsx index d8ef0c2438d..498d5ba1b34 100644 --- a/mobile/src/source-control/MobileGitHistoryList.test.tsx +++ b/mobile/src/source-control/MobileGitHistoryList.test.tsx @@ -31,7 +31,7 @@ const transport = vi.hoisted( forceReconnect: () => Promise.resolve() }) ) -vi.mock('../transport/client-context', () => ({ +vi.mock('../transport/host-client-hooks', () => ({ useForceReconnect: () => transport.forceReconnect })) diff --git a/mobile/src/source-control/MobileGitHistoryList.tsx b/mobile/src/source-control/MobileGitHistoryList.tsx index 495ff22d362..86ed5d0ae7d 100644 --- a/mobile/src/source-control/MobileGitHistoryList.tsx +++ b/mobile/src/source-control/MobileGitHistoryList.tsx @@ -4,7 +4,7 @@ import { ChevronDown, ChevronRight } from 'lucide-react-native' import { colors, radii, spacing, typography } from '../theme/mobile-theme' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { connectionRetryAction } from '../transport/connection-retry-action' import { gitCommitCompareRead } from './mobile-git-read-operations' import type { MobileGitChangedFile } from './git-compare-reply-schema' diff --git a/mobile/src/source-control/use-mobile-source-control-state.ts b/mobile/src/source-control/use-mobile-source-control-state.ts index 940954ca069..ebf7f14b9a3 100644 --- a/mobile/src/source-control/use-mobile-source-control-state.ts +++ b/mobile/src/source-control/use-mobile-source-control-state.ts @@ -1,6 +1,6 @@ import { useCallback, useMemo, useRef, useState } from 'react' import { useSafeAreaInsets } from 'react-native-safe-area-context' -import { useForceReconnect } from '../transport/client-context' +import { useForceReconnect } from '../transport/host-client-hooks' import { useWorkspaceClient } from '../transport/use-workspace-client' import { getWorktreeLabel } from '../session/worktree-label' import { useMobileGitRequests } from './use-mobile-git-requests' diff --git a/mobile/src/tasks/mobile-tasks-dependencies.ts b/mobile/src/tasks/mobile-tasks-dependencies.ts index 2be9049c975..b317b650e0a 100644 --- a/mobile/src/tasks/mobile-tasks-dependencies.ts +++ b/mobile/src/tasks/mobile-tasks-dependencies.ts @@ -55,7 +55,7 @@ export { } from 'lucide-react-native' export type { RpcClient } from '../transport/rpc-client' export type { RpcSuccess } from '../transport/types' -export { useHostClient } from '../transport/client-context' +export { useHostClient } from '../transport/host-client-hooks' export { useLastConnectedAt, useRelayRecoveryStatus, diff --git a/mobile/src/terminal/document/cell-geometry.ts b/mobile/src/terminal/document/cell-geometry.ts index 79db5156026..d4c0157b21d 100644 --- a/mobile/src/terminal/document/cell-geometry.ts +++ b/mobile/src/terminal/document/cell-geometry.ts @@ -1,5 +1,4 @@ -import { getCellHeight } from './fit-scale' -import { getCellWidth, getTotalScale } from './viewport-transform' +import { getCellHeight, getCellWidth, getTotalScale } from './cell-metrics' import type { TerminalDocumentScope } from './document-scope' export function cellToViewportPx(scope: TerminalDocumentScope, col: number, absRow: number) { diff --git a/mobile/src/terminal/document/cell-metrics.ts b/mobile/src/terminal/document/cell-metrics.ts new file mode 100644 index 00000000000..723e81d4578 --- /dev/null +++ b/mobile/src/terminal/document/cell-metrics.ts @@ -0,0 +1,31 @@ +import type { TerminalDocumentScope } from './document-scope' + +// Why: a leaf of pure reads, so fit, transform and overlay modules can share them without a cycle. + +/** 0 until the renderer has measured a cell. */ +export function getMeasuredCellHeight(scope: TerminalDocumentScope) { + const core = scope.term?._core + if (core && core._renderService && core._renderService.dimensions) { + return core._renderService.dimensions.css.cell.height || 0 + } + return 0 +} + +export function getCellHeight(scope: TerminalDocumentScope) { + return getMeasuredCellHeight(scope) || 15 +} + +export function getCellWidth(scope: TerminalDocumentScope) { + if (!scope.term || !scope.term._core) { + return 0 + } + const core = scope.term._core + if (core._renderService && core._renderService.dimensions) { + return core._renderService.dimensions.css.cell.width || 0 + } + return 0 +} + +export function getTotalScale(scope: TerminalDocumentScope) { + return scope.currentScale * scope.userScale +} diff --git a/mobile/src/terminal/document/fit-scale.ts b/mobile/src/terminal/document/fit-scale.ts index 8047b9f630c..6b0c3cac0db 100644 --- a/mobile/src/terminal/document/fit-scale.ts +++ b/mobile/src/terminal/document/fit-scale.ts @@ -1,21 +1,11 @@ import { repositionOverlay } from './selection-overlay' -import { - computeFitScale, - flog, - getCellWidth, - getMeasuredCellHeight, - getTotalScale, - updateTransform -} from './viewport-transform' +import { computeFitScale, flog, updateTransform } from './viewport-transform' +import { getCellWidth, getTotalScale } from './cell-metrics' import type { TerminalDocumentScope } from './document-scope' import type { TerminalViewportChange } from './document-host-seams' import { scheduleDocumentFrame } from './document-frame-registry' import { emitKeyboardAvoidanceMetrics } from './keyboard-avoidance-metrics' -export function getCellHeight(scope: TerminalDocumentScope) { - return getMeasuredCellHeight(scope) || 15 -} - // Why: clamp pan so the terminal content always covers the viewport // when zoomed in. When content is smaller than viewport in a // dimension, pin to top-left (no floating in the middle). diff --git a/mobile/src/terminal/document/host-message-router.ts b/mobile/src/terminal/document/host-message-router.ts index 3d5352b3962..6adfb3bcdee 100644 --- a/mobile/src/terminal/document/host-message-router.ts +++ b/mobile/src/terminal/document/host-message-router.ts @@ -6,7 +6,7 @@ import { emitModesIfChanged } from './mode-mirroring' import { reflow } from './reflow' import { resumeTerminalDataReplyAuthority } from './query-reply' import { repositionOverlay } from './selection-overlay' -import { cancelSelect } from './selection-range' +import { cancelSelect } from './selection-mode' import { resetEvictionCounter } from './selection-state-and-eviction' import { applyTerminalTheme } from './terminal-theme' import { init, resize, write } from './terminal-init' diff --git a/mobile/src/terminal/document/keyboard-avoidance-metrics.ts b/mobile/src/terminal/document/keyboard-avoidance-metrics.ts index a0f28d801c1..f21f5ba489b 100644 --- a/mobile/src/terminal/document/keyboard-avoidance-metrics.ts +++ b/mobile/src/terminal/document/keyboard-avoidance-metrics.ts @@ -1,4 +1,4 @@ -import { getMeasuredCellHeight, getTotalScale } from './viewport-transform' +import { getMeasuredCellHeight, getTotalScale } from './cell-metrics' import { notify } from './host-notify' import type { TerminalDocumentCell, diff --git a/mobile/src/terminal/document/mouse-click-drag.ts b/mobile/src/terminal/document/mouse-click-drag.ts index dbae42668d4..53415f966ef 100644 --- a/mobile/src/terminal/document/mouse-click-drag.ts +++ b/mobile/src/terminal/document/mouse-click-drag.ts @@ -1,5 +1,6 @@ import { handleDragMove, repositionOverlay, stopEdgeScroll } from './selection-overlay' -import { applyXtermSelection, cancelSelect } from './selection-range' +import { cancelSelect } from './selection-mode' +import { applyXtermSelection } from './selection-range' import { notify } from './host-notify' import { getMouseTrackingMode, isSafeSgrMouseCoordinate } from './mouse-input-encoding' import { viewportToCell } from './viewport-cell' diff --git a/mobile/src/terminal/document/mouse-report-cell.ts b/mobile/src/terminal/document/mouse-report-cell.ts index 58ee2ff80b7..b985128f34d 100644 --- a/mobile/src/terminal/document/mouse-report-cell.ts +++ b/mobile/src/terminal/document/mouse-report-cell.ts @@ -1,5 +1,4 @@ -import { getCellHeight } from './fit-scale' -import { getCellWidth, getTotalScale } from './viewport-transform' +import { getCellHeight, getCellWidth, getTotalScale } from './cell-metrics' import { viewportPoint } from './viewport-cell' import type { TerminalDocumentScope } from './document-scope' diff --git a/mobile/src/terminal/document/native-document-bundle.test.ts b/mobile/src/terminal/document/native-document-bundle.test.ts index 4389c3bb105..a946ed30d73 100644 --- a/mobile/src/terminal/document/native-document-bundle.test.ts +++ b/mobile/src/terminal/document/native-document-bundle.test.ts @@ -313,8 +313,8 @@ describe('the bundled native document', () => { expect(inputs.filter((input) => input.includes('node_modules'))).toEqual([]) // 50: `laid-out-cell-box` reads the cell box xterm laid out, for web-ready and each render; // `terminal-grid-fit` is the one fit of that box and `terminal-cell-box` its one comparison, - // both shared with the app. - expect(inputs).toHaveLength(50) + // both shared with the app. 52: `cell-metrics` and `selection-mode` split out to break cycles. + expect(inputs).toHaveLength(52) expect(script).not.toContain('__commonJS') // `__esm` wrappers are esbuild's answer to a cycle, and a cycle would make a module's top level // run at first import rather than where the bundle places it. diff --git a/mobile/src/terminal/document/normal-buffer-smooth-scroll.ts b/mobile/src/terminal/document/normal-buffer-smooth-scroll.ts index 27c91d3031d..ac51924e747 100644 --- a/mobile/src/terminal/document/normal-buffer-smooth-scroll.ts +++ b/mobile/src/terminal/document/normal-buffer-smooth-scroll.ts @@ -1,5 +1,5 @@ -import { getCellHeight } from './fit-scale' -import { getTotalScale, updateScrollIndicator } from './viewport-transform' +import { getCellHeight, getTotalScale } from './cell-metrics' +import { updateScrollIndicator } from './viewport-transform' import type { TerminalDocumentScope } from './document-scope' import { scheduleDocumentFrame } from './document-frame-registry' diff --git a/mobile/src/terminal/document/selection-menu-buttons.ts b/mobile/src/terminal/document/selection-menu-buttons.ts index 6f7dc745907..150e6a99b42 100644 --- a/mobile/src/terminal/document/selection-menu-buttons.ts +++ b/mobile/src/terminal/document/selection-menu-buttons.ts @@ -1,6 +1,6 @@ import type { TerminalDocumentScope } from './document-scope' import { notify } from './host-notify' -import { cancelSelect } from './selection-range' +import { cancelSelect } from './selection-mode' import { repositionOverlay } from './selection-overlay' export function startSelectionMenuButtons(scope: TerminalDocumentScope) { diff --git a/mobile/src/terminal/document/selection-mode.ts b/mobile/src/terminal/document/selection-mode.ts new file mode 100644 index 00000000000..aa9c7c5033a --- /dev/null +++ b/mobile/src/terminal/document/selection-mode.ts @@ -0,0 +1,36 @@ +import type { TerminalDocumentScope } from './document-scope' +import { notify } from './host-notify' +import { repositionOverlay, stopEdgeScroll } from './selection-overlay' +import { seedWordSelection } from './selection-range' + +// Why: entering and leaving select mode drives the overlay; kept out of selection-range so the +// range math the overlay reads doesn't import the overlay back. + +export function cancelSelect(scope: TerminalDocumentScope) { + scope.selMode = 'navigate' + scope.sel = null + stopEdgeScroll(scope) + if (scope.term) { + try { + scope.term.clearSelection() + } catch {} + // Why: some xterm renderers cache cells and skip repaint on + // clearSelection alone, leaving the previously-highlighted cells + // visually selected. Force a full refresh so the selection layer + // actually clears on screen. + try { + scope.term.refresh(0, scope.term.rows - 1) + } catch {} + } + scope.selectionOverlay!.classList.remove('active') + notify(scope, { type: 'set-select-mode', enabled: false }) +} + +export function enterSelect(scope: TerminalDocumentScope, col: number, absRow: number) { + scope.selMode = 'select' + seedWordSelection(scope, col, absRow) + scope.selectionOverlay!.classList.add('active') + notify(scope, { type: 'set-select-mode', enabled: true }) + notify(scope, { type: 'haptic', kind: 'selection' }) + repositionOverlay(scope) +} diff --git a/mobile/src/terminal/document/selection-overlay.ts b/mobile/src/terminal/document/selection-overlay.ts index 5c2f89e367b..6d411413c2a 100644 --- a/mobile/src/terminal/document/selection-overlay.ts +++ b/mobile/src/terminal/document/selection-overlay.ts @@ -1,10 +1,9 @@ +import { getCellHeight, getTotalScale } from './cell-metrics' import { cellToViewportPx } from './cell-geometry' import type { TerminalDocumentScope } from './document-scope' -import { getCellHeight } from './fit-scale' import { notify } from './host-notify' import { applyXtermSelection, selRange } from './selection-range' import { viewportPoint, viewportToCell } from './viewport-cell' -import { getTotalScale } from './viewport-transform' /** How close to an edge a handle drag starts scrolling, in pixels. */ const EDGE_SCROLL_PX = 40 diff --git a/mobile/src/terminal/document/selection-range.ts b/mobile/src/terminal/document/selection-range.ts index ce32ccdcd0c..bde95116758 100644 --- a/mobile/src/terminal/document/selection-range.ts +++ b/mobile/src/terminal/document/selection-range.ts @@ -1,7 +1,5 @@ import { getLineText } from './cell-geometry' import type { TerminalDocumentScope, TerminalDocumentSelection } from './document-scope' -import { notify } from './host-notify' -import { repositionOverlay, stopEdgeScroll } from './selection-overlay' /** What counts as one word for select-all and for word seeding. */ const WORD_RE = /[\p{L}\p{N}_./:@~+=?&#%-]/u @@ -87,32 +85,3 @@ export function applyXtermSelection(scope: TerminalDocumentScope) { scope.term.select(r.start.col, r.start.row, length) } catch {} } - -export function cancelSelect(scope: TerminalDocumentScope) { - scope.selMode = 'navigate' - scope.sel = null - stopEdgeScroll(scope) - if (scope.term) { - try { - scope.term.clearSelection() - } catch {} - // Why: some xterm renderers cache cells and skip repaint on - // clearSelection alone, leaving the previously-highlighted cells - // visually selected. Force a full refresh so the selection layer - // actually clears on screen. - try { - scope.term.refresh(0, scope.term.rows - 1) - } catch {} - } - scope.selectionOverlay!.classList.remove('active') - notify(scope, { type: 'set-select-mode', enabled: false }) -} - -export function enterSelect(scope: TerminalDocumentScope, col: number, absRow: number) { - scope.selMode = 'select' - seedWordSelection(scope, col, absRow) - scope.selectionOverlay!.classList.add('active') - notify(scope, { type: 'set-select-mode', enabled: true }) - notify(scope, { type: 'haptic', kind: 'selection' }) - repositionOverlay(scope) -} diff --git a/mobile/src/terminal/document/selection-state-and-eviction.ts b/mobile/src/terminal/document/selection-state-and-eviction.ts index 5ff46219bee..65f394517b9 100644 --- a/mobile/src/terminal/document/selection-state-and-eviction.ts +++ b/mobile/src/terminal/document/selection-state-and-eviction.ts @@ -1,6 +1,6 @@ import { elementInRoot } from './document-host-seams' import { repositionOverlay } from './selection-overlay' -import { cancelSelect } from './selection-range' +import { cancelSelect } from './selection-mode' import { notify } from './host-notify' import type { TerminalDocumentScope } from './document-scope' diff --git a/mobile/src/terminal/document/surface-touch-gestures.ts b/mobile/src/terminal/document/surface-touch-gestures.ts index aad20c045f1..5f8d187cb7c 100644 --- a/mobile/src/terminal/document/surface-touch-gestures.ts +++ b/mobile/src/terminal/document/surface-touch-gestures.ts @@ -1,7 +1,8 @@ +import { getCellHeight, getTotalScale } from './cell-metrics' import type { TerminalDocumentScope } from './document-scope' import { scheduleDocumentFrame } from './document-frame-registry' import { touchesInRoot } from './document-host-seams' -import { clampPan, getCellHeight } from './fit-scale' +import { clampPan } from './fit-scale' import { notify } from './host-notify' import { attachSurfaceMouseClickDragHandler } from './mouse-click-drag' import { routeScrollLines, shouldRouteScrollToTerminalInput } from './mouse-input-encoding' @@ -17,7 +18,7 @@ import { MIN_TEXT_SCALE, snapToTextScalePreset } from './text-scaling' -import { getTotalScale, updateTransform } from './viewport-transform' +import { updateTransform } from './viewport-transform' import { viewportPoint } from './viewport-cell' import { attachSurfaceWheelHandler } from './wheel-scroll' diff --git a/mobile/src/terminal/document/tap-dispatch.ts b/mobile/src/terminal/document/tap-dispatch.ts index dbf459c225f..b0cf9a1ec05 100644 --- a/mobile/src/terminal/document/tap-dispatch.ts +++ b/mobile/src/terminal/document/tap-dispatch.ts @@ -1,5 +1,5 @@ import { handleDragMove, stopEdgeScroll } from './selection-overlay' -import { cancelSelect, enterSelect } from './selection-range' +import { cancelSelect, enterSelect } from './selection-mode' import { notify } from './host-notify' import { viewportToCell } from './viewport-cell' import type { TerminalDocumentScope } from './document-scope' diff --git a/mobile/src/terminal/document/terminal-init.ts b/mobile/src/terminal/document/terminal-init.ts index 091321503b6..0df9228275b 100644 --- a/mobile/src/terminal/document/terminal-init.ts +++ b/mobile/src/terminal/document/terminal-init.ts @@ -14,7 +14,7 @@ import { } from './mouse-mode-decset-scan' import { captureInitialOscLinkTexts } from './osc-link-tap' import { attachTerminalQueryReplyBridge, resetTerminalDataReplyAuthority } from './query-reply' -import { cancelSelect } from './selection-range' +import { cancelSelect } from './selection-mode' import { resetEvictionCounter } from './selection-state-and-eviction' import { beginTerminalSurfaceSwap, commitTerminalSurfaceSwap } from './surface-swap' import { attachTermObservers } from './term-observers' diff --git a/mobile/src/terminal/document/text-scaling.ts b/mobile/src/terminal/document/text-scaling.ts index 88d7b5dd762..5fc9395e0db 100644 --- a/mobile/src/terminal/document/text-scaling.ts +++ b/mobile/src/terminal/document/text-scaling.ts @@ -1,10 +1,10 @@ +import { getCellHeight, getCellWidth } from './cell-metrics' import { elementInRoot } from './document-host-seams' import { TERMINAL_TEXT_SCALES } from '../terminal-text-scales' import type { TerminalDocumentScope } from './document-scope' import { scheduleDocumentFrame } from './document-frame-registry' -import { applyFitScale, getCellHeight } from './fit-scale' +import { applyFitScale } from './fit-scale' import { fitDimensionsFromCell } from '../terminal-grid-fit' -import { getCellWidth } from './viewport-transform' // Why: init() flips ready false on every re-init (live width reflow included) // while the old surface stays visible; a document-scoped latch drives the diff --git a/mobile/src/terminal/document/viewport-cell.ts b/mobile/src/terminal/document/viewport-cell.ts index 63718a91afa..b78a51b3eb6 100644 --- a/mobile/src/terminal/document/viewport-cell.ts +++ b/mobile/src/terminal/document/viewport-cell.ts @@ -1,6 +1,5 @@ +import { getCellHeight, getCellWidth, getTotalScale } from './cell-metrics' import type { TerminalDocumentScope } from './document-scope' -import { getCellHeight } from './fit-scale' -import { getCellWidth, getTotalScale } from './viewport-transform' /** * A client point in the grid's own frame, which is where pan, cells and overlays are measured. diff --git a/mobile/src/terminal/document/viewport-transform.ts b/mobile/src/terminal/document/viewport-transform.ts index 61190307db4..d0a26e36dc5 100644 --- a/mobile/src/terminal/document/viewport-transform.ts +++ b/mobile/src/terminal/document/viewport-transform.ts @@ -1,6 +1,7 @@ import { repositionOverlay } from './selection-overlay' import { shouldRouteScrollToTerminalInput } from './mouse-input-encoding' import type { TerminalDocumentScope } from './document-scope' +import { getCellWidth, getTotalScale } from './cell-metrics' // Why: after init() the initial scrollback applyFitScale may have run // against an empty buffer (or one without the widest line yet). Re-fit @@ -19,26 +20,6 @@ export function flog(scope: TerminalDocumentScope, tag: string, payload: Record< } catch {} } -/** 0 until the renderer has measured a cell. */ -export function getMeasuredCellHeight(scope: TerminalDocumentScope) { - const core = scope.term?._core - if (core && core._renderService && core._renderService.dimensions) { - return core._renderService.dimensions.css.cell.height || 0 - } - return 0 -} - -export function getCellWidth(scope: TerminalDocumentScope) { - if (!scope.term || !scope.term._core) { - return 0 - } - const core = scope.term._core - if (core._renderService && core._renderService.dimensions) { - return core._renderService.dimensions.css.cell.width || 0 - } - return 0 -} - // Why: width measurement strategy. // 1. Prefer cellWidth × term.cols — this is what xterm's renderer uses // to lay out and is independent of buffer content. It's the "logical @@ -67,10 +48,6 @@ export function computeFitScale(scope: TerminalDocumentScope) { return Math.min(1, vpWidth / termWidth) } -export function getTotalScale(scope: TerminalDocumentScope) { - return scope.currentScale * scope.userScale -} - export function updateTransform(scope: TerminalDocumentScope) { scope.surface!.style.transform = 'translate(' + scope.panX + 'px,' + scope.panY + 'px) scale(' + getTotalScale(scope) + ')' diff --git a/mobile/src/terminal/document/wheel-scroll.ts b/mobile/src/terminal/document/wheel-scroll.ts index 46c57aad88a..1674d775053 100644 --- a/mobile/src/terminal/document/wheel-scroll.ts +++ b/mobile/src/terminal/document/wheel-scroll.ts @@ -1,6 +1,5 @@ -import { getCellHeight } from './fit-scale' +import { getCellHeight, getTotalScale } from './cell-metrics' import { routeScrollLines, shouldRouteScrollToTerminalInput } from './mouse-input-encoding' -import { getTotalScale } from './viewport-transform' import { dispatcherShouldBlockSurface } from './tap-dispatch' import { enqueueNormalBufferScrollDelta, diff --git a/mobile/src/transport/client-context.test.ts b/mobile/src/transport/client-context.test.ts index 23bdd0cc79a..2fd56fa7768 100644 --- a/mobile/src/transport/client-context.test.ts +++ b/mobile/src/transport/client-context.test.ts @@ -44,14 +44,14 @@ vi.mock('./connection-log-background-flush', () => ({ subscribeConnectionLogBackgroundFlush: () => () => {} })) +import { RpcClientProvider } from './client-context' import { - RpcClientProvider, useDisconnectHostClient, useForceReconnect, useHostClient, usePrimeHosts, useRefreshHostClient -} from './client-context' +} from './host-client-hooks' import { useAllHostClients } from './use-all-host-clients' import { useRelayRecoveryStatus } from './client-context-connection-metrics' import { selectHomeAutoConnectHostIds } from './home-host-auto-connect' diff --git a/mobile/src/transport/client-context.tsx b/mobile/src/transport/client-context.tsx index 0348c22105d..356ac12efa6 100644 --- a/mobile/src/transport/client-context.tsx +++ b/mobile/src/transport/client-context.tsx @@ -35,15 +35,6 @@ import { import type { ConnectionState, HostProfile } from './types' import type { RpcClientContextValue } from './rpc-client-context-contract' -export { - useDisconnectHostClient, - useForceReconnect, - useForgetHostClient, - useHostClient, - usePrimeHosts, - useRefreshHostClient -} from './host-client-hooks' - type StoreEntry = HostClientStoreEntry const Ctx = createContext(null) diff --git a/mobile/src/transport/client-context.web.tsx b/mobile/src/transport/client-context.web.tsx index a1606ba9853..ebb65532f34 100644 --- a/mobile/src/transport/client-context.web.tsx +++ b/mobile/src/transport/client-context.web.tsx @@ -7,15 +7,6 @@ import { BRIDGE_PAGE_CLIENT_ID } from '../mobile-web-shell/bridge/bridge-page-cl import type { ConnectionState, HostProfile } from './types' import type { RpcClientContextValue } from './rpc-client-context-contract' -export { - useDisconnectHostClient, - useForceReconnect, - useForgetHostClient, - useHostClient, - usePrimeHosts, - useRefreshHostClient -} from './host-client-hooks' - const Ctx = createContext(null) /** The page's own client, which is more than an `RpcClient`: the route seam reads the session off * it to decide which screens are this document's. Separate from `Ctx` so the shared contract above diff --git a/mobile/src/transport/host-open-recovery.test.tsx b/mobile/src/transport/host-open-recovery.test.tsx index d4bf4fe669a..6e2cc52c7f7 100644 --- a/mobile/src/transport/host-open-recovery.test.tsx +++ b/mobile/src/transport/host-open-recovery.test.tsx @@ -35,14 +35,14 @@ vi.mock('./connection-log-background-flush', () => ({ subscribeConnectionLogBackgroundFlush: () => () => {} })) +import { RpcClientProvider } from './client-context' import { useDisconnectHostClient, - RpcClientProvider, useForgetHostClient, useForceReconnect, useHostClient, useRefreshHostClient -} from './client-context' +} from './host-client-hooks' const HOST = { id: 'host-1', diff --git a/mobile/src/transport/settings-host-client-lifecycle.test.ts b/mobile/src/transport/settings-host-client-lifecycle.test.ts index 44a9a94f86a..b158a8aeaa1 100644 --- a/mobile/src/transport/settings-host-client-lifecycle.test.ts +++ b/mobile/src/transport/settings-host-client-lifecycle.test.ts @@ -8,11 +8,10 @@ import { useAllHostClients } from './use-all-host-clients' import { useFocusedSettingsHostClients } from './settings-host-client-connections' import { RpcClientProvider, - useHostClient, - usePrimeHosts, useRpcClientContext, type RpcClientContextValue } from './client-context' +import { useHostClient, usePrimeHosts } from './host-client-hooks' import { selectHomeAutoConnectHostIds } from './home-host-auto-connect' import type { RpcClient } from './rpc-client' import type { ConnectionState, HostProfile } from './types' diff --git a/mobile/src/transport/use-workspace-client.ts b/mobile/src/transport/use-workspace-client.ts index b147b1bbb52..3830976b487 100644 --- a/mobile/src/transport/use-workspace-client.ts +++ b/mobile/src/transport/use-workspace-client.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react' -import { useHostClient } from './client-context' +import { useHostClient } from './host-client-hooks' import type { RpcClient } from './rpc-client' import type { ConnectionState } from './types' diff --git a/mobile/web-entry/web-overrides.json b/mobile/web-entry/web-overrides.json index ea99a509653..21837a94823 100644 --- a/mobile/web-entry/web-overrides.json +++ b/mobile/web-entry/web-overrides.json @@ -139,7 +139,7 @@ }, { "file": "src/components/AuthFailedBannerActions.web.tsx", - "reason": "The auth-failed banner offers Retry, Re-pair and Remove, and on the page only Re-pair works. /pair-scan sits outside the page's route root of app/h (config/scripts/mobile-web-app-route-manifest.mjs:6), so route-handoff.web.ts:207 hands the push to the shell, which opens the native scan screen and returns to the same page document on Back. Retry and Remove are inert here: forceReconnect is null (client-context.web.tsx:57, read through host-client-hooks.ts:88) and removal refuses (page-host-removal-refusal.ts). The banner is reachable on the page because the shell forwards the native client's state (mobile-web-shell/bridge-host.ts:301) and auth-failed is in the wire enum (bridge/bridge-envelope.ts:44). So this one renders Re-pair as native does and one line naming the app for reconnect and removal." + "reason": "The auth-failed banner offers Retry, Re-pair and Remove, and on the page only Re-pair works. /pair-scan sits outside the page's route root of app/h (config/scripts/mobile-web-app-route-manifest.mjs:6), so route-handoff.web.ts:207 hands the push to the shell, which opens the native scan screen and returns to the same page document on Back. Retry and Remove are inert here: forceReconnect is null (client-context.web.tsx:45, read through host-client-hooks.ts:88) and removal refuses (page-host-removal-refusal.ts). The banner is reachable on the page because the shell forwards the native client's state (mobile-web-shell/bridge-host.ts:301) and auth-failed is in the wire enum (bridge/bridge-envelope.ts:44). So this one renders Re-pair as native does and one line naming the app for reconnect and removal." }, { "file": "src/components/use-html-preview-link-grant.web.ts",