From 1c486a40501baf7ab675383efe23c36508a9f24c Mon Sep 17 00:00:00 2001 From: Amir Hardon Date: Thu, 8 May 2025 20:56:46 -0700 Subject: [PATCH] Fix: Prevent CLI from crashing when a configured theme is not found Previously, if a theme specified in the user's settings was not found, the CLI would crash during startup. This was particularly affecting users upgrading from older versions as the "ANSI colors only" theme was renamed to "ANSI". This commit adds error handling to catch the theme not found error during initial loading and when setting themes later. Instead of crashing, the application now logs a warning, displays an error message in the UI, and opens the theme selection dialog to allow the user to choose a valid theme. --- packages/cli/src/gemini.ts | 19 ++++- packages/cli/src/ui/App.tsx | 20 ++++-- packages/cli/src/ui/hooks/useThemeCommand.ts | 73 ++++++++++++++++---- 3 files changed, 92 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/gemini.ts b/packages/cli/src/gemini.ts index 420f3242..32e34c20 100644 --- a/packages/cli/src/gemini.ts +++ b/packages/cli/src/gemini.ts @@ -24,7 +24,24 @@ async function main() { const settings = loadSettings(process.cwd()); const config = await loadCliConfig(settings.merged); if (settings.merged.theme) { - themeManager.setActiveTheme(settings.merged.theme); + try { + themeManager.setActiveTheme(settings.merged.theme); + } catch (error: unknown) { + // If the theme is not found during initial load, log a warning and continue. + // The useThemeCommand hook in App.tsx will handle opening the dialog. + if ( + error instanceof Error && + error.message.includes('Theme') && + error.message.includes('not found') + ) { + console.warn( + `Warning: ${error instanceof Error ? error.message : String(error)}`, + ); + } else { + // Re-throw other errors to be caught by the main catch block + throw error; + } + } } // hop into sandbox if we are outside and sandboxing is enabled diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 9fe651b5..725a2012 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -39,12 +39,13 @@ export const App = ({ config, settings, cliVersion }: AppProps) => { const { history, addItem, clearItems } = useHistory(); const [startupWarnings, setStartupWarnings] = useState([]); const [showHelp, setShowHelp] = useState(false); + const [themeError, setThemeError] = useState(null); const { isThemeDialogOpen, openThemeDialog, handleThemeSelect, handleThemeHighlight, - } = useThemeCommand(settings); + } = useThemeCommand(settings, setThemeError); const [staticKey, setStaticKey] = useState(0); const refreshStatic = useCallback(() => { @@ -191,11 +192,18 @@ export const App = ({ config, settings, cliVersion }: AppProps) => { )} {isThemeDialogOpen ? ( - + + {themeError && ( + + {themeError} + + )} + + ) : ( <> void, ): UseThemeCommandReturn => { // Determine the effective theme const effectiveTheme = loadedSettings.merged.theme; // Initial state: Open dialog if no theme is set in either user or workspace settings - const [isThemeDialogOpen, setIsThemeDialogOpen] = useState( - effectiveTheme === undefined, - ); + const [isThemeDialogOpen, setIsThemeDialogOpen] = useState(false); // TODO: refactor how theme's are accessed to avoid requiring a forced render. const [, setForceRender] = useState(0); + // Apply initial theme on component mount + useEffect(() => { + try { + themeManager.setActiveTheme(effectiveTheme); + setThemeError(null); // Clear any previous theme error on success + } catch (error: unknown) { + // If theme is not found during initial load, open the theme selection dialog and set error message + if ( + error instanceof Error && + error.message.includes('Theme') && + error.message.includes('not found') + ) { + setIsThemeDialogOpen(true); + setThemeError( + `Error: ${error instanceof Error ? error.message : String(error)}`, + ); + } else { + console.error( + `Error setting initial theme: ${error instanceof Error ? error.message : String(error)}`, + ); + setThemeError( + `Error setting initial theme: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + }, [effectiveTheme, setThemeError]); // Re-run if effectiveTheme or setThemeError changes + const openThemeDialog = useCallback(() => { setIsThemeDialogOpen(true); }, []); - const applyTheme = useCallback((themeName: string | undefined) => { - try { - themeManager.setActiveTheme(themeName); - setForceRender((v) => v + 1); // Trigger potential re-render - } catch (error) { - console.error(`Error setting theme: ${error}`); - } - }, []); + const applyTheme = useCallback( + (themeName: string | undefined) => { + try { + themeManager.setActiveTheme(themeName); + setForceRender((v) => v + 1); // Trigger potential re-render + setThemeError(null); // Clear any previous theme error on success + } catch (error: unknown) { + // If theme is not found, open the theme selection dialog and set error message + if ( + error instanceof Error && + error.message.includes('Theme') && + error.message.includes('not found') + ) { + setIsThemeDialogOpen(true); + setThemeError( + `Error: ${error instanceof Error ? error.message : String(error)}`, + ); + } else { + console.error( + `Error setting theme: ${error instanceof Error ? error.message : String(error)}`, + ); + setThemeError( + `Error setting theme: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + }, + [setForceRender, setThemeError], + ); const handleThemeHighlight = useCallback( (themeName: string | undefined) => { applyTheme(themeName); }, [applyTheme], - ); // Added applyTheme to dependencies + ); const handleThemeSelect = useCallback( (themeName: string | undefined, scope: SettingScope) => {