@andespindola/brainlink 0.1.0-beta.21 → 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
|
|
702
|
+
const factor = wheelZoomFactor(event)
|
|
663
703
|
zoomAtPoint(cursorX, cursorY, factor)
|
|
664
704
|
}, { passive: false })
|
|
665
705
|
canvas.addEventListener('pointerdown', event => {
|
|
@@ -709,9 +749,10 @@ const loadAgents = async () => {
|
|
|
709
749
|
|
|
710
750
|
state.agentId = selected
|
|
711
751
|
if (signature !== state.agentsSignature) {
|
|
752
|
+
const formatAgentLabel = (agent) => agent.id
|
|
712
753
|
elements.agent.innerHTML = agents.length
|
|
713
|
-
? agents.map(agent => '<option value="' + escapeHtml(agent.id) + '">' + escapeHtml(agent
|
|
714
|
-
: '<option value="shared">shared
|
|
754
|
+
? agents.map(agent => '<option value="' + escapeHtml(agent.id) + '">' + escapeHtml(formatAgentLabel(agent)) + '</option>').join('')
|
|
755
|
+
: '<option value="shared">shared</option>'
|
|
715
756
|
state.agentsSignature = signature
|
|
716
757
|
}
|
|
717
758
|
elements.agent.value = selected
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -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