diff --git a/packages/cli/src/ui/components/AppHeader.test.tsx b/packages/cli/src/ui/components/AppHeader.test.tsx
index 74388c816a..ca7488846f 100644
--- a/packages/cli/src/ui/components/AppHeader.test.tsx
+++ b/packages/cli/src/ui/components/AppHeader.test.tsx
@@ -200,4 +200,83 @@ describe('', () => {
expect(lastFrame()).not.toContain('First line\\nSecond line');
unmount();
});
+
+ it('should render Tips when tipsShown is false', () => {
+ persistentStateMock.get.mockImplementation((key) => {
+ if (key === 'tipsShown') return false;
+ return {};
+ });
+
+ const mockConfig = makeFakeConfig();
+ const uiState = {
+ history: [],
+ bannerData: {
+ defaultText: 'First line\\nSecond line',
+ warningText: '',
+ },
+ bannerVisible: true,
+ };
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ { config: mockConfig, uiState },
+ );
+
+ expect(lastFrame()).toContain('Tips');
+
+ expect(persistentStateMock.set).toHaveBeenCalledWith('tipsShown', true);
+ unmount();
+ });
+
+ it('should NOT render Tips when tipsShown is true', () => {
+ persistentStateMock.get.mockImplementation((key) => {
+ if (key === 'tipsShown') return true;
+ return {};
+ });
+
+ const mockConfig = makeFakeConfig();
+ const { lastFrame, unmount } = renderWithProviders(
+ ,
+ { config: mockConfig },
+ );
+
+ expect(lastFrame()).not.toContain('Tips');
+ unmount();
+ });
+
+ it('should show tips on the first run and hide them on the second run (persistence flow)', () => {
+ const fakeStore: Record = {};
+
+ persistentStateMock.get.mockImplementation((key) => fakeStore[key]);
+ persistentStateMock.set.mockImplementation((key, val) => {
+ fakeStore[key] = val;
+ });
+
+ const mockConfig = makeFakeConfig();
+ const uiState = {
+ history: [],
+ bannerData: {
+ defaultText: 'First line\\nSecond line',
+ warningText: '',
+ },
+ bannerVisible: true,
+ };
+ const session1 = renderWithProviders(, {
+ config: mockConfig,
+ uiState,
+ });
+
+ expect(session1.lastFrame()).toContain('Tips');
+
+ expect(fakeStore['tipsShown']).toBe(true);
+
+ session1.unmount();
+
+ const session2 = renderWithProviders(, {
+ config: mockConfig,
+ });
+
+ expect(session2.lastFrame()).not.toContain('Tips');
+
+ session2.unmount();
+ });
});
diff --git a/packages/cli/src/ui/components/AppHeader.tsx b/packages/cli/src/ui/components/AppHeader.tsx
index a70a7b20d8..5ad1b1ed8b 100644
--- a/packages/cli/src/ui/components/AppHeader.tsx
+++ b/packages/cli/src/ui/components/AppHeader.tsx
@@ -12,6 +12,7 @@ import { useConfig } from '../contexts/ConfigContext.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { Banner } from './Banner.js';
import { useBanner } from '../hooks/useBanner.js';
+import { useTips } from '../hooks/useTips.js';
interface AppHeaderProps {
version: string;
@@ -23,6 +24,7 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
const { nightly, mainAreaWidth, bannerData, bannerVisible } = useUIState();
const { bannerText } = useBanner(bannerData, config);
+ const tipsShown = useTips();
return (
@@ -38,9 +40,8 @@ export const AppHeader = ({ version }: AppHeaderProps) => {
)}
>
)}
- {!(settings.merged.ui.hideTips || config.getScreenReader()) && (
-
- )}
+ {!(settings.merged.ui.hideTips || config.getScreenReader()) &&
+ !tipsShown && }
);
};
diff --git a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
index 6da8b523f2..dcd5a4ca2a 100644
--- a/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/AppHeader.test.tsx.snap
@@ -10,12 +10,7 @@ exports[` > should not render the banner when no flags are set 1`]
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
-
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+"
`;
exports[` > should not render the banner when previewFeatures is enabled 1`] = `
@@ -28,12 +23,7 @@ exports[` > should not render the banner when previewFeatures is en
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
-
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+"
`;
exports[` > should not render the default banner if shown count is 5 or more 1`] = `
@@ -46,12 +36,7 @@ exports[` > should not render the default banner if shown count is
███░ ░░███ ░░███
███░ ░░█████████
░░░ ░░░░░░░░░
-
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+"
`;
exports[` > should render the banner when previewFeatures is disabled 1`] = `
@@ -67,12 +52,7 @@ exports[` > should render the banner when previewFeatures is disabl
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner │
-╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[` > should render the banner with default text 1`] = `
@@ -88,12 +68,7 @@ exports[` > should render the banner with default text 1`] = `
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ This is the default banner │
-╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
exports[` > should render the banner with warning text 1`] = `
@@ -109,10 +84,5 @@ exports[` > should render the banner with warning text 1`] = `
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ There are capacity issues │
-╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
-Tips for getting started:
-1. Ask questions, edit files, or run commands.
-2. Be specific for the best results.
-3. Create GEMINI.md files to customize your interactions with Gemini.
-4. /help for more information."
+╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯"
`;
diff --git a/packages/cli/src/ui/hooks/useTips.test.ts b/packages/cli/src/ui/hooks/useTips.test.ts
new file mode 100644
index 0000000000..0270f73864
--- /dev/null
+++ b/packages/cli/src/ui/hooks/useTips.test.ts
@@ -0,0 +1,42 @@
+/**
+ * @license
+ * Copyright 2026 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { renderHookWithProviders } from '../../test-utils/render.js';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { useTips } from './useTips.js';
+import { persistentState } from '../../utils/persistentState.js';
+
+vi.mock('../../utils/persistentState.js', () => ({
+ persistentState: {
+ get: vi.fn(),
+ set: vi.fn(),
+ },
+}));
+
+describe('useTips()', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should return false and call set(true) if state is undefined', () => {
+ vi.mocked(persistentState.get).mockReturnValue(undefined);
+
+ const { result } = renderHookWithProviders(() => useTips());
+
+ expect(result.current).toBe(false);
+
+ expect(persistentState.set).toHaveBeenCalledWith('tipsShown', true);
+ });
+
+ it('should return true if state is already true', () => {
+ vi.mocked(persistentState.get).mockReturnValue(true);
+
+ const { result } = renderHookWithProviders(() => useTips());
+
+ expect(result.current).toBe(true);
+ expect(persistentState.set).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/cli/src/ui/hooks/useTips.ts b/packages/cli/src/ui/hooks/useTips.ts
new file mode 100644
index 0000000000..d28df9ddb8
--- /dev/null
+++ b/packages/cli/src/ui/hooks/useTips.ts
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Copyright 2026 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { useEffect, useState } from 'react';
+import { persistentState } from '../../utils/persistentState.js';
+
+export function useTips() {
+ const [tipsShown] = useState(() => !!persistentState.get('tipsShown'));
+
+ useEffect(() => {
+ if (!tipsShown) {
+ persistentState.set('tipsShown', true);
+ }
+ }, [tipsShown]);
+
+ return tipsShown;
+}
diff --git a/packages/cli/src/utils/persistentState.ts b/packages/cli/src/utils/persistentState.ts
index f5fc4d4b29..f565157f69 100644
--- a/packages/cli/src/utils/persistentState.ts
+++ b/packages/cli/src/utils/persistentState.ts
@@ -12,6 +12,7 @@ const STATE_FILENAME = 'state.json';
interface PersistentStateData {
defaultBannerShownCount?: Record;
+ tipsShown?: boolean;
// Add other persistent state keys here as needed
}