@andespindola/brainlink 0.1.0-beta.78 → 0.1.0-beta.79

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
@@ -84,7 +84,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
84
84
  - Graph renderer optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
85
85
  - Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
86
86
  - Large-segment layout spacing now grows logarithmically to keep initial visual density consistent between medium and very large vaults (for example, ~1k vs ~50k notes).
87
- - Zoomed-out graph LOD now clusters dense regions and progressively expands nodes as zoom increases.
87
+ - Zoomed-out graph LOD clusters dense regions and progressively expands the focused viewport as zoom increases, including very large vaults.
88
88
  - Graph reset starts in macro "galaxy" overview mode and progressively reveals nearby nodes as zoom increases, including smaller vaults.
89
89
  - Graph filtering runs in a dedicated browser worker to keep the UI thread responsive during heavy datasets.
90
90
  - Edge rendering budgets adapt to zoom level to prevent frame spikes on large graph panoramas.
@@ -595,7 +595,7 @@ The graph UI shows:
595
595
  - double-click on canvas zooms in at cursor position
596
596
  - floating graph totals (notes, links, tags) below the Brainlink title
597
597
  - large-graph rendering safeguards (edge draw caps, lower redraw rate, zoom-aware interaction)
598
- - 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 reveals nodes and edges as zoom increases
598
+ - 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
599
599
 
600
600
  The server indexes before starting by default. Use `--no-index` to skip that step:
601
601
 
@@ -4,6 +4,7 @@ const largeGraphNodeThreshold = 4000
4
4
  const massiveGraphNodeThreshold = 20000
5
5
  const largeGraphEdgeRenderLimit = 120000
6
6
  const renderNodeBudget = 900
7
+ const zoomedMassiveRenderNodeBudget = 2200
7
8
  const renderEdgeBudget = 2400
8
9
  const clusterActivationNodeThreshold = 600
9
10
  const clusterZoomThreshold = 0.18
@@ -576,6 +577,13 @@ const nodeBudgetForScale = (scale) => {
576
577
  if (scale < 0.06) return 360
577
578
  if (scale < 0.09) return 520
578
579
  if (scale < 0.14) return 720
580
+ if (state.visibleNodes.length > massiveGraphNodeThreshold) {
581
+ if (scale < 0.28) return renderNodeBudget
582
+ if (scale < 0.45) return 1100
583
+ if (scale < 0.7) return 1400
584
+ if (scale < 1.05) return 1800
585
+ return zoomedMassiveRenderNodeBudget
586
+ }
579
587
  return renderNodeBudget
580
588
  }
581
589
 
@@ -739,16 +747,22 @@ const selectStableSampleNodes = (sourceNodes, limit) => {
739
747
  : null
740
748
  const anchor = recentZoomFocus ?? viewportCenterWorldPoint()
741
749
  const previousIds = new Set(state.renderNodes.map((node) => node.id))
750
+ const preferAnchorDistance = state.visibleNodes.length > massiveGraphNodeThreshold && state.transform.scale >= 0.28
742
751
 
743
752
  return [...sourceNodes]
744
753
  .sort((left, right) => {
745
754
  const leftWasVisible = previousIds.has(left.id) ? 1 : 0
746
755
  const rightWasVisible = previousIds.has(right.id) ? 1 : 0
747
- if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
748
-
749
756
  const leftDistance = Math.hypot(left.x - anchor.x, left.y - anchor.y)
750
757
  const rightDistance = Math.hypot(right.x - anchor.x, right.y - anchor.y)
751
- if (leftDistance !== rightDistance) return leftDistance - rightDistance
758
+
759
+ if (preferAnchorDistance) {
760
+ if (leftDistance !== rightDistance) return leftDistance - rightDistance
761
+ if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
762
+ } else {
763
+ if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
764
+ if (leftDistance !== rightDistance) return leftDistance - rightDistance
765
+ }
752
766
 
753
767
  const leftDegree = state.nodeDegrees.get(left.id) ?? 0
754
768
  const rightDegree = state.nodeDegrees.get(right.id) ?? 0
@@ -1942,7 +1956,7 @@ const computeRenderVisibility = () => {
1942
1956
  const sampleLimit = nodeBudgetForScale(state.transform.scale)
1943
1957
  const carryMargin = Math.max(240, Math.min(1200, 340 / Math.max(state.transform.scale, 0.0001)))
1944
1958
  const carryViewport = expandViewportBounds(viewport, carryMargin)
1945
- const carryOverLimit = Math.max(180, Math.min(900, sampleLimit))
1959
+ const carryOverLimit = Math.max(180, Math.min(sampleLimit, Math.floor(sampleLimit * 0.5)))
1946
1960
  const carryOverNodes = (state.renderNodes ?? [])
1947
1961
  .filter((node) => isNodeInViewport(node, carryViewport))
1948
1962
  .slice(0, carryOverLimit)
@@ -1951,18 +1965,19 @@ const computeRenderVisibility = () => {
1951
1965
  carryOverNodes,
1952
1966
  Math.max(sampleLimit * 7, carryOverLimit)
1953
1967
  )
1968
+ const sourceWithCarryIds = new Set(sourceWithCarry.map((node) => node.id))
1954
1969
  const sampledRaw = selectStableSampleNodes(
1955
1970
  sourceWithCarry,
1956
- Math.min(sampleLimit, renderNodeBudget)
1971
+ sampleLimit
1957
1972
  )
1958
1973
  const continuityBudget = Math.max(24, Math.min(sampleLimit - 8, Math.floor(sampleLimit * 0.42)))
1959
1974
  const previousVisibleNodes = (state.renderNodes ?? [])
1960
- .filter((node) => sourceWithCarry.some((candidate) => candidate.id === node.id))
1975
+ .filter((node) => sourceWithCarryIds.has(node.id))
1961
1976
  const continuityNodes = selectStableSampleNodes(previousVisibleNodes, continuityBudget)
1962
1977
  const sampled = mergeUniqueNodes(
1963
1978
  continuityNodes,
1964
1979
  sampledRaw,
1965
- Math.min(sampleLimit, renderNodeBudget)
1980
+ sampleLimit
1966
1981
  )
1967
1982
  let sampledNodes = ensureHubNodesInRenderedSet(sampled)
1968
1983
  if (state.transform.scale < 0.035) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.78",
3
+ "version": "0.1.0-beta.79",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",