@andespindola/brainlink 0.1.0-beta.65 → 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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.65",
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",