@@ -185,17 +185,56 @@ export function MemoryGraph({
185185 // Drag end handled by InputHandler
186186 } , [ ] )
187187
188+ // Load more when user zooms out enough that visible area dwarfs the node extent
189+ const loadMoreTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null )
190+ const loadMoreRef = useRef ( { hasMore, isLoadingMore, onLoadMore } )
191+ loadMoreRef . current = { hasMore, isLoadingMore, onLoadMore }
192+
188193 const handleViewportChange = useCallback (
189194 ( zoom : number , popoverVisible : boolean ) => {
190195 setZoomDisplay ( Math . round ( zoom * 100 ) )
191- // Only increment viewportVersion (which triggers popover repositioning
192- // via activePopoverPosition useMemo) when a popover is actually visible.
193- // This avoids 60fps React reconciliation during plain panning/zooming.
194196 if ( popoverVisible ) {
195197 setViewportVersion ( ( v ) => v + 1 )
196198 }
199+
200+ const { hasMore : more , isLoadingMore : loading , onLoadMore : load } = loadMoreRef . current
201+ if ( ! more || loading || ! load || ! viewportRef . current ) return
202+
203+ const vp = viewportRef . current
204+ const currentNodes = nodes
205+ if ( currentNodes . length === 0 ) return
206+
207+ const topLeft = vp . screenToWorld ( 0 , 0 )
208+ const bottomRight = vp . screenToWorld ( containerSize . width , containerSize . height )
209+ const viewW = bottomRight . x - topLeft . x
210+ const viewH = bottomRight . y - topLeft . y
211+
212+ let minX = Infinity
213+ let minY = Infinity
214+ let maxX = - Infinity
215+ let maxY = - Infinity
216+ for ( const n of currentNodes ) {
217+ if ( n . x < minX ) minX = n . x
218+ if ( n . y < minY ) minY = n . y
219+ if ( n . x > maxX ) maxX = n . x
220+ if ( n . y > maxY ) maxY = n . y
221+ }
222+
223+ const nodeW = maxX - minX || 1
224+ const nodeH = maxY - minY || 1
225+
226+ // Only trigger when the visible area is 3x larger than the node extent
227+ // (i.e. user has zoomed out significantly past the data)
228+ const zoomedOut = viewW > nodeW * 3 || viewH > nodeH * 3
229+
230+ if ( zoomedOut && ! loadMoreTimerRef . current ) {
231+ loadMoreTimerRef . current = setTimeout ( ( ) => {
232+ loadMoreTimerRef . current = null
233+ loadMoreRef . current . onLoadMore ?.( )
234+ } , 500 )
235+ }
197236 } ,
198- [ ] ,
237+ [ nodes , containerSize . width , containerSize . height ] ,
199238 )
200239
201240 // Navigation
@@ -509,7 +548,7 @@ export function MemoryGraph({
509548 display : "flex" ,
510549 alignItems : "center" ,
511550 justifyContent : "center" ,
512- backgroundColor : colors . bg ,
551+ backgroundColor : "transparent" ,
513552 borderRadius : 12 ,
514553 }
515554
@@ -535,9 +574,7 @@ export function MemoryGraph({
535574 height : "100%" ,
536575 borderRadius : 12 ,
537576 overflow : "hidden" ,
538- backgroundColor : colors . bg ,
539- backgroundImage : `radial-gradient(circle, ${ colors . textMuted } 0.5px, transparent 0.5px)` ,
540- backgroundSize : "16px 16px" ,
577+ backgroundColor : "transparent" ,
541578 }
542579
543580 const canvasContainerStyle : React . CSSProperties = {
@@ -576,30 +613,6 @@ export function MemoryGraph({
576613 colors = { colors }
577614 />
578615
579- { ! isLoading && hasMore && onLoadMore && (
580- < button
581- type = "button"
582- onClick = { onLoadMore }
583- style = { {
584- position : "absolute" ,
585- top : 16 ,
586- right : 16 ,
587- zIndex : 30 ,
588- borderRadius : 12 ,
589- border : `1px solid ${ colors . controlBorder } ` ,
590- backgroundColor : colors . controlBg ,
591- color : colors . textSecondary ,
592- paddingLeft : 16 ,
593- paddingRight : 16 ,
594- paddingTop : 8 ,
595- paddingBottom : 8 ,
596- fontSize : 13 ,
597- cursor : "pointer" ,
598- } }
599- >
600- { isLoadingMore ? "Loading..." : "Load more" }
601- </ button >
602- ) }
603616
604617 { ! isLoading && ! nodes . some ( ( n ) => n . type === "document" ) && children && (
605618 < div style = { emptyStateStyle } > { children } </ div >
0 commit comments