From ff0902e6088ee9e961ba48b5e14a08a36cfaa2ad Mon Sep 17 00:00:00 2001 From: jacob314 Date: Wed, 1 Apr 2026 12:29:54 -0700 Subject: [PATCH] feat: Auto-scroll to bottom via Events feat: Auto-scroll to bottom on exiting mouse mode --- packages/cli/src/ui/AppContainer.tsx | 8 +++++++- packages/cli/src/ui/components/Composer.tsx | 7 +++++++ packages/cli/src/ui/components/InputPrompt.tsx | 4 ++++ packages/cli/src/ui/components/MainContent.tsx | 11 +++++++++++ packages/cli/src/utils/events.ts | 2 ++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index e38e1996db..69767fdefa 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -1755,7 +1755,13 @@ Logging in with Google... Restarting Gemini CLI to continue. } if (keyMatchers[Command.TOGGLE_MOUSE_MODE](key)) { - setMouseMode((prev) => !prev); + setMouseMode((prev) => { + const next = !prev; + if (!next && !isAlternateBuffer) { + appEvents.emit(AppEvent.ScrollToBottom); + } + return next; + }); return true; } diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 590d1e9c6b..b9e1749bfd 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -26,6 +26,7 @@ import { OverflowProvider } from '../contexts/OverflowContext.js'; import { ConfigInitDisplay } from './ConfigInitDisplay.js'; import { TodoTray } from './messages/Todo.js'; import { useComposerStatus } from '../hooks/useComposerStatus.js'; +import { appEvents, AppEvent } from '../../utils/events.js'; export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { const uiState = useUIState(); @@ -55,6 +56,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => { const { setShortcutsHelpVisible } = uiActions; + useEffect(() => { + if (hasPendingActionRequired) { + appEvents.emit(AppEvent.ScrollToBottom); + } + }, [hasPendingActionRequired]); + useEffect(() => { if (uiState.shortcutsHelpVisible && !isPassiveShortcutsHelpState) { setShortcutsHelpVisible(false); diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index f078dbc7d6..cce8bf2eaa 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -338,6 +338,10 @@ export const InputPrompt: React.FC = ({ const showCursor = focus && isShellFocused && !isEmbeddedShellFocused && !copyModeEnabled; + useEffect(() => { + appEvents.emit(AppEvent.ScrollToBottom); + }, [buffer.text, buffer.cursor]); + // Notify parent component about escape prompt state changes useEffect(() => { if (onEscapePromptChange) { diff --git a/packages/cli/src/ui/components/MainContent.tsx b/packages/cli/src/ui/components/MainContent.tsx index 056f6041e6..8515db445f 100644 --- a/packages/cli/src/ui/components/MainContent.tsx +++ b/packages/cli/src/ui/components/MainContent.tsx @@ -23,6 +23,7 @@ import { MAX_GEMINI_MESSAGE_LINES } from '../constants.js'; import { useConfirmingTool } from '../hooks/useConfirmingTool.js'; import { ToolConfirmationQueue } from './ToolConfirmationQueue.js'; import { isTopicTool } from './messages/TopicMessage.js'; +import { appEvents, AppEvent } from '../../utils/events.js'; const MemoizedHistoryItemDisplay = memo(HistoryItemDisplay); const MemoizedAppHeader = memo(AppHeader); @@ -53,6 +54,16 @@ export const MainContent = () => { } }, [showConfirmationQueue, confirmingToolCallId]); + useEffect(() => { + const handleScroll = () => { + scrollableListRef.current?.scrollToEnd(); + }; + appEvents.on(AppEvent.ScrollToBottom, handleScroll); + return () => { + appEvents.off(AppEvent.ScrollToBottom, handleScroll); + }; + }, []); + const { pendingHistoryItems, mainAreaWidth, diff --git a/packages/cli/src/utils/events.ts b/packages/cli/src/utils/events.ts index 8291528ac1..9c3ec6a365 100644 --- a/packages/cli/src/utils/events.ts +++ b/packages/cli/src/utils/events.ts @@ -23,6 +23,7 @@ export enum AppEvent { PasteTimeout = 'paste-timeout', TerminalBackground = 'terminal-background', TransientMessage = 'transient-message', + ScrollToBottom = 'scroll-to-bottom', } export interface AppEvents { @@ -32,6 +33,7 @@ export interface AppEvents { [AppEvent.PasteTimeout]: never[]; [AppEvent.TerminalBackground]: [string]; [AppEvent.TransientMessage]: [TransientMessagePayload]; + [AppEvent.ScrollToBottom]: never[]; } export const appEvents = new EventEmitter();