Skip to content

Commit 19e4387

Browse files
pashpashpashCline Evaluation
andauthored
Optimizing memory management for task timeline via virtuoso (RooCodeInc#3545)
* optimizing memory management for task timeline via virtuoso * removing logs --------- Co-authored-by: Cline Evaluation <[email protected]>
1 parent ab01a51 commit 19e4387

File tree

2 files changed

+78
-40
lines changed

2 files changed

+78
-40
lines changed

.changeset/afraid-jobs-sing.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Optimized memory management for task timeline via virtuoso

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

Lines changed: 73 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import React, { useMemo, useState, useRef, useEffect } from "react"
1+
import React, { useMemo, useState, useRef, useEffect, useCallback } from "react"
2+
import { Virtuoso } from "react-virtuoso"
23
import { ClineMessage } from "@shared/ExtensionMessage"
34
import { combineApiRequests } from "@shared/combineApiRequests"
45
import { combineCommandSequences } from "@shared/combineCommandSequences"
@@ -136,11 +137,7 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
136137
}
137138
}, [taskTimelinePropsMessages])
138139

139-
if (taskTimelinePropsMessages.length === 0) {
140-
return null
141-
}
142-
143-
const handleMouseEnter = (message: ClineMessage, event: React.MouseEvent<HTMLDivElement>) => {
140+
const handleMouseEnter = useCallback((message: ClineMessage, event: React.MouseEvent<HTMLDivElement>) => {
144141
setHoveredMessage(message)
145142

146143
const viewportWidth = window.innerWidth
@@ -150,11 +147,56 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
150147
const x = TOOLTIP_MARGIN
151148

152149
setTooltipPosition({ x, y: event.clientY })
153-
}
150+
}, [])
154151

155-
const handleMouseLeave = () => {
152+
const handleMouseLeave = useCallback(() => {
156153
setHoveredMessage(null)
157154
setTooltipPosition(null)
155+
}, [])
156+
157+
// Calculate the item size (width of block + gap)
158+
const itemWidth = parseInt(BLOCK_WIDTH.replace("px", "")) + parseInt(BLOCK_GAP.replace("px", ""))
159+
160+
// Size function for Virtuoso
161+
const itemSize = useCallback(() => itemWidth, [itemWidth])
162+
163+
// Virtuoso requires a reference to scroll to the end
164+
const virtuosoRef = useRef<any>(null)
165+
166+
// Render a timeline block
167+
const TimelineBlock = useCallback(
168+
(index: number) => {
169+
const message = taskTimelinePropsMessages[index]
170+
return (
171+
<div
172+
style={{
173+
width: BLOCK_WIDTH,
174+
height: "100%",
175+
backgroundColor: getBlockColor(message),
176+
flexShrink: 0,
177+
cursor: "pointer",
178+
marginRight: BLOCK_GAP,
179+
}}
180+
onMouseEnter={(e) => handleMouseEnter(message, e)}
181+
onMouseLeave={handleMouseLeave}
182+
/>
183+
)
184+
},
185+
[taskTimelinePropsMessages, handleMouseEnter, handleMouseLeave],
186+
)
187+
188+
// Scroll to the end when messages change
189+
useEffect(() => {
190+
if (virtuosoRef.current && taskTimelinePropsMessages.length > 0) {
191+
virtuosoRef.current.scrollToIndex({
192+
index: taskTimelinePropsMessages.length - 1,
193+
align: "end",
194+
})
195+
}
196+
}, [taskTimelinePropsMessages])
197+
198+
if (taskTimelinePropsMessages.length === 0) {
199+
return null
158200
}
159201

160202
return (
@@ -167,41 +209,32 @@ const TaskTimeline: React.FC<TaskTimelineProps> = ({ messages }) => {
167209
marginBottom: "4px",
168210
overflow: "hidden",
169211
}}>
170-
<div
171-
ref={scrollableRef}
212+
<style>
213+
{`
214+
/* Hide scrollbar for Chrome, Safari and Opera */
215+
.timeline-virtuoso::-webkit-scrollbar {
216+
display: none;
217+
}
218+
.timeline-virtuoso {
219+
scrollbar-width: none;
220+
-ms-overflow-style: none;
221+
}
222+
`}
223+
</style>
224+
225+
<Virtuoso
226+
ref={virtuosoRef}
227+
className="timeline-virtuoso"
172228
style={{
173-
display: "flex",
174229
height: TIMELINE_HEIGHT,
175-
overflowX: "auto",
176-
scrollbarWidth: "none",
177-
msOverflowStyle: "none",
178230
width: "100%",
179-
WebkitOverflowScrolling: "touch",
180-
gap: BLOCK_GAP, // Using flexbox gap instead of marginRight
181-
}}>
182-
<style>
183-
{`
184-
/* Hide scrollbar for Chrome, Safari and Opera */
185-
div::-webkit-scrollbar {
186-
display: none;
187-
}
188-
`}
189-
</style>
190-
{taskTimelinePropsMessages.map((message, index) => (
191-
<div
192-
key={index}
193-
style={{
194-
width: BLOCK_WIDTH,
195-
height: "100%",
196-
backgroundColor: getBlockColor(message),
197-
flexShrink: 0,
198-
cursor: "pointer",
199-
}}
200-
onMouseEnter={(e) => handleMouseEnter(message, e)}
201-
onMouseLeave={handleMouseLeave}
202-
/>
203-
))}
204-
</div>
231+
}}
232+
totalCount={taskTimelinePropsMessages.length}
233+
itemContent={TimelineBlock}
234+
horizontalDirection={true}
235+
increaseViewportBy={12}
236+
fixedItemHeight={itemWidth}
237+
/>
205238

206239
{hoveredMessage && containerRef.current && tooltipPosition && (
207240
<div

0 commit comments

Comments
 (0)