@andespindola/brainlink 0.1.0-beta.140 → 0.1.0-beta.141

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,7 +82,7 @@ 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 keeps the full filtered graph visible during zoom/pan, rendering every visible node and edge without viewport culling.
85
+ - Graph renderer keeps the full filtered graph visible during zoom/pan, rendering every visible node and edge without viewport culling or edge caps in the main view.
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.
@@ -91,7 +91,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
91
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
- - Edge rendering budgets adapt to zoom level to prevent frame spikes on large graph panoramas.
94
+ - Node titles are shown as the user zooms closer, while labels remain bounded to visible on-screen nodes in very large graphs.
95
95
 
96
96
  ## Install
97
97
 
@@ -603,10 +603,10 @@ The graph UI shows:
603
603
  - keyboard shortcuts: `+` zoom in, `-` zoom out, `0` reset fit
604
604
  - click on a node opens its details panel; double-click on empty canvas zooms in at cursor position
605
605
  - floating graph totals (notes, links, tags) below the Brainlink title
606
- - graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
606
+ - graph rendering safeguards (batched canvas drawing across graph sizes, 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 view keeps a single-level graph model across zoom levels and renders the full filtered scene instead of viewport-sampled subsets
609
+ - large graph view keeps a single-level graph model across zoom levels, renders the full filtered scene instead of viewport-sampled subsets, and shows node titles as zoom approaches readable scale
610
610
 
611
611
  The server indexes before starting by default. Use `--no-index` to skip that step:
612
612
 
@@ -3,7 +3,6 @@ const glCanvas = document.getElementById('graphGl')
3
3
  const ctx = canvas.getContext('2d')
4
4
  const largeGraphNodeThreshold = 4000
5
5
  const massiveGraphNodeThreshold = 20000
6
- const largeGraphEdgeRenderLimit = 120000
7
6
  const renderNodeBudget = 1000
8
7
  const zoomedMassiveRenderNodeBudget = 2200
9
8
  const massiveOverviewRenderNodeBudget = 1800
@@ -539,16 +538,11 @@ const recomputeVisibility = () => {
539
538
  const nodes = filteredNodes()
540
539
  const ids = new Set(nodes.map(node => node.id))
541
540
  const edges = state.edges.filter(edge => ids.has(edge.source) && edge.target && ids.has(edge.target))
542
- const limitedEdges = state.nodes.length > largeGraphNodeThreshold
543
- ? [...edges]
544
- .sort((left, right) => edgeWeight(right) - edgeWeight(left))
545
- .slice(0, largeGraphEdgeRenderLimit)
546
- : edges
547
541
 
548
542
  state.visibleNodes = nodes
549
- state.visibleEdges = limitedEdges
543
+ state.visibleEdges = edges
550
544
  state.visibleNodeSpatial = createSpatialIndex(nodes)
551
- state.visibleEdgeByNode = createVisibleEdgeLookup(limitedEdges)
545
+ state.visibleEdgeByNode = createVisibleEdgeLookup(edges)
552
546
  const primaryHub = rankedHubNodes()[0] ?? null
553
547
  state.primaryHub = primaryHub
554
548
  markRenderDirty()
@@ -1007,7 +1001,7 @@ const drawGraphEdges = () => {
1007
1001
  const shouldDrawNodeLabels = (node, isSelected, isHovered) =>
1008
1002
  isSelected ||
1009
1003
  isHovered ||
1010
- (state.nodes.length > largeGraphNodeThreshold && !state.renderNodes.some(item => !item.isGroupNode) && state.transform.scale >= 1.25 && state.renderNodes.length <= 420) ||
1004
+ (state.nodes.length > largeGraphNodeThreshold && state.transform.scale >= 0.78 && isNodeVisibleOnScreen(node, state.viewport.width, state.viewport.height)) ||
1011
1005
  (state.nodes.length <= largeGraphNodeThreshold && (state.transform.scale > 1.18 || state.nodes.length <= 25))
1012
1006
 
1013
1007
  const drawSingleNode = (node, options = { drawLabel: true }) => {
@@ -1157,24 +1151,35 @@ const partitionGraphForAcceleratedRenderer = () => {
1157
1151
 
1158
1152
  const drawGraphLabels = nodes => {
1159
1153
  const shouldDrawLabels = state.nodes.length > largeGraphNodeThreshold
1160
- ? state.transform.scale >= 1.25 && state.renderNodes.length <= 420
1154
+ ? state.transform.scale >= 0.78
1161
1155
  : state.transform.scale >= 0.62 && state.renderNodes.length <= 1200
1162
1156
 
1163
1157
  if (!shouldDrawLabels) {
1164
1158
  return
1165
1159
  }
1166
1160
 
1161
+ const maxLabels = state.nodes.length > largeGraphNodeThreshold
1162
+ ? (state.transform.scale >= 1.5 ? 900 : state.transform.scale >= 1.05 ? 520 : 260)
1163
+ : state.renderNodes.length
1164
+ let drawnLabels = 0
1167
1165
  ctx.fillStyle = graphTheme.label
1168
1166
  ctx.font = '12px Inter, system-ui, sans-serif'
1169
1167
  ctx.textAlign = 'center'
1170
1168
  ctx.textBaseline = 'top'
1171
1169
  for (let index = 0; index < nodes.length; index += 1) {
1172
1170
  const node = nodes[index]
1171
+ if (drawnLabels >= maxLabels) {
1172
+ break
1173
+ }
1174
+ if (state.nodes.length > largeGraphNodeThreshold && !isNodeVisibleOnScreen(node, state.viewport.width, state.viewport.height)) {
1175
+ continue
1176
+ }
1173
1177
  const x = node.x
1174
1178
  const y = node.y
1175
1179
  const radius = nodeRadius(node)
1176
1180
  ctx.globalAlpha = 1
1177
1181
  ctx.fillText(node.title.slice(0, 34), x, y + radius + 8)
1182
+ drawnLabels += 1
1178
1183
  }
1179
1184
  ctx.globalAlpha = 1
1180
1185
  }
@@ -2875,17 +2880,7 @@ const render = now => {
2875
2880
  } else {
2876
2881
  state.offscreenFrameCount = 0
2877
2882
  }
2878
- const minimumEdgeScale =
2879
- state.nodes.length > massiveGraphNodeThreshold
2880
- ? 0
2881
- : state.renderNodes.length > 1300
2882
- ? 0.12
2883
- : state.renderNodes.length > 900
2884
- ? 0.085
2885
- : state.renderNodes.length > 500
2886
- ? 0.05
2887
- : 0
2888
- const drawEdges = state.transform.scale >= minimumEdgeScale
2883
+ const drawEdges = true
2889
2884
  if (drawAcceleratedGraph(width, height, drawEdges)) {
2890
2885
  // WebGL handles the dense node/edge layer; the 2D canvas remains the interaction overlay.
2891
2886
  } else {
@@ -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 render the full filtered node/edge set during navigation, without viewport-sampled subgraph switching.
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. Node titles appear as zoom approaches readable scale, limited to on-screen nodes in very large graphs.
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.140",
3
+ "version": "0.1.0-beta.141",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",