diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 32d085ec5e..b253ff88b5 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -799,7 +799,7 @@ Logging in with Google... Please restart Gemini CLI to continue. const [showIdeRestartPrompt, setShowIdeRestartPrompt] = useState(false); const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } = - useFolderTrust(settings, config, setIsTrustedFolder); + useFolderTrust(settings, config, setIsTrustedFolder, refreshStatic); const { needsRestart: ideNeedsRestart } = useIdeTrustListener(); const isInitialMount = useRef(true); diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx index 685799111b..d13af344ad 100644 --- a/packages/cli/src/ui/components/AppHeader.tsx +++ b/packages/cli/src/ui/components/AppHeader.tsx @@ -18,15 +18,18 @@ interface AppHeaderProps { export const AppHeader = ({ version }: AppHeaderProps) => { const settings = useSettings(); const config = useConfig(); - const { nightly } = useUIState(); + const { nightly, isFolderTrustDialogOpen } = useUIState(); + const showTips = + !isFolderTrustDialogOpen && + !settings.merged.ui?.hideTips && + !config.getScreenReader(); + return ( {!(settings.merged.ui?.hideBanner || config.getScreenReader()) && (
)} - {!(settings.merged.ui?.hideTips || config.getScreenReader()) && ( - - )} + {showTips && } ); }; diff --git a/packages/cli/src/ui/hooks/useFolderTrust.test.ts b/packages/cli/src/ui/hooks/useFolderTrust.test.ts index 99a7c3b05e..180593fa9f 100644 --- a/packages/cli/src/ui/hooks/useFolderTrust.test.ts +++ b/packages/cli/src/ui/hooks/useFolderTrust.test.ts @@ -27,12 +27,16 @@ describe('useFolderTrust', () => { let loadTrustedFoldersSpy: vi.SpyInstance; let isWorkspaceTrustedSpy: vi.SpyInstance; let onTrustChange: (isTrusted: boolean | undefined) => void; + let refreshStatic: () => void; beforeEach(() => { mockSettings = { merged: { - folderTrustFeature: true, - folderTrust: undefined, + security: { + folderTrust: { + enabled: true, + }, + }, }, setValue: vi.fn(), } as unknown as LoadedSettings; @@ -49,6 +53,7 @@ describe('useFolderTrust', () => { isWorkspaceTrustedSpy = vi.spyOn(trustedFolders, 'isWorkspaceTrusted'); (process.cwd as vi.Mock).mockReturnValue('/test/path'); onTrustChange = vi.fn(); + refreshStatic = vi.fn(); }); afterEach(() => { @@ -58,7 +63,7 @@ describe('useFolderTrust', () => { it('should not open dialog when folder is already trusted', () => { isWorkspaceTrustedSpy.mockReturnValue(true); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); expect(result.current.isFolderTrustDialogOpen).toBe(false); expect(onTrustChange).toHaveBeenCalledWith(true); @@ -67,7 +72,7 @@ describe('useFolderTrust', () => { it('should not open dialog when folder is already untrusted', () => { isWorkspaceTrustedSpy.mockReturnValue(false); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); expect(result.current.isFolderTrustDialogOpen).toBe(false); expect(onTrustChange).toHaveBeenCalledWith(false); @@ -76,7 +81,7 @@ describe('useFolderTrust', () => { it('should open dialog when folder trust is undefined', () => { isWorkspaceTrustedSpy.mockReturnValue(undefined); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); expect(result.current.isFolderTrustDialogOpen).toBe(true); expect(onTrustChange).toHaveBeenCalledWith(undefined); @@ -87,7 +92,7 @@ describe('useFolderTrust', () => { .mockReturnValueOnce(undefined) .mockReturnValueOnce(true); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); isWorkspaceTrustedSpy.mockReturnValue(true); @@ -109,7 +114,7 @@ describe('useFolderTrust', () => { .mockReturnValueOnce(undefined) .mockReturnValueOnce(true); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); act(() => { @@ -129,7 +134,7 @@ describe('useFolderTrust', () => { .mockReturnValueOnce(undefined) .mockReturnValueOnce(false); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); act(() => { @@ -148,7 +153,7 @@ describe('useFolderTrust', () => { it('should do nothing for default choice', () => { isWorkspaceTrustedSpy.mockReturnValue(undefined); const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); act(() => { @@ -166,7 +171,7 @@ describe('useFolderTrust', () => { it('should set isRestarting to true when trust status changes from false to true', () => { isWorkspaceTrustedSpy.mockReturnValueOnce(false).mockReturnValueOnce(true); // Initially untrusted, then trusted const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); act(() => { @@ -182,7 +187,7 @@ describe('useFolderTrust', () => { .mockReturnValueOnce(undefined) .mockReturnValueOnce(true); // Initially undefined, then trust const { result } = renderHook(() => - useFolderTrust(mockSettings, mockConfig, onTrustChange), + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), ); act(() => { @@ -192,4 +197,26 @@ describe('useFolderTrust', () => { expect(result.current.isRestarting).toBe(false); expect(result.current.isFolderTrustDialogOpen).toBe(false); // Dialog should close }); + + it('should call refreshStatic when dialog opens and closes', () => { + isWorkspaceTrustedSpy.mockReturnValue(undefined); + const { result } = renderHook(() => + useFolderTrust(mockSettings, mockConfig, onTrustChange, refreshStatic), + ); + + // The hook runs, isFolderTrustDialogOpen becomes true, useEffect triggers. + // It's called once on mount, and once when the dialog state changes. + expect(refreshStatic).toHaveBeenCalledTimes(2); + expect(result.current.isFolderTrustDialogOpen).toBe(true); + + // Now, simulate closing the dialog + isWorkspaceTrustedSpy.mockReturnValue(true); // So the state update works + act(() => { + result.current.handleFolderTrustSelect(FolderTrustChoice.TRUST_FOLDER); + }); + + // The state isFolderTrustDialogOpen becomes false, useEffect triggers again + expect(refreshStatic).toHaveBeenCalledTimes(3); + expect(result.current.isFolderTrustDialogOpen).toBe(false); + }); }); diff --git a/packages/cli/src/ui/hooks/useFolderTrust.ts b/packages/cli/src/ui/hooks/useFolderTrust.ts index 1d1b6b8776..93e019ae20 100644 --- a/packages/cli/src/ui/hooks/useFolderTrust.ts +++ b/packages/cli/src/ui/hooks/useFolderTrust.ts @@ -19,6 +19,7 @@ export const useFolderTrust = ( settings: LoadedSettings, config: Config, onTrustChange: (isTrusted: boolean | undefined) => void, + refreshStatic: () => void, ) => { const [isTrusted, setIsTrusted] = useState(undefined); const [isFolderTrustDialogOpen, setIsFolderTrustDialogOpen] = useState(false); @@ -33,6 +34,12 @@ export const useFolderTrust = ( onTrustChange(trusted); }, [folderTrust, onTrustChange, settings.merged]); + useEffect(() => { + // When the folder trust dialog is about to open/close, we need to force a refresh + // of the static content to ensure the Tips are hidden/shown correctly. + refreshStatic(); + }, [isFolderTrustDialogOpen, refreshStatic]); + const handleFolderTrustSelect = useCallback( (choice: FolderTrustChoice) => { const trustedFolders = loadTrustedFolders();