@andespindola/brainlink 0.1.0-beta.160 → 0.1.0-beta.162

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.
@@ -342,15 +342,21 @@ li small {
342
342
  }
343
343
 
344
344
  .content-dialog {
345
- position: fixed;
345
+ position: absolute;
346
+ z-index: 6;
347
+ top: 12px;
348
+ right: 12px;
349
+ bottom: 12px;
350
+ left: 12px;
346
351
  top: max(12px, env(safe-area-inset-top));
347
352
  right: max(12px, env(safe-area-inset-right));
353
+ bottom: max(12px, env(safe-area-inset-bottom));
354
+ left: max(12px, calc(100% - 780px));
348
355
  margin: 0;
349
- width: min(760px, calc(100vw - 32px));
350
- height: min(calc(100vh - 24px), 920px);
351
- height: min(calc(100dvh - 24px), 920px);
352
- max-height: calc(100vh - 24px);
353
- max-height: calc(100dvh - 24px);
356
+ width: auto;
357
+ height: auto;
358
+ max-width: none;
359
+ max-height: none;
354
360
  padding: 0;
355
361
  border: 1px solid var(--line);
356
362
  border-radius: 8px;
@@ -360,9 +366,8 @@ li small {
360
366
  overflow: hidden;
361
367
  }
362
368
 
363
- .content-dialog::backdrop {
364
- background: rgba(4, 7, 10, 0.72);
365
- backdrop-filter: blur(4px);
369
+ .content-dialog[hidden] {
370
+ display: none;
366
371
  }
367
372
 
368
373
  .content-dialog article {
@@ -375,6 +380,9 @@ li small {
375
380
 
376
381
  .content-dialog header {
377
382
  display: flex;
383
+ position: sticky;
384
+ top: 0;
385
+ z-index: 1;
378
386
  align-items: flex-start;
379
387
  justify-content: space-between;
380
388
  gap: 18px;
@@ -383,6 +391,10 @@ li small {
383
391
  background: var(--panel);
384
392
  }
385
393
 
394
+ .content-dialog header > div {
395
+ min-width: 0;
396
+ }
397
+
386
398
  .content-dialog h2,
387
399
  .content-dialog p {
388
400
  margin: 0;
@@ -441,6 +453,10 @@ li small {
441
453
  display: grid;
442
454
  grid-template-columns: repeat(2, minmax(0, 1fr));
443
455
  gap: 10px;
456
+ min-height: 0;
457
+ max-height: min(42vh, 360px);
458
+ max-height: min(42dvh, 360px);
459
+ overflow: auto;
444
460
  padding: 14px 22px;
445
461
  border-bottom: 1px solid var(--line);
446
462
  }
@@ -554,6 +570,7 @@ li small {
554
570
 
555
571
  .content-meta {
556
572
  grid-template-columns: 1fr;
573
+ max-height: 34vh;
557
574
  max-height: 34dvh;
558
575
  overflow: auto;
559
576
  padding: 10px 14px;
@@ -52,47 +52,47 @@ export const createClientHtml = () => `<!doctype html>
52
52
  <div id="graphLabels" class="graph-labels" aria-hidden="true"></div>
53
53
  <div id="graphTooltip" class="graph-tooltip" role="tooltip" hidden></div>
54
54
  <canvas id="miniMap" class="mini-map" aria-label="Graph overview"></canvas>
55
+ <aside id="contentDialog" class="content-dialog" role="dialog" aria-labelledby="contentTitle" hidden>
56
+ <article>
57
+ <header>
58
+ <div>
59
+ <span class="eyebrow">Node details</span>
60
+ <h2 id="contentTitle">Selected note</h2>
61
+ <p id="contentPath"></p>
62
+ </div>
63
+ <button id="contentClose" type="button" aria-label="Close node details" title="Close node details">&times;</button>
64
+ </header>
65
+ <div class="content-meta">
66
+ <section class="content-meta-section">
67
+ <h3>Facts</h3>
68
+ <ul id="contentFacts"></ul>
69
+ </section>
70
+ <section class="content-meta-section">
71
+ <h3>Context Links</h3>
72
+ <ul id="contentContextLinks"></ul>
73
+ </section>
74
+ <section class="content-meta-section">
75
+ <h3>Tags</h3>
76
+ <div id="contentTags" class="tags"></div>
77
+ </section>
78
+ <section class="content-meta-section">
79
+ <h3>Outgoing</h3>
80
+ <ul id="contentOutgoing"></ul>
81
+ </section>
82
+ <section class="content-meta-section">
83
+ <h3>Backlinks</h3>
84
+ <ul id="contentIncoming"></ul>
85
+ </section>
86
+ </div>
87
+ <pre id="contentBody" class="note-content"></pre>
88
+ </article>
89
+ </aside>
55
90
  </div>
56
91
  </section>
57
92
  </main>
58
93
  <footer class="app-footer" aria-label="Copyright notice">
59
94
  <small>Copyright © 2026 Substructa</small>
60
95
  </footer>
61
- <dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
62
- <article>
63
- <header>
64
- <div>
65
- <span class="eyebrow">Node details</span>
66
- <h2 id="contentTitle">Selected note</h2>
67
- <p id="contentPath"></p>
68
- </div>
69
- <button id="contentClose" type="button" aria-label="Close node details" title="Close node details">&times;</button>
70
- </header>
71
- <div class="content-meta">
72
- <section class="content-meta-section">
73
- <h3>Facts</h3>
74
- <ul id="contentFacts"></ul>
75
- </section>
76
- <section class="content-meta-section">
77
- <h3>Context Links</h3>
78
- <ul id="contentContextLinks"></ul>
79
- </section>
80
- <section class="content-meta-section">
81
- <h3>Tags</h3>
82
- <div id="contentTags" class="tags"></div>
83
- </section>
84
- <section class="content-meta-section">
85
- <h3>Outgoing</h3>
86
- <ul id="contentOutgoing"></ul>
87
- </section>
88
- <section class="content-meta-section">
89
- <h3>Backlinks</h3>
90
- <ul id="contentIncoming"></ul>
91
- </section>
92
- </div>
93
- <pre id="contentBody" class="note-content"></pre>
94
- </article>
95
- </dialog>
96
96
  <script src="/app.js"></script>
97
97
  </body>
98
98
  </html>`;
@@ -866,10 +866,11 @@ const linkedNodes = (node) => {
866
866
  }
867
867
 
868
868
  const openContentDialog = () => {
869
- const dialog = elements.contentDialog
870
- if (!dialog.open) {
871
- dialog.show()
872
- }
869
+ elements.contentDialog.hidden = false
870
+ }
871
+
872
+ const closeContentDialog = () => {
873
+ elements.contentDialog.hidden = true
873
874
  }
874
875
 
875
876
  const loadNodeDetails = async (nodeId) => {
@@ -1282,6 +1283,10 @@ const setupInput = () => {
1282
1283
  })
1283
1284
 
1284
1285
  window.addEventListener('keydown', (event) => {
1286
+ if (event.key === 'Escape' && !elements.contentDialog.hidden) {
1287
+ closeContentDialog()
1288
+ return
1289
+ }
1285
1290
  if (event.key === '+') {
1286
1291
  zoomAtPoint(state.viewport.width / 2, state.viewport.height / 2, 1.06)
1287
1292
  return
@@ -1323,12 +1328,12 @@ const setupControls = () => {
1323
1328
  })
1324
1329
 
1325
1330
  elements.contentClose.addEventListener('click', () => {
1326
- elements.contentDialog.close()
1331
+ closeContentDialog()
1327
1332
  })
1328
1333
 
1329
1334
  elements.contentDialog.addEventListener('click', (event) => {
1330
1335
  if (event.target === elements.contentDialog) {
1331
- elements.contentDialog.close()
1336
+ closeContentDialog()
1332
1337
  }
1333
1338
  })
1334
1339
 
@@ -32,6 +32,8 @@ let hoverX = null
32
32
  let hoverY = null
33
33
  let lastFrameAt = 0
34
34
  let lastVisibleEdges = 0
35
+ let interactionUntil = 0
36
+ let settledRenderTimer = null
35
37
  let edgePositionsBuffer = new Float32Array(0)
36
38
  let pointPositionsBuffer = new Float32Array(0)
37
39
  let pointSizesBuffer = new Float32Array(0)
@@ -354,6 +356,20 @@ const clear = () => {
354
356
  gl.clear(gl.COLOR_BUFFER_BIT)
355
357
  }
356
358
 
359
+ const isCameraInteracting = (now) => now < interactionUntil
360
+
361
+ const scheduleSettledRender = (now) => {
362
+ if (settledRenderTimer) {
363
+ return
364
+ }
365
+ const delay = Math.max(32, interactionUntil - now + 16)
366
+ settledRenderTimer = setTimeout(() => {
367
+ settledRenderTimer = null
368
+ dirty = true
369
+ requestRender()
370
+ }, delay)
371
+ }
372
+
357
373
  const renderFrame = (now) => {
358
374
  renderScheduled = false
359
375
  if (!dirty) {
@@ -374,7 +390,13 @@ const renderFrame = (now) => {
374
390
 
375
391
  cullVisibleNodes()
376
392
  clear()
377
- drawEdges()
393
+ const cameraInteracting = isCameraInteracting(now)
394
+ if (!cameraInteracting || state.edgeCount < 1200) {
395
+ drawEdges()
396
+ } else {
397
+ lastVisibleEdges = 0
398
+ scheduleSettledRender(now)
399
+ }
378
400
 
379
401
  drawNodeLayer(
380
402
  (index) => state.visible[index] === 1 && state.selected[index] === 0 && state.highlighted[index] === 0,
@@ -442,6 +464,7 @@ const setCamera = (nextCamera) => {
442
464
  camera.x = Number.isFinite(nextCamera.x) ? Number(nextCamera.x) : camera.x
443
465
  camera.y = Number.isFinite(nextCamera.y) ? Number(nextCamera.y) : camera.y
444
466
  camera.scale = Number.isFinite(nextCamera.scale) ? Math.max(0.0002, Math.min(8, Number(nextCamera.scale))) : camera.scale
467
+ interactionUntil = performance.now() + 140
445
468
  dirty = true
446
469
  requestRender()
447
470
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.160",
3
+ "version": "0.1.0-beta.162",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",