diff --git a/src/renderer/src/components/orca-cloud-setup/CloudSetupPage.tsx b/src/renderer/src/components/orca-cloud-setup/CloudSetupPage.tsx index 2c92bc3ba0b..136b2899d67 100644 --- a/src/renderer/src/components/orca-cloud-setup/CloudSetupPage.tsx +++ b/src/renderer/src/components/orca-cloud-setup/CloudSetupPage.tsx @@ -12,6 +12,7 @@ import type { CloudSetupStep } from '@/components/new-workspace/run-on/cloud-sta import { CloudSetupErrorNotice } from '@/components/new-workspace/cloud-setup/CloudSetupErrorNotice' import { sessionDetailCopy } from '@/components/new-workspace/run-on/cloud-setup-session-copy' import type { CloudSetupFlow } from './use-cloud-setup-flow' +import { openOrcaCloudAgentAccounts } from '@/components/new-workspace/OrcaCloudAgentSetupRow' export type CloudSetupPageProps = { flow: CloudSetupFlow @@ -286,11 +287,9 @@ function StepActions({ flow }: { flow: CloudSetupFlow }): React.JSX.Element | nu case 'agent': return (
- {flow.current?.action ? ( - - ) : null} + diff --git a/src/renderer/src/components/orca-cloud-setup/cloud-setup-page-model.test.ts b/src/renderer/src/components/orca-cloud-setup/cloud-setup-page-model.test.ts index f6864620616..29903001e3d 100644 --- a/src/renderer/src/components/orca-cloud-setup/cloud-setup-page-model.test.ts +++ b/src/renderer/src/components/orca-cloud-setup/cloud-setup-page-model.test.ts @@ -1,7 +1,6 @@ import { describe, expect, it } from 'vitest' import type { CloudSetupStep } from '@/components/new-workspace/run-on/cloud-start-model' import { actionForStep, cloudSetupPageModel, sessionNeedsFrom } from './cloud-setup-page-model' -import { effectiveAvailability } from './use-cloud-setup-flow' describe('cloudSetupPageModel', () => { it('walks one step at a time: the first unfinished step is current', () => { @@ -44,14 +43,3 @@ describe('cloudSetupPageModel', () => { expect(actionForStep({ id: 'github', state: 'loading' })).toBe('checking') }) }) - -describe('effectiveAvailability', () => { - it('a sign-in that lands upgrades the snapshot from Start', () => { - expect(effectiveAvailability('needs-sign-in', { state: 'signed-out' })).toBe('needs-sign-in') - expect(effectiveAvailability('needs-sign-in', { state: 'loading' })).toBe('needs-sign-in') - expect( - effectiveAvailability('needs-sign-in', { state: 'entitled', remainingSeconds: 60 }) - ).toBe('offered') - expect(effectiveAvailability('offered', { state: 'signed-out' })).toBe('offered') - }) -}) diff --git a/src/renderer/src/components/orca-cloud-setup/use-cloud-setup-flow.ts b/src/renderer/src/components/orca-cloud-setup/use-cloud-setup-flow.ts index 1dcc361fdd5..bac1d3e1a45 100644 --- a/src/renderer/src/components/orca-cloud-setup/use-cloud-setup-flow.ts +++ b/src/renderer/src/components/orca-cloud-setup/use-cloud-setup-flow.ts @@ -33,15 +33,42 @@ import { cloudSetupPageModel, sessionNeedsFrom, type CloudSetupPageModel } from /** Billing rows re-read this often while the page waits on a browser step (focus also re-reads). */ const BILLING_POLL_MS = 4_000 -/** Main's availability is a snapshot from Start; once billing answers for a signed-in account, it wins. */ -export function effectiveAvailability( - requested: OrcaCloudAvailability, - access: OrcaCloudRunTargetAccess +/** + * Main's Orca Cloud availability for the project (needs-sign-in, needs-capability, offered), read + * the way the composer reads it and re-read while the page waits, so a sign-in in the browser (or + * the dev sign-in) moves the page on. Starts from the composer's answer at Start. + */ +function useLiveAvailability( + repoId: string, + initial: OrcaCloudAvailability, + enabled: boolean ): OrcaCloudAvailability { - if (requested === 'needs-sign-in' && access.state !== 'signed-out' && access.state !== 'loading') { - return 'offered' - } - return requested + const [availability, setAvailability] = useState(initial) + useEffect(() => { + if (!enabled) { + return + } + let cancelled = false + const read = (): void => { + void Promise.resolve() + .then(() => window.api.ephemeralVm.listRecipes({ repoId })) + .then((result) => { + if (!cancelled && result.orcaCloud) { + setAvailability(result.orcaCloud) + } + }) + .catch(() => undefined) + } + read() + const timer = window.setInterval(read, BILLING_POLL_MS) + window.addEventListener('focus', read) + return () => { + cancelled = true + window.clearInterval(timer) + window.removeEventListener('focus', read) + } + }, [enabled, repoId]) + return availability } export type CloudSetupFlow = CloudSetupPageModel & { @@ -69,7 +96,8 @@ export function useCloudSetupFlow( ): CloudSetupFlow { const fetched = useOrcaCloudRunTargetAccess(!accessOverride) const access = accessOverride ?? fetched - const availability = effectiveAvailability(request.availability, access) + const [settled, setSettled] = useState(false) + const availability = useLiveAvailability(request.repoId, request.availability, !settled && !accessOverride) const [sessionId, setSessionId] = useState(null) const [inBrowser, setInBrowser] = useState(false) const [busy, setBusy] = useState(false) @@ -89,6 +117,11 @@ export function useCloudSetupFlow( }) const { steps, held: switchTeam } = useSwitchTeamSteps(rowSteps, !session) const model = cloudSetupPageModel(steps, { agentSkipped, teamSwitch: switchTeam !== null }) + useEffect(() => { + if (model.action === 'done') { + setSettled(true) + } + }, [model.action]) const error = useMemo(() => { const parsed = parseCloudSetupError(session?.error) return parsed && session?.teamName ? { ...parsed, teamName: session.teamName } : parsed