fix: memory graph progressively loads all data and remove background

- Switch to infinite query with viewport-triggered pagination (loads more when user zooms out 3x past node bounds)
- Remove maxNodes cap so all data renders
- Remove background color and dot pattern from graph
- Make document-memory edges light grey

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dhravya Shah 2026-04-11 14:20:30 -07:00
parent a91136dad8
commit 26ad0e32e2
6 changed files with 58 additions and 44 deletions

View file

@ -42,7 +42,7 @@ export const GraphLayoutView = memo<GraphLayoutViewProps>(({ isChatOpen }) => {
variant="consumer"
highlightDocumentIds={allHighlightDocumentIds}
highlightsVisible={isChatOpen}
maxNodes={200}
maxNodes={undefined}
canvasRef={canvasRef}
/>
</div>

View file

@ -126,10 +126,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) {
},
getNextPageParam: (lastPage) => {
const { currentPage, totalPages } = lastPage.pagination
if (currentPage < totalPages) {
return currentPage + 1
}
return undefined
return currentPage < totalPages ? currentPage + 1 : undefined
},
staleTime: 30 * 1000,
enabled,

View file

@ -27,7 +27,7 @@ export function MemoryGraph({
error: externalError = null,
variant = "console",
containerTags,
maxNodes = 200,
maxNodes,
canvasRef,
...rest
}: MemoryGraphWrapperProps) {
@ -59,7 +59,7 @@ export function MemoryGraph({
})
return (
<div ref={containerRef} className="w-full h-full">
<div ref={containerRef} className="w-full h-full [&>div]:!bg-none">
<MemoryGraphBase
documents={documents}
isLoading={externalIsLoading || apiIsLoading}
@ -71,6 +71,10 @@ export function MemoryGraph({
maxNodes={maxNodes}
canvasRef={canvasRef}
totalCount={totalCount}
colors={{
bg: "transparent",
edgeDerives: "#9ca3af",
} as any}
{...rest}
>
{children}

View file

@ -44,7 +44,10 @@
"@react-router/fs-routes": "^7.6.2",
"@react-router/node": "^7.6.2",
"@react-router/serve": "^7.6.2",
"@repo/lib": "workspace:*",
"@repo/validation": "workspace:*",
"@sentry/nextjs": "^10.33.0",
"@supermemory/memory-graph": "^0.2.0",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-form": "^1.12.4",
"@tanstack/react-query": "^5.90.14",
@ -104,10 +107,7 @@
"tw-animate-css": "^1.3.4",
"use-debounce": "^10.1.0",
"vaul": "^1.1.2",
"zustand": "^5.0.7",
"@repo/lib": "workspace:*",
"@repo/validation": "workspace:*",
"@supermemory/memory-graph": "workspace:*"
"zustand": "^5.0.7"
},
"devDependencies": {
"@biomejs/biome": "^2.2.2",

View file

@ -161,7 +161,7 @@
"@repo/lib": "workspace:*",
"@repo/validation": "workspace:*",
"@sentry/nextjs": "^10.33.0",
"@supermemory/memory-graph": "workspace:*",
"@supermemory/memory-graph": "^0.2.0",
"@tailwindcss/typography": "^0.5.16",
"@tanstack/react-form": "^1.12.4",
"@tanstack/react-query": "^5.90.14",

View file

@ -185,17 +185,56 @@ export function MemoryGraph({
// Drag end handled by InputHandler
}, [])
// Load more when user zooms out enough that visible area dwarfs the node extent
const loadMoreTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const loadMoreRef = useRef({ hasMore, isLoadingMore, onLoadMore })
loadMoreRef.current = { hasMore, isLoadingMore, onLoadMore }
const handleViewportChange = useCallback(
(zoom: number, popoverVisible: boolean) => {
setZoomDisplay(Math.round(zoom * 100))
// Only increment viewportVersion (which triggers popover repositioning
// via activePopoverPosition useMemo) when a popover is actually visible.
// This avoids 60fps React reconciliation during plain panning/zooming.
if (popoverVisible) {
setViewportVersion((v) => v + 1)
}
const { hasMore: more, isLoadingMore: loading, onLoadMore: load } = loadMoreRef.current
if (!more || loading || !load || !viewportRef.current) return
const vp = viewportRef.current
const currentNodes = nodes
if (currentNodes.length === 0) return
const topLeft = vp.screenToWorld(0, 0)
const bottomRight = vp.screenToWorld(containerSize.width, containerSize.height)
const viewW = bottomRight.x - topLeft.x
const viewH = bottomRight.y - topLeft.y
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const n of currentNodes) {
if (n.x < minX) minX = n.x
if (n.y < minY) minY = n.y
if (n.x > maxX) maxX = n.x
if (n.y > maxY) maxY = n.y
}
const nodeW = maxX - minX || 1
const nodeH = maxY - minY || 1
// Only trigger when the visible area is 3x larger than the node extent
// (i.e. user has zoomed out significantly past the data)
const zoomedOut = viewW > nodeW * 3 || viewH > nodeH * 3
if (zoomedOut && !loadMoreTimerRef.current) {
loadMoreTimerRef.current = setTimeout(() => {
loadMoreTimerRef.current = null
loadMoreRef.current.onLoadMore?.()
}, 500)
}
},
[],
[nodes, containerSize.width, containerSize.height],
)
// Navigation
@ -509,7 +548,7 @@ export function MemoryGraph({
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: colors.bg,
backgroundColor: "transparent",
borderRadius: 12,
}
@ -535,9 +574,7 @@ export function MemoryGraph({
height: "100%",
borderRadius: 12,
overflow: "hidden",
backgroundColor: colors.bg,
backgroundImage: `radial-gradient(circle, ${colors.textMuted} 0.5px, transparent 0.5px)`,
backgroundSize: "16px 16px",
backgroundColor: "transparent",
}
const canvasContainerStyle: React.CSSProperties = {
@ -576,30 +613,6 @@ export function MemoryGraph({
colors={colors}
/>
{!isLoading && hasMore && onLoadMore && (
<button
type="button"
onClick={onLoadMore}
style={{
position: "absolute",
top: 16,
right: 16,
zIndex: 30,
borderRadius: 12,
border: `1px solid ${colors.controlBorder}`,
backgroundColor: colors.controlBg,
color: colors.textSecondary,
paddingLeft: 16,
paddingRight: 16,
paddingTop: 8,
paddingBottom: 8,
fontSize: 13,
cursor: "pointer",
}}
>
{isLoadingMore ? "Loading..." : "Load more"}
</button>
)}
{!isLoading && !nodes.some((n) => n.type === "document") && children && (
<div style={emptyStateStyle}>{children}</div>