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
This commit is contained in:
Roo Code 2025-07-26 02:31:13 +00:00
parent 0504199ce4
commit 1f0ab5e5dc
3 changed files with 60 additions and 2 deletions

View file

@ -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<Tab>("chat")
const [showTimeout, setShowTimeout] = useState(false)
const [humanRelayDialogState, setHumanRelayDialogState] = useState<HumanRelayDialogState>({
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 <LoadingScreen showTimeout={showTimeout} />
}
// Do not conditionally load ChatView, it's expensive and there's state we

View file

@ -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<LoadingScreenProps> = ({
message = "Loading Roo Code...",
showTimeout = false,
}) => {
return (
<div className="fixed inset-0 flex flex-col items-center justify-center bg-vscode-editor-background">
<div className="flex flex-col items-center gap-4">
<VSCodeProgressRing />
<p className="text-vscode-foreground text-sm">{message}</p>
{showTimeout && (
<p className="text-vscode-descriptionForeground text-xs mt-2">Taking longer than expected...</p>
)}
</div>
</div>
)
}

View file

@ -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,