@andespindola/brainlink 0.1.0-beta.92 → 0.1.0-beta.94

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
@@ -602,7 +602,7 @@ The graph UI shows:
602
602
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
603
603
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
604
604
  - 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
605
- - graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of small connected 1000-note points, zoom-in delays expansion until the focused cluster is visually close, then continuously spreads and fades only focused clusters through 500-note, 250-note, 125-note and 60-note subgraphs with aggregated real links plus local sibling mesh links, keeps parent points during expansion to avoid visual jumps, then progressively raises the focused node budget so local areas keep nearby notes and links visible
605
+ - graph LOD progression: graphs up to 1000 notes render directly; larger graphs use a compact memory-hub-centered mesh of alpha-particle 1000-note points, zoom-in keeps focused child clusters latent at the parent position before fading them in and only then separating them through 500-note, 250-note, 125-note and 60-note subgraphs with aggregated real links plus local sibling mesh links, and in massive graphs keeps this subgraph mode active much longer with finer wheel steps so deep zoom does not abruptly switch to a broad sampled node cloud
606
606
 
607
607
  The server indexes before starting by default. Use `--no-index` to skip that step:
608
608
 
@@ -28,14 +28,15 @@ const ecosystemClusterEdgeLimit = 520
28
28
  const ecosystemHubEdgeLimit = 120
29
29
  const ecosystemSiblingEdgeLimit = 180
30
30
  const ecosystemClusterScaleThreshold = 0.78
31
+ const massiveEcosystemClusterScaleThreshold = 2.4
31
32
  const ecosystemSubgraphScaleThreshold = 0.18
32
33
  const ecosystemMicroScaleThreshold = 0.08
33
34
  const ecosystemFocusedParentLimit = 2
34
35
  const ecosystemExpansionLevels = [
35
- { parentSize: 1000, childSize: 500, start: 0.16, end: 0.34 },
36
- { parentSize: 500, childSize: 250, start: 0.3, end: 0.5 },
37
- { parentSize: 250, childSize: 125, start: 0.46, end: 0.66 },
38
- { parentSize: 125, childSize: 60, start: 0.62, end: 0.78 }
36
+ { parentSize: 1000, childSize: 500, start: 0.04, end: 0.62 },
37
+ { parentSize: 500, childSize: 250, start: 0.16, end: 0.72 },
38
+ { parentSize: 250, childSize: 125, start: 0.3, end: 0.78 },
39
+ { parentSize: 125, childSize: 60, start: 0.46, end: 0.86 }
39
40
  ]
40
41
  const zoomRecoveryGuardMs = 4200
41
42
  const zoomCapTargetViewportShare = 0.72
@@ -894,11 +895,11 @@ const smoothStep = value => {
894
895
  const zoomProgress = (scale, start, end) =>
895
896
  smoothStep((scale - start) / Math.max(end - start, 0.0001))
896
897
 
897
- const semanticZoomSpread = progress => Math.pow(progress, 2.6)
898
+ const semanticZoomSpread = progress => Math.pow(progress, 4.2)
898
899
 
899
- const opacityForSpread = spread => 0.03 + smoothStep(spread) * 0.97
900
+ const opacityForProgress = progress => Math.pow(progress, 2.1)
900
901
 
901
- const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
902
+ const expandFocusedClusters = (parentClusters, childSize, progress, spread, viewport) => {
902
903
  const focusPoint = ecosystemFocusPoint()
903
904
  const expandedParentIds = new Set(nearestEcosystemParentIds(
904
905
  parentClusters,
@@ -908,7 +909,7 @@ const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
908
909
  const childClusters = state.ecosystemClustersBySize.get(childSize) ?? []
909
910
  const visibleChildClusters = childClusters
910
911
  .filter(cluster => expandedParentIds.has(cluster.parentId))
911
- .map(cluster => spreadChildClusterFromParent(cluster, spread))
912
+ .map(cluster => spreadChildClusterFromParent(cluster, progress, spread))
912
913
  .filter(cluster => isClusterInViewport(cluster, viewport))
913
914
 
914
915
  return {
@@ -917,11 +918,11 @@ const expandFocusedClusters = (parentClusters, childSize, spread, viewport) => {
917
918
  }
918
919
  }
919
920
 
920
- const spreadChildClusterFromParent = (cluster, spread) => {
921
+ const spreadChildClusterFromParent = (cluster, progress, spread) => {
921
922
  if (!Number.isFinite(cluster.parentX) || !Number.isFinite(cluster.parentY)) {
922
923
  return {
923
924
  ...cluster,
924
- lodOpacity: opacityForSpread(spread)
925
+ lodOpacity: opacityForProgress(progress)
925
926
  }
926
927
  }
927
928
 
@@ -929,7 +930,7 @@ const spreadChildClusterFromParent = (cluster, spread) => {
929
930
  ...cluster,
930
931
  x: cluster.parentX + (cluster.x - cluster.parentX) * spread,
931
932
  y: cluster.parentY + (cluster.y - cluster.parentY) * spread,
932
- lodOpacity: opacityForSpread(spread)
933
+ lodOpacity: opacityForProgress(progress)
933
934
  }
934
935
  }
935
936
 
@@ -946,11 +947,8 @@ const selectHierarchicalEcosystemClusters = viewport => {
946
947
  continue
947
948
  }
948
949
  const progress = zoomProgress(state.transform.scale, level.start, level.end)
949
- if (progress <= 0.025) {
950
- continue
951
- }
952
950
  const spread = semanticZoomSpread(progress)
953
- const expansion = expandFocusedClusters(parentClusters, level.childSize, spread, viewport)
951
+ const expansion = expandFocusedClusters(parentClusters, level.childSize, progress, spread, viewport)
954
952
  visibleClusters.push(...expansion.childClusters)
955
953
  }
956
954
 
@@ -1005,11 +1003,12 @@ const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
1005
1003
  }
1006
1004
 
1007
1005
  const ecosystemEdgesForClusters = clusters => {
1008
- const clusterById = new Map(clusters.map(cluster => [cluster.id, cluster]))
1006
+ const edgeClusters = clusters.filter(cluster => cluster.isHub || clusterOpacity(cluster) > 0.018)
1007
+ const clusterById = new Map(edgeClusters.map(cluster => [cluster.id, cluster]))
1009
1008
  const clusterIds = new Set(clusterById.keys())
1010
1009
  const levelsBySize = []
1011
- for (let index = 0; index < clusters.length; index += 1) {
1012
- const cluster = clusters[index]
1010
+ for (let index = 0; index < edgeClusters.length; index += 1) {
1011
+ const cluster = edgeClusters[index]
1013
1012
  if (!cluster.size || cluster.isHub) continue
1014
1013
  if (!levelsBySize.some(level => level.size === cluster.size)) {
1015
1014
  levelsBySize.push({
@@ -1060,7 +1059,7 @@ const ecosystemEdgesForClusters = clusters => {
1060
1059
  })
1061
1060
  }
1062
1061
 
1063
- ecosystemSiblingEdgesForClusters(clusters, edgeByClusterPair)
1062
+ ecosystemSiblingEdgesForClusters(edgeClusters, edgeByClusterPair)
1064
1063
  const edges = Array.from(edgeByClusterPair.values())
1065
1064
  .sort((left, right) => right.weight - left.weight)
1066
1065
  .slice(0, ecosystemClusterEdgeLimit)
@@ -1078,7 +1077,7 @@ const ecosystemEdgesForClusters = clusters => {
1078
1077
  ? [edge.sourceCluster.id]
1079
1078
  : []
1080
1079
  ))
1081
- const syntheticHubEdges = clusters
1080
+ const syntheticHubEdges = edgeClusters
1082
1081
  .filter(cluster => cluster.id !== hubCluster.id && !existingHubTargets.has(cluster.id))
1083
1082
  .slice(0, ecosystemHubEdgeLimit)
1084
1083
  .map(cluster => ({
@@ -2530,11 +2529,11 @@ const clusterRadiusPx = cluster => {
2530
2529
  return 10
2531
2530
  }
2532
2531
  if (cluster.isHub) {
2533
- return 4.2
2532
+ return 3.2
2534
2533
  }
2535
2534
  if (String(cluster.id).startsWith('ecosystem-')) {
2536
- const base = cluster.size >= 1000 ? 1.2 : cluster.size >= 500 ? 1.1 : cluster.size >= 250 ? 1 : cluster.size >= 125 ? 0.92 : 0.86
2537
- return Math.max(0.85, Math.min(2.45, base + Math.log10(cluster.count + 1) * 0.18))
2535
+ const base = cluster.size >= 1000 ? 0.68 : cluster.size >= 500 ? 0.64 : cluster.size >= 250 ? 0.6 : cluster.size >= 125 ? 0.56 : 0.52
2536
+ return Math.max(0.5, Math.min(1.55, base + Math.log10(cluster.count + 1) * 0.12))
2538
2537
  }
2539
2538
  return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
2540
2539
  }
@@ -2703,9 +2702,12 @@ const computeRenderVisibility = () => {
2703
2702
  return
2704
2703
  }
2705
2704
 
2705
+ const ecosystemScaleThreshold = state.visibleNodes.length > massiveGraphNodeThreshold
2706
+ ? massiveEcosystemClusterScaleThreshold
2707
+ : ecosystemClusterScaleThreshold
2706
2708
  if (
2707
2709
  state.visibleNodes.length > ecosystemActivationNodeThreshold &&
2708
- state.transform.scale <= ecosystemClusterScaleThreshold &&
2710
+ state.transform.scale <= ecosystemScaleThreshold &&
2709
2711
  state.ecosystemClusters.length > 0
2710
2712
  ) {
2711
2713
  const clusters = selectHierarchicalEcosystemClusters(viewport)
@@ -3159,10 +3161,16 @@ const wheelZoomFactor = event => {
3159
3161
  return 1
3160
3162
  }
3161
3163
 
3162
- const sensitivity = wheelZoomExponent * (isModifierZoom ? wheelZoomModifierBoost : 1)
3164
+ const isMassiveEcosystemZoom =
3165
+ state.visibleNodes.length > massiveGraphNodeThreshold &&
3166
+ state.transform.scale <= massiveEcosystemClusterScaleThreshold
3167
+ const sensitivityMultiplier = isMassiveEcosystemZoom ? 0.62 : 1
3168
+ const capMultiplier = isMassiveEcosystemZoom ? 0.48 : 1
3169
+ const sensitivity = wheelZoomExponent * (isModifierZoom ? wheelZoomModifierBoost : 1) * sensitivityMultiplier
3170
+ const exponentCap = wheelZoomExponentCap * capMultiplier
3163
3171
  const exponent = Math.max(
3164
- -wheelZoomExponentCap,
3165
- Math.min(wheelZoomExponentCap, -normalizedDelta * sensitivity)
3172
+ -exponentCap,
3173
+ Math.min(exponentCap, -normalizedDelta * sensitivity)
3166
3174
  )
3167
3175
  return Math.exp(exponent)
3168
3176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.92",
3
+ "version": "0.1.0-beta.94",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",