Skip to content

Commit 1ce7380

Browse files
committed
Try to improve agent store list
1 parent 04bdbcb commit 1ce7380

File tree

1 file changed

+72
-38
lines changed

1 file changed

+72
-38
lines changed

web/src/app/store/page.tsx

Lines changed: 72 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useMemo, useCallback, memo, useEffect } from 'react'
3+
import { useState, useMemo, useCallback, memo, useEffect, useRef } from 'react'
44
import { useQuery } from '@tanstack/react-query'
55
import { useSession } from 'next-auth/react'
66
import { motion } from 'framer-motion'
@@ -183,18 +183,76 @@ const AgentStorePage = () => {
183183

184184
// Only create virtualizer when we have data and the component is mounted
185185
const [isMounted, setIsMounted] = useState(false)
186+
const measurementCache = useRef<Map<number, number>>(new Map())
186187

187188
useEffect(() => {
188189
setIsMounted(true)
189190
}, [])
190191

192+
// Dynamic overscan based on device/viewport
193+
const getOverscan = () => {
194+
if (typeof window === 'undefined') return 6
195+
const isMobile = window.innerWidth < 768
196+
const isTouchDevice = 'ontouchstart' in window
197+
return isMobile || isTouchDevice ? 15 : 8
198+
}
199+
200+
// Dynamic height estimation based on columns
201+
const getEstimatedSize = () => {
202+
// Base card height + gap between rows
203+
const baseCardHeight = 240 // More conservative estimate
204+
const rowGap = 24 // gap-6 = 24px
205+
return baseCardHeight + rowGap
206+
}
207+
191208
// Virtualizer for All Agents section only
192209
const allAgentsVirtualizer = useWindowVirtualizer({
193210
count: isMounted ? totalRows : 0,
194-
estimateSize: () => 270, // Height for agent rows (card + gap)
195-
overscan: 6,
211+
estimateSize: getEstimatedSize,
212+
overscan: getOverscan(),
213+
measureElement: (element) => {
214+
// Cache measurements for better performance
215+
const height =
216+
element?.getBoundingClientRect().height ?? getEstimatedSize()
217+
return height
218+
},
196219
})
197220

221+
// Remeasure when columns change or data changes significantly
222+
useEffect(() => {
223+
if (allAgentsVirtualizer && isMounted) {
224+
// Clear cache and remeasure when layout changes
225+
measurementCache.current.clear()
226+
allAgentsVirtualizer.measure()
227+
}
228+
}, [columns, filteredAndSortedAgents.length, allAgentsVirtualizer, isMounted])
229+
230+
// Handle viewport/orientation changes
231+
useEffect(() => {
232+
if (!isMounted || !allAgentsVirtualizer) return
233+
234+
const handleResize = () => {
235+
// Debounce resize events
236+
measurementCache.current.clear()
237+
allAgentsVirtualizer.measure()
238+
}
239+
240+
let resizeTimeout: NodeJS.Timeout
241+
const debouncedResize = () => {
242+
clearTimeout(resizeTimeout)
243+
resizeTimeout = setTimeout(handleResize, 150)
244+
}
245+
246+
window.addEventListener('resize', debouncedResize)
247+
window.addEventListener('orientationchange', debouncedResize)
248+
249+
return () => {
250+
window.removeEventListener('resize', debouncedResize)
251+
window.removeEventListener('orientationchange', debouncedResize)
252+
clearTimeout(resizeTimeout)
253+
}
254+
}, [allAgentsVirtualizer, isMounted])
255+
198256
// Determine if we should use virtualization for All Agents section
199257
const shouldVirtualizeAllAgents = isMounted && totalRows > 6
200258

@@ -257,7 +315,9 @@ const AgentStorePage = () => {
257315
className={cn(
258316
'relative h-full transition-all duration-200 cursor-pointer border bg-card/50',
259317
'hover:border-accent/50 hover:bg-card/80',
260-
isEditorsChoice && 'ring-2 ring-amber-400/50 border-amber-400/30'
318+
isEditorsChoice && 'ring-2 ring-amber-400/50 border-amber-400/30',
319+
// Ensure consistent minimum height to reduce layout shifts
320+
'min-h-[220px]'
261321
)}
262322
style={{
263323
// Use CSS transforms for hover effects instead of Framer Motion
@@ -286,12 +346,6 @@ const AgentStorePage = () => {
286346
<h3 className="text-xl font-bold font-mono text-foreground truncate group-hover:text-primary transition-colors">
287347
{agent.id}
288348
</h3>
289-
<Badge
290-
variant="outline"
291-
className="text-xs font-mono px-2 py-1 border-border/50 bg-muted/30 shrink-0"
292-
>
293-
v{agent.version}
294-
</Badge>
295349
</div>
296350
<div className="flex items-center gap-1">
297351
<div onClick={(e) => e.preventDefault()}>
@@ -304,7 +358,7 @@ const AgentStorePage = () => {
304358
description: `Agent run command copied to clipboard!`,
305359
})
306360
}}
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"
361+
className="hidden md:flex p-2 hover:bg-muted/50 rounded-lg transition-all duration-200 opacity-60 group-hover:opacity-100 hover:scale-110 active:scale-95"
308362
title={`Copy: codebuff --agent ${agent.publisher.id}/${agent.id}@${agent.version}`}
309363
>
310364
<Copy className="h-4 w-4 text-muted-foreground hover:text-foreground" />
@@ -398,31 +452,6 @@ const AgentStorePage = () => {
398452
<p className="text-xs text-muted-foreground">Users</p>
399453
</div>
400454
</div>
401-
402-
{/* Tags - Improved design and spacing */}
403-
{agent.tags && agent.tags.length > 0 && (
404-
<div className="pt-2">
405-
<div className="flex flex-wrap gap-1.5">
406-
{agent.tags.slice(0, 4).map((tag) => (
407-
<Badge
408-
key={tag}
409-
variant="secondary"
410-
className="text-xs px-2.5 py-1 bg-muted/40 hover:bg-muted/60 transition-colors border-0 rounded-full"
411-
>
412-
{tag}
413-
</Badge>
414-
))}
415-
{agent.tags.length > 4 && (
416-
<Badge
417-
variant="secondary"
418-
className="text-xs px-2.5 py-1 bg-muted/40 border-0 rounded-full opacity-60"
419-
>
420-
+{agent.tags.length - 4}
421-
</Badge>
422-
)}
423-
</div>
424-
</div>
425-
)}
426455
</CardContent>
427456
</Card>
428457
</Link>
@@ -556,16 +585,21 @@ const AgentStorePage = () => {
556585
return (
557586
<div
558587
key={virtualItem.key}
588+
ref={(node) =>
589+
allAgentsVirtualizer.measureElement(node)
590+
}
591+
data-index={virtualItem.index}
559592
style={{
560593
position: 'absolute',
561594
top: 0,
562595
left: 0,
563596
width: '100%',
564-
height: `${virtualItem.size}px`,
565597
transform: `translateY(${virtualItem.start}px)`,
598+
// Include padding/margin in measured element
599+
paddingBottom: '24px',
566600
}}
567601
>
568-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-6">
602+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
569603
{agents?.map((agent) => (
570604
// No motion for virtualized items - use CSS transitions instead
571605
<div

0 commit comments

Comments
 (0)