brustjs 0.1.62-alpha → 0.1.64-alpha

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brustjs",
3
- "version": "0.1.62-alpha",
3
+ "version": "0.1.64-alpha",
4
4
  "description": "Bun + Rust SSR framework — React on the server, Rust everywhere else (napi cdylib + per-worker SharedArrayBuffer).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,12 +41,12 @@
41
41
  "typescript": "^6.0.3"
42
42
  },
43
43
  "optionalDependencies": {
44
- "brustjs-darwin-x64": "0.1.62-alpha",
45
- "brustjs-darwin-arm64": "0.1.62-alpha",
46
- "brustjs-linux-x64-gnu": "0.1.62-alpha",
47
- "brustjs-linux-arm64-gnu": "0.1.62-alpha",
48
- "brustjs-linux-x64-musl": "0.1.62-alpha",
49
- "brustjs-linux-arm64-musl": "0.1.62-alpha"
44
+ "brustjs-darwin-x64": "0.1.64-alpha",
45
+ "brustjs-darwin-arm64": "0.1.64-alpha",
46
+ "brustjs-linux-x64-gnu": "0.1.64-alpha",
47
+ "brustjs-linux-arm64-gnu": "0.1.64-alpha",
48
+ "brustjs-linux-x64-musl": "0.1.64-alpha",
49
+ "brustjs-linux-arm64-musl": "0.1.64-alpha"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": "^19.2.6",
@@ -8,6 +8,22 @@ import { islandChunkBasename } from '../islands/chunk-id.ts'
8
8
  import { emitBehaviorSsrModule } from '../islands/behavior-ssr-loader.ts'
9
9
  import { DIRECTIVES_BOOTSTRAP, ISLANDS_IMPORTMAP_AND_BOOTSTRAP } from '../islands/importmap.ts'
10
10
 
11
+ const NATIVE_INLINE_FALLBACK_WARNING = /^native component "([^"\r\n]+)" not inlined: ([\s\S]+)$/
12
+
13
+ export function formatCompilerWarning(warning: string): string {
14
+ const match = NATIVE_INLINE_FALLBACK_WARNING.exec(warning)
15
+ if (!match) return `brust: ${warning}`
16
+
17
+ const [, component, reason] = match
18
+ return [
19
+ `brust: warning — native component "${component}" was not inlined`,
20
+ ` reason: ${reason}`,
21
+ ' impact: rendered through React SSR; React hooks and event handlers are not hydrated automatically on a native route',
22
+ ` interactive fix: use <Island component={${component}} props={...} />`,
23
+ ` zero-JS fix: rewrite ${component} using native-compatible JSX`,
24
+ ].join('\n')
25
+ }
26
+
11
27
  /** Gather transitive component sources starting from a page source file.
12
28
  *
13
29
  * BFS/DFS over local imports reachable from `pageSourcePath`:
@@ -936,8 +952,12 @@ export async function emitNativeTemplates(opts: NativeRouteEmitOpts): Promise<Na
936
952
  )
937
953
  }
938
954
 
939
- // Print non-fatal compiler warnings to stderr.
940
- for (const w of compiled.warnings ?? []) process.stderr.write(`brust: ${w}\n`)
955
+ // Print non-fatal compiler warnings to stderr. Native-inline fallbacks get
956
+ // actionable multiline guidance; all other warning strings retain the
957
+ // existing one-line prefix.
958
+ for (const warning of compiled.warnings ?? []) {
959
+ process.stderr.write(`${formatCompilerWarning(warning)}\n`)
960
+ }
941
961
 
942
962
  // SPA navigation extracts the FIRST <main>…</main> block, so a native route
943
963
  // template must hold exactly one <main>. More than one (typically a leaf
package/runtime/index.js CHANGED
@@ -77,8 +77,8 @@ function requireNative() {
77
77
  try {
78
78
  const binding = require('brustjs-android-arm64')
79
79
  const bindingPackageVersion = require('brustjs-android-arm64/package.json').version
80
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
80
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
82
82
  }
83
83
  return binding
84
84
  } catch (e) {
@@ -93,8 +93,8 @@ function requireNative() {
93
93
  try {
94
94
  const binding = require('brustjs-android-arm-eabi')
95
95
  const bindingPackageVersion = require('brustjs-android-arm-eabi/package.json').version
96
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
96
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
98
98
  }
99
99
  return binding
100
100
  } catch (e) {
@@ -114,8 +114,8 @@ function requireNative() {
114
114
  try {
115
115
  const binding = require('brustjs-win32-x64-gnu')
116
116
  const bindingPackageVersion = require('brustjs-win32-x64-gnu/package.json').version
117
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
117
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
119
119
  }
120
120
  return binding
121
121
  } catch (e) {
@@ -130,8 +130,8 @@ function requireNative() {
130
130
  try {
131
131
  const binding = require('brustjs-win32-x64-msvc')
132
132
  const bindingPackageVersion = require('brustjs-win32-x64-msvc/package.json').version
133
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
133
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
135
135
  }
136
136
  return binding
137
137
  } catch (e) {
@@ -147,8 +147,8 @@ function requireNative() {
147
147
  try {
148
148
  const binding = require('brustjs-win32-ia32-msvc')
149
149
  const bindingPackageVersion = require('brustjs-win32-ia32-msvc/package.json').version
150
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
150
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
152
152
  }
153
153
  return binding
154
154
  } catch (e) {
@@ -163,8 +163,8 @@ function requireNative() {
163
163
  try {
164
164
  const binding = require('brustjs-win32-arm64-msvc')
165
165
  const bindingPackageVersion = require('brustjs-win32-arm64-msvc/package.json').version
166
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
166
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
168
168
  }
169
169
  return binding
170
170
  } catch (e) {
@@ -182,8 +182,8 @@ function requireNative() {
182
182
  try {
183
183
  const binding = require('brustjs-darwin-universal')
184
184
  const bindingPackageVersion = require('brustjs-darwin-universal/package.json').version
185
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
185
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
187
187
  }
188
188
  return binding
189
189
  } catch (e) {
@@ -198,8 +198,8 @@ function requireNative() {
198
198
  try {
199
199
  const binding = require('brustjs-darwin-x64')
200
200
  const bindingPackageVersion = require('brustjs-darwin-x64/package.json').version
201
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
201
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
203
203
  }
204
204
  return binding
205
205
  } catch (e) {
@@ -214,8 +214,8 @@ function requireNative() {
214
214
  try {
215
215
  const binding = require('brustjs-darwin-arm64')
216
216
  const bindingPackageVersion = require('brustjs-darwin-arm64/package.json').version
217
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
217
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
219
219
  }
220
220
  return binding
221
221
  } catch (e) {
@@ -234,8 +234,8 @@ function requireNative() {
234
234
  try {
235
235
  const binding = require('brustjs-freebsd-x64')
236
236
  const bindingPackageVersion = require('brustjs-freebsd-x64/package.json').version
237
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
237
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
239
239
  }
240
240
  return binding
241
241
  } catch (e) {
@@ -250,8 +250,8 @@ function requireNative() {
250
250
  try {
251
251
  const binding = require('brustjs-freebsd-arm64')
252
252
  const bindingPackageVersion = require('brustjs-freebsd-arm64/package.json').version
253
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
253
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
255
255
  }
256
256
  return binding
257
257
  } catch (e) {
@@ -271,8 +271,8 @@ function requireNative() {
271
271
  try {
272
272
  const binding = require('brustjs-linux-x64-musl')
273
273
  const bindingPackageVersion = require('brustjs-linux-x64-musl/package.json').version
274
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
274
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
276
276
  }
277
277
  return binding
278
278
  } catch (e) {
@@ -287,8 +287,8 @@ function requireNative() {
287
287
  try {
288
288
  const binding = require('brustjs-linux-x64-gnu')
289
289
  const bindingPackageVersion = require('brustjs-linux-x64-gnu/package.json').version
290
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
290
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
292
292
  }
293
293
  return binding
294
294
  } catch (e) {
@@ -305,8 +305,8 @@ function requireNative() {
305
305
  try {
306
306
  const binding = require('brustjs-linux-arm64-musl')
307
307
  const bindingPackageVersion = require('brustjs-linux-arm64-musl/package.json').version
308
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
308
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
310
310
  }
311
311
  return binding
312
312
  } catch (e) {
@@ -321,8 +321,8 @@ function requireNative() {
321
321
  try {
322
322
  const binding = require('brustjs-linux-arm64-gnu')
323
323
  const bindingPackageVersion = require('brustjs-linux-arm64-gnu/package.json').version
324
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
324
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
326
326
  }
327
327
  return binding
328
328
  } catch (e) {
@@ -339,8 +339,8 @@ function requireNative() {
339
339
  try {
340
340
  const binding = require('brustjs-linux-arm-musleabihf')
341
341
  const bindingPackageVersion = require('brustjs-linux-arm-musleabihf/package.json').version
342
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
342
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
344
344
  }
345
345
  return binding
346
346
  } catch (e) {
@@ -355,8 +355,8 @@ function requireNative() {
355
355
  try {
356
356
  const binding = require('brustjs-linux-arm-gnueabihf')
357
357
  const bindingPackageVersion = require('brustjs-linux-arm-gnueabihf/package.json').version
358
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
358
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
360
360
  }
361
361
  return binding
362
362
  } catch (e) {
@@ -373,8 +373,8 @@ function requireNative() {
373
373
  try {
374
374
  const binding = require('brustjs-linux-loong64-musl')
375
375
  const bindingPackageVersion = require('brustjs-linux-loong64-musl/package.json').version
376
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
376
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
378
378
  }
379
379
  return binding
380
380
  } catch (e) {
@@ -389,8 +389,8 @@ function requireNative() {
389
389
  try {
390
390
  const binding = require('brustjs-linux-loong64-gnu')
391
391
  const bindingPackageVersion = require('brustjs-linux-loong64-gnu/package.json').version
392
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
392
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
394
394
  }
395
395
  return binding
396
396
  } catch (e) {
@@ -407,8 +407,8 @@ function requireNative() {
407
407
  try {
408
408
  const binding = require('brustjs-linux-riscv64-musl')
409
409
  const bindingPackageVersion = require('brustjs-linux-riscv64-musl/package.json').version
410
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
410
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
412
412
  }
413
413
  return binding
414
414
  } catch (e) {
@@ -423,8 +423,8 @@ function requireNative() {
423
423
  try {
424
424
  const binding = require('brustjs-linux-riscv64-gnu')
425
425
  const bindingPackageVersion = require('brustjs-linux-riscv64-gnu/package.json').version
426
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
426
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
428
428
  }
429
429
  return binding
430
430
  } catch (e) {
@@ -440,8 +440,8 @@ function requireNative() {
440
440
  try {
441
441
  const binding = require('brustjs-linux-ppc64-gnu')
442
442
  const bindingPackageVersion = require('brustjs-linux-ppc64-gnu/package.json').version
443
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
443
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
445
445
  }
446
446
  return binding
447
447
  } catch (e) {
@@ -456,8 +456,8 @@ function requireNative() {
456
456
  try {
457
457
  const binding = require('brustjs-linux-s390x-gnu')
458
458
  const bindingPackageVersion = require('brustjs-linux-s390x-gnu/package.json').version
459
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
459
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
461
461
  }
462
462
  return binding
463
463
  } catch (e) {
@@ -476,8 +476,8 @@ function requireNative() {
476
476
  try {
477
477
  const binding = require('brustjs-openharmony-arm64')
478
478
  const bindingPackageVersion = require('brustjs-openharmony-arm64/package.json').version
479
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
479
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
481
481
  }
482
482
  return binding
483
483
  } catch (e) {
@@ -492,8 +492,8 @@ function requireNative() {
492
492
  try {
493
493
  const binding = require('brustjs-openharmony-x64')
494
494
  const bindingPackageVersion = require('brustjs-openharmony-x64/package.json').version
495
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
495
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
497
497
  }
498
498
  return binding
499
499
  } catch (e) {
@@ -508,8 +508,8 @@ function requireNative() {
508
508
  try {
509
509
  const binding = require('brustjs-openharmony-arm')
510
510
  const bindingPackageVersion = require('brustjs-openharmony-arm/package.json').version
511
- if (bindingPackageVersion !== '0.1.62-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
- throw new Error(`Native binding package version mismatch, expected 0.1.62-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
511
+ if (bindingPackageVersion !== '0.1.64-alpha' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
+ throw new Error(`Native binding package version mismatch, expected 0.1.64-alpha but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
513
513
  }
514
514
  return binding
515
515
  } catch (e) {
@@ -12,22 +12,25 @@ export type Instance = Record<string, unknown>
12
12
  * for side-effects on signal change (sync localStorage, the DOM
13
13
  * outside the component, timers). Returns the disposer too.
14
14
  * - `onCleanup(fn)` — register a one-shot teardown for unmount (e.g. removeEventListener). */
15
- export interface BehaviorCtx {
16
- el: HTMLElement
15
+ export interface BehaviorCtx<Host extends Element = HTMLElement> {
16
+ el: Host
17
17
  props: unknown
18
18
  // biome-ignore lint/suspicious/noConfusingVoidType: React useEffect return shape (`void | Destructor`) — see store `effect`.
19
19
  effect: (fn: () => void | (() => void)) => () => void
20
20
  onCleanup: (fn: () => void) => void
21
21
  }
22
- export type Behavior = (ctx: BehaviorCtx) => Instance
22
+ export type Behavior<Host extends Element = HTMLElement> = (ctx: BehaviorCtx<Host>) => Instance
23
23
 
24
24
  interface Mounted {
25
25
  disposers: Array<() => void>
26
26
  }
27
27
 
28
- const registry = new Map<string, Behavior>()
29
- const mounted = new WeakMap<HTMLElement, Mounted>()
28
+ type RegisteredBehavior = Behavior<Element>
29
+
30
+ const registry = new Map<string, RegisteredBehavior>()
31
+ const mounted = new WeakMap<Element, Mounted>()
30
32
  const loading = new Map<string, Promise<unknown>>()
33
+ const pending = new Map<string, Set<Element>>()
31
34
  let started = false
32
35
 
33
36
  /** Per-component behavior chunk URL. Each native interactive component is built to
@@ -39,8 +42,15 @@ const CHUNK_BASE = '/_brust/islands/'
39
42
  /** Register a component behavior under `name`. Called by `<name>.directive.js` chunks
40
43
  * via the global handle below (they do NOT import this module — keeps each chunk to
41
44
  * just its behavior, with the runtime shared as the single `_directives.js` copy). */
42
- export function register(name: string, behavior: Behavior): void {
43
- registry.set(name, behavior)
45
+ export function register<Host extends Element = HTMLElement>(
46
+ name: string,
47
+ behavior: Behavior<Host>,
48
+ ): void {
49
+ registry.set(name, behavior as unknown as RegisteredBehavior)
50
+ const hosts = pending.get(name)
51
+ if (!hosts) return
52
+ pending.delete(name)
53
+ for (const el of hosts) mountElement(el)
44
54
  }
45
55
  // Expose `register` on a global so dynamically-imported behavior chunks self-register
46
56
  // into THIS runtime's registry without importing/duplicating the runtime. Symbol.for
@@ -72,8 +82,8 @@ export function start(root?: ParentNode): void {
72
82
  }
73
83
 
74
84
  function scanAndMount(scope: ParentNode): void {
75
- if (scope instanceof HTMLElement && scope.hasAttribute('x-data')) mountElement(scope)
76
- for (const el of Array.from(scope.querySelectorAll<HTMLElement>('[x-data]'))) {
85
+ if (scope instanceof Element && scope.hasAttribute('x-data')) mountElement(scope)
86
+ for (const el of Array.from(scope.querySelectorAll<Element>('[x-data]'))) {
77
87
  mountElement(el)
78
88
  }
79
89
  // R10 — OPEN shadow roots host their own component trees: scan each the same
@@ -81,8 +91,8 @@ function scanAndMount(scope: ParentNode): void {
81
91
  // per root, since neither the body observer nor an outer root's observer sees
82
92
  // mutations inside an inner shadow tree. Closed roots expose `shadowRoot ===
83
93
  // null` and are unreachable by design. The walk-all is per added subtree only.
84
- if (scope instanceof HTMLElement && scope.shadowRoot) scanShadowRoot(scope.shadowRoot)
85
- for (const el of Array.from(scope.querySelectorAll<HTMLElement>('*'))) {
94
+ if (scope instanceof Element && scope.shadowRoot) scanShadowRoot(scope.shadowRoot)
95
+ for (const el of Array.from(scope.querySelectorAll<Element>('*'))) {
86
96
  if (el.shadowRoot) scanShadowRoot(el.shadowRoot)
87
97
  }
88
98
  }
@@ -92,7 +102,7 @@ function scanShadowRoot(root: ShadowRoot): void {
92
102
  observeRoot(root)
93
103
  }
94
104
 
95
- function mountElement(el: HTMLElement): void {
105
+ function mountElement(el: Element): void {
96
106
  if (mounted.has(el)) return
97
107
  // Removed mid-scan (e.g. an initial-falsy x-if subtree pruned while scanAndMount's
98
108
  // snapshot loop was still iterating): mounting a detached element would leak its
@@ -101,6 +111,12 @@ function mountElement(el: HTMLElement): void {
101
111
  const name = el.getAttribute('x-data') ?? ''
102
112
  const behavior = registry.get(name)
103
113
  if (!behavior) {
114
+ let hosts = pending.get(name)
115
+ if (!hosts) {
116
+ hosts = new Set()
117
+ pending.set(name, hosts)
118
+ }
119
+ hosts.add(el)
104
120
  // Behavior chunk not loaded yet → fetch it on demand, then mount this name.
105
121
  loadBehavior(name)
106
122
  return
@@ -124,12 +140,12 @@ function mountElement(el: HTMLElement): void {
124
140
  mounted.set(el, m)
125
141
  // ctxEffect/onCleanup typed via BehaviorCtx so the `void | Destructor` shape is
126
142
  // declared in one place (the interface) — no inline void-union to suppress here.
127
- const ctxEffect: BehaviorCtx['effect'] = (fn) => {
143
+ const ctxEffect: BehaviorCtx<Element>['effect'] = (fn) => {
128
144
  const dispose = effect(fn)
129
145
  m.disposers.push(dispose)
130
146
  return dispose
131
147
  }
132
- const onCleanup: BehaviorCtx['onCleanup'] = (fn) => {
148
+ const onCleanup: BehaviorCtx<Element>['onCleanup'] = (fn) => {
133
149
  m.disposers.push(fn)
134
150
  }
135
151
  const instance = behavior({ el, props, effect: ctxEffect, onCleanup })
@@ -150,6 +166,7 @@ function mountElement(el: HTMLElement): void {
150
166
  function loadBehavior(name: string): void {
151
167
  if (registry.has(name) || loading.has(name)) return
152
168
  if (!/^[A-Za-z0-9_-]+$/.test(name)) {
169
+ pending.delete(name)
153
170
  console.warn(`[brust] unsafe x-data component name "${name}" — not loaded`)
154
171
  return
155
172
  }
@@ -159,17 +176,16 @@ function loadBehavior(name: string): void {
159
176
  .then(() => import(/* @vite-ignore */ `${CHUNK_BASE}${name}.directive.js`))
160
177
  .then(() => {
161
178
  if (!registry.has(name)) {
179
+ loading.delete(name)
180
+ pending.delete(name)
162
181
  console.warn(`[brust] "${name}.directive.js" loaded but did not register "${name}"`)
163
- return
164
- }
165
- // Mount every element waiting on this name (initial + swapped-in).
166
- if (typeof document !== 'undefined') {
167
- for (const el of Array.from(document.querySelectorAll<HTMLElement>(`[x-data="${name}"]`))) {
168
- mountElement(el)
169
- }
170
182
  }
171
183
  })
172
- .catch((e) => console.error(`[brust] failed to load directive component "${name}":`, e))
184
+ .catch((e) => {
185
+ loading.delete(name)
186
+ pending.delete(name)
187
+ console.error(`[brust] failed to load directive component "${name}":`, e)
188
+ })
173
189
  loading.set(name, p)
174
190
  }
175
191
 
@@ -180,7 +196,7 @@ function loadBehavior(name: string): void {
180
196
  // independently via scanAndMount's shadow-root scan (R10), never inheriting the
181
197
  // enclosing instance's scope. (The `el.children` walk below naturally excludes
182
198
  // shadow content; this is by design, not an accident.)
183
- function bindTree(el: HTMLElement, instance: Instance, disposers: Array<() => void>): void {
199
+ function bindTree(el: Element, instance: Instance, disposers: Array<() => void>): void {
184
200
  // Coexistence check MUST precede the x-for early-exit, else x-for preempts and the
185
201
  // warn never fires. Strip x-if so x-for's template clones don't carry it either.
186
202
  if (el.hasAttribute('x-if') && el.hasAttribute('x-for')) {
@@ -197,7 +213,7 @@ function bindTree(el: HTMLElement, instance: Instance, disposers: Array<() => vo
197
213
  }
198
214
  bindAttrs(el, instance, disposers)
199
215
  for (const child of Array.from(el.children)) {
200
- if (!(child instanceof HTMLElement)) continue
216
+ if (!(child instanceof Element)) continue
201
217
  if (child.hasAttribute('x-data')) continue
202
218
  bindTree(child, instance, disposers)
203
219
  }
@@ -243,7 +259,7 @@ export function parseFor(raw: string): ForExpr | null {
243
259
  }
244
260
 
245
261
  interface ForEntry {
246
- node: HTMLElement
262
+ node: Element
247
263
  itemSig: Signal<unknown>
248
264
  idxSig?: Signal<number>
249
265
  disposers: Array<() => void>
@@ -259,7 +275,7 @@ const forMountGuard = new WeakMap<Node, Set<string>>()
259
275
  // (legacy v1, now with optional plain index). With `by <keypath>...` it is an opt-in
260
276
  // keyed reconcile that reuses DOM nodes (focus/scroll survive) and is reactive
261
277
  // per-item via a per-clone `signal(item)` resolved through `read`'s unwrap-each-hop.
262
- function bindFor(tplEl: HTMLElement, instance: Instance, disposers: Array<() => void>): void {
278
+ function bindFor(tplEl: Element, instance: Instance, disposers: Array<() => void>): void {
263
279
  const expr = parseFor(tplEl.getAttribute('x-for') ?? '')
264
280
  if (!expr) {
265
281
  console.warn(`[brust] malformed x-for expression: "${tplEl.getAttribute('x-for')}"`)
@@ -292,12 +308,12 @@ function bindFor(tplEl: HTMLElement, instance: Instance, disposers: Array<() =>
292
308
  const anchor = tplEl.ownerDocument.createComment(`x-for:${itemName}`)
293
309
  parent.insertBefore(anchor, tplEl)
294
310
  tplEl.removeAttribute('x-for')
295
- const template = tplEl.cloneNode(true) as HTMLElement
311
+ const template = tplEl.cloneNode(true) as Element
296
312
  tplEl.remove()
297
313
 
298
314
  // ---- legacy (no `by`) — full re-render, with optional plain index ----
299
315
  if (!keyPaths) {
300
- const rendered: HTMLElement[] = []
316
+ const rendered: Element[] = []
301
317
  const childDisposers: Array<() => void> = []
302
318
  const clear = () => {
303
319
  for (const d of childDisposers.splice(0)) {
@@ -315,7 +331,7 @@ function bindFor(tplEl: HTMLElement, instance: Instance, disposers: Array<() =>
315
331
  const list = read(instance, listPath)
316
332
  if (!Array.isArray(list)) return
317
333
  for (let i = 0; i < list.length; i++) {
318
- const clone = template.cloneNode(true) as HTMLElement
334
+ const clone = template.cloneNode(true) as Element
319
335
  const childScope: Instance = Object.create(instance)
320
336
  childScope[itemName] = list[i]
321
337
  if (indexName) childScope[indexName] = i
@@ -340,7 +356,7 @@ function installKeyedReconcile(
340
356
  instance: Instance,
341
357
  parent: Node,
342
358
  expr: ForExpr,
343
- template: HTMLElement,
359
+ template: Element,
344
360
  anchor: Comment,
345
361
  initialMap: Map<string, ForEntry>,
346
362
  disposers: Array<() => void>,
@@ -382,7 +398,7 @@ function installKeyedReconcile(
382
398
  live.delete(key)
383
399
  next.set(key, existing)
384
400
  } else {
385
- const clone = template.cloneNode(true) as HTMLElement
401
+ const clone = template.cloneNode(true) as Element
386
402
  const itemSig = signal(item)
387
403
  const idxSig = indexName ? signal(i) : undefined
388
404
  const childScope: Instance = Object.create(instance)
@@ -409,11 +425,11 @@ function installKeyedReconcile(
409
425
  * `data-x-key-0`). Only direct children — the key attr lives on the for-item
410
426
  * root, never a descendant. The x-for match prevents a sibling x-for list under
411
427
  * the same parent from having its seeds consumed by this one. */
412
- function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string): HTMLElement[] {
428
+ function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string): Element[] {
413
429
  const sel = keyPaths.length > 1 ? '[data-x-key-0]' : '[data-x-key]'
414
- const out: HTMLElement[] = []
430
+ const out: Element[] = []
415
431
  for (const c of Array.from((parent as Element).children ?? [])) {
416
- if (c instanceof HTMLElement && c.matches(sel) && c.getAttribute('x-for') === xforRaw) {
432
+ if (c instanceof Element && c.matches(sel) && c.getAttribute('x-for') === xforRaw) {
417
433
  out.push(c)
418
434
  }
419
435
  }
@@ -422,14 +438,14 @@ function collectSeeds(parent: Node, keyPaths: string[], xforRaw: string): HTMLEl
422
438
 
423
439
  /** The seed's key from markup — must match the reconcile's computed key (single
424
440
  * `data-x-key`, OR `data-x-key-*` joined with `\x00` IN JS — NUL never in HTML). */
425
- function seedKey(node: HTMLElement, keyPaths: string[]): string {
441
+ function seedKey(node: Element, keyPaths: string[]): string {
426
442
  if (keyPaths.length > 1) {
427
443
  return keyPaths.map((_, i) => node.getAttribute(`data-x-key-${i}`) ?? '').join('\x00')
428
444
  }
429
445
  return node.getAttribute('data-x-key') ?? ''
430
446
  }
431
447
 
432
- function stripKeyAttrs(el: HTMLElement, keyPaths: string[]): void {
448
+ function stripKeyAttrs(el: Element, keyPaths: string[]): void {
433
449
  if (keyPaths.length > 1) {
434
450
  for (let i = 0; i < keyPaths.length; i++) el.removeAttribute(`data-x-key-${i}`)
435
451
  } else {
@@ -440,10 +456,10 @@ function stripKeyAttrs(el: HTMLElement, keyPaths: string[]): void {
440
456
  /** Bind an adopted seed node as a plain subtree. The node KEEPS its x-for attr so
441
457
  * the parent bindTree loop's later re-visit routes back to bindFor → mount guard
442
458
  * no-ops; do NOT route the node itself through bindFor again here. */
443
- function bindAdoptedNode(node: HTMLElement, scope: Instance, disposers: Array<() => void>): void {
459
+ function bindAdoptedNode(node: Element, scope: Instance, disposers: Array<() => void>): void {
444
460
  bindAttrs(node, scope, disposers)
445
461
  for (const child of Array.from(node.children)) {
446
- if (!(child instanceof HTMLElement)) continue
462
+ if (!(child instanceof Element)) continue
447
463
  if (child.hasAttribute('x-data')) continue
448
464
  bindTree(child, scope, disposers)
449
465
  }
@@ -453,7 +469,7 @@ function bindAdoptedNode(node: HTMLElement, scope: Instance, disposers: Array<()
453
469
  * each item-signal from the matching client item by key, wire reactivity, then
454
470
  * hand the pre-populated map to the shared reconcile (first run = all reused). */
455
471
  function bindForAdopt(
456
- seeds: HTMLElement[],
472
+ seeds: Element[],
457
473
  instance: Instance,
458
474
  parent: Node,
459
475
  expr: ForExpr,
@@ -469,7 +485,7 @@ function bindForAdopt(
469
485
  }
470
486
  const keys = keyPaths as string[]
471
487
  // template for future creates: stripped clone of the first seed.
472
- const template = seeds[0].cloneNode(true) as HTMLElement
488
+ const template = seeds[0].cloneNode(true) as Element
473
489
  template.removeAttribute('x-for')
474
490
  stripKeyAttrs(template, keys)
475
491
  // anchor AFTER the last seed so future inserts keep document order.
@@ -489,7 +505,7 @@ function bindForAdopt(
489
505
  // adopt each seed in place.
490
506
  const map = new Map<string, ForEntry>()
491
507
  for (let si = 0; si < seeds.length; si++) {
492
- const node = seeds[si] as HTMLElement
508
+ const node = seeds[si] as Element
493
509
  let key = seedKey(node, keys)
494
510
  if (map.has(key)) {
495
511
  // mirror the reconcile's dup-key handling: suffix so the entry is tracked
@@ -518,15 +534,15 @@ function bindForAdopt(
518
534
  * the node and runs those disposers. The per-clone disposers cover only non-x-data
519
535
  * teardown (bindTree skips nested x-data); nested x-data dispose/mount is delegated
520
536
  * to the MutationObserver on removal/insert — single-owner discipline. */
521
- function bindIf(el: HTMLElement, instance: Instance, disposers: Array<() => void>): void {
537
+ function bindIf(el: Element, instance: Instance, disposers: Array<() => void>): void {
522
538
  const path = el.getAttribute('x-if') ?? ''
523
539
  const parent = el.parentNode
524
540
  if (!parent) return
525
541
  const anchor = el.ownerDocument.createComment('x-if')
526
542
  parent.insertBefore(anchor, el)
527
543
  el.removeAttribute('x-if')
528
- const template = el.cloneNode(true) as HTMLElement // capture FIRST (before initial effect)
529
- let current: HTMLElement | null = el // the original, adopted if initially truthy
544
+ const template = el.cloneNode(true) as Element // capture FIRST (before initial effect)
545
+ let current: Element | null = el // the original, adopted if initially truthy
530
546
  let bound = false // original starts unbound; clones are bound at creation
531
547
  const currentDisposers: Array<() => void> = []
532
548
  const teardown = () => {
@@ -557,7 +573,7 @@ function bindIf(el: HTMLElement, instance: Instance, disposers: Array<() => void
557
573
  }
558
574
  return
559
575
  }
560
- const clone = template.cloneNode(true) as HTMLElement
576
+ const clone = template.cloneNode(true) as Element
561
577
  bindTree(clone, instance, currentDisposers) // bind BEFORE insert (observer mounts nested x-data after)
562
578
  anchor.parentNode?.insertBefore(clone, anchor.nextSibling)
563
579
  current = clone
@@ -657,13 +673,14 @@ function bindModel(
657
673
  )
658
674
  }
659
675
 
660
- function bindAttrs(el: HTMLElement, scope: Instance, disposers: Array<() => void>): void {
676
+ function bindAttrs(el: Element, scope: Instance, disposers: Array<() => void>): void {
661
677
  for (const attr of Array.from(el.attributes)) {
662
678
  const name = attr.name
663
679
  const value = attr.value
664
680
  if (name === 'x-data' || name === 'x-props') continue
665
681
  if (name === 'x-model') {
666
- bindModel(el, scope, value, disposers)
682
+ if (el instanceof HTMLElement) bindModel(el, scope, value, disposers)
683
+ else console.warn('[brust] x-model is only supported on HTML elements — binding skipped')
667
684
  continue
668
685
  }
669
686
  if (name === 'x-text') {
@@ -678,7 +695,11 @@ function bindAttrs(el: HTMLElement, scope: Instance, disposers: Array<() => void
678
695
  if (name === 'x-show') {
679
696
  disposers.push(
680
697
  effect(() => {
681
- el.style.display = read(scope, value) ? '' : 'none'
698
+ if ('style' in el) {
699
+ ;(el as Element & { style: CSSStyleDeclaration }).style.display = read(scope, value)
700
+ ? ''
701
+ : 'none'
702
+ }
682
703
  }),
683
704
  )
684
705
  continue
@@ -718,40 +739,40 @@ function observeRoot(root: Node): void {
718
739
  const obs = new MutationObserver((records) => {
719
740
  for (const rec of records) {
720
741
  for (const node of Array.from(rec.removedNodes)) {
721
- if (node instanceof HTMLElement) disposeTree(node)
742
+ if (node instanceof Element) disposeTree(node)
722
743
  }
723
744
  for (const node of Array.from(rec.addedNodes)) {
724
- if (node instanceof HTMLElement) scanAndMount(node)
745
+ if (node instanceof Element) scanAndMount(node)
725
746
  }
726
747
  }
727
748
  })
728
749
  obs.observe(root, { childList: true, subtree: true })
729
750
  }
730
751
 
731
- function disposeTree(node: HTMLElement): void {
752
+ function disposeTree(node: Element): void {
732
753
  if (mounted.has(node)) disposeElement(node)
733
- for (const el of Array.from(node.querySelectorAll<HTMLElement>('[x-data]'))) {
754
+ for (const el of Array.from(node.querySelectorAll<Element>('[x-data]'))) {
734
755
  disposeElement(el)
735
756
  }
736
757
  // R10 — a removed HOST's shadow contents never reach any observer: the host's
737
758
  // removal fires on the light tree's observer, and the shadow root's own
738
759
  // observer only sees mutations INSIDE the root. Walk shadow roots explicitly.
739
760
  if (node.shadowRoot) disposeShadowContents(node.shadowRoot)
740
- for (const el of Array.from(node.querySelectorAll<HTMLElement>('*'))) {
761
+ for (const el of Array.from(node.querySelectorAll<Element>('*'))) {
741
762
  if (el.shadowRoot) disposeShadowContents(el.shadowRoot)
742
763
  }
743
764
  }
744
765
 
745
766
  function disposeShadowContents(root: ShadowRoot): void {
746
- for (const el of Array.from(root.querySelectorAll<HTMLElement>('[x-data]'))) {
767
+ for (const el of Array.from(root.querySelectorAll<Element>('[x-data]'))) {
747
768
  disposeElement(el)
748
769
  }
749
- for (const el of Array.from(root.querySelectorAll<HTMLElement>('*'))) {
770
+ for (const el of Array.from(root.querySelectorAll<Element>('*'))) {
750
771
  if (el.shadowRoot) disposeShadowContents(el.shadowRoot)
751
772
  }
752
773
  }
753
774
 
754
- function disposeElement(el: HTMLElement): void {
775
+ function disposeElement(el: Element): void {
755
776
  const m = mounted.get(el)
756
777
  if (!m) return
757
778
  for (const d of m.disposers.splice(0)) {
@@ -766,15 +787,19 @@ function disposeElement(el: HTMLElement): void {
766
787
 
767
788
  const BOOL_PROPS = new Set(['disabled', 'checked', 'hidden', 'readonly', 'required', 'selected'])
768
789
 
769
- /** Apply a bound value to a DOM attr/property. class → className; boolean props →
770
- * property (when present) + attribute presence; value property; else attribute. */
771
- export function setBound(el: HTMLElement, attr: string, value: unknown): void {
790
+ /** Apply a bound value to a DOM attr/property. class → HTML className or a
791
+ * namespace-safe attribute; boolean/value properties are used only when present. */
792
+ export function setBound(el: Element, attr: string, value: unknown): void {
772
793
  if (attr === 'class') {
773
- el.className = value == null ? '' : String(value)
794
+ if (el instanceof HTMLElement) el.className = value == null ? '' : String(value)
795
+ else if (value == null) el.removeAttribute('class')
796
+ else el.setAttribute('class', String(value))
774
797
  return
775
798
  }
776
799
  if (attr === 'value') {
777
- ;(el as unknown as { value: unknown }).value = value == null ? '' : value
800
+ if ('value' in el) (el as unknown as { value: unknown }).value = value == null ? '' : value
801
+ else if (value == null) el.removeAttribute(attr)
802
+ else el.setAttribute(attr, String(value))
778
803
  return
779
804
  }
780
805
  if (BOOL_PROPS.has(attr)) {
@@ -1,3 +1,4 @@
1
+ export declare function formatCompilerWarning(warning: string): string;
1
2
  /** Gather transitive component sources starting from a page source file.
2
3
  *
3
4
  * BFS/DFS over local imports reachable from `pageSourcePath`:
@@ -6,17 +6,17 @@ export type Instance = Record<string, unknown>;
6
6
  * for side-effects on signal change (sync localStorage, the DOM
7
7
  * outside the component, timers). Returns the disposer too.
8
8
  * - `onCleanup(fn)` — register a one-shot teardown for unmount (e.g. removeEventListener). */
9
- export interface BehaviorCtx {
10
- el: HTMLElement;
9
+ export interface BehaviorCtx<Host extends Element = HTMLElement> {
10
+ el: Host;
11
11
  props: unknown;
12
12
  effect: (fn: () => void | (() => void)) => () => void;
13
13
  onCleanup: (fn: () => void) => void;
14
14
  }
15
- export type Behavior = (ctx: BehaviorCtx) => Instance;
15
+ export type Behavior<Host extends Element = HTMLElement> = (ctx: BehaviorCtx<Host>) => Instance;
16
16
  /** Register a component behavior under `name`. Called by `<name>.directive.js` chunks
17
17
  * via the global handle below (they do NOT import this module — keeps each chunk to
18
18
  * just its behavior, with the runtime shared as the single `_directives.js` copy). */
19
- export declare function register(name: string, behavior: Behavior): void;
19
+ export declare function register<Host extends Element = HTMLElement>(name: string, behavior: Behavior<Host>): void;
20
20
  /** Scan `root` (default: document) for [x-data], mount each, and (once) attach a
21
21
  * MutationObserver for dynamic mount/dispose. Idempotent. NOTE: `root` scopes the
22
22
  * INITIAL scan only; the observer always watches the global `document.body` (one
@@ -39,9 +39,9 @@ export declare function parseFor(raw: string): ForExpr | null;
39
39
  * for multi-hop paths). The LEAF is never called: `isSignal(leaf)` → `.set(value)`,
40
40
  * else warn once and skip. */
41
41
  export declare function writePath(scope: Instance, path: string, value: unknown): void;
42
- /** Apply a bound value to a DOM attr/property. class → className; boolean props →
43
- * property (when present) + attribute presence; value property; else attribute. */
44
- export declare function setBound(el: HTMLElement, attr: string, value: unknown): void;
42
+ /** Apply a bound value to a DOM attr/property. class → HTML className or a
43
+ * namespace-safe attribute; boolean/value properties are used only when present. */
44
+ export declare function setBound(el: Element, attr: string, value: unknown): void;
45
45
  /** Walk a dotted member path against `scope`, unwrapping a signal/computed at EVERY
46
46
  * hop (so an intermediate item-signal is tracked by `effect`); at the LEAF also call
47
47
  * a plain function to obtain its value (this read is what `effect` tracks). */