From 4d9cc36146867c707a7df0a309ebfc4527c37890 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Fri, 27 Feb 2026 07:07:14 -0800 Subject: [PATCH] Fix flicker showing message to press ctrl-O again to collapse. (#20414) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packages/cli/src/ui/AppContainer.tsx | 87 +++++--------------- packages/cli/src/ui/hooks/useTimedMessage.ts | 10 ++- 2 files changed, 25 insertions(+), 72 deletions(-) diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index b89d0b83c0..99c4b0c424 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -264,14 +264,16 @@ export const AppContainer = (props: AppContainerProps) => { () => isWorkspaceTrusted(settings.merged).isTrusted, ); - const [queueErrorMessage, setQueueErrorMessage] = useState( - null, + const [queueErrorMessage, setQueueErrorMessage] = useTimedMessage( + QUEUE_ERROR_DISPLAY_DURATION_MS, ); const [newAgents, setNewAgents] = useState(null); const [constrainHeight, setConstrainHeight] = useState(true); - const [showIsExpandableHint, setShowIsExpandableHint] = useState(false); - const expandHintTimerRef = useRef(null); + const [expandHintTrigger, triggerExpandHint] = useTimedMessage( + EXPAND_HINT_DURATION_MS, + ); + const showIsExpandableHint = Boolean(expandHintTrigger); const overflowState = useOverflowState(); const overflowingIdsSize = overflowState?.overflowingIds.size ?? 0; const hasOverflowState = overflowingIdsSize > 0 || !constrainHeight; @@ -284,39 +286,15 @@ export const AppContainer = (props: AppContainerProps) => { * boolean dependency (hasOverflowState) to ensure the timer only resets on * genuine state transitions, preventing it from infinitely resetting during * active text streaming. + * + * In alternate buffer mode, we don't trigger the hint automatically on overflow + * to avoid noise, but the user can still trigger it manually with Ctrl+O. */ useEffect(() => { - if (isAlternateBuffer) { - setShowIsExpandableHint(false); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } - return; + if (hasOverflowState && !isAlternateBuffer) { + triggerExpandHint(true); } - - if (hasOverflowState) { - setShowIsExpandableHint(true); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } - expandHintTimerRef.current = setTimeout(() => { - setShowIsExpandableHint(false); - }, EXPAND_HINT_DURATION_MS); - } - }, [hasOverflowState, isAlternateBuffer, constrainHeight]); - - /** - * Safe cleanup to ensure the expansion hint timer is cancelled when the - * component unmounts, preventing memory leaks. - */ - useEffect( - () => () => { - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } - }, - [], - ); + }, [hasOverflowState, isAlternateBuffer, triggerExpandHint]); const [defaultBannerText, setDefaultBannerText] = useState(''); const [warningBannerText, setWarningBannerText] = useState(''); @@ -1252,10 +1230,7 @@ Logging in with Google... Restarting Gemini CLI to continue. async (submittedValue: string) => { reset(); // Explicitly hide the expansion hint and clear its x-second timer when a new turn begins. - setShowIsExpandableHint(false); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } + triggerExpandHint(null); if (!constrainHeight) { setConstrainHeight(true); if (!isAlternateBuffer) { @@ -1327,16 +1302,14 @@ Logging in with Google... Restarting Gemini CLI to continue. refreshStatic, reset, handleHintSubmit, + triggerExpandHint, ], ); const handleClearScreen = useCallback(() => { reset(); // Explicitly hide the expansion hint and clear its x-second timer when clearing the screen. - setShowIsExpandableHint(false); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } + triggerExpandHint(null); historyManager.clearItems(); clearConsoleMessagesState(); refreshStatic(); @@ -1345,7 +1318,7 @@ Logging in with Google... Restarting Gemini CLI to continue. clearConsoleMessagesState, refreshStatic, reset, - setShowIsExpandableHint, + triggerExpandHint, ]); const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit); @@ -1632,17 +1605,6 @@ Logging in with Google... Restarting Gemini CLI to continue. } }, [ideNeedsRestart]); - useEffect(() => { - if (queueErrorMessage) { - const timer = setTimeout(() => { - setQueueErrorMessage(null); - }, QUEUE_ERROR_DISPLAY_DURATION_MS); - - return () => clearTimeout(timer); - } - return undefined; - }, [queueErrorMessage, setQueueErrorMessage]); - useEffect(() => { if (isInitialMount.current) { isInitialMount.current = false; @@ -1748,13 +1710,7 @@ Logging in with Google... Restarting Gemini CLI to continue. setConstrainHeight(true); if (keyMatchers[Command.SHOW_MORE_LINES](key)) { // If the user manually collapses the view, show the hint and reset the x-second timer. - setShowIsExpandableHint(true); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } - expandHintTimerRef.current = setTimeout(() => { - setShowIsExpandableHint(false); - }, EXPAND_HINT_DURATION_MS); + triggerExpandHint(true); } if (!isAlternateBuffer) { refreshStatic(); @@ -1803,13 +1759,7 @@ Logging in with Google... Restarting Gemini CLI to continue. ) { setConstrainHeight(false); // If the user manually expands the view, show the hint and reset the x-second timer. - setShowIsExpandableHint(true); - if (expandHintTimerRef.current) { - clearTimeout(expandHintTimerRef.current); - } - expandHintTimerRef.current = setTimeout(() => { - setShowIsExpandableHint(false); - }, EXPAND_HINT_DURATION_MS); + triggerExpandHint(true); if (!isAlternateBuffer) { refreshStatic(); } @@ -1914,6 +1864,7 @@ Logging in with Google... Restarting Gemini CLI to continue. showTransientMessage, settings.merged.general.devtools, showErrorDetails, + triggerExpandHint, ], ); diff --git a/packages/cli/src/ui/hooks/useTimedMessage.ts b/packages/cli/src/ui/hooks/useTimedMessage.ts index 3fe5f0b9c4..547968cb90 100644 --- a/packages/cli/src/ui/hooks/useTimedMessage.ts +++ b/packages/cli/src/ui/hooks/useTimedMessage.ts @@ -15,14 +15,16 @@ export function useTimedMessage(durationMs: number) { const timeoutRef = useRef(null); const showMessage = useCallback( - (msg: T) => { + (msg: T | null) => { setMessage(msg); if (timeoutRef.current) { clearTimeout(timeoutRef.current); } - timeoutRef.current = setTimeout(() => { - setMessage(null); - }, durationMs); + if (msg !== null) { + timeoutRef.current = setTimeout(() => { + setMessage(null); + }, durationMs); + } }, [durationMs], );