@andespindola/brainlink 0.1.0-beta.64 → 0.1.0-beta.66

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.
@@ -573,7 +573,7 @@ const nodeBudgetForScale = (scale) => {
573
573
  const layerFocusForScale = (scale) => {
574
574
  const normalized = Math.max(0, Math.min(1, (scale - 0.06) / 0.94))
575
575
  const shellCenter = Math.max(0.08, 0.96 - normalized * 0.86)
576
- const shellWidth = Math.max(0.16, 0.34 - normalized * 0.2)
576
+ const shellWidth = Math.max(0.24, 0.46 - normalized * 0.16)
577
577
  const coreRadius = Math.max(0.06, 0.1 + normalized * 0.22)
578
578
  const coreRatio = Math.max(0.2, Math.min(0.72, 0.24 + normalized * 0.48))
579
579
 
@@ -604,7 +604,7 @@ const selectLayeredNodesForScale = (sourceNodes, targetCount) => {
604
604
  ...item,
605
605
  normalized: item.distance / maxDistance
606
606
  }))
607
- const desired = Math.max(220, Math.min(sourceNodes.length, targetCount * 2))
607
+ const desired = Math.max(260, Math.min(sourceNodes.length, targetCount * 2))
608
608
  const coreTarget = Math.max(36, Math.min(desired - 8, Math.floor(desired * focus.coreRatio)))
609
609
  const shellTarget = Math.max(12, desired - coreTarget)
610
610
  const shellHalf = focus.shellWidth / 2
@@ -653,6 +653,62 @@ const selectLayeredNodesForScale = (sourceNodes, targetCount) => {
653
653
  return merged.length > 0 ? merged : sourceNodes
654
654
  }
655
655
 
656
+ const cursorWorldPoint = () => {
657
+ if (!state.cursor.inCanvas) {
658
+ return null
659
+ }
660
+
661
+ const rect = canvas.getBoundingClientRect()
662
+ const screenX = state.cursor.x - rect.left
663
+ const screenY = state.cursor.y - rect.top
664
+ return {
665
+ x: (screenX - state.transform.x) / state.transform.scale,
666
+ y: (screenY - state.transform.y) / state.transform.scale
667
+ }
668
+ }
669
+
670
+ const mergeUniqueNodes = (leftNodes, rightNodes, limit) => {
671
+ const merged = []
672
+ const ids = new Set()
673
+
674
+ const push = (node) => {
675
+ if (!node || ids.has(node.id) || merged.length >= limit) {
676
+ return
677
+ }
678
+ ids.add(node.id)
679
+ merged.push(node)
680
+ }
681
+
682
+ for (let index = 0; index < leftNodes.length && merged.length < limit; index += 1) {
683
+ push(leftNodes[index])
684
+ }
685
+ for (let index = 0; index < rightNodes.length && merged.length < limit; index += 1) {
686
+ push(rightNodes[index])
687
+ }
688
+
689
+ return merged
690
+ }
691
+
692
+ const selectAccessBridgeNodes = (sourceNodes, limit) => {
693
+ if (limit <= 0 || sourceNodes.length === 0) {
694
+ return []
695
+ }
696
+
697
+ const cursor = cursorWorldPoint()
698
+ const anchor = cursor ?? state.primaryHub ?? state.macroCenter ?? { x: 0, y: 0 }
699
+ return [...sourceNodes]
700
+ .sort((left, right) => {
701
+ const leftDistance = Math.hypot(left.x - anchor.x, left.y - anchor.y)
702
+ const rightDistance = Math.hypot(right.x - anchor.x, right.y - anchor.y)
703
+ if (leftDistance !== rightDistance) return leftDistance - rightDistance
704
+ const leftDegree = state.nodeDegrees.get(left.id) ?? 0
705
+ const rightDegree = state.nodeDegrees.get(right.id) ?? 0
706
+ if (leftDegree !== rightDegree) return rightDegree - leftDegree
707
+ return left.id.localeCompare(right.id)
708
+ })
709
+ .slice(0, limit)
710
+ }
711
+
656
712
  const edgeIdentityKey = edge => {
657
713
  if (!edge.target) return ''
658
714
  const pair = edge.source < edge.target
@@ -1672,9 +1728,15 @@ const computeRenderVisibility = () => {
1672
1728
  const sourceNodes = viewportNodes.length > 0 ? viewportNodes : state.visibleNodes
1673
1729
  const sampleLimit = nodeBudgetForScale(state.transform.scale)
1674
1730
  const layeredNodes = selectLayeredNodesForScale(sourceNodes, sampleLimit)
1675
- const sampled = layeredNodes.length > sampleLimit
1676
- ? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget), layeredNodes)
1677
- : layeredNodes.slice(0, Math.min(layeredNodes.length, renderNodeBudget))
1731
+ const bridgeLimit = Math.max(24, Math.min(180, Math.floor(sampleLimit * 0.26)))
1732
+ const bridgedNodes = mergeUniqueNodes(
1733
+ layeredNodes,
1734
+ selectAccessBridgeNodes(sourceNodes, bridgeLimit),
1735
+ Math.min(renderNodeBudget, sampleLimit + bridgeLimit)
1736
+ )
1737
+ const sampled = bridgedNodes.length > sampleLimit
1738
+ ? sampleVisibleNodes(Math.min(sampleLimit, renderNodeBudget), bridgedNodes)
1739
+ : bridgedNodes.slice(0, Math.min(bridgedNodes.length, renderNodeBudget))
1678
1740
  const sampledIds = new Set(sampled.map((node) => node.id))
1679
1741
  let sampledEdges = state.transform.scale >= 0.035 ? collectVisibleEdgesForNodes(sampledIds) : []
1680
1742
  let sampledNodes = ensureHubNodesInRenderedSet(sampled)
@@ -2133,6 +2195,13 @@ const bindEvents = () => {
2133
2195
  })
2134
2196
  canvas.addEventListener('wheel', handleWheelZoom, { passive: false })
2135
2197
  canvas.addEventListener('dblclick', event => {
2198
+ const point = worldPoint(event)
2199
+ const node = hitNode(point)
2200
+ if (node) {
2201
+ selectNode(node, { openContent: true })
2202
+ return
2203
+ }
2204
+
2136
2205
  const rect = canvas.getBoundingClientRect()
2137
2206
  const cursorX = event.clientX - rect.left
2138
2207
  const cursorY = event.clientY - rect.top
@@ -2192,8 +2261,8 @@ const bindEvents = () => {
2192
2261
  settleNeighborhoodAroundNode(draggedNode)
2193
2262
  markRenderDirty()
2194
2263
  }
2195
- if (draggedNode && !state.pointer.moved) selectNode(draggedNode, { openContent: true })
2196
- if (!draggedNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
2264
+ if (draggedNode && !state.pointer.moved) selectNode(draggedNode, { openContent: false })
2265
+ if (!draggedNode && !state.pointer.moved) selectNode(state.hovered, { openContent: false })
2197
2266
  state.pointer = { x: 0, y: 0, down: false, dragNode: null, moved: false }
2198
2267
  canvas.releasePointerCapture(event.pointerId)
2199
2268
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.64",
3
+ "version": "0.1.0-beta.66",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",