@andespindola/brainlink 0.1.0-beta.56 → 0.1.0-beta.58

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.0012
10
+ const macroGalaxyZoomThreshold = 0.012
11
11
  const massiveAutoFitMacroScale = 0.006
12
12
  const defaultMacroScale = 0.006
13
13
  const clusterCellPixelSize = 64
@@ -235,7 +235,6 @@ const normalizeQuery = value => value.trim().toLowerCase()
235
235
  const hubNodeRetentionLimit = 2
236
236
  const hubNodePattern = /\b(memory\s*hub|knowledge\s*hub|hub|moc|map|memory\s*map|mapa)\b/i
237
237
  const memoryHubPathPattern = /\bmemory[-_\s]*hub\b/i
238
- const isMemoryHubNode = node => node.title.trim().toLowerCase() === 'memory hub'
239
238
 
240
239
  const hubNodeScore = node => {
241
240
  const title = node.title.trim().toLowerCase()
@@ -310,13 +309,14 @@ const resolveMacroRepresentative = (nodes) => {
310
309
  return null
311
310
  }
312
311
 
313
- const nonHubNodes = nodes.filter(node => !isMemoryHubNode(node))
314
- const pool = nonHubNodes.length > 0 ? nonHubNodes : nodes
315
- let best = pool[0]
312
+ const hubCandidate = state.primaryHub && nodes.some(node => node.id === state.primaryHub.id)
313
+ ? state.primaryHub
314
+ : null
315
+ let best = hubCandidate ?? nodes[0]
316
316
  let bestDegree = state.nodeDegrees.get(best.id) ?? 0
317
317
 
318
- for (let index = 1; index < pool.length; index += 1) {
319
- const node = pool[index]
318
+ for (let index = 1; index < nodes.length; index += 1) {
319
+ const node = nodes[index]
320
320
  const degree = state.nodeDegrees.get(node.id) ?? 0
321
321
  if (degree > bestDegree) {
322
322
  best = node
@@ -710,9 +710,9 @@ const ensureHubNodesInRenderedSet = (nodes) => {
710
710
  }
711
711
 
712
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
713
+ if (nodeCount > 50000) return 2.6
714
+ if (nodeCount > 20000) return 2.35
715
+ if (nodeCount > 6000) return 2.1
716
716
  if (nodeCount > 2000) return 2.2
717
717
  return zoomRange.max
718
718
  }
@@ -731,7 +731,9 @@ const zoomCapByHubDistance = (distance) => {
731
731
 
732
732
  const currentZoomMax = () => {
733
733
  const nodeCount = state.visibleNodes.length > 0 ? state.visibleNodes.length : state.nodes.length
734
- const capped = Math.min(zoomCapByNodeCount(nodeCount), zoomCapByHubDistance(state.hubNeighborDistance))
734
+ const hubDistanceCap = zoomCapByHubDistance(state.hubNeighborDistance)
735
+ const minimumUsefulCap = nodeCount > massiveGraphNodeThreshold ? 1.9 : nodeCount > largeGraphNodeThreshold ? 1.35 : 0.8
736
+ const capped = Math.min(zoomCapByNodeCount(nodeCount), Math.max(minimumUsefulCap, hubDistanceCap))
735
737
  return Math.max(zoomRange.min * 2, capped)
736
738
  }
737
739
 
@@ -826,7 +828,9 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
826
828
  const macroScale = nodes.length > massiveGraphNodeThreshold ? massiveAutoFitMacroScale : defaultMacroScale
827
829
  const scale = options.macro && nodes.length > 1
828
830
  ? clampScale(Math.min(baselineScale, macroScale))
829
- : baselineScale
831
+ : nodes.length > massiveGraphNodeThreshold
832
+ ? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
833
+ : baselineScale
830
834
  const hubCenter =
831
835
  options.preferHubCenter && state.primaryHub && nodes.some((node) => node.id === state.primaryHub.id)
832
836
  ? state.primaryHub
@@ -844,7 +848,7 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
844
848
  markRenderDirty()
845
849
  }
846
850
 
847
- const resetView = () => fitView({ useFiltered: false, macro: false, preferHubCenter: true })
851
+ const resetView = () => fitView({ useFiltered: false, macro: true, preferHubCenter: true })
848
852
 
849
853
  const createLayout = graph => {
850
854
  const nodeRows = Array.isArray(graph.nodes) ? graph.nodes : []
@@ -1064,7 +1068,12 @@ const hitNode = point => {
1064
1068
  if (state.renderClusters.length > 0) {
1065
1069
  return null
1066
1070
  }
1067
- if (state.nodes.length > largeGraphNodeThreshold && state.transform.scale < 0.9) {
1071
+ const hitScaleFloor = state.nodes.length > massiveGraphNodeThreshold
1072
+ ? 0.2
1073
+ : state.nodes.length > largeGraphNodeThreshold
1074
+ ? 0.34
1075
+ : 0
1076
+ if (state.transform.scale < hitScaleFloor) {
1068
1077
  return null
1069
1078
  }
1070
1079
 
@@ -1199,7 +1208,7 @@ const computeRenderVisibility = () => {
1199
1208
  if (shouldRenderMacroGalaxy) {
1200
1209
  const viewportNodes = viewportNodesFromSpatialIndex(viewport)
1201
1210
  const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
1202
- const representative = state.macroRepresentative ?? sourceNodes[0] ?? null
1211
+ const representative = state.primaryHub ?? state.macroRepresentative ?? sourceNodes[0] ?? null
1203
1212
  if (representative) {
1204
1213
  state.renderClusters = [
1205
1214
  {
@@ -1447,6 +1456,13 @@ const render = now => {
1447
1456
  ctx.lineWidth = 1.4 / safeScale
1448
1457
  ctx.strokeStyle = isMacro ? '#ffffff' : graphTheme.nodeStroke
1449
1458
  ctx.stroke()
1459
+ if (isMacro && cluster.representative?.title) {
1460
+ ctx.fillStyle = '#edf2f7'
1461
+ ctx.font = 12 / safeScale + 'px Inter, system-ui, sans-serif'
1462
+ ctx.textAlign = 'center'
1463
+ ctx.textBaseline = 'top'
1464
+ ctx.fillText(cluster.representative.title.slice(0, 28), cluster.x, cluster.y + (radiusPx + 9) / safeScale)
1465
+ }
1450
1466
  // Keep cluster markers minimal and faster to draw on large graphs.
1451
1467
  })
1452
1468
  } else {
@@ -1469,6 +1485,7 @@ const render = now => {
1469
1485
  const shouldDrawLabels =
1470
1486
  isSelected ||
1471
1487
  isHovered ||
1488
+ (state.nodes.length > largeGraphNodeThreshold && state.transform.scale >= 0.62 && state.renderNodes.length <= 1200) ||
1472
1489
  (state.nodes.length <= largeGraphNodeThreshold && (state.transform.scale > 1.18 || state.nodes.length <= 25))
1473
1490
  if (shouldDrawLabels) {
1474
1491
  ctx.fillStyle = graphTheme.label
@@ -1712,7 +1729,7 @@ const bindEvents = () => {
1712
1729
  const point = worldPoint(event)
1713
1730
  const now = performance.now()
1714
1731
  const canHoverHitTest =
1715
- !(state.nodes.length > massiveGraphNodeThreshold && state.transform.scale < 0.12)
1732
+ !(state.nodes.length > massiveGraphNodeThreshold && state.transform.scale < 0.06)
1716
1733
  const shouldHitTest = canHoverHitTest &&
1717
1734
  (state.pointer.down || now - state.lastHoverHitAt >= hoverHitTestIntervalMs)
1718
1735
  if (shouldHitTest) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.56",
3
+ "version": "0.1.0-beta.58",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",