@andespindola/brainlink 0.1.0-beta.22 → 0.1.0-beta.23

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
@@ -560,11 +560,11 @@ The graph UI shows:
560
560
  - `[[wiki links]]` as weighted edges
561
561
  - details opened on node click (tags, outgoing links, backlinks, full Markdown content)
562
562
  - neutral graph nodes with segment/group metadata
563
- - agent selector for isolated views
563
+ - agent selector (id-only labels) for isolated views
564
564
  - graph filter matches title, path, tags and note content
565
565
  - realtime refresh while `--watch` is enabled
566
566
  - graph controls for zoom in, zoom out, fit visible nodes and reset-to-fit-all
567
- - wheel zoom anchored to cursor position for faster navigation in large graphs
567
+ - wheel zoom (including `cmd+scroll` and `ctrl+scroll`) anchored to cursor position for faster navigation in large graphs
568
568
  - floating graph totals (notes, links, tags) below the Brainlink title
569
569
  - large-graph rendering safeguards (edge draw caps, lower redraw rate, zoom-aware interaction)
570
570
 
@@ -33,7 +33,7 @@ select {
33
33
 
34
34
  .shell {
35
35
  width: 100%;
36
- height: 100svh;
36
+ height: calc(100svh - 28px);
37
37
  overflow: hidden;
38
38
  }
39
39
 
@@ -104,14 +104,6 @@ select {
104
104
  margin-left: auto;
105
105
  }
106
106
 
107
- .license-badge {
108
- margin-left: 10px;
109
- color: var(--muted);
110
- font-size: 11px;
111
- letter-spacing: 0.02em;
112
- white-space: nowrap;
113
- }
114
-
115
107
  .search input,
116
108
  .agent-filter select {
117
109
  width: 100%;
@@ -353,6 +345,20 @@ li small {
353
345
  padding: 22px;
354
346
  }
355
347
 
348
+ .app-footer {
349
+ height: 28px;
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ background: transparent;
354
+ }
355
+
356
+ .app-footer small {
357
+ color: var(--muted);
358
+ font-size: 11px;
359
+ letter-spacing: 0.02em;
360
+ }
361
+
356
362
  @media (max-width: 860px) {
357
363
  .graph-header {
358
364
  align-items: stretch;
@@ -378,13 +384,6 @@ li small {
378
384
  order: 4;
379
385
  }
380
386
 
381
- .license-badge {
382
- width: 100%;
383
- margin-left: 0;
384
- order: 5;
385
- white-space: normal;
386
- }
387
-
388
387
  .content-dialog header {
389
388
  align-items: stretch;
390
389
  flex-direction: column;
@@ -42,11 +42,13 @@ export const createClientHtml = () => `<!doctype html>
42
42
  <button id="reset" type="button" title="Reset view">⌂</button>
43
43
  </div>
44
44
  </div>
45
- <small class="license-badge" aria-label="License notice">MIT License · Copyright © 2026 Anderson Espindola</small>
46
45
  </header>
47
46
  <canvas id="graph" aria-label="Brainlink knowledge graph"></canvas>
48
47
  </section>
49
48
  </main>
49
+ <footer class="app-footer" aria-label="License notice">
50
+ <small>MIT License · Copyright © 2026 Anderson Espindola</small>
51
+ </footer>
50
52
  <dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
51
53
  <article>
52
54
  <header>
@@ -411,6 +411,13 @@ const viewportNodeStride = () => {
411
411
  }
412
412
 
413
413
  const computeRenderVisibility = () => {
414
+ if (state.visibleNodes.length <= 2000) {
415
+ state.renderNodes = state.visibleNodes
416
+ const ids = new Set(state.renderNodes.map((node) => node.id))
417
+ state.renderEdges = state.visibleEdges.filter((edge) => ids.has(edge.source) && edge.target && ids.has(edge.target))
418
+ return
419
+ }
420
+
414
421
  const viewport = worldViewportBounds()
415
422
  const stride = viewportNodeStride()
416
423
  const picked = []
@@ -433,6 +440,25 @@ const computeRenderVisibility = () => {
433
440
  const nodes = picked.length > renderNodeBudget
434
441
  ? picked.slice(0, renderNodeBudget)
435
442
  : picked
443
+ if (nodes.length === 0 && state.visibleNodes.length > 0) {
444
+ const centerX = (viewport.minX + viewport.maxX) / 2
445
+ const centerY = (viewport.minY + viewport.maxY) / 2
446
+ const closest = [...state.visibleNodes]
447
+ .sort((left, right) => {
448
+ const leftDistance = (left.x - centerX) ** 2 + (left.y - centerY) ** 2
449
+ const rightDistance = (right.x - centerX) ** 2 + (right.y - centerY) ** 2
450
+ return leftDistance - rightDistance
451
+ })
452
+ .slice(0, Math.min(renderNodeBudget, 180))
453
+ const closestIds = new Set(closest.map((node) => node.id))
454
+
455
+ state.renderNodes = closest
456
+ state.renderEdges = state.visibleEdges.filter(
457
+ (edge) => closestIds.has(edge.source) && edge.target && closestIds.has(edge.target)
458
+ )
459
+ return
460
+ }
461
+
436
462
  const nodeIds = new Set(nodes.map((node) => node.id))
437
463
  const edges = state.visibleEdges.filter((edge) => nodeIds.has(edge.source) && edge.target && nodeIds.has(edge.target))
438
464
 
@@ -611,6 +637,20 @@ const zoomAtPoint = (screenX, screenY, factor) => {
611
637
  state.transform.y = screenY - worldY * nextScale
612
638
  }
613
639
 
640
+ const wheelZoomFactor = event => {
641
+ const isModifierZoom = event.metaKey || event.ctrlKey
642
+ const delta = Math.abs(event.deltaY)
643
+
644
+ if (delta < 1) {
645
+ return event.deltaY < 0 ? 1.04 : 0.96
646
+ }
647
+
648
+ const zoomInFactor = isModifierZoom ? 1.12 : 1.08
649
+ const zoomOutFactor = isModifierZoom ? 0.88 : 0.92
650
+
651
+ return event.deltaY < 0 ? zoomInFactor : zoomOutFactor
652
+ }
653
+
614
654
  const bindEvents = () => {
615
655
  window.addEventListener('resize', resize)
616
656
  elements.search.addEventListener('input', event => {
@@ -659,7 +699,7 @@ const bindEvents = () => {
659
699
  const rect = canvas.getBoundingClientRect()
660
700
  const cursorX = event.clientX - rect.left
661
701
  const cursorY = event.clientY - rect.top
662
- const factor = event.deltaY < 0 ? 1.08 : 0.92
702
+ const factor = wheelZoomFactor(event)
663
703
  zoomAtPoint(cursorX, cursorY, factor)
664
704
  }, { passive: false })
665
705
  canvas.addEventListener('pointerdown', event => {
@@ -709,7 +749,7 @@ const loadAgents = async () => {
709
749
 
710
750
  state.agentId = selected
711
751
  if (signature !== state.agentsSignature) {
712
- const formatAgentLabel = (agent) => agent.id === 'shared' ? agent.id : agent.id + ' · ' + agent.documentCount
752
+ const formatAgentLabel = (agent) => agent.id
713
753
  elements.agent.innerHTML = agents.length
714
754
  ? agents.map(agent => '<option value="' + escapeHtml(agent.id) + '">' + escapeHtml(formatAgentLabel(agent)) + '</option>').join('')
715
755
  : '<option value="shared">shared</option>'
@@ -530,9 +530,9 @@ This starts a local frontend for inspecting the knowledge graph.
530
530
 
531
531
  Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
532
532
 
533
- The frontend includes an agent selector. Selecting an agent calls the same read APIs with `agent=<agent-id>` and renders that namespace instead of merging every agent into one graph.
533
+ 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.
534
534
 
535
- Graph navigation controls include zoom in, zoom out, fit visible nodes and reset-to-fit-all nodes. Mouse wheel zoom is anchored to the cursor. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open on click in a modal (tags, outgoing links, backlinks and Markdown content).
535
+ 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. Totals for notes, links and tags stay visible as floating metrics under the Brainlink title, and node details open on click in a modal (tags, outgoing links, backlinks and Markdown content).
536
536
 
537
537
  The command reindexes by default, then serves:
538
538
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.22",
3
+ "version": "0.1.0-beta.23",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",