Speculative fixes to try to fix react error. (#19508)

This commit is contained in:
Jacob Richman
2026-02-18 22:39:32 -08:00
committed by GitHub
parent c43500c50b
commit a804f99fe0
3 changed files with 35 additions and 18 deletions

View File

@@ -80,20 +80,24 @@ export function useFocusHint(
isThisShellFocused: boolean,
resultDisplay: ToolResultDisplay | undefined,
) {
const [lastUpdateTime, setLastUpdateTime] = useState<Date | null>(null);
const [userHasFocused, setUserHasFocused] = useState(false);
// Derive a stable reset key for the inactivity timer. For strings and arrays
// (shell output), we use the length to capture updates without referential
// identity issues or expensive deep comparisons.
const resetKey =
typeof resultDisplay === 'string'
? resultDisplay.length
: Array.isArray(resultDisplay)
? resultDisplay.length
: !!resultDisplay;
const showFocusHint = useInactivityTimer(
isThisShellFocusable,
lastUpdateTime ? lastUpdateTime.getTime() : 0,
resetKey,
SHELL_FOCUS_HINT_DELAY_MS,
);
useEffect(() => {
if (resultDisplay) {
setLastUpdateTime(new Date());
}
}, [resultDisplay]);
useEffect(() => {
if (isThisShellFocused) {
setUserHasFocused(true);

View File

@@ -96,7 +96,7 @@ describe('useConsoleMessages', () => {
});
await act(async () => {
await vi.advanceTimersByTimeAsync(20);
await vi.advanceTimersByTimeAsync(60);
});
expect(result.current.consoleMessages).toEqual([
@@ -114,7 +114,7 @@ describe('useConsoleMessages', () => {
});
await act(async () => {
await vi.advanceTimersByTimeAsync(20);
await vi.advanceTimersByTimeAsync(60);
});
expect(result.current.consoleMessages).toEqual([
@@ -131,7 +131,7 @@ describe('useConsoleMessages', () => {
});
await act(async () => {
await vi.advanceTimersByTimeAsync(20);
await vi.advanceTimersByTimeAsync(60);
});
expect(result.current.consoleMessages).toEqual([
@@ -148,7 +148,7 @@ describe('useConsoleMessages', () => {
});
await act(async () => {
await vi.advanceTimersByTimeAsync(20);
await vi.advanceTimersByTimeAsync(60);
});
expect(result.current.consoleMessages).toHaveLength(1);

View File

@@ -9,7 +9,7 @@ import {
useEffect,
useReducer,
useRef,
useTransition,
startTransition,
} from 'react';
import type { ConsoleMessageItem } from '../types.js';
import {
@@ -71,10 +71,11 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
const [consoleMessages, dispatch] = useReducer(consoleMessagesReducer, []);
const messageQueueRef = useRef<ConsoleMessageItem[]>([]);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const [, startTransition] = useTransition();
const isProcessingRef = useRef(false);
const processQueue = useCallback(() => {
if (messageQueueRef.current.length > 0) {
isProcessingRef.current = true;
const messagesToProcess = messageQueueRef.current;
messageQueueRef.current = [];
startTransition(() => {
@@ -87,15 +88,26 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
const handleNewMessage = useCallback(
(message: ConsoleMessageItem) => {
messageQueueRef.current.push(message);
if (!timeoutRef.current) {
// Batch updates using a timeout. 16ms is a reasonable delay to batch
// rapid-fire messages without noticeable lag.
timeoutRef.current = setTimeout(processQueue, 16);
if (!isProcessingRef.current && !timeoutRef.current) {
// Batch updates using a timeout. 50ms is a reasonable delay to batch
// rapid-fire messages without noticeable lag while avoiding React update
// queue flooding.
timeoutRef.current = setTimeout(processQueue, 50);
}
},
[processQueue],
);
// Once the updated consoleMessages have been committed to the screen,
// we can safely process the next batch of queued messages if any exist.
// This completely eliminates overlapping concurrent updates to this state.
useEffect(() => {
isProcessingRef.current = false;
if (messageQueueRef.current.length > 0 && !timeoutRef.current) {
timeoutRef.current = setTimeout(processQueue, 50);
}
}, [consoleMessages, processQueue]);
useEffect(() => {
const handleConsoleLog = (payload: ConsoleLogPayload) => {
let content = payload.content;
@@ -149,6 +161,7 @@ export function useConsoleMessages(): UseConsoleMessagesReturn {
timeoutRef.current = null;
}
messageQueueRef.current = [];
isProcessingRef.current = true;
startTransition(() => {
dispatch({ type: 'CLEAR' });
});