@andespindola/brainlink 0.1.0-beta.102 → 0.1.0-beta.104
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 +2 -1
- package/dist/application/frontend/client-js.js +49 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -595,11 +595,12 @@ The graph UI shows:
|
|
|
595
595
|
- realtime refresh while `--watch` is enabled
|
|
596
596
|
- graph controls for zoom in, zoom out, fit visible nodes and reset-to-fit-all
|
|
597
597
|
- wheel zoom (including `cmd+scroll` and `ctrl+scroll`) anchored to cursor position for faster navigation in large graphs
|
|
598
|
-
- zoom-out floor for large and massive graphs, plus reset macro floor tied to hub-neighbor distance and first-level cluster spacing,
|
|
598
|
+
- zoom-out floor for large and massive graphs, plus reset macro floor tied to hub-neighbor distance and first-level cluster spacing, with a tighter initial camera so the first graph appears as a closer cell instead of a distant mesh
|
|
599
599
|
- keyboard shortcuts: `+` zoom in, `-` zoom out, `0` reset fit
|
|
600
600
|
- double-click on canvas zooms in at cursor position
|
|
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
|
+
- 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
|
|
603
604
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
604
605
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
605
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
|
|
@@ -42,6 +42,10 @@ const dragSettleRounds = 3
|
|
|
42
42
|
const wheelZoomExponent = 0.0009
|
|
43
43
|
const wheelZoomExponentCap = 0.035
|
|
44
44
|
const wheelZoomModifierBoost = 1.08
|
|
45
|
+
const physicsDragFrameIntervalMs = 16
|
|
46
|
+
const physicsIdleFrameIntervalMs = 78
|
|
47
|
+
const physicsLargeGraphIdleFrameIntervalMs = 108
|
|
48
|
+
const physicsStepDeltaCapMs = 96
|
|
45
49
|
const state = {
|
|
46
50
|
graph: { nodes: [], edges: [] },
|
|
47
51
|
nodes: [],
|
|
@@ -67,7 +71,10 @@ const state = {
|
|
|
67
71
|
graphSignature: '',
|
|
68
72
|
graphStatus: '',
|
|
69
73
|
graphTotals: { nodes: 0, edges: 0 },
|
|
74
|
+
viewport: { width: 320, height: 320 },
|
|
70
75
|
last: performance.now(),
|
|
76
|
+
lastPhysicsAt: performance.now(),
|
|
77
|
+
physicsRestFrames: 0,
|
|
71
78
|
offscreenFrameCount: 0,
|
|
72
79
|
recoveringViewport: false,
|
|
73
80
|
renderVisibilityDirty: true,
|
|
@@ -434,6 +441,7 @@ const resize = () => {
|
|
|
434
441
|
glCanvas.width = Math.floor(width * ratio)
|
|
435
442
|
glCanvas.height = Math.floor(height * ratio)
|
|
436
443
|
}
|
|
444
|
+
state.viewport = { width, height }
|
|
437
445
|
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
|
|
438
446
|
markRenderDirty()
|
|
439
447
|
}
|
|
@@ -2214,9 +2222,9 @@ const currentZoomMax = () => {
|
|
|
2214
2222
|
}
|
|
2215
2223
|
|
|
2216
2224
|
const zoomFloorByNodeCount = (nodeCount) => {
|
|
2217
|
-
if (nodeCount > massiveGraphNodeThreshold) return 0.
|
|
2218
|
-
if (nodeCount > largeGraphNodeThreshold) return 0.
|
|
2219
|
-
if (nodeCount > ecosystemActivationNodeThreshold) return 0.
|
|
2225
|
+
if (nodeCount > massiveGraphNodeThreshold) return 0.0042
|
|
2226
|
+
if (nodeCount > largeGraphNodeThreshold) return 0.0021
|
|
2227
|
+
if (nodeCount > ecosystemActivationNodeThreshold) return 0.001
|
|
2220
2228
|
return zoomRange.min
|
|
2221
2229
|
}
|
|
2222
2230
|
|
|
@@ -2289,8 +2297,8 @@ const macroFaceToFaceScale = (nodeCount, hubDistance) => {
|
|
|
2289
2297
|
|
|
2290
2298
|
const rect = canvas.getBoundingClientRect()
|
|
2291
2299
|
const viewportReference = Math.max(320, Math.min(rect.width, rect.height))
|
|
2292
|
-
const share = nodeCount > massiveGraphNodeThreshold ? 0.
|
|
2293
|
-
const targetPx = Math.max(
|
|
2300
|
+
const share = nodeCount > massiveGraphNodeThreshold ? 0.082 : 0.068
|
|
2301
|
+
const targetPx = Math.max(24, viewportReference * share)
|
|
2294
2302
|
return targetPx / hubDistance
|
|
2295
2303
|
}
|
|
2296
2304
|
|
|
@@ -2328,8 +2336,8 @@ const macroEcosystemFaceScale = (nodeCount) => {
|
|
|
2328
2336
|
|
|
2329
2337
|
const rect = canvas.getBoundingClientRect()
|
|
2330
2338
|
const viewportReference = Math.max(320, Math.min(rect.width, rect.height))
|
|
2331
|
-
const targetShare = nodeCount > massiveGraphNodeThreshold ? 0.
|
|
2332
|
-
const targetPx = Math.max(
|
|
2339
|
+
const targetShare = nodeCount > massiveGraphNodeThreshold ? 0.114 : 0.096
|
|
2340
|
+
const targetPx = Math.max(30, viewportReference * targetShare)
|
|
2333
2341
|
return targetPx / nearestDistance
|
|
2334
2342
|
}
|
|
2335
2343
|
const fitView = (options = { useFiltered: true, macro: false, preferHubCenter: true }) => {
|
|
@@ -2565,7 +2573,7 @@ const scheduleContentFilterSync = () => {
|
|
|
2565
2573
|
}
|
|
2566
2574
|
}
|
|
2567
2575
|
|
|
2568
|
-
const tick = delta => {
|
|
2576
|
+
const tick = (delta, now) => {
|
|
2569
2577
|
const nodes = state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes
|
|
2570
2578
|
const edges = state.renderEdges.length > 0 ? state.renderEdges : state.visibleEdges
|
|
2571
2579
|
const shouldRunPhysics =
|
|
@@ -2573,9 +2581,24 @@ const tick = delta => {
|
|
|
2573
2581
|
nodes.length <= 320 &&
|
|
2574
2582
|
state.transform.scale >= 0.08
|
|
2575
2583
|
if (!shouldRunPhysics) {
|
|
2584
|
+
state.physicsRestFrames = 0
|
|
2576
2585
|
return
|
|
2577
2586
|
}
|
|
2578
|
-
const
|
|
2587
|
+
const isDragging = Boolean(state.pointer.dragNode)
|
|
2588
|
+
const intervalMs = isDragging
|
|
2589
|
+
? physicsDragFrameIntervalMs
|
|
2590
|
+
: state.nodes.length > largeGraphNodeThreshold
|
|
2591
|
+
? physicsLargeGraphIdleFrameIntervalMs
|
|
2592
|
+
: physicsIdleFrameIntervalMs
|
|
2593
|
+
if (!isDragging && state.physicsRestFrames >= 18) {
|
|
2594
|
+
return
|
|
2595
|
+
}
|
|
2596
|
+
const elapsedSincePhysics = now - state.lastPhysicsAt
|
|
2597
|
+
if (elapsedSincePhysics < intervalMs) {
|
|
2598
|
+
return
|
|
2599
|
+
}
|
|
2600
|
+
state.lastPhysicsAt = now
|
|
2601
|
+
const strength = Math.min(Math.max(elapsedSincePhysics, delta, 16) / 16, physicsStepDeltaCapMs / 16)
|
|
2579
2602
|
|
|
2580
2603
|
edges.forEach(edge => {
|
|
2581
2604
|
const source = edge.sourceNode
|
|
@@ -2617,6 +2640,7 @@ const tick = delta => {
|
|
|
2617
2640
|
}
|
|
2618
2641
|
}
|
|
2619
2642
|
|
|
2643
|
+
let kinetic = 0
|
|
2620
2644
|
nodes.forEach(node => {
|
|
2621
2645
|
node.vx = Number.isFinite(node.vx) ? node.vx : 0
|
|
2622
2646
|
node.vy = Number.isFinite(node.vy) ? node.vy : 0
|
|
@@ -2633,7 +2657,16 @@ const tick = delta => {
|
|
|
2633
2657
|
node.vy *= 0.88
|
|
2634
2658
|
node.x += node.vx * strength
|
|
2635
2659
|
node.y += node.vy * strength
|
|
2660
|
+
kinetic += Math.abs(node.vx) + Math.abs(node.vy)
|
|
2636
2661
|
})
|
|
2662
|
+
if (isDragging) {
|
|
2663
|
+
state.physicsRestFrames = 0
|
|
2664
|
+
return
|
|
2665
|
+
}
|
|
2666
|
+
const kineticFloor = Math.max(0.16, nodes.length * 0.01)
|
|
2667
|
+
state.physicsRestFrames = kinetic <= kineticFloor
|
|
2668
|
+
? state.physicsRestFrames + 1
|
|
2669
|
+
: 0
|
|
2637
2670
|
}
|
|
2638
2671
|
|
|
2639
2672
|
const worldPoint = event => {
|
|
@@ -2789,9 +2822,8 @@ const clusterOpacity = cluster =>
|
|
|
2789
2822
|
Math.max(0, Math.min(1, Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1))
|
|
2790
2823
|
|
|
2791
2824
|
const worldViewportBounds = () => {
|
|
2792
|
-
const
|
|
2793
|
-
const
|
|
2794
|
-
const height = Math.max(rect.height, 320)
|
|
2825
|
+
const width = Math.max(state.viewport.width, 320)
|
|
2826
|
+
const height = Math.max(state.viewport.height, 320)
|
|
2795
2827
|
const paddingMultiplier =
|
|
2796
2828
|
state.nodes.length > massiveGraphNodeThreshold
|
|
2797
2829
|
? (state.transform.scale >= 0.6 ? 2.8 : state.transform.scale >= 0.25 ? 2.35 : 1.9)
|
|
@@ -3136,10 +3168,10 @@ const render = now => {
|
|
|
3136
3168
|
state.last = now
|
|
3137
3169
|
const backgroundFrameIntervalMs =
|
|
3138
3170
|
state.nodes.length > massiveGraphNodeThreshold
|
|
3139
|
-
? (state.transform.scale < 0.035 ?
|
|
3171
|
+
? (state.transform.scale < 0.035 ? 156 : state.transform.scale < 0.08 ? 128 : 98)
|
|
3140
3172
|
: state.nodes.length > largeGraphNodeThreshold
|
|
3141
|
-
?
|
|
3142
|
-
:
|
|
3173
|
+
? 72
|
|
3174
|
+
: 18
|
|
3143
3175
|
const isInteracting =
|
|
3144
3176
|
state.pointer.down ||
|
|
3145
3177
|
state.renderVisibilityDirty ||
|
|
@@ -3152,6 +3184,7 @@ const render = now => {
|
|
|
3152
3184
|
const rect = canvas.getBoundingClientRect()
|
|
3153
3185
|
const width = Math.max(rect.width, 320)
|
|
3154
3186
|
const height = Math.max(rect.height, 320)
|
|
3187
|
+
state.viewport = { width, height }
|
|
3155
3188
|
sanitizeGraphState()
|
|
3156
3189
|
if (!hasValidTransform()) {
|
|
3157
3190
|
resetView()
|
|
@@ -3168,7 +3201,7 @@ const render = now => {
|
|
|
3168
3201
|
}
|
|
3169
3202
|
|
|
3170
3203
|
computeRenderVisibility()
|
|
3171
|
-
tick(delta)
|
|
3204
|
+
tick(delta, now)
|
|
3172
3205
|
const hasVisibleNodeOnScreen = state.renderNodes.some((node) => isNodeVisibleOnScreen(node, width, height))
|
|
3173
3206
|
const manualZoomGuardActive = now - state.lastManualZoomAt < zoomRecoveryGuardMs
|
|
3174
3207
|
const allowViewportAutoRecovery = state.nodes.length <= massiveGraphNodeThreshold
|
package/package.json
CHANGED