Skip to content

Commit 917bf6a

Browse files
Fix: Improve webview rendering robustness and error handling
This change addresses potential grey/blank screen or crashing issues in the webview by: 1. **Adding an ErrorBoundary:** I introduced an `ErrorBoundary` component at the root of `webview-ui/src/App.tsx`. This component catches JavaScript rendering errors in its children, displaying a user-friendly fallback UI instead of a blank or broken screen. It also logs the error to the console for easier debugging. 2. **Implementing Loading/Timeout Logic:** I modified `webview-ui/src/App.tsx` to: * Display a "Loading..." message while the initial extension state (`didHydrateState`) is being processed. * Implement a 10-second timeout. If state hydration doesn't complete within this period, an error message is shown, guiding you to try reloading or checking for updates. This provides better feedback than a persistent blank screen. 3. **Adding Unit Tests:** * I created tests for the `ErrorBoundary` to ensure it correctly catches errors and renders the fallback UI or child components as expected. * I created tests for the `App.tsx` loading and timeout logic to verify that loading indicators and error messages are displayed appropriately under different hydration scenarios. These changes aim to make the webview more resilient to rendering errors and provide better feedback to you during initialization, improving the overall user experience and aiding in diagnosing future issues.
1 parent ea93cea commit 917bf6a

File tree

4 files changed

+311
-203
lines changed

4 files changed

+311
-203
lines changed

webview-ui/src/App.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import TranslationProvider from "./i18n/TranslationContext"
88
import { vscode } from "./utils/vscode"
99
import { telemetryClient } from "./utils/TelemetryClient"
1010
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
11+
import ErrorBoundary from "./components/common/ErrorBoundary"
1112
import ChatView, { ChatViewRef } from "./components/chat/ChatView"
1213
import HistoryView from "./components/history/HistoryView"
1314
import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView"
@@ -30,6 +31,8 @@ const App = () => {
3031
const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
3132
useExtensionState()
3233

34+
const [isLoading, setIsLoading] = useState(true);
35+
const [loadingError, setLoadingError] = useState<string | null>(null);
3336
const [showAnnouncement, setShowAnnouncement] = useState(false)
3437
const [tab, setTab] = useState<Tab>("chat")
3538

@@ -102,16 +105,46 @@ const App = () => {
102105
// Tell the extension that we are ready to receive messages.
103106
useEffect(() => vscode.postMessage({ type: "webviewDidLaunch" }), [])
104107

105-
if (!didHydrateState) {
106-
return null
108+
useEffect(() => {
109+
if (didHydrateState) {
110+
setIsLoading(false);
111+
return;
112+
}
113+
114+
const timer = setTimeout(() => {
115+
if (!didHydrateState) {
116+
setLoadingError(
117+
"The extension is taking longer than expected to load. Please try reloading the VS Code window. If the issue persists, ensure your extension and VS Code are up to date."
118+
);
119+
}
120+
}, 10000); // 10 seconds timeout
121+
122+
return () => clearTimeout(timer);
123+
}, [didHydrateState]);
124+
125+
if (loadingError) {
126+
return (
127+
<div style={{ padding: '20px', textAlign: 'center' }}>
128+
<h1>Error</h1>
129+
<p>{loadingError}</p>
130+
</div>
131+
);
132+
}
133+
134+
if (isLoading) {
135+
return (
136+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
137+
<p>Loading...</p>
138+
</div>
139+
);
107140
}
108141

109142
// Do not conditionally load ChatView, it's expensive and there's state we
110143
// don't want to lose (user input, disableInput, askResponse promise, etc.)
111144
return showWelcome ? (
112145
<WelcomeView />
113146
) : (
114-
<>
147+
<ErrorBoundary>
115148
{tab === "prompts" && <PromptsView onDone={() => switchTab("chat")} />}
116149
{tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
117150
{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
@@ -132,7 +165,7 @@ const App = () => {
132165
onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })}
133166
onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })}
134167
/>
135-
</>
168+
</ErrorBoundary>
136169
)
137170
}
138171

0 commit comments

Comments
 (0)