@@ -96,6 +96,17 @@ const AgentStorePage = () => {
96
96
}
97
97
return await response . json ( )
98
98
} ,
99
+ select : ( data ) => {
100
+ // Normalize data once to prevent reference changes and precompute expensive operations
101
+ return data . map ( ( agent ) => ( {
102
+ ...agent ,
103
+ // Precompute expensive operations
104
+ createdAtMs : new Date ( agent . created_at ) . getTime ( ) ,
105
+ nameLower : agent . name . toLowerCase ( ) ,
106
+ descriptionLower : agent . description ?. toLowerCase ( ) || '' ,
107
+ tagsLower : agent . tags ?. map ( ( tag ) => tag . toLowerCase ( ) ) || [ ] ,
108
+ } ) )
109
+ } ,
99
110
} )
100
111
101
112
// Fetch user's publishers if signed in
@@ -158,22 +169,17 @@ const AgentStorePage = () => {
158
169
} )
159
170
} , [ editorsChoice , searchQuery ] )
160
171
161
- // Helper function to get agents for a specific row
172
+ // Get agents for a specific row without pre-building all rows
162
173
const getAgentsForRow = useCallback (
163
- ( agents : AgentData [ ] , rowIndex : number , cols : number ) => {
164
- const startIndex = rowIndex * cols
165
- return agents . slice ( startIndex , startIndex + cols )
174
+ ( rowIndex : number ) => {
175
+ const startIndex = rowIndex * columns
176
+ return filteredAndSortedAgents . slice ( startIndex , startIndex + columns )
166
177
} ,
167
- [ ]
178
+ [ filteredAndSortedAgents , columns ]
168
179
)
169
180
170
- // Create virtualized rows for All Agents only
171
- const allAgentsRows = useMemo ( ( ) => {
172
- const rowCount = Math . ceil ( filteredAndSortedAgents . length / columns )
173
- return Array . from ( { length : rowCount } , ( _ , i ) =>
174
- getAgentsForRow ( filteredAndSortedAgents , i , columns )
175
- )
176
- } , [ filteredAndSortedAgents , columns , getAgentsForRow ] )
181
+ // Calculate total rows needed
182
+ const totalRows = Math . ceil ( filteredAndSortedAgents . length / columns )
177
183
178
184
// Only create virtualizer when we have data and the component is mounted
179
185
const [ isMounted , setIsMounted ] = useState ( false )
@@ -184,14 +190,13 @@ const AgentStorePage = () => {
184
190
185
191
// Virtualizer for All Agents section only
186
192
const allAgentsVirtualizer = useWindowVirtualizer ( {
187
- count : isMounted ? allAgentsRows . length : 0 ,
193
+ count : isMounted ? totalRows : 0 ,
188
194
estimateSize : ( ) => 270 , // Height for agent rows (card + gap)
189
195
overscan : 6 ,
190
- useAnimationFrameWithResizeObserver : true ,
191
196
} )
192
197
193
198
// Determine if we should use virtualization for All Agents section
194
- const shouldVirtualizeAllAgents = isMounted && allAgentsRows . length > 6
199
+ const shouldVirtualizeAllAgents = isMounted && totalRows > 6
195
200
196
201
// Publisher button logic
197
202
const renderPublisherButton = ( ) => {
@@ -235,7 +240,6 @@ const AgentStorePage = () => {
235
240
return count . toString ( )
236
241
}
237
242
238
- // Memoized AgentCard component to prevent unnecessary re-renders
239
243
const AgentCard = memo (
240
244
( {
241
245
agent,
@@ -251,10 +255,14 @@ const AgentStorePage = () => {
251
255
>
252
256
< Card
253
257
className = { cn (
254
- 'relative h-full transition-all duration-200 cursor-pointer border bg-card/50 backdrop-blur-sm ' ,
258
+ 'relative h-full transition-all duration-200 cursor-pointer border bg-card/50' ,
255
259
'hover:border-accent/50 hover:bg-card/80' ,
256
260
isEditorsChoice && 'ring-2 ring-amber-400/50 border-amber-400/30'
257
261
) }
262
+ style = { {
263
+ // Use CSS transforms for hover effects instead of Framer Motion
264
+ transition : 'all 0.2s ease' ,
265
+ } }
258
266
>
259
267
{ /* Editor's Choice Badge - Positioned absolutely for better visual hierarchy */ }
260
268
{ isEditorsChoice && (
@@ -285,12 +293,9 @@ const AgentStorePage = () => {
285
293
v{ agent . version }
286
294
</ Badge >
287
295
</ div >
288
- { /* Action buttons */ }
289
296
< div className = "flex items-center gap-1" >
290
297
< div onClick = { ( e ) => e . preventDefault ( ) } >
291
- < motion . button
292
- whileHover = { { scale : 1.1 } }
293
- whileTap = { { scale : 0.95 } }
298
+ < button
294
299
onClick = { ( ) => {
295
300
navigator . clipboard . writeText (
296
301
`codebuff --agent ${ agent . publisher . id } /${ agent . id } @${ agent . version } `
@@ -299,11 +304,11 @@ const AgentStorePage = () => {
299
304
description : `Agent run command copied to clipboard!` ,
300
305
} )
301
306
} }
302
- className = "p-2 hover:bg-muted/50 rounded-lg transition-all duration-200 opacity-60 group-hover:opacity-100"
307
+ className = "p-2 hover:bg-muted/50 rounded-lg transition-all duration-200 opacity-60 group-hover:opacity-100 hover:scale-110 active:scale-95 "
303
308
title = { `Copy: codebuff --agent ${ agent . publisher . id } /${ agent . id } @${ agent . version } ` }
304
309
>
305
310
< Copy className = "h-4 w-4 text-muted-foreground hover:text-foreground" />
306
- </ motion . button >
311
+ </ button >
307
312
</ div >
308
313
< ChevronRight className = "h-5 w-5 text-muted-foreground transition-all duration-300 group-hover:text-primary group-hover:translate-x-1" />
309
314
</ div >
@@ -511,9 +516,9 @@ const AgentStorePage = () => {
511
516
</ p >
512
517
</ div >
513
518
514
- { /* Non-virtualized Editor's Choice */ }
515
519
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
516
520
{ filteredEditorsChoice . map ( ( agent ) => (
521
+ // Only use motion for small, non-virtualized sections
517
522
< motion . div
518
523
key = { agent . id }
519
524
whileHover = { { y : - 4 , transition : { duration : 0.2 } } }
@@ -547,7 +552,7 @@ const AgentStorePage = () => {
547
552
{ allAgentsVirtualizer
548
553
. getVirtualItems ( )
549
554
. map ( ( virtualItem ) => {
550
- const agents = allAgentsRows [ virtualItem . index ]
555
+ const agents = getAgentsForRow ( virtualItem . index )
551
556
return (
552
557
< div
553
558
key = { virtualItem . key }
@@ -562,20 +567,18 @@ const AgentStorePage = () => {
562
567
>
563
568
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6" >
564
569
{ agents ?. map ( ( agent ) => (
565
- < motion . div
570
+ // No motion for virtualized items - use CSS transitions instead
571
+ < div
566
572
key = { agent . id }
567
- whileHover = { {
568
- y : - 4 ,
569
- transition : { duration : 0.2 } ,
570
- } }
573
+ className = "hover:-translate-y-1 transition-transform duration-200"
571
574
>
572
575
< AgentCard
573
576
agent = { agent }
574
577
isEditorsChoice = { EDITORS_CHOICE_AGENTS . includes (
575
578
agent . id
576
579
) }
577
580
/>
578
- </ motion . div >
581
+ </ div >
579
582
) ) }
580
583
</ div >
581
584
</ div >
@@ -586,17 +589,17 @@ const AgentStorePage = () => {
586
589
// Non-virtualized All Agents
587
590
< div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
588
591
{ filteredAndSortedAgents . map ( ( agent ) => (
589
- < motion . div
592
+ < div
590
593
key = { agent . id }
591
- whileHover = { { y : - 4 , transition : { duration : 0.2 } } }
594
+ className = "hover:-translate-y-1 transition-transform duration-200"
592
595
>
593
596
< AgentCard
594
597
agent = { agent }
595
598
isEditorsChoice = { EDITORS_CHOICE_AGENTS . includes (
596
599
agent . id
597
600
) }
598
601
/>
599
- </ motion . div >
602
+ </ div >
600
603
) ) }
601
604
</ div >
602
605
) }
0 commit comments