Skip to content

Commit 60910c4

Browse files
committed
fix: resolve gray screen of death issue during long chats (#7165)
- Enhanced ErrorBoundary with recovery mechanism and auto-retry - Added proper error state management and recovery buttons - Improved memory management in ChatView with better LRUCache settings - Added periodic memory cleanup for very long chat sessions - Implemented error handling for Virtuoso virtual scrolling component - Added fallback UI for scroll rendering failures - Optimized viewport settings to reduce memory usage - Added error boundaries around critical rendering components This fix addresses the gray screen issue that occurs during extended chat sessions by: 1. Preventing the error boundary from showing a gray overlay 2. Adding automatic recovery mechanisms 3. Improving memory management to prevent crashes 4. Providing user-friendly recovery options when errors occur
1 parent 185365a commit 60910c4

File tree

2 files changed

+304
-61
lines changed

2 files changed

+304
-61
lines changed

webview-ui/src/components/ErrorBoundary.tsx

Lines changed: 107 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { Component } from "react"
22
import { telemetryClient } from "@src/utils/TelemetryClient"
33
import { withTranslation, WithTranslation } from "react-i18next"
44
import { enhanceErrorWithSourceMaps } from "@src/utils/sourceMapUtils"
5+
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
56

67
type ErrorProps = {
78
children: React.ReactNode
@@ -11,12 +12,19 @@ type ErrorState = {
1112
error?: string
1213
componentStack?: string | null
1314
timestamp?: number
15+
hasError: boolean
16+
errorCount: number
1417
}
1518

1619
class ErrorBoundary extends Component<ErrorProps, ErrorState> {
20+
private retryTimeoutId: NodeJS.Timeout | null = null
21+
1722
constructor(props: ErrorProps) {
1823
super(props)
19-
this.state = {}
24+
this.state = {
25+
hasError: false,
26+
errorCount: 0,
27+
}
2028
}
2129

2230
static getDerivedStateFromError(error: unknown) {
@@ -31,31 +39,71 @@ class ErrorBoundary extends Component<ErrorProps, ErrorState> {
3139
return {
3240
error: errorMessage,
3341
timestamp: Date.now(),
42+
hasError: true,
3443
}
3544
}
3645

3746
async componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
3847
const componentStack = errorInfo.componentStack || ""
3948
const enhancedError = await enhanceErrorWithSourceMaps(error, componentStack)
4049

50+
// Increment error count
51+
this.setState((prevState) => ({
52+
errorCount: prevState.errorCount + 1,
53+
}))
54+
4155
telemetryClient.capture("error_boundary_caught_error", {
4256
error: enhancedError.message,
4357
stack: enhancedError.sourceMappedStack || enhancedError.stack,
4458
componentStack: enhancedError.sourceMappedComponentStack || componentStack,
4559
timestamp: Date.now(),
4660
errorType: enhancedError.name,
61+
errorCount: this.state.errorCount + 1,
4762
})
4863

4964
this.setState({
5065
error: enhancedError.sourceMappedStack || enhancedError.stack,
5166
componentStack: enhancedError.sourceMappedComponentStack || componentStack,
5267
})
68+
69+
// Auto-retry after 5 seconds if this is the first error
70+
if (this.state.errorCount === 0 && !this.retryTimeoutId) {
71+
this.retryTimeoutId = setTimeout(() => {
72+
this.handleReset()
73+
}, 5000)
74+
}
75+
}
76+
77+
componentWillUnmount() {
78+
if (this.retryTimeoutId) {
79+
clearTimeout(this.retryTimeoutId)
80+
this.retryTimeoutId = null
81+
}
82+
}
83+
84+
handleReset = () => {
85+
if (this.retryTimeoutId) {
86+
clearTimeout(this.retryTimeoutId)
87+
this.retryTimeoutId = null
88+
}
89+
90+
this.setState({
91+
error: undefined,
92+
componentStack: undefined,
93+
timestamp: undefined,
94+
hasError: false,
95+
// Don't reset errorCount to track total errors in session
96+
})
97+
}
98+
99+
handleReload = () => {
100+
window.location.reload()
53101
}
54102

55103
render() {
56104
const { t } = this.props
57105

58-
if (!this.state.error) {
106+
if (!this.state.hasError || !this.state.error) {
59107
return this.props.children
60108
}
61109

@@ -64,30 +112,65 @@ class ErrorBoundary extends Component<ErrorProps, ErrorState> {
64112

65113
const version = process.env.PKG_VERSION || "unknown"
66114

115+
// Use a white background to ensure visibility and prevent gray screen
67116
return (
68-
<div>
69-
<h2 className="text-lg font-bold mt-0 mb-2">
70-
{t("errorBoundary.title")} (v{version})
71-
</h2>
72-
<p className="mb-4">
73-
{t("errorBoundary.reportText")}{" "}
74-
<a href="https://github.com/RooCodeInc/Roo-Code/issues" target="_blank" rel="noreferrer">
75-
{t("errorBoundary.githubText")}
76-
</a>
77-
</p>
78-
<p className="mb-2">{t("errorBoundary.copyInstructions")}</p>
79-
80-
<div className="mb-4">
81-
<h3 className="text-md font-bold mb-1">{t("errorBoundary.errorStack")}</h3>
82-
<pre className="p-2 border rounded text-sm overflow-auto">{errorDisplay}</pre>
83-
</div>
84-
85-
{componentStackDisplay && (
86-
<div>
87-
<h3 className="text-md font-bold mb-1">{t("errorBoundary.componentStack")}</h3>
88-
<pre className="p-2 border rounded text-sm overflow-auto">{componentStackDisplay}</pre>
117+
<div
118+
className="fixed inset-0 bg-vscode-editor-background overflow-auto p-4"
119+
style={{ backgroundColor: "var(--vscode-editor-background, white)", zIndex: 9999 }}>
120+
<div className="max-w-4xl mx-auto">
121+
<h2 className="text-lg font-bold mt-0 mb-2 text-vscode-editor-foreground">
122+
{t("errorBoundary.title")} (v{version})
123+
</h2>
124+
125+
{this.state.errorCount === 1 && (
126+
<div className="mb-4 p-3 bg-vscode-notifications-background border border-vscode-notifications-border rounded">
127+
<p className="text-vscode-notifications-foreground">
128+
The application will attempt to recover automatically in a few seconds...
129+
</p>
130+
</div>
131+
)}
132+
133+
<div className="flex gap-2 mb-4">
134+
<VSCodeButton appearance="primary" onClick={this.handleReset}>
135+
Try Again
136+
</VSCodeButton>
137+
<VSCodeButton appearance="secondary" onClick={this.handleReload}>
138+
Reload Window
139+
</VSCodeButton>
89140
</div>
90-
)}
141+
142+
<p className="mb-4 text-vscode-editor-foreground">
143+
{t("errorBoundary.reportText")}{" "}
144+
<a
145+
href="https://github.com/RooCodeInc/Roo-Code/issues"
146+
target="_blank"
147+
rel="noreferrer"
148+
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground">
149+
{t("errorBoundary.githubText")}
150+
</a>
151+
</p>
152+
<p className="mb-2 text-vscode-editor-foreground">{t("errorBoundary.copyInstructions")}</p>
153+
154+
<details className="mb-4">
155+
<summary className="cursor-pointer text-vscode-editor-foreground font-bold mb-2">
156+
{t("errorBoundary.errorStack")} (Click to expand)
157+
</summary>
158+
<pre className="p-2 border border-vscode-panel-border rounded text-sm overflow-auto bg-vscode-editor-background text-vscode-editor-foreground mt-2">
159+
{errorDisplay}
160+
</pre>
161+
</details>
162+
163+
{componentStackDisplay && (
164+
<details>
165+
<summary className="cursor-pointer text-vscode-editor-foreground font-bold mb-2">
166+
{t("errorBoundary.componentStack")} (Click to expand)
167+
</summary>
168+
<pre className="p-2 border border-vscode-panel-border rounded text-sm overflow-auto bg-vscode-editor-background text-vscode-editor-foreground mt-2">
169+
{componentStackDisplay}
170+
</pre>
171+
</details>
172+
)}
173+
</div>
91174
</div>
92175
)
93176
}

0 commit comments

Comments
 (0)