@andespindola/brainlink 0.1.0-beta.47 → 0.1.0-beta.48

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,6 +7,8 @@ const renderNodeBudget = 900
7
7
  const renderEdgeBudget = 2400
8
8
  const clusterActivationNodeThreshold = 600
9
9
  const clusterZoomThreshold = 0.18
10
+ const macroGalaxyZoomThreshold = 0.012
11
+ const massiveAutoFitMacroScale = 0.006
10
12
  const clusterCellPixelSize = 64
11
13
  const minNodePixelRadius = 2.3
12
14
  const viewportPaddingPx = 280
@@ -45,6 +47,8 @@ const state = {
45
47
  visibleNodeSpatial: { cellSize: 220, minX: 0, minY: 0, maxX: 0, maxY: 0, buckets: new Map() },
46
48
  visibleEdgeByNode: new Map(),
47
49
  overviewClusters: [],
50
+ macroCenter: { x: 0, y: 0 },
51
+ macroRepresentative: null,
48
52
  filterWorker: null,
49
53
  filterReady: false,
50
54
  lastHoverHitAt: 0
@@ -245,6 +249,26 @@ const filteredNodes = () => {
245
249
  return withPersistentHubNodes(localFilteredNodes(query))
246
250
  }
247
251
 
252
+ const resolveMacroRepresentative = (nodes) => {
253
+ if (nodes.length === 0) {
254
+ return null
255
+ }
256
+
257
+ let best = nodes[0]
258
+ let bestDegree = state.nodeDegrees.get(best.id) ?? 0
259
+
260
+ for (let index = 1; index < nodes.length; index += 1) {
261
+ const node = nodes[index]
262
+ const degree = state.nodeDegrees.get(node.id) ?? 0
263
+ if (degree > bestDegree) {
264
+ best = node
265
+ bestDegree = degree
266
+ }
267
+ }
268
+
269
+ return best
270
+ }
271
+
248
272
  const recomputeVisibility = () => {
249
273
  const nodes = filteredNodes()
250
274
  const ids = new Set(nodes.map(node => node.id))
@@ -260,6 +284,14 @@ const recomputeVisibility = () => {
260
284
  state.visibleNodeSpatial = createSpatialIndex(nodes)
261
285
  state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
262
286
  state.overviewClusters = nodes.length > massiveGraphNodeThreshold ? buildOverviewClusters(nodes) : []
287
+ const bounds = graphBounds(nodes)
288
+ state.macroCenter = bounds
289
+ ? {
290
+ x: (bounds.minX + bounds.maxX) / 2,
291
+ y: (bounds.minY + bounds.maxY) / 2
292
+ }
293
+ : { x: 0, y: 0 }
294
+ state.macroRepresentative = resolveMacroRepresentative(nodes)
263
295
  markRenderDirty()
264
296
  }
265
297
 
@@ -607,7 +639,10 @@ const fitView = (options = { useFiltered: true }) => {
607
639
  const fitScale = Math.min(scaleX, scaleY)
608
640
  const biasedScale = clampScale(fitScale * fitScaleBiasByNodeCount(nodes.length))
609
641
  const scaleRange = autoFitScaleRangeByNodeCount(nodes.length)
610
- const scale = clampScale(Math.min(scaleRange.max, Math.max(scaleRange.min, biasedScale)))
642
+ const baselineScale = clampScale(Math.min(scaleRange.max, Math.max(scaleRange.min, biasedScale)))
643
+ const scale = nodes.length > massiveGraphNodeThreshold
644
+ ? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
645
+ : baselineScale
611
646
  const centerX = (bounds.minX + bounds.maxX) / 2
612
647
  const centerY = (bounds.minY + bounds.maxY) / 2
613
648
 
@@ -981,6 +1016,26 @@ const computeRenderVisibility = () => {
981
1016
  if (state.visibleNodes.length > massiveGraphNodeThreshold) {
982
1017
  const viewportNodes = viewportNodesFromSpatialIndex(viewport)
983
1018
  const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
1019
+ if (state.transform.scale <= macroGalaxyZoomThreshold) {
1020
+ const representative = state.macroRepresentative ?? sourceNodes[0] ?? null
1021
+ if (representative) {
1022
+ state.renderClusters = [
1023
+ {
1024
+ id: 'macro-galaxy',
1025
+ x: state.macroCenter.x,
1026
+ y: state.macroCenter.y,
1027
+ count: sourceNodes.length,
1028
+ representative
1029
+ }
1030
+ ]
1031
+ state.renderNodes = [representative]
1032
+ } else {
1033
+ state.renderClusters = []
1034
+ state.renderNodes = []
1035
+ }
1036
+ state.renderEdges = []
1037
+ return
1038
+ }
984
1039
  const sampleLimit = nodeBudgetForScale(state.transform.scale)
985
1040
  const sampled = sourceNodes.length > sampleLimit
986
1041
  ? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget), sourceNodes)
@@ -1167,19 +1222,22 @@ const render = now => {
1167
1222
  if (state.renderClusters.length > 0) {
1168
1223
  const safeScale = Math.max(state.transform.scale, 0.0001)
1169
1224
  state.renderClusters.forEach(cluster => {
1170
- const radiusPx = Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
1225
+ const isMacro = cluster.id === 'macro-galaxy'
1226
+ const radiusPx = isMacro
1227
+ ? 10
1228
+ : Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
1171
1229
  const radius = radiusPx / safeScale
1172
- const haloRadius = (radiusPx + 4) / safeScale
1230
+ const haloRadius = (radiusPx + (isMacro ? 8 : 4)) / safeScale
1173
1231
  ctx.beginPath()
1174
1232
  ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
1175
- ctx.fillStyle = graphTheme.nodeHalo
1233
+ ctx.fillStyle = isMacro ? 'rgba(243, 247, 251, 0.28)' : graphTheme.nodeHalo
1176
1234
  ctx.fill()
1177
1235
  ctx.beginPath()
1178
1236
  ctx.arc(cluster.x, cluster.y, radius, 0, Math.PI * 2)
1179
- ctx.fillStyle = graphTheme.node
1237
+ ctx.fillStyle = isMacro ? '#f3f7fb' : graphTheme.node
1180
1238
  ctx.fill()
1181
1239
  ctx.lineWidth = 1.4 / safeScale
1182
- ctx.strokeStyle = graphTheme.nodeStroke
1240
+ ctx.strokeStyle = isMacro ? '#ffffff' : graphTheme.nodeStroke
1183
1241
  ctx.stroke()
1184
1242
  // Keep cluster markers minimal and faster to draw on large graphs.
1185
1243
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.47",
3
+ "version": "0.1.0-beta.48",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",