@andespindola/brainlink 0.1.0-beta.54 → 0.1.0-beta.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@ const renderNodeBudget = 900
|
|
|
7
7
|
const renderEdgeBudget = 2400
|
|
8
8
|
const clusterActivationNodeThreshold = 600
|
|
9
9
|
const clusterZoomThreshold = 0.18
|
|
10
|
-
const macroGalaxyZoomThreshold = 0.
|
|
10
|
+
const macroGalaxyZoomThreshold = 0.0012
|
|
11
11
|
const massiveAutoFitMacroScale = 0.006
|
|
12
12
|
const defaultMacroScale = 0.006
|
|
13
13
|
const clusterCellPixelSize = 64
|
|
@@ -18,6 +18,7 @@ const transformCoordinateLimit = 20_000_000
|
|
|
18
18
|
const hoverHitTestIntervalMs = 64
|
|
19
19
|
const overviewClusterMaxCount = 1400
|
|
20
20
|
const zoomRecoveryGuardMs = 1500
|
|
21
|
+
const zoomCapTargetViewportShare = 0.72
|
|
21
22
|
const state = {
|
|
22
23
|
graph: { nodes: [], edges: [] },
|
|
23
24
|
nodes: [],
|
|
@@ -53,6 +54,7 @@ const state = {
|
|
|
53
54
|
macroCenter: { x: 0, y: 0 },
|
|
54
55
|
macroRepresentative: null,
|
|
55
56
|
primaryHub: null,
|
|
57
|
+
hubNeighborDistance: Number.POSITIVE_INFINITY,
|
|
56
58
|
filterWorker: null,
|
|
57
59
|
filterReady: false,
|
|
58
60
|
lastHoverHitAt: 0,
|
|
@@ -232,6 +234,20 @@ const resize = () => {
|
|
|
232
234
|
const normalizeQuery = value => value.trim().toLowerCase()
|
|
233
235
|
const hubNodeRetentionLimit = 2
|
|
234
236
|
const hubNodePattern = /\b(memory\s*hub|knowledge\s*hub|hub|moc|map|memory\s*map|mapa)\b/i
|
|
237
|
+
const memoryHubPathPattern = /\bmemory[-_\s]*hub\b/i
|
|
238
|
+
const isMemoryHubNode = node => node.title.trim().toLowerCase() === 'memory hub'
|
|
239
|
+
|
|
240
|
+
const hubNodeScore = node => {
|
|
241
|
+
const title = node.title.trim().toLowerCase()
|
|
242
|
+
if (title === 'memory hub') return 6
|
|
243
|
+
if (title === 'knowledge hub') return 5
|
|
244
|
+
if (memoryHubPathPattern.test(node.path || '')) return 4
|
|
245
|
+
if (node.tags.some(tag => tag.trim().toLowerCase() === 'memory-hub')) return 3
|
|
246
|
+
if (/\bmoc\b/i.test(node.title)) return 2
|
|
247
|
+
return hubNodePattern.test(node.title) || hubNodePattern.test(node.path || '') || node.tags.some(tag => hubNodePattern.test(tag))
|
|
248
|
+
? 1
|
|
249
|
+
: 0
|
|
250
|
+
}
|
|
235
251
|
|
|
236
252
|
const localFilteredNodes = query =>
|
|
237
253
|
state.nodes.filter(node =>
|
|
@@ -246,8 +262,10 @@ const rankedHubNodes = () => {
|
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
const byTitleAndDegree = [...state.nodes]
|
|
249
|
-
.filter(node =>
|
|
265
|
+
.filter(node => hubNodeScore(node) > 0)
|
|
250
266
|
.sort((left, right) => {
|
|
267
|
+
const byHubScore = hubNodeScore(right) - hubNodeScore(left)
|
|
268
|
+
if (byHubScore !== 0) return byHubScore
|
|
251
269
|
const byDegree = (state.nodeDegrees.get(right.id) ?? 0) - (state.nodeDegrees.get(left.id) ?? 0)
|
|
252
270
|
if (byDegree !== 0) return byDegree
|
|
253
271
|
return left.title.localeCompare(right.title)
|
|
@@ -292,11 +310,13 @@ const resolveMacroRepresentative = (nodes) => {
|
|
|
292
310
|
return null
|
|
293
311
|
}
|
|
294
312
|
|
|
295
|
-
|
|
313
|
+
const nonHubNodes = nodes.filter(node => !isMemoryHubNode(node))
|
|
314
|
+
const pool = nonHubNodes.length > 0 ? nonHubNodes : nodes
|
|
315
|
+
let best = pool[0]
|
|
296
316
|
let bestDegree = state.nodeDegrees.get(best.id) ?? 0
|
|
297
317
|
|
|
298
|
-
for (let index = 1; index <
|
|
299
|
-
const node =
|
|
318
|
+
for (let index = 1; index < pool.length; index += 1) {
|
|
319
|
+
const node = pool[index]
|
|
300
320
|
const degree = state.nodeDegrees.get(node.id) ?? 0
|
|
301
321
|
if (degree > bestDegree) {
|
|
302
322
|
best = node
|
|
@@ -307,6 +327,24 @@ const resolveMacroRepresentative = (nodes) => {
|
|
|
307
327
|
return best
|
|
308
328
|
}
|
|
309
329
|
|
|
330
|
+
const nearestHubNeighborDistance = (hub, nodes) => {
|
|
331
|
+
if (!hub || nodes.length <= 1) {
|
|
332
|
+
return Number.POSITIVE_INFINITY
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let minimum = Number.POSITIVE_INFINITY
|
|
336
|
+
for (let index = 0; index < nodes.length; index += 1) {
|
|
337
|
+
const node = nodes[index]
|
|
338
|
+
if (node.id === hub.id) continue
|
|
339
|
+
const distance = Math.hypot(node.x - hub.x, node.y - hub.y)
|
|
340
|
+
if (distance < minimum) {
|
|
341
|
+
minimum = distance
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return minimum
|
|
346
|
+
}
|
|
347
|
+
|
|
310
348
|
const recomputeVisibility = () => {
|
|
311
349
|
const nodes = filteredNodes()
|
|
312
350
|
const ids = new Set(nodes.map(node => node.id))
|
|
@@ -322,15 +360,17 @@ const recomputeVisibility = () => {
|
|
|
322
360
|
state.visibleNodeSpatial = createSpatialIndex(nodes)
|
|
323
361
|
state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
|
|
324
362
|
state.overviewClusters = nodes.length > massiveGraphNodeThreshold ? buildOverviewClusters(nodes) : []
|
|
363
|
+
const primaryHub = rankedHubNodes()[0] ?? null
|
|
364
|
+
state.primaryHub = primaryHub
|
|
365
|
+
state.hubNeighborDistance = nearestHubNeighborDistance(primaryHub, nodes)
|
|
325
366
|
const bounds = graphBounds(nodes)
|
|
326
367
|
state.macroCenter = bounds
|
|
327
368
|
? {
|
|
328
|
-
x: (bounds.minX + bounds.maxX) / 2,
|
|
329
|
-
y: (bounds.minY + bounds.maxY) / 2
|
|
369
|
+
x: primaryHub ? primaryHub.x : (bounds.minX + bounds.maxX) / 2,
|
|
370
|
+
y: primaryHub ? primaryHub.y : (bounds.minY + bounds.maxY) / 2
|
|
330
371
|
}
|
|
331
372
|
: { x: 0, y: 0 }
|
|
332
373
|
state.macroRepresentative = resolveMacroRepresentative(nodes)
|
|
333
|
-
state.primaryHub = rankedHubNodes()[0] ?? null
|
|
334
374
|
markRenderDirty()
|
|
335
375
|
}
|
|
336
376
|
|
|
@@ -641,23 +681,61 @@ const ensureHubNodesInRenderedSet = (nodes) => {
|
|
|
641
681
|
return nodes
|
|
642
682
|
}
|
|
643
683
|
|
|
644
|
-
const maxNodes = Math.max(renderNodeBudget, nodes.length)
|
|
684
|
+
const maxNodes = Math.max(Math.min(renderNodeBudget, nodes.length), 1)
|
|
645
685
|
const ids = new Set(nodes.map((node) => node.id))
|
|
646
686
|
const hubs = rankedHubNodes()
|
|
647
687
|
const merged = [...nodes]
|
|
648
688
|
|
|
649
|
-
for (let index = 0; index < hubs.length
|
|
689
|
+
for (let index = 0; index < hubs.length; index += 1) {
|
|
650
690
|
const hub = hubs[index]
|
|
651
|
-
if (
|
|
691
|
+
if (ids.has(hub.id)) {
|
|
692
|
+
continue
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (merged.length < maxNodes) {
|
|
652
696
|
merged.push(hub)
|
|
653
697
|
ids.add(hub.id)
|
|
698
|
+
continue
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const replacementIndex = merged.findIndex((node) => !hubs.some((candidate) => candidate.id === node.id))
|
|
702
|
+
if (replacementIndex >= 0) {
|
|
703
|
+
ids.delete(merged[replacementIndex].id)
|
|
704
|
+
merged[replacementIndex] = hub
|
|
705
|
+
ids.add(hub.id)
|
|
654
706
|
}
|
|
655
707
|
}
|
|
656
708
|
|
|
657
709
|
return merged
|
|
658
710
|
}
|
|
659
711
|
|
|
660
|
-
const
|
|
712
|
+
const zoomCapByNodeCount = (nodeCount) => {
|
|
713
|
+
if (nodeCount > 50000) return 0.88
|
|
714
|
+
if (nodeCount > 20000) return 1.15
|
|
715
|
+
if (nodeCount > 6000) return 1.65
|
|
716
|
+
if (nodeCount > 2000) return 2.2
|
|
717
|
+
return zoomRange.max
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const zoomCapByHubDistance = (distance) => {
|
|
721
|
+
if (!Number.isFinite(distance) || distance <= 0) {
|
|
722
|
+
return zoomRange.max
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const rect = canvas.getBoundingClientRect()
|
|
726
|
+
const viewportWidth = Math.max(rect.width, 320)
|
|
727
|
+
const viewportHeight = Math.max(rect.height, 320)
|
|
728
|
+
const reference = Math.max(220, Math.min(viewportWidth, viewportHeight) * zoomCapTargetViewportShare)
|
|
729
|
+
return Math.max(0.3, Math.min(zoomRange.max, reference / distance))
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const currentZoomMax = () => {
|
|
733
|
+
const nodeCount = state.visibleNodes.length > 0 ? state.visibleNodes.length : state.nodes.length
|
|
734
|
+
const capped = Math.min(zoomCapByNodeCount(nodeCount), zoomCapByHubDistance(state.hubNeighborDistance))
|
|
735
|
+
return Math.max(zoomRange.min * 2, capped)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const clampScale = value => Math.max(zoomRange.min, Math.min(currentZoomMax(), value))
|
|
661
739
|
const isFiniteNumber = value => Number.isFinite(value)
|
|
662
740
|
const isReasonableCoordinate = value => isFiniteNumber(value) && Math.abs(value) <= worldCoordinateLimit
|
|
663
741
|
const clampTransformCoordinate = value => {
|
|
@@ -748,9 +826,7 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
|
|
|
748
826
|
const macroScale = nodes.length > massiveGraphNodeThreshold ? massiveAutoFitMacroScale : defaultMacroScale
|
|
749
827
|
const scale = options.macro && nodes.length > 1
|
|
750
828
|
? clampScale(Math.min(baselineScale, macroScale))
|
|
751
|
-
:
|
|
752
|
-
? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
|
|
753
|
-
: baselineScale
|
|
829
|
+
: baselineScale
|
|
754
830
|
const hubCenter =
|
|
755
831
|
options.preferHubCenter && state.primaryHub && nodes.some((node) => node.id === state.primaryHub.id)
|
|
756
832
|
? state.primaryHub
|
|
@@ -768,7 +844,7 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
|
|
|
768
844
|
markRenderDirty()
|
|
769
845
|
}
|
|
770
846
|
|
|
771
|
-
const resetView = () => fitView({ useFiltered: false, macro:
|
|
847
|
+
const resetView = () => fitView({ useFiltered: false, macro: false, preferHubCenter: true })
|
|
772
848
|
|
|
773
849
|
const createLayout = graph => {
|
|
774
850
|
const nodeRows = Array.isArray(graph.nodes) ? graph.nodes : []
|
package/package.json
CHANGED