mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-26 06:04:08 +00:00
fix(memory-graph): stop hit-testing against stale nodes after resize or theme change (#1284)
This commit is contained in:
parent
575ee2eaf5
commit
7bfe0b6be4
4 changed files with 33 additions and 13 deletions
|
|
@ -54,6 +54,33 @@ describe("SpatialIndex", () => {
|
|||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("hash guard keeps stale objects for a new generation at the same positions", () => {
|
||||
const idx = new SpatialIndex()
|
||||
const genA = [makeNode("a", 100, 100)]
|
||||
idx.rebuild(genA)
|
||||
|
||||
const genB = [makeNode("a", 100, 100)]
|
||||
expect(idx.rebuild(genB)).toBe(false)
|
||||
expect(idx.queryPoint(100, 100)).toBe(genA[0])
|
||||
})
|
||||
|
||||
it("force rebuild swaps the grid to the new node generation", () => {
|
||||
const idx = new SpatialIndex()
|
||||
const genA = [makeNode("a", 100, 100)]
|
||||
idx.rebuild(genA)
|
||||
|
||||
const genB = [makeNode("a", 100, 100)]
|
||||
expect(idx.rebuild(genB, true)).toBe(true)
|
||||
expect(idx.queryPoint(100, 100)).toBe(genB[0])
|
||||
})
|
||||
|
||||
it("force rebuild still refreshes the hash for subsequent unforced calls", () => {
|
||||
const idx = new SpatialIndex()
|
||||
const nodes = [makeNode("a", 100, 100)]
|
||||
idx.rebuild(nodes, true)
|
||||
expect(idx.rebuild(nodes)).toBe(false)
|
||||
})
|
||||
|
||||
it("rebuild detects sub-pixel movements (10x granularity)", () => {
|
||||
const idx = new SpatialIndex()
|
||||
const nodes = [makeNode("a", 100.0, 100.0)]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ export class SpatialIndex {
|
|||
private cellSize = 200
|
||||
private lastHash = 0
|
||||
|
||||
rebuild(nodes: GraphNode[]): boolean {
|
||||
// Hash only covers ids/positions, so pass force when the node array identity changes.
|
||||
rebuild(nodes: GraphNode[], force = false): boolean {
|
||||
const hash = this.computeHash(nodes)
|
||||
if (hash === this.lastHash) return false
|
||||
if (!force && hash === this.lastHash) return false
|
||||
this.lastHash = hash
|
||||
this.grid.clear()
|
||||
|
||||
|
|
|
|||
|
|
@ -97,13 +97,13 @@ export const GraphCanvas = memo<ExtendedGraphCanvasProps>(function GraphCanvas({
|
|||
simulation,
|
||||
}
|
||||
|
||||
// Rebuild nodeMap + spatial index when nodes change
|
||||
// Force the spatial rebuild: a new node generation can keep the same ids/positions
|
||||
useEffect(() => {
|
||||
const map = nodeMapRef.current
|
||||
map.clear()
|
||||
for (const n of nodes) map.set(n.id, n)
|
||||
inputRef.current?.syncNodes(map)
|
||||
spatialRef.current.rebuild(nodes)
|
||||
spatialRef.current.rebuild(nodes, true)
|
||||
renderNeeded.current = true
|
||||
}, [nodes])
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import type {
|
|||
GraphApiDocument,
|
||||
GraphNode,
|
||||
MemoryNodeData,
|
||||
GraphThemeColors,
|
||||
MemoryGraphProps,
|
||||
ResolvedMemoryGraphLabels,
|
||||
} from "../types"
|
||||
|
|
@ -85,14 +84,7 @@ export function MemoryGraph({
|
|||
)
|
||||
const hoverPopoverZIndex =
|
||||
layering?.hoverPopoverZIndex ?? DEFAULT_HOVER_POPOVER_Z_INDEX
|
||||
const resolvedColors = useGraphTheme(colorOverrides)
|
||||
const colors = useMemo<GraphThemeColors>(
|
||||
() =>
|
||||
colorOverrides
|
||||
? { ...resolvedColors, ...colorOverrides }
|
||||
: resolvedColors,
|
||||
[resolvedColors, colorOverrides],
|
||||
)
|
||||
const colors = useGraphTheme(colorOverrides)
|
||||
|
||||
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 })
|
||||
const [containerBounds, setContainerBounds] = useState<DOMRect | null>(null)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue