diff --git a/packages/cli/src/config/settings.test.ts b/packages/cli/src/config/settings.test.ts index 462a67d0b0..15a0535e0f 100644 --- a/packages/cli/src/config/settings.test.ts +++ b/packages/cli/src/config/settings.test.ts @@ -2200,6 +2200,18 @@ describe('Settings Loading and Merging', () => { allowMCPServers: ['serverA'], }); }); + + it('should correctly migrate customWittyPhrases', () => { + const v2Settings: Partial = { + ui: { + customWittyPhrases: ['test phrase'], + }, + }; + const v1Settings = migrateSettingsToV1(v2Settings as Settings); + expect(v1Settings).toEqual({ + customWittyPhrases: ['test phrase'], + }); + }); }); describe('loadEnvironment', () => { @@ -2258,7 +2270,7 @@ describe('Settings Loading and Merging', () => { }); it('should return false for settings that are already in V2 format', () => { - const v2Settings = { + const v2Settings: Partial = { ui: { theme: 'dark', }, diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index a02e3928df..97feb171b3 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -28,7 +28,7 @@ import { getSettingsSchema, } from './settingsSchema.js'; import { resolveEnvVarsInObject } from '../utils/envVarResolver.js'; -import { customDeepMerge } from '../utils/deepMerge.js'; +import { customDeepMerge, type MergeableObject } from '../utils/deepMerge.js'; import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js'; import { disableExtension } from './extension.js'; @@ -69,6 +69,7 @@ const MIGRATION_MAP: Record = { coreTools: 'tools.core', contextFileName: 'context.fileName', customThemes: 'ui.customThemes', + customWittyPhrases: 'ui.customWittyPhrases', debugKeystrokeLogging: 'general.debugKeystrokeLogging', disableAutoUpdate: 'general.disableAutoUpdate', disableUpdateNag: 'general.disableUpdateNag', @@ -252,7 +253,28 @@ function migrateSettingsToV2( // Carry over any unrecognized keys for (const remainingKey of flatKeys) { - v2Settings[remainingKey] = flatSettings[remainingKey]; + const existingValue = v2Settings[remainingKey]; + const newValue = flatSettings[remainingKey]; + + if ( + typeof existingValue === 'object' && + existingValue !== null && + !Array.isArray(existingValue) && + typeof newValue === 'object' && + newValue !== null && + !Array.isArray(newValue) + ) { + const pathAwareGetStrategy = (path: string[]) => + getMergeStrategyForPath([remainingKey, ...path]); + v2Settings[remainingKey] = customDeepMerge( + pathAwareGetStrategy, + {}, + newValue as MergeableObject, + existingValue as MergeableObject, + ); + } else { + v2Settings[remainingKey] = newValue; + } } return v2Settings; diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index d9dec05d11..1c332ff432 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -833,8 +833,10 @@ Logging in with Google... Please restart Gemini CLI to continue. [handleSlashCommand, settings], ); - const { elapsedTime, currentLoadingPhrase } = - useLoadingIndicator(streamingState); + const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator( + streamingState, + settings.merged.ui?.customWittyPhrases, + ); const handleExit = useCallback( ( diff --git a/packages/cli/src/utils/deepMerge.ts b/packages/cli/src/utils/deepMerge.ts index e83c7a5203..85200ddbae 100644 --- a/packages/cli/src/utils/deepMerge.ts +++ b/packages/cli/src/utils/deepMerge.ts @@ -6,7 +6,7 @@ import { MergeStrategy } from '../config/settingsSchema.js'; -type Mergeable = +export type Mergeable = | string | number | boolean @@ -15,7 +15,7 @@ type Mergeable = | object | Mergeable[]; -type MergeableObject = Record; +export type MergeableObject = Record; function isPlainObject(item: unknown): item is MergeableObject { return !!item && typeof item === 'object' && !Array.isArray(item);