@andespindola/brainlink 0.1.0-beta.77 → 0.1.0-beta.79
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
|
@@ -83,7 +83,8 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
83
83
|
- Realtime graph UI with agent selector and colored knowledge groups.
|
|
84
84
|
- Graph renderer optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
|
|
85
85
|
- Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
|
|
86
|
-
-
|
|
86
|
+
- 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).
|
|
87
|
+
- Zoomed-out graph LOD clusters dense regions and progressively expands the focused viewport as zoom increases, including very large vaults.
|
|
87
88
|
- Graph reset starts in macro "galaxy" overview mode and progressively reveals nearby nodes as zoom increases, including smaller vaults.
|
|
88
89
|
- Graph filtering runs in a dedicated browser worker to keep the UI thread responsive during heavy datasets.
|
|
89
90
|
- Edge rendering budgets adapt to zoom level to prevent frame spikes on large graph panoramas.
|
|
@@ -594,7 +595,7 @@ The graph UI shows:
|
|
|
594
595
|
- double-click on canvas zooms in at cursor position
|
|
595
596
|
- floating graph totals (notes, links, tags) below the Brainlink title
|
|
596
597
|
- large-graph rendering safeguards (edge draw caps, lower redraw rate, zoom-aware interaction)
|
|
597
|
-
- massive-graph LOD progression: very low zoom uses spatial overview sampling plus hub-neighborhood edge previews to preserve whole-vault shape and orientation, then progressively
|
|
598
|
+
- massive-graph LOD progression: very low zoom uses spatial overview sampling plus hub-neighborhood edge previews to preserve whole-vault shape and orientation, then progressively raises the focused node budget as zoom increases so dense local areas keep nearby notes and links visible
|
|
598
599
|
|
|
599
600
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
600
601
|
|
|
@@ -4,6 +4,7 @@ const largeGraphNodeThreshold = 4000
|
|
|
4
4
|
const massiveGraphNodeThreshold = 20000
|
|
5
5
|
const largeGraphEdgeRenderLimit = 120000
|
|
6
6
|
const renderNodeBudget = 900
|
|
7
|
+
const zoomedMassiveRenderNodeBudget = 2200
|
|
7
8
|
const renderEdgeBudget = 2400
|
|
8
9
|
const clusterActivationNodeThreshold = 600
|
|
9
10
|
const clusterZoomThreshold = 0.18
|
|
@@ -576,6 +577,13 @@ const nodeBudgetForScale = (scale) => {
|
|
|
576
577
|
if (scale < 0.06) return 360
|
|
577
578
|
if (scale < 0.09) return 520
|
|
578
579
|
if (scale < 0.14) return 720
|
|
580
|
+
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
581
|
+
if (scale < 0.28) return renderNodeBudget
|
|
582
|
+
if (scale < 0.45) return 1100
|
|
583
|
+
if (scale < 0.7) return 1400
|
|
584
|
+
if (scale < 1.05) return 1800
|
|
585
|
+
return zoomedMassiveRenderNodeBudget
|
|
586
|
+
}
|
|
579
587
|
return renderNodeBudget
|
|
580
588
|
}
|
|
581
589
|
|
|
@@ -739,16 +747,22 @@ const selectStableSampleNodes = (sourceNodes, limit) => {
|
|
|
739
747
|
: null
|
|
740
748
|
const anchor = recentZoomFocus ?? viewportCenterWorldPoint()
|
|
741
749
|
const previousIds = new Set(state.renderNodes.map((node) => node.id))
|
|
750
|
+
const preferAnchorDistance = state.visibleNodes.length > massiveGraphNodeThreshold && state.transform.scale >= 0.28
|
|
742
751
|
|
|
743
752
|
return [...sourceNodes]
|
|
744
753
|
.sort((left, right) => {
|
|
745
754
|
const leftWasVisible = previousIds.has(left.id) ? 1 : 0
|
|
746
755
|
const rightWasVisible = previousIds.has(right.id) ? 1 : 0
|
|
747
|
-
if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
|
|
748
|
-
|
|
749
756
|
const leftDistance = Math.hypot(left.x - anchor.x, left.y - anchor.y)
|
|
750
757
|
const rightDistance = Math.hypot(right.x - anchor.x, right.y - anchor.y)
|
|
751
|
-
|
|
758
|
+
|
|
759
|
+
if (preferAnchorDistance) {
|
|
760
|
+
if (leftDistance !== rightDistance) return leftDistance - rightDistance
|
|
761
|
+
if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
|
|
762
|
+
} else {
|
|
763
|
+
if (leftWasVisible !== rightWasVisible) return rightWasVisible - leftWasVisible
|
|
764
|
+
if (leftDistance !== rightDistance) return leftDistance - rightDistance
|
|
765
|
+
}
|
|
752
766
|
|
|
753
767
|
const leftDegree = state.nodeDegrees.get(left.id) ?? 0
|
|
754
768
|
const rightDegree = state.nodeDegrees.get(right.id) ?? 0
|
|
@@ -1942,7 +1956,7 @@ const computeRenderVisibility = () => {
|
|
|
1942
1956
|
const sampleLimit = nodeBudgetForScale(state.transform.scale)
|
|
1943
1957
|
const carryMargin = Math.max(240, Math.min(1200, 340 / Math.max(state.transform.scale, 0.0001)))
|
|
1944
1958
|
const carryViewport = expandViewportBounds(viewport, carryMargin)
|
|
1945
|
-
const carryOverLimit = Math.max(180, Math.min(
|
|
1959
|
+
const carryOverLimit = Math.max(180, Math.min(sampleLimit, Math.floor(sampleLimit * 0.5)))
|
|
1946
1960
|
const carryOverNodes = (state.renderNodes ?? [])
|
|
1947
1961
|
.filter((node) => isNodeInViewport(node, carryViewport))
|
|
1948
1962
|
.slice(0, carryOverLimit)
|
|
@@ -1951,18 +1965,19 @@ const computeRenderVisibility = () => {
|
|
|
1951
1965
|
carryOverNodes,
|
|
1952
1966
|
Math.max(sampleLimit * 7, carryOverLimit)
|
|
1953
1967
|
)
|
|
1968
|
+
const sourceWithCarryIds = new Set(sourceWithCarry.map((node) => node.id))
|
|
1954
1969
|
const sampledRaw = selectStableSampleNodes(
|
|
1955
1970
|
sourceWithCarry,
|
|
1956
|
-
|
|
1971
|
+
sampleLimit
|
|
1957
1972
|
)
|
|
1958
1973
|
const continuityBudget = Math.max(24, Math.min(sampleLimit - 8, Math.floor(sampleLimit * 0.42)))
|
|
1959
1974
|
const previousVisibleNodes = (state.renderNodes ?? [])
|
|
1960
|
-
.filter((node) =>
|
|
1975
|
+
.filter((node) => sourceWithCarryIds.has(node.id))
|
|
1961
1976
|
const continuityNodes = selectStableSampleNodes(previousVisibleNodes, continuityBudget)
|
|
1962
1977
|
const sampled = mergeUniqueNodes(
|
|
1963
1978
|
continuityNodes,
|
|
1964
1979
|
sampledRaw,
|
|
1965
|
-
|
|
1980
|
+
sampleLimit
|
|
1966
1981
|
)
|
|
1967
1982
|
let sampledNodes = ensureHubNodesInRenderedSet(sampled)
|
|
1968
1983
|
if (state.transform.scale < 0.035) {
|
|
@@ -167,13 +167,17 @@ const groupNodesBySegment = (nodes, segments) => {
|
|
|
167
167
|
return new Map(groups);
|
|
168
168
|
};
|
|
169
169
|
const segmentAngle = (segment, index, count) => segmentAngles[segment] ?? (Math.PI * 2 * index) / Math.max(count, 1) - Math.PI / 2;
|
|
170
|
+
const petalSpreadForSegmentSize = (size) => {
|
|
171
|
+
const safeSize = Math.max(size, 1);
|
|
172
|
+
return 180 + Math.log2(safeSize + 1) * 6;
|
|
173
|
+
};
|
|
170
174
|
const createSegmentNodes = (segments, degrees, segmentCount) => ([segment, nodes], segmentIndex) => {
|
|
171
175
|
const sortedNodes = [...nodes].sort(byDegreeThenTitle(degrees));
|
|
172
176
|
const angle = segmentAngle(segment, segmentIndex, segmentCount);
|
|
173
177
|
const baseRadius = segmentCount === 1 ? 0 : 340 + Math.min(sortedNodes.length, 22) * 10;
|
|
174
178
|
const centerX = Math.cos(angle) * baseRadius;
|
|
175
179
|
const centerY = Math.sin(angle) * (baseRadius * 0.78);
|
|
176
|
-
const petalSpread =
|
|
180
|
+
const petalSpread = petalSpreadForSegmentSize(sortedNodes.length);
|
|
177
181
|
return sortedNodes.map((node, index) => {
|
|
178
182
|
const localAngle = index * 2.399963 + jitter(node.title, 0.42);
|
|
179
183
|
const localRadius = Math.sqrt(index + 1) * petalSpread;
|
package/package.json
CHANGED