fix(cli): resolve scroll-jacking and enable click-to-focus for shell toolboxes

This commit is contained in:
Keith Guerin
2026-03-01 01:05:28 -08:00
parent 30e2820dc2
commit 9d028db351
7 changed files with 24 additions and 9 deletions

View File

@@ -604,6 +604,7 @@ const mockUIActions: UIActions = {
revealCleanUiDetailsTemporarily: vi.fn(),
handleWarning: vi.fn(),
setEmbeddedShellFocused: vi.fn(),
setActivePtyId: vi.fn(),
dismissBackgroundShell: vi.fn(),
setActiveBackgroundShellPid: vi.fn(),
setIsBackgroundShellListOpen: vi.fn(),

View File

@@ -1109,6 +1109,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
backgroundShells,
dismissBackgroundShell,
retryStatus,
setActivePtyId,
} = useGeminiStream(
config.getGeminiClient(),
historyManager.history,
@@ -2509,6 +2510,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
revealCleanUiDetailsTemporarily,
handleWarning,
setEmbeddedShellFocused,
setActivePtyId,
dismissBackgroundShell,
setActiveBackgroundShellPid,
setIsBackgroundShellListOpen,
@@ -2601,6 +2603,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
revealCleanUiDetailsTemporarily,
handleWarning,
setEmbeddedShellFocused,
setActivePtyId,
dismissBackgroundShell,
setActiveBackgroundShellPid,
setIsBackgroundShellListOpen,

View File

@@ -78,7 +78,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
embeddedShellFocused,
);
const { setEmbeddedShellFocused } = useUIActions();
const { setEmbeddedShellFocused, setActivePtyId } = useUIActions();
const wasFocusedRef = React.useRef(false);
React.useEffect(() => {
@@ -102,6 +102,7 @@ export const ShellToolMessage: React.FC<ShellToolMessageProps> = ({
const handleFocus = () => {
if (isThisShellFocusable) {
setActivePtyId(ptyId ?? null);
setEmbeddedShellFocused(true);
}
};

View File

@@ -209,7 +209,7 @@ export const Scrollable: React.FC<ScrollableProps> = ({
width={width ?? maxWidth}
height={height}
flexDirection="column"
overflowY="scroll"
overflowY={hasFocus ? 'scroll' : 'hidden'}
overflowX="hidden"
scrollTop={scrollTop}
flexGrow={flexGrow}

View File

@@ -80,6 +80,7 @@ export interface UIActions {
revealCleanUiDetailsTemporarily: (durationMs?: number) => void;
handleWarning: (message: string) => void;
setEmbeddedShellFocused: (value: boolean) => void;
setActivePtyId: (pid: number | null) => void;
dismissBackgroundShell: (pid: number) => void;
setActiveBackgroundShellPid: (pid: number) => void;
setIsBackgroundShellListOpen: (isOpen: boolean) => void;

View File

@@ -152,6 +152,13 @@ export const useShellCommandProcessor = (
[m],
);
const setActivePtyId = useCallback(
(pid: number | null) => {
dispatch({ type: 'SET_ACTIVE_PTY', pid });
},
[dispatch],
);
const toggleBackgroundShell = useCallback(() => {
if (state.backgroundShells.size > 0) {
const willBeVisible = !state.isBackgroundShellVisible;
@@ -550,5 +557,6 @@ export const useShellCommandProcessor = (
registerBackgroundShell,
dismissBackgroundShell,
backgroundShells: state.backgroundShells,
setActivePtyId,
};
};

View File

@@ -204,9 +204,8 @@ export const useGeminiStream = (
consumeUserHint?: () => string | null,
) => {
const [initError, setInitError] = useState<string | null>(null);
const [retryStatus, setRetryStatus] = useState<RetryAttemptPayload | null>(
null,
);
const [modelRetryStatus, setModelRetryStatus] =
useState<RetryAttemptPayload | null>(null);
const isLowErrorVerbosity = settings.merged.ui?.errorVerbosity !== 'full';
const suppressedToolErrorCountRef = useRef(0);
const suppressedToolErrorNoteShownRef = useRef(false);
@@ -242,7 +241,7 @@ export const useGeminiStream = (
useEffect(() => {
const handleRetryAttempt = (payload: RetryAttemptPayload) => {
setRetryStatus(payload);
setModelRetryStatus(payload);
};
coreEvents.on(CoreEvent.RetryAttempt, handleRetryAttempt);
return () => {
@@ -338,6 +337,7 @@ export const useGeminiStream = (
registerBackgroundShell,
dismissBackgroundShell,
backgroundShells,
setActivePtyId,
} = useShellCommandProcessor(
addItem,
setPendingHistoryItem,
@@ -564,7 +564,7 @@ export const useGeminiStream = (
useEffect(() => {
if (!isResponding) {
setRetryStatus(null);
setModelRetryStatus(null);
}
}, [isResponding]);
@@ -844,7 +844,7 @@ export const useGeminiStream = (
currentGeminiMessageBuffer: string,
userMessageTimestamp: number,
): string => {
setRetryStatus(null);
setModelRetryStatus(null);
if (turnCancelledRef.current) {
// Prevents additional output after a user initiated cancel.
return '';
@@ -1897,6 +1897,7 @@ export const useGeminiStream = (
backgroundCurrentShell,
backgroundShells,
dismissBackgroundShell,
retryStatus,
retryStatus: modelRetryStatus,
setActivePtyId,
};
};