@christianirack/brainllm-runtime 0.8.11 → 0.9.0

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/brain-links.js CHANGED
@@ -14,8 +14,10 @@ export default function links(lang, b, p, re, s) {
14
14
  const t = tables[document.documentElement.getAttribute('lang')]
15
15
  if (!t || !h || h[0] !== '/' || h[1] === '/') return h
16
16
  const a = t.b && (h + '/').startsWith(t.b + '/') ? h.slice(t.b.length) || '/' : h
17
- const m = /^[^?#]*/.exec(a)[0]
18
- const n = t.s[m] || (t.re.test(m) && (m === '/' ? t.p : t.p + m))
17
+ const m = /^[^?#]*/.exec(a)[0], key = m.replace(/\/+$/, '') || '/'
18
+ if (key === t.p || key.startsWith(t.p + '/')) return h
19
+ let n = t.s[key] || (t.re.test(key) && (key === '/' ? t.p : t.p + key))
20
+ if (n && key !== '/' && m.endsWith('/') && !n.endsWith('/')) n += '/'
19
21
  return n ? t.b + n + a.slice(m.length) : h
20
22
  }
21
23
  G.__brainHref = to
package/brain-list.js ADDED
@@ -0,0 +1,9 @@
1
+ // Pure bounded integer iteration, outside the runtime core.
2
+ export function range(from, to) {
3
+ if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to)) throw new RangeError('range requires two safe integer endpoints')
4
+ const length = Math.max(0, to - from)
5
+ if (length > 10000) throw new RangeError('range cannot emit more than 10000 integers')
6
+ const values = new Array(length)
7
+ for (let i = 0; i < length; i++) values[i] = from + i
8
+ return values
9
+ }
@@ -29,7 +29,7 @@ export async function prefetchPage(url, signal) {
29
29
  let hit
30
30
  while ((hit = pattern.exec(text)) && assets.length < 16) {
31
31
  const asset = new URL(hit[1] || hit[2] || hit[3], base)
32
- if (asset.origin === base.origin && asset.pathname.includes('/_b/')) assets.push(asset.href)
32
+ if (asset.origin === base.origin && (asset.pathname.includes('/_b/') || /\/[a-z0-9]{8}\.(?:js|mjs|jsbrain|css|cssbrain)$/.test(asset.pathname))) assets.push(asset.href)
33
33
  }
34
34
  return { ok: true, status: r.status, url: r.url, headers: r.headers, text, bytes, assets }
35
35
  } catch { return null }
package/brain-router.js CHANGED
@@ -96,6 +96,10 @@ export function router(o = {}) {
96
96
  const region = o.region || 'main'
97
97
  const parse = o.parse || (h => new DOMParser().parseFromString(h, 'text/html'))
98
98
  const fullLoad = new Set((o.fullLoad || []).map(p => new URL(p, location.href).pathname.replace(/\/+$/, '') || '/'))
99
+ // COOP belongs to the document, not the swapped view. Only real head markers opt into provider popups.
100
+ const popup = d => [...((d.head || d.querySelector('head'))?.querySelectorAll('meta') || [])].some(m =>
101
+ /^(brain-google-signin|brain-apple-signin)$/i.test(m.getAttribute('name') || '') && m.getAttribute('content') === '1')
102
+ const popupDocument = popup(document)
99
103
  let seq = 0, through = null, stale = false
100
104
  const ahead = predict(o, (u, p = true) => same(u, p))
101
105
  // Build signature (§35.7): the page's <meta name="brain-build">. A navigation response from another build, or the
@@ -205,6 +209,7 @@ export function router(o = {}) {
205
209
  if (!r.ok || !/html/.test(hd('content-type') || 'html')) throw Error(r.status)
206
210
  const doc = parse(await r.text())
207
211
  if (my !== seq) return // a newer navigation won
212
+ if (popup(doc) !== popupDocument) return hard(url) // entering or leaving a popup page needs fresh HTTP headers
208
213
  // another language (§37): a full load brings its own dictionaries, `lang` and `dir`; locales never mix in memory
209
214
  const lang = d => d?.documentElement?.getAttribute('lang')
210
215
  if (lang(doc) && lang(document) && lang(doc) !== lang(document)) return hard(url)
@@ -308,7 +313,7 @@ export function router(o = {}) {
308
313
  const h = e => {
309
314
  if (through && e.destination.url === through) return void (through = null)
310
315
  if (e.navigationType === 'reload') return // a reload is always a real full load (e.g. after a deploy deleted a chunk)
311
- if (!e.canIntercept || e.hashChange || e.downloadRequest || e.formData || !same(e.destination.url)) return
316
+ if (!e.canIntercept || e.hashChange || e.downloadRequest || e.formData || externalLink(e.sourceElement) || !same(e.destination.url)) return
312
317
  if (e.cancelable && !leave()) return e.preventDefault()
313
318
  const to = e.destination.url, from = nav.currentEntry?.url
314
319
  if (G.__brainURLOnly?.(from || location.href, to)) return e.intercept({ focusReset: 'manual', handler: () => G.__brainURLRestore?.() })
@@ -362,7 +367,7 @@ export function router(o = {}) {
362
367
  if (e.defaultPrevented || e.button || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
363
368
  let a = e.target
364
369
  while (a && a.tagName !== 'A') a = a.parentNode
365
- if (!a || !a.getAttribute('href') || a.getAttribute('target') || a.hasAttribute('download') || !same(a.href || a.getAttribute('href'))) return
370
+ if (!a || !a.getAttribute('href') || a.getAttribute('target') || a.hasAttribute('download') || externalLink(a) || !same(a.href || a.getAttribute('href'))) return
366
371
  const u = new URL(a.href || a.getAttribute('href'), location.href)
367
372
  if (u.pathname === location.pathname && u.hash) return // in-page anchor: native
368
373
  e.preventDefault()
@@ -416,7 +421,7 @@ export function predict(o = {}, same = () => false) {
416
421
  return !NOPE.test(u.pathname) && !FILE.test(u.pathname) && !SECRET.test(u.search)
417
422
  }
418
423
  const ok = a => {
419
- if (!a?.getAttribute?.('href') || a.getAttribute('target') || a.hasAttribute('download')) return null
424
+ if (!a?.getAttribute?.('href') || a.getAttribute('target') || a.hasAttribute('download') || externalLink(a)) return null
420
425
  for (let n = a; n?.getAttribute; n = n.parentNode) if (n.getAttribute('data-prefetch') === 'false') return null
421
426
  const k = key(a.href || a.getAttribute('href'))
422
427
  return safe(k) ? k : null
@@ -426,7 +431,7 @@ export function predict(o = {}, same = () => false) {
426
431
  let n = 0
427
432
  for (const href of list) {
428
433
  const u = key(href)
429
- if (!u || pre.has(u) || pre.size > 47 || !same(u) || !new URL(u).pathname.includes('/_b/') || ++n > 8) continue
434
+ if (!u || pre.has(u) || pre.size > 47 || !same(u) || !(new URL(u).pathname.includes('/_b/') || /\/[a-z0-9]{8}\.(?:js|mjs|jsbrain|css|cssbrain)$/.test(new URL(u).pathname)) || ++n > 8) continue
430
435
  const l = document.createElement('link')
431
436
  l.setAttribute('rel', /\.(js|jsbrain)(\?|$)/.test(u) ? 'modulepreload' : 'prefetch')
432
437
  l.setAttribute('href', u)
@@ -619,3 +624,5 @@ function hold(el) {
619
624
  },
620
625
  })
621
626
  }
627
+ // An absolute navigation can intentionally belong to another site on the same origin.
628
+ const externalLink = a => (a?.getAttribute?.('rel') || '').split(/\s+/).some(token => token.toLowerCase() === 'external') && /^https?:\/\//i.test((a?.getAttribute?.('href') || '').trim())
package/brain-text.js ADDED
@@ -0,0 +1,56 @@
1
+ // Pure bounded Unicode matching; no locale, network, DOM or persistent state.
2
+ const letters = value => {
3
+ if (typeof value !== 'string' || value.length > 8192) throw new RangeError('text helpers require strings of at most 4096 Unicode characters')
4
+ const result = Array.from(value)
5
+ if (result.length > 4096) throw new RangeError('text helpers require strings of at most 4096 Unicode characters')
6
+ return result
7
+ }
8
+ const fold = value => {
9
+ letters(value)
10
+ const result = value.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase()
11
+ letters(result)
12
+ return result
13
+ }
14
+ // Optimal string alignment: adjacent transpositions count as one edit; the band and row minimum bound work.
15
+ const distance = (left, right, max = 2) => {
16
+ if (!Number.isInteger(max) || max < 0 || max > 64) throw new RangeError('text.distance max must be an integer from 0 to 64')
17
+ const a = letters(left), b = letters(right), n = a.length, m = b.length, cap = max + 1
18
+ if (Math.abs(n - m) > max) return cap
19
+ if (!n) return Math.min(m, cap)
20
+ if (!m) return Math.min(n, cap)
21
+ let before = new Uint16Array(m + 1).fill(cap)
22
+ let previous = new Uint16Array(m + 1).fill(cap)
23
+ let current = new Uint16Array(m + 1).fill(cap)
24
+ for (let j = 0; j <= Math.min(m, max); j++) previous[j] = j
25
+ for (let i = 1; i <= n; i++) {
26
+ const low = Math.max(1, i - max), high = Math.min(m, i + max)
27
+ current[0] = Math.min(i, cap)
28
+ if (low > 1) current[low - 1] = cap
29
+ if (high < m) current[high + 1] = cap
30
+ let minimum = current[0]
31
+ for (let j = low; j <= high; j++) {
32
+ let value = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1), cap)
33
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) value = Math.min(value, before[j - 2] + 1)
34
+ current[j] = value
35
+ minimum = Math.min(minimum, value)
36
+ }
37
+ if (minimum > max) return cap
38
+ const spare = before; before = previous; previous = current; current = spare
39
+ }
40
+ return Math.min(previous[m], cap)
41
+ }
42
+ const fuzzy = (word, list) => {
43
+ const needle = fold(word), size = letters(needle).length
44
+ if (!Array.isArray(list) || list.length > 10000) throw new RangeError('text.fuzzy requires at most 10000 candidate strings')
45
+ const count = list.length, max = size < 4 ? 0 : size < 8 ? 1 : 2
46
+ const found = []
47
+ let units = 0
48
+ for (let i = 0; i < count; i++) {
49
+ const candidate = list[i]
50
+ if (typeof candidate !== 'string' || candidate.length > 1048576 - units) throw new RangeError('text.fuzzy candidate text exceeds 1048576 UTF-16 code units or contains a non-string')
51
+ units += candidate.length
52
+ if (distance(needle, fold(candidate), max) <= max) found.push(candidate)
53
+ }
54
+ return found
55
+ }
56
+ export const text = Object.freeze({ fold, distance, fuzzy })
package/brain-url.js CHANGED
@@ -45,10 +45,35 @@ export function urlState(initial, options) {
45
45
  }
46
46
 
47
47
  // A native UI primitive stays stateless in author source; its DOM adapter owns navigation state and cleanup.
48
- export function urlDialog(element, key) {
49
- if (!element || !key || G.__brainSSR || G.__brainRoute || !G.location) return
48
+ export function urlDialog(element, key, options) {
49
+ if (!element || G.__brainSSR || G.__brainRoute || !G.location) return
50
50
  return I.root(dispose => {
51
51
  let alive = true
52
+ if (options?.dismissible === true) {
53
+ let startedOutside = false
54
+ const outside = event => {
55
+ if (event.target !== element || !element.open) return false
56
+ const bounds = element.getBoundingClientRect()
57
+ return event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom
58
+ }
59
+ const down = event => { startedOutside = !event.defaultPrevented && event.button === 0 && event.isPrimary !== false && outside(event) }
60
+ const cancel = () => { startedOutside = false }
61
+ const click = event => {
62
+ const dismiss = alive && !event.defaultPrevented && startedOutside && event.button === 0 && outside(event)
63
+ startedOutside = false
64
+ if (dismiss) { event.preventDefault(); event.stopPropagation(); element.close() }
65
+ }
66
+ element.addEventListener('pointerdown', down)
67
+ element.addEventListener('pointercancel', cancel)
68
+ element.addEventListener('click', click)
69
+ fx(() => () => {
70
+ alive = false
71
+ element.removeEventListener('pointerdown', down)
72
+ element.removeEventListener('pointercancel', cancel)
73
+ element.removeEventListener('click', click)
74
+ })
75
+ }
76
+ if (!key) return dispose
52
77
  const opened = urlState(!!element.open, {key})
53
78
  const read = () => { if (alive) opened.v = !!element.open }
54
79
  const apply = value => {
@@ -59,7 +84,15 @@ export function urlDialog(element, key) {
59
84
  fx(() => {
60
85
  const value = opened.v
61
86
  if (element.isConnected) apply(value)
62
- else queueMicrotask(() => apply(value))
87
+ else {
88
+ const wasOpen = !!element.open
89
+ queueMicrotask(() => {
90
+ // An exposed open() may run immediately after mount, before this initial deferred sync.
91
+ // Preserve that newer DOM action instead of applying the old initial state over it.
92
+ if (alive && !!element.open !== wasOpen) read()
93
+ else apply(value)
94
+ })
95
+ }
63
96
  })
64
97
  const observer = G.MutationObserver ? new G.MutationObserver(read) : null
65
98
  observer?.observe(element, {attributes:true,attributeFilter:['open']})
package/brain.js CHANGED
@@ -350,20 +350,26 @@ export function _ev(e) {
350
350
  // a data property, not a getter: V8 keeps accessors in the event's shared hidden class, so a getter closure
351
351
  // would keep the first event (and its component) alive for the life of the page
352
352
  Object.defineProperty(e, 'currentTarget', { configurable: true, value: n })
353
- try {
354
- // n moves on with the loop: keep the element for a later rejection
355
- const r = h.call(n, e), el = n
356
- if (r && r.then) r.then(null, x => oops(x, el, 'on:' + e.type)) // async handler: a rejected await too
357
- } catch (x) {
358
- oops(x, n, 'on:' + e.type)
359
- }
353
+ eventCall(n, h, e)
360
354
  if (e.cancelBubble) break
361
355
  }
362
356
  }
363
357
  })
364
358
  }
359
+ function eventCall(el, h, e) {
360
+ try {
361
+ const r = h.call(el, e)
362
+ if (r && r.then) r.then(null, x => oops(x, el, 'on:' + e.type))
363
+ } catch (x) { oops(x, el, 'on:' + e.type) }
364
+ }
365
365
  export function on(el, t, h) {
366
- if (/^(focus|blur|mouseenter|mouseleave|load|error|scroll)$/.test(t)) return el.addEventListener(t, e => batch(() => h(e)))
366
+ if (/^(focus|blur|mouseenter|mouseleave|pointerenter|pointerleave|load|error|scroll|scrollend|resize|close|cancel|toggle|beforetoggle|invalid|abort|canplay|canplaythrough|durationchange|emptied|ended|loadeddata|loadedmetadata|loadstart|pause|play|playing|progress|ratechange|seeked|seeking|stalled|suspend|timeupdate|volumechange|waiting|encrypted|waitingforkey)$/.test(t)) {
367
+ const listener = e => batch(() => eventCall(el, h, e))
368
+ return fx(() => {
369
+ el.addEventListener(t, listener)
370
+ return () => el.removeEventListener(t, listener)
371
+ })
372
+ }
367
373
  el['$' + t] = h
368
374
  document.addEventListener(t, _ev) // once per type: the DOM ignores the same listener added again
369
375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@christianirack/brainllm-runtime",
3
- "version": "0.8.11",
3
+ "version": "0.9.0",
4
4
  "description": "The BRAINLLM runtime for the Rust-built web compiler for LLMs: dependency-free ES modules (signals, DOM, router, data, forms, channels). The brain binary embeds this same version.",
5
5
  "type": "module",
6
6
  "exports": {