diff --git a/src/cli/handlers/account-list-format.ts b/src/cli/handlers/account-list-format.ts index 227c96fc3aa..59df0c6b3a4 100644 --- a/src/cli/handlers/account-list-format.ts +++ b/src/cli/handlers/account-list-format.ts @@ -37,7 +37,7 @@ export function formatAccountsBlock(label: string, block: AccountsBlock): string const lines = block.accounts.map( (account) => ` ${account.email}${activeAccountIds.has(account.id) ? ' (active)' : ''}${ - account.needsSignIn ? ' (sign in again in Orca Settings > Accounts)' : '' + account.needsSignIn ? ' (sign in again in Orca Settings > AI Provider Accounts)' : '' }` ) return `Managed ${label} accounts (${block.accounts.length}):\n${lines.join('\n')}` diff --git a/src/cli/handlers/account.test.ts b/src/cli/handlers/account.test.ts index 0308cc6cd0d..e19fd4a8ad2 100644 --- a/src/cli/handlers/account.test.ts +++ b/src/cli/handlers/account.test.ts @@ -694,7 +694,7 @@ describe('account CLI handlers', () => { expect(logSpy).toHaveBeenCalledWith( expect.stringContaining( - ' old@example.com (active) (sign in again in Orca Settings > Accounts)\n ok@example.com\n' + ' old@example.com (active) (sign in again in Orca Settings > AI Provider Accounts)\n ok@example.com\n' ) ) }) diff --git a/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx b/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx index 87e632d89ae..07fed72e405 100644 --- a/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx +++ b/src/renderer/src/components/status-bar/ClaudeSwitcherMenu.tsx @@ -37,7 +37,8 @@ import { AccountRuntimeToggle } from './StatusBarAccountControls' import { InlineUsageBars, InlineUsageSignInAction, - InlineUsageSkeleton + InlineUsageSkeleton, + isUnavailableInactiveUsage } from './InlineProviderUsage' import { ProviderDetailsMenu } from './ProviderDetailsMenu' import { getClaudeAccountSyncKey } from './provider-account-sync-key' @@ -352,7 +353,9 @@ export function ClaudeSwitcherMenu({ ) : null} {inactiveUsage?.isFetching && !inactiveUsage.rateLimits ? ( - ) : inactiveUsage?.rateLimits ? ( + ) : inactiveUsage?.rateLimits && + // Why: the sign-in note above already says this; skip the repeat. + !(signInId && isUnavailableInactiveUsage(inactiveUsage.rateLimits)) ? ( snapshot()) const selectClaudeProviderAccount = vi.fn(async () => snapshot()) let storeSettings: GlobalSettings let activeRuntimeEnvironmentId: string | null = null +let inactiveClaudeAccounts: unknown[] = [] function account(id: string): ClaudeManagedAccount { return { @@ -102,7 +103,7 @@ vi.mock('../../store', () => { recordFeatureInteraction: vi.fn(), refreshClaudeRateLimitsForTarget: vi.fn(), fetchInactiveClaudeAccountUsage: vi.fn(), - rateLimits: { inactiveClaudeAccounts: [], claudeTarget: { runtime: 'host', wslDistro: null } } + rateLimits: { inactiveClaudeAccounts, claudeTarget: { runtime: 'host', wslDistro: null } } }) const useAppStore = (selector: (value: Record) => unknown): unknown => selector(state()) @@ -137,6 +138,7 @@ describe('status bar Claude account that needs a sign-in', () => { beforeEach(() => { vi.clearAllMocks() activeRuntimeEnvironmentId = null + inactiveClaudeAccounts = [] storeSettings = { ...getDefaultSettings('/home/me'), claudeManagedAccounts: [account('old'), account('new')], @@ -163,6 +165,21 @@ describe('status bar Claude account that needs a sign-in', () => { expect(toastError).not.toHaveBeenCalled() }) + it('does not repeat its sign-in note with a usage sign-in line', async () => { + const unavailable = (accountId: string): unknown => ({ + accountId, + isFetching: false, + rateLimits: { ...claudeProvider, status: 'error', error: 'Not signed in' } + }) + inactiveClaudeAccounts = [unavailable('old'), unavailable('new')] + await openAccounts() + expect(screen.getByText('Sign in again to use this account')).toBeTruthy() + // Only the row without a sign-in note keeps the usage line. + expect(screen.getAllByText('Sign in to see usage')).toHaveLength(1) + const newRow = screen.getAllByText('new@example.com').at(-1)!.closest('[role="menuitem"]') + expect(newRow?.textContent).toContain('Sign in to see usage') + }) + it('says why a failed sign-in failed', async () => { reauthenticate.mockRejectedValueOnce(new Error('Claude login exited with code 1.')) await openAccounts() diff --git a/src/renderer/src/components/status-bar/inline-usage-bars.test.tsx b/src/renderer/src/components/status-bar/inline-usage-bars.test.tsx index a6b78d6f849..98b1009da1c 100644 --- a/src/renderer/src/components/status-bar/inline-usage-bars.test.tsx +++ b/src/renderer/src/components/status-bar/inline-usage-bars.test.tsx @@ -71,6 +71,22 @@ describe('InlineUsageBars', () => { expect(markup).toContain('42% used Fable') }) + it('asks for a sign-in when another provider has no usage to show', async () => { + const { InlineUsageBars } = await import('./StatusBar') + const limits: ProviderRateLimits = { + provider: 'codex', + session: null, + weekly: null, + updatedAt: Date.now(), + error: 'Not signed in', + status: 'error' + } + + const markup = renderToStaticMarkup() + + expect(markup).toContain('Sign in to see usage') + }) + it('derives the collapsed session label from resetsAt (#5399)', async () => { const { InlineUsageBars } = await import('./StatusBar') diff --git a/src/renderer/src/components/status-bar/tooltip.test.ts b/src/renderer/src/components/status-bar/tooltip.test.ts index c2a2ef35dfb..3cfbded6392 100644 --- a/src/renderer/src/components/status-bar/tooltip.test.ts +++ b/src/renderer/src/components/status-bar/tooltip.test.ts @@ -295,7 +295,7 @@ describe('provider usage error copy', () => { expect(getProviderUsageStatusLabel(p)).toBe('Sign in again') expect(getProviderUsageErrorMessage(p)).toBe( - 'The selected Claude account needs you to sign in again. Open Settings > Accounts, or choose System default.' + 'The selected Claude account needs you to sign in again. Open Settings > AI Provider Accounts, or choose System default.' ) }) diff --git a/src/renderer/src/components/status-bar/usage-error-copy.ts b/src/renderer/src/components/status-bar/usage-error-copy.ts index d9f6cf4557d..be278fc9e9f 100644 --- a/src/renderer/src/components/status-bar/usage-error-copy.ts +++ b/src/renderer/src/components/status-bar/usage-error-copy.ts @@ -227,7 +227,7 @@ export function getProviderUsageErrorMessage(p: ProviderRateLimits): string { case 'missing-credentials': return translate( 'accounts.claude.usageSignInMessage', - 'The selected Claude account needs you to sign in again. Open Settings > Accounts, or choose System default.' + 'The selected Claude account needs you to sign in again. Open Settings > AI Provider Accounts, or choose System default.' ) case 'server': case 'parse': diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index ce8dde6e839..a2c1dbb24a5 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -19439,7 +19439,7 @@ "signInRequired": "Sign in again to use this account", "signInAgain": "Sign in again", "usageSignInLabel": "Sign in again", - "usageSignInMessage": "The selected Claude account needs you to sign in again. Open Settings > Accounts, or choose System default.", + "usageSignInMessage": "The selected Claude account needs you to sign in again. Open Settings > AI Provider Accounts, or choose System default.", "signIn": "Sign in", "signInNotice": { "title": "Finish setting up your Claude accounts",