diff --git a/src/renderer/src/components/settings/ArtifactsSettingsPane.test.tsx b/src/renderer/src/components/settings/ArtifactsSettingsPane.test.tsx
index ff4a3c1aafa..0e74eb57fad 100644
--- a/src/renderer/src/components/settings/ArtifactsSettingsPane.test.tsx
+++ b/src/renderer/src/components/settings/ArtifactsSettingsPane.test.tsx
@@ -15,7 +15,6 @@ const mocks = vi.hoisted(() => ({
configured: true,
state: 'connected'
} as Record
| null,
- orcaProfileConnecting: false,
isWebClient: false
}
}))
@@ -46,7 +45,6 @@ describe('ArtifactsSettingsPane', () => {
mocks.fetchAuthStatus.mockReset()
mocks.openArtifactsPage.mockReset()
mocks.state.orcaProfileAuthStatus = { configured: true, state: 'connected' }
- mocks.state.orcaProfileConnecting = false
mocks.state.isWebClient = false
})
@@ -92,19 +90,11 @@ describe('ArtifactsSettingsPane', () => {
expect(mocks.connect).toHaveBeenCalledOnce()
})
- it('shows reconnect and connecting states', () => {
+ it('keeps sign in clickable while reconnect is required', () => {
mocks.state.orcaProfileAuthStatus = { configured: true, state: 'reconnect-required' }
- const { rerender } = render(
-
- )
+ render()
expect(screen.getByRole('button', { name: 'Sign in again' })).toBeEnabled()
-
- mocks.state.orcaProfileConnecting = true
- rerender(
-
- )
- expect(screen.getByRole('button', { name: 'Signing in…' })).toBeDisabled()
})
it('loads missing account status and disables sign in until configured', () => {
diff --git a/src/renderer/src/components/settings/ArtifactsSettingsPane.tsx b/src/renderer/src/components/settings/ArtifactsSettingsPane.tsx
index 7e8b87029d4..a90906a0759 100644
--- a/src/renderer/src/components/settings/ArtifactsSettingsPane.tsx
+++ b/src/renderer/src/components/settings/ArtifactsSettingsPane.tsx
@@ -18,7 +18,6 @@ export function ArtifactsSettingsPane({
}): React.JSX.Element {
const openArtifactsPage = useAppStore((state) => state.openArtifactsPage)
const authStatus = useAppStore((state) => state.orcaProfileAuthStatus)
- const connecting = useAppStore((state) => state.orcaProfileConnecting)
const connect = useAppStore((state) => state.connectCurrentOrcaProfile)
const signedIn = authStatus?.state === 'connected'
// Why: the capability lives in the desktop host's store and is deliberately absent from the
@@ -128,14 +127,12 @@ export function ArtifactsSettingsPane({
) : null}
diff --git a/src/renderer/src/components/settings/DevToolsPane.tsx b/src/renderer/src/components/settings/DevToolsPane.tsx
index 7042e67758a..c2631c5423f 100644
--- a/src/renderer/src/components/settings/DevToolsPane.tsx
+++ b/src/renderer/src/components/settings/DevToolsPane.tsx
@@ -139,7 +139,6 @@ function showDeleteFailureToast(): void {
// progress; this surfaces it (and its status) in dev when the env vars are set.
function OrcaCloudDevSubsection(): React.JSX.Element {
const authStatus = useAppStore((s) => s.orcaProfileAuthStatus)
- const connecting = useAppStore((s) => s.orcaProfileConnecting)
const connect = useAppStore((s) => s.connectCurrentOrcaProfile)
const signOut = useAppStore((s) => s.signOutCurrentOrcaProfile)
const refresh = useAppStore((s) => s.fetchOrcaProfileAuthStatus)
@@ -170,23 +169,11 @@ function OrcaCloudDevSubsection(): React.JSX.Element {
{connected ? (
-
diff --git a/src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx b/src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx
index 32770122074..06052b4caf3 100644
--- a/src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx
+++ b/src/renderer/src/components/settings/ShareSkillsSettingsPane.test.tsx
@@ -16,7 +16,6 @@ const mocks = vi.hoisted(() => ({
string,
unknown
> | null,
- orcaProfileConnecting: false,
isWebClient: false,
settings: { showSkillsButton: false, agentSkillSharingEnabled: false }
}
@@ -50,7 +49,6 @@ describe('ShareSkillsSettingsPane', () => {
mocks.openSkillsPage.mockReset()
mocks.updateSettings.mockReset()
mocks.state.orcaProfileAuthStatus = { configured: true, state: 'connected' }
- mocks.state.orcaProfileConnecting = false
mocks.state.isWebClient = false
Object.defineProperty(window, 'api', {
configurable: true,
diff --git a/src/renderer/src/components/settings/ShareSkillsSettingsPane.tsx b/src/renderer/src/components/settings/ShareSkillsSettingsPane.tsx
index ddbf35734c9..54ce5edd14a 100644
--- a/src/renderer/src/components/settings/ShareSkillsSettingsPane.tsx
+++ b/src/renderer/src/components/settings/ShareSkillsSettingsPane.tsx
@@ -14,7 +14,6 @@ export function ShareSkillsSettingsPane(): React.JSX.Element {
const settings = useAppStore((state) => state.settings)
const updateSettings = useAppStore((state) => state.updateSettings)
const authStatus = useAppStore((state) => state.orcaProfileAuthStatus)
- const connecting = useAppStore((state) => state.orcaProfileConnecting)
const connect = useAppStore((state) => state.connectCurrentOrcaProfile)
const signedIn = authStatus?.state === 'connected'
const isWebClient = isWebClientLocation()
@@ -135,14 +134,12 @@ export function ShareSkillsSettingsPane(): React.JSX.Element {
void connect()}
>
- {connecting
- ? translate('auto.components.settings.shareSkills.signingIn', 'Signing in…')
- : authStatus?.state === 'reconnect-required'
- ? translate('auto.components.settings.shareSkills.signInAgain', 'Sign in again')
- : translate('auto.components.settings.shareSkills.signIn', 'Sign in to Orca')}
+ {authStatus?.state === 'reconnect-required'
+ ? translate('auto.components.settings.shareSkills.signInAgain', 'Sign in again')
+ : translate('auto.components.settings.shareSkills.signIn', 'Sign in to Orca')}
) : null}
diff --git a/src/renderer/src/store/slices/orca-profiles-auth-actions.test.ts b/src/renderer/src/store/slices/orca-profiles-auth-actions.test.ts
index 7f4f16abf5c..698cd9de05b 100644
--- a/src/renderer/src/store/slices/orca-profiles-auth-actions.test.ts
+++ b/src/renderer/src/store/slices/orca-profiles-auth-actions.test.ts
@@ -1,5 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { createTestStore } from './store-test-helpers'
import type {
ConnectCurrentOrcaProfileResult,
CreateCloudLinkedOrcaProfileResult,
@@ -9,6 +8,21 @@ import type {
SelectOrcaProfileOrgResult,
SignOutCurrentOrcaProfileResult
} from '../../../../shared/orca-profiles'
+import { createTestStore } from './store-test-helpers'
+
+const { toastErrorMock, toastSuccessMock } = vi.hoisted(() => ({
+ toastErrorMock: vi.fn(),
+ toastSuccessMock: vi.fn()
+}))
+
+vi.mock('sonner', () => ({
+ toast: {
+ error: toastErrorMock,
+ info: vi.fn(),
+ success: toastSuccessMock,
+ warning: vi.fn()
+ }
+}))
const listState: OrcaProfileListState = {
activeProfileId: 'local-default',
@@ -73,6 +87,8 @@ const orcaProfilesApi = {
describe('orca profile auth actions slice', () => {
beforeEach(() => {
vi.resetAllMocks()
+ toastErrorMock.mockReset()
+ toastSuccessMock.mockReset()
orcaProfilesApi.authStatus.mockResolvedValue(localAuthStatus)
vi.stubGlobal('window', {
api: {
@@ -98,13 +114,51 @@ describe('orca profile auth actions slice', () => {
orcaProfilesApi.connectCurrent.mockResolvedValue(result)
const store = createTestStore()
- const pending = store.getState().connectCurrentOrcaProfile()
-
- expect(store.getState().orcaProfileConnecting).toBe(true)
- await expect(pending).resolves.toEqual(result)
- expect(store.getState().orcaProfileConnecting).toBe(false)
+ await expect(store.getState().connectCurrentOrcaProfile()).resolves.toEqual(result)
expect(store.getState().orcaProfileAuthStatus).toEqual(connectedAuthStatus)
expect(store.getState().orcaProfiles).toEqual(connectedProfiles)
+ expect(toastSuccessMock).toHaveBeenCalledOnce()
+ })
+
+ it('starts a second sign-in while the first browser wait is still open', async () => {
+ const connectedProfiles = [
+ {
+ ...listState.profiles[0],
+ kind: 'cloud-linked' as const,
+ cloud: connectedAuthStatus.cloud
+ }
+ ]
+ const connected: ConnectCurrentOrcaProfileResult = {
+ status: 'connected',
+ auth: connectedAuthStatus,
+ activeProfileId: 'local-default',
+ profiles: connectedProfiles
+ }
+ const cancelled: ConnectCurrentOrcaProfileResult = {
+ status: 'cancelled',
+ auth: connectedAuthStatus
+ }
+ let finishFirst!: (value: ConnectCurrentOrcaProfileResult) => void
+ orcaProfilesApi.connectCurrent
+ .mockReturnValueOnce(
+ new Promise((resolve) => {
+ finishFirst = resolve
+ })
+ )
+ .mockResolvedValueOnce(connected)
+ const store = createTestStore()
+
+ const first = store.getState().connectCurrentOrcaProfile()
+ const second = store.getState().connectCurrentOrcaProfile()
+
+ expect(orcaProfilesApi.connectCurrent).toHaveBeenCalledTimes(2)
+ await expect(second).resolves.toEqual(connected)
+ expect(toastSuccessMock).toHaveBeenCalledOnce()
+ finishFirst(cancelled)
+ await expect(first).resolves.toEqual(cancelled)
+ expect(toastErrorMock).not.toHaveBeenCalled()
+ expect(toastSuccessMock).toHaveBeenCalledOnce()
+ expect(store.getState().orcaProfileAuthStatus).toEqual(connectedAuthStatus)
})
it('refreshes current profile auth and stores fresh capability flags', async () => {
diff --git a/src/renderer/src/store/slices/orca-profiles-auth-actions.ts b/src/renderer/src/store/slices/orca-profiles-auth-actions.ts
index c8212962821..0a1474b9148 100644
--- a/src/renderer/src/store/slices/orca-profiles-auth-actions.ts
+++ b/src/renderer/src/store/slices/orca-profiles-auth-actions.ts
@@ -70,14 +70,12 @@ export const createOrcaProfilesAuthActions: StateCreator<
},
connectCurrentOrcaProfile: async () => {
- if (get().orcaProfileConnecting) {
- return null
- }
- set({ orcaProfileConnecting: true })
try {
+ // Why: a pending browser callback must not block retry. Another click
+ // starts a second PKCE wait; the earlier tab can still complete.
+ const alreadyConnected = get().orcaProfileAuthStatus?.state === 'connected'
const result = await window.api.orcaProfiles.connectCurrent()
set({
- orcaProfileConnecting: false,
orcaProfileAuthStatus: result.auth,
...(result.status === 'connected'
? {
@@ -96,24 +94,29 @@ export const createOrcaProfilesAuthActions: StateCreator<
description: result.auth.setupMessage
}
)
- } else if (result.status === 'failed') {
+ } else if (
+ result.status === 'failed' &&
+ !alreadyConnected &&
+ result.auth.state !== 'connected'
+ ) {
toast.error(
translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
{ description: result.error }
)
- } else if (result.status === 'connected') {
+ } else if (result.status === 'connected' && !alreadyConnected) {
toast.success(translate('auto.store.slices.orca.profiles.9fcb07a796', 'Profile connected'))
}
return result
} catch (err) {
console.error('Failed to connect Orca profile:', err)
- set({ orcaProfileConnecting: false })
- toast.error(
- translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
- {
- description: err instanceof Error ? err.message : String(err)
- }
- )
+ if (get().orcaProfileAuthStatus?.state !== 'connected') {
+ toast.error(
+ translate('auto.store.slices.orca.profiles.33290e88ed', 'Failed to connect profile'),
+ {
+ description: err instanceof Error ? err.message : String(err)
+ }
+ )
+ }
return null
}
},
diff --git a/src/renderer/src/store/slices/orca-profiles.ts b/src/renderer/src/store/slices/orca-profiles.ts
index d885e96b7e3..e406938d0ac 100644
--- a/src/renderer/src/store/slices/orca-profiles.ts
+++ b/src/renderer/src/store/slices/orca-profiles.ts
@@ -21,7 +21,6 @@ export type OrcaProfilesSlice = OrcaProfilesAuthActions & {
orcaProfilesMultiProfileUi: boolean
orcaProfilesLoading: boolean
orcaProfileSwitching: boolean
- orcaProfileConnecting: boolean
fetchOrcaProfiles: () => Promise
fetchOrcaProfileAuthStatus: () => Promise
createLocalOrcaProfile: (name?: string) => Promise
@@ -42,7 +41,6 @@ export const createOrcaProfilesSlice: StateCreator {
set({ orcaProfilesLoading: true })