From 9d305f3ce4f87e3935af6ab7cc75e769c6c2c191 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 11 Oct 2026 17:50:13 -0700 Subject: [PATCH] refactor(settings): one settings host-scope type and label (#27629) Settings had two identical provider-scope types, a host-choice type that copied RuntimeClientTarget, a scope-to-environment-id getter that copied runtimeTargetEnvironmentId, and the remote-server/local label written three times. Keep one of each. getProviderAccountScope now reads the default host through defaultScopeHost, the sanctioned reader; output is unchanged (trimmed id, blank means this computer). Co-authored-by: m4air Co-authored-by: Claude Opus 5.5 --- .../src/components/settings/AccountsPane.tsx | 6 +- .../src/components/settings/AgentsPane.tsx | 5 +- .../settings/ProviderHostScopeControl.tsx | 4 +- .../settings/SettingsHostScopePicker.tsx | 12 +-- .../settings/provider-account-scope.test.ts | 12 ++- .../settings/provider-account-scope.ts | 96 +++++++++---------- .../settings/settings-host-scope.ts | 8 +- .../task-tracker-integration-cards.tsx | 4 +- .../settings/use-settings-store-model.ts | 5 +- .../settings/use-settings-terminal-model.ts | 4 +- 10 files changed, 73 insertions(+), 83 deletions(-) diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index 9eb82c25524..33c41b27501 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -41,8 +41,8 @@ import { } from './provider-account-visibility' import { GrokAccountsSection } from './GrokAccountsSection' import { AntigravityAccountsSection } from './AntigravityAccountsSection' -import type { RuntimeClientTarget } from '@/runtime/runtime-rpc-client' -import { getSettingsHostScopeEnvironmentId } from './settings-host-scope' +import type { RuntimeClientTarget } from '@/runtime/runtime-client-target' +import { runtimeTargetEnvironmentId } from '@/runtime/runtime-client-target' import { CursorAccountsSection } from './CursorAccountsSection' import { ZcodePlanAccountsSection } from './ZcodePlanAccountsSection' import type { @@ -112,7 +112,7 @@ export function AccountsPane({ // below goes to the host chosen in the page, never to the focused one. const accountOwner = hostScope.target const isRemoteAccountScope = accountOwner.kind === 'environment' - const scopeEnvironmentId = getSettingsHostScopeEnvironmentId(hostScope) + const scopeEnvironmentId = runtimeTargetEnvironmentId(hostScope.target) // Why: a select/remove can outlive a host change; its result // belongs to the owner it was sent to, not the one now shown. const accountOwnerKey = getProviderAccountsOwnerKey(accountOwner) diff --git a/src/renderer/src/components/settings/AgentsPane.tsx b/src/renderer/src/components/settings/AgentsPane.tsx index 2533fdec691..11fc128c81c 100644 --- a/src/renderer/src/components/settings/AgentsPane.tsx +++ b/src/renderer/src/components/settings/AgentsPane.tsx @@ -51,7 +51,8 @@ import { import { AgentAvailabilityControl, type AgentCatalogRowProps } from './AgentCatalogRow' import { AgentDefaultSetting } from './AgentDefaultSetting' import { AgentDetectionCatalog } from './AgentDetectionCatalog' -import { getSettingsHostScopeEnvironmentId, type SettingsHostScope } from './settings-host-scope' +import type { SettingsHostScope } from './settings-host-scope' +import { runtimeTargetEnvironmentId } from '@/runtime/runtime-client-target' export { buildAgentAvailabilitySettingsUpdate, @@ -158,7 +159,7 @@ export function AgentsPane({ wslDistros, wslCapabilitiesLoading }: AgentsPaneProps): React.JSX.Element { - const activeServerEnvironmentId = getSettingsHostScopeEnvironmentId(hostScope) + const activeServerEnvironmentId = runtimeTargetEnvironmentId(hostScope.target) const agentDetectionTarget = useMemo( () => activeServerEnvironmentId diff --git a/src/renderer/src/components/settings/ProviderHostScopeControl.tsx b/src/renderer/src/components/settings/ProviderHostScopeControl.tsx index e477f4818f0..62d0963f9b9 100644 --- a/src/renderer/src/components/settings/ProviderHostScopeControl.tsx +++ b/src/renderer/src/components/settings/ProviderHostScopeControl.tsx @@ -1,12 +1,12 @@ import { ServerCog } from 'lucide-react' import { Button } from '@/components/ui/button' import { useAppStore } from '@/store' -import type { ProviderAccountScope, ProviderRateLimitScope } from './provider-account-scope' +import type { ProviderHostScope } from './provider-account-scope' import { translate } from '@/i18n/i18n' type ProviderHostScopeControlProps = { labelPrefix: string - scope: ProviderAccountScope | ProviderRateLimitScope + scope: ProviderHostScope className?: string } diff --git a/src/renderer/src/components/settings/SettingsHostScopePicker.tsx b/src/renderer/src/components/settings/SettingsHostScopePicker.tsx index d6338facb74..ca075c8b208 100644 --- a/src/renderer/src/components/settings/SettingsHostScopePicker.tsx +++ b/src/renderer/src/components/settings/SettingsHostScopePicker.tsx @@ -9,10 +9,10 @@ import { translate } from '@/i18n/i18n' import { getLocalExecutionHostLabel } from '../../../../shared/execution-host' import type { PublicKnownRuntimeEnvironment } from '../../../../shared/runtime-environments' import { - getSettingsHostScopeEnvironmentId, - type SettingsHostChoice, - type SettingsHostScope -} from './settings-host-scope' + runtimeTargetEnvironmentId, + type RuntimeClientTarget +} from '@/runtime/runtime-client-target' +import type { SettingsHostScope } from './settings-host-scope' const LOCAL_VALUE = 'local' const ENVIRONMENT_VALUE_PREFIX = 'environment:' @@ -20,7 +20,7 @@ const ENVIRONMENT_VALUE_PREFIX = 'environment:' type SettingsHostScopePickerProps = { scope: SettingsHostScope environments: readonly PublicKnownRuntimeEnvironment[] - onChoose: (choice: SettingsHostChoice) => void + onChoose: (choice: RuntimeClientTarget) => void } export function shouldShowSettingsHostScopePicker( @@ -35,7 +35,7 @@ export function SettingsHostScopePicker({ environments, onChoose }: SettingsHostScopePickerProps): React.JSX.Element { - const scopeEnvironmentId = getSettingsHostScopeEnvironmentId(scope) + const scopeEnvironmentId = runtimeTargetEnvironmentId(scope.target) const value = scopeEnvironmentId ? `${ENVIRONMENT_VALUE_PREFIX}${scopeEnvironmentId}` : LOCAL_VALUE diff --git a/src/renderer/src/components/settings/provider-account-scope.test.ts b/src/renderer/src/components/settings/provider-account-scope.test.ts index 5036199c868..d587a9641bc 100644 --- a/src/renderer/src/components/settings/provider-account-scope.test.ts +++ b/src/renderer/src/components/settings/provider-account-scope.test.ts @@ -10,11 +10,13 @@ const LOCAL_HOST_LABEL = getExecutionHostLabel('local') describe('getProviderAccountScope', () => { it('describes provider accounts as client-owned without an active runtime', () => { - expect(getProviderAccountScope({ activeRuntimeEnvironmentId: null })).toEqual({ - label: LOCAL_HOST_LABEL, - description: - 'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' - }) + for (const activeRuntimeEnvironmentId of [null, ' ']) { + expect(getProviderAccountScope({ activeRuntimeEnvironmentId })).toEqual({ + label: LOCAL_HOST_LABEL, + description: + 'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' + }) + } }) it('describes provider accounts as remote-server-owned with an active runtime', () => { diff --git a/src/renderer/src/components/settings/provider-account-scope.ts b/src/renderer/src/components/settings/provider-account-scope.ts index 54bb607268b..7d48f6872f5 100644 --- a/src/renderer/src/components/settings/provider-account-scope.ts +++ b/src/renderer/src/components/settings/provider-account-scope.ts @@ -1,31 +1,38 @@ import { translate } from '@/i18n/i18n' import { getLocalExecutionHostLabel } from '../../../../shared/execution-host' import type { GlobalSettings } from '../../../../shared/global-settings-types' +import { defaultScopeHost } from '@/lib/default-creation-host' import type { RuntimeClientTarget } from '@/runtime/runtime-client-target' -export type ProviderAccountScope = { +/** Which host a provider card or budget belongs to, and how to change it. */ +export type ProviderHostScope = { label: string description: string } -export type ProviderRateLimitScope = { - label: string - description: string +function remoteServerScopeLabel(name: string): string { + return translate( + 'auto.components.settings.providerAccountScope.remoteServer', + 'Remote server: {{value0}}', + { value0: name } + ) +} + +function providerHostScopeLabel(target: RuntimeClientTarget): string { + return target.kind === 'environment' + ? remoteServerScopeLabel(target.environmentId) + : getLocalExecutionHostLabel() } /** Accounts-pane scope while a remote server owns the roster (#8186). */ -export function getRemoteAccountsPaneScope(serverName: string | null): ProviderAccountScope { +export function getRemoteAccountsPaneScope(serverName: string | null): ProviderHostScope { const name = serverName?.trim() return { // Why: the saved-server list is still empty on first paint, and the generic // fallback is already a noun phrase — interpolating it would render // "Remote server: the remote server". label: name - ? translate( - 'auto.components.settings.providerAccountScope.remoteServer', - 'Remote server: {{value0}}', - { value0: name } - ) + ? remoteServerScopeLabel(name) : translate( 'auto.components.settings.AccountsPane.accountScopeRemoteServerUnnamed', 'Remote server' @@ -39,55 +46,40 @@ export function getRemoteAccountsPaneScope(serverName: string | null): ProviderA export function getProviderAccountScope( settings: Pick | null | undefined -): ProviderAccountScope { - const runtimeId = settings?.activeRuntimeEnvironmentId?.trim() - if (runtimeId) { - return { - label: translate( - 'auto.components.settings.providerAccountScope.remoteServer', - 'Remote server: {{value0}}', - { value0: runtimeId } - ), - description: translate( - 'auto.components.settings.providerAccountScope.remoteServerCredentials', - 'Credentials and account checks for this provider are owned by this remote server. To use another host, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' - ) - } - } +): ProviderHostScope { + const target = defaultScopeHost(settings) return { - label: getLocalExecutionHostLabel(), - description: translate( - 'auto.components.settings.providerAccountScope.localCredentials', - 'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' - ) + label: providerHostScopeLabel(target), + description: + target.kind === 'environment' + ? translate( + 'auto.components.settings.providerAccountScope.remoteServerCredentials', + 'Credentials and account checks for this provider are owned by this remote server. To use another host, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' + ) + : translate( + 'auto.components.settings.providerAccountScope.localCredentials', + 'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > Advanced > Default host for new projects.' + ) } } export function getProviderRateLimitScope( target: RuntimeClientTarget, providerLabel: string -): ProviderRateLimitScope { - if (target.kind === 'environment') { - const runtimeId = target.environmentId - return { - label: translate( - 'auto.components.settings.providerAccountScope.remoteServer', - 'Remote server: {{value0}}', - { value0: runtimeId } - ), - description: translate( - 'auto.components.settings.providerAccountScope.remoteServerRateLimit', - '{{value0}} API budget is fetched from the CLI on this remote server. Choose another host in the Host menu at the top of Settings to see its budget.', - { value0: providerLabel } - ) - } - } +): ProviderHostScope { return { - label: getLocalExecutionHostLabel(), - description: translate( - 'auto.components.settings.providerAccountScope.localRateLimit', - '{{value0}} API budget is fetched from the CLI on this desktop client. Choose a server in the Host menu at the top of Settings to see its budget.', - { value0: providerLabel } - ) + label: providerHostScopeLabel(target), + description: + target.kind === 'environment' + ? translate( + 'auto.components.settings.providerAccountScope.remoteServerRateLimit', + '{{value0}} API budget is fetched from the CLI on this remote server. Choose another host in the Host menu at the top of Settings to see its budget.', + { value0: providerLabel } + ) + : translate( + 'auto.components.settings.providerAccountScope.localRateLimit', + '{{value0}} API budget is fetched from the CLI on this desktop client. Choose a server in the Host menu at the top of Settings to see its budget.', + { value0: providerLabel } + ) } } diff --git a/src/renderer/src/components/settings/settings-host-scope.ts b/src/renderer/src/components/settings/settings-host-scope.ts index 81e6f5665c7..abe743f9a6a 100644 --- a/src/renderer/src/components/settings/settings-host-scope.ts +++ b/src/renderer/src/components/settings/settings-host-scope.ts @@ -7,8 +7,6 @@ export type SettingsHostScope = { available: boolean } -export type SettingsHostChoice = { kind: 'local' } | { kind: 'environment'; environmentId: string } - export const LOCAL_SETTINGS_HOST_SCOPE: SettingsHostScope = { target: { kind: 'local' }, available: true @@ -16,7 +14,7 @@ export const LOCAL_SETTINGS_HOST_SCOPE: SettingsHostScope = { export function resolveSettingsHostScope(args: { /** Null until the user picks a host in the page. */ - choice: SettingsHostChoice | null + choice: RuntimeClientTarget | null /** The "default host for new projects" setting; only the initial default, never a later override. */ defaultEnvironmentId: string | null | undefined savedEnvironmentIds: readonly string[] @@ -37,7 +35,3 @@ export function resolveSettingsHostScope(args: { available: !args.catalogHydrated || args.savedEnvironmentIds.includes(environmentId) } } - -export function getSettingsHostScopeEnvironmentId(scope: SettingsHostScope): string | null { - return scope.target.kind === 'environment' ? scope.target.environmentId : null -} diff --git a/src/renderer/src/components/settings/task-tracker-integration-cards.tsx b/src/renderer/src/components/settings/task-tracker-integration-cards.tsx index e264f1bd202..9644c4a4c0b 100644 --- a/src/renderer/src/components/settings/task-tracker-integration-cards.tsx +++ b/src/renderer/src/components/settings/task-tracker-integration-cards.tsx @@ -10,7 +10,7 @@ import { useAppStore } from '@/store' import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell' import { useIntegrationSubordinateRowClass } from './integration-card-presentation' import { LinearAgentSkillInstallCta } from './linear-agent-skill-install-cta' -import { getProviderAccountScope } from './provider-account-scope' +import { getProviderAccountScope, type ProviderHostScope } from './provider-account-scope' import { ProviderHostScopeControl } from './ProviderHostScopeControl' import { LINEAR_INTEGRATION_SECTION_ID } from './task-provider-integration-section-ids' import { translate } from '@/i18n/i18n' @@ -236,7 +236,7 @@ export function LinearIntegrationCard(): React.JSX.Element { ) } -function ProviderAccountScopeRow({ scope }: { scope: ReturnType }) { +function ProviderAccountScopeRow({ scope }: { scope: ProviderHostScope }) { const subordinateRowClass = useIntegrationSubordinateRowClass('text-xs') return ( diff --git a/src/renderer/src/components/settings/use-settings-store-model.ts b/src/renderer/src/components/settings/use-settings-store-model.ts index 71312bec172..c0141caa22e 100644 --- a/src/renderer/src/components/settings/use-settings-store-model.ts +++ b/src/renderer/src/components/settings/use-settings-store-model.ts @@ -31,7 +31,8 @@ import { buildSettingsProjectList, removeSettingsProjectFromAllHosts } from './settings-project-list' -import { resolveSettingsHostScope, type SettingsHostChoice } from './settings-host-scope' +import { resolveSettingsHostScope } from './settings-host-scope' +import type { RuntimeClientTarget } from '@/runtime/runtime-client-target' import { defaultScopeHost } from '@/lib/default-creation-host' export function useSettingsStoreModel() { @@ -148,7 +149,7 @@ export function useSettingsStoreModel() { const confirm = useConfirmationDialog() // Why: session-only (deliberately not persisted) unlock — Option-click the Experimental page title reveals the hidden group. const [hiddenExperimentalUnlocked, setHiddenExperimentalUnlocked] = useState(false) - const [settingsHostChoice, setSettingsHostChoice] = useState(null) + const [settingsHostChoice, setSettingsHostChoice] = useState(null) const defaultScope = defaultScopeHost(settings) const defaultRuntimeEnvironmentId = defaultScope.kind === 'environment' ? defaultScope.environmentId : null diff --git a/src/renderer/src/components/settings/use-settings-terminal-model.ts b/src/renderer/src/components/settings/use-settings-terminal-model.ts index 6847bac2091..dc3650c7f01 100644 --- a/src/renderer/src/components/settings/use-settings-terminal-model.ts +++ b/src/renderer/src/components/settings/use-settings-terminal-model.ts @@ -10,13 +10,13 @@ import { getRepoHostIdentity } from '../../store/slices/repo-host-identity' import { getSettingsEntryHostSelection, getSettingsProjectHostRepo } from './settings-project-list' import type { SettingsStoreModel } from './use-settings-store-model' import type { SettingsNavigationModel } from './use-settings-navigation-model' -import { getSettingsHostScopeEnvironmentId } from './settings-host-scope' +import { runtimeTargetEnvironmentId } from '@/runtime/runtime-client-target' export function useSettingsTerminalModel( model: SettingsStoreModel, navigation: SettingsNavigationModel ) { - const scopeEnvironmentId = getSettingsHostScopeEnvironmentId(model.settingsHostScope) + const scopeEnvironmentId = runtimeTargetEnvironmentId(model.settingsHostScope.target) const windowsTerminalCapabilityOwnerKey = useWindowsTerminalCapabilityOwnerKey(scopeEnvironmentId) const runtimeTarget = model.settingsHostScope.target const capabilityLoadTarget = useMemo(