@andespindola/brainlink 0.1.0-beta.61 → 0.1.0-beta.62

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.
@@ -23,6 +23,8 @@ const meshEdgeScaleThreshold = 0.09
23
23
  const meshEdgeMinBudget = 140
24
24
  const meshEdgeMaxBudget = 1400
25
25
  const layeredCoreScaleThreshold = 0.55
26
+ const dragNeighborhoodMaxAffected = 180
27
+ const dragSettleRounds = 3
26
28
  const state = {
27
29
  graph: { nodes: [], edges: [] },
28
30
  nodes: [],
@@ -1354,6 +1356,104 @@ const worldPoint = event => {
1354
1356
  }
1355
1357
  }
1356
1358
 
1359
+ const connectedNodeIdsFor = (nodeId) => {
1360
+ const edges = state.visibleEdgeByNode.get(nodeId) ?? []
1361
+ const ids = new Set()
1362
+
1363
+ for (let index = 0; index < edges.length; index += 1) {
1364
+ const edge = edges[index]
1365
+ if (!edge.target) continue
1366
+ if (edge.source === nodeId) {
1367
+ ids.add(edge.target)
1368
+ } else if (edge.target === nodeId) {
1369
+ ids.add(edge.source)
1370
+ }
1371
+ }
1372
+
1373
+ return ids
1374
+ }
1375
+
1376
+ const applyDragNeighborhoodAdjustment = (dragNode, deltaX, deltaY) => {
1377
+ if (!dragNode) return
1378
+ if (!Number.isFinite(deltaX) || !Number.isFinite(deltaY)) return
1379
+ if (Math.abs(deltaX) + Math.abs(deltaY) <= 0.001) return
1380
+
1381
+ const scale = Math.max(state.transform.scale, 0.0001)
1382
+ const influenceRadius = Math.max(220, Math.min(920, 440 / scale))
1383
+ const influenceRadiusSquared = influenceRadius * influenceRadius
1384
+ const connectedIds = connectedNodeIdsFor(dragNode.id)
1385
+ const candidates = state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes
1386
+ let adjusted = 0
1387
+
1388
+ for (let index = 0; index < candidates.length && adjusted < dragNeighborhoodMaxAffected; index += 1) {
1389
+ const node = candidates[index]
1390
+ if (node.id === dragNode.id) continue
1391
+
1392
+ const isConnected = connectedIds.has(node.id)
1393
+ const dx = node.x - dragNode.x
1394
+ const dy = node.y - dragNode.y
1395
+ const distanceSquared = dx * dx + dy * dy
1396
+ const withinRadius = distanceSquared <= influenceRadiusSquared
1397
+ if (!isConnected && !withinRadius) continue
1398
+
1399
+ const distance = Math.max(Math.sqrt(distanceSquared), 0.0001)
1400
+ const proximity = withinRadius ? 1 - (distance / influenceRadius) : 0
1401
+ const coupledStrength = isConnected ? 0.28 : 0.12
1402
+ const influence = Math.min(0.46, coupledStrength + proximity * 0.34)
1403
+ node.x += deltaX * influence
1404
+ node.y += deltaY * influence
1405
+ node.vx = (Number.isFinite(node.vx) ? node.vx : 0) + deltaX * influence * 0.06
1406
+ node.vy = (Number.isFinite(node.vy) ? node.vy : 0) + deltaY * influence * 0.06
1407
+ adjusted += 1
1408
+ }
1409
+ }
1410
+
1411
+ const settleNeighborhoodAroundNode = (dragNode) => {
1412
+ if (!dragNode) return
1413
+
1414
+ const scale = Math.max(state.transform.scale, 0.0001)
1415
+ const settleRadius = Math.max(240, Math.min(980, 520 / scale))
1416
+ const settleRadiusSquared = settleRadius * settleRadius
1417
+ const connectedIds = connectedNodeIdsFor(dragNode.id)
1418
+ const candidates = (state.renderNodes.length > 0 ? state.renderNodes : state.visibleNodes)
1419
+ .filter((node) => {
1420
+ if (node.id === dragNode.id) return true
1421
+ const dx = node.x - dragNode.x
1422
+ const dy = node.y - dragNode.y
1423
+ const distanceSquared = dx * dx + dy * dy
1424
+ return connectedIds.has(node.id) || distanceSquared <= settleRadiusSquared
1425
+ })
1426
+ .slice(0, dragNeighborhoodMaxAffected)
1427
+
1428
+ if (candidates.length <= 1) return
1429
+
1430
+ for (let round = 0; round < dragSettleRounds; round += 1) {
1431
+ for (let leftIndex = 0; leftIndex < candidates.length; leftIndex += 1) {
1432
+ const left = candidates[leftIndex]
1433
+ for (let rightIndex = leftIndex + 1; rightIndex < candidates.length; rightIndex += 1) {
1434
+ const right = candidates[rightIndex]
1435
+ const dx = right.x - left.x
1436
+ const dy = right.y - left.y
1437
+ const distance = Math.max(Math.hypot(dx, dy), 0.001)
1438
+ const minDistance = baseNodeRadius(left) + baseNodeRadius(right) + 10
1439
+ if (distance >= minDistance) continue
1440
+
1441
+ const push = (minDistance - distance) * 0.36
1442
+ const ux = dx / distance
1443
+ const uy = dy / distance
1444
+ if (left.id !== dragNode.id) {
1445
+ left.x -= ux * push
1446
+ left.y -= uy * push
1447
+ }
1448
+ if (right.id !== dragNode.id) {
1449
+ right.x += ux * push
1450
+ right.y += uy * push
1451
+ }
1452
+ }
1453
+ }
1454
+ }
1455
+ }
1456
+
1357
1457
  const hitNode = point => {
1358
1458
  computeRenderVisibility()
1359
1459
  if (state.renderClusters.length > 0) {
@@ -2030,8 +2130,12 @@ const bindEvents = () => {
2030
2130
  state.pointer.y = event.clientY
2031
2131
  state.pointer.moved = state.pointer.moved || Math.abs(dx) + Math.abs(dy) > 3
2032
2132
  if (state.pointer.dragNode) {
2033
- state.pointer.dragNode.x = point.x
2034
- state.pointer.dragNode.y = point.y
2133
+ const dragNode = state.pointer.dragNode
2134
+ const previousX = dragNode.x
2135
+ const previousY = dragNode.y
2136
+ dragNode.x = point.x
2137
+ dragNode.y = point.y
2138
+ applyDragNeighborhoodAdjustment(dragNode, dragNode.x - previousX, dragNode.y - previousY)
2035
2139
  markRenderDirty()
2036
2140
  return
2037
2141
  }
@@ -2043,8 +2147,13 @@ const bindEvents = () => {
2043
2147
  markRenderDirty()
2044
2148
  })
2045
2149
  canvas.addEventListener('pointerup', event => {
2046
- if (state.pointer.dragNode && !state.pointer.moved) selectNode(state.pointer.dragNode, { openContent: true })
2047
- if (!state.pointer.dragNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
2150
+ const draggedNode = state.pointer.dragNode
2151
+ if (draggedNode && state.pointer.moved) {
2152
+ settleNeighborhoodAroundNode(draggedNode)
2153
+ markRenderDirty()
2154
+ }
2155
+ if (draggedNode && !state.pointer.moved) selectNode(draggedNode, { openContent: true })
2156
+ if (!draggedNode && !state.pointer.moved) selectNode(state.hovered, { openContent: true })
2048
2157
  state.pointer = { x: 0, y: 0, down: false, dragNode: null, moved: false }
2049
2158
  canvas.releasePointerCapture(event.pointerId)
2050
2159
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.61",
3
+ "version": "0.1.0-beta.62",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",