diff --git a/src/renderer/src/components/settings/AccountsPane.test.tsx b/src/renderer/src/components/settings/AccountsPane.test.tsx index a069bc44f..d9b5cd182 100644 --- a/src/renderer/src/components/settings/AccountsPane.test.tsx +++ b/src/renderer/src/components/settings/AccountsPane.test.tsx @@ -23,7 +23,7 @@ function renderPane( describe('AccountsPane', () => { beforeEach(async () => { await i18n.changeLanguage('en') - useAppStore.setState({ settingsSearchQuery: '' }) + useAppStore.setState({ settingsSearchQuery: '', runtimeEnvironments: [] }) }) it('hides the WSL account location controls on platforms without WSL support', () => { @@ -116,6 +116,14 @@ describe('AccountsPane', () => { expect(markup).toContain( 'Showing accounts managed by the remote server. Add or re-authenticate accounts on that server.' ) + // Both the Claude and Codex sections must say local accounts are intact and + // link the default-runtime control, so the scoped list never reads as loss. + expect(markup.split('Accounts managed on this desktop are unchanged').length - 1).toBe(2) + expect(markup.split('Open Remote Servers').length - 1).toBe(2) + // Before the saved-server list loads there is no name to interpolate, so the + // scope label must stay bare instead of stuttering the prose fallback. + expect(markup).toContain('Account scope: Remote server<') + expect(markup).not.toContain('Remote server: the remote server') // The WSL account-location toggle is a local concern; a remote owner hides it. expect(markup).not.toContain('aria-label="Account location"') const addAccountIndex = markup.indexOf('Add Account') @@ -125,10 +133,34 @@ describe('AccountsPane', () => { ) }) + it('omits the scope control on the web client, which cannot select Local desktop', () => { + const webGlobal = globalThis as { window?: { __ORCA_WEB_CLIENT__?: boolean } } + const hadWindow = 'window' in webGlobal + webGlobal.window = { ...webGlobal.window, __ORCA_WEB_CLIENT__: true } + try { + const markup = renderPane({ + ...getDefaultSettings('/tmp'), + activeRuntimeEnvironmentId: 'env-1' + }) + + // The web client has no desktop-managed accounts to switch back to, so + // this copy would promise a move it cannot make. + expect(markup).not.toContain('Accounts managed on this desktop are unchanged') + expect(markup).not.toContain('Open Remote Servers') + // The server-scope copy itself still applies. + expect(markup).toContain('Showing accounts managed by') + } finally { + if (!hadWindow) { + delete webGlobal.window + } + } + }) + it('keeps local copy and enabled sign-in actions when no remote server is active', () => { const markup = renderPane(getDefaultSettings('/tmp')) expect(markup).toContain('Showing accounts for this device. New accounts are added there.') + expect(markup).not.toContain('Open Remote Servers') const addAccountIndex = markup.indexOf('Add Account') expect(addAccountIndex).toBeGreaterThan(0) expect( diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index e9a1c0efc..6f96cd2e1 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -52,6 +52,8 @@ import { getAccountsPaneSearchEntries } from './accounts-search' import { GrokAccountsSection } from './GrokAccountsSection' +import { getRemoteAccountsPaneScope } from './provider-account-scope' +import { ProviderHostScopeControl } from './ProviderHostScopeControl' import { SearchableSetting } from './SearchableSetting' import { SettingsRow, SettingsSegmentedControl } from './SettingsFormControls' import { matchesSettingsSearch } from './settings-search' @@ -77,6 +79,7 @@ import { } from './provider-account-visibility' import { translate } from '@/i18n/i18n' import { cn } from '@/lib/utils' +import { isWebClientLocation } from '@/lib/web-client-location' import { emptyClaudeAccountsState, emptyCodexAccountsState, @@ -350,9 +353,14 @@ export function AccountsPane({ // (see #7973); every list/select/remove below must scope to it, not host/WSL. const isRemoteAccountScope = hasRemoteProviderAccountOwner(settings) const activeRuntimeEnvironmentId = settings.activeRuntimeEnvironmentId?.trim() || null - const remoteServerLabel = isRemoteAccountScope + // Why: keep the real name separate from the prose fallback below; the scope + // label must not interpolate the fallback. + const remoteServerName = isRemoteAccountScope ? (runtimeEnvironments.find((environment) => environment.id === activeRuntimeEnvironmentId) - ?.name ?? + ?.name ?? null) + : null + const remoteServerLabel = isRemoteAccountScope + ? (remoteServerName ?? translate('auto.components.settings.AccountsPane.remoteServerFallback', 'the remote server')) : null const accountRuntime: LocalAccountRuntime = isRemoteAccountScope @@ -369,6 +377,21 @@ export function AccountsPane({ localAccountRuntime.runtime === 'host' && !navigator.userAgent.includes('Windows') ? `${localAccountRuntime.label.charAt(0).toLocaleLowerCase()}${localAccountRuntime.label.slice(1)}` : localAccountRuntime.label + // Why: users read the remote-scoped list as their desktop accounts being + // deleted (#8186); say they are intact and link the default-runtime control. + // The web client has no desktop-owned accounts and cannot select Local + // desktop, so promising a switch back would be a dead end there. + const remoteAccountScopeNotice = + isRemoteAccountScope && !isWebClientLocation() ? ( + + ) : null const [codexAccounts, setCodexAccounts] = useState(emptyCodexAccountsState) @@ -886,6 +909,7 @@ export function AccountsPane({ ) : null} + {remoteAccountScopeNotice}
+ {remoteAccountScopeNotice}