Skip to content

Commit acd51c5

Browse files
samhvw8daniel-lxs
andauthored
fix(webview): resolve memory leak in ChatView by stabilizing callback props (#3926)
* fix(webview): resolve memory leak in ChatView by stabilizing callback props - Stabilize handleSendMessage using clineAskRef to prevent frequent re-creation - Stabilize toggleRowExpansion by extracting handleSetExpandedRow and managing dependencies - Re-integrate scrolling logic into useEffect hook to avoid destabilizing callbacks - Add everVisibleMessagesTsRef to reduce unnecessary ChatRow remounts by Virtuoso - Update onToggleExpand signature to accept timestamp parameter for better stability - Remove diagnostic console.log statements used for debugging callback changes These changes address detached DOM elements memory leak caused by frequent callback re-creation triggering unnecessary component re-renders and preventing proper garbage collection of chat message DOM nodes. * comment correct TTL --------- Co-authored-by: Daniel <[email protected]>
1 parent dde71d2 commit acd51c5

File tree

4 files changed

+151
-106
lines changed

4 files changed

+151
-106
lines changed

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"i18next": "^24.2.2",
4848
"i18next-http-backend": "^3.0.2",
4949
"knuth-shuffle-seeded": "^1.0.6",
50+
"lru-cache": "^11.1.0",
5051
"lucide-react": "^0.510.0",
5152
"mermaid": "^11.4.1",
5253
"posthog-js": "^1.227.2",
@@ -70,8 +71,8 @@
7071
"tailwindcss": "^4.0.0",
7172
"tailwindcss-animate": "^1.0.7",
7273
"unist-util-visit": "^5.0.0",
73-
"vscode-material-icons": "^0.1.1",
7474
"use-sound": "^5.0.0",
75+
"vscode-material-icons": "^0.1.1",
7576
"vscrui": "^0.2.2",
7677
"zod": "^3.24.2"
7778
},

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { memo, useEffect, useMemo, useRef, useState } from "react"
1+
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
22
import { useSize } from "react-use"
33
import { useTranslation, Trans } from "react-i18next"
44
import deepEqual from "fast-deep-equal"
@@ -44,7 +44,7 @@ interface ChatRowProps {
4444
isExpanded: boolean
4545
isLast: boolean
4646
isStreaming: boolean
47-
onToggleExpand: () => void
47+
onToggleExpand: (ts: number) => void
4848
onHeightChange: (isTaller: boolean) => void
4949
onSuggestionClick?: (answer: string, event?: React.MouseEvent) => void
5050
}
@@ -103,6 +103,11 @@ export const ChatRowContent = ({
103103
const [showCopySuccess, setShowCopySuccess] = useState(false)
104104
const { copyWithFeedback } = useCopyToClipboard()
105105

106+
// Memoized callback to prevent re-renders caused by inline arrow functions
107+
const handleToggleExpand = useCallback(() => {
108+
onToggleExpand(message.ts)
109+
}, [onToggleExpand, message.ts])
110+
106111
const [cost, apiReqCancelReason, apiReqStreamingFailedMessage] = useMemo(() => {
107112
if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
108113
const info = safeJsonParse<ClineApiReqInfo>(message.text)
@@ -302,7 +307,7 @@ export const ChatRowContent = ({
302307
progressStatus={message.progressStatus}
303308
isLoading={message.partial}
304309
isExpanded={isExpanded}
305-
onToggleExpand={onToggleExpand}
310+
onToggleExpand={handleToggleExpand}
306311
/>
307312
</>
308313
)
@@ -328,7 +333,7 @@ export const ChatRowContent = ({
328333
progressStatus={message.progressStatus}
329334
isLoading={message.partial}
330335
isExpanded={isExpanded}
331-
onToggleExpand={onToggleExpand}
336+
onToggleExpand={handleToggleExpand}
332337
/>
333338
</>
334339
)
@@ -350,7 +355,7 @@ export const ChatRowContent = ({
350355
progressStatus={message.progressStatus}
351356
isLoading={message.partial}
352357
isExpanded={isExpanded}
353-
onToggleExpand={onToggleExpand}
358+
onToggleExpand={handleToggleExpand}
354359
/>
355360
</>
356361
)
@@ -389,7 +394,7 @@ export const ChatRowContent = ({
389394
language={getLanguageFromPath(tool.path || "") || "log"}
390395
isLoading={message.partial}
391396
isExpanded={isExpanded}
392-
onToggleExpand={onToggleExpand}
397+
onToggleExpand={handleToggleExpand}
393398
/>
394399
</>
395400
)
@@ -435,7 +440,7 @@ export const ChatRowContent = ({
435440
language="markdown"
436441
isLoading={message.partial}
437442
isExpanded={isExpanded}
438-
onToggleExpand={onToggleExpand}
443+
onToggleExpand={handleToggleExpand}
439444
/>
440445
</>
441446
)
@@ -455,7 +460,7 @@ export const ChatRowContent = ({
455460
code={tool.content}
456461
language="shell-session"
457462
isExpanded={isExpanded}
458-
onToggleExpand={onToggleExpand}
463+
onToggleExpand={handleToggleExpand}
459464
/>
460465
</>
461466
)
@@ -475,7 +480,7 @@ export const ChatRowContent = ({
475480
code={tool.content}
476481
language="shellsession"
477482
isExpanded={isExpanded}
478-
onToggleExpand={onToggleExpand}
483+
onToggleExpand={handleToggleExpand}
479484
/>
480485
</>
481486
)
@@ -495,7 +500,7 @@ export const ChatRowContent = ({
495500
code={tool.content}
496501
language="markdown"
497502
isExpanded={isExpanded}
498-
onToggleExpand={onToggleExpand}
503+
onToggleExpand={handleToggleExpand}
499504
/>
500505
</>
501506
)
@@ -525,7 +530,7 @@ export const ChatRowContent = ({
525530
code={tool.content}
526531
language="shellsession"
527532
isExpanded={isExpanded}
528-
onToggleExpand={onToggleExpand}
533+
onToggleExpand={handleToggleExpand}
529534
/>
530535
</>
531536
)
@@ -813,7 +818,7 @@ export const ChatRowContent = ({
813818
MozUserSelect: "none",
814819
msUserSelect: "none",
815820
}}
816-
onClick={onToggleExpand}>
821+
onClick={handleToggleExpand}>
817822
<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
818823
{icon}
819824
{title}
@@ -852,7 +857,7 @@ export const ChatRowContent = ({
852857
code={safeJsonParse<any>(message.text)?.request}
853858
language="markdown"
854859
isExpanded={true}
855-
onToggleExpand={onToggleExpand}
860+
onToggleExpand={handleToggleExpand}
856861
/>
857862
</div>
858863
)}
@@ -898,7 +903,7 @@ export const ChatRowContent = ({
898903
language="diff"
899904
isFeedback={true}
900905
isExpanded={isExpanded}
901-
onToggleExpand={onToggleExpand}
906+
onToggleExpand={handleToggleExpand}
902907
/>
903908
</div>
904909
)
@@ -945,7 +950,7 @@ export const ChatRowContent = ({
945950
code={message.text}
946951
language="json"
947952
isExpanded={true}
948-
onToggleExpand={onToggleExpand}
953+
onToggleExpand={handleToggleExpand}
949954
/>
950955
</div>
951956
</>
@@ -1105,7 +1110,7 @@ export const ChatRowContent = ({
11051110
code={useMcpServer.arguments}
11061111
language="json"
11071112
isExpanded={true}
1108-
onToggleExpand={onToggleExpand}
1113+
onToggleExpand={handleToggleExpand}
11091114
/>
11101115
</div>
11111116
)}

0 commit comments

Comments
 (0)