@andespindola/brainlink 0.1.0-beta.139 → 0.1.0-beta.140
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 -3
- package/dist/application/frontend/client-js.js +2 -127
- package/docs/AGENT_USAGE.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,13 +82,13 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
82
82
|
- Built-in MCP stdio server for agent tool integration.
|
|
83
83
|
- Local HTTP API.
|
|
84
84
|
- Realtime graph UI with agent selector and colored knowledge groups.
|
|
85
|
-
- Graph renderer
|
|
85
|
+
- Graph renderer keeps the full filtered graph visible during zoom/pan, rendering every visible node and edge without viewport culling.
|
|
86
86
|
- 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.
|
|
87
87
|
- WebGL acceleration is used when available for dense node and edge drawing, with Canvas 2D preserved as the interaction and fallback layer.
|
|
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 fitted scene and zoom-in progressively reveals local detail.
|
|
91
|
-
- Zoomed-out graph
|
|
91
|
+
- Zoomed-out graph keeps the same flat graph scene and preserves complete filtered relationships without switching to nested subgraphs.
|
|
92
92
|
- Graph reset fits the full graph scene instead of starting in a separate macro overview mode.
|
|
93
93
|
- Graph filtering runs in a dedicated browser worker to keep the UI thread responsive during heavy datasets.
|
|
94
94
|
- Edge rendering budgets adapt to zoom level to prevent frame spikes on large graph panoramas.
|
|
@@ -606,7 +606,7 @@ The graph UI shows:
|
|
|
606
606
|
- graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
|
|
607
607
|
- 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
|
|
608
608
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
609
|
-
- large graph
|
|
609
|
+
- large graph view keeps a single-level graph model across zoom levels and renders the full filtered scene instead of viewport-sampled subsets
|
|
610
610
|
|
|
611
611
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
612
612
|
|
|
@@ -2775,133 +2775,8 @@ const computeRenderVisibility = () => {
|
|
|
2775
2775
|
state.lastViewportKey = viewportKey
|
|
2776
2776
|
state.renderVisibilityDirty = false
|
|
2777
2777
|
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
const ids = new Set(state.renderNodes.map((node) => node.id))
|
|
2781
|
-
state.renderEdges = limitRenderEdges(state.renderNodes, collectVisibleEdgesForNodes(ids, { preferComplete: true }))
|
|
2782
|
-
return
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
if (state.visibleNodes.length > massiveGraphNodeThreshold) {
|
|
2786
|
-
const sampleLimit = nodeBudgetForScale(state.transform.scale)
|
|
2787
|
-
if (state.transform.scale < massiveOverviewScaleThreshold) {
|
|
2788
|
-
const overviewNodes = sampleMassiveOverviewNodes(sampleLimit)
|
|
2789
|
-
const overviewIds = new Set(overviewNodes.map((node) => node.id))
|
|
2790
|
-
state.renderNodes = overviewNodes
|
|
2791
|
-
state.renderEdges = limitRenderEdges(overviewNodes, collectVisibleEdgesForNodes(overviewIds))
|
|
2792
|
-
return
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
2796
|
-
const segmentedNodes =
|
|
2797
|
-
state.transform.scale < massiveSegmentedScaleThreshold
|
|
2798
|
-
? sampleMassiveSegmentedNodes(sampleLimit, viewport)
|
|
2799
|
-
: []
|
|
2800
|
-
const sourceNodes =
|
|
2801
|
-
segmentedNodes.length > 0
|
|
2802
|
-
? segmentedNodes
|
|
2803
|
-
: viewportNodes.length > 0
|
|
2804
|
-
? viewportNodes
|
|
2805
|
-
: sampleMassiveOverviewNodes(sampleLimit)
|
|
2806
|
-
const shouldCarryOverNodes = state.transform.scale < 0.18
|
|
2807
|
-
const carryMargin = Math.max(180, Math.min(640, 260 / Math.max(state.transform.scale, 0.0001)))
|
|
2808
|
-
const carryViewport = expandViewportBounds(viewport, carryMargin)
|
|
2809
|
-
const carryOverLimit = shouldCarryOverNodes
|
|
2810
|
-
? Math.max(80, Math.min(sampleLimit, Math.floor(sampleLimit * 0.24)))
|
|
2811
|
-
: 0
|
|
2812
|
-
const carryOverNodes = shouldCarryOverNodes
|
|
2813
|
-
? (state.renderNodes ?? [])
|
|
2814
|
-
.filter((node) => isNodeInViewport(node, carryViewport))
|
|
2815
|
-
.slice(0, carryOverLimit)
|
|
2816
|
-
: []
|
|
2817
|
-
const sourceWithCarry = mergeUniqueNodes(
|
|
2818
|
-
sourceNodes,
|
|
2819
|
-
carryOverNodes,
|
|
2820
|
-
Math.max(sampleLimit * 7, carryOverLimit)
|
|
2821
|
-
)
|
|
2822
|
-
const sourceWithCarryIds = new Set(sourceWithCarry.map((node) => node.id))
|
|
2823
|
-
const sampledRaw = selectStableSampleNodes(
|
|
2824
|
-
sourceWithCarry,
|
|
2825
|
-
sampleLimit
|
|
2826
|
-
)
|
|
2827
|
-
const continuityBudget = Math.max(24, Math.min(sampleLimit - 8, Math.floor(sampleLimit * 0.42)))
|
|
2828
|
-
const previousVisibleNodes = (state.renderNodes ?? [])
|
|
2829
|
-
.filter((node) => sourceWithCarryIds.has(node.id))
|
|
2830
|
-
const continuityNodes = selectStableSampleNodes(previousVisibleNodes, continuityBudget)
|
|
2831
|
-
const sampled = mergeUniqueNodes(
|
|
2832
|
-
continuityNodes,
|
|
2833
|
-
sampledRaw,
|
|
2834
|
-
sampleLimit
|
|
2835
|
-
)
|
|
2836
|
-
let sampledNodes = ensureHubNodesInRenderedSet(sampled)
|
|
2837
|
-
if (state.transform.scale < 0.035) {
|
|
2838
|
-
sampledNodes = includeHubPreviewNeighborhood(
|
|
2839
|
-
sampledNodes,
|
|
2840
|
-
Math.min(renderNodeBudget, sampleLimit + 160)
|
|
2841
|
-
)
|
|
2842
|
-
}
|
|
2843
|
-
const sampledIds = new Set(sampledNodes.map((node) => node.id))
|
|
2844
|
-
let sampledEdges = collectVisibleEdgesForNodes(sampledIds)
|
|
2845
|
-
|
|
2846
|
-
if (state.transform.scale >= 0.035 && sampledEdges.length === 0) {
|
|
2847
|
-
const enriched = enrichSampleWithNeighbors(sampledNodes)
|
|
2848
|
-
sampledNodes = ensureHubNodesInRenderedSet(enriched.nodes)
|
|
2849
|
-
const sampledWithHubsIds = new Set(sampledNodes.map((node) => node.id))
|
|
2850
|
-
sampledEdges = collectVisibleEdgesForNodes(sampledWithHubsIds)
|
|
2851
|
-
}
|
|
2852
|
-
state.renderNodes = sampledNodes
|
|
2853
|
-
state.renderEdges = limitRenderEdges(sampledNodes, sampledEdges)
|
|
2854
|
-
return
|
|
2855
|
-
}
|
|
2856
|
-
|
|
2857
|
-
if (state.transform.scale <= 0.0015) {
|
|
2858
|
-
const sampled = sampleVisibleNodes(Math.min(renderNodeBudget, 900))
|
|
2859
|
-
const sampledIds = new Set(sampled.map((node) => node.id))
|
|
2860
|
-
state.renderNodes = sampled
|
|
2861
|
-
state.renderEdges = limitRenderEdges(sampled, collectVisibleEdgesForNodes(sampledIds))
|
|
2862
|
-
return
|
|
2863
|
-
}
|
|
2864
|
-
|
|
2865
|
-
const viewportNodes = viewportNodesFromSpatialIndex(viewport)
|
|
2866
|
-
const stride = viewportNodeStride()
|
|
2867
|
-
const picked = []
|
|
2868
|
-
|
|
2869
|
-
for (let index = 0; index < viewportNodes.length; index += 1) {
|
|
2870
|
-
const node = viewportNodes[index]
|
|
2871
|
-
|
|
2872
|
-
const isPriority =
|
|
2873
|
-
node.id === state.selected?.id ||
|
|
2874
|
-
node.id === state.hovered?.id ||
|
|
2875
|
-
node.id === state.pointer.dragNode?.id
|
|
2876
|
-
if (isPriority || index % stride === 0) {
|
|
2877
|
-
picked.push(node)
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
const nodes = picked.length > renderNodeBudget
|
|
2882
|
-
? picked.slice(0, renderNodeBudget)
|
|
2883
|
-
: picked
|
|
2884
|
-
if (nodes.length === 0 && state.visibleNodes.length > 0) {
|
|
2885
|
-
const fallbackNodes = fallbackViewportNodes()
|
|
2886
|
-
const fallbackIds = new Set(fallbackNodes.map((node) => node.id))
|
|
2887
|
-
state.renderNodes = fallbackNodes
|
|
2888
|
-
state.renderEdges = limitRenderEdges(fallbackNodes, collectVisibleEdgesForNodes(fallbackIds))
|
|
2889
|
-
return
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
const normalizedNodes = ensureHubNodesInRenderedSet(nodes)
|
|
2893
|
-
const nodeIds = new Set(normalizedNodes.map((node) => node.id))
|
|
2894
|
-
const edges = collectVisibleEdgesForNodes(nodeIds)
|
|
2895
|
-
|
|
2896
|
-
state.renderNodes = normalizedNodes
|
|
2897
|
-
state.renderEdges = limitRenderEdges(normalizedNodes, edges)
|
|
2898
|
-
|
|
2899
|
-
if (state.renderNodes.length === 0 && state.visibleNodes.length > 0) {
|
|
2900
|
-
const fallbackNodes = sampleVisibleNodes(Math.min(renderNodeBudget, 260))
|
|
2901
|
-
const fallbackIds = new Set(fallbackNodes.map((node) => node.id))
|
|
2902
|
-
state.renderNodes = fallbackNodes
|
|
2903
|
-
state.renderEdges = limitRenderEdges(fallbackNodes, collectVisibleEdgesForNodes(fallbackIds))
|
|
2904
|
-
}
|
|
2778
|
+
state.renderNodes = state.visibleNodes
|
|
2779
|
+
state.renderEdges = state.visibleEdges.filter((edge) => edge.targetNode)
|
|
2905
2780
|
}
|
|
2906
2781
|
|
|
2907
2782
|
const isNodeVisibleOnScreen = (node, width, height) => {
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -607,7 +607,7 @@ Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
|
607
607
|
|
|
608
608
|
The frontend includes an agent selector that shows only the agent id. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
|
|
609
609
|
|
|
610
|
-
Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom (including `cmd+scroll` and `ctrl+scroll`) is anchored to the cursor and applied immediately without delayed focus interpolation. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on empty canvas zooms in at cursor position. Clicking a node opens its details panel. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open in a non-modal side panel (tags, outgoing links, backlinks and Markdown content), so zoom and pan remain available during inspection. Vaults above 1000 notes keep the same single graph scene and
|
|
610
|
+
Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom (including `cmd+scroll` and `ctrl+scroll`) is anchored to the cursor and applied immediately without delayed focus interpolation. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on empty canvas zooms in at cursor position. Clicking a node opens its details panel. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open in a non-modal side panel (tags, outgoing links, backlinks and Markdown content), so zoom and pan remain available during inspection. Vaults above 1000 notes keep the same single graph scene and render the full filtered node/edge set during navigation, without viewport-sampled subgraph switching.
|
|
611
611
|
During graph filtering, Brainlink keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) so filtered views still show relationship anchors.
|
|
612
612
|
|
|
613
613
|
The command reindexes by default, then serves:
|
package/package.json
CHANGED