From f137f43f699bf970a408ab740167eef5e6687d8f Mon Sep 17 00:00:00 2001 From: Hriday Taneja Date: Thu, 28 Aug 2025 17:24:00 +0000 Subject: [PATCH 1/8] fix: hide tips when trust folder dialog is active --- packages/cli/src/ui/App.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 4561853b171..24714c8df3e 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -260,6 +260,16 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } = useFolderTrust(settings, setIsTrustedFolder); + const showTips = !isFolderTrustDialogOpen && !settings.merged.hideTips && !config.getScreenReader(); + + useEffect(() => { + // When the folder trust dialog is about to open, we need to force a refresh + // of the static content to ensure the Tips are hidden correctly. + if (isFolderTrustDialogOpen) { + refreshStatic(); + } + }, [isFolderTrustDialogOpen, refreshStatic]); + const { isAuthDialogOpen, openAuthDialog, @@ -922,9 +932,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { {!(settings.merged.hideBanner || config.getScreenReader()) && (
)} - {!(settings.merged.hideTips || config.getScreenReader()) && ( - - )} + {showTips && } , ...history.map((h) => ( Date: Fri, 29 Aug 2025 08:44:22 +0000 Subject: [PATCH 2/8] added test --- packages/cli/src/ui/App.test.tsx | 20 ++++++++++++++++++++ packages/cli/src/ui/App.tsx | 17 ++++++++++------- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 55e16e09923..80a68db9193 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -1265,6 +1265,26 @@ describe('App UI', () => { expect(lastFrame()).toContain('Do you trust this folder?'); }); + it('should not display Tips component when folder trust dialog is open', async () => { + const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); + vi.mocked(useFolderTrust).mockReturnValue({ + isFolderTrustDialogOpen: true, + handleFolderTrustSelect: vi.fn(), + isRestarting: false, + }); + + const { unmount } = renderWithProviders( + , + ); + currentUnmount = unmount; + await Promise.resolve(); + expect(vi.mocked(Tips)).not.toHaveBeenCalled(); + }); + it('should not display the folder trust dialog when the feature is disabled', async () => { const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); vi.mocked(useFolderTrust).mockReturnValue({ diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 4c27f0fa1ad..1d8d475ab35 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -303,15 +303,18 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { const { isFolderTrustDialogOpen, handleFolderTrustSelect, isRestarting } = useFolderTrust(settings, setIsTrustedFolder); - const showTips = !isFolderTrustDialogOpen && !settings.merged.ui?.hideTips && !config.getScreenReader(); + const showTips = + !isFolderTrustDialogOpen && + !settings.merged.ui?.hideTips && + !config.getScreenReader(); useEffect(() => { - // When the folder trust dialog is about to open, we need to force a refresh - // of the static content to ensure the Tips are hidden correctly. - if (isFolderTrustDialogOpen) { - refreshStatic(); - } - }, [isFolderTrustDialogOpen, refreshStatic]); + // When the folder trust dialog is about to open, we need to force a refresh + // of the static content to ensure the Tips are hidden correctly. + if (isFolderTrustDialogOpen) { + refreshStatic(); + } + }, [isFolderTrustDialogOpen, refreshStatic]); const { isAuthDialogOpen, From 7e0c5c78491ce4bb53b142a465f0485c9959e9dd Mon Sep 17 00:00:00 2001 From: Hriday Taneja Date: Fri, 29 Aug 2025 09:24:15 +0000 Subject: [PATCH 3/8] updated logic --- packages/cli/src/ui/App.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 1d8d475ab35..d5b2702d10e 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -309,11 +309,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { !config.getScreenReader(); useEffect(() => { - // When the folder trust dialog is about to open, we need to force a refresh - // of the static content to ensure the Tips are hidden correctly. - if (isFolderTrustDialogOpen) { - refreshStatic(); - } + // 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 { From fdc0b4f376be22136fd95615a90dacb9af358be6 Mon Sep 17 00:00:00 2001 From: Hriday Taneja Date: Fri, 29 Aug 2025 11:22:24 +0000 Subject: [PATCH 4/8] test added --- packages/cli/src/ui/App.test.tsx | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index 80a68db9193..96d92789503 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -1285,6 +1285,53 @@ describe('App UI', () => { expect(vi.mocked(Tips)).not.toHaveBeenCalled(); }); + it('should show Tips after the dialog is closed via user interaction', async () => { + const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); + const mockUseFolderTrust = vi.mocked(useFolderTrust); + let isFolderTrustDialogOpen = true; + + const handleFolderTrustSelect = () => { + isFolderTrustDialogOpen = false; + }; + + mockUseFolderTrust.mockImplementation(() => ({ + isFolderTrustDialogOpen, + handleFolderTrustSelect, + isRestarting: false, + })); + + const { lastFrame, rerender, stdin, unmount } = renderWithProviders( + , + ); + currentUnmount = unmount; + + // Ensure dialog is open and tips are hidden + expect(lastFrame()).toContain('Do you trust this folder?'); + expect(vi.mocked(Tips)).not.toHaveBeenCalled(); + + // Simulate user pressing escape to close the dialog + stdin.write('\x1b'); + + // Rerender to apply the state change from the mock + rerender( + , + ); + + // Wait for the UI to update + await waitFor(() => { + expect(lastFrame()).not.toContain('Do you trust this folder?'); + expect(vi.mocked(Tips)).toHaveBeenCalled(); + }); + }); + it('should not display the folder trust dialog when the feature is disabled', async () => { const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); vi.mocked(useFolderTrust).mockReturnValue({ From 0aa23f156b66ff84f42e7db47b727a114af2a585 Mon Sep 17 00:00:00 2001 From: Hriday Taneja Date: Thu, 4 Sep 2025 12:05:50 +0000 Subject: [PATCH 5/8] lint fix --- packages/cli/src/ui/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index d2cde17cadf..74f60256a0d 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -316,7 +316,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { // of the static content to ensure the Tips are hidden/shown correctly. refreshStatic(); }, [isFolderTrustDialogOpen, refreshStatic]); - + const { needsRestart: ideNeedsRestart } = useIdeTrustListener(config); useEffect(() => { if (ideNeedsRestart) { From 1f73e142fa3ea0a5ca8b7a3281d94d37ccab5774 Mon Sep 17 00:00:00 2001 From: Taneja Hriday Date: Mon, 8 Sep 2025 15:37:25 +0000 Subject: [PATCH 6/8] fix: hide tips logic updated after reafactored UI --- packages/cli/src/ui/AppContainer.tsx | 2 +- packages/cli/src/ui/components/AppHeader.tsx | 11 +++++++---- packages/cli/src/ui/hooks/useFolderTrust.ts | 7 +++++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index 32d085ec5ef..b253ff88b5b 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 685799111b4..d13af344ad8 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.ts b/packages/cli/src/ui/hooks/useFolderTrust.ts index 1d1b6b87762..3e98fa771ad 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(); From bc705714bc2cc2ce66f4e820dad81f7d3fd67cef Mon Sep 17 00:00:00 2001 From: Taneja Hriday Date: Mon, 8 Sep 2025 16:19:57 +0000 Subject: [PATCH 7/8] test: added tests --- .../cli/src/ui/hooks/useFolderTrust.test.ts | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/ui/hooks/useFolderTrust.test.ts b/packages/cli/src/ui/hooks/useFolderTrust.test.ts index 99a7c3b05eb..180593fa9f6 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); + }); }); From e265eabf3ff1891a9558cc27fbdafcb490512fa7 Mon Sep 17 00:00:00 2001 From: Taneja Hriday Date: Mon, 8 Sep 2025 16:29:09 +0000 Subject: [PATCH 8/8] lint fixes --- packages/cli/src/ui/hooks/useFolderTrust.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/ui/hooks/useFolderTrust.ts b/packages/cli/src/ui/hooks/useFolderTrust.ts index 3e98fa771ad..93e019ae20b 100644 --- a/packages/cli/src/ui/hooks/useFolderTrust.ts +++ b/packages/cli/src/ui/hooks/useFolderTrust.ts @@ -34,7 +34,7 @@ export const useFolderTrust = ( onTrustChange(trusted); }, [folderTrust, onTrustChange, settings.merged]); - useEffect(() => { + 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();