diff --git a/apps/desktop/src/app/settings/profile-scope.tsx b/apps/desktop/src/app/settings/profile-scope.tsx index 451d3943eb..4e5c8667b1 100644 --- a/apps/desktop/src/app/settings/profile-scope.tsx +++ b/apps/desktop/src/app/settings/profile-scope.tsx @@ -3,8 +3,13 @@ import { useEffect } from 'react' import { useI18n } from '@/i18n' import { cn } from '@/lib/utils' -import { $activeGatewayProfile, $profiles, normalizeProfileKey, refreshProfiles } from '@/store/profile' -import { $settingsScopeOverride, setSettingsScope } from '@/store/settings-scope' +import { $profiles, normalizeProfileKey, refreshProfiles } from '@/store/profile' +import { + $settingsScopeEditsNonDefault, + $settingsScopeOverride, + $settingsScopeProfile, + setSettingsScope +} from '@/store/settings-scope' // The same chip affordance the Gateway page uses for its per-profile // connection overrides (gateway-settings ScopeChip). That one stays local to @@ -37,7 +42,8 @@ export function SettingsProfileScope({ className }: { className?: string }) { const { t } = useI18n() const scope = t.settings.profileScope const override = useStore($settingsScopeOverride) - const active = useStore($activeGatewayProfile) + const selected = useStore($settingsScopeProfile) + const editingNonDefault = useStore($settingsScopeEditsNonDefault) const profiles = useStore($profiles) // Refresh lazily so a profile created elsewhere shows up; the cached list @@ -50,10 +56,6 @@ export function SettingsProfileScope({ className }: { className?: string }) { return null } - const selected = normalizeProfileKey(override ?? active) - const defaultProfile = profiles.find(profile => profile.is_default) - const editingNonDefault = defaultProfile ? selected !== normalizeProfileKey(defaultProfile.name) : false - return (
({
@@ -16,14 +18,20 @@ vi.mock('@/hermes', () => ({
vi.mock('@/lib/query-client', () => ({ invalidateProfileScopedQueries: vi.fn() }))
vi.mock('@/store/starmap', () => ({ resetStarmapGraph: vi.fn() }))
-const { $activeGatewayProfile } = await import('./profile')
+const { $activeGatewayProfile, $profiles } = await import('./profile')
-const { $settingsRequestProfile, $settingsScopeOverride, $settingsScopeProfile, setSettingsScope } =
- await import('./settings-scope')
+const {
+ $settingsRequestProfile,
+ $settingsScopeEditsNonDefault,
+ $settingsScopeOverride,
+ $settingsScopeProfile,
+ setSettingsScope
+} = await import('./settings-scope')
beforeEach(() => {
$activeGatewayProfile.set('default')
$settingsScopeOverride.set(null)
+ $profiles.set([])
})
describe('settings scope store', () => {
@@ -72,6 +80,34 @@ describe('settings scope store', () => {
expect($settingsRequestProfile.get()).toBeUndefined()
})
+ it('flags a non-default edit target whether it comes from the active profile or an override', () => {
+ const roster = [
+ { is_default: true, name: 'default' },
+ { is_default: false, name: 'scout' }
+ ] as unknown as ProfileInfo[]
+ $profiles.set(roster)
+
+ // Following the active DEFAULT profile → editing the default.
+ expect($settingsScopeEditsNonDefault.get()).toBe(false)
+
+ // A Bot Mode chat made the bot the active profile; no override is set,
+ // yet the settings pages now edit profiles/scout/config.yaml.
+ $activeGatewayProfile.set('scout')
+ expect($settingsScopeEditsNonDefault.get()).toBe(true)
+
+ // Explicit override back onto the default → editing the default again.
+ setSettingsScope('default')
+ expect($settingsScopeEditsNonDefault.get()).toBe(false)
+ })
+
+ it('treats an unloaded roster as "default = the root profile", so an unknown default fails loud', () => {
+ $activeGatewayProfile.set('scout')
+ expect($settingsScopeEditsNonDefault.get()).toBe(true)
+
+ $activeGatewayProfile.set('default')
+ expect($settingsScopeEditsNonDefault.get()).toBe(false)
+ })
+
it('drops the override on an app-wide profile switch', () => {
setSettingsScope('research')
expect($settingsScopeOverride.get()).toBe('research')
diff --git a/apps/desktop/src/store/settings-scope.ts b/apps/desktop/src/store/settings-scope.ts
index 115707b8d5..fe9f8f99a7 100644
--- a/apps/desktop/src/store/settings-scope.ts
+++ b/apps/desktop/src/store/settings-scope.ts
@@ -1,6 +1,6 @@
import { atom, computed } from 'nanostores'
-import { $activeGatewayProfile, normalizeProfileKey } from '@/store/profile'
+import { $activeGatewayProfile, $profiles, normalizeProfileKey } from '@/store/profile'
// ── Shared settings "Applies to" scope ──────────────────────────────────────
// One selection shared by every config-backed settings page (Model, Workspace,
@@ -16,6 +16,20 @@ export const $settingsScopeProfile = computed([$settingsScopeOverride, $activeGa
normalizeProfileKey(override ?? active)
)
+// Whether the settings pages are editing a profile OTHER than the default
+// one. The scope follows the app's active profile when no override is set —
+// which, after opening any Bot Mode chat, is the BOT's profile — so an edit
+// can land in profiles/