@andespindola/brainlink 0.1.0-beta.107 → 0.1.0-beta.108

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
@@ -601,6 +601,8 @@ The graph UI shows:
601
601
  - floating graph totals (notes, links, tags) below the Brainlink title
602
602
  - graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
603
603
  - adaptive CPU safeguards for large graphs: idle frame pacing, throttled background physics updates and cached viewport dimensions to reduce redraw/layout overhead while preserving interaction responsiveness
604
+ - hierarchical hot-path optimizations reduce per-frame allocations and repeated scans during layered cluster expansion and edge projection
605
+ - hierarchical edge projection now caches hub membership and node-to-cluster resolution per frame to keep large recursive subgraph rendering smooth during continuous zoom and pan
604
606
  - WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
605
607
  - compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
606
608
  - 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
@@ -91,6 +91,8 @@ const state = {
91
91
  ecosystemClustersBySize: new Map(),
92
92
  ecosystemNodeClusterBySize: new Map(),
93
93
  ecosystemLevelSizes: [],
94
+ ecosystemLevelIndexBySize: new Map(),
95
+ ecosystemHubNodeIds: new Set(),
94
96
  ecosystemExpansionLevels: [],
95
97
  ecosystemBaseSize: ecosystemLevelNodeCap,
96
98
  ecosystemHubCluster: null,
@@ -617,6 +619,11 @@ const recomputeVisibility = () => {
617
619
  state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
618
620
  state.ecosystemNodeClusterBySize = ecosystemGraph.nodeClusterBySize
619
621
  state.ecosystemLevelSizes = ecosystemGraph.levelSizes
622
+ state.ecosystemLevelIndexBySize = ecosystemGraph.levelSizes.reduce((map, size, index) => {
623
+ map.set(size, index)
624
+ return map
625
+ }, new Map())
626
+ state.ecosystemHubNodeIds = new Set(ecosystemGraph.hubCluster?.nodeIds ?? [])
620
627
  state.ecosystemExpansionLevels = ecosystemGraph.expansionLevels
621
628
  state.ecosystemBaseSize = ecosystemGraph.baseSize
622
629
  state.ecosystemHubCluster = ecosystemGraph.hubCluster
@@ -1105,10 +1112,18 @@ const expandFocusedClusters = (parentClusters, focusPoint, childSize, progress,
1105
1112
  ecosystemFocusedParentLimit
1106
1113
  ))
1107
1114
  const childClusters = state.ecosystemClustersBySize.get(childSize) ?? []
1108
- const visibleChildClusters = childClusters
1109
- .filter(cluster => expandedParentIds.has(cluster.parentId))
1110
- .map(cluster => spreadChildClusterFromParent(cluster, childSize, progress, spread))
1111
- .filter(cluster => isClusterInViewport(cluster, viewport))
1115
+ const visibleChildClusters = []
1116
+ for (let index = 0; index < childClusters.length; index += 1) {
1117
+ const cluster = childClusters[index]
1118
+ if (!expandedParentIds.has(cluster.parentId)) {
1119
+ continue
1120
+ }
1121
+ const spreadCluster = spreadChildClusterFromParent(cluster, childSize, progress, spread)
1122
+ if (!isClusterInViewport(spreadCluster, viewport)) {
1123
+ continue
1124
+ }
1125
+ visibleChildClusters.push(spreadCluster)
1126
+ }
1112
1127
 
1113
1128
  return {
1114
1129
  expandedParentIds,
@@ -1137,11 +1152,21 @@ const selectHierarchicalEcosystemClusters = viewport => {
1137
1152
  const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
1138
1153
  const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
1139
1154
  const visibleClusters = [...visibleBaseClusters]
1155
+ const clustersBySize = new Map()
1156
+ for (let index = 0; index < visibleBaseClusters.length; index += 1) {
1157
+ const cluster = visibleBaseClusters[index]
1158
+ const levelClusters = clustersBySize.get(cluster.size)
1159
+ if (levelClusters) {
1160
+ levelClusters.push(cluster)
1161
+ } else {
1162
+ clustersBySize.set(cluster.size, [cluster])
1163
+ }
1164
+ }
1140
1165
  const focusPoint = ecosystemFocusPoint()
1141
1166
 
1142
1167
  for (let index = 0; index < state.ecosystemExpansionLevels.length; index += 1) {
1143
1168
  const level = state.ecosystemExpansionLevels[index]
1144
- const parentClusters = visibleClusters.filter(cluster => cluster.size === level.parentSize)
1169
+ const parentClusters = clustersBySize.get(level.parentSize) ?? []
1145
1170
  if (parentClusters.length === 0) {
1146
1171
  continue
1147
1172
  }
@@ -1154,18 +1179,20 @@ const selectHierarchicalEcosystemClusters = viewport => {
1154
1179
  const spread = semanticZoomSpread(progress, level.childSize)
1155
1180
  const expansion = expandFocusedClusters(parentClusters, focusPoint, level.childSize, progress, spread, viewport)
1156
1181
  visibleClusters.push(...expansion.childClusters)
1182
+ if (expansion.childClusters.length > 0) {
1183
+ const levelClusters = clustersBySize.get(level.childSize)
1184
+ if (levelClusters) {
1185
+ levelClusters.push(...expansion.childClusters)
1186
+ } else {
1187
+ clustersBySize.set(level.childSize, [...expansion.childClusters])
1188
+ }
1189
+ }
1157
1190
  }
1158
1191
 
1159
1192
  return [...hubClusters, ...visibleClusters]
1160
1193
  }
1161
1194
 
1162
- const ecosystemLevelIndexBySize = () => {
1163
- const indexBySize = new Map()
1164
- for (let index = 0; index < state.ecosystemLevelSizes.length; index += 1) {
1165
- indexBySize.set(state.ecosystemLevelSizes[index], index)
1166
- }
1167
- return indexBySize
1168
- }
1195
+ const ecosystemLevelIndexBySize = () => state.ecosystemLevelIndexBySize
1169
1196
 
1170
1197
  const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
1171
1198
  if (cluster.isHub) {
@@ -1216,20 +1243,20 @@ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
1216
1243
  clusterById.set(projectedCluster.id, projectedCluster)
1217
1244
  }
1218
1245
 
1219
- const projectedEdges = edges
1220
- .map((edge) => {
1221
- const sourceCluster = clusterById.get(edge.sourceCluster.id)
1222
- const targetCluster = clusterById.get(edge.targetCluster.id)
1223
- if (!sourceCluster || !targetCluster) {
1224
- return null
1225
- }
1226
- return {
1227
- ...edge,
1228
- sourceCluster,
1229
- targetCluster
1230
- }
1246
+ const projectedEdges = []
1247
+ for (let index = 0; index < edges.length; index += 1) {
1248
+ const edge = edges[index]
1249
+ const sourceCluster = clusterById.get(edge.sourceCluster.id)
1250
+ const targetCluster = clusterById.get(edge.targetCluster.id)
1251
+ if (!sourceCluster || !targetCluster) {
1252
+ continue
1253
+ }
1254
+ projectedEdges.push({
1255
+ ...edge,
1256
+ sourceCluster,
1257
+ targetCluster
1231
1258
  })
1232
- .filter(Boolean)
1259
+ }
1233
1260
 
1234
1261
  return {
1235
1262
  clusters: projectedClusters,
@@ -1289,28 +1316,37 @@ const ecosystemEdgesForClusters = clusters => {
1289
1316
  const clusterById = new Map(edgeClusters.map(cluster => [cluster.id, cluster]))
1290
1317
  const clusterIds = new Set(clusterById.keys())
1291
1318
  const levelsBySize = []
1319
+ const seenSizes = new Set()
1292
1320
  for (let index = 0; index < edgeClusters.length; index += 1) {
1293
1321
  const cluster = edgeClusters[index]
1294
1322
  if (!cluster.size || cluster.isHub) continue
1295
- if (!levelsBySize.some(level => level.size === cluster.size)) {
1296
- levelsBySize.push({
1297
- size: cluster.size,
1298
- lookup: state.ecosystemNodeClusterBySize.get(cluster.size) ?? new Map()
1299
- })
1300
- }
1323
+ if (seenSizes.has(cluster.size)) continue
1324
+ seenSizes.add(cluster.size)
1325
+ levelsBySize.push({
1326
+ size: cluster.size,
1327
+ lookup: state.ecosystemNodeClusterBySize.get(cluster.size) ?? new Map()
1328
+ })
1301
1329
  }
1302
1330
  levelsBySize.sort((left, right) => left.size - right.size)
1331
+ const resolvedNodeClusterById = new Map()
1303
1332
  const resolveClusterForNode = nodeId => {
1304
- if (state.ecosystemHubCluster?.nodeIds.includes(nodeId) && clusterIds.has(state.ecosystemHubCluster.id)) {
1333
+ if (resolvedNodeClusterById.has(nodeId)) {
1334
+ return resolvedNodeClusterById.get(nodeId)
1335
+ }
1336
+ if (state.ecosystemHubNodeIds.has(nodeId) && state.ecosystemHubCluster && clusterIds.has(state.ecosystemHubCluster.id)) {
1337
+ resolvedNodeClusterById.set(nodeId, state.ecosystemHubCluster)
1305
1338
  return state.ecosystemHubCluster
1306
1339
  }
1307
1340
  for (let index = 0; index < levelsBySize.length; index += 1) {
1308
1341
  const lookup = levelsBySize[index].lookup
1309
1342
  const cluster = lookup.get(nodeId)
1310
1343
  if (cluster && clusterIds.has(cluster.id)) {
1311
- return clusterById.get(cluster.id) ?? cluster
1344
+ const resolvedCluster = clusterById.get(cluster.id) ?? cluster
1345
+ resolvedNodeClusterById.set(nodeId, resolvedCluster)
1346
+ return resolvedCluster
1312
1347
  }
1313
1348
  }
1349
+ resolvedNodeClusterById.set(nodeId, null)
1314
1350
  return null
1315
1351
  }
1316
1352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.107",
3
+ "version": "0.1.0-beta.108",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",