@andespindola/brainlink 0.1.0-beta.120 → 0.1.0-beta.121
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 +2 -2
- package/dist/application/frontend/client-js.js +73 -12
- package/docs/AGENT_USAGE.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Legacy `.jsonl.gz` packs are upgraded to `.blpk` automatically on first search/c
|
|
|
88
88
|
- Large graph layout API automatically uses compact payload encoding with link-coverage-aware edge selection to reduce initial client load without hiding major relationships.
|
|
89
89
|
- Large-segment layout spacing now grows logarithmically to keep initial visual density consistent between medium and very large vaults (for example, ~1k vs ~50k notes).
|
|
90
90
|
- Graph coordinates are visually compacted across graph sizes so reset starts from a stable fitted scene and zoom-in progressively reveals local detail.
|
|
91
|
-
- Zoomed-out graph LOD renders hierarchy groups as normal graph nodes and expands
|
|
91
|
+
- Zoomed-out graph LOD renders hierarchy groups as normal graph nodes and expands a group only after it is framed in the viewport, progressively hiding sibling groups in micro view.
|
|
92
92
|
- Graph reset fits the full graph scene instead of starting in a separate macro overview mode.
|
|
93
93
|
- Graph filtering runs in a dedicated browser worker to keep the UI thread responsive during heavy datasets.
|
|
94
94
|
- Edge rendering budgets adapt to zoom level to prevent frame spikes on large graph panoramas.
|
|
@@ -604,7 +604,7 @@ The graph UI shows:
|
|
|
604
604
|
- graph rendering safeguards (batched canvas drawing across graph sizes, edge draw caps, lower redraw rate, zoom-aware interaction)
|
|
605
605
|
- 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
|
|
606
606
|
- WebGL node and edge acceleration when supported, falling back to Canvas 2D without changing graph behavior
|
|
607
|
-
- large graph LOD keeps a recursive graph-of-graphs model: zoom-out shows one level of group nodes, zoom-in expands the
|
|
607
|
+
- large graph LOD keeps a recursive graph-of-graphs model: zoom-out shows one level of group nodes, zoom-in expands the framed node into its child graph, micro view renders only that focused subgraph, and zoom-out restores sibling groups
|
|
608
608
|
|
|
609
609
|
The server indexes before starting by default. Use `--no-index` to skip that step:
|
|
610
610
|
|
|
@@ -13,7 +13,10 @@ const massiveSegmentRepresentativeBudget = 760
|
|
|
13
13
|
const massiveAutoFitMacroScale = 0.018
|
|
14
14
|
const hierarchyGroupScaleThreshold = 0.62
|
|
15
15
|
const hierarchyExpansionStartScale = 0.18
|
|
16
|
-
const
|
|
16
|
+
const hierarchyMicroEnterCoverage = 0.72
|
|
17
|
+
const hierarchyMicroExitCoverage = 0.52
|
|
18
|
+
const hierarchyMicroEnterScale = 0.24
|
|
19
|
+
const hierarchyMicroExitScale = 0.16
|
|
17
20
|
const minNodePixelRadius = 2.3
|
|
18
21
|
const viewportPaddingPx = 280
|
|
19
22
|
const worldCoordinateLimit = 5_000_000
|
|
@@ -81,6 +84,7 @@ const state = {
|
|
|
81
84
|
lastHoverHitAt: 0,
|
|
82
85
|
lastManualZoomAt: 0,
|
|
83
86
|
lastZoomFocus: { x: 0, y: 0, at: 0 },
|
|
87
|
+
hierarchyFocusGroupId: null,
|
|
84
88
|
zoomTransition: {
|
|
85
89
|
active: false,
|
|
86
90
|
source: 'generic',
|
|
@@ -1199,12 +1203,9 @@ const focusedGroup = () => {
|
|
|
1199
1203
|
|
|
1200
1204
|
const groupRenderNodeId = group => 'group:' + group.id
|
|
1201
1205
|
|
|
1202
|
-
const
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
if (scale >= hierarchyExpansionEndScale) return 1
|
|
1206
|
-
const progress = (scale - hierarchyExpansionStartScale) / (hierarchyExpansionEndScale - hierarchyExpansionStartScale)
|
|
1207
|
-
return progress * progress * (3 - 2 * progress)
|
|
1206
|
+
const smoothProgress = value => {
|
|
1207
|
+
const bounded = Math.max(0, Math.min(1, value))
|
|
1208
|
+
return bounded * bounded * (3 - 2 * bounded)
|
|
1208
1209
|
}
|
|
1209
1210
|
|
|
1210
1211
|
const groupRenderRadius = group => {
|
|
@@ -1249,6 +1250,60 @@ const hierarchyGroupsForScale = () => {
|
|
|
1249
1250
|
return state.leafGroups
|
|
1250
1251
|
}
|
|
1251
1252
|
|
|
1253
|
+
const groupViewportCoverage = (group, viewport) => {
|
|
1254
|
+
const viewportWidth = Math.max(viewport.maxX - viewport.minX, 1)
|
|
1255
|
+
const viewportHeight = Math.max(viewport.maxY - viewport.minY, 1)
|
|
1256
|
+
const viewportRadius = Math.max(viewportWidth, viewportHeight) / 2
|
|
1257
|
+
const centerX = (viewport.minX + viewport.maxX) / 2
|
|
1258
|
+
const centerY = (viewport.minY + viewport.maxY) / 2
|
|
1259
|
+
const centerDistance = Math.hypot(group.x - centerX, group.y - centerY)
|
|
1260
|
+
const fitCoverage = Math.min(1, group.radius / Math.max(viewportRadius, 1))
|
|
1261
|
+
const centerCoverage = 1 - Math.min(1, centerDistance / Math.max(viewportRadius, 1))
|
|
1262
|
+
|
|
1263
|
+
return fitCoverage * 0.72 + centerCoverage * 0.28
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const groupWithCoverage = (group, viewport) => ({
|
|
1267
|
+
group,
|
|
1268
|
+
coverage: groupViewportCoverage(group, viewport)
|
|
1269
|
+
})
|
|
1270
|
+
|
|
1271
|
+
const updateHierarchyFocusGroup = (groups, viewport) => {
|
|
1272
|
+
const current = state.hierarchyFocusGroupId ? state.groupById.get(state.hierarchyFocusGroupId) : null
|
|
1273
|
+
const currentCoverage = current ? groupViewportCoverage(current, viewport) : 0
|
|
1274
|
+
|
|
1275
|
+
if (
|
|
1276
|
+
current &&
|
|
1277
|
+
state.transform.scale >= hierarchyMicroExitScale &&
|
|
1278
|
+
currentCoverage >= hierarchyMicroExitCoverage
|
|
1279
|
+
) {
|
|
1280
|
+
return current
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const candidate = groups
|
|
1284
|
+
.map(group => groupWithCoverage(group, viewport))
|
|
1285
|
+
.sort((left, right) => right.coverage - left.coverage)[0]
|
|
1286
|
+
|
|
1287
|
+
if (
|
|
1288
|
+
candidate &&
|
|
1289
|
+
state.transform.scale >= hierarchyMicroEnterScale &&
|
|
1290
|
+
candidate.coverage >= hierarchyMicroEnterCoverage
|
|
1291
|
+
) {
|
|
1292
|
+
state.hierarchyFocusGroupId = candidate.group.id
|
|
1293
|
+
return candidate.group
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
state.hierarchyFocusGroupId = null
|
|
1297
|
+
return null
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const hierarchyViewportProgress = (group, viewport) => {
|
|
1301
|
+
const coverage = groupViewportCoverage(group, viewport)
|
|
1302
|
+
const coverageProgress = (coverage - hierarchyMicroExitCoverage) / (hierarchyMicroEnterCoverage - hierarchyMicroExitCoverage)
|
|
1303
|
+
const scaleProgress = (state.transform.scale - hierarchyMicroExitScale) / (hierarchyMicroEnterScale - hierarchyMicroExitScale)
|
|
1304
|
+
return smoothProgress(Math.min(coverageProgress, scaleProgress))
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1252
1307
|
const groupEdgesForRenderedGroups = (groupNodes) => {
|
|
1253
1308
|
if (groupNodes.length <= 1) {
|
|
1254
1309
|
return []
|
|
@@ -1298,11 +1353,10 @@ const groupEdgesForRenderedGroups = (groupNodes) => {
|
|
|
1298
1353
|
|
|
1299
1354
|
const computeHierarchyRenderVisibility = (viewport) => {
|
|
1300
1355
|
if (state.groups.length === 0 || state.visibleNodes.length <= 1000 || state.transform.scale >= hierarchyGroupScaleThreshold) {
|
|
1356
|
+
state.hierarchyFocusGroupId = null
|
|
1301
1357
|
return false
|
|
1302
1358
|
}
|
|
1303
1359
|
|
|
1304
|
-
const progress = hierarchyExpansionProgress()
|
|
1305
|
-
const focus = focusedGroup()
|
|
1306
1360
|
const groups = hierarchyGroupsForScale()
|
|
1307
1361
|
.filter(group =>
|
|
1308
1362
|
group.x + group.radius >= viewport.minX &&
|
|
@@ -1311,9 +1365,11 @@ const computeHierarchyRenderVisibility = (viewport) => {
|
|
|
1311
1365
|
group.y - group.radius <= viewport.maxY
|
|
1312
1366
|
)
|
|
1313
1367
|
.slice(0, renderNodeBudget)
|
|
1368
|
+
const focus = updateHierarchyFocusGroup(groups, viewport)
|
|
1369
|
+
const progress = focus ? hierarchyViewportProgress(focus, viewport) : 0
|
|
1314
1370
|
const groupNodes = groups.map(createGroupRenderNode)
|
|
1315
1371
|
|
|
1316
|
-
if (progress <= 0.02
|
|
1372
|
+
if (!focus || progress <= 0.02) {
|
|
1317
1373
|
state.renderNodes = groupNodes
|
|
1318
1374
|
state.renderEdges = groupEdgesForRenderedGroups(groupNodes)
|
|
1319
1375
|
return true
|
|
@@ -1327,8 +1383,11 @@ const computeHierarchyRenderVisibility = (viewport) => {
|
|
|
1327
1383
|
).map(node => interpolateNodeFromGroup(node, focus, progress))
|
|
1328
1384
|
const childIds = new Set(childNodes.map(node => node.id))
|
|
1329
1385
|
const childById = new Map(childNodes.map(node => [node.id, node]))
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
1386
|
+
const isMicroView = progress >= 0.72
|
|
1387
|
+
const visibleGroupNodes = isMicroView
|
|
1388
|
+
? []
|
|
1389
|
+
: groupNodes.filter(node => node.groupId !== focus.id || progress < 0.92)
|
|
1390
|
+
const groupEdges = isMicroView ? [] : groupEdgesForRenderedGroups(visibleGroupNodes)
|
|
1332
1391
|
const childEdges = progress > 0.32
|
|
1333
1392
|
? collectVisibleEdgesForNodes(childIds).map(edge => ({
|
|
1334
1393
|
...edge,
|
|
@@ -2785,6 +2844,7 @@ const selectNode = (node, options = { openContent: false }) => {
|
|
|
2785
2844
|
targetScale
|
|
2786
2845
|
}
|
|
2787
2846
|
state.lastZoomFocus = { x: node.x, y: node.y, at: performance.now() }
|
|
2847
|
+
state.hierarchyFocusGroupId = node.groupId
|
|
2788
2848
|
markRenderDirty()
|
|
2789
2849
|
return
|
|
2790
2850
|
}
|
|
@@ -3089,6 +3149,7 @@ const loadGraph = async (options = { reset: false }) => {
|
|
|
3089
3149
|
state.graph = graph
|
|
3090
3150
|
state.nodes = layout.nodes
|
|
3091
3151
|
state.groups = layout.groups
|
|
3152
|
+
state.hierarchyFocusGroupId = null
|
|
3092
3153
|
state.groupById = new Map(state.groups.map(group => [group.id, group]))
|
|
3093
3154
|
state.leafGroups = state.groups.filter(group => group.nodeIds.length > 0)
|
|
3094
3155
|
state.nodeLeafGroupById = new Map(state.leafGroups.flatMap(group => group.nodeIds.map(nodeId => [nodeId, group])))
|
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 of up to 1000 direct nodes, with recursive parent groups when a level exceeds 1000 groups; zoom-out renders those groups as normal graph nodes,
|
|
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 of up to 1000 direct nodes, with recursive parent groups when a level exceeds 1000 groups; zoom-out renders those groups as normal graph nodes, zoom-in expands a group only after it is framed in the viewport, and micro view renders only the focused subgraph until zoom-out restores sibling groups.
|
|
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