@andespindola/brainlink 0.1.0-beta.97 → 0.1.0-beta.98

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.
@@ -936,6 +936,10 @@ const filterEcosystemClustersByViewport = (clusters, viewport) => {
936
936
  }
937
937
 
938
938
  const ecosystemFocusPoint = () => {
939
+ const cursorPoint = cursorWorldPoint()
940
+ if (cursorPoint) {
941
+ return cursorPoint
942
+ }
939
943
  const now = performance.now()
940
944
  if (now - state.lastZoomFocus.at <= 1800) {
941
945
  return { x: state.lastZoomFocus.x, y: state.lastZoomFocus.y }
@@ -947,7 +951,11 @@ const nearestEcosystemParentIds = (clusters, focusPoint, limit) =>
947
951
  clusters
948
952
  .map(cluster => ({
949
953
  cluster,
950
- distance: Math.hypot(cluster.x - focusPoint.x, cluster.y - focusPoint.y)
954
+ distance: Math.max(
955
+ 0,
956
+ Math.hypot(cluster.x - focusPoint.x, cluster.y - focusPoint.y) -
957
+ clusterRadiusPx(cluster) / Math.max(state.transform.scale, 0.0001)
958
+ )
951
959
  }))
952
960
  .sort((left, right) => left.distance - right.distance)
953
961
  .slice(0, limit)
@@ -1293,6 +1301,29 @@ const viewportCenterWorldPoint = () => {
1293
1301
  }
1294
1302
  }
1295
1303
 
1304
+ const screenToWorldPoint = (screenX, screenY) => ({
1305
+ x: (screenX - state.transform.x) / state.transform.scale,
1306
+ y: (screenY - state.transform.y) / state.transform.scale
1307
+ })
1308
+
1309
+ const cursorWorldPoint = () => {
1310
+ if (!state.cursor.inCanvas) {
1311
+ return null
1312
+ }
1313
+ const rect = canvas.getBoundingClientRect()
1314
+ const screenX = state.cursor.x - rect.left
1315
+ const screenY = state.cursor.y - rect.top
1316
+ const width = Math.max(rect.width, 320)
1317
+ const height = Math.max(rect.height, 320)
1318
+ if (!Number.isFinite(screenX) || !Number.isFinite(screenY)) {
1319
+ return null
1320
+ }
1321
+ if (screenX < 0 || screenX > width || screenY < 0 || screenY > height) {
1322
+ return null
1323
+ }
1324
+ return screenToWorldPoint(screenX, screenY)
1325
+ }
1326
+
1296
1327
  const visibilityScaleBucket = (scale) => {
1297
1328
  const safeScale = Math.max(zoomRange.min, scale)
1298
1329
  if (safeScale < 0.01) return Math.round(safeScale * 300_000)
@@ -1348,11 +1379,12 @@ const selectStableSampleNodes = (sourceNodes, limit) => {
1348
1379
  }
1349
1380
 
1350
1381
  const now = performance.now()
1382
+ const cursorPoint = cursorWorldPoint()
1351
1383
  const recentZoomFocus =
1352
1384
  now - state.lastZoomFocus.at <= 1500
1353
1385
  ? { x: state.lastZoomFocus.x, y: state.lastZoomFocus.y }
1354
1386
  : null
1355
- const anchor = recentZoomFocus ?? viewportCenterWorldPoint()
1387
+ const anchor = cursorPoint ?? recentZoomFocus ?? viewportCenterWorldPoint()
1356
1388
  const previousIds = new Set(state.renderNodes.map((node) => node.id))
1357
1389
  const preferAnchorDistance = state.visibleNodes.length > massiveGraphNodeThreshold && state.transform.scale >= 0.28
1358
1390
 
@@ -1386,11 +1418,12 @@ const selectAccessBridgeNodes = (sourceNodes, limit) => {
1386
1418
  }
1387
1419
 
1388
1420
  const now = performance.now()
1421
+ const cursorPoint = cursorWorldPoint()
1389
1422
  const recentZoomFocus =
1390
1423
  now - state.lastZoomFocus.at <= 1200
1391
1424
  ? { x: state.lastZoomFocus.x, y: state.lastZoomFocus.y }
1392
1425
  : null
1393
- const anchor = recentZoomFocus ?? viewportCenterWorldPoint()
1426
+ const anchor = cursorPoint ?? recentZoomFocus ?? viewportCenterWorldPoint()
1394
1427
  return [...sourceNodes]
1395
1428
  .sort((left, right) => {
1396
1429
  const leftDistance = Math.hypot(left.x - anchor.x, left.y - anchor.y)
@@ -2468,10 +2501,7 @@ const tick = delta => {
2468
2501
 
2469
2502
  const worldPoint = event => {
2470
2503
  const rect = canvas.getBoundingClientRect()
2471
- return {
2472
- x: (event.clientX - rect.left - state.transform.x) / state.transform.scale,
2473
- y: (event.clientY - rect.top - state.transform.y) / state.transform.scale
2474
- }
2504
+ return screenToWorldPoint(event.clientX - rect.left, event.clientY - rect.top)
2475
2505
  }
2476
2506
 
2477
2507
  const connectedNodeIdsFor = (nodeId) => {
@@ -3218,8 +3248,9 @@ const zoomAtPoint = (screenX, screenY, factor, source = 'generic') => {
3218
3248
  if (nextScale === state.transform.scale) {
3219
3249
  return
3220
3250
  }
3221
- const worldX = (screenX - state.transform.x) / state.transform.scale
3222
- const worldY = (screenY - state.transform.y) / state.transform.scale
3251
+ const worldPointAtCursor = screenToWorldPoint(screenX, screenY)
3252
+ const worldX = worldPointAtCursor.x
3253
+ const worldY = worldPointAtCursor.y
3223
3254
  state.lastZoomFocus = {
3224
3255
  x: worldX,
3225
3256
  y: worldY,
@@ -3266,6 +3297,7 @@ const handleWheelZoom = event => {
3266
3297
  const rawCursorY = Number.isFinite(event.offsetY) ? event.offsetY : event.clientY - rect.top
3267
3298
  const cursorX = Math.max(0, Math.min(Math.max(rect.width, 320), rawCursorX))
3268
3299
  const cursorY = Math.max(0, Math.min(Math.max(rect.height, 320), rawCursorY))
3300
+ state.cursor = { x: event.clientX, y: event.clientY, inCanvas: true }
3269
3301
  const factor = wheelZoomFactor(event)
3270
3302
 
3271
3303
  if (!Number.isFinite(factor) || factor <= 0 || factor === 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.97",
3
+ "version": "0.1.0-beta.98",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",