@andespindola/brainlink 0.1.0-beta.103 → 0.1.0-beta.105
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 +88 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -600,10 +600,11 @@ The graph UI shows:
|
|
|
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
|
|
606
|
-
- graph LOD progression:
|
|
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
608
|
|
|
608
609
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
609
610
|
|
|
@@ -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
|
}
|
|
@@ -588,7 +596,7 @@ const recomputeVisibility = () => {
|
|
|
588
596
|
y: macroHub ? macroHub.y : (bounds.minY + bounds.maxY) / 2
|
|
589
597
|
}
|
|
590
598
|
: { x: 0, y: 0 }
|
|
591
|
-
const ecosystemGraph = nodes.length >
|
|
599
|
+
const ecosystemGraph = nodes.length > 1
|
|
592
600
|
? buildEcosystemGraph(nodes, state.macroCenter, primaryHub)
|
|
593
601
|
: {
|
|
594
602
|
clusters: [],
|
|
@@ -760,12 +768,29 @@ const ecosystemLayoutSpacingForSize = size => {
|
|
|
760
768
|
return 7
|
|
761
769
|
}
|
|
762
770
|
|
|
771
|
+
const buildIntermediateEcosystemSizes = (fromSize, toSize) => {
|
|
772
|
+
if (fromSize <= toSize + 1) {
|
|
773
|
+
return []
|
|
774
|
+
}
|
|
775
|
+
const intermediate = []
|
|
776
|
+
let current = fromSize
|
|
777
|
+
while (current > toSize + 1) {
|
|
778
|
+
const stepped = Math.max(toSize + 1, Math.ceil(current / 3))
|
|
779
|
+
if (stepped >= current) {
|
|
780
|
+
break
|
|
781
|
+
}
|
|
782
|
+
intermediate.push(stepped)
|
|
783
|
+
current = stepped
|
|
784
|
+
}
|
|
785
|
+
return intermediate
|
|
786
|
+
}
|
|
787
|
+
|
|
763
788
|
const buildEcosystemLevelSizes = nodeCount => {
|
|
764
789
|
if (nodeCount <= 0) return []
|
|
765
|
-
const
|
|
790
|
+
const primarySizes = []
|
|
766
791
|
let currentSize = Math.max(1, Math.ceil(nodeCount / ecosystemLevelNodeCap))
|
|
767
792
|
while (currentSize >= 1) {
|
|
768
|
-
|
|
793
|
+
primarySizes.push(currentSize)
|
|
769
794
|
if (currentSize === 1) {
|
|
770
795
|
break
|
|
771
796
|
}
|
|
@@ -775,7 +800,28 @@ const buildEcosystemLevelSizes = nodeCount => {
|
|
|
775
800
|
}
|
|
776
801
|
currentSize = nextSize
|
|
777
802
|
}
|
|
778
|
-
|
|
803
|
+
const expandedSizes = []
|
|
804
|
+
for (let index = 0; index < primarySizes.length; index += 1) {
|
|
805
|
+
const size = primarySizes[index]
|
|
806
|
+
if (expandedSizes.length === 0 || expandedSizes[expandedSizes.length - 1] !== size) {
|
|
807
|
+
expandedSizes.push(size)
|
|
808
|
+
}
|
|
809
|
+
const nextSize = primarySizes[index + 1]
|
|
810
|
+
if (!Number.isFinite(nextSize)) {
|
|
811
|
+
continue
|
|
812
|
+
}
|
|
813
|
+
const intermediate = buildIntermediateEcosystemSizes(size, nextSize)
|
|
814
|
+
for (let intermediateIndex = 0; intermediateIndex < intermediate.length; intermediateIndex += 1) {
|
|
815
|
+
const candidate = intermediate[intermediateIndex]
|
|
816
|
+
if (expandedSizes[expandedSizes.length - 1] !== candidate) {
|
|
817
|
+
expandedSizes.push(candidate)
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (expandedSizes[expandedSizes.length - 1] !== 1) {
|
|
822
|
+
expandedSizes.push(1)
|
|
823
|
+
}
|
|
824
|
+
return expandedSizes
|
|
779
825
|
}
|
|
780
826
|
|
|
781
827
|
const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
@@ -786,12 +832,12 @@ const buildEcosystemExpansionLevels = (levelSizes, nodeCount) => {
|
|
|
786
832
|
const maxScale = isMassive
|
|
787
833
|
? massiveEcosystemClusterScaleThreshold
|
|
788
834
|
: ecosystemClusterScaleThreshold
|
|
789
|
-
const startScale = isMassive ?
|
|
835
|
+
const startScale = isMassive ? 1.12 : 0.24
|
|
790
836
|
const transitionCount = levelSizes.length - 1
|
|
791
837
|
const usableScale = Math.max(0.08, maxScale - startScale)
|
|
792
838
|
const step = usableScale / transitionCount
|
|
793
|
-
const stride = isMassive ? 0.
|
|
794
|
-
const overlap = isMassive ? 1.
|
|
839
|
+
const stride = isMassive ? 0.93 : 0.82
|
|
840
|
+
const overlap = isMassive ? 1.22 : 1.62
|
|
795
841
|
const levels = []
|
|
796
842
|
for (let index = 0; index < transitionCount; index += 1) {
|
|
797
843
|
const start = startScale + step * index * stride
|
|
@@ -2565,7 +2611,7 @@ const scheduleContentFilterSync = () => {
|
|
|
2565
2611
|
}
|
|
2566
2612
|
}
|
|
2567
2613
|
|
|
2568
|
-
const tick = delta => {
|
|
2614
|
+
const tick = (delta, now) => {
|
|
2569
2615
|
const nodes = state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes
|
|
2570
2616
|
const edges = state.renderEdges.length > 0 ? state.renderEdges : state.visibleEdges
|
|
2571
2617
|
const shouldRunPhysics =
|
|
@@ -2573,9 +2619,24 @@ const tick = delta => {
|
|
|
2573
2619
|
nodes.length <= 320 &&
|
|
2574
2620
|
state.transform.scale >= 0.08
|
|
2575
2621
|
if (!shouldRunPhysics) {
|
|
2622
|
+
state.physicsRestFrames = 0
|
|
2623
|
+
return
|
|
2624
|
+
}
|
|
2625
|
+
const isDragging = Boolean(state.pointer.dragNode)
|
|
2626
|
+
const intervalMs = isDragging
|
|
2627
|
+
? physicsDragFrameIntervalMs
|
|
2628
|
+
: state.nodes.length > largeGraphNodeThreshold
|
|
2629
|
+
? physicsLargeGraphIdleFrameIntervalMs
|
|
2630
|
+
: physicsIdleFrameIntervalMs
|
|
2631
|
+
if (!isDragging && state.physicsRestFrames >= 18) {
|
|
2632
|
+
return
|
|
2633
|
+
}
|
|
2634
|
+
const elapsedSincePhysics = now - state.lastPhysicsAt
|
|
2635
|
+
if (elapsedSincePhysics < intervalMs) {
|
|
2576
2636
|
return
|
|
2577
2637
|
}
|
|
2578
|
-
|
|
2638
|
+
state.lastPhysicsAt = now
|
|
2639
|
+
const strength = Math.min(Math.max(elapsedSincePhysics, delta, 16) / 16, physicsStepDeltaCapMs / 16)
|
|
2579
2640
|
|
|
2580
2641
|
edges.forEach(edge => {
|
|
2581
2642
|
const source = edge.sourceNode
|
|
@@ -2617,6 +2678,7 @@ const tick = delta => {
|
|
|
2617
2678
|
}
|
|
2618
2679
|
}
|
|
2619
2680
|
|
|
2681
|
+
let kinetic = 0
|
|
2620
2682
|
nodes.forEach(node => {
|
|
2621
2683
|
node.vx = Number.isFinite(node.vx) ? node.vx : 0
|
|
2622
2684
|
node.vy = Number.isFinite(node.vy) ? node.vy : 0
|
|
@@ -2633,7 +2695,16 @@ const tick = delta => {
|
|
|
2633
2695
|
node.vy *= 0.88
|
|
2634
2696
|
node.x += node.vx * strength
|
|
2635
2697
|
node.y += node.vy * strength
|
|
2698
|
+
kinetic += Math.abs(node.vx) + Math.abs(node.vy)
|
|
2636
2699
|
})
|
|
2700
|
+
if (isDragging) {
|
|
2701
|
+
state.physicsRestFrames = 0
|
|
2702
|
+
return
|
|
2703
|
+
}
|
|
2704
|
+
const kineticFloor = Math.max(0.16, nodes.length * 0.01)
|
|
2705
|
+
state.physicsRestFrames = kinetic <= kineticFloor
|
|
2706
|
+
? state.physicsRestFrames + 1
|
|
2707
|
+
: 0
|
|
2637
2708
|
}
|
|
2638
2709
|
|
|
2639
2710
|
const worldPoint = event => {
|
|
@@ -2789,9 +2860,8 @@ const clusterOpacity = cluster =>
|
|
|
2789
2860
|
Math.max(0, Math.min(1, Number.isFinite(cluster.lodOpacity) ? cluster.lodOpacity : 1))
|
|
2790
2861
|
|
|
2791
2862
|
const worldViewportBounds = () => {
|
|
2792
|
-
const
|
|
2793
|
-
const
|
|
2794
|
-
const height = Math.max(rect.height, 320)
|
|
2863
|
+
const width = Math.max(state.viewport.width, 320)
|
|
2864
|
+
const height = Math.max(state.viewport.height, 320)
|
|
2795
2865
|
const paddingMultiplier =
|
|
2796
2866
|
state.nodes.length > massiveGraphNodeThreshold
|
|
2797
2867
|
? (state.transform.scale >= 0.6 ? 2.8 : state.transform.scale >= 0.25 ? 2.35 : 1.9)
|
|
@@ -2953,7 +3023,7 @@ const computeRenderVisibility = () => {
|
|
|
2953
3023
|
? massiveEcosystemClusterScaleThreshold
|
|
2954
3024
|
: ecosystemClusterScaleThreshold
|
|
2955
3025
|
if (
|
|
2956
|
-
state.
|
|
3026
|
+
state.ecosystemExpansionLevels.length > 0 &&
|
|
2957
3027
|
state.transform.scale <= ecosystemScaleThreshold &&
|
|
2958
3028
|
state.ecosystemClusters.length > 0
|
|
2959
3029
|
) {
|
|
@@ -3136,10 +3206,10 @@ const render = now => {
|
|
|
3136
3206
|
state.last = now
|
|
3137
3207
|
const backgroundFrameIntervalMs =
|
|
3138
3208
|
state.nodes.length > massiveGraphNodeThreshold
|
|
3139
|
-
? (state.transform.scale < 0.035 ?
|
|
3209
|
+
? (state.transform.scale < 0.035 ? 156 : state.transform.scale < 0.08 ? 128 : 98)
|
|
3140
3210
|
: state.nodes.length > largeGraphNodeThreshold
|
|
3141
|
-
?
|
|
3142
|
-
:
|
|
3211
|
+
? 72
|
|
3212
|
+
: 18
|
|
3143
3213
|
const isInteracting =
|
|
3144
3214
|
state.pointer.down ||
|
|
3145
3215
|
state.renderVisibilityDirty ||
|
|
@@ -3152,6 +3222,7 @@ const render = now => {
|
|
|
3152
3222
|
const rect = canvas.getBoundingClientRect()
|
|
3153
3223
|
const width = Math.max(rect.width, 320)
|
|
3154
3224
|
const height = Math.max(rect.height, 320)
|
|
3225
|
+
state.viewport = { width, height }
|
|
3155
3226
|
sanitizeGraphState()
|
|
3156
3227
|
if (!hasValidTransform()) {
|
|
3157
3228
|
resetView()
|
|
@@ -3168,7 +3239,7 @@ const render = now => {
|
|
|
3168
3239
|
}
|
|
3169
3240
|
|
|
3170
3241
|
computeRenderVisibility()
|
|
3171
|
-
tick(delta)
|
|
3242
|
+
tick(delta, now)
|
|
3172
3243
|
const hasVisibleNodeOnScreen = state.renderNodes.some((node) => isNodeVisibleOnScreen(node, width, height))
|
|
3173
3244
|
const manualZoomGuardActive = now - state.lastManualZoomAt < zoomRecoveryGuardMs
|
|
3174
3245
|
const allowViewportAutoRecovery = state.nodes.length <= massiveGraphNodeThreshold
|
package/package.json
CHANGED