@andespindola/brainlink 0.1.0-beta.131 → 0.1.0-beta.133
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 +1 -1
- package/dist/application/frontend/client-js.js +80 -29
- package/docs/AGENT_USAGE.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -606,7 +606,7 @@ The graph UI shows:
|
|
|
606
606
|
- graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
|
|
607
607
|
- adaptive CPU safeguards for large graphs: idle frame pacing, throttled background physics updates and cached viewport dimensions to reduce redraw/layout overhead while preserving interaction responsiveness
|
|
608
608
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
609
|
-
- large graph LOD keeps a recursive graph-of-graphs model: zoom-out fills the projected macro level toward 1000 lightweight group nodes with
|
|
609
|
+
- large graph LOD keeps a recursive graph-of-graphs model: zoom-out fills the projected macro level toward 1000 lightweight group nodes with every aggregated link between visible groups, zoom-in grows the focused node into a local child graph anchored at the same point, once the child graph is framed the sibling groups stop rendering, groups with child groups open another graph level instead of jumping to leaf notes, and zoom-out restores sibling groups
|
|
610
610
|
|
|
611
611
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
612
612
|
|
|
@@ -16,7 +16,11 @@ const hierarchyMicroEnterCoverage = 0.58
|
|
|
16
16
|
const hierarchyMicroExitCoverage = 0.38
|
|
17
17
|
const hierarchyMicroEnterScale = 0.18
|
|
18
18
|
const hierarchyMicroExitScale = 0.12
|
|
19
|
-
const
|
|
19
|
+
const hierarchyFocusAcquireCoverage = 0.52
|
|
20
|
+
const hierarchyFocusAcquireScale = 0.16
|
|
21
|
+
const hierarchyChildRevealPower = 4
|
|
22
|
+
const hierarchyFocusedOnlyProgress = 0.64
|
|
23
|
+
const hierarchyChildGraphFitMargin = 1.28
|
|
20
24
|
const minNodePixelRadius = 2.3
|
|
21
25
|
const viewportPaddingPx = 280
|
|
22
26
|
const worldCoordinateLimit = 5_000_000
|
|
@@ -647,7 +651,7 @@ const createVisibleEdgeLookup = edges => {
|
|
|
647
651
|
|
|
648
652
|
const edgeBudgetForCurrentFrame = () => {
|
|
649
653
|
const zoom = state.transform.scale
|
|
650
|
-
if (zoom < 0.12) return 380
|
|
654
|
+
if (zoom < 0.12) return state.groups.length > 0 ? 1200 : 380
|
|
651
655
|
if (zoom < 0.18) return 900
|
|
652
656
|
if (zoom < 0.28) return 1700
|
|
653
657
|
if (zoom < 0.45) return 2800
|
|
@@ -1319,6 +1323,43 @@ const arrangeChildGraphNodes = (nodes, group, origin = group) => {
|
|
|
1319
1323
|
return arranged
|
|
1320
1324
|
}
|
|
1321
1325
|
|
|
1326
|
+
const arrangeChildGroupNodes = (groups, parentGroup, origin) => {
|
|
1327
|
+
if (groups.length <= 1) {
|
|
1328
|
+
return groups.map(group => ({
|
|
1329
|
+
...createGroupRenderNode(group),
|
|
1330
|
+
x: origin.x,
|
|
1331
|
+
y: origin.y
|
|
1332
|
+
}))
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const targetRadius = childGraphRenderRadius(parentGroup)
|
|
1336
|
+
const centerGroup = groups
|
|
1337
|
+
.map(group => ({
|
|
1338
|
+
group,
|
|
1339
|
+
score: Math.max(group.nodeIds.length, group.childGroupIds.length, 1)
|
|
1340
|
+
}))
|
|
1341
|
+
.sort((left, right) => right.score - left.score || left.group.title.localeCompare(right.group.title))[0]?.group
|
|
1342
|
+
const outerGroups = groups
|
|
1343
|
+
.filter(group => group.id !== centerGroup?.id)
|
|
1344
|
+
.sort((left, right) => left.segment.localeCompare(right.segment) || left.title.localeCompare(right.title))
|
|
1345
|
+
const goldenAngle = Math.PI * (3 - Math.sqrt(5))
|
|
1346
|
+
const arranged = centerGroup
|
|
1347
|
+
? [{ ...createGroupRenderNode(centerGroup), x: origin.x, y: origin.y }]
|
|
1348
|
+
: []
|
|
1349
|
+
|
|
1350
|
+
outerGroups.forEach((group, index) => {
|
|
1351
|
+
const ringRadius = targetRadius * Math.sqrt((index + 1) / Math.max(outerGroups.length, 1))
|
|
1352
|
+
const angle = index * goldenAngle
|
|
1353
|
+
arranged.push({
|
|
1354
|
+
...createGroupRenderNode(group),
|
|
1355
|
+
x: origin.x + Math.cos(angle) * ringRadius,
|
|
1356
|
+
y: origin.y + Math.sin(angle) * ringRadius
|
|
1357
|
+
})
|
|
1358
|
+
})
|
|
1359
|
+
|
|
1360
|
+
return arranged
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1322
1363
|
const interpolateNodeFromGroup = (node, origin, progress) => {
|
|
1323
1364
|
return {
|
|
1324
1365
|
...node,
|
|
@@ -1364,7 +1405,7 @@ const hierarchyGroupsForScale = () => {
|
|
|
1364
1405
|
const groupViewportCoverage = (group, viewport) => {
|
|
1365
1406
|
const viewportWidth = Math.max(viewport.maxX - viewport.minX, 1)
|
|
1366
1407
|
const viewportHeight = Math.max(viewport.maxY - viewport.minY, 1)
|
|
1367
|
-
const viewportRadius = Math.
|
|
1408
|
+
const viewportRadius = Math.min(viewportWidth, viewportHeight) / 2
|
|
1368
1409
|
const centerX = (viewport.minX + viewport.maxX) / 2
|
|
1369
1410
|
const centerY = (viewport.minY + viewport.maxY) / 2
|
|
1370
1411
|
const centerDistance = Math.hypot(group.x - centerX, group.y - centerY)
|
|
@@ -1422,6 +1463,16 @@ const updateHierarchyFocusGroup = (groups, viewport) => {
|
|
|
1422
1463
|
? groups.find(group => group.id === state.hierarchyFocusGroupId) ?? null
|
|
1423
1464
|
: null
|
|
1424
1465
|
const currentCoverage = current ? groupViewportCoverage(current, viewport) : 0
|
|
1466
|
+
const hasActiveFocusedTransition =
|
|
1467
|
+
Boolean(current) &&
|
|
1468
|
+
state.zoomTransition.active &&
|
|
1469
|
+
state.zoomTransition.source === 'group' &&
|
|
1470
|
+
state.selected?.isGroupNode &&
|
|
1471
|
+
state.selected.groupId === current.id
|
|
1472
|
+
|
|
1473
|
+
if (hasActiveFocusedTransition) {
|
|
1474
|
+
return current
|
|
1475
|
+
}
|
|
1425
1476
|
|
|
1426
1477
|
if (
|
|
1427
1478
|
current &&
|
|
@@ -1440,8 +1491,8 @@ const updateHierarchyFocusGroup = (groups, viewport) => {
|
|
|
1440
1491
|
|
|
1441
1492
|
if (
|
|
1442
1493
|
candidate &&
|
|
1443
|
-
state.transform.scale >=
|
|
1444
|
-
candidate.coverage >=
|
|
1494
|
+
state.transform.scale >= hierarchyFocusAcquireScale &&
|
|
1495
|
+
candidate.coverage >= hierarchyFocusAcquireCoverage
|
|
1445
1496
|
) {
|
|
1446
1497
|
state.hierarchyFocusGroupId = candidate.group.id
|
|
1447
1498
|
if (candidate.group.childGroupIds.length > 0 && state.hierarchyFocusStack.at(-1) !== candidate.group.id) {
|
|
@@ -1459,9 +1510,23 @@ const updateHierarchyFocusGroup = (groups, viewport) => {
|
|
|
1459
1510
|
|
|
1460
1511
|
const hierarchyViewportProgress = (group, viewport) => {
|
|
1461
1512
|
const coverage = groupViewportCoverage(group, viewport)
|
|
1462
|
-
const coverageProgress = (coverage -
|
|
1463
|
-
const scaleProgress = (state.transform.scale -
|
|
1464
|
-
|
|
1513
|
+
const coverageProgress = (coverage - hierarchyFocusAcquireCoverage) / (hierarchyMicroEnterCoverage - hierarchyFocusAcquireCoverage)
|
|
1514
|
+
const scaleProgress = (state.transform.scale - hierarchyFocusAcquireScale) / (hierarchyMicroEnterScale - hierarchyFocusAcquireScale)
|
|
1515
|
+
const viewportProgress = smoothProgress(Math.min(coverageProgress, scaleProgress))
|
|
1516
|
+
const hasActiveFocusedTransition =
|
|
1517
|
+
state.zoomTransition.active &&
|
|
1518
|
+
state.zoomTransition.source === 'group' &&
|
|
1519
|
+
state.selected?.isGroupNode &&
|
|
1520
|
+
state.selected.groupId === group.id
|
|
1521
|
+
|
|
1522
|
+
if (!hasActiveFocusedTransition) {
|
|
1523
|
+
return viewportProgress
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const targetSpan = Math.max(state.zoomTransition.targetScale - hierarchyFocusAcquireScale, 0.0001)
|
|
1527
|
+
const transitionScaleProgress = (state.transform.scale - hierarchyFocusAcquireScale) / targetSpan
|
|
1528
|
+
const transitionProgress = Math.pow(Math.max(0, Math.min(1, transitionScaleProgress)), 2.8)
|
|
1529
|
+
return Math.max(viewportProgress, transitionProgress)
|
|
1465
1530
|
}
|
|
1466
1531
|
|
|
1467
1532
|
const groupEdgesForRenderedGroups = (groupNodes) => {
|
|
@@ -1508,19 +1573,8 @@ const groupEdgesForRenderedGroups = (groupNodes) => {
|
|
|
1508
1573
|
})
|
|
1509
1574
|
}
|
|
1510
1575
|
|
|
1511
|
-
const degreeCounts = new Map()
|
|
1512
1576
|
return Array.from(selected.values())
|
|
1513
1577
|
.sort((left, right) => edgeWeight(right) - edgeWeight(left) || left.source.localeCompare(right.source) || left.target.localeCompare(right.target))
|
|
1514
|
-
.filter((edge) => {
|
|
1515
|
-
const sourceCount = degreeCounts.get(edge.source) ?? 0
|
|
1516
|
-
const targetCount = degreeCounts.get(edge.target) ?? 0
|
|
1517
|
-
if (sourceCount >= 3 || targetCount >= 3) {
|
|
1518
|
-
return false
|
|
1519
|
-
}
|
|
1520
|
-
degreeCounts.set(edge.source, sourceCount + 1)
|
|
1521
|
-
degreeCounts.set(edge.target, targetCount + 1)
|
|
1522
|
-
return true
|
|
1523
|
-
})
|
|
1524
1578
|
.slice(0, Math.min(hierarchyGroupEdgeLimit, edgeBudgetForCurrentFrame()))
|
|
1525
1579
|
}
|
|
1526
1580
|
|
|
@@ -1547,25 +1601,22 @@ const computeHierarchyRenderVisibility = (viewport) => {
|
|
|
1547
1601
|
const focusChildGroups = focus.childGroupIds
|
|
1548
1602
|
.map(groupId => state.groupById.get(groupId))
|
|
1549
1603
|
.filter(Boolean)
|
|
1550
|
-
const
|
|
1604
|
+
const childRevealProgress = Math.pow(Math.max(0, Math.min(1, progress)), hierarchyChildRevealPower)
|
|
1605
|
+
const childLimit = Math.min(renderNodeBudget, Math.max(1, Math.floor(renderNodeBudget * childRevealProgress)))
|
|
1551
1606
|
const focusRenderNode = groupNodes.find(node => node.groupId === focus.id) ?? createGroupRenderNode(focus)
|
|
1552
1607
|
const arrangedChildren = focusChildGroups.length > 0
|
|
1553
|
-
?
|
|
1554
|
-
...createGroupRenderNode(group),
|
|
1555
|
-
x: focusRenderNode.x + group.x,
|
|
1556
|
-
y: focusRenderNode.y + group.y
|
|
1557
|
-
}))
|
|
1608
|
+
? arrangeChildGroupNodes(focusChildGroups, focus, focusRenderNode)
|
|
1558
1609
|
: arrangeChildGraphNodes(rawChildNodes, focus, focusRenderNode)
|
|
1559
1610
|
const childNodes = selectStableSampleNodes(arrangedChildren, childLimit)
|
|
1560
1611
|
.map(node => interpolateNodeFromGroup(node, focusRenderNode, progress))
|
|
1561
1612
|
const childIds = new Set(childNodes.map(node => node.id))
|
|
1562
1613
|
const childById = new Map(childNodes.map(node => [node.id, node]))
|
|
1563
|
-
const isMicroView = progress >=
|
|
1614
|
+
const isMicroView = progress >= hierarchyFocusedOnlyProgress
|
|
1564
1615
|
const visibleGroupNodes = isMicroView
|
|
1565
1616
|
? []
|
|
1566
1617
|
: groupNodes.filter(node => node.groupId !== focus.id || progress < 0.92)
|
|
1567
1618
|
const groupEdges = isMicroView ? [] : groupEdgesForRenderedGroups(visibleGroupNodes)
|
|
1568
|
-
const childEdges = progress > 0.
|
|
1619
|
+
const childEdges = (isMicroView || progress > 0.02)
|
|
1569
1620
|
? focusChildGroups.length > 0
|
|
1570
1621
|
? groupEdgesForRenderedGroups(childNodes)
|
|
1571
1622
|
: collectVisibleEdgesForNodes(childIds).map(edge => ({
|
|
@@ -2086,7 +2137,7 @@ const childGraphFitTransition = (node, group) => {
|
|
|
2086
2137
|
const width = Math.max(rect.width, 320)
|
|
2087
2138
|
const height = Math.max(rect.height, 320)
|
|
2088
2139
|
const fitScale = childGraphFitScaleForGroup(group, width, height)
|
|
2089
|
-
const nextScale = clampScale(Math.max(fitScale, state.transform.scale * 1.
|
|
2140
|
+
const nextScale = clampScale(Math.max(fitScale, state.transform.scale * 1.08, hierarchyMicroExitScale * 1.02))
|
|
2090
2141
|
|
|
2091
2142
|
return {
|
|
2092
2143
|
active: true,
|
|
@@ -2946,7 +2997,7 @@ const selectNode = (node, options = { openContent: false }) => {
|
|
|
2946
2997
|
screenY: state.viewport.height / 2,
|
|
2947
2998
|
worldX: node.x,
|
|
2948
2999
|
worldY: node.y,
|
|
2949
|
-
targetScale: clampScale(Math.max(state.transform.scale * 1.
|
|
3000
|
+
targetScale: clampScale(Math.max(state.transform.scale * 1.08, hierarchyMicroExitScale * 1.02))
|
|
2950
3001
|
}
|
|
2951
3002
|
state.lastZoomFocus = { x: node.x, y: node.y, at: performance.now() }
|
|
2952
3003
|
state.hierarchyFocusGroupId = node.groupId
|
package/docs/AGENT_USAGE.md
CHANGED
|
@@ -607,7 +607,7 @@ Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
|
|
|
607
607
|
|
|
608
608
|
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.
|
|
609
609
|
|
|
610
|
-
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. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on canvas zooms in at cursor position. 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). Vaults above 1000 notes also expose stable hierarchy groups that fill each visible graph level toward 1000 nodes while keeping every group capped at 1000 child nodes; zoom-out renders the macro level
|
|
610
|
+
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. Keyboard shortcuts are `+` (zoom in), `-` (zoom out) and `0` (reset fit). Double-click on canvas zooms in at cursor position. 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). Vaults above 1000 notes also expose stable hierarchy groups that fill each visible graph level toward 1000 nodes while keeping every group capped at 1000 child nodes; zoom-out renders the macro level with every aggregated link between visible group nodes, zoom-in grows the focused node into a local child graph anchored at the same point, once the child graph is framed the sibling groups stop rendering, and groups with child groups open another graph level instead of jumping directly to leaf notes.
|
|
611
611
|
During graph filtering, Brainlink keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) so filtered views still show relationship anchors.
|
|
612
612
|
|
|
613
613
|
The command reindexes by default, then serves:
|
package/package.json
CHANGED