feat(cli): implement dynamic terminal tab titles for CLI status (#16378)

This commit is contained in:
N. Taylor Mullen
2026-01-12 17:18:14 -08:00
committed by GitHub
parent c572b9e9ac
commit 2fc61685a3
10 changed files with 508 additions and 114 deletions

View File

@@ -81,7 +81,7 @@ import { calculateMainAreaWidth } from './utils/ui-sizing.js';
import ansiEscapes from 'ansi-escapes';
import * as fs from 'node:fs';
import { basename } from 'node:path';
import { computeWindowTitle } from '../utils/windowTitle.js';
import { computeTerminalTitle } from '../utils/windowTitle.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useLogger } from './hooks/useLogger.js';
import { useGeminiStream } from './hooks/useGeminiStream.js';
@@ -125,8 +125,10 @@ import { useHookDisplayState } from './hooks/useHookDisplayState.js';
import {
WARNING_PROMPT_DURATION_MS,
QUEUE_ERROR_DISPLAY_DURATION_MS,
SHELL_ACTION_REQUIRED_TITLE_DELAY_MS,
} from './constants.js';
import { LoginWithGoogleRestartDialog } from './auth/LoginWithGoogleRestartDialog.js';
import { useInactivityTimer } from './hooks/useInactivityTimer.js';
function isToolExecuting(pendingHistoryItems: HistoryItemWithoutId[]) {
return pendingHistoryItems.some((item) => {
@@ -278,9 +280,6 @@ export const AppContainer = (props: AppContainerProps) => {
const mainControlsRef = useRef<DOMElement>(null);
// For performance profiling only
const rootUiRef = useRef<DOMElement>(null);
const originalTitleRef = useRef(
computeWindowTitle(basename(config.getTargetDir())),
);
const lastTitleRef = useRef<string | null>(null);
const staticExtraHeight = 3;
@@ -828,6 +827,13 @@ Logging in with Google... Restarting Gemini CLI to continue.
lastOutputTimeRef.current = lastOutputTime;
}, [lastOutputTime]);
const isShellAwaitingFocus = !!activePtyId && !embeddedShellFocused;
const showShellActionRequired = useInactivityTimer(
isShellAwaitingFocus,
isShellAwaitingFocus,
SHELL_ACTION_REQUIRED_TITLE_DELAY_MS,
);
// Auto-accept indicator
const showAutoAcceptIndicator = useAutoAcceptIndicator({
config,
@@ -1338,25 +1344,20 @@ Logging in with Google... Restarting Gemini CLI to continue.
// Update terminal title with Gemini CLI status and thoughts
useEffect(() => {
// Respect both showStatusInTitle and hideWindowTitle settings
if (
!settings.merged.ui?.showStatusInTitle ||
settings.merged.ui?.hideWindowTitle
)
return;
// Respect hideWindowTitle settings
if (settings.merged.ui?.hideWindowTitle) return;
let title;
if (streamingState === StreamingState.Idle) {
title = originalTitleRef.current;
} else {
const statusText = thought?.subject
?.replace(/[\r\n]+/g, ' ')
.substring(0, 80);
title = statusText || originalTitleRef.current;
}
// Pad the title to a fixed width to prevent taskbar icon resizing.
const paddedTitle = title.padEnd(80, ' ');
const paddedTitle = computeTerminalTitle({
streamingState,
thoughtSubject: thought?.subject,
isConfirming:
!!shellConfirmationRequest ||
!!confirmationRequest ||
showShellActionRequired,
folderName: basename(config.getTargetDir()),
showThoughts: !!settings.merged.ui?.showStatusInTitle,
useDynamicTitle: settings.merged.ui?.dynamicWindowTitle ?? true,
});
// Only update the title if it's different from the last value we set
if (lastTitleRef.current !== paddedTitle) {
@@ -1367,8 +1368,13 @@ Logging in with Google... Restarting Gemini CLI to continue.
}, [
streamingState,
thought,
shellConfirmationRequest,
confirmationRequest,
showShellActionRequired,
settings.merged.ui?.showStatusInTitle,
settings.merged.ui?.dynamicWindowTitle,
settings.merged.ui?.hideWindowTitle,
config,
stdout,
]);