@andespindola/brainlink 0.1.0-beta.105 → 0.1.0-beta.106

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
@@ -604,7 +604,7 @@ The graph UI shows:
604
604
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
605
605
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
606
606
  - 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
607
- - graph LOD progression: hierarchical rendering now follows one recursive graph-of-graphs standard whenever a graph has more than one hierarchy level; each level expands through intermediate subgraph sizes (instead of jumping directly to leaves), starts from a memory-hub-centered mesh, and each supernode can expand into another same-shape subgraph level (up to 999 children) with latent fade-in, aggregated real links and local sibling mesh links so org-heavy-like and stress-50k-like structures share the same layered behavior at different depths; for massive graphs the first expansion starts deeper in zoom and is additionally gated by focus readiness (screen-space isolation of the focused parent) so child levels open only when that subgraph is truly centered and separated in view
607
+ - graph LOD progression: hierarchical rendering now follows one recursive graph-of-graphs standard whenever a graph has more than one hierarchy level; each level expands through intermediate subgraph sizes (instead of jumping directly to leaves), starts from a memory-hub-centered mesh, and each supernode can expand into another same-shape subgraph level (up to 999 children) with latent fade-in, aggregated real links and local sibling mesh links so org-heavy-like and stress-50k-like structures share the same layered behavior at different depths; layered clusters also receive perspective depth projection (Z-depth) so expansion reads as a true depth field instead of a flat 2D switch; for massive graphs the first expansion starts deeper in zoom and is additionally gated by focus readiness (screen-space isolation of the focused parent) so child levels open only when that subgraph is truly centered and separated in view
608
608
 
609
609
  The server indexes before starting by default. Use `--no-index` to skip that step:
610
610
 
@@ -31,6 +31,10 @@ const massiveEcosystemClusterScaleThreshold = 4.2
31
31
  const ecosystemSubgraphScaleThreshold = 0.18
32
32
  const ecosystemMicroScaleThreshold = 0.08
33
33
  const ecosystemFocusedParentLimit = 2
34
+ const ecosystemDepthNear = 36
35
+ const ecosystemDepthFar = 560
36
+ const ecosystemDepthPerspective = 1400
37
+ const ecosystemDepthOpacityFloor = 0.42
34
38
  const zoomRecoveryGuardMs = 4200
35
39
  const zoomCapTargetViewportShare = 0.72
36
40
  const meshEdgeScaleThreshold = 0.09
@@ -1153,6 +1157,81 @@ const selectHierarchicalEcosystemClusters = viewport => {
1153
1157
  return [...hubClusters, ...visibleClusters]
1154
1158
  }
1155
1159
 
1160
+ const ecosystemLevelIndexBySize = () => {
1161
+ const indexBySize = new Map()
1162
+ for (let index = 0; index < state.ecosystemLevelSizes.length; index += 1) {
1163
+ indexBySize.set(state.ecosystemLevelSizes[index], index)
1164
+ }
1165
+ return indexBySize
1166
+ }
1167
+
1168
+ const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
1169
+ if (cluster.isHub) {
1170
+ return ecosystemDepthNear
1171
+ }
1172
+ const maxLevelIndex = Math.max(state.ecosystemLevelSizes.length - 1, 0)
1173
+ const levelIndex = levelIndexMap.get(cluster.size) ?? 0
1174
+ const reverseIndex = Math.max(0, maxLevelIndex - levelIndex)
1175
+ const normalized = maxLevelIndex === 0 ? 0 : reverseIndex / maxLevelIndex
1176
+ return ecosystemDepthNear + normalized * (ecosystemDepthFar - ecosystemDepthNear)
1177
+ }
1178
+
1179
+ const projectEcosystemPoint = (x, y, depth, anchor) => {
1180
+ const factor = ecosystemDepthPerspective / (ecosystemDepthPerspective + Math.max(0, depth))
1181
+ return {
1182
+ x: anchor.x + (x - anchor.x) * factor,
1183
+ y: anchor.y + (y - anchor.y) * factor,
1184
+ factor
1185
+ }
1186
+ }
1187
+
1188
+ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
1189
+ const levelIndexMap = ecosystemLevelIndexBySize()
1190
+ const projectedClusters = []
1191
+ const clusterById = new Map()
1192
+
1193
+ for (let index = 0; index < clusters.length; index += 1) {
1194
+ const cluster = clusters[index]
1195
+ const depth = ecosystemDepthForCluster(cluster, levelIndexMap)
1196
+ const projected = projectEcosystemPoint(cluster.x, cluster.y, depth, anchor)
1197
+ const baseOpacity = Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1
1198
+ const depthOpacity = Math.max(
1199
+ ecosystemDepthOpacityFloor,
1200
+ Math.min(1, 0.58 + projected.factor * 0.62)
1201
+ )
1202
+ const projectedCluster = {
1203
+ ...cluster,
1204
+ x: projected.x,
1205
+ y: projected.y,
1206
+ lodOpacity: baseOpacity * depthOpacity,
1207
+ depth,
1208
+ depthScale: projected.factor
1209
+ }
1210
+ projectedClusters.push(projectedCluster)
1211
+ clusterById.set(projectedCluster.id, projectedCluster)
1212
+ }
1213
+
1214
+ const projectedEdges = edges
1215
+ .map((edge) => {
1216
+ const sourceCluster = clusterById.get(edge.sourceCluster.id)
1217
+ const targetCluster = clusterById.get(edge.targetCluster.id)
1218
+ if (!sourceCluster || !targetCluster) {
1219
+ return null
1220
+ }
1221
+ return {
1222
+ ...edge,
1223
+ sourceCluster,
1224
+ targetCluster
1225
+ }
1226
+ })
1227
+ .filter(Boolean)
1228
+
1229
+ return {
1230
+ clusters: projectedClusters,
1231
+ edges: projectedEdges
1232
+ }
1233
+ }
1234
+
1156
1235
  const ecosystemSiblingEdgesForClusters = (clusters, existingEdges) => {
1157
1236
  const byParent = new Map()
1158
1237
  for (let index = 0; index < clusters.length; index += 1) {
@@ -2851,7 +2930,9 @@ const clusterRadiusPx = cluster => {
2851
2930
  const size = Math.max(1, Math.min(ecosystemLevelNodeCap, cluster.size || cluster.count || 1))
2852
2931
  const sizeBias = 0.56 + Math.log10(size + 1) * 0.28
2853
2932
  const densityBias = Math.log10((cluster.count || 1) + 1) * 0.12
2854
- return Math.max(0.62, Math.min(2.4, sizeBias + densityBias))
2933
+ const radius = Math.max(0.62, Math.min(2.4, sizeBias + densityBias))
2934
+ const depthScale = Number.isFinite(cluster.depthScale) ? cluster.depthScale : 1
2935
+ return Math.max(0.56, Math.min(3.2, radius * depthScale))
2855
2936
  }
2856
2937
  return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
2857
2938
  }
@@ -3029,8 +3110,11 @@ const computeRenderVisibility = () => {
3029
3110
  ) {
3030
3111
  const clusters = selectHierarchicalEcosystemClusters(viewport)
3031
3112
  .sort((left, right) => right.count - left.count)
3032
- state.renderClusters = clusters
3033
- state.renderClusterEdges = ecosystemEdgesForClusters(clusters)
3113
+ const edges = ecosystemEdgesForClusters(clusters)
3114
+ const projectionAnchor = ecosystemFocusPoint()
3115
+ const projected = applyEcosystemDepthProjection(clusters, edges, projectionAnchor)
3116
+ state.renderClusters = projected.clusters
3117
+ state.renderClusterEdges = projected.edges
3034
3118
  state.renderNodes = []
3035
3119
  state.renderEdges = []
3036
3120
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.105",
3
+ "version": "0.1.0-beta.106",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",