sibujs 1.4.0 → 2.0.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.
Files changed (190) hide show
  1. package/README.md +105 -119
  2. package/dist/browser.cjs +288 -80
  3. package/dist/browser.d.cts +19 -9
  4. package/dist/browser.d.ts +19 -9
  5. package/dist/browser.js +6 -6
  6. package/dist/build.cjs +1019 -313
  7. package/dist/build.d.cts +1 -1
  8. package/dist/build.d.ts +1 -1
  9. package/dist/build.js +15 -13
  10. package/dist/cdn.global.js +17 -16
  11. package/dist/chunk-2RA7SHDA.js +65 -0
  12. package/dist/chunk-2UPRY23K.js +80 -0
  13. package/dist/chunk-3JHCYHWN.js +125 -0
  14. package/dist/{chunk-ZWKZCBO6.js → chunk-3LR7GLWQ.js} +154 -33
  15. package/dist/{chunk-3AIRKM3B.js → chunk-3NSGB5JN.js} +115 -34
  16. package/dist/{chunk-3ARAQO7B.js → chunk-52YJLLRO.js} +29 -6
  17. package/dist/chunk-54EDRCEF.js +93 -0
  18. package/dist/chunk-7JDB7I65.js +1327 -0
  19. package/dist/{chunk-WZSPOOER.js → chunk-CC65Y57T.js} +8 -5
  20. package/dist/{chunk-23VV7YD3.js → chunk-DFPFITST.js} +25 -30
  21. package/dist/{chunk-WR5D4EGH.js → chunk-GTBNNBJ6.js} +14 -2
  22. package/dist/chunk-HB24TBAF.js +121 -0
  23. package/dist/{chunk-CZUGLNJS.js → chunk-ITX6OO3F.js} +3 -3
  24. package/dist/{chunk-JAKHTMQU.js → chunk-JA6667UN.js} +206 -46
  25. package/dist/{chunk-77L6NL3X.js → chunk-JXMMDLBY.js} +306 -183
  26. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  27. package/dist/{chunk-F3FA4F32.js → chunk-KLRMB5ZS.js} +135 -79
  28. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  29. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  30. package/dist/{chunk-TSOKIX5Z.js → chunk-MIUAXB7K.js} +126 -74
  31. package/dist/{chunk-QWZG56ET.js → chunk-ND2664SF.js} +558 -190
  32. package/dist/{chunk-JCI5M6U6.js → chunk-O2MNQFLP.js} +261 -79
  33. package/dist/{chunk-EWFVA3TJ.js → chunk-R73P76YZ.js} +1 -1
  34. package/dist/{chunk-2BYQDGN3.js → chunk-SAHNHTFC.js} +234 -63
  35. package/dist/chunk-UCS6AMJ7.js +79 -0
  36. package/dist/{chunk-ZD6OAMTH.js → chunk-VLPPXTYG.js} +90 -35
  37. package/dist/{chunk-OUZZEE4S.js → chunk-WOMYAHHI.js} +17 -11
  38. package/dist/{contracts-xo5ckdRP.d.cts → contracts-ey_Qh8ef.d.cts} +7 -8
  39. package/dist/{contracts-xo5ckdRP.d.ts → contracts-ey_Qh8ef.d.ts} +7 -8
  40. package/dist/{customElement-D2DJp_xn.d.cts → customElement-CPfIrbvg.d.cts} +18 -9
  41. package/dist/{customElement-D2DJp_xn.d.ts → customElement-CPfIrbvg.d.ts} +18 -9
  42. package/dist/data.cjs +452 -100
  43. package/dist/data.d.cts +20 -2
  44. package/dist/data.d.ts +20 -2
  45. package/dist/data.js +11 -9
  46. package/dist/devtools.cjs +535 -247
  47. package/dist/devtools.d.cts +1 -1
  48. package/dist/devtools.d.ts +1 -1
  49. package/dist/devtools.js +34 -30
  50. package/dist/ecosystem.cjs +499 -143
  51. package/dist/ecosystem.d.cts +13 -11
  52. package/dist/ecosystem.d.ts +13 -11
  53. package/dist/ecosystem.js +12 -11
  54. package/dist/extras.cjs +3639 -1629
  55. package/dist/extras.d.cts +11 -11
  56. package/dist/extras.d.ts +11 -11
  57. package/dist/extras.js +58 -45
  58. package/dist/index.cjs +1023 -313
  59. package/dist/index.d.cts +128 -55
  60. package/dist/index.d.ts +128 -55
  61. package/dist/index.js +28 -16
  62. package/dist/{introspect-BumjnBKr.d.cts → introspect-BWNjNw64.d.cts} +22 -2
  63. package/dist/{introspect-CZrlcaYy.d.ts → introspect-cY2pg9pW.d.ts} +22 -2
  64. package/dist/motion.cjs +90 -36
  65. package/dist/motion.d.cts +1 -1
  66. package/dist/motion.d.ts +1 -1
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +414 -81
  69. package/dist/patterns.d.cts +53 -20
  70. package/dist/patterns.d.ts +53 -20
  71. package/dist/patterns.js +7 -7
  72. package/dist/performance.cjs +364 -108
  73. package/dist/performance.d.cts +29 -17
  74. package/dist/performance.d.ts +29 -17
  75. package/dist/performance.js +13 -6
  76. package/dist/plugin-D30wlGW5.d.cts +71 -0
  77. package/dist/plugin-D30wlGW5.d.ts +71 -0
  78. package/dist/plugins.cjs +652 -271
  79. package/dist/plugins.d.cts +13 -6
  80. package/dist/plugins.d.ts +13 -6
  81. package/dist/plugins.js +116 -50
  82. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  83. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  84. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  85. package/dist/ssr.cjs +648 -219
  86. package/dist/ssr.d.cts +27 -7
  87. package/dist/ssr.d.ts +27 -7
  88. package/dist/ssr.js +12 -11
  89. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.cts} +9 -1
  90. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.ts} +9 -1
  91. package/dist/testing.cjs +252 -63
  92. package/dist/testing.d.cts +17 -4
  93. package/dist/testing.d.ts +17 -4
  94. package/dist/testing.js +100 -44
  95. package/dist/ui.cjs +576 -168
  96. package/dist/ui.d.cts +13 -16
  97. package/dist/ui.d.ts +13 -16
  98. package/dist/ui.js +20 -17
  99. package/dist/widgets.cjs +1001 -93
  100. package/dist/widgets.d.cts +104 -2
  101. package/dist/widgets.d.ts +104 -2
  102. package/dist/widgets.js +9 -7
  103. package/package.json +8 -2
  104. package/dist/chunk-32DY64NT.js +0 -282
  105. package/dist/chunk-3CRQALYP.js +0 -877
  106. package/dist/chunk-4EI4AG32.js +0 -482
  107. package/dist/chunk-4MYMUBRS.js +0 -21
  108. package/dist/chunk-6HLLIF3K.js +0 -398
  109. package/dist/chunk-6LSNVCS2.js +0 -937
  110. package/dist/chunk-6SA3QQES.js +0 -61
  111. package/dist/chunk-7BF6TK55.js +0 -1097
  112. package/dist/chunk-7TQKR4PP.js +0 -294
  113. package/dist/chunk-7V26P53V.js +0 -712
  114. package/dist/chunk-AZ3ISID5.js +0 -298
  115. package/dist/chunk-B7SWRFUT.js +0 -332
  116. package/dist/chunk-BGN5ZMP4.js +0 -26
  117. package/dist/chunk-BTU3TJDS.js +0 -365
  118. package/dist/chunk-BW3WT46K.js +0 -937
  119. package/dist/chunk-C6KFWOFV.js +0 -616
  120. package/dist/chunk-CHF5OHIA.js +0 -61
  121. package/dist/chunk-CHJ27IGK.js +0 -26
  122. package/dist/chunk-CMBFNA7L.js +0 -27
  123. package/dist/chunk-DAHRH4ON.js +0 -331
  124. package/dist/chunk-DKOHBI74.js +0 -924
  125. package/dist/chunk-DTCOOBMX.js +0 -725
  126. package/dist/chunk-EBGIRKQY.js +0 -616
  127. package/dist/chunk-EUZND3CB.js +0 -27
  128. package/dist/chunk-EVCZO745.js +0 -365
  129. package/dist/chunk-FGOEVHY3.js +0 -60
  130. package/dist/chunk-G3BOQPVO.js +0 -365
  131. package/dist/chunk-GCOK2LC3.js +0 -282
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-K5ZUMYVS.js +0 -89
  134. package/dist/chunk-KQPDEVVS.js +0 -398
  135. package/dist/chunk-L6JRBDNS.js +0 -60
  136. package/dist/chunk-LA6KQEDU.js +0 -712
  137. package/dist/chunk-MDVXJWFN.js +0 -304
  138. package/dist/chunk-MEZVEBPN.js +0 -2008
  139. package/dist/chunk-MK4ERFYL.js +0 -2249
  140. package/dist/chunk-MLKGABMK.js +0 -9
  141. package/dist/chunk-MQ5GOYPH.js +0 -2249
  142. package/dist/chunk-N6IZB6KJ.js +0 -567
  143. package/dist/chunk-NEKUBFPT.js +0 -60
  144. package/dist/chunk-NHUC2QWH.js +0 -282
  145. package/dist/chunk-NMRUZALC.js +0 -1097
  146. package/dist/chunk-NYVAC6P5.js +0 -37
  147. package/dist/chunk-OF7UZIVB.js +0 -725
  148. package/dist/chunk-P6W3STU4.js +0 -2249
  149. package/dist/chunk-PBHF5WKN.js +0 -616
  150. package/dist/chunk-PTQJDMRT.js +0 -146
  151. package/dist/chunk-PZEGYCF5.js +0 -61
  152. package/dist/chunk-QBMDLBU2.js +0 -975
  153. package/dist/chunk-RQGQSLQK.js +0 -725
  154. package/dist/chunk-SDLZDHKP.js +0 -107
  155. package/dist/chunk-TNQWPPE6.js +0 -37
  156. package/dist/chunk-UHNL42EF.js +0 -2730
  157. package/dist/chunk-UNXCEF6S.js +0 -21
  158. package/dist/chunk-V2XTI523.js +0 -347
  159. package/dist/chunk-VAU366PN.js +0 -2241
  160. package/dist/chunk-VMVDTCXB.js +0 -712
  161. package/dist/chunk-VRW3FULF.js +0 -725
  162. package/dist/chunk-WADYRCO2.js +0 -304
  163. package/dist/chunk-WILQZRO4.js +0 -282
  164. package/dist/chunk-WUHJISPP.js +0 -298
  165. package/dist/chunk-XYU6TZOW.js +0 -182
  166. package/dist/chunk-Y6GP4QGG.js +0 -276
  167. package/dist/chunk-YECR7UIA.js +0 -347
  168. package/dist/chunk-YUTWTI4B.js +0 -654
  169. package/dist/chunk-Z65KYU7I.js +0 -26
  170. package/dist/chunk-Z6POF5YC.js +0 -975
  171. package/dist/chunk-ZBJP6WFL.js +0 -482
  172. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  173. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  174. package/dist/contracts-DOrhwbke.d.cts +0 -245
  175. package/dist/contracts-DOrhwbke.d.ts +0 -245
  176. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  177. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  178. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  179. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  180. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  181. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  182. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  183. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  184. package/dist/ssr-3RXHP5ES.js +0 -38
  185. package/dist/ssr-6GIMY5MX.js +0 -38
  186. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  187. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  188. package/dist/ssr-WKUPVSSK.js +0 -36
  189. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  190. package/dist/tagFactory-Dl8QCLga.d.ts +0 -23
package/dist/build.cjs CHANGED
@@ -1134,6 +1134,7 @@ __export(index_exports, {
1134
1134
  footer: () => footer,
1135
1135
  form: () => form,
1136
1136
  g: () => g,
1137
+ getSSRStore: () => getSSRStore,
1137
1138
  getSlot: () => getSlot,
1138
1139
  h1: () => h1,
1139
1140
  h2: () => h2,
@@ -1206,6 +1207,7 @@ __export(index_exports, {
1206
1207
  rp: () => rp,
1207
1208
  rt: () => rt,
1208
1209
  ruby: () => ruby,
1210
+ runInSSRContext: () => runInSSRContext,
1209
1211
  s: () => s,
1210
1212
  samp: () => samp,
1211
1213
  script: () => script,
@@ -1231,6 +1233,7 @@ __export(index_exports, {
1231
1233
  symbol: () => symbol,
1232
1234
  table: () => table,
1233
1235
  tagFactory: () => tagFactory,
1236
+ takePendingError: () => takePendingError,
1234
1237
  tbody: () => tbody,
1235
1238
  td: () => td,
1236
1239
  template: () => template,
@@ -1245,6 +1248,7 @@ __export(index_exports, {
1245
1248
  track: () => track2,
1246
1249
  transition: () => transition,
1247
1250
  trapFocus: () => trapFocus,
1251
+ trustHTML: () => trustHTML,
1248
1252
  tspan: () => tspan,
1249
1253
  u: () => u,
1250
1254
  ul: () => ul,
@@ -1266,33 +1270,80 @@ function isDev() {
1266
1270
  var _isDev = isDev();
1267
1271
  function devAssert(condition, message) {
1268
1272
  if (_isDev && !condition) {
1269
- throw new Error(`[Sibu] ${message}`);
1273
+ throw new Error(`[SibuJS] ${message}`);
1270
1274
  }
1271
1275
  }
1272
1276
  function devWarn(message) {
1273
1277
  if (_isDev) {
1274
- console.warn(`[Sibu] ${message}`);
1278
+ console.warn(`[SibuJS] ${message}`);
1275
1279
  }
1276
1280
  }
1277
1281
 
1278
1282
  // src/utils/sanitize.ts
1283
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1279
1284
  function sanitizeUrl(url) {
1280
1285
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1281
1286
  if (!trimmed) return "";
1282
1287
  const lower = trimmed.toLowerCase();
1283
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1284
- return "";
1288
+ let schemeEnd = -1;
1289
+ for (let i2 = 0; i2 < lower.length; i2++) {
1290
+ const ch = lower.charCodeAt(i2);
1291
+ if (ch === 58) {
1292
+ schemeEnd = i2;
1293
+ break;
1294
+ }
1295
+ if (ch === 47 || ch === 63 || ch === 35) break;
1285
1296
  }
1297
+ if (schemeEnd === -1) return trimmed;
1298
+ const scheme = lower.slice(0, schemeEnd + 1);
1299
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1300
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1286
1301
  return trimmed;
1287
1302
  }
1303
+ function sanitizeSrcset(value) {
1304
+ const parts = value.split(",");
1305
+ const out = [];
1306
+ for (let i2 = 0; i2 < parts.length; i2++) {
1307
+ const part = parts[i2].trim();
1308
+ if (!part) continue;
1309
+ const m = part.match(/^(\S+)(\s+.+)?$/);
1310
+ if (!m) continue;
1311
+ const safe = sanitizeUrl(m[1]);
1312
+ if (!safe) continue;
1313
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
1314
+ }
1315
+ return out.join(", ");
1316
+ }
1288
1317
  function sanitizeCSSValue(value) {
1289
- const lower = value.toLowerCase().replace(/\s+/g, "");
1290
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
1318
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
1319
+ const code2 = Number.parseInt(hex, 16);
1320
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
1321
+ try {
1322
+ return String.fromCodePoint(code2);
1323
+ } catch {
1324
+ return "";
1325
+ }
1326
+ });
1327
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
1328
+ if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("vbscript:") || lower.includes("-moz-binding") || lower.includes("behavior:") || lower.includes("@import") || lower.includes("image-set(") || lower.includes("filter:progid")) {
1291
1329
  return "";
1292
1330
  }
1293
1331
  return value;
1294
1332
  }
1295
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
1333
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1334
+ "href",
1335
+ "xlink:href",
1336
+ "src",
1337
+ "action",
1338
+ "formaction",
1339
+ "formtarget",
1340
+ "cite",
1341
+ "poster",
1342
+ "background",
1343
+ "srcset",
1344
+ "ping",
1345
+ "data"
1346
+ ]);
1296
1347
  function isUrlAttribute(attr) {
1297
1348
  return URL_ATTRIBUTES.has(attr);
1298
1349
  }
@@ -1303,11 +1354,11 @@ var subscriberStack = new Array(32);
1303
1354
  var stackCapacity = 32;
1304
1355
  var stackTop = -1;
1305
1356
  var currentSubscriber = null;
1306
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
1307
1357
  var SUBS = "__s";
1308
1358
  var notifyDepth = 0;
1309
1359
  var pendingQueue = [];
1310
1360
  var pendingSet = /* @__PURE__ */ new Set();
1361
+ var propagateStack = [];
1311
1362
  function safeInvoke(sub2) {
1312
1363
  try {
1313
1364
  sub2();
@@ -1317,6 +1368,15 @@ function safeInvoke(sub2) {
1317
1368
  }
1318
1369
  var suspendDepth = 0;
1319
1370
  var trackingSuspended = false;
1371
+ function retrack(effectFn, subscriber) {
1372
+ const prev = currentSubscriber;
1373
+ currentSubscriber = subscriber;
1374
+ try {
1375
+ effectFn();
1376
+ } finally {
1377
+ currentSubscriber = prev;
1378
+ }
1379
+ }
1320
1380
  function track(effectFn, subscriber) {
1321
1381
  if (!subscriber) subscriber = effectFn;
1322
1382
  cleanup(subscriber);
@@ -1384,7 +1444,6 @@ function recordDependency(signal2) {
1384
1444
  let subs = signal2[SUBS];
1385
1445
  if (!subs) {
1386
1446
  subs = /* @__PURE__ */ new Set();
1387
- signalSubscribers.set(signal2, subs);
1388
1447
  signal2[SUBS] = subs;
1389
1448
  }
1390
1449
  subs.add(currentSubscriber);
@@ -1406,57 +1465,71 @@ function queueSignalNotification(signal2) {
1406
1465
  }
1407
1466
  }
1408
1467
  }
1468
+ var maxDrainIterations = 1e5;
1409
1469
  function drainNotificationQueue() {
1410
1470
  if (notifyDepth > 0) return;
1411
1471
  notifyDepth++;
1412
1472
  try {
1413
1473
  let i2 = 0;
1414
1474
  while (i2 < pendingQueue.length) {
1475
+ if (i2 >= maxDrainIterations) {
1476
+ if (typeof console !== "undefined") {
1477
+ console.error(
1478
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1479
+ );
1480
+ }
1481
+ break;
1482
+ }
1415
1483
  safeInvoke(pendingQueue[i2]);
1416
1484
  i2++;
1417
1485
  }
1418
1486
  } finally {
1419
- pendingQueue.length = 0;
1420
- pendingSet.clear();
1421
1487
  notifyDepth--;
1488
+ if (notifyDepth === 0) {
1489
+ pendingQueue.length = 0;
1490
+ pendingSet.clear();
1491
+ }
1422
1492
  }
1423
1493
  }
1424
1494
  function propagateDirty(sub2) {
1425
1495
  sub2();
1426
- let sig = sub2._sig;
1427
- while (sig) {
1496
+ const rootSig = sub2._sig;
1497
+ if (!rootSig) return;
1498
+ const stack = propagateStack;
1499
+ const baseLen = stack.length;
1500
+ stack.push(rootSig);
1501
+ while (stack.length > baseLen) {
1502
+ const sig = stack.pop();
1428
1503
  const first = sig.__f;
1429
1504
  if (first) {
1430
1505
  if (first._c) {
1431
1506
  const nSig = first._sig;
1432
- nSig._d = true;
1433
- sig = nSig;
1434
- continue;
1435
- }
1436
- if (!pendingSet.has(first)) {
1507
+ if (!nSig._d) {
1508
+ nSig._d = true;
1509
+ stack.push(nSig);
1510
+ }
1511
+ } else if (!pendingSet.has(first)) {
1437
1512
  pendingSet.add(first);
1438
1513
  pendingQueue.push(first);
1439
1514
  }
1440
- break;
1515
+ continue;
1441
1516
  }
1442
1517
  const subs = sig[SUBS];
1443
- if (!subs) break;
1444
- let nextSig;
1518
+ if (!subs) continue;
1445
1519
  for (const s2 of subs) {
1446
1520
  if (s2._c) {
1447
- s2();
1448
1521
  const nSig = s2._sig;
1449
- if (nSig && !nextSig) {
1450
- nextSig = nSig;
1451
- } else if (nSig) {
1452
- propagateDirty(s2);
1522
+ if (nSig && !nSig._d) {
1523
+ nSig._d = true;
1524
+ stack.push(nSig);
1525
+ } else if (!nSig) {
1526
+ s2();
1453
1527
  }
1454
1528
  } else if (!pendingSet.has(s2)) {
1455
1529
  pendingSet.add(s2);
1456
1530
  pendingQueue.push(s2);
1457
1531
  }
1458
1532
  }
1459
- sig = nextSig;
1460
1533
  }
1461
1534
  }
1462
1535
  function notifySubscribers(signal2) {
@@ -1480,13 +1553,23 @@ function notifySubscribers(signal2) {
1480
1553
  }
1481
1554
  let i2 = 0;
1482
1555
  while (i2 < pendingQueue.length) {
1556
+ if (i2 >= maxDrainIterations) {
1557
+ if (typeof console !== "undefined") {
1558
+ console.error(
1559
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1560
+ );
1561
+ }
1562
+ break;
1563
+ }
1483
1564
  safeInvoke(pendingQueue[i2]);
1484
1565
  i2++;
1485
1566
  }
1486
1567
  } finally {
1487
- pendingQueue.length = 0;
1488
- pendingSet.clear();
1489
1568
  notifyDepth--;
1569
+ if (notifyDepth === 0) {
1570
+ pendingQueue.length = 0;
1571
+ pendingSet.clear();
1572
+ }
1490
1573
  }
1491
1574
  return;
1492
1575
  }
@@ -1506,30 +1589,48 @@ function notifySubscribers(signal2) {
1506
1589
  notifyDepth++;
1507
1590
  try {
1508
1591
  let directCount = 0;
1592
+ let hasComputedSub = false;
1509
1593
  for (const sub2 of subs) {
1594
+ if (sub2._c) hasComputedSub = true;
1510
1595
  pendingQueue[directCount++] = sub2;
1511
1596
  }
1512
- for (let i3 = 0; i3 < directCount; i3++) {
1513
- if (pendingQueue[i3]._c) {
1514
- propagateDirty(pendingQueue[i3]);
1597
+ if (!hasComputedSub) {
1598
+ for (let i3 = 0; i3 < directCount; i3++) {
1599
+ safeInvoke(pendingQueue[i3]);
1515
1600
  }
1516
- }
1517
- for (let i3 = 0; i3 < directCount; i3++) {
1518
- if (!pendingQueue[i3]._c) {
1519
- if (!pendingSet.has(pendingQueue[i3])) {
1520
- safeInvoke(pendingQueue[i3]);
1601
+ } else {
1602
+ for (let i3 = 0; i3 < directCount; i3++) {
1603
+ if (pendingQueue[i3]._c) {
1604
+ propagateDirty(pendingQueue[i3]);
1605
+ }
1606
+ }
1607
+ for (let i3 = 0; i3 < directCount; i3++) {
1608
+ const sub2 = pendingQueue[i3];
1609
+ if (!sub2._c && !pendingSet.has(sub2)) {
1610
+ pendingSet.add(sub2);
1611
+ safeInvoke(sub2);
1521
1612
  }
1522
1613
  }
1523
1614
  }
1524
1615
  let i2 = directCount;
1525
1616
  while (i2 < pendingQueue.length) {
1617
+ if (i2 - directCount >= maxDrainIterations) {
1618
+ if (typeof console !== "undefined") {
1619
+ console.error(
1620
+ `[SibuJS] Notification queue exceeded ${maxDrainIterations} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1621
+ );
1622
+ }
1623
+ break;
1624
+ }
1526
1625
  safeInvoke(pendingQueue[i2]);
1527
1626
  i2++;
1528
1627
  }
1529
1628
  } finally {
1530
- pendingQueue.length = 0;
1531
- pendingSet.clear();
1532
1629
  notifyDepth--;
1630
+ if (notifyDepth === 0) {
1631
+ pendingQueue.length = 0;
1632
+ pendingSet.clear();
1633
+ }
1533
1634
  }
1534
1635
  }
1535
1636
  function cleanup(subscriber) {
@@ -1540,7 +1641,9 @@ function cleanup(subscriber) {
1540
1641
  if (subs) {
1541
1642
  subs.delete(subscriber);
1542
1643
  if (singleDep.__f === subscriber) {
1543
- singleDep.__f = void 0;
1644
+ singleDep.__f = subs.size === 1 ? subs.values().next().value : void 0;
1645
+ } else if (subs.size === 1 && singleDep.__f === void 0) {
1646
+ singleDep.__f = subs.values().next().value;
1544
1647
  }
1545
1648
  }
1546
1649
  sub2._dep = void 0;
@@ -1553,7 +1656,9 @@ function cleanup(subscriber) {
1553
1656
  if (subs) {
1554
1657
  subs.delete(subscriber);
1555
1658
  if (signal2.__f === subscriber) {
1556
- signal2.__f = void 0;
1659
+ signal2.__f = subs.size === 1 ? subs.values().next().value : void 0;
1660
+ } else if (subs.size === 1 && signal2.__f === void 0) {
1661
+ signal2.__f = subs.values().next().value;
1557
1662
  }
1558
1663
  }
1559
1664
  }
@@ -1562,6 +1667,9 @@ function cleanup(subscriber) {
1562
1667
 
1563
1668
  // src/reactivity/bindAttribute.ts
1564
1669
  var _isDev3 = isDev();
1670
+ function setProp(el, key, val) {
1671
+ el[key] = val;
1672
+ }
1565
1673
  function isEventHandlerAttr(name) {
1566
1674
  if (name.length < 3) return false;
1567
1675
  const lower = name.toLowerCase();
@@ -1587,7 +1695,7 @@ function bindAttribute(el, attr, getter) {
1587
1695
  }
1588
1696
  if (typeof value === "boolean") {
1589
1697
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1590
- el[attr] = value;
1698
+ setProp(el, attr, value);
1591
1699
  } else if (value) {
1592
1700
  el.setAttribute(attr, "");
1593
1701
  } else {
@@ -1597,7 +1705,7 @@ function bindAttribute(el, attr, getter) {
1597
1705
  }
1598
1706
  const str = String(value);
1599
1707
  if ((attr === "value" || attr === "checked") && attr in el) {
1600
- el[attr] = attr === "checked" ? Boolean(value) : str;
1708
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1601
1709
  } else {
1602
1710
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1603
1711
  }
@@ -1628,7 +1736,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
1628
1736
  }
1629
1737
  const str = String(value);
1630
1738
  if ((name === "value" || name === "checked") && name in el) {
1631
- el[name] = name === "checked" ? Boolean(value) : str;
1739
+ setProp(el, name, name === "checked" ? Boolean(value) : str);
1632
1740
  } else {
1633
1741
  el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
1634
1742
  }
@@ -1671,24 +1779,29 @@ function bindChildNode(placeholder, getter) {
1671
1779
  let newNodes;
1672
1780
  if (Array.isArray(result)) {
1673
1781
  newNodes = [];
1782
+ const seen = /* @__PURE__ */ new Set();
1674
1783
  for (let i2 = 0; i2 < result.length; i2++) {
1675
1784
  const item = result[i2];
1676
1785
  if (item == null || typeof item === "boolean") continue;
1677
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1786
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1787
+ if (seen.has(node)) {
1788
+ if (_isDev4)
1789
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1790
+ continue;
1791
+ }
1792
+ seen.add(node);
1793
+ newNodes.push(node);
1678
1794
  }
1679
1795
  } else {
1680
1796
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1681
1797
  newNodes = [node];
1682
1798
  }
1683
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1684
- if (reused) {
1799
+ let reused;
1800
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1801
+ const lastSet = new Set(lastNodes);
1802
+ reused = /* @__PURE__ */ new Set();
1685
1803
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1686
- for (let j = 0; j < lastNodes.length; j++) {
1687
- if (newNodes[i2] === lastNodes[j]) {
1688
- reused.add(newNodes[i2]);
1689
- break;
1690
- }
1691
- }
1804
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1692
1805
  }
1693
1806
  }
1694
1807
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1731,7 +1844,7 @@ function dispose(node) {
1731
1844
  while (stack.length > 0) {
1732
1845
  const current = stack.pop();
1733
1846
  order.push(current);
1734
- const children = current.childNodes;
1847
+ const children = Array.from(current.childNodes);
1735
1848
  for (let i2 = 0; i2 < children.length; i2++) {
1736
1849
  stack.push(children[i2]);
1737
1850
  }
@@ -1740,9 +1853,35 @@ function dispose(node) {
1740
1853
  const current = order[i2];
1741
1854
  const disposers = elementDisposers.get(current);
1742
1855
  if (disposers) {
1743
- if (_isDev5) activeBindingCount -= disposers.length;
1744
- for (const d of disposers) d();
1856
+ const snapshot = disposers.slice();
1745
1857
  elementDisposers.delete(current);
1858
+ if (_isDev5) activeBindingCount -= snapshot.length;
1859
+ for (const d of snapshot) {
1860
+ try {
1861
+ d();
1862
+ } catch (err) {
1863
+ if (_isDev5 && typeof console !== "undefined") {
1864
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1865
+ }
1866
+ }
1867
+ }
1868
+ let extraPasses = 0;
1869
+ while (extraPasses++ < 8) {
1870
+ const added = elementDisposers.get(current);
1871
+ if (!added || added.length === 0) break;
1872
+ const moreSnapshot = added.slice();
1873
+ elementDisposers.delete(current);
1874
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
1875
+ for (const d of moreSnapshot) {
1876
+ try {
1877
+ d();
1878
+ } catch (err) {
1879
+ if (_isDev5 && typeof console !== "undefined") {
1880
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1881
+ }
1882
+ }
1883
+ }
1884
+ }
1746
1885
  }
1747
1886
  }
1748
1887
  }
@@ -1758,6 +1897,30 @@ function checkLeaks(warnThreshold = 0) {
1758
1897
 
1759
1898
  // src/core/rendering/tagFactory.ts
1760
1899
  var SVG_NS = "http://www.w3.org/2000/svg";
1900
+ var _isDev6 = isDev();
1901
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1902
+ function validateTagName(tag) {
1903
+ const lower = tag.toLowerCase();
1904
+ if (BLOCKED_TAGS.has(lower)) {
1905
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1906
+ }
1907
+ }
1908
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1909
+ "config",
1910
+ "location",
1911
+ "history",
1912
+ "document",
1913
+ "window",
1914
+ "navigator",
1915
+ "name",
1916
+ "top",
1917
+ "parent",
1918
+ "self",
1919
+ "frames"
1920
+ ]);
1921
+ function setProp2(el, key, val) {
1922
+ el[key] = val;
1923
+ }
1761
1924
  var kebabCache = /* @__PURE__ */ new Map();
1762
1925
  function toKebab(prop) {
1763
1926
  let cached = kebabCache.get(prop);
@@ -1882,79 +2045,103 @@ function appendChildren(el, nodes) {
1882
2045
  }
1883
2046
  }
1884
2047
  }
1885
- var tagFactory = (tag, ns) => (first, second) => {
1886
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1887
- if (first === void 0) return el;
1888
- if (typeof first === "string") {
1889
- if (second !== void 0) {
1890
- el.setAttribute("class", first);
1891
- appendChildren(el, second);
2048
+ var tagFactory = (tag, ns) => {
2049
+ return (first, second) => {
2050
+ validateTagName(tag);
2051
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
2052
+ if (first === void 0) return el;
2053
+ if (typeof first === "string") {
2054
+ if (second !== void 0) {
2055
+ el.setAttribute("class", first);
2056
+ appendChildren(el, second);
2057
+ return el;
2058
+ }
2059
+ el.textContent = first;
1892
2060
  return el;
1893
2061
  }
1894
- el.textContent = first;
1895
- return el;
1896
- }
1897
- if (typeof first === "number") {
1898
- el.textContent = String(first);
1899
- return el;
1900
- }
1901
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1902
- appendChildren(el, first);
1903
- return el;
1904
- }
1905
- const props = first;
1906
- const pClass = props.class;
1907
- if (pClass != null) applyClass(el, pClass);
1908
- const pId = props.id;
1909
- if (pId != null) el.id = pId;
1910
- const pNodes = second !== void 0 ? second : props.nodes;
1911
- if (pNodes != null) appendChildren(el, pNodes);
1912
- const pOn = props.on;
1913
- if (pOn) {
1914
- for (const ev in pOn) {
1915
- el.addEventListener(ev, pOn[ev]);
1916
- }
1917
- }
1918
- const pStyle = props.style;
1919
- if (pStyle != null) applyStyle(el, pStyle);
1920
- const pRef = props.ref;
1921
- if (pRef) pRef.current = el;
1922
- for (const key in props) {
1923
- switch (key) {
1924
- case "class":
1925
- case "id":
1926
- case "nodes":
1927
- case "on":
1928
- case "style":
1929
- case "ref":
1930
- case "onElement":
1931
- continue;
1932
- // already handled above / below
1933
- default: {
1934
- const value = props[key];
1935
- if (value == null) continue;
1936
- if (key[0] === "o" && key[1] === "n") continue;
1937
- if (typeof value === "function") {
1938
- registerDisposer(el, bindAttribute(el, key, value));
1939
- } else if (typeof value === "boolean") {
1940
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1941
- el[key] = value;
1942
- } else if (value) {
1943
- el.setAttribute(key, "");
2062
+ if (typeof first === "number") {
2063
+ el.textContent = String(first);
2064
+ return el;
2065
+ }
2066
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
2067
+ appendChildren(el, first);
2068
+ return el;
2069
+ }
2070
+ const props = first;
2071
+ const pClass = props.class;
2072
+ if (pClass != null) applyClass(el, pClass);
2073
+ const pId = props.id;
2074
+ if (pId != null) {
2075
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2076
+ devWarn(
2077
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
2078
+ );
2079
+ }
2080
+ el.id = pId;
2081
+ }
2082
+ const pNodes = second !== void 0 ? second : props.nodes;
2083
+ if (pNodes != null) appendChildren(el, pNodes);
2084
+ const pOn = props.on;
2085
+ if (pOn) {
2086
+ for (const ev in pOn) {
2087
+ const handler = pOn[ev];
2088
+ if (typeof handler === "function") {
2089
+ el.addEventListener(ev, handler);
2090
+ } else if (_isDev6) {
2091
+ devWarn(
2092
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
2093
+ );
2094
+ }
2095
+ }
2096
+ }
2097
+ const pStyle = props.style;
2098
+ if (pStyle != null) applyStyle(el, pStyle);
2099
+ const pRef = props.ref;
2100
+ if (pRef) pRef.current = el;
2101
+ for (const key in props) {
2102
+ switch (key) {
2103
+ case "class":
2104
+ case "id":
2105
+ case "nodes":
2106
+ case "on":
2107
+ case "style":
2108
+ case "ref":
2109
+ case "onElement":
2110
+ continue;
2111
+ // already handled above / below
2112
+ default: {
2113
+ const value = props[key];
2114
+ if (value == null) continue;
2115
+ const lkey = key.toLowerCase();
2116
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
2117
+ if (typeof value === "function") {
2118
+ registerDisposer(el, bindAttribute(el, key, value));
2119
+ } else if (typeof value === "boolean") {
2120
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
2121
+ setProp2(el, key, value);
2122
+ } else if (value) {
2123
+ el.setAttribute(key, "");
2124
+ } else {
2125
+ el.removeAttribute(key);
2126
+ }
1944
2127
  } else {
1945
- el.removeAttribute(key);
2128
+ const str = String(value);
2129
+ if (lkey === "srcset") {
2130
+ el.setAttribute(key, sanitizeSrcset(str));
2131
+ } else if (isUrlAttribute(lkey)) {
2132
+ el.setAttribute(key, sanitizeUrl(str));
2133
+ } else {
2134
+ el.setAttribute(key, str);
2135
+ }
1946
2136
  }
1947
- } else {
1948
- const str = String(value);
1949
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1950
2137
  }
1951
2138
  }
1952
2139
  }
1953
- }
1954
- if (props.onElement && typeof props.onElement === "function") {
1955
- props.onElement(el);
1956
- }
1957
- return el;
2140
+ if (props.onElement && typeof props.onElement === "function") {
2141
+ props.onElement(el);
2142
+ }
2143
+ return el;
2144
+ };
1958
2145
  };
1959
2146
 
1960
2147
  // src/core/rendering/html.ts
@@ -2097,6 +2284,8 @@ var marquee = tagFactory("marquee");
2097
2284
  var customElement = (tagName) => tagFactory(tagName);
2098
2285
 
2099
2286
  // src/core/rendering/htm.ts
2287
+ var _isDev7 = isDev();
2288
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
2100
2289
  var VOID_ELEMENTS3 = /* @__PURE__ */ new Set([
2101
2290
  "area",
2102
2291
  "base",
@@ -2281,6 +2470,15 @@ function parseTemplate(strings) {
2281
2470
  children.push({ t: 0, el: { tag, svg: SVG_TAGS2.has(tag), attrs, children: [] } });
2282
2471
  } else {
2283
2472
  const inner = parseChildren();
2473
+ if (RAW_TEXT_TAGS.has(tag.toLowerCase())) {
2474
+ for (let i2 = 0; i2 < inner.length; i2++) {
2475
+ if (inner[i2].t === 2) {
2476
+ throw new Error(
2477
+ `html: dynamic \${...} expressions are not allowed inside <${tag}> (raw-text context). Build the content separately and append it as a Node.`
2478
+ );
2479
+ }
2480
+ }
2481
+ }
2284
2482
  if (template2[pos] === "<" && pos + 1 < len && template2[pos + 1] === "/") {
2285
2483
  pos += 2;
2286
2484
  readTagName();
@@ -2307,27 +2505,50 @@ function executeElement(tmpl, values) {
2307
2505
  break;
2308
2506
  case 1: {
2309
2507
  const name = attr.name;
2310
- if (name[0] === "o" && name[1] === "n") break;
2508
+ const lname = name.toLowerCase();
2509
+ if (lname[0] === "o" && lname[1] === "n") break;
2311
2510
  const val = values[attr.idx];
2312
2511
  if (typeof val === "function") {
2313
2512
  registerDisposer(el, bindAttribute(el, name, val));
2314
2513
  } else if (val != null) {
2315
2514
  const str = String(val);
2316
- el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
2515
+ if (lname === "srcset") {
2516
+ el.setAttribute(name, sanitizeSrcset(str));
2517
+ } else if (isUrlAttribute(lname)) {
2518
+ el.setAttribute(name, sanitizeUrl(str));
2519
+ } else {
2520
+ el.setAttribute(name, str);
2521
+ }
2317
2522
  }
2318
2523
  break;
2319
2524
  }
2320
2525
  case 2: {
2321
2526
  let val = attr.statics[0];
2322
2527
  for (let j = 0; j < attr.exprs.length; j++) {
2323
- val += String(values[attr.exprs[j]]) + attr.statics[j + 1];
2528
+ const ev = values[attr.exprs[j]];
2529
+ val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
2530
+ }
2531
+ const lname2 = attr.name.toLowerCase();
2532
+ if (lname2 === "srcset") {
2533
+ el.setAttribute(attr.name, sanitizeSrcset(val));
2534
+ } else if (isUrlAttribute(lname2)) {
2535
+ el.setAttribute(attr.name, sanitizeUrl(val));
2536
+ } else {
2537
+ el.setAttribute(attr.name, val);
2324
2538
  }
2325
- el.setAttribute(attr.name, val);
2326
2539
  break;
2327
2540
  }
2328
- case 3:
2329
- el.addEventListener(attr.name, values[attr.idx]);
2541
+ case 3: {
2542
+ const fn = values[attr.idx];
2543
+ if (typeof fn === "function") {
2544
+ el.addEventListener(attr.name, fn);
2545
+ } else if (_isDev7) {
2546
+ devWarn(
2547
+ `html: on:${attr.name} handler is not a function (got ${typeof fn}). Event listener was not attached.`
2548
+ );
2549
+ }
2330
2550
  break;
2551
+ }
2331
2552
  case 4:
2332
2553
  el.setAttribute(attr.name, "");
2333
2554
  break;
@@ -2421,7 +2642,7 @@ function html2(strings, ...values) {
2421
2642
  function mount(component, container) {
2422
2643
  if (!container) {
2423
2644
  throw new Error(
2424
- "[Sibu] mount: container element not found. Make sure the DOM element exists before calling mount()."
2645
+ "[SibuJS mount] container element not found. Make sure the DOM element exists before calling mount()."
2425
2646
  );
2426
2647
  }
2427
2648
  devAssert(
@@ -2449,7 +2670,7 @@ function mount(component, container) {
2449
2670
  }
2450
2671
 
2451
2672
  // src/core/rendering/each.ts
2452
- var _isDev6 = isDev();
2673
+ var _isDev8 = isDev();
2453
2674
  function resolveNodeChild(child) {
2454
2675
  if (typeof child === "function") {
2455
2676
  return resolveNodeChild(child());
@@ -2544,17 +2765,31 @@ function each(getArray, render, options) {
2544
2765
  node = existing;
2545
2766
  } else {
2546
2767
  const itemKey = key;
2547
- const itemGetter = () => getArray()[keyIndexMap.get(itemKey)];
2768
+ const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
2548
2769
  const indexGetter = () => keyIndexMap.get(itemKey);
2549
2770
  try {
2550
2771
  node = resolveNodeChild(render(itemGetter, indexGetter));
2551
2772
  } catch (err) {
2552
- if (_isDev6) {
2773
+ if (_isDev8) {
2553
2774
  devWarn(
2554
2775
  `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
2555
2776
  );
2556
2777
  }
2557
2778
  node = document.createComment(`each:error:${i2}`);
2779
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2780
+ queueMicrotask(() => {
2781
+ try {
2782
+ const target = anchor.parentNode;
2783
+ if (target?.dispatchEvent) {
2784
+ target.dispatchEvent(
2785
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2786
+ );
2787
+ } else if (_isDev8) {
2788
+ devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
2789
+ }
2790
+ } catch {
2791
+ }
2792
+ });
2558
2793
  }
2559
2794
  }
2560
2795
  workMap.set(key, node);
@@ -2623,7 +2858,8 @@ function each(getArray, render, options) {
2623
2858
  workMap = tmp;
2624
2859
  initialized = true;
2625
2860
  };
2626
- track(update);
2861
+ const untrack = track(update);
2862
+ registerDisposer(anchor, untrack);
2627
2863
  if (!initialized) {
2628
2864
  queueMicrotask(() => {
2629
2865
  if (!initialized && anchor.parentNode) {
@@ -2670,24 +2906,36 @@ function Portal(nodes, target) {
2670
2906
  const anchor = document.createComment("portal");
2671
2907
  const container = target || document.body;
2672
2908
  let portalContent = null;
2909
+ let disposed = false;
2673
2910
  queueMicrotask(() => {
2911
+ if (disposed) return;
2674
2912
  try {
2675
2913
  portalContent = nodes();
2676
2914
  container.appendChild(portalContent);
2677
2915
  } catch (err) {
2678
- console.error("[Portal] Render error:", err);
2916
+ if (typeof console !== "undefined") {
2917
+ console.error("[Portal] Render error:", err);
2918
+ }
2919
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2920
+ queueMicrotask(() => {
2921
+ try {
2922
+ const target2 = anchor.parentNode;
2923
+ if (target2?.dispatchEvent) {
2924
+ target2.dispatchEvent(
2925
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2926
+ );
2927
+ }
2928
+ } catch {
2929
+ }
2930
+ });
2679
2931
  }
2680
2932
  });
2681
- const observer = new MutationObserver(() => {
2682
- if (!anchor.isConnected && portalContent) {
2933
+ registerDisposer(anchor, () => {
2934
+ disposed = true;
2935
+ if (portalContent) {
2936
+ dispose(portalContent);
2683
2937
  portalContent.remove();
2684
2938
  portalContent = null;
2685
- observer.disconnect();
2686
- }
2687
- });
2688
- queueMicrotask(() => {
2689
- if (anchor.parentNode) {
2690
- observer.observe(anchor.parentNode, { childList: true });
2691
2939
  }
2692
2940
  });
2693
2941
  return anchor;
@@ -2723,7 +2971,8 @@ function DynamicComponent(is) {
2723
2971
  }
2724
2972
  container.replaceChildren(el);
2725
2973
  }
2726
- track(render);
2974
+ const untrack = track(render);
2975
+ registerDisposer(container, untrack);
2727
2976
  return container;
2728
2977
  }
2729
2978
 
@@ -2813,11 +3062,16 @@ function KeepAlive(activeKey, cases, options) {
2813
3062
  const anchor = document.createComment("keep-alive");
2814
3063
  const cache2 = /* @__PURE__ */ new Map();
2815
3064
  const lruOrder = [];
2816
- const max = options?.max ?? 0;
3065
+ const max = options?.max ?? 10;
3066
+ if (max === 0 && isDev()) {
3067
+ devWarn("KeepAlive: unbounded cache (max: 0). Cached subtrees will never be evicted \u2014 set `max` to bound memory.");
3068
+ }
2817
3069
  let currentKey;
2818
3070
  let currentNode = null;
2819
3071
  let initialized = false;
3072
+ let disposed = false;
2820
3073
  const update = () => {
3074
+ if (disposed) return;
2821
3075
  const key = activeKey();
2822
3076
  const parent = anchor.parentNode;
2823
3077
  if (!parent) return;
@@ -2857,12 +3111,23 @@ function KeepAlive(activeKey, cases, options) {
2857
3111
  currentNode = node;
2858
3112
  initialized = true;
2859
3113
  };
2860
- track(update);
3114
+ const untrack = track(update);
2861
3115
  if (!initialized) {
2862
3116
  queueMicrotask(() => {
2863
3117
  if (!initialized && anchor.parentNode) update();
2864
3118
  });
2865
3119
  }
3120
+ registerDisposer(anchor, () => {
3121
+ disposed = true;
3122
+ untrack();
3123
+ for (const node of cache2.values()) {
3124
+ dispose(node);
3125
+ if (node.parentNode) node.parentNode.removeChild(node);
3126
+ }
3127
+ cache2.clear();
3128
+ lruOrder.length = 0;
3129
+ currentNode = null;
3130
+ });
2866
3131
  return anchor;
2867
3132
  }
2868
3133
 
@@ -3024,19 +3289,22 @@ function isBatching() {
3024
3289
  return batchDepth > 0;
3025
3290
  }
3026
3291
  function flushBatch() {
3027
- for (const signal2 of pendingSignals) {
3028
- queueSignalNotification(signal2);
3292
+ try {
3293
+ for (const signal2 of pendingSignals) {
3294
+ queueSignalNotification(signal2);
3295
+ }
3296
+ } finally {
3297
+ pendingSignals.clear();
3029
3298
  }
3030
- pendingSignals.clear();
3031
3299
  drainNotificationQueue();
3032
3300
  }
3033
3301
 
3034
3302
  // src/core/signals/signal.ts
3035
3303
  var _g = globalThis;
3036
- var _isDev7 = isDev();
3304
+ var _isDev9 = isDev();
3037
3305
  function signal(initial, options) {
3038
3306
  const state = { value: initial };
3039
- const debugName = _isDev7 ? options?.name : void 0;
3307
+ const debugName = _isDev9 ? options?.name : void 0;
3040
3308
  const equalsFn = options?.equals;
3041
3309
  if (debugName) {
3042
3310
  state.__name = debugName;
@@ -3050,7 +3318,7 @@ function signal(initial, options) {
3050
3318
  function set(next) {
3051
3319
  const newValue = typeof next === "function" ? next(state.value) : next;
3052
3320
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
3053
- if (_isDev7) {
3321
+ if (_isDev9) {
3054
3322
  const oldValue = state.value;
3055
3323
  state.value = newValue;
3056
3324
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -3062,7 +3330,7 @@ function signal(initial, options) {
3062
3330
  notifySubscribers(state);
3063
3331
  }
3064
3332
  }
3065
- if (_isDev7) {
3333
+ if (_isDev9) {
3066
3334
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3067
3335
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
3068
3336
  }
@@ -3070,23 +3338,59 @@ function signal(initial, options) {
3070
3338
  }
3071
3339
 
3072
3340
  // src/core/ssr-context.ts
3073
- var ssrMode = false;
3341
+ var als = null;
3342
+ try {
3343
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
3344
+ const req = Function("return typeof require==='function'?require:null")();
3345
+ if (req) {
3346
+ const mod = req("node:async_hooks");
3347
+ als = new mod.AsyncLocalStorage();
3348
+ }
3349
+ }
3350
+ } catch {
3351
+ als = null;
3352
+ }
3353
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
3354
+ function getSSRStore() {
3355
+ if (als) {
3356
+ const s2 = als.getStore();
3357
+ if (s2) return s2;
3358
+ }
3359
+ return fallbackStore;
3360
+ }
3074
3361
  function isSSR() {
3075
- return ssrMode;
3362
+ return getSSRStore().ssr;
3076
3363
  }
3077
3364
  function enableSSR() {
3078
- ssrMode = true;
3365
+ getSSRStore().ssr = true;
3079
3366
  }
3080
3367
  function disableSSR() {
3081
- ssrMode = false;
3368
+ getSSRStore().ssr = false;
3369
+ }
3370
+ function runInSSRContext(fn) {
3371
+ const store2 = { ssr: true, suspenseIdCounter: 0 };
3372
+ if (als) {
3373
+ return als.run(store2, fn);
3374
+ }
3375
+ const prevSSR = fallbackStore.ssr;
3376
+ const prevCounter = fallbackStore.suspenseIdCounter;
3377
+ fallbackStore.ssr = true;
3378
+ fallbackStore.suspenseIdCounter = 0;
3379
+ try {
3380
+ return fn();
3381
+ } finally {
3382
+ fallbackStore.ssr = prevSSR;
3383
+ fallbackStore.suspenseIdCounter = prevCounter;
3384
+ }
3082
3385
  }
3083
3386
  function withSSR(fn) {
3084
- const wasSSR = ssrMode;
3085
- enableSSR();
3387
+ const store2 = getSSRStore();
3388
+ const wasSSR = store2.ssr;
3389
+ store2.ssr = true;
3086
3390
  try {
3087
3391
  return fn();
3088
3392
  } finally {
3089
- if (!wasSSR) disableSSR();
3393
+ if (!wasSSR) store2.ssr = false;
3090
3394
  }
3091
3395
  }
3092
3396
 
@@ -3113,26 +3417,86 @@ function effect(effectFn, options) {
3113
3417
  if (isSSR()) return () => {
3114
3418
  };
3115
3419
  const onError = options?.onError;
3420
+ let userCleanups = [];
3421
+ const onCleanup2 = (fn) => {
3422
+ userCleanups.push(fn);
3423
+ };
3424
+ const runUserCleanups = () => {
3425
+ if (userCleanups.length === 0) return;
3426
+ const list = userCleanups;
3427
+ userCleanups = [];
3428
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
3429
+ try {
3430
+ list[i2]();
3431
+ } catch (err) {
3432
+ if (typeof console !== "undefined") {
3433
+ console.warn("[SibuJS effect] onCleanup threw:", err);
3434
+ }
3435
+ }
3436
+ }
3437
+ };
3438
+ const invokeBody = () => effectFn(onCleanup2);
3116
3439
  const wrappedFn = onError ? () => {
3117
3440
  try {
3118
- effectFn();
3441
+ invokeBody();
3119
3442
  } catch (err) {
3120
3443
  onError(err);
3121
3444
  }
3122
- } : effectFn;
3445
+ } : invokeBody;
3123
3446
  let cleanupHandle = () => {
3124
3447
  };
3448
+ let running = false;
3125
3449
  const subscriber = () => {
3126
- cleanupHandle();
3127
- cleanupHandle = track(wrappedFn, subscriber);
3450
+ if (running) {
3451
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
3452
+ console.warn(
3453
+ "[SibuJS] effect re-entered itself while running \u2014 the triggering update will be ignored. Wrap mutual writes in `batch()` or split the effect to avoid this."
3454
+ );
3455
+ }
3456
+ return;
3457
+ }
3458
+ running = true;
3459
+ try {
3460
+ runUserCleanups();
3461
+ cleanupHandle();
3462
+ cleanupHandle = track(wrappedFn, subscriber);
3463
+ } finally {
3464
+ running = false;
3465
+ }
3128
3466
  };
3129
- cleanupHandle = track(wrappedFn, subscriber);
3467
+ running = true;
3468
+ try {
3469
+ cleanupHandle = track(wrappedFn, subscriber);
3470
+ } finally {
3471
+ running = false;
3472
+ }
3130
3473
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3131
3474
  if (hook) hook.emit("effect:create", { effectFn });
3475
+ let disposed = false;
3132
3476
  return () => {
3477
+ if (disposed) return;
3478
+ disposed = true;
3133
3479
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3134
- if (h) h.emit("effect:destroy", { effectFn });
3135
- cleanupHandle();
3480
+ if (h) {
3481
+ try {
3482
+ h.emit("effect:destroy", { effectFn });
3483
+ } catch {
3484
+ }
3485
+ }
3486
+ try {
3487
+ runUserCleanups();
3488
+ } catch (err) {
3489
+ if (typeof console !== "undefined") {
3490
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
3491
+ }
3492
+ }
3493
+ try {
3494
+ cleanupHandle();
3495
+ } catch (err) {
3496
+ if (typeof console !== "undefined") {
3497
+ console.warn("[SibuJS effect] dispose threw:", err);
3498
+ }
3499
+ }
3136
3500
  };
3137
3501
  }
3138
3502
 
@@ -3140,6 +3504,7 @@ function effect(effectFn, options) {
3140
3504
  function derived(getter, options) {
3141
3505
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
3142
3506
  const debugName = options?.name;
3507
+ const equals = options?.equals;
3143
3508
  const cs = {};
3144
3509
  cs._d = false;
3145
3510
  cs._g = getter;
@@ -3150,25 +3515,56 @@ function derived(getter, options) {
3150
3515
  markDirty._c = 1;
3151
3516
  markDirty._sig = cs;
3152
3517
  track(() => {
3153
- cs._d = false;
3154
- cs._v = getter();
3518
+ let threw = true;
3519
+ try {
3520
+ cs._v = getter();
3521
+ cs._d = false;
3522
+ threw = false;
3523
+ } finally {
3524
+ if (threw) cs._d = true;
3525
+ }
3155
3526
  }, markDirty);
3156
3527
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3528
+ let evaluating = false;
3157
3529
  function computedGetter() {
3530
+ if (evaluating) {
3531
+ throw new Error(
3532
+ `[SibuJS] Circular dependency detected in derived${debugName ? ` "${debugName}"` : ""}. A derived signal cannot read itself (directly or through a chain).`
3533
+ );
3534
+ }
3158
3535
  if (trackingSuspended) {
3159
3536
  if (cs._d) {
3160
- cs._d = false;
3161
- cs._v = getter();
3537
+ evaluating = true;
3538
+ let threw = true;
3539
+ try {
3540
+ retrack(() => {
3541
+ cs._v = getter();
3542
+ cs._d = false;
3543
+ threw = false;
3544
+ }, markDirty);
3545
+ } finally {
3546
+ evaluating = false;
3547
+ if (threw) cs._d = true;
3548
+ }
3162
3549
  }
3163
3550
  return cs._v;
3164
3551
  }
3165
3552
  recordDependency(cs);
3166
3553
  if (cs._d) {
3167
3554
  const oldValue = cs._v;
3168
- track(() => {
3169
- cs._d = false;
3170
- cs._v = getter();
3171
- }, markDirty);
3555
+ evaluating = true;
3556
+ let threw = true;
3557
+ try {
3558
+ retrack(() => {
3559
+ const next = getter();
3560
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
3561
+ cs._d = false;
3562
+ threw = false;
3563
+ }, markDirty);
3564
+ } finally {
3565
+ evaluating = false;
3566
+ if (threw) cs._d = true;
3567
+ }
3172
3568
  if (hook && oldValue !== cs._v) {
3173
3569
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
3174
3570
  }
@@ -3229,7 +3625,7 @@ function store(initialState) {
3229
3625
  },
3230
3626
  set() {
3231
3627
  throw new Error(
3232
- "[Sibu] store: Direct mutation is not allowed. Use actions.setState() to update store properties."
3628
+ "[SibuJS store] Direct mutation is not allowed. Use actions.setState() to update store properties."
3233
3629
  );
3234
3630
  }
3235
3631
  });
@@ -3243,16 +3639,20 @@ function store(initialState) {
3243
3639
  const setState = (patch) => {
3244
3640
  const current = getSnapshot();
3245
3641
  const nextState = typeof patch === "function" ? patch(current) : patch;
3246
- Object.entries(nextState).forEach(([key, value]) => {
3247
- if (key in signals) {
3248
- signals[key][1](value);
3249
- }
3642
+ batch(() => {
3643
+ Object.entries(nextState).forEach(([key, value]) => {
3644
+ if (key in signals) {
3645
+ signals[key][1](value);
3646
+ }
3647
+ });
3250
3648
  });
3251
3649
  };
3252
3650
  const reset = () => {
3253
- Object.keys(initialState).forEach((key) => {
3254
- const setter = signals[key][1];
3255
- setter(initialState[key]);
3651
+ batch(() => {
3652
+ Object.keys(initialState).forEach((key) => {
3653
+ const setter = signals[key][1];
3654
+ setter(initialState[key]);
3655
+ });
3256
3656
  });
3257
3657
  };
3258
3658
  const subscribe = (callback) => {
@@ -3386,7 +3786,8 @@ function reactiveArray(initial = []) {
3386
3786
  function get() {
3387
3787
  recordDependency(signal2);
3388
3788
  if (snapshot === null) {
3389
- snapshot = Object.freeze([...data2]);
3789
+ const copy = data2.slice();
3790
+ snapshot = Object.freeze(copy);
3390
3791
  }
3391
3792
  return snapshot;
3392
3793
  }
@@ -3496,21 +3897,67 @@ function deepEqual(a2, b2, seen) {
3496
3897
  if (a2 == null || b2 == null) return false;
3497
3898
  if (typeof a2 !== typeof b2) return false;
3498
3899
  if (typeof a2 !== "object") return false;
3499
- if (a2 instanceof Date && b2 instanceof Date) return a2.getTime() === b2.getTime();
3500
- if (a2 instanceof RegExp && b2 instanceof RegExp) return a2.toString() === b2.toString();
3501
- if (!seen) seen = /* @__PURE__ */ new Set();
3502
- if (seen.has(a2)) return true;
3503
- seen.add(a2);
3900
+ const objA = a2;
3901
+ const objB = b2;
3902
+ if (objA.constructor !== objB.constructor) return false;
3903
+ if (a2 instanceof Date) return a2.getTime() === b2.getTime();
3904
+ if (a2 instanceof RegExp) {
3905
+ const rb = b2;
3906
+ return a2.source === rb.source && a2.flags === rb.flags;
3907
+ }
3908
+ if (!seen) seen = /* @__PURE__ */ new Map();
3909
+ let peers = seen.get(objA);
3910
+ if (peers?.has(objB)) return true;
3911
+ if (!peers) {
3912
+ peers = /* @__PURE__ */ new Set();
3913
+ seen.set(objA, peers);
3914
+ }
3915
+ peers.add(objB);
3916
+ if (a2 instanceof Map) {
3917
+ const mb = b2;
3918
+ if (a2.size !== mb.size) return false;
3919
+ for (const [k, v] of a2) {
3920
+ if (!mb.has(k)) return false;
3921
+ if (!deepEqual(v, mb.get(k), seen)) return false;
3922
+ }
3923
+ return true;
3924
+ }
3925
+ if (a2 instanceof Set) {
3926
+ const sb = b2;
3927
+ if (a2.size !== sb.size) return false;
3928
+ for (const item of a2) {
3929
+ if (!sb.has(item)) return false;
3930
+ }
3931
+ return true;
3932
+ }
3933
+ if (a2 instanceof ArrayBuffer) {
3934
+ const viewA = new Uint8Array(a2);
3935
+ const viewB = new Uint8Array(b2);
3936
+ if (viewA.length !== viewB.length) return false;
3937
+ for (let i2 = 0; i2 < viewA.length; i2++) {
3938
+ if (viewA[i2] !== viewB[i2]) return false;
3939
+ }
3940
+ return true;
3941
+ }
3942
+ if (ArrayBuffer.isView(a2) && ArrayBuffer.isView(b2)) {
3943
+ const ta = a2;
3944
+ const tb = b2;
3945
+ if (ta.length !== tb.length) return false;
3946
+ for (let i2 = 0; i2 < ta.length; i2++) {
3947
+ if (ta[i2] !== tb[i2]) return false;
3948
+ }
3949
+ return true;
3950
+ }
3504
3951
  if (Array.isArray(a2)) {
3505
3952
  if (!Array.isArray(b2)) return false;
3506
3953
  if (a2.length !== b2.length) return false;
3507
3954
  return a2.every((val, i2) => deepEqual(val, b2[i2], seen));
3508
3955
  }
3509
- const keysA = Object.keys(a2);
3510
- const keysB = Object.keys(b2);
3956
+ const keysA = Object.keys(objA);
3957
+ const keysB = Object.keys(objB);
3511
3958
  if (keysA.length !== keysB.length) return false;
3512
3959
  return keysA.every(
3513
- (key) => deepEqual(a2[key], b2[key], seen)
3960
+ (key) => deepEqual(objA[key], objB[key], seen)
3514
3961
  );
3515
3962
  }
3516
3963
  function deepSignal(initial) {
@@ -3536,26 +3983,34 @@ function asyncDerived(factory, initial) {
3536
3983
  effect(() => {
3537
3984
  tick();
3538
3985
  const currentRun = ++runId;
3539
- setLoading(true);
3540
- setError(null);
3986
+ batch(() => {
3987
+ setLoading(true);
3988
+ setError(null);
3989
+ });
3541
3990
  let promise;
3542
3991
  try {
3543
3992
  promise = factory();
3544
3993
  } catch (err) {
3545
- setError(err);
3546
- setLoading(false);
3994
+ batch(() => {
3995
+ setError(err);
3996
+ setLoading(false);
3997
+ });
3547
3998
  return;
3548
3999
  }
3549
4000
  promise.then(
3550
4001
  (result) => {
3551
4002
  if (currentRun !== runId) return;
3552
- setValue(result);
3553
- setLoading(false);
4003
+ batch(() => {
4004
+ setValue(result);
4005
+ setLoading(false);
4006
+ });
3554
4007
  },
3555
4008
  (err) => {
3556
4009
  if (currentRun !== runId) return;
3557
- setError(err);
3558
- setLoading(false);
4010
+ batch(() => {
4011
+ setError(err);
4012
+ setLoading(false);
4013
+ });
3559
4014
  }
3560
4015
  );
3561
4016
  });
@@ -3570,32 +4025,198 @@ function asyncDerived(factory, initial) {
3570
4025
  // src/core/rendering/lifecycle.ts
3571
4026
  function safeCall(cb, hookName) {
3572
4027
  try {
3573
- cb();
4028
+ return cb();
3574
4029
  } catch (err) {
3575
4030
  devWarn(`${hookName}: callback threw: ${err instanceof Error ? err.message : String(err)}`);
4031
+ return void 0;
4032
+ }
4033
+ }
4034
+ function runMountCallback(callback, hookName, element) {
4035
+ const cleanup2 = safeCall(callback, hookName);
4036
+ if (typeof cleanup2 === "function" && element) {
4037
+ registerDisposer(element, cleanup2);
4038
+ }
4039
+ }
4040
+ var mountWatchers = /* @__PURE__ */ new WeakMap();
4041
+ var unmountWatchers = /* @__PURE__ */ new WeakMap();
4042
+ var watchedMountElements = /* @__PURE__ */ new Set();
4043
+ var watchedUnmountElements = /* @__PURE__ */ new Set();
4044
+ var sharedObserver = null;
4045
+ var mutationCounter = 0;
4046
+ var FULL_SWEEP_INTERVAL = 256;
4047
+ function fireMount(el) {
4048
+ const cbs = mountWatchers.get(el);
4049
+ if (!cbs) return;
4050
+ mountWatchers.delete(el);
4051
+ watchedMountElements.delete(el);
4052
+ for (const cb of cbs) {
4053
+ try {
4054
+ cb();
4055
+ } catch {
4056
+ }
4057
+ }
4058
+ }
4059
+ function fireUnmount(el) {
4060
+ const cbs = unmountWatchers.get(el);
4061
+ if (!cbs) return;
4062
+ queueMicrotask(() => {
4063
+ if (el.isConnected) return;
4064
+ const stillCbs = unmountWatchers.get(el);
4065
+ if (!stillCbs) return;
4066
+ unmountWatchers.delete(el);
4067
+ watchedUnmountElements.delete(el);
4068
+ for (const cb of stillCbs) {
4069
+ try {
4070
+ cb();
4071
+ } catch {
4072
+ }
4073
+ }
4074
+ });
4075
+ }
4076
+ function visitAddedNode(node) {
4077
+ if (watchedMountElements.size === 0) return;
4078
+ if (node.nodeType !== 1) return;
4079
+ const el = node;
4080
+ if (watchedMountElements.has(el) && el.isConnected) {
4081
+ fireMount(el);
4082
+ }
4083
+ if (el.firstElementChild) {
4084
+ for (const watched of Array.from(watchedMountElements)) {
4085
+ if (watched !== el && watched.isConnected && el.contains(watched)) {
4086
+ fireMount(watched);
4087
+ }
4088
+ }
4089
+ }
4090
+ }
4091
+ function visitRemovedNode(node) {
4092
+ if (watchedUnmountElements.size === 0) return;
4093
+ if (node.nodeType !== 1) return;
4094
+ const el = node;
4095
+ if (watchedUnmountElements.has(el) && !el.isConnected) {
4096
+ fireUnmount(el);
4097
+ }
4098
+ if (el.firstElementChild) {
4099
+ for (const watched of Array.from(watchedUnmountElements)) {
4100
+ if (watched !== el && !watched.isConnected && el.contains(watched)) {
4101
+ fireUnmount(watched);
4102
+ }
4103
+ }
4104
+ }
4105
+ }
4106
+ function fullSweep() {
4107
+ if (watchedMountElements.size > 0) {
4108
+ for (const el of Array.from(watchedMountElements)) {
4109
+ if (el.isConnected) fireMount(el);
4110
+ }
4111
+ }
4112
+ if (watchedUnmountElements.size > 0) {
4113
+ for (const el of Array.from(watchedUnmountElements)) {
4114
+ if (!el.isConnected) fireUnmount(el);
4115
+ }
4116
+ }
4117
+ }
4118
+ function ensureObserver() {
4119
+ if (sharedObserver || typeof document === "undefined") return;
4120
+ sharedObserver = new MutationObserver((mutations) => {
4121
+ for (const m of mutations) {
4122
+ if (m.type !== "childList") continue;
4123
+ if (m.addedNodes.length > 0) {
4124
+ for (let i2 = 0; i2 < m.addedNodes.length; i2++) {
4125
+ visitAddedNode(m.addedNodes[i2]);
4126
+ }
4127
+ }
4128
+ if (m.removedNodes.length > 0) {
4129
+ for (let i2 = 0; i2 < m.removedNodes.length; i2++) {
4130
+ visitRemovedNode(m.removedNodes[i2]);
4131
+ }
4132
+ }
4133
+ }
4134
+ mutationCounter += mutations.length;
4135
+ if (mutationCounter >= FULL_SWEEP_INTERVAL) {
4136
+ mutationCounter = 0;
4137
+ fullSweep();
4138
+ }
4139
+ maybeDisconnectObserver();
4140
+ });
4141
+ sharedObserver.observe(document.body, { childList: true, subtree: true });
4142
+ }
4143
+ function maybeDisconnectObserver() {
4144
+ if (!sharedObserver) return;
4145
+ if (watchedMountElements.size === 0 && watchedUnmountElements.size === 0) {
4146
+ sharedObserver.disconnect();
4147
+ sharedObserver = null;
4148
+ mutationCounter = 0;
3576
4149
  }
3577
4150
  }
4151
+ function registerMountWatcher(element, cb) {
4152
+ let list = mountWatchers.get(element);
4153
+ if (!list) {
4154
+ list = [];
4155
+ mountWatchers.set(element, list);
4156
+ }
4157
+ list.push(cb);
4158
+ watchedMountElements.add(element);
4159
+ ensureObserver();
4160
+ return () => {
4161
+ const cbs = mountWatchers.get(element);
4162
+ if (cbs) {
4163
+ const idx = cbs.indexOf(cb);
4164
+ if (idx !== -1) cbs.splice(idx, 1);
4165
+ if (cbs.length === 0) {
4166
+ mountWatchers.delete(element);
4167
+ watchedMountElements.delete(element);
4168
+ }
4169
+ }
4170
+ maybeDisconnectObserver();
4171
+ };
4172
+ }
4173
+ function registerUnmountWatcher(element, cb) {
4174
+ let list = unmountWatchers.get(element);
4175
+ if (!list) {
4176
+ list = [];
4177
+ unmountWatchers.set(element, list);
4178
+ }
4179
+ list.push(cb);
4180
+ watchedUnmountElements.add(element);
4181
+ ensureObserver();
4182
+ return () => {
4183
+ const cbs = unmountWatchers.get(element);
4184
+ if (cbs) {
4185
+ const idx = cbs.indexOf(cb);
4186
+ if (idx !== -1) cbs.splice(idx, 1);
4187
+ if (cbs.length === 0) {
4188
+ unmountWatchers.delete(element);
4189
+ watchedUnmountElements.delete(element);
4190
+ }
4191
+ }
4192
+ maybeDisconnectObserver();
4193
+ };
4194
+ }
3578
4195
  function onMount(callback, element) {
3579
4196
  if (typeof document === "undefined") return;
3580
4197
  if (element) {
4198
+ let disposed = false;
4199
+ registerDisposer(element, () => {
4200
+ disposed = true;
4201
+ });
3581
4202
  if (element.isConnected) {
3582
4203
  queueMicrotask(() => {
3583
- safeCall(callback, "onMount");
4204
+ if (disposed) return;
4205
+ runMountCallback(callback, "onMount", element);
3584
4206
  });
3585
4207
  return;
3586
4208
  }
3587
- const observer = new MutationObserver(() => {
3588
- if (element.isConnected) {
3589
- observer.disconnect();
3590
- safeCall(callback, "onMount");
3591
- }
3592
- });
3593
4209
  queueMicrotask(() => {
4210
+ if (disposed) return;
3594
4211
  if (element.isConnected) {
3595
- safeCall(callback, "onMount");
3596
- } else {
3597
- observer.observe(document.body, { childList: true, subtree: true });
4212
+ runMountCallback(callback, "onMount", element);
4213
+ return;
3598
4214
  }
4215
+ const unregister = registerMountWatcher(element, () => {
4216
+ if (disposed) return;
4217
+ runMountCallback(callback, "onMount", element);
4218
+ });
4219
+ registerDisposer(element, unregister);
3599
4220
  });
3600
4221
  } else {
3601
4222
  queueMicrotask(() => {
@@ -3604,20 +4225,24 @@ function onMount(callback, element) {
3604
4225
  }
3605
4226
  }
3606
4227
  function onUnmount(callback, element) {
3607
- const startObserving = () => {
3608
- const observer = new MutationObserver(() => {
3609
- if (!element.isConnected) {
3610
- observer.disconnect();
3611
- safeCall(callback, "onUnmount");
3612
- }
3613
- });
3614
- observer.observe(document.body, { childList: true, subtree: true });
4228
+ if (typeof document === "undefined") return;
4229
+ let fired = false;
4230
+ const fireOnce = () => {
4231
+ if (fired) return;
4232
+ fired = true;
4233
+ safeCall(callback, "onUnmount");
4234
+ };
4235
+ registerDisposer(element, fireOnce);
4236
+ const startWatching = () => {
4237
+ if (fired) return;
4238
+ const unregister = registerUnmountWatcher(element, fireOnce);
4239
+ registerDisposer(element, unregister);
3615
4240
  };
3616
4241
  if (element.isConnected) {
3617
- startObserving();
4242
+ startWatching();
3618
4243
  } else {
3619
4244
  onMount(() => {
3620
- startObserving();
4245
+ startWatching();
3621
4246
  return void 0;
3622
4247
  }, element);
3623
4248
  }
@@ -3629,9 +4254,11 @@ function onCleanup(callback, element) {
3629
4254
  // src/core/rendering/context.ts
3630
4255
  function context(defaultValue) {
3631
4256
  const [getValue, setValue] = signal(defaultValue);
3632
- return {
4257
+ const ctx = {
3633
4258
  provide(value) {
4259
+ const previous = getValue();
3634
4260
  setValue(value);
4261
+ return () => setValue(previous);
3635
4262
  },
3636
4263
  use() {
3637
4264
  return getValue;
@@ -3641,8 +4268,18 @@ function context(defaultValue) {
3641
4268
  },
3642
4269
  set(value) {
3643
4270
  setValue(value);
4271
+ },
4272
+ withContext(value, fn) {
4273
+ const previous = getValue();
4274
+ setValue(value);
4275
+ try {
4276
+ return fn();
4277
+ } finally {
4278
+ setValue(previous);
4279
+ }
3644
4280
  }
3645
4281
  };
4282
+ return ctx;
3646
4283
  }
3647
4284
 
3648
4285
  // src/core/strict.ts
@@ -3653,7 +4290,7 @@ function strict(fn) {
3653
4290
  try {
3654
4291
  fn();
3655
4292
  } catch (err) {
3656
- console.warn("[Sibu strict] second run threw:", err);
4293
+ console.warn("[SibuJS strict] second run threw:", err);
3657
4294
  }
3658
4295
  });
3659
4296
  }
@@ -3669,7 +4306,7 @@ function strictEffect(fn) {
3669
4306
  try {
3670
4307
  secondTeardown = effect(fn);
3671
4308
  } catch (err) {
3672
- console.warn("[Sibu strictEffect] second run threw:", err);
4309
+ console.warn("[SibuJS strictEffect] second run threw:", err);
3673
4310
  }
3674
4311
  });
3675
4312
  return () => {
@@ -3695,9 +4332,11 @@ function nextTick() {
3695
4332
  function defer(getter) {
3696
4333
  const [value, setValue] = signal(getter());
3697
4334
  let pending = false;
4335
+ let disposed = false;
3698
4336
  let latest = value();
3699
4337
  const flush = () => {
3700
4338
  pending = false;
4339
+ if (disposed) return;
3701
4340
  setValue(latest);
3702
4341
  };
3703
4342
  const schedule = () => {
@@ -3711,11 +4350,17 @@ function defer(getter) {
3711
4350
  }
3712
4351
  });
3713
4352
  };
3714
- track(() => {
4353
+ const teardown = track(() => {
3715
4354
  latest = getter();
3716
4355
  schedule();
3717
4356
  });
3718
- return value;
4357
+ const accessor = (() => value());
4358
+ accessor.dispose = () => {
4359
+ if (disposed) return;
4360
+ disposed = true;
4361
+ teardown();
4362
+ };
4363
+ return accessor;
3719
4364
  }
3720
4365
  var IDLE_FALLBACK_MS = 16;
3721
4366
  function scheduleIdle(fn) {
@@ -3756,32 +4401,56 @@ function transition() {
3756
4401
  }
3757
4402
 
3758
4403
  // src/core/rendering/lazy.ts
4404
+ var PENDING_ERROR = "__sibuPendingError";
4405
+ function dispatchPropagate(node, error) {
4406
+ const fire = () => {
4407
+ try {
4408
+ if (!node.parentNode) return false;
4409
+ node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
4410
+ return true;
4411
+ } catch {
4412
+ return false;
4413
+ }
4414
+ };
4415
+ if (node.parentNode && fire()) return;
4416
+ queueMicrotask(() => {
4417
+ if (fire()) return;
4418
+ node[PENDING_ERROR] = error;
4419
+ });
4420
+ }
4421
+ function takePendingError(node) {
4422
+ const rec = node;
4423
+ const err = rec[PENDING_ERROR];
4424
+ if (err instanceof Error) {
4425
+ delete rec[PENDING_ERROR];
4426
+ return err;
4427
+ }
4428
+ return void 0;
4429
+ }
3759
4430
  function lazy(importFn) {
3760
4431
  let cached = null;
3761
4432
  return function LazyComponent() {
3762
4433
  if (cached) {
3763
4434
  return cached();
3764
4435
  }
3765
- const [_status, setStatus] = signal("loading");
3766
- const [_error, setError] = signal(null);
3767
4436
  const container = div({ class: "sibu-lazy" });
4437
+ let disposed = false;
3768
4438
  importFn().then((mod) => {
4439
+ if (disposed) return;
3769
4440
  cached = mod.default;
3770
4441
  const rendered = cached();
3771
4442
  container.replaceChildren(rendered);
3772
- setStatus("loaded");
3773
4443
  }).catch((err) => {
4444
+ if (disposed) return;
3774
4445
  const errorObj = err instanceof Error ? err : new Error(String(err));
3775
- setError(errorObj);
3776
- setStatus("error");
3777
- container.replaceChildren(
3778
- div({
3779
- class: "sibu-lazy-error",
3780
- nodes: `Failed to load component: ${errorObj.message}`
3781
- })
3782
- );
4446
+ devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
4447
+ container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
4448
+ dispatchPropagate(container, errorObj);
4449
+ });
4450
+ container.appendChild(span("sibu-lazy-loading", "Loading..."));
4451
+ registerDisposer(container, () => {
4452
+ disposed = true;
3783
4453
  });
3784
- container.appendChild(span({ class: "sibu-lazy-loading", nodes: "Loading..." }));
3785
4454
  return container;
3786
4455
  };
3787
4456
  }
@@ -3789,32 +4458,55 @@ function Suspense({ nodes, fallback }) {
3789
4458
  const container = div({ class: "sibu-suspense" });
3790
4459
  const fallbackEl = fallback();
3791
4460
  container.appendChild(fallbackEl);
4461
+ let suspenseDisposed = false;
4462
+ let observer = null;
4463
+ registerDisposer(container, () => {
4464
+ suspenseDisposed = true;
4465
+ if (observer) {
4466
+ observer.disconnect();
4467
+ observer = null;
4468
+ }
4469
+ });
3792
4470
  queueMicrotask(() => {
4471
+ if (suspenseDisposed) return;
3793
4472
  try {
3794
4473
  const childEl = nodes();
3795
4474
  if (childEl.classList.contains("sibu-lazy")) {
3796
- const observer = new MutationObserver(() => {
4475
+ if (!childEl.querySelector(".sibu-lazy-loading")) {
4476
+ container.replaceChildren(childEl);
4477
+ return;
4478
+ }
4479
+ observer = new MutationObserver(() => {
4480
+ if (suspenseDisposed) return;
3797
4481
  const loading = childEl.querySelector(".sibu-lazy-loading");
3798
4482
  if (!loading) {
3799
- observer.disconnect();
4483
+ observer?.disconnect();
4484
+ observer = null;
3800
4485
  container.replaceChildren(childEl);
3801
4486
  }
3802
4487
  });
3803
4488
  observer.observe(childEl, { childList: true, subtree: true });
3804
- if (!childEl.querySelector(".sibu-lazy-loading")) {
3805
- container.replaceChildren(childEl);
3806
- }
3807
4489
  } else {
3808
4490
  container.replaceChildren(childEl);
3809
4491
  }
3810
- } catch {
4492
+ } catch (err) {
4493
+ const errorObj = err instanceof Error ? err : new Error(String(err));
4494
+ devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
4495
+ dispatchPropagate(container, errorObj);
3811
4496
  }
3812
4497
  });
3813
4498
  return container;
3814
4499
  }
3815
4500
 
4501
+ // src/platform/ssr.ts
4502
+ var _isDev10 = isDev();
4503
+ function trustHTML(html3) {
4504
+ return html3;
4505
+ }
4506
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
4507
+
3816
4508
  // src/components/ErrorDisplay.ts
3817
- var _isDev8 = isDev();
4509
+ var _isDev11 = isDev();
3818
4510
  var STYLES = `
3819
4511
  .sibu-error-display {
3820
4512
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -3900,20 +4592,21 @@ var STYLES = `
3900
4592
  font-weight: 600;
3901
4593
  }
3902
4594
  .sibu-error-display .sibu-err-copy-btn {
3903
- background: transparent;
3904
- border: 1px solid #3a3a4e;
4595
+ background: rgba(0, 0, 0, 0.22);
4596
+ border: 1px solid rgba(255, 255, 255, 0.15);
3905
4597
  border-radius: 4px;
3906
- color: #a0a3b8;
4598
+ color: rgba(255, 255, 255, 0.85);
3907
4599
  cursor: pointer;
3908
4600
  padding: 2px 10px;
3909
- font-size: 0.95em;
4601
+ font-size: 0.78em;
3910
4602
  font-family: inherit;
3911
4603
  transition: all 0.12s ease;
4604
+ flex-shrink: 0;
3912
4605
  }
3913
4606
  .sibu-error-display .sibu-err-copy-btn:hover {
3914
- background: #2a2a3e;
3915
- color: #e5e7eb;
3916
- border-color: #4a4a5e;
4607
+ background: rgba(0, 0, 0, 0.35);
4608
+ color: white;
4609
+ border-color: rgba(255, 255, 255, 0.3);
3917
4610
  }
3918
4611
 
3919
4612
  .sibu-error-display .sibu-err-stack {
@@ -4038,21 +4731,25 @@ function normalizeError(err) {
4038
4731
  cause: null
4039
4732
  };
4040
4733
  }
4041
- function buildCopyText(err, meta2) {
4734
+ function buildCopyText(err, meta2, headline) {
4042
4735
  const lines = [];
4736
+ lines.push(headline);
4043
4737
  lines.push(`[${err.code}] ${err.message}`);
4044
4738
  if (err.stack) {
4045
4739
  lines.push("");
4740
+ lines.push("Stack Trace:");
4046
4741
  lines.push(err.stack);
4047
4742
  }
4048
- if (err.cause) {
4743
+ let cause = err.cause;
4744
+ while (cause) {
4049
4745
  lines.push("");
4050
4746
  lines.push("Caused by:");
4051
- lines.push(` [${err.cause.code}] ${err.cause.message}`);
4052
- if (err.cause.stack) {
4053
- const indented = err.cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4747
+ lines.push(` [${cause.code}] ${cause.message}`);
4748
+ if (cause.stack) {
4749
+ const indented = cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4054
4750
  lines.push(indented);
4055
4751
  }
4752
+ cause = cause.cause;
4056
4753
  }
4057
4754
  if (meta2 && Object.keys(meta2).length > 0) {
4058
4755
  lines.push("");
@@ -4062,9 +4759,13 @@ function buildCopyText(err, meta2) {
4062
4759
  }
4063
4760
  }
4064
4761
  lines.push("");
4065
- lines.push(`At: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4762
+ lines.push("Environment:");
4763
+ lines.push(` Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4764
+ if (typeof location !== "undefined") {
4765
+ lines.push(` URL: ${location.href}`);
4766
+ }
4066
4767
  if (typeof navigator !== "undefined" && navigator.userAgent) {
4067
- lines.push(`UA: ${navigator.userAgent}`);
4768
+ lines.push(` User Agent: ${navigator.userAgent}`);
4068
4769
  }
4069
4770
  return lines.join("\n");
4070
4771
  }
@@ -4116,7 +4817,7 @@ function ErrorDisplay(props) {
4116
4817
  injectStyles();
4117
4818
  const severity = props.severity ?? "error";
4118
4819
  const normalized = normalizeError(props.error);
4119
- const showDetails = props.alwaysShowDetails ?? _isDev8;
4820
+ const showDetails = props.alwaysShowDetails ?? _isDev11;
4120
4821
  const headline = props.title ?? normalized.message;
4121
4822
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
4122
4823
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -4125,7 +4826,7 @@ function ErrorDisplay(props) {
4125
4826
  nodes: () => copyLabel(),
4126
4827
  on: {
4127
4828
  click: () => {
4128
- const text2 = buildCopyText(normalized, props.metadata);
4829
+ const text2 = buildCopyText(normalized, props.metadata, headline);
4129
4830
  if (typeof navigator !== "undefined" && navigator.clipboard) {
4130
4831
  navigator.clipboard.writeText(text2).then(
4131
4832
  () => {
@@ -4146,6 +4847,7 @@ function ErrorDisplay(props) {
4146
4847
  nodes: [
4147
4848
  code({ class: "sibu-err-icon", nodes: normalized.code }),
4148
4849
  h3({ class: "sibu-err-title", nodes: headline }),
4850
+ copyBtn,
4149
4851
  span({ class: "sibu-err-timestamp", nodes: timestamp })
4150
4852
  ]
4151
4853
  });
@@ -4157,25 +4859,12 @@ function ErrorDisplay(props) {
4157
4859
  nodes: [
4158
4860
  div({
4159
4861
  class: "sibu-err-section-head",
4160
- nodes: [span({ nodes: "Stack Trace" }), copyBtn]
4862
+ nodes: [span({ nodes: "Stack Trace" })]
4161
4863
  }),
4162
4864
  renderFrames(normalized.frames)
4163
4865
  ]
4164
4866
  })
4165
4867
  );
4166
- } else if (showDetails) {
4167
- bodyChildren.push(
4168
- div({
4169
- class: "sibu-err-section",
4170
- nodes: [
4171
- div({
4172
- class: "sibu-err-section-head",
4173
- nodes: [span({ nodes: "Details" }), copyBtn]
4174
- }),
4175
- div({ class: "sibu-err-stack", nodes: "(no stack available)" })
4176
- ]
4177
- })
4178
- );
4179
4868
  }
4180
4869
  if (showDetails) {
4181
4870
  bodyChildren.push(...renderCauseChain(normalized.cause));
@@ -4191,37 +4880,6 @@ function ErrorDisplay(props) {
4191
4880
  })
4192
4881
  );
4193
4882
  }
4194
- if (showDetails && typeof navigator !== "undefined" && navigator.userAgent) {
4195
- bodyChildren.push(
4196
- div({
4197
- class: "sibu-err-section",
4198
- nodes: [
4199
- div({ class: "sibu-err-section-head", nodes: [span({ nodes: "Environment" })] }),
4200
- div({
4201
- class: "sibu-err-meta",
4202
- nodes: (() => {
4203
- const dl2 = document.createElement("dl");
4204
- dl2.className = "sibu-err-meta";
4205
- const entries = [
4206
- ["User Agent", navigator.userAgent],
4207
- ["URL", typeof location !== "undefined" ? location.href : "(n/a)"],
4208
- ["Timestamp", (/* @__PURE__ */ new Date()).toISOString()]
4209
- ];
4210
- for (const [k, v] of entries) {
4211
- const dt2 = document.createElement("dt");
4212
- dt2.textContent = k;
4213
- const dd2 = document.createElement("dd");
4214
- dd2.textContent = v;
4215
- dl2.appendChild(dt2);
4216
- dl2.appendChild(dd2);
4217
- }
4218
- return dl2;
4219
- })()
4220
- })
4221
- ]
4222
- })
4223
- );
4224
- }
4225
4883
  const actionButtons = [];
4226
4884
  if (props.onRetry) {
4227
4885
  actionButtons.push(
@@ -4416,6 +5074,7 @@ function injectStyles2() {
4416
5074
  stylesInjected = true;
4417
5075
  }
4418
5076
  }
5077
+ var FALLBACK_CACHE_MAX = 50;
4419
5078
  var fallbackCache = /* @__PURE__ */ new WeakMap();
4420
5079
  function getMemoizedFallback(fallbackFn, error, retry) {
4421
5080
  let cache2 = fallbackCache.get(fallbackFn);
@@ -4424,27 +5083,42 @@ function getMemoizedFallback(fallbackFn, error, retry) {
4424
5083
  fallbackCache.set(fallbackFn, cache2);
4425
5084
  }
4426
5085
  const key = error.message;
4427
- if (!cache2.has(key)) {
4428
- cache2.set(key, fallbackFn(error, retry));
5086
+ let factory = cache2.get(key);
5087
+ if (factory) {
5088
+ cache2.delete(key);
5089
+ cache2.set(key, factory);
5090
+ } else {
5091
+ factory = () => fallbackFn(error, retry);
5092
+ cache2.set(key, factory);
5093
+ if (cache2.size > FALLBACK_CACHE_MAX) {
5094
+ const oldestKey = cache2.keys().next().value;
5095
+ if (oldestKey !== void 0) cache2.delete(oldestKey);
5096
+ }
4429
5097
  }
4430
- return cache2.get(key);
5098
+ return factory();
4431
5099
  }
4432
5100
  function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4433
5101
  injectStyles2();
4434
5102
  const [error, setError] = signal(null);
4435
5103
  const retry = () => {
4436
5104
  if (fallback) {
4437
- fallbackCache.delete(fallback);
5105
+ const cur = error();
5106
+ const inner = fallbackCache.get(fallback);
5107
+ if (cur && inner) inner.delete(cur.message);
4438
5108
  }
4439
5109
  setError(null);
4440
5110
  };
5111
+ let resetKeysTeardown = null;
4441
5112
  if (resetKeys && resetKeys.length > 0) {
4442
5113
  let initialized = false;
4443
- effect(() => {
5114
+ resetKeysTeardown = effect(() => {
4444
5115
  for (const k of resetKeys) {
4445
5116
  try {
4446
5117
  k();
4447
- } catch {
5118
+ } catch (err) {
5119
+ if (typeof console !== "undefined") {
5120
+ console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
5121
+ }
4448
5122
  }
4449
5123
  }
4450
5124
  if (!initialized) {
@@ -4457,7 +5131,15 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4457
5131
  const handleError = (e) => {
4458
5132
  const errorObj = e instanceof Error ? e : new Error(String(e));
4459
5133
  setError(errorObj);
4460
- onError?.(errorObj);
5134
+ if (onError) {
5135
+ try {
5136
+ onError(errorObj);
5137
+ } catch (cbErr) {
5138
+ if (typeof console !== "undefined") {
5139
+ console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
5140
+ }
5141
+ }
5142
+ }
4461
5143
  return errorObj;
4462
5144
  };
4463
5145
  const defaultFallback = (err, retryFn) => {
@@ -4509,7 +5191,7 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4509
5191
  }
4510
5192
  }
4511
5193
  });
4512
- container.addEventListener("sibu:error-propagate", (e) => {
5194
+ const propagateListener = (e) => {
4513
5195
  if (error()) return;
4514
5196
  e.stopPropagation();
4515
5197
  const customEvent = e;
@@ -4517,6 +5199,30 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4517
5199
  if (propagatedError) {
4518
5200
  handleError(propagatedError);
4519
5201
  }
5202
+ };
5203
+ container.addEventListener("sibu:error-propagate", propagateListener);
5204
+ onMount(() => {
5205
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
5206
+ const collected = [];
5207
+ let node = walker.currentNode;
5208
+ while (node) {
5209
+ const pending = takePendingError(node);
5210
+ if (pending) collected.push(pending);
5211
+ node = walker.nextNode();
5212
+ }
5213
+ if (collected.length === 1) {
5214
+ handleError(collected[0]);
5215
+ } else if (collected.length > 1) {
5216
+ const Agg = globalThis.AggregateError;
5217
+ handleError(
5218
+ Agg ? new Agg(collected, `${collected.length} pre-mount errors caught by ErrorBoundary`) : new Error(collected.map((e) => e.message).join("; "))
5219
+ );
5220
+ }
5221
+ return void 0;
5222
+ }, container);
5223
+ registerDisposer(container, () => {
5224
+ if (resetKeysTeardown) resetKeysTeardown();
5225
+ container.removeEventListener("sibu:error-propagate", propagateListener);
4520
5226
  });
4521
5227
  return container;
4522
5228
  }