@andespindola/brainlink 0.1.0-beta.36 → 0.1.0-beta.37

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/CHANGELOG.md CHANGED
@@ -28,6 +28,8 @@
28
28
  - Added cross-platform native desktop GUI auto-open for `blink server` (macOS Swift/WebKit, Windows PowerShell WinForms, Linux Python GTK/WebKit2), with app-window/browser fallback.
29
29
  - Changed Linux default UI launch to app-window/browser for lighter startup; Linux native GUI is now opt-in via `BRAINLINK_LINUX_NATIVE_GUI=1`.
30
30
  - Added native GUI parent-process monitoring so GUI windows close automatically when `blink server` stops.
31
+ - Improved non-mac browser detection fallback to try installed Edge/Chrome/Firefox/Chromium candidates before system default open.
32
+ - Improved graph filter rendering to keep hub anchor nodes visible (`Memory Hub`/`MOC`/high-degree fallback) for coherent relationship context.
31
33
 
32
34
  ## 0.1.0-beta.3
33
35
 
package/COPYRIGHT.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2026 Anderson Espindola
1
+ Copyright (c) 2026 Substructa
2
2
 
3
3
  This project is licensed under the MIT License.
4
4
 
package/README.md CHANGED
@@ -571,6 +571,7 @@ The graph UI shows:
571
571
  - neutral graph nodes with segment/group metadata
572
572
  - agent selector (id-only labels) for isolated views
573
573
  - graph filter matches title, path, tags and note content
574
+ - graph filter keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) to preserve relationship readability
574
575
  - realtime refresh while `--watch` is enabled
575
576
  - graph controls for zoom in, zoom out, fit visible nodes and reset-to-fit-all
576
577
  - wheel zoom (including `cmd+scroll` and `ctrl+scroll`) anchored to cursor position for faster navigation in large graphs
@@ -1055,7 +1056,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
1055
1056
  ## License
1056
1057
 
1057
1058
  MIT. See [LICENSE](LICENSE).
1058
- Copyright (c) 2026 Anderson Espindola. See [COPYRIGHT.md](COPYRIGHT.md).
1059
+ Copyright (c) 2026 Substructa. See [COPYRIGHT.md](COPYRIGHT.md).
1059
1060
 
1060
1061
  ### Memory Optimization Loop (1-7)
1061
1062
 
@@ -46,8 +46,8 @@ export const createClientHtml = () => `<!doctype html>
46
46
  <canvas id="graph" aria-label="Brainlink knowledge graph"></canvas>
47
47
  </section>
48
48
  </main>
49
- <footer class="app-footer" aria-label="License notice">
50
- <small>MIT License · Copyright © 2026 Anderson Espindola</small>
49
+ <footer class="app-footer" aria-label="Copyright notice">
50
+ <small>Copyright © 2026 Substructa</small>
51
51
  </footer>
52
52
  <dialog id="contentDialog" class="content-dialog" aria-labelledby="contentTitle">
53
53
  <article>
@@ -99,6 +99,8 @@ const resize = () => {
99
99
  }
100
100
 
101
101
  const normalizeQuery = value => value.trim().toLowerCase()
102
+ const hubNodeRetentionLimit = 2
103
+ const hubNodePattern = /\b(memory\s*hub|knowledge\s*hub|hub|moc|map|memory\s*map|mapa)\b/i
102
104
 
103
105
  const localFilteredNodes = query =>
104
106
  state.nodes.filter(node =>
@@ -107,14 +109,51 @@ const localFilteredNodes = query =>
107
109
  node.tags.some(tag => tag.toLowerCase().includes(query))
108
110
  )
109
111
 
112
+ const rankedHubNodes = () => {
113
+ if (state.nodes.length === 0) {
114
+ return []
115
+ }
116
+
117
+ const byTitleAndDegree = [...state.nodes]
118
+ .filter(node => hubNodePattern.test(node.title) || hubNodePattern.test(node.path) || node.tags.some(tag => hubNodePattern.test(tag)))
119
+ .sort((left, right) => {
120
+ const byDegree = (state.nodeDegrees.get(right.id) ?? 0) - (state.nodeDegrees.get(left.id) ?? 0)
121
+ if (byDegree !== 0) return byDegree
122
+ return left.title.localeCompare(right.title)
123
+ })
124
+
125
+ if (byTitleAndDegree.length > 0) {
126
+ return byTitleAndDegree.slice(0, hubNodeRetentionLimit)
127
+ }
128
+
129
+ return [...state.nodes]
130
+ .sort((left, right) => {
131
+ const byDegree = (state.nodeDegrees.get(right.id) ?? 0) - (state.nodeDegrees.get(left.id) ?? 0)
132
+ if (byDegree !== 0) return byDegree
133
+ return left.title.localeCompare(right.title)
134
+ })
135
+ .slice(0, 1)
136
+ }
137
+
138
+ const withPersistentHubNodes = nodes => {
139
+ if (nodes.length === 0) {
140
+ return rankedHubNodes()
141
+ }
142
+
143
+ const ids = new Set(nodes.map(node => node.id))
144
+ const hubsToKeep = rankedHubNodes().filter(node => !ids.has(node.id))
145
+ return nodes.concat(hubsToKeep)
146
+ }
147
+
110
148
  const filteredNodes = () => {
111
149
  const query = normalizeQuery(state.query)
112
150
  if (!query) return state.nodes
113
151
  if (state.contentFilter.query === query && state.contentFilter.ids instanceof Set) {
114
- return state.nodes.filter(node => state.contentFilter.ids.has(node.id))
152
+ const matched = state.nodes.filter(node => state.contentFilter.ids.has(node.id))
153
+ return withPersistentHubNodes(matched)
115
154
  }
116
155
 
117
- return localFilteredNodes(query)
156
+ return withPersistentHubNodes(localFilteredNodes(query))
118
157
  }
119
158
 
120
159
  const recomputeVisibility = () => {
@@ -207,6 +207,10 @@ const commandExists = (command) => {
207
207
  }
208
208
  };
209
209
  const envFlagEnabled = (name) => process.env[name] === '1' || process.env[name] === 'true';
210
+ const spawnAnyDetached = (candidates) => candidates.some(([command, args]) => spawnDetached(command, args));
211
+ const windowsStartCandidates = (program, args = []) => [
212
+ ['cmd', ['/c', 'start', '', program, ...args]]
213
+ ];
210
214
  const resolveSwiftExecutable = () => {
211
215
  const directSwift = '/usr/bin/swift';
212
216
  if (existsSync(directSwift)) {
@@ -295,16 +299,47 @@ const openGraphInAppWindow = (url) => {
295
299
  }
296
300
  if (platform() === 'win32') {
297
301
  const appArgument = `--app=${url}`;
298
- return (spawnDetached('cmd', ['/c', 'start', '', 'chrome', appArgument, '--new-window']) ||
299
- spawnDetached('cmd', ['/c', 'start', '', 'msedge', appArgument, '--new-window']) ||
300
- spawnDetached('cmd', ['/c', 'start', '', 'chromium', appArgument, '--new-window']));
302
+ return spawnAnyDetached([
303
+ ...windowsStartCandidates('msedge', [appArgument, '--new-window']),
304
+ ...windowsStartCandidates('chrome', [appArgument, '--new-window']),
305
+ ...windowsStartCandidates('chromium', [appArgument, '--new-window']),
306
+ ...windowsStartCandidates('brave', [appArgument, '--new-window'])
307
+ ]);
301
308
  }
302
309
  const appArgument = `--app=${url}`;
303
- return (spawnDetached('google-chrome', [appArgument, '--new-window']) ||
304
- spawnDetached('chromium-browser', [appArgument, '--new-window']) ||
305
- spawnDetached('chromium', [appArgument, '--new-window']) ||
306
- spawnDetached('microsoft-edge', [appArgument, '--new-window']) ||
307
- spawnDetached('microsoft-edge-stable', [appArgument, '--new-window']));
310
+ const linuxAppWindowCandidates = [
311
+ 'microsoft-edge',
312
+ 'microsoft-edge-stable',
313
+ 'google-chrome',
314
+ 'google-chrome-stable',
315
+ 'chromium',
316
+ 'chromium-browser',
317
+ 'brave-browser'
318
+ ].filter((candidate) => commandExists(candidate));
319
+ return spawnAnyDetached(linuxAppWindowCandidates.map((command) => [command, [appArgument, '--new-window']]));
320
+ };
321
+ const openGraphInDetectedBrowser = (url) => {
322
+ if (platform() === 'win32') {
323
+ return spawnAnyDetached([
324
+ ...windowsStartCandidates('msedge', [url]),
325
+ ...windowsStartCandidates('chrome', [url]),
326
+ ...windowsStartCandidates('firefox', ['-new-window', url]),
327
+ ...windowsStartCandidates('chromium', [url]),
328
+ ...windowsStartCandidates('brave', [url])
329
+ ]);
330
+ }
331
+ const linuxBrowserCandidates = [
332
+ ['microsoft-edge', [url]],
333
+ ['microsoft-edge-stable', [url]],
334
+ ['google-chrome', [url]],
335
+ ['google-chrome-stable', [url]],
336
+ ['chromium', [url]],
337
+ ['chromium-browser', [url]],
338
+ ['brave-browser', [url]],
339
+ ['firefox', ['-new-window', url]]
340
+ ];
341
+ const available = linuxBrowserCandidates.filter(([command]) => commandExists(command));
342
+ return spawnAnyDetached(available);
308
343
  };
309
344
  const openUrlInUi = (url, parentPid) => {
310
345
  const openDisabled = process.env.BRAINLINK_NO_BROWSER === '1' ||
@@ -326,6 +361,9 @@ const openUrlInUi = (url, parentPid) => {
326
361
  if (platform() === 'darwin') {
327
362
  return { opened: spawnDetached('open', [url]), mode: 'browser' };
328
363
  }
364
+ if (openGraphInDetectedBrowser(url)) {
365
+ return { opened: true, mode: 'browser' };
366
+ }
329
367
  if (platform() === 'win32') {
330
368
  return { opened: spawnDetached('cmd', ['/c', 'start', '', url]), mode: 'browser' };
331
369
  }
@@ -555,6 +555,7 @@ Without `--vault`, the graph UI serves `$HOME/.brainlink/vault`.
555
555
  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.
556
556
 
557
557
  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).
558
+ During graph filtering, Brainlink keeps hub context nodes visible (`Memory Hub`/`MOC`/high-degree fallback) so filtered views still show relationship anchors.
558
559
 
559
560
  The command reindexes by default, then serves:
560
561
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@andespindola/brainlink",
3
- "version": "0.1.0-beta.36",
3
+ "version": "0.1.0-beta.37",
4
4
  "description": "Local-first knowledge memory for agents with Markdown, backlinks, indexing and context retrieval.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
- "author": "Anderson Espindola",
7
+ "author": "Substructa",
8
8
  "homepage": "https://github.com/andersonflima/brainlink#readme",
9
9
  "repository": {
10
10
  "type": "git",