@andespindola/brainlink 0.1.0-beta.86 → 0.1.0-beta.87
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 -2
- package/dist/application/frontend/client-js.js +63 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
84
84
|
- Graph renderer optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
|
|
85
85
|
- Canvas graph rendering uses the same batched node and edge pipeline for every graph size, reducing per-frame draw calls while keeping selected and hovered items highlighted.
|
|
86
86
|
- WebGL acceleration is used when available for dense node and edge drawing, with Canvas 2D preserved as the interaction and fallback layer.
|
|
87
|
-
- Graph zoom-out renders hierarchical ecosystem subgraphs:
|
|
87
|
+
- Graph zoom-out renders hierarchical ecosystem subgraphs only above 1000 notes: distant groups stay as small sand-like points and expand near the user's focus into smaller graph meshes before individual notes are rendered.
|
|
88
88
|
- Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
|
|
89
89
|
- Large-segment layout spacing now grows logarithmically to keep initial visual density consistent between medium and very large vaults (for example, ~1k vs ~50k notes).
|
|
90
90
|
- Graph coordinates are visually compacted across graph sizes so reset starts from a stable macro mass and zoom-in progressively expands toward local detail.
|
|
@@ -602,7 +602,7 @@ The graph UI shows:
|
|
|
602
602
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
603
603
|
- compact macro-to-micro density progression so reset keeps the graph mass oriented and zoom-in separates local neighborhoods progressively
|
|
604
604
|
- 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
|
|
605
|
-
- graph LOD progression:
|
|
605
|
+
- graph LOD progression: graphs up to 1000 notes render directly; larger graphs use connected sand-like ecosystem points of up to 1000 notes, zoom-in spreads only focused clusters into 250-note and 60-note subgraphs with aggregated real links, then progressively raises the focused node budget so local areas keep nearby notes and links visible
|
|
606
606
|
|
|
607
607
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
608
608
|
|
|
@@ -22,6 +22,7 @@ const worldCoordinateLimit = 5_000_000
|
|
|
22
22
|
const transformCoordinateLimit = 20_000_000
|
|
23
23
|
const hoverHitTestIntervalMs = 64
|
|
24
24
|
const ecosystemGroupSize = 1000
|
|
25
|
+
const ecosystemActivationNodeThreshold = 1000
|
|
25
26
|
const ecosystemGroupSizes = [1000, 250, 60]
|
|
26
27
|
const ecosystemClusterEdgeLimit = 520
|
|
27
28
|
const ecosystemClusterScaleThreshold = 0.32
|
|
@@ -569,7 +570,7 @@ const recomputeVisibility = () => {
|
|
|
569
570
|
state.visibleEdges = limitedEdges
|
|
570
571
|
state.visibleNodeSpatial = createSpatialIndex(nodes)
|
|
571
572
|
state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
|
|
572
|
-
const ecosystemGraph = nodes.length >
|
|
573
|
+
const ecosystemGraph = nodes.length > ecosystemActivationNodeThreshold
|
|
573
574
|
? buildEcosystemGraph(nodes)
|
|
574
575
|
: { clusters: [], clustersBySize: new Map() }
|
|
575
576
|
state.ecosystemClusters = ecosystemGraph.clusters
|
|
@@ -759,7 +760,9 @@ const buildEcosystemLevel = (sortedNodes, size, parentLookup) => {
|
|
|
759
760
|
...buildEcosystemCluster(clusterNodes, clusters.length),
|
|
760
761
|
id: 'ecosystem-' + size + '-' + clusters.length,
|
|
761
762
|
size,
|
|
762
|
-
parentId: parentCluster?.id ?? null
|
|
763
|
+
parentId: parentCluster?.id ?? null,
|
|
764
|
+
parentX: parentCluster?.x ?? null,
|
|
765
|
+
parentY: parentCluster?.y ?? null
|
|
763
766
|
}
|
|
764
767
|
clusters.push(cluster)
|
|
765
768
|
for (let index = 0; index < clusterNodes.length; index += 1) {
|
|
@@ -821,14 +824,42 @@ const nearestEcosystemParentIds = (clusters, focusPoint, limit) =>
|
|
|
821
824
|
.slice(0, limit)
|
|
822
825
|
.map(item => item.cluster.id)
|
|
823
826
|
|
|
827
|
+
const smoothStep = value => {
|
|
828
|
+
const clamped = Math.max(0, Math.min(1, value))
|
|
829
|
+
return clamped * clamped * (3 - clamped * 2)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const zoomProgress = (scale, start, end) =>
|
|
833
|
+
smoothStep((scale - start) / Math.max(end - start, 0.0001))
|
|
834
|
+
|
|
824
835
|
const ecosystemPlanForScale = scale => {
|
|
825
836
|
if (scale <= ecosystemMicroScaleThreshold) {
|
|
826
|
-
return { baseSize: 1000, childSize: null }
|
|
837
|
+
return { baseSize: 1000, childSize: null, spread: 0 }
|
|
827
838
|
}
|
|
828
839
|
if (scale <= ecosystemSubgraphScaleThreshold) {
|
|
829
|
-
return {
|
|
840
|
+
return {
|
|
841
|
+
baseSize: 1000,
|
|
842
|
+
childSize: 250,
|
|
843
|
+
spread: zoomProgress(scale, ecosystemMicroScaleThreshold, ecosystemSubgraphScaleThreshold)
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
baseSize: 250,
|
|
848
|
+
childSize: 60,
|
|
849
|
+
spread: zoomProgress(scale, ecosystemSubgraphScaleThreshold, ecosystemClusterScaleThreshold)
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const spreadChildClusterFromParent = (cluster, spread) => {
|
|
854
|
+
if (!Number.isFinite(cluster.parentX) || !Number.isFinite(cluster.parentY)) {
|
|
855
|
+
return cluster
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
...cluster,
|
|
860
|
+
x: cluster.parentX + (cluster.x - cluster.parentX) * spread,
|
|
861
|
+
y: cluster.parentY + (cluster.y - cluster.parentY) * spread
|
|
830
862
|
}
|
|
831
|
-
return { baseSize: 250, childSize: 60 }
|
|
832
863
|
}
|
|
833
864
|
|
|
834
865
|
const selectHierarchicalEcosystemClusters = viewport => {
|
|
@@ -850,7 +881,7 @@ const selectHierarchicalEcosystemClusters = viewport => {
|
|
|
850
881
|
const visibleChildClusters = childClusters.filter(cluster =>
|
|
851
882
|
expandedParentIds.has(cluster.parentId) &&
|
|
852
883
|
isClusterInViewport(cluster, viewport)
|
|
853
|
-
)
|
|
884
|
+
).map(cluster => spreadChildClusterFromParent(cluster, plan.spread))
|
|
854
885
|
|
|
855
886
|
if (visibleChildClusters.length === 0) {
|
|
856
887
|
return visibleBaseClusters
|
|
@@ -2337,6 +2368,17 @@ const baseNodeRadius = node => {
|
|
|
2337
2368
|
|
|
2338
2369
|
const nodeRadius = node => Math.max(baseNodeRadius(node), minNodePixelRadius / Math.max(state.transform.scale, 0.0001))
|
|
2339
2370
|
|
|
2371
|
+
const clusterRadiusPx = cluster => {
|
|
2372
|
+
if (cluster.id === 'macro-galaxy') {
|
|
2373
|
+
return 10
|
|
2374
|
+
}
|
|
2375
|
+
if (String(cluster.id).startsWith('ecosystem-')) {
|
|
2376
|
+
const base = cluster.size >= 1000 ? 2.4 : cluster.size >= 250 ? 2.1 : 1.8
|
|
2377
|
+
return Math.max(1.8, Math.min(4.2, base + Math.log10(cluster.count + 1) * 0.28))
|
|
2378
|
+
}
|
|
2379
|
+
return Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2340
2382
|
const worldViewportBounds = () => {
|
|
2341
2383
|
const rect = canvas.getBoundingClientRect()
|
|
2342
2384
|
const width = Math.max(rect.width, 320)
|
|
@@ -2498,7 +2540,11 @@ const computeRenderVisibility = () => {
|
|
|
2498
2540
|
return
|
|
2499
2541
|
}
|
|
2500
2542
|
|
|
2501
|
-
if (
|
|
2543
|
+
if (
|
|
2544
|
+
state.visibleNodes.length > ecosystemActivationNodeThreshold &&
|
|
2545
|
+
state.transform.scale <= ecosystemClusterScaleThreshold &&
|
|
2546
|
+
state.ecosystemClusters.length > 0
|
|
2547
|
+
) {
|
|
2502
2548
|
const clusters = selectHierarchicalEcosystemClusters(viewport)
|
|
2503
2549
|
.sort((left, right) => right.count - left.count)
|
|
2504
2550
|
state.renderClusters = clusters
|
|
@@ -2760,20 +2806,21 @@ const render = now => {
|
|
|
2760
2806
|
}
|
|
2761
2807
|
state.renderClusters.forEach(cluster => {
|
|
2762
2808
|
const isMacro = cluster.id === 'macro-galaxy'
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
: Math.max(8, Math.min(28, 8 + Math.log2(cluster.count + 1) * 3))
|
|
2809
|
+
const isEcosystem = String(cluster.id).startsWith('ecosystem-')
|
|
2810
|
+
const radiusPx = clusterRadiusPx(cluster)
|
|
2766
2811
|
const radius = radiusPx / safeScale
|
|
2767
|
-
const haloRadius = (radiusPx + (isMacro ? 8 : 4)) / safeScale
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2812
|
+
const haloRadius = (radiusPx + (isMacro ? 8 : isEcosystem ? 1.1 : 4)) / safeScale
|
|
2813
|
+
if (!isEcosystem || state.transform.scale >= ecosystemSubgraphScaleThreshold) {
|
|
2814
|
+
ctx.beginPath()
|
|
2815
|
+
ctx.arc(cluster.x, cluster.y, haloRadius, 0, Math.PI * 2)
|
|
2816
|
+
ctx.fillStyle = isMacro ? 'rgba(243, 247, 251, 0.28)' : graphTheme.nodeHalo
|
|
2817
|
+
ctx.fill()
|
|
2818
|
+
}
|
|
2772
2819
|
ctx.beginPath()
|
|
2773
2820
|
ctx.arc(cluster.x, cluster.y, radius, 0, Math.PI * 2)
|
|
2774
2821
|
ctx.fillStyle = isMacro ? '#f3f7fb' : graphTheme.node
|
|
2775
2822
|
ctx.fill()
|
|
2776
|
-
ctx.lineWidth = 1.4 / safeScale
|
|
2823
|
+
ctx.lineWidth = (isEcosystem ? 0.7 : 1.4) / safeScale
|
|
2777
2824
|
ctx.strokeStyle = isMacro ? '#ffffff' : graphTheme.nodeStroke
|
|
2778
2825
|
ctx.stroke()
|
|
2779
2826
|
if (isMacro && cluster.representative?.title) {
|
package/package.json
CHANGED