@andespindola/brainlink 0.1.0-beta.83 → 0.1.0-beta.84

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.
package/README.md CHANGED
@@ -600,6 +600,7 @@ The graph UI shows:
600
600
  - graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
601
601
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
602
602
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
603
+ - graph camera treats hub-centered navigation as structural only when the hub is dominant; diffuse stress graphs reset and zoom around the full graph mass
603
604
  - massive-graph LOD progression: very low zoom uses spatial overview sampling plus hub-neighborhood edge previews to preserve whole-vault shape and orientation, then progressively raises the focused node budget as zoom increases so dense local areas keep nearby notes and links visible
604
605
 
605
606
  The server indexes before starting by default. Use `--no-index` to skip that step:
@@ -537,6 +537,17 @@ const nearestHubNeighborDistance = (hub, nodes) => {
537
537
  return minimum
538
538
  }
539
539
 
540
+ const isDominantHub = (hub, nodeCount = state.visibleNodes.length) => {
541
+ if (!hub || nodeCount <= 0) {
542
+ return false
543
+ }
544
+
545
+ const degree = state.nodeDegrees.get(hub.id) ?? 0
546
+ const minimumDegree = Math.max(18, Math.sqrt(nodeCount) * 1.8)
547
+ const degreeRatio = degree / Math.max(nodeCount, 1)
548
+ return degree >= minimumDegree || degreeRatio >= 0.035
549
+ }
550
+
540
551
  const recomputeVisibility = () => {
541
552
  const nodes = filteredNodes()
542
553
  const ids = new Set(nodes.map(node => node.id))
@@ -556,10 +567,11 @@ const recomputeVisibility = () => {
556
567
  state.primaryHub = primaryHub
557
568
  state.hubNeighborDistance = nearestHubNeighborDistance(primaryHub, nodes)
558
569
  const bounds = graphBounds(nodes)
570
+ const macroHub = isDominantHub(primaryHub, nodes.length) ? primaryHub : null
559
571
  state.macroCenter = bounds
560
572
  ? {
561
- x: primaryHub ? primaryHub.x : (bounds.minX + bounds.maxX) / 2,
562
- y: primaryHub ? primaryHub.y : (bounds.minY + bounds.maxY) / 2
573
+ x: macroHub ? macroHub.x : (bounds.minX + bounds.maxX) / 2,
574
+ y: macroHub ? macroHub.y : (bounds.minY + bounds.maxY) / 2
563
575
  }
564
576
  : { x: 0, y: 0 }
565
577
  state.macroRepresentative = resolveMacroRepresentative(nodes)
@@ -1675,7 +1687,9 @@ const zoomCapByHubDistance = (distance) => {
1675
1687
 
1676
1688
  const currentZoomMax = () => {
1677
1689
  const nodeCount = state.visibleNodes.length > 0 ? state.visibleNodes.length : state.nodes.length
1678
- const hubDistanceCap = zoomCapByHubDistance(state.hubNeighborDistance)
1690
+ const hubDistanceCap = isDominantHub(state.primaryHub, nodeCount)
1691
+ ? zoomCapByHubDistance(state.hubNeighborDistance)
1692
+ : zoomRange.max
1679
1693
  const minimumUsefulCap = nodeCount > massiveGraphNodeThreshold ? 1.9 : nodeCount > largeGraphNodeThreshold ? 1.35 : 0.8
1680
1694
  const capped = Math.min(zoomCapByNodeCount(nodeCount), Math.max(minimumUsefulCap, hubDistanceCap))
1681
1695
  return Math.max(zoomRange.min * 2, capped)
@@ -1776,7 +1790,7 @@ const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: t
1776
1790
  ? clampScale(Math.min(baselineScale, massiveAutoFitMacroScale))
1777
1791
  : baselineScale
1778
1792
  const hubCenter =
1779
- options.preferHubCenter && state.primaryHub && nodes.some((node) => node.id === state.primaryHub.id)
1793
+ options.preferHubCenter && isDominantHub(state.primaryHub, nodes.length) && nodes.some((node) => node.id === state.primaryHub.id)
1780
1794
  ? state.primaryHub
1781
1795
  : null
1782
1796
  const centerX = hubCenter ? hubCenter.x : (bounds.minX + bounds.maxX) / 2
@@ -2735,27 +2749,8 @@ const selectNodeById = id => {
2735
2749
  }
2736
2750
 
2737
2751
  const zoomAtPoint = (screenX, screenY, factor, source = 'generic') => {
2738
- const resolveZoomFactor = () => {
2739
- if (state.nodes.length <= massiveGraphNodeThreshold) {
2740
- return factor
2741
- }
2742
-
2743
- const scale = state.transform.scale
2744
- if (factor > 1) {
2745
- if (scale < 0.006) return Math.max(factor, 1.48)
2746
- if (scale < 0.02) return Math.max(factor, 1.34)
2747
- if (scale < 0.08) return Math.max(factor, 1.22)
2748
- return factor
2749
- }
2750
-
2751
- if (scale < 0.006) return Math.min(factor, 0.68)
2752
- if (scale < 0.02) return Math.min(factor, 0.78)
2753
- if (scale < 0.08) return Math.min(factor, 0.86)
2754
- return factor
2755
- }
2756
-
2757
2752
  state.lastManualZoomAt = performance.now()
2758
- const effectiveFactor = resolveZoomFactor()
2753
+ const effectiveFactor = factor
2759
2754
  const nextScale = clampScale(state.transform.scale * effectiveFactor)
2760
2755
  if (nextScale === state.transform.scale) {
2761
2756
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.83",
3
+ "version": "0.1.0-beta.84",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",