diff --git a/src/screens/REPL.tsx b/src/screens/REPL.tsx index bcfd6a4d..4b15666b 100644 --- a/src/screens/REPL.tsx +++ b/src/screens/REPL.tsx @@ -793,20 +793,8 @@ export function REPL({ // accepts, and only then is the REPL component mounted and this effect runs. // This ensures that plugin installations from repository and user settings only // happen after explicit user consent to trust the current working directory. - // We defer startup checks by STARTUP_CHECK_DELAY_MS and gate on - // promptTypingSuppressionActive so that plugin loading doesn't steal focus - // from the prompt during the vulnerable startup window (issue #363). - const startupChecksStartedRef = React.useRef(false) - useEffect(() => { - if (isRemoteSession) return - if (startupChecksStartedRef.current) return - const timer = setTimeout(() => { - if (!shouldRunStartupChecks(isRemoteSession, startupChecksStartedRef.current, promptTypingSuppressionActive)) return - startupChecksStartedRef.current = true - void performStartupChecks(setAppState) - }, STARTUP_CHECK_DELAY_MS) - return () => clearTimeout(timer) - }, [setAppState, isRemoteSession, promptTypingSuppressionActive]) + // Deferring startup checks is handled below (after promptTypingSuppressionActive + // is declared) to avoid temporal dead zone issues. // Allow Claude in Chrome MCP to send prompts through MCP notifications // and sync permission mode changes to the Chrome extension @@ -1349,6 +1337,21 @@ export function REPL({ const inputValueRef = useRef(inputValue); inputValueRef.current = inputValue; const promptTypingSuppressionActive = isPromptTypingSuppressionActive(isPromptInputActive, inputValue); + + // Defer startup checks by STARTUP_CHECK_DELAY_MS and gate on + // promptTypingSuppressionActive so that plugin loading doesn't steal focus + // from the prompt during the vulnerable startup window (issue #363). + const startupChecksStartedRef = React.useRef(false); + useEffect(() => { + if (isRemoteSession) return; + if (startupChecksStartedRef.current) return; + const timer = setTimeout(() => { + if (!shouldRunStartupChecks(isRemoteSession, startupChecksStartedRef.current, promptTypingSuppressionActive)) return; + startupChecksStartedRef.current = true; + void performStartupChecks(setAppState); + }, STARTUP_CHECK_DELAY_MS); + return () => clearTimeout(timer); + }, [setAppState, isRemoteSession, promptTypingSuppressionActive]); const insertTextRef = useRef<{ insert: (text: string) => void; setInputWithCursor: (value: string, cursor: number) => void; @@ -2072,14 +2075,14 @@ export function REPL({ if (allowDialogsWithAnimation && showRemoteCallout) return 'remote-callout'; // LSP plugin recommendation (lowest priority - non-blocking suggestion) - // Suppress during startup window to prevent stealing focus from the prompt (issue #363) - if (allowDialogsWithAnimation && lspRecommendation && startupChecksStartedRef.current) return 'lsp-recommendation'; + // Suppress during startup window to prevent stealing focus from the prompt (issue #363) + if (allowDialogsWithAnimation && lspRecommendation && startupChecksStartedRef.current) return 'lsp-recommendation'; - // Plugin hint from CLI/SDK stderr (same priority band as LSP rec) - if (allowDialogsWithAnimation && hintRecommendation && startupChecksStartedRef.current) return 'plugin-hint'; + // Plugin hint from CLI/SDK stderr (same priority band as LSP rec) + if (allowDialogsWithAnimation && hintRecommendation && startupChecksStartedRef.current) return 'plugin-hint'; - // Desktop app upsell (max 3 launches, lowest priority) - if (allowDialogsWithAnimation && showDesktopUpsellStartup && startupChecksStartedRef.current) return 'desktop-upsell'; + // Desktop app upsell (max 3 launches, lowest priority) + if (allowDialogsWithAnimation && showDesktopUpsellStartup && startupChecksStartedRef.current) return 'desktop-upsell'; return undefined; } const focusedInputDialog = getFocusedInputDialog();