@andespindola/brainlink 0.1.0-beta.106 → 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 +3 -1
- package/dist/application/frontend/client-js.js +82 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -601,10 +601,12 @@ 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
|
|
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
|
|
609
|
+
- 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 stronger perspective depth projection (Z-depth) with vertical camera tilt/parallax 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
610
|
|
|
609
611
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
610
612
|
|
|
@@ -31,10 +31,12 @@ const massiveEcosystemClusterScaleThreshold = 4.2
|
|
|
31
31
|
const ecosystemSubgraphScaleThreshold = 0.18
|
|
32
32
|
const ecosystemMicroScaleThreshold = 0.08
|
|
33
33
|
const ecosystemFocusedParentLimit = 2
|
|
34
|
-
const ecosystemDepthNear =
|
|
35
|
-
const ecosystemDepthFar =
|
|
36
|
-
const ecosystemDepthPerspective =
|
|
37
|
-
const
|
|
34
|
+
const ecosystemDepthNear = 80
|
|
35
|
+
const ecosystemDepthFar = 2600
|
|
36
|
+
const ecosystemDepthPerspective = 620
|
|
37
|
+
const ecosystemDepthTiltY = 0.24
|
|
38
|
+
const ecosystemDepthMinScale = 0.24
|
|
39
|
+
const ecosystemDepthOpacityFloor = 0.2
|
|
38
40
|
const zoomRecoveryGuardMs = 4200
|
|
39
41
|
const zoomCapTargetViewportShare = 0.72
|
|
40
42
|
const meshEdgeScaleThreshold = 0.09
|
|
@@ -89,6 +91,8 @@ const state = {
|
|
|
89
91
|
ecosystemClustersBySize: new Map(),
|
|
90
92
|
ecosystemNodeClusterBySize: new Map(),
|
|
91
93
|
ecosystemLevelSizes: [],
|
|
94
|
+
ecosystemLevelIndexBySize: new Map(),
|
|
95
|
+
ecosystemHubNodeIds: new Set(),
|
|
92
96
|
ecosystemExpansionLevels: [],
|
|
93
97
|
ecosystemBaseSize: ecosystemLevelNodeCap,
|
|
94
98
|
ecosystemHubCluster: null,
|
|
@@ -615,6 +619,11 @@ const recomputeVisibility = () => {
|
|
|
615
619
|
state.ecosystemClustersBySize = ecosystemGraph.clustersBySize
|
|
616
620
|
state.ecosystemNodeClusterBySize = ecosystemGraph.nodeClusterBySize
|
|
617
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 ?? [])
|
|
618
627
|
state.ecosystemExpansionLevels = ecosystemGraph.expansionLevels
|
|
619
628
|
state.ecosystemBaseSize = ecosystemGraph.baseSize
|
|
620
629
|
state.ecosystemHubCluster = ecosystemGraph.hubCluster
|
|
@@ -1103,10 +1112,18 @@ const expandFocusedClusters = (parentClusters, focusPoint, childSize, progress,
|
|
|
1103
1112
|
ecosystemFocusedParentLimit
|
|
1104
1113
|
))
|
|
1105
1114
|
const childClusters = state.ecosystemClustersBySize.get(childSize) ?? []
|
|
1106
|
-
const visibleChildClusters =
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
.
|
|
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
|
+
}
|
|
1110
1127
|
|
|
1111
1128
|
return {
|
|
1112
1129
|
expandedParentIds,
|
|
@@ -1135,11 +1152,21 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
1135
1152
|
const visibleBaseClusters = filterEcosystemClustersByViewport(baseClusters, viewport)
|
|
1136
1153
|
const hubClusters = state.ecosystemHubCluster ? [state.ecosystemHubCluster] : []
|
|
1137
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
|
+
}
|
|
1138
1165
|
const focusPoint = ecosystemFocusPoint()
|
|
1139
1166
|
|
|
1140
1167
|
for (let index = 0; index < state.ecosystemExpansionLevels.length; index += 1) {
|
|
1141
1168
|
const level = state.ecosystemExpansionLevels[index]
|
|
1142
|
-
const parentClusters =
|
|
1169
|
+
const parentClusters = clustersBySize.get(level.parentSize) ?? []
|
|
1143
1170
|
if (parentClusters.length === 0) {
|
|
1144
1171
|
continue
|
|
1145
1172
|
}
|
|
@@ -1152,18 +1179,20 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
1152
1179
|
const spread = semanticZoomSpread(progress, level.childSize)
|
|
1153
1180
|
const expansion = expandFocusedClusters(parentClusters, focusPoint, level.childSize, progress, spread, viewport)
|
|
1154
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
|
+
}
|
|
1155
1190
|
}
|
|
1156
1191
|
|
|
1157
1192
|
return [...hubClusters, ...visibleClusters]
|
|
1158
1193
|
}
|
|
1159
1194
|
|
|
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
|
-
}
|
|
1195
|
+
const ecosystemLevelIndexBySize = () => state.ecosystemLevelIndexBySize
|
|
1167
1196
|
|
|
1168
1197
|
const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
|
|
1169
1198
|
if (cluster.isHub) {
|
|
@@ -1177,10 +1206,12 @@ const ecosystemDepthForCluster = (cluster, levelIndexMap) => {
|
|
|
1177
1206
|
}
|
|
1178
1207
|
|
|
1179
1208
|
const projectEcosystemPoint = (x, y, depth, anchor) => {
|
|
1180
|
-
const
|
|
1209
|
+
const safeDepth = Math.max(0, depth)
|
|
1210
|
+
const factor = ecosystemDepthPerspective / (ecosystemDepthPerspective + safeDepth)
|
|
1211
|
+
const verticalTilt = safeDepth * ecosystemDepthTiltY
|
|
1181
1212
|
return {
|
|
1182
1213
|
x: anchor.x + (x - anchor.x) * factor,
|
|
1183
|
-
y: anchor.y + (y - anchor.y) * factor,
|
|
1214
|
+
y: anchor.y + (y - anchor.y) * factor - verticalTilt,
|
|
1184
1215
|
factor
|
|
1185
1216
|
}
|
|
1186
1217
|
}
|
|
@@ -1195,9 +1226,10 @@ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
|
|
|
1195
1226
|
const depth = ecosystemDepthForCluster(cluster, levelIndexMap)
|
|
1196
1227
|
const projected = projectEcosystemPoint(cluster.x, cluster.y, depth, anchor)
|
|
1197
1228
|
const baseOpacity = Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1
|
|
1229
|
+
const depthScale = ecosystemDepthMinScale + (1 - ecosystemDepthMinScale) * projected.factor
|
|
1198
1230
|
const depthOpacity = Math.max(
|
|
1199
1231
|
ecosystemDepthOpacityFloor,
|
|
1200
|
-
Math.min(1,
|
|
1232
|
+
Math.min(1, depthScale * 1.08)
|
|
1201
1233
|
)
|
|
1202
1234
|
const projectedCluster = {
|
|
1203
1235
|
...cluster,
|
|
@@ -1205,26 +1237,26 @@ const applyEcosystemDepthProjection = (clusters, edges, anchor) => {
|
|
|
1205
1237
|
y: projected.y,
|
|
1206
1238
|
lodOpacity: baseOpacity * depthOpacity,
|
|
1207
1239
|
depth,
|
|
1208
|
-
depthScale
|
|
1240
|
+
depthScale
|
|
1209
1241
|
}
|
|
1210
1242
|
projectedClusters.push(projectedCluster)
|
|
1211
1243
|
clusterById.set(projectedCluster.id, projectedCluster)
|
|
1212
1244
|
}
|
|
1213
1245
|
|
|
1214
|
-
const projectedEdges =
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
|
1226
1258
|
})
|
|
1227
|
-
|
|
1259
|
+
}
|
|
1228
1260
|
|
|
1229
1261
|
return {
|
|
1230
1262
|
clusters: projectedClusters,
|
|
@@ -1284,28 +1316,37 @@ const ecosystemEdgesForClusters = clusters => {
|
|
|
1284
1316
|
const clusterById = new Map(edgeClusters.map(cluster => [cluster.id, cluster]))
|
|
1285
1317
|
const clusterIds = new Set(clusterById.keys())
|
|
1286
1318
|
const levelsBySize = []
|
|
1319
|
+
const seenSizes = new Set()
|
|
1287
1320
|
for (let index = 0; index < edgeClusters.length; index += 1) {
|
|
1288
1321
|
const cluster = edgeClusters[index]
|
|
1289
1322
|
if (!cluster.size || cluster.isHub) continue
|
|
1290
|
-
if (
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
}
|
|
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
|
+
})
|
|
1296
1329
|
}
|
|
1297
1330
|
levelsBySize.sort((left, right) => left.size - right.size)
|
|
1331
|
+
const resolvedNodeClusterById = new Map()
|
|
1298
1332
|
const resolveClusterForNode = nodeId => {
|
|
1299
|
-
if (
|
|
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)
|
|
1300
1338
|
return state.ecosystemHubCluster
|
|
1301
1339
|
}
|
|
1302
1340
|
for (let index = 0; index < levelsBySize.length; index += 1) {
|
|
1303
1341
|
const lookup = levelsBySize[index].lookup
|
|
1304
1342
|
const cluster = lookup.get(nodeId)
|
|
1305
1343
|
if (cluster && clusterIds.has(cluster.id)) {
|
|
1306
|
-
|
|
1344
|
+
const resolvedCluster = clusterById.get(cluster.id) ?? cluster
|
|
1345
|
+
resolvedNodeClusterById.set(nodeId, resolvedCluster)
|
|
1346
|
+
return resolvedCluster
|
|
1307
1347
|
}
|
|
1308
1348
|
}
|
|
1349
|
+
resolvedNodeClusterById.set(nodeId, null)
|
|
1309
1350
|
return null
|
|
1310
1351
|
}
|
|
1311
1352
|
|
package/package.json
CHANGED