diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx
index ea8482a161..54684a8c2c 100644
--- a/packages/cli/src/ui/App.tsx
+++ b/packages/cli/src/ui/App.tsx
@@ -5,34 +5,15 @@
*/
import { useIsScreenReaderEnabled } from 'ink';
-import { useTerminalSize } from './hooks/useTerminalSize.js';
-import { lerp } from '../utils/math.js';
import { useUIState } from './contexts/UIStateContext.js';
import { StreamingContext } from './contexts/StreamingContext.js';
import { QuittingDisplay } from './components/QuittingDisplay.js';
import { ScreenReaderAppLayout } from './layouts/ScreenReaderAppLayout.js';
import { DefaultAppLayout } from './layouts/DefaultAppLayout.js';
-const getContainerWidth = (terminalWidth: number): string => {
- if (terminalWidth <= 80) {
- return '98%';
- }
- if (terminalWidth >= 132) {
- return '90%';
- }
-
- // Linearly interpolate between 80 columns (98%) and 132 columns (90%).
- const t = (terminalWidth - 80) / (132 - 80);
- const percentage = lerp(98, 90, t);
-
- return `${Math.round(percentage)}%`;
-};
-
export const App = () => {
const uiState = useUIState();
const isScreenReaderEnabled = useIsScreenReaderEnabled();
- const { columns } = useTerminalSize();
- const containerWidth = getContainerWidth(columns);
if (uiState.quittingMessages) {
return ;
@@ -40,11 +21,7 @@ export const App = () => {
return (
- {isScreenReaderEnabled ? (
-
- ) : (
-
- )}
+ {isScreenReaderEnabled ? : }
);
};
diff --git a/packages/cli/src/ui/components/ContextUsageDisplay.tsx b/packages/cli/src/ui/components/ContextUsageDisplay.tsx
index 25dad9c7e3..d7d8c063f0 100644
--- a/packages/cli/src/ui/components/ContextUsageDisplay.tsx
+++ b/packages/cli/src/ui/components/ContextUsageDisplay.tsx
@@ -11,21 +11,15 @@ import { tokenLimit } from '@google/gemini-cli-core';
export const ContextUsageDisplay = ({
promptTokenCount,
model,
- terminalWidth,
}: {
promptTokenCount: number;
model: string;
- terminalWidth: number;
}) => {
const percentage = promptTokenCount / tokenLimit(model);
- const percentageLeft = ((1 - percentage) * 100).toFixed(0);
-
- const label = terminalWidth < 100 ? '%' : '% context left';
return (
- ({percentageLeft}
- {label})
+ ({((1 - percentage) * 100).toFixed(0)}% context left)
);
};
diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx
index 31f256d5c6..3c0a4d9b96 100644
--- a/packages/cli/src/ui/components/Footer.test.tsx
+++ b/packages/cli/src/ui/components/Footer.test.tsx
@@ -9,6 +9,7 @@ import { describe, it, expect, vi } from 'vitest';
import { Footer } from './Footer.js';
import * as useTerminalSize from '../hooks/useTerminalSize.js';
import { tildeifyPath } from '@google/gemini-cli-core';
+import path from 'node:path';
import { type UIState, UIStateContext } from '../contexts/UIStateContext.js';
import { ConfigContext } from '../contexts/ConfigContext.js';
import { SettingsContext } from '../contexts/SettingsContext.js';
@@ -102,22 +103,34 @@ describe('', () => {
});
describe('path display', () => {
- it('should display a shortened path on a narrow terminal', () => {
- const { lastFrame } = renderWithWidth(79, createMockUIState());
+ it('should display shortened path on a wide terminal', () => {
+ const { lastFrame } = renderWithWidth(120, createMockUIState());
const tildePath = tildeifyPath(defaultProps.targetDir);
- const pathLength = Math.max(20, Math.floor(79 * 0.25));
- const expectedPath =
- '...' + tildePath.slice(tildePath.length - pathLength + 3);
+ const expectedPath = '...' + tildePath.slice(tildePath.length - 48 + 3);
+ expect(lastFrame()).toContain(expectedPath);
+ });
+
+ it('should display only the base directory name on a narrow terminal', () => {
+ const { lastFrame } = renderWithWidth(79, createMockUIState());
+ const expectedPath = path.basename(defaultProps.targetDir);
expect(lastFrame()).toContain(expectedPath);
});
it('should use wide layout at 80 columns', () => {
const { lastFrame } = renderWithWidth(80, createMockUIState());
const tildePath = tildeifyPath(defaultProps.targetDir);
- const expectedPath =
- '...' + tildePath.slice(tildePath.length - 80 * 0.25 + 3);
+ const expectedPath = '...' + tildePath.slice(tildePath.length - 32 + 3);
expect(lastFrame()).toContain(expectedPath);
});
+
+ it('should use narrow layout at 79 columns', () => {
+ const { lastFrame } = renderWithWidth(79, createMockUIState());
+ const expectedPath = path.basename(defaultProps.targetDir);
+ expect(lastFrame()).toContain(expectedPath);
+ const tildePath = tildeifyPath(defaultProps.targetDir);
+ const unexpectedPath = '...' + tildePath.slice(tildePath.length - 31 + 3);
+ expect(lastFrame()).not.toContain(unexpectedPath);
+ });
});
it('displays the branch name when provided', () => {
@@ -138,13 +151,7 @@ describe('', () => {
it('displays the model name and context percentage', () => {
const { lastFrame } = renderWithWidth(120, createMockUIState());
expect(lastFrame()).toContain(defaultProps.model);
- expect(lastFrame()).toMatch(/\(\d+% context left\)/);
- });
-
- it('displays the model name and abbreviated context percentage', () => {
- const { lastFrame } = renderWithWidth(99, createMockUIState());
- expect(lastFrame()).toContain(defaultProps.model);
- expect(lastFrame()).toMatch(/\(\d+%\)/);
+ expect(lastFrame()).toMatch(/\(\d+% context[\s\S]*left\)/);
});
describe('sandbox and trust info', () => {
diff --git a/packages/cli/src/ui/components/Footer.tsx b/packages/cli/src/ui/components/Footer.tsx
index 7bbe5ef52d..f35eb6abad 100644
--- a/packages/cli/src/ui/components/Footer.tsx
+++ b/packages/cli/src/ui/components/Footer.tsx
@@ -10,11 +10,14 @@ import { theme } from '../semantic-colors.js';
import { shortenPath, tildeifyPath } from '@google/gemini-cli-core';
import { ConsoleSummaryDisplay } from './ConsoleSummaryDisplay.js';
import process from 'node:process';
+import path from 'node:path';
import Gradient from 'ink-gradient';
import { MemoryUsageDisplay } from './MemoryUsageDisplay.js';
import { ContextUsageDisplay } from './ContextUsageDisplay.js';
import { DebugProfiler } from './DebugProfiler.js';
+
import { useTerminalSize } from '../hooks/useTerminalSize.js';
+import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { useUIState } from '../contexts/UIStateContext.js';
import { useConfig } from '../contexts/ConfigContext.js';
@@ -62,8 +65,13 @@ export const Footer: React.FC = () => {
const { columns: terminalWidth } = useTerminalSize();
- const pathLength = Math.max(20, Math.floor(terminalWidth * 0.25));
- const displayPath = shortenPath(tildeifyPath(targetDir), pathLength);
+ const isNarrow = isNarrowWidth(terminalWidth);
+
+ // Adjust path length based on terminal width
+ const pathLength = Math.max(20, Math.floor(terminalWidth * 0.4));
+ const displayPath = isNarrow
+ ? path.basename(tildeifyPath(targetDir))
+ : shortenPath(tildeifyPath(targetDir), pathLength);
const justifyContent = hideCWD && hideModelInfo ? 'center' : 'space-between';
const displayVimMode = vimEnabled ? vimMode : undefined;
@@ -72,8 +80,8 @@ export const Footer: React.FC = () => {
{(debugMode || displayVimMode || !hideCWD) && (
@@ -108,10 +116,12 @@ export const Footer: React.FC = () => {
{/* Middle Section: Centered Trust/Sandbox Info */}
{!hideSandboxStatus && (
{isTrustedFolder === false ? (
untrusted
@@ -129,33 +139,35 @@ export const Footer: React.FC = () => {
) : (
- no sandbox
- {terminalWidth >= 100 && (
- (see /docs)
- )}
+ no sandbox (see /docs)
)}
)}
{/* Right Section: Gemini Label and Console Summary */}
- {!hideModelInfo && (
-
-
-
- {model}{' '}
-
-
- {showMemoryUsage && }
-
+ {(!hideModelInfo ||
+ showMemoryUsage ||
+ corgiMode ||
+ (!showErrorDetails && errorCount > 0)) && (
+
+ {!hideModelInfo && (
+
+
+ {isNarrow ? '' : ' '}
+ {model}{' '}
+
+
+ {showMemoryUsage && }
+
+ )}
{corgiMode && (
- |
+ {!hideModelInfo && | }
▼
(´
ᴥ
@@ -165,7 +177,7 @@ export const Footer: React.FC = () => {
)}
{!showErrorDetails && errorCount > 0 && (
- |
+ {!hideModelInfo && | }
)}
diff --git a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
index 23b14903d5..8c6553c33d 100644
--- a/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
+++ b/packages/cli/src/ui/components/__snapshots__/Footer.test.tsx.snap
@@ -1,11 +1,20 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[` > footer configuration filtering (golden snapshots) > renders complete footer in narrow terminal (baseline narrow) > complete-footer-narrow 1`] = `"...s/to/make/it/long (main*) no sandbox gemini-pro (100%)"`;
+exports[` > footer configuration filtering (golden snapshots) > renders complete footer in narrow terminal (baseline narrow) > complete-footer-narrow 1`] = `
+"long (main*)
-exports[` > footer configuration filtering (golden snapshots) > renders complete footer with all sections visible (baseline) > complete-footer-wide 1`] = `"...directories/to/make/it/long (main*) no sandbox (see /docs) gemini-pro (100% context left)"`;
+no sandbox (see /docs)
+
+gemini-pro (100% context left)"
+`;
+
+exports[` > footer configuration filtering (golden snapshots) > renders complete footer with all sections visible (baseline) > complete-footer-wide 1`] = `
+"...bar/and/some/more/directories/to/make/it/long no sandbox (see gemini-pro (100% context
+(main*) /docs) left)"
+`;
exports[` > footer configuration filtering (golden snapshots) > renders footer with CWD and model info hidden to test alignment (only sandbox visible) > footer-only-sandbox 1`] = `" no sandbox (see /docs)"`;
exports[` > footer configuration filtering (golden snapshots) > renders footer with all optional sections hidden (minimal footer) > footer-minimal 1`] = `""`;
-exports[` > footer configuration filtering (golden snapshots) > renders footer with only model info hidden (partial filtering) > footer-no-model 1`] = `"...directories/to/make/it/long (main*) no sandbox (see /docs)"`;
+exports[` > footer configuration filtering (golden snapshots) > renders footer with only model info hidden (partial filtering) > footer-no-model 1`] = `"...bar/and/some/more/directories/to/make/it/long (main*) no sandbox (see /docs)"`;
diff --git a/packages/cli/src/ui/layouts/DefaultAppLayout.tsx b/packages/cli/src/ui/layouts/DefaultAppLayout.tsx
index 37a617250d..1fd980e387 100644
--- a/packages/cli/src/ui/layouts/DefaultAppLayout.tsx
+++ b/packages/cli/src/ui/layouts/DefaultAppLayout.tsx
@@ -13,13 +13,11 @@ import { Composer } from '../components/Composer.js';
import { ExitWarning } from '../components/ExitWarning.js';
import { useUIState } from '../contexts/UIStateContext.js';
-export const DefaultAppLayout: React.FC<{ width?: string }> = ({
- width = '90%',
-}) => {
+export const DefaultAppLayout: React.FC = () => {
const uiState = useUIState();
return (
-
+
diff --git a/packages/cli/src/utils/math.ts b/packages/cli/src/utils/math.ts
deleted file mode 100644
index f067a4ef95..0000000000
--- a/packages/cli/src/utils/math.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @license
- * Copyright 2025 Google LLC
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * Linearly interpolates between two values.
- *
- * @param start The start value.
- * @param end The end value.
- * @param t The interpolation amount (typically between 0 and 1).
- */
-export const lerp = (start: number, end: number, t: number): number =>
- start + (end - start) * t;