@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 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 optimized for large datasets with viewport-driven node culling and edge lookup by visible nodes.
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 LOD keeps a single flat graph scene, with viewport-driven node sampling and edge coverage safeguards to preserve visible relationships without switching to nested subgraphs.
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 LOD keeps a single-level graph model across zoom levels, preserving visual continuity while adjusting node/edge density from the active viewport
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
- if (state.visibleNodes.length <= 2000) {
2779
- state.renderNodes = state.visibleNodes
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) => {
@@ -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 use viewport-driven LOD sampling with edge-coverage safeguards to preserve visible relationships without switching into nested subgraph views.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.139",
3
+ "version": "0.1.0-beta.140",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",