@andespindola/brainlink 0.1.0-beta.163 → 0.1.0-beta.164

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.
@@ -1,14 +1,14 @@
1
1
  export const createClientCss = () => `:root {
2
- color-scheme: light;
3
- --bg: #eef2f7;
4
- --panel: #ffffff;
5
- --panel-strong: #f7f9fc;
6
- --line: #d9e1ec;
7
- --text: #172033;
8
- --muted: #657386;
9
- --accent: #0b6fcb;
10
- --accent-weak: rgba(11, 111, 203, 0.12);
11
- --danger: #d84949;
2
+ color-scheme: dark;
3
+ --bg: #071019;
4
+ --panel: #0d1823;
5
+ --panel-strong: #112130;
6
+ --line: rgba(143, 172, 204, 0.18);
7
+ --text: #edf4ff;
8
+ --muted: #97a9bd;
9
+ --accent: #5aa8ff;
10
+ --accent-weak: rgba(90, 168, 255, 0.18);
11
+ --danger: #ff6b6b;
12
12
  }
13
13
 
14
14
  * {
@@ -63,8 +63,8 @@ select {
63
63
  min-height: 72px;
64
64
  padding: 10px 16px;
65
65
  border-bottom: 1px solid var(--line);
66
- background: rgba(255, 255, 255, 0.96);
67
- box-shadow: 0 1px 8px rgba(23, 32, 51, 0.08);
66
+ background: rgba(9, 18, 28, 0.92);
67
+ box-shadow: 0 1px 14px rgba(1, 6, 13, 0.42);
68
68
  backdrop-filter: blur(8px);
69
69
  }
70
70
 
@@ -82,10 +82,14 @@ select {
82
82
  position: relative;
83
83
  width: 100%;
84
84
  height: 100%;
85
+ touch-action: none;
86
+ user-select: none;
87
+ -webkit-user-select: none;
85
88
  background:
86
- linear-gradient(rgba(23, 32, 51, 0.035) 1px, transparent 1px),
87
- linear-gradient(90deg, rgba(23, 32, 51, 0.035) 1px, transparent 1px),
88
- #f6f8fb;
89
+ linear-gradient(rgba(164, 197, 230, 0.05) 1px, transparent 1px),
90
+ linear-gradient(90deg, rgba(164, 197, 230, 0.05) 1px, transparent 1px),
91
+ radial-gradient(circle at top, rgba(43, 93, 143, 0.22), transparent 44%),
92
+ #08131d;
89
93
  background-size: 28px 28px, 28px 28px, auto;
90
94
  overflow: hidden;
91
95
  }
@@ -96,6 +100,7 @@ select {
96
100
  inset: 0;
97
101
  width: 100%;
98
102
  height: 100%;
103
+ touch-action: none;
99
104
  }
100
105
 
101
106
  #graph {
@@ -131,21 +136,21 @@ select {
131
136
  max-width: 220px;
132
137
  transform: translate(-50%, calc(-100% - 12px));
133
138
  padding: 4px 8px;
134
- border: 1px solid rgba(101, 115, 134, 0.24);
139
+ border: 1px solid rgba(143, 172, 204, 0.18);
135
140
  border-radius: 6px;
136
- background: rgba(255, 255, 255, 0.92);
141
+ background: rgba(7, 16, 25, 0.92);
137
142
  color: var(--text);
138
143
  font-size: 11px;
139
144
  line-height: 1.25;
140
145
  white-space: nowrap;
141
146
  overflow: hidden;
142
147
  text-overflow: ellipsis;
143
- box-shadow: 0 8px 22px rgba(23, 32, 51, 0.12);
148
+ box-shadow: 0 12px 28px rgba(1, 6, 13, 0.36);
144
149
  }
145
150
 
146
151
  .graph-label.is-focused {
147
- border-color: rgba(11, 111, 203, 0.56);
148
- color: #0b4f92;
152
+ border-color: rgba(90, 168, 255, 0.56);
153
+ color: #d7e9ff;
149
154
  }
150
155
 
151
156
  .graph-tooltip {
@@ -155,12 +160,12 @@ select {
155
160
  padding: 8px 10px;
156
161
  border: 1px solid var(--line);
157
162
  border-radius: 6px;
158
- background: rgba(255, 255, 255, 0.96);
163
+ background: rgba(9, 18, 28, 0.96);
159
164
  color: var(--text);
160
165
  font-size: 12px;
161
166
  line-height: 1.35;
162
167
  pointer-events: none;
163
- box-shadow: 0 16px 40px rgba(23, 32, 51, 0.18);
168
+ box-shadow: 0 18px 44px rgba(1, 6, 13, 0.42);
164
169
  }
165
170
 
166
171
  .graph-tooltip strong,
@@ -182,10 +187,10 @@ select {
182
187
  z-index: 3;
183
188
  width: 180px;
184
189
  height: 120px;
185
- border: 1px solid rgba(129, 146, 170, 0.28);
190
+ border: 1px solid rgba(143, 172, 204, 0.18);
186
191
  border-radius: 8px;
187
- background: rgba(255, 255, 255, 0.88);
188
- box-shadow: 0 16px 42px rgba(23, 32, 51, 0.16);
192
+ background: rgba(8, 19, 29, 0.84);
193
+ box-shadow: 0 18px 40px rgba(1, 6, 13, 0.36);
189
194
  }
190
195
 
191
196
  .eyebrow {
@@ -218,7 +223,7 @@ select {
218
223
  border: 1px solid var(--line);
219
224
  border-radius: 8px;
220
225
  outline: none;
221
- background: rgba(255, 255, 255, 0.94);
226
+ background: rgba(12, 24, 36, 0.94);
222
227
  color: var(--text);
223
228
  padding: 0 14px;
224
229
  }
@@ -239,7 +244,7 @@ select {
239
244
  height: 38px;
240
245
  border: 1px solid var(--line);
241
246
  border-radius: 8px;
242
- background: rgba(255, 255, 255, 0.94);
247
+ background: rgba(12, 24, 36, 0.94);
243
248
  color: var(--text);
244
249
  cursor: pointer;
245
250
  }
@@ -260,7 +265,7 @@ select {
260
265
  padding: 10px 12px;
261
266
  border: 1px solid var(--line);
262
267
  border-radius: 10px;
263
- background: rgba(255, 255, 255, 0.94);
268
+ background: rgba(12, 24, 36, 0.94);
264
269
  display: grid;
265
270
  gap: 3px;
266
271
  }
@@ -335,7 +340,7 @@ li small {
335
340
  padding: 12px;
336
341
  border: 1px solid var(--line);
337
342
  border-radius: 8px;
338
- background: #f8fafc;
343
+ background: #091521;
339
344
  color: var(--text);
340
345
  white-space: pre-wrap;
341
346
  overflow: auto;
@@ -366,6 +371,7 @@ li small {
366
371
  background: var(--panel);
367
372
  color: var(--text);
368
373
  box-shadow: 0 24px 80px rgba(23, 32, 51, 0.22);
374
+ backdrop-filter: blur(10px);
369
375
  overflow: hidden;
370
376
  }
371
377
 
@@ -469,7 +475,7 @@ li small {
469
475
  padding: 10px;
470
476
  border: 1px solid var(--line);
471
477
  border-radius: 8px;
472
- background: #f8fafc;
478
+ background: #091521;
473
479
  display: grid;
474
480
  grid-template-rows: auto minmax(0, 1fr);
475
481
  gap: 8px;
@@ -509,7 +515,7 @@ li small {
509
515
  display: flex;
510
516
  align-items: center;
511
517
  justify-content: center;
512
- background: transparent;
518
+ background: linear-gradient(180deg, rgba(7, 16, 25, 0), rgba(7, 16, 25, 0.84));
513
519
  }
514
520
 
515
521
  .app-footer small {
@@ -369,28 +369,28 @@ const parseColor = (hex) => {
369
369
  }
370
370
 
371
371
  const graphTheme = {
372
- node: parseColor('#4c8eda'),
373
- nodeCluster: parseColor('#2f6fb4'),
374
- nodeHighlight: parseColor('#f2b441'),
375
- nodeSelected: parseColor('#172033'),
372
+ node: parseColor('#5aa8ff'),
373
+ nodeCluster: parseColor('#3f7fbd'),
374
+ nodeHighlight: parseColor('#ffcb67'),
375
+ nodeSelected: parseColor('#edf4ff'),
376
376
  nodePalette: [
377
- parseColor('#4c8eda'),
378
- parseColor('#65b96e'),
379
- parseColor('#f0a33a'),
380
- parseColor('#d95f8d'),
381
- parseColor('#8d72d9'),
382
- parseColor('#55bfc4'),
383
- parseColor('#ec6b56'),
384
- parseColor('#9aa6b2'),
385
- parseColor('#b78255'),
386
- parseColor('#6f9fd8')
377
+ parseColor('#5aa8ff'),
378
+ parseColor('#5ecf92'),
379
+ parseColor('#ffb65c'),
380
+ parseColor('#ff7dac'),
381
+ parseColor('#a88fff'),
382
+ parseColor('#59d0dd'),
383
+ parseColor('#ff8f6a'),
384
+ parseColor('#a4b3c3'),
385
+ parseColor('#c9945f'),
386
+ parseColor('#7cb6ff')
387
387
  ],
388
- edge: [0.23, 0.31, 0.42, 0.18],
389
- edgeHeavy: [0.23, 0.31, 0.42, 0.34],
390
- clear: parseColor('#f6f8fb')
388
+ edge: [0.59, 0.71, 0.83, 0.14],
389
+ edgeHeavy: [0.59, 0.71, 0.83, 0.3],
390
+ clear: parseColor('#08131d')
391
391
  }
392
392
 
393
- const segmentPalette = ['#4c8eda', '#65b96e', '#f0a33a', '#d95f8d', '#8d72d9', '#55bfc4', '#ec6b56', '#9aa6b2', '#b78255', '#6f9fd8']
393
+ const segmentPalette = ['#5aa8ff', '#5ecf92', '#ffb65c', '#ff7dac', '#a88fff', '#59d0dd', '#ff8f6a', '#a4b3c3', '#c9945f', '#7cb6ff']
394
394
 
395
395
  const segmentColorIndex = (segment) => {
396
396
  const value = String(segment || '')
@@ -402,6 +402,8 @@ const segmentColorIndex = (segment) => {
402
402
  }
403
403
 
404
404
  const segmentColor = (segment) => segmentPalette[segmentColorIndex(segment)] || segmentPalette[0]
405
+ const nodeKind = (node) => node?.[6] === 'cluster' ? 'cluster' : 'node'
406
+ const isRealGraphNode = (node) => nodeKind(node) === 'node'
405
407
 
406
408
  const clampScale = (scale) => Math.max(zoomRange.min, Math.min(zoomRange.max, scale))
407
409
 
@@ -543,7 +545,7 @@ const drawFallback = () => {
543
545
  canvas.width = Math.floor(width * ratio)
544
546
  canvas.height = Math.floor(height * ratio)
545
547
  ctx2dFallback.setTransform(ratio, 0, 0, ratio, 0, 0)
546
- ctx2dFallback.fillStyle = '#f6f8fb'
548
+ ctx2dFallback.fillStyle = '#08131d'
547
549
  ctx2dFallback.fillRect(0, 0, width, height)
548
550
 
549
551
  const nodes = Array.isArray(state.chunk.nodes) ? state.chunk.nodes : []
@@ -553,7 +555,7 @@ const drawFallback = () => {
553
555
  nodeById.set(nodes[i][0], nodes[i])
554
556
  }
555
557
 
556
- ctx2dFallback.strokeStyle = 'rgba(59,79,108,0.18)'
558
+ ctx2dFallback.strokeStyle = 'rgba(151,181,212,0.18)'
557
559
  ctx2dFallback.lineWidth = 1
558
560
  for (let i = 0; i < edges.length; i += 1) {
559
561
  const edge = edges[i]
@@ -576,12 +578,12 @@ const drawFallback = () => {
576
578
  const radius = Math.max(2.4, Math.min(14, 4 + node[7] * 0.55))
577
579
 
578
580
  ctx2dFallback.beginPath()
579
- ctx2dFallback.fillStyle = selected ? '#ffffff' : color
581
+ ctx2dFallback.fillStyle = selected ? '#edf4ff' : color
580
582
  ctx2dFallback.arc(p.x, p.y, radius, 0, Math.PI * 2)
581
583
  ctx2dFallback.fill()
582
584
  }
583
585
 
584
- ctx2dFallback.fillStyle = '#172033'
586
+ ctx2dFallback.fillStyle = '#97a9bd'
585
587
  ctx2dFallback.font = '12px Inter, system-ui, sans-serif'
586
588
  ctx2dFallback.textAlign = 'center'
587
589
  ctx2dFallback.fillText('Fallback canvas mode', Math.max(width, 320) / 2, 24)
@@ -670,6 +672,28 @@ const updateNodePositionInChunk = (nodeId, x, y) => {
670
672
  updateGraphOverlays()
671
673
  }
672
674
 
675
+ const focusNodeInViewport = (nodeId, nextScale = null) => {
676
+ const node = nodeByIdFromChunk().get(nodeId)
677
+ if (!node) {
678
+ return false
679
+ }
680
+
681
+ const x = Number(node[2])
682
+ const y = Number(node[3])
683
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
684
+ return false
685
+ }
686
+
687
+ if (Number.isFinite(nextScale)) {
688
+ state.camera.scale = clampScale(Number(nextScale))
689
+ }
690
+ state.camera.x = state.viewport.width / 2 - x * state.camera.scale
691
+ state.camera.y = state.viewport.height / 2 - y * state.camera.scale
692
+ updateWorkerCamera()
693
+ scheduleChunkFetch()
694
+ return true
695
+ }
696
+
673
697
  const showTooltip = (node, pointer) => {
674
698
  if (!elements.tooltip || !node) {
675
699
  return
@@ -743,7 +767,7 @@ const drawMiniMap = () => {
743
767
  miniMap.height = Math.floor(height * ratio)
744
768
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
745
769
  ctx.clearRect(0, 0, width, height)
746
- ctx.fillStyle = 'rgba(255, 255, 255, 0.88)'
770
+ ctx.fillStyle = 'rgba(8, 19, 29, 0.88)'
747
771
  ctx.fillRect(0, 0, width, height)
748
772
 
749
773
  const xs = nodes.map((node) => Number(node[2])).filter(Number.isFinite)
@@ -763,7 +787,7 @@ const drawMiniMap = () => {
763
787
  })
764
788
  state.miniMapView = { minX, minY, scale, offsetX, offsetY, width, height }
765
789
 
766
- ctx.fillStyle = 'rgba(76, 142, 218, 0.62)'
790
+ ctx.fillStyle = 'rgba(90, 168, 255, 0.62)'
767
791
  nodes.forEach((node) => {
768
792
  const point = toMini(Number(node[2]), Number(node[3]))
769
793
  ctx.fillRect(point.x - 1, point.y - 1, 2, 2)
@@ -773,7 +797,7 @@ const drawMiniMap = () => {
773
797
  const worldBottomRight = screenToWorld(state.viewport.width, state.viewport.height)
774
798
  const topLeft = toMini(Math.min(worldTopLeft.x, worldBottomRight.x), Math.min(worldTopLeft.y, worldBottomRight.y))
775
799
  const bottomRight = toMini(Math.max(worldTopLeft.x, worldBottomRight.x), Math.max(worldTopLeft.y, worldBottomRight.y))
776
- ctx.strokeStyle = 'rgba(11, 111, 203, 0.86)'
800
+ ctx.strokeStyle = 'rgba(90, 168, 255, 0.86)'
777
801
  ctx.lineWidth = 1
778
802
  ctx.strokeRect(topLeft.x, topLeft.y, Math.max(3, bottomRight.x - topLeft.x), Math.max(3, bottomRight.y - topLeft.y))
779
803
  }
@@ -1128,11 +1152,28 @@ const pickFallbackNodeId = (screenX, screenY) => {
1128
1152
  return typeof node?.[0] === 'string' ? node[0] : ''
1129
1153
  }
1130
1154
 
1155
+ const handlePickedNode = (node) => {
1156
+ const nodeId = typeof node?.id === 'string' ? node.id : typeof node?.[0] === 'string' ? node[0] : ''
1157
+ if (!nodeId) {
1158
+ return
1159
+ }
1160
+
1161
+ const kind = typeof node?.kind === 'string' ? node.kind : nodeKind(node)
1162
+ if (kind === 'cluster') {
1163
+ const currentScale = state.camera.scale
1164
+ const targetScale = currentScale < 0.22 ? 0.28 : Math.min(1.1, currentScale * 1.6)
1165
+ focusNodeInViewport(nodeId, targetScale)
1166
+ return
1167
+ }
1168
+
1169
+ loadNodeDetails(nodeId).catch((error) => console.error(error))
1170
+ }
1171
+
1131
1172
  const pickAt = (screenX, screenY) => {
1132
1173
  if (state.rendererMode === 'fallback') {
1133
- const nodeId = pickFallbackNodeId(screenX, screenY)
1134
- if (nodeId) {
1135
- loadNodeDetails(nodeId).catch((error) => console.error(error))
1174
+ const node = pickFallbackNode(screenX, screenY)
1175
+ if (node) {
1176
+ handlePickedNode(node)
1136
1177
  }
1137
1178
  return
1138
1179
  }
@@ -1170,6 +1211,19 @@ const resolvePointer = (event) => {
1170
1211
 
1171
1212
  const setupInput = () => {
1172
1213
  const dragActivationDistance = 6
1214
+ const resetPointerState = (pointerId = null) => {
1215
+ state.pointer.down = false
1216
+ state.pointer.dragging = false
1217
+ state.pointer.dragNodeId = ''
1218
+ canvas.classList.remove('is-node-dragging')
1219
+ if (pointerId !== null) {
1220
+ try {
1221
+ if (canvas.hasPointerCapture(pointerId)) {
1222
+ canvas.releasePointerCapture(pointerId)
1223
+ }
1224
+ } catch {}
1225
+ }
1226
+ }
1173
1227
 
1174
1228
  canvas.addEventListener('wheel', (event) => {
1175
1229
  event.preventDefault()
@@ -1180,9 +1234,10 @@ const setupInput = () => {
1180
1234
  }, { passive: false })
1181
1235
 
1182
1236
  canvas.addEventListener('pointerdown', (event) => {
1237
+ event.preventDefault()
1183
1238
  const pointer = resolvePointer(event)
1184
1239
  const candidateNode = pickFallbackNode(pointer.x, pointer.y)
1185
- const candidateNodeId = candidateNode?.[6] === 'node' && typeof candidateNode?.[0] === 'string' ? candidateNode[0] : ''
1240
+ const candidateNodeId = isRealGraphNode(candidateNode) && typeof candidateNode?.[0] === 'string' ? candidateNode[0] : ''
1186
1241
  const candidateX = Number(candidateNode?.[2])
1187
1242
  const candidateY = Number(candidateNode?.[3])
1188
1243
  const world = screenToWorld(pointer.x, pointer.y)
@@ -1200,10 +1255,15 @@ const setupInput = () => {
1200
1255
  state.pointer.nodeStartY = candidateNodeId && Number.isFinite(candidateY) ? candidateY : 0
1201
1256
  state.pointer.worldAnchorX = world.x
1202
1257
  state.pointer.worldAnchorY = world.y
1203
- canvas.setPointerCapture(event.pointerId)
1258
+ try {
1259
+ canvas.setPointerCapture(event.pointerId)
1260
+ } catch {}
1204
1261
  })
1205
1262
 
1206
1263
  canvas.addEventListener('pointermove', (event) => {
1264
+ if (state.pointer.down) {
1265
+ event.preventDefault()
1266
+ }
1207
1267
  const pointer = resolvePointer(event)
1208
1268
 
1209
1269
  if (state.pointer.down) {
@@ -1241,7 +1301,7 @@ const setupInput = () => {
1241
1301
  }
1242
1302
 
1243
1303
  const hovered = pickFallbackNode(pointer.x, pointer.y)
1244
- const hoveredId = hovered?.[6] === 'node' && typeof hovered?.[0] === 'string' ? hovered[0] : ''
1304
+ const hoveredId = isRealGraphNode(hovered) && typeof hovered?.[0] === 'string' ? hovered[0] : ''
1245
1305
  if (state.hoveredNodeId !== hoveredId) {
1246
1306
  state.hoveredNodeId = hoveredId
1247
1307
  canvas.classList.toggle('is-node-hover', Boolean(hoveredId))
@@ -1260,11 +1320,7 @@ const setupInput = () => {
1260
1320
  const shouldPick = !state.pointer.dragging && distanceFromStart < dragActivationDistance
1261
1321
  const shouldRefreshAfterDrag = state.pointer.dragging
1262
1322
  const shouldPersistNodePosition = state.pointer.dragging && Boolean(state.pointer.dragNodeId)
1263
- state.pointer.down = false
1264
- state.pointer.dragging = false
1265
- canvas.classList.remove('is-node-dragging')
1266
- state.pointer.dragNodeId = ''
1267
- canvas.releasePointerCapture(event.pointerId)
1323
+ resetPointerState(event.pointerId)
1268
1324
 
1269
1325
  if (shouldPick) {
1270
1326
  pickAt(pointer.x, pointer.y)
@@ -1287,6 +1343,16 @@ const setupInput = () => {
1287
1343
  updateGraphOverlays()
1288
1344
  })
1289
1345
 
1346
+ canvas.addEventListener('pointercancel', (event) => {
1347
+ resetPointerState(event.pointerId)
1348
+ hideTooltip()
1349
+ updateGraphOverlays()
1350
+ })
1351
+
1352
+ canvas.addEventListener('lostpointercapture', () => {
1353
+ resetPointerState()
1354
+ })
1355
+
1290
1356
  elements.miniMap.addEventListener('click', (event) => {
1291
1357
  if (!state.miniMapView) {
1292
1358
  return
@@ -1507,7 +1573,7 @@ const setupRenderWorker = () => {
1507
1573
 
1508
1574
  if (payload.type === 'pick-result') {
1509
1575
  if (payload.node && typeof payload.node.id === 'string' && payload.node.id.length > 0) {
1510
- loadNodeDetails(payload.node.id).catch((error) => console.error(error))
1576
+ handlePickedNode(payload.node)
1511
1577
  }
1512
1578
  return
1513
1579
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.163",
3
+ "version": "0.1.0-beta.164",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",