From 9dcda9941c2592f8d4591b2dfaec53fae6af6cc2 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 10 Nov 2026 02:32:17 -0800 Subject: [PATCH] chore(cloud): mobile switch exhaustiveness; extract the read-panel hook --- mobile/src/cloud/CloudReadSnapshotSection.tsx | 2 ++ mobile/src/cloud/cloud-workspace-copy.ts | 7 +++++- .../cloud-workspaces/CloudWorkspacesHome.tsx | 23 ++++-------------- .../cloud-workspaces/use-cloud-read-panel.ts | 24 +++++++++++++++++++ 4 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/components/cloud-workspaces/use-cloud-read-panel.ts diff --git a/mobile/src/cloud/CloudReadSnapshotSection.tsx b/mobile/src/cloud/CloudReadSnapshotSection.tsx index a3dac41565c..afad96d971c 100644 --- a/mobile/src/cloud/CloudReadSnapshotSection.tsx +++ b/mobile/src/cloud/CloudReadSnapshotSection.tsx @@ -226,6 +226,8 @@ function prChecksLabel(pr: ReadSnapshotPr, t: Translate): string | null { case 'pending': case 'running': return t('cloud.snapshot.checks.pending', 'Checks running') + // No status, or one this build doesn't name: the VM's own summary line, if any. + case undefined: default: return pr.checksSummary } diff --git a/mobile/src/cloud/cloud-workspace-copy.ts b/mobile/src/cloud/cloud-workspace-copy.ts index 76388468114..7e637d15d41 100644 --- a/mobile/src/cloud/cloud-workspace-copy.ts +++ b/mobile/src/cloud/cloud-workspace-copy.ts @@ -37,7 +37,9 @@ export function attachErrorMessage( 'cloud.error.pairing', 'The workspace is running, but this phone couldn’t connect to it. Your files are safe.' ) - default: + case 'not_found': + return t('cloud.workspace.missing', 'This workspace is gone or you no longer have access.') + case 'failed': return t( 'cloud.error.failed', 'The workspace couldn’t start. Try again, or open it on your desktop.' @@ -128,6 +130,9 @@ export function cloudPromptErrorMessage(code: string | undefined, t: Translate): return t('cloud.prompt.errorNotConnected', 'This phone couldn’t reach the workspace') case 'cloud_agent_unknown': return t('cloud.prompt.errorAgent', 'That agent isn’t installed in this workspace') + // An older queue entry may carry no code: it failed to launch all the same. + case undefined: + case 'launch_failed': default: return t('cloud.prompt.errorLaunch', 'The agent didn’t start') } diff --git a/src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx b/src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx index dfa675417ed..d7d79561217 100644 --- a/src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx +++ b/src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx @@ -1,6 +1,6 @@ // Home: the org's cloud workspaces (registry on the control plane), filterable by who owns them, // which repo they are in, Mine/Team and archived, grouped by section, with live presence and cost. -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { useAppStore } from '@/store' import { useIntlLocale, useTranslate } from '@/i18n/use-translate' @@ -18,7 +18,7 @@ import { ANY, CloudWorkspacesToolbar, type CloudWorkspacesFilters } from './Clou import { EmptyState, ErrorState, NewSectionDialog, SkeletonRows } from './CloudWorkspacesStates' import { CloudWorkspaceConfirmDialog, type PendingConfirm } from './CloudWorkspaceConfirmDialog' import { CloudReadSnapshotSheet } from './CloudReadSnapshotSheet' -import type { CloudReadWake } from '../orca-cloud/read-snapshot/CloudReadSnapshotView' +import { useCloudReadPanel } from './use-cloud-read-panel' import { formatSandboxHours, sandboxHours, type MemberDirectory } from './cloud-workspace-format' import { refreshCloudMessageSenders } from '@/lib/cloud-message-sender' import { describeError } from './cloud-workspace-errors' @@ -87,22 +87,7 @@ export function CloudWorkspacesHome({ const [now, setNow] = useState(() => Date.now()) const [sectionFor, setSectionFor] = useState(null) const [confirm, setConfirm] = useState(null) - // The Asleep workspace whose read-only copy is open (never wakes it). - const [readingId, setReadingIdState] = useState(null) - // A wake started from the read panel: shown there (elapsed, slow, failed) until it opens. - const [readWake, setReadWake] = useState(null) - const readingRef = useRef(null) - const setReadingId = useCallback( - (next: string | null | ((id: string | null) => string | null)) => { - const value = typeof next === 'function' ? next(readingRef.current) : next - if (value !== readingRef.current) { - setReadWake(null) - } - readingRef.current = value - setReadingIdState(value) - }, - [] - ) + const { readingId, readingRef, setReadingId, readWake, setReadWake } = useCloudReadPanel() const signIn = useAppStore((s) => s.connectCurrentOrcaProfile) const scope = orgId ? filters.scope : 'mine' const { state, stale, reading, refresh, replace } = useCloudWorkspaces( @@ -210,7 +195,7 @@ export function CloudWorkspacesHome({ setOpening((current) => (current === w.id ? null : current)) } }, - [setReadingId] + [readingRef, setReadWake, setReadingId] ) const patch = useCallback( diff --git a/src/renderer/src/components/cloud-workspaces/use-cloud-read-panel.ts b/src/renderer/src/components/cloud-workspaces/use-cloud-read-panel.ts new file mode 100644 index 00000000000..ed5689fb32e --- /dev/null +++ b/src/renderer/src/components/cloud-workspaces/use-cloud-read-panel.ts @@ -0,0 +1,24 @@ +import { useCallback, useRef, useState } from 'react' +import type { CloudReadWake } from '../orca-cloud/read-snapshot/CloudReadSnapshotView' + +/** + * The Asleep workspace whose read-only copy is open (opening it never wakes it), plus a wake + * started from that panel, shown there (elapsed, slow, failed) until the workspace opens. + */ +export function useCloudReadPanel() { + const [readingId, setReadingIdState] = useState(null) + const [readWake, setReadWake] = useState(null) + const readingRef = useRef(null) + const setReadingId = useCallback( + (next: string | null | ((id: string | null) => string | null)) => { + const value = typeof next === 'function' ? next(readingRef.current) : next + if (value !== readingRef.current) { + setReadWake(null) + } + readingRef.current = value + setReadingIdState(value) + }, + [] + ) + return { readingId, readingRef, setReadingId, readWake, setReadWake } +}