From 1f0ab5e5dcc8afec187295cf7e1a9eb415b09f11 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sat, 26 Jul 2025 02:31:13 +0000 Subject: [PATCH] fix: add loading screen and retry logic to prevent webview lockup on startup - Created LoadingScreen component to show proper loading state instead of blank screen - Added timeout handling to show message when loading takes longer than expected - Implemented retry logic in App and ExtensionStateContext to resend initialization message - Changed App to render LoadingScreen instead of null when waiting for state hydration This fixes the issue where the webview would show a blank screen indefinitely if the extension was slow to respond with the initial state. Fixes #6234 --- webview-ui/src/App.tsx | 25 ++++++++++++++++++- .../src/components/common/LoadingScreen.tsx | 24 ++++++++++++++++++ .../src/context/ExtensionStateContext.tsx | 13 +++++++++- 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 webview-ui/src/components/common/LoadingScreen.tsx diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 3782242707c..e3ee95e9dc1 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -25,6 +25,7 @@ import { AccountView } from "./components/account/AccountView" import { useAddNonInteractiveClickListener } from "./components/ui/hooks/useNonInteractiveClick" import { TooltipProvider } from "./components/ui/tooltip" import { STANDARD_TOOLTIP_DELAY } from "./components/ui/standard-tooltip" +import { LoadingScreen } from "./components/common/LoadingScreen" type Tab = "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "account" @@ -81,6 +82,7 @@ const App = () => { const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") + const [showTimeout, setShowTimeout] = useState(false) const [humanRelayDialogState, setHumanRelayDialogState] = useState({ isOpen: false, @@ -207,6 +209,27 @@ const App = () => { console.debug("App initialized with source map support") }, []) + // Add timeout handling for extension state + useEffect(() => { + if (!didHydrateState) { + // Show timeout message after 5 seconds + const timeoutTimer = setTimeout(() => { + setShowTimeout(true) + }, 5000) + + // Retry sending webviewDidLaunch after 3 seconds + const retryTimer = setTimeout(() => { + console.debug("Retrying webviewDidLaunch message...") + vscode.postMessage({ type: "webviewDidLaunch" }) + }, 3000) + + return () => { + clearTimeout(timeoutTimer) + clearTimeout(retryTimer) + } + } + }, [didHydrateState]) + // Focus the WebView when non-interactive content is clicked (only in editor/tab mode) useAddNonInteractiveClickListener( useCallback(() => { @@ -224,7 +247,7 @@ const App = () => { }, [tab]) if (!didHydrateState) { - return null + return } // Do not conditionally load ChatView, it's expensive and there's state we diff --git a/webview-ui/src/components/common/LoadingScreen.tsx b/webview-ui/src/components/common/LoadingScreen.tsx new file mode 100644 index 00000000000..89c649a865a --- /dev/null +++ b/webview-ui/src/components/common/LoadingScreen.tsx @@ -0,0 +1,24 @@ +import React from "react" +import { VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react" + +interface LoadingScreenProps { + message?: string + showTimeout?: boolean +} + +export const LoadingScreen: React.FC = ({ + message = "Loading Roo Code...", + showTimeout = false, +}) => { + return ( +
+
+ +

{message}

+ {showTimeout && ( +

Taking longer than expected...

+ )} +
+
+ ) +} diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index ff1ce31c53c..a97703dc40d 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -360,8 +360,19 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode }, [handleMessage]) useEffect(() => { + // Send initial launch message vscode.postMessage({ type: "webviewDidLaunch" }) - }, []) + + // If we don't get a response within 2 seconds, try again + const retryTimer = setTimeout(() => { + if (!didHydrateState) { + console.warn("No state received from extension, retrying webviewDidLaunch...") + vscode.postMessage({ type: "webviewDidLaunch" }) + } + }, 2000) + + return () => clearTimeout(retryTimer) + }, [didHydrateState]) const contextValue: ExtensionStateContextType = { ...state,