sibujs 1.5.0 → 2.1.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 (208) hide show
  1. package/dist/browser.cjs +332 -121
  2. package/dist/browser.d.cts +5 -0
  3. package/dist/browser.d.ts +5 -0
  4. package/dist/browser.js +6 -6
  5. package/dist/build.cjs +1049 -344
  6. package/dist/build.js +15 -13
  7. package/dist/cdn.global.js +17 -16
  8. package/dist/chunk-2RA7SHDA.js +65 -0
  9. package/dist/chunk-2UPRY23K.js +80 -0
  10. package/dist/{chunk-BMPL52BF.js → chunk-3DZP6OIT.js} +118 -66
  11. package/dist/chunk-3JHCYHWN.js +125 -0
  12. package/dist/{chunk-CZUGLNJS.js → chunk-45YP72ZQ.js} +3 -3
  13. package/dist/{chunk-JCDUJN2F.js → chunk-AMK2TYNW.js} +490 -153
  14. package/dist/{chunk-NHUC2QWH.js → chunk-CWBVQML6.js} +1 -1
  15. package/dist/{chunk-XHK6BDAJ.js → chunk-DRUZZAK4.js} +25 -8
  16. package/dist/{chunk-RJ46C3CS.js → chunk-GWWURC5M.js} +71 -20
  17. package/dist/{chunk-3X2YG6YM.js → chunk-JYD2PWXH.js} +59 -28
  18. package/dist/{chunk-2BYQDGN3.js → chunk-KGYT6UO6.js} +234 -63
  19. package/dist/{chunk-5X6PP2UK.js → chunk-LMLD24FC.js} +2 -2
  20. package/dist/{chunk-M4NLBH4I.js → chunk-LYTCUZ7H.js} +3 -2
  21. package/dist/{chunk-XUEEGU5O.js → chunk-NASX6ST2.js} +16 -4
  22. package/dist/{chunk-VQDZK23A.js → chunk-O6EFQ3KT.js} +181 -66
  23. package/dist/{chunk-BGN5ZMP4.js → chunk-OJ3P4ECI.js} +14 -2
  24. package/dist/chunk-ON5MMR2J.js +1327 -0
  25. package/dist/{chunk-SFKNRVCU.js → chunk-P2HSJDDN.js} +135 -79
  26. package/dist/chunk-QO3WC6FS.js +384 -0
  27. package/dist/{chunk-WZSPOOER.js → chunk-RDTDJCAB.js} +8 -5
  28. package/dist/{chunk-7GRNSCFT.js → chunk-TH2ILCYW.js} +312 -185
  29. package/dist/chunk-UCS6AMJ7.js +79 -0
  30. package/dist/{chunk-VAPYJN4X.js → chunk-V6C4FADE.js} +93 -23
  31. package/dist/{chunk-OUZZEE4S.js → chunk-WANSMF2L.js} +17 -11
  32. package/dist/{chunk-23VV7YD3.js → chunk-WIPZPFBQ.js} +25 -30
  33. package/dist/chunk-WZA53FXU.js +149 -0
  34. package/dist/{chunk-BGTHZHJ5.js → chunk-ZAQSMOED.js} +188 -44
  35. package/dist/{customElement-BL3Uo8dL.d.cts → customElement-CPfIrbvg.d.cts} +14 -10
  36. package/dist/{customElement-BL3Uo8dL.d.ts → customElement-CPfIrbvg.d.ts} +14 -10
  37. package/dist/data.cjs +536 -151
  38. package/dist/data.d.cts +20 -2
  39. package/dist/data.d.ts +20 -2
  40. package/dist/data.js +11 -9
  41. package/dist/devtools.cjs +613 -266
  42. package/dist/devtools.d.cts +1 -1
  43. package/dist/devtools.d.ts +1 -1
  44. package/dist/devtools.js +12 -6
  45. package/dist/ecosystem.cjs +602 -197
  46. package/dist/ecosystem.d.cts +9 -7
  47. package/dist/ecosystem.d.ts +9 -7
  48. package/dist/ecosystem.js +12 -11
  49. package/dist/extras.cjs +3500 -1608
  50. package/dist/extras.d.cts +9 -9
  51. package/dist/extras.d.ts +9 -9
  52. package/dist/extras.js +58 -45
  53. package/dist/index.cjs +1055 -344
  54. package/dist/index.d.cts +85 -8
  55. package/dist/index.d.ts +85 -8
  56. package/dist/index.js +32 -16
  57. package/dist/{introspect-BumjnBKr.d.cts → introspect-2TOlQ7oa.d.cts} +25 -3
  58. package/dist/{introspect-CZrlcaYy.d.ts → introspect-DnIpHQQz.d.ts} +25 -3
  59. package/dist/motion.cjs +122 -63
  60. package/dist/motion.js +4 -4
  61. package/dist/patterns.cjs +450 -110
  62. package/dist/patterns.d.cts +11 -12
  63. package/dist/patterns.d.ts +11 -12
  64. package/dist/patterns.js +7 -7
  65. package/dist/performance.cjs +373 -149
  66. package/dist/performance.d.cts +23 -16
  67. package/dist/performance.d.ts +23 -16
  68. package/dist/performance.js +13 -8
  69. package/dist/plugin-D30wlGW5.d.cts +71 -0
  70. package/dist/plugin-D30wlGW5.d.ts +71 -0
  71. package/dist/plugins.cjs +729 -301
  72. package/dist/plugins.d.cts +10 -3
  73. package/dist/plugins.d.ts +10 -3
  74. package/dist/plugins.js +106 -38
  75. package/dist/{ssr-Do_SiVoL.d.cts → ssr-CrVNy6Pa.d.cts} +9 -15
  76. package/dist/{ssr-Do_SiVoL.d.ts → ssr-CrVNy6Pa.d.ts} +9 -15
  77. package/dist/{ssr-4PBXAOO3.js → ssr-FXD2PPMC.js} +4 -3
  78. package/dist/ssr.cjs +736 -274
  79. package/dist/ssr.d.cts +26 -6
  80. package/dist/ssr.d.ts +26 -6
  81. package/dist/ssr.js +12 -11
  82. package/dist/{tagFactory-DaJ0YWX6.d.cts → tagFactory-S17H2qxu.d.cts} +9 -1
  83. package/dist/{tagFactory-DaJ0YWX6.d.ts → tagFactory-S17H2qxu.d.ts} +9 -1
  84. package/dist/testing.cjs +303 -76
  85. package/dist/testing.d.cts +17 -4
  86. package/dist/testing.d.ts +17 -4
  87. package/dist/testing.js +100 -44
  88. package/dist/ui.cjs +589 -178
  89. package/dist/ui.d.cts +1 -1
  90. package/dist/ui.d.ts +1 -1
  91. package/dist/ui.js +20 -17
  92. package/dist/widgets.cjs +1103 -146
  93. package/dist/widgets.d.cts +104 -2
  94. package/dist/widgets.d.ts +104 -2
  95. package/dist/widgets.js +9 -7
  96. package/package.json +8 -2
  97. package/dist/chunk-32DY64NT.js +0 -282
  98. package/dist/chunk-3AIRKM3B.js +0 -1263
  99. package/dist/chunk-3ARAQO7B.js +0 -398
  100. package/dist/chunk-3CRQALYP.js +0 -877
  101. package/dist/chunk-4EI4AG32.js +0 -482
  102. package/dist/chunk-4MYMUBRS.js +0 -21
  103. package/dist/chunk-5ZYQ6KDD.js +0 -154
  104. package/dist/chunk-6BMPXPUW.js +0 -26
  105. package/dist/chunk-6HLLIF3K.js +0 -398
  106. package/dist/chunk-6LSNVCS2.js +0 -937
  107. package/dist/chunk-6SA3QQES.js +0 -61
  108. package/dist/chunk-77L6NL3X.js +0 -1097
  109. package/dist/chunk-7BF6TK55.js +0 -1097
  110. package/dist/chunk-7TQKR4PP.js +0 -294
  111. package/dist/chunk-7V26P53V.js +0 -712
  112. package/dist/chunk-AZ3ISID5.js +0 -298
  113. package/dist/chunk-B7SWRFUT.js +0 -332
  114. package/dist/chunk-BTU3TJDS.js +0 -365
  115. package/dist/chunk-BW3WT46K.js +0 -937
  116. package/dist/chunk-C6KFWOFV.js +0 -616
  117. package/dist/chunk-CHF5OHIA.js +0 -61
  118. package/dist/chunk-CHJ27IGK.js +0 -26
  119. package/dist/chunk-CMBFNA7L.js +0 -27
  120. package/dist/chunk-DAHRH4ON.js +0 -331
  121. package/dist/chunk-DKOHBI74.js +0 -924
  122. package/dist/chunk-DTCOOBMX.js +0 -725
  123. package/dist/chunk-EBGIRKQY.js +0 -616
  124. package/dist/chunk-EUZND3CB.js +0 -27
  125. package/dist/chunk-EVCZO745.js +0 -365
  126. package/dist/chunk-EWFVA3TJ.js +0 -282
  127. package/dist/chunk-F3FA4F32.js +0 -292
  128. package/dist/chunk-FGOEVHY3.js +0 -60
  129. package/dist/chunk-G3BOQPVO.js +0 -365
  130. package/dist/chunk-GCOK2LC3.js +0 -282
  131. package/dist/chunk-GJPXRJ45.js +0 -37
  132. package/dist/chunk-HGMJFBC7.js +0 -654
  133. package/dist/chunk-JAKHTMQU.js +0 -1000
  134. package/dist/chunk-JCI5M6U6.js +0 -956
  135. package/dist/chunk-K4G4ZQNR.js +0 -286
  136. package/dist/chunk-K5ZUMYVS.js +0 -89
  137. package/dist/chunk-KQPDEVVS.js +0 -398
  138. package/dist/chunk-L6JRBDNS.js +0 -60
  139. package/dist/chunk-LA6KQEDU.js +0 -712
  140. package/dist/chunk-MB6QFH3I.js +0 -2776
  141. package/dist/chunk-MDVXJWFN.js +0 -304
  142. package/dist/chunk-MEZVEBPN.js +0 -2008
  143. package/dist/chunk-MK4ERFYL.js +0 -2249
  144. package/dist/chunk-MLKGABMK.js +0 -9
  145. package/dist/chunk-MQ5GOYPH.js +0 -2249
  146. package/dist/chunk-MYRV7VDM.js +0 -742
  147. package/dist/chunk-N6IZB6KJ.js +0 -567
  148. package/dist/chunk-NEKUBFPT.js +0 -60
  149. package/dist/chunk-NMRUZALC.js +0 -1097
  150. package/dist/chunk-NYVAC6P5.js +0 -37
  151. package/dist/chunk-NZIIMDWI.js +0 -84
  152. package/dist/chunk-OF7UZIVB.js +0 -725
  153. package/dist/chunk-P3XWXJZU.js +0 -282
  154. package/dist/chunk-P6W3STU4.js +0 -2249
  155. package/dist/chunk-PBHF5WKN.js +0 -616
  156. package/dist/chunk-PDZQY43A.js +0 -616
  157. package/dist/chunk-PTQJDMRT.js +0 -146
  158. package/dist/chunk-PZEGYCF5.js +0 -61
  159. package/dist/chunk-QBMDLBU2.js +0 -975
  160. package/dist/chunk-QWZG56ET.js +0 -2744
  161. package/dist/chunk-RQGQSLQK.js +0 -725
  162. package/dist/chunk-SDLZDHKP.js +0 -107
  163. package/dist/chunk-TDGZL5CU.js +0 -365
  164. package/dist/chunk-TNQWPPE6.js +0 -37
  165. package/dist/chunk-TSOKIX5Z.js +0 -654
  166. package/dist/chunk-UHNL42EF.js +0 -2730
  167. package/dist/chunk-UNXCEF6S.js +0 -21
  168. package/dist/chunk-V2XTI523.js +0 -347
  169. package/dist/chunk-VAU366PN.js +0 -2241
  170. package/dist/chunk-VMVDTCXB.js +0 -712
  171. package/dist/chunk-VQNQZCWJ.js +0 -61
  172. package/dist/chunk-VRW3FULF.js +0 -725
  173. package/dist/chunk-WADYRCO2.js +0 -304
  174. package/dist/chunk-WILQZRO4.js +0 -282
  175. package/dist/chunk-WR5D4EGH.js +0 -26
  176. package/dist/chunk-WUHJISPP.js +0 -298
  177. package/dist/chunk-XYU6TZOW.js +0 -182
  178. package/dist/chunk-Y6GP4QGG.js +0 -276
  179. package/dist/chunk-YECR7UIA.js +0 -347
  180. package/dist/chunk-YUTWTI4B.js +0 -654
  181. package/dist/chunk-Z65KYU7I.js +0 -26
  182. package/dist/chunk-Z6POF5YC.js +0 -975
  183. package/dist/chunk-ZBJP6WFL.js +0 -482
  184. package/dist/chunk-ZD6OAMTH.js +0 -277
  185. package/dist/chunk-ZWKZCBO6.js +0 -317
  186. package/dist/contracts-DDrwxvJ-.d.cts +0 -245
  187. package/dist/contracts-DDrwxvJ-.d.ts +0 -245
  188. package/dist/contracts-DOrhwbke.d.cts +0 -245
  189. package/dist/contracts-DOrhwbke.d.ts +0 -245
  190. package/dist/contracts-xo5ckdRP.d.cts +0 -240
  191. package/dist/contracts-xo5ckdRP.d.ts +0 -240
  192. package/dist/customElement-BKQfbSZQ.d.cts +0 -262
  193. package/dist/customElement-BKQfbSZQ.d.ts +0 -262
  194. package/dist/customElement-D2DJp_xn.d.cts +0 -313
  195. package/dist/customElement-D2DJp_xn.d.ts +0 -313
  196. package/dist/customElement-yz8uyk-0.d.cts +0 -308
  197. package/dist/customElement-yz8uyk-0.d.ts +0 -308
  198. package/dist/introspect-Cb0zgpi2.d.cts +0 -477
  199. package/dist/introspect-Y2xNXGSf.d.ts +0 -477
  200. package/dist/plugin-Bek4RhJY.d.cts +0 -43
  201. package/dist/plugin-Bek4RhJY.d.ts +0 -43
  202. package/dist/ssr-3RXHP5ES.js +0 -38
  203. package/dist/ssr-6GIMY5MX.js +0 -38
  204. package/dist/ssr-BA6sxxUd.d.cts +0 -135
  205. package/dist/ssr-BA6sxxUd.d.ts +0 -135
  206. package/dist/ssr-WKUPVSSK.js +0 -36
  207. package/dist/tagFactory-Dl8QCLga.d.cts +0 -23
  208. 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,
@@ -1203,15 +1204,18 @@ __export(index_exports, {
1203
1204
  registerComponent: () => registerComponent,
1204
1205
  registerDisposer: () => registerDisposer,
1205
1206
  resolveComponent: () => resolveComponent,
1207
+ retrack: () => retrack,
1206
1208
  rp: () => rp,
1207
1209
  rt: () => rt,
1208
1210
  ruby: () => ruby,
1211
+ runInSSRContext: () => runInSSRContext,
1209
1212
  s: () => s,
1210
1213
  samp: () => samp,
1211
1214
  script: () => script,
1212
1215
  section: () => section,
1213
1216
  select: () => select,
1214
1217
  setGlobalErrorHandler: () => setGlobalErrorHandler,
1218
+ setMaxDrainIterations: () => setMaxDrainIterations,
1215
1219
  show: () => show,
1216
1220
  signal: () => signal,
1217
1221
  slot: () => slot,
@@ -1231,6 +1235,7 @@ __export(index_exports, {
1231
1235
  symbol: () => symbol,
1232
1236
  table: () => table,
1233
1237
  tagFactory: () => tagFactory,
1238
+ takePendingError: () => takePendingError,
1234
1239
  tbody: () => tbody,
1235
1240
  td: () => td,
1236
1241
  template: () => template,
@@ -1245,6 +1250,7 @@ __export(index_exports, {
1245
1250
  track: () => track2,
1246
1251
  transition: () => transition,
1247
1252
  trapFocus: () => trapFocus,
1253
+ trustHTML: () => trustHTML,
1248
1254
  tspan: () => tspan,
1249
1255
  u: () => u,
1250
1256
  ul: () => ul,
@@ -1266,48 +1272,108 @@ function isDev() {
1266
1272
  var _isDev = isDev();
1267
1273
  function devAssert(condition, message) {
1268
1274
  if (_isDev && !condition) {
1269
- throw new Error(`[Sibu] ${message}`);
1275
+ throw new Error(`[SibuJS] ${message}`);
1270
1276
  }
1271
1277
  }
1272
1278
  function devWarn(message) {
1273
1279
  if (_isDev) {
1274
- console.warn(`[Sibu] ${message}`);
1280
+ console.warn(`[SibuJS] ${message}`);
1275
1281
  }
1276
1282
  }
1277
1283
 
1278
1284
  // src/utils/sanitize.ts
1285
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
1279
1286
  function sanitizeUrl(url) {
1280
1287
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1281
1288
  if (!trimmed) return "";
1282
1289
  const lower = trimmed.toLowerCase();
1283
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1284
- return "";
1290
+ let schemeEnd = -1;
1291
+ for (let i2 = 0; i2 < lower.length; i2++) {
1292
+ const ch = lower.charCodeAt(i2);
1293
+ if (ch === 58) {
1294
+ schemeEnd = i2;
1295
+ break;
1296
+ }
1297
+ if (ch === 47 || ch === 63 || ch === 35) break;
1285
1298
  }
1299
+ if (schemeEnd === -1) return trimmed;
1300
+ const scheme = lower.slice(0, schemeEnd + 1);
1301
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
1302
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
1286
1303
  return trimmed;
1287
1304
  }
1305
+ function sanitizeSrcset(value) {
1306
+ const parts = value.split(",");
1307
+ const out = [];
1308
+ for (let i2 = 0; i2 < parts.length; i2++) {
1309
+ const part = parts[i2].trim();
1310
+ if (!part) continue;
1311
+ const m = part.match(/^(\S+)(\s+.+)?$/);
1312
+ if (!m) continue;
1313
+ const safe = sanitizeUrl(m[1]);
1314
+ if (!safe) continue;
1315
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
1316
+ }
1317
+ return out.join(", ");
1318
+ }
1288
1319
  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")) {
1320
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
1321
+ const code2 = Number.parseInt(hex, 16);
1322
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
1323
+ try {
1324
+ return String.fromCodePoint(code2);
1325
+ } catch {
1326
+ return "";
1327
+ }
1328
+ });
1329
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
1330
+ 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
1331
  return "";
1292
1332
  }
1293
1333
  return value;
1294
1334
  }
1295
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
1335
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
1336
+ "href",
1337
+ "xlink:href",
1338
+ "src",
1339
+ "action",
1340
+ "formaction",
1341
+ "formtarget",
1342
+ "cite",
1343
+ "poster",
1344
+ "background",
1345
+ "srcset",
1346
+ "ping",
1347
+ "data"
1348
+ ]);
1296
1349
  function isUrlAttribute(attr) {
1297
1350
  return URL_ATTRIBUTES.has(attr);
1298
1351
  }
1299
1352
 
1300
1353
  // src/reactivity/track.ts
1301
1354
  var _isDev2 = isDev();
1302
- var subscriberStack = new Array(32);
1303
- var stackCapacity = 32;
1355
+ var STACK_INITIAL = 32;
1356
+ var STACK_SHRINK_THRESHOLD = 128;
1357
+ var subscriberStack = new Array(STACK_INITIAL);
1358
+ var stackCapacity = STACK_INITIAL;
1304
1359
  var stackTop = -1;
1305
1360
  var currentSubscriber = null;
1306
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
1307
1361
  var SUBS = "__s";
1362
+ function syncFastPath(signal2, subs) {
1363
+ const size = subs.size;
1364
+ if (size === 0) {
1365
+ signal2.__f = void 0;
1366
+ delete signal2[SUBS];
1367
+ } else if (size === 1) {
1368
+ signal2.__f = subs.values().next().value;
1369
+ } else {
1370
+ signal2.__f = void 0;
1371
+ }
1372
+ }
1308
1373
  var notifyDepth = 0;
1309
1374
  var pendingQueue = [];
1310
1375
  var pendingSet = /* @__PURE__ */ new Set();
1376
+ var propagateStack = [];
1311
1377
  function safeInvoke(sub2) {
1312
1378
  try {
1313
1379
  sub2();
@@ -1317,6 +1383,47 @@ function safeInvoke(sub2) {
1317
1383
  }
1318
1384
  var suspendDepth = 0;
1319
1385
  var trackingSuspended = false;
1386
+ var subscriberEpochCounter = 0;
1387
+ function retrack(effectFn, subscriber) {
1388
+ const prev = currentSubscriber;
1389
+ currentSubscriber = subscriber;
1390
+ const sub2 = subscriber;
1391
+ const epoch = ++subscriberEpochCounter;
1392
+ sub2._epoch = epoch;
1393
+ try {
1394
+ effectFn();
1395
+ } finally {
1396
+ currentSubscriber = prev;
1397
+ pruneStaleDeps(sub2, epoch);
1398
+ }
1399
+ }
1400
+ function pruneStaleDeps(sub2, currentEpoch) {
1401
+ if (sub2._dep !== void 0) {
1402
+ if (sub2._depEpoch !== currentEpoch) {
1403
+ const sig = sub2._dep;
1404
+ const subs = sig[SUBS];
1405
+ if (subs?.delete(sub2)) syncFastPath(sig, subs);
1406
+ sub2._dep = void 0;
1407
+ sub2._depEpoch = void 0;
1408
+ }
1409
+ return;
1410
+ }
1411
+ const deps = sub2._deps;
1412
+ if (!deps || deps.size === 0) return;
1413
+ let stales;
1414
+ for (const [signal2, epoch] of deps) {
1415
+ if (epoch !== currentEpoch) {
1416
+ (stales ?? (stales = [])).push(signal2);
1417
+ }
1418
+ }
1419
+ if (!stales) return;
1420
+ for (const signal2 of stales) {
1421
+ deps.delete(signal2);
1422
+ const sig = signal2;
1423
+ const subs = sig[SUBS];
1424
+ if (subs?.delete(sub2)) syncFastPath(sig, subs);
1425
+ }
1426
+ }
1320
1427
  function track(effectFn, subscriber) {
1321
1428
  if (!subscriber) subscriber = effectFn;
1322
1429
  cleanup(subscriber);
@@ -1332,6 +1439,10 @@ function track(effectFn, subscriber) {
1332
1439
  } finally {
1333
1440
  stackTop--;
1334
1441
  currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
1442
+ if (stackTop < 0 && stackCapacity > STACK_SHRINK_THRESHOLD) {
1443
+ stackCapacity = Math.max(STACK_INITIAL, stackCapacity >>> 1);
1444
+ subscriberStack.length = stackCapacity;
1445
+ }
1335
1446
  }
1336
1447
  return () => cleanup(subscriber);
1337
1448
  }
@@ -1367,31 +1478,39 @@ function untracked(fn) {
1367
1478
  function recordDependency(signal2) {
1368
1479
  if (!currentSubscriber) return;
1369
1480
  const sub2 = currentSubscriber;
1370
- if (sub2._dep === signal2) return;
1481
+ const epoch = sub2._epoch;
1482
+ if (sub2._dep === signal2) {
1483
+ sub2._depEpoch = epoch;
1484
+ return;
1485
+ }
1371
1486
  const deps = sub2._deps;
1372
1487
  if (deps) {
1373
- if (deps.has(signal2)) return;
1374
- deps.add(signal2);
1488
+ deps.set(signal2, epoch);
1375
1489
  } else if (sub2._dep !== void 0) {
1376
- const set = /* @__PURE__ */ new Set();
1377
- set.add(sub2._dep);
1378
- set.add(signal2);
1379
- sub2._deps = set;
1490
+ const map2 = /* @__PURE__ */ new Map();
1491
+ map2.set(sub2._dep, sub2._depEpoch);
1492
+ map2.set(signal2, epoch);
1493
+ sub2._deps = map2;
1380
1494
  sub2._dep = void 0;
1495
+ sub2._depEpoch = void 0;
1381
1496
  } else {
1382
1497
  sub2._dep = signal2;
1498
+ sub2._depEpoch = epoch;
1383
1499
  }
1384
- let subs = signal2[SUBS];
1500
+ const sig = signal2;
1501
+ let subs = sig[SUBS];
1385
1502
  if (!subs) {
1386
1503
  subs = /* @__PURE__ */ new Set();
1387
- signalSubscribers.set(signal2, subs);
1388
- signal2[SUBS] = subs;
1504
+ sig[SUBS] = subs;
1389
1505
  }
1506
+ const prevSize = subs.size;
1390
1507
  subs.add(currentSubscriber);
1391
- if (subs.size === 1) {
1392
- signal2.__f = currentSubscriber;
1393
- } else if (signal2.__f !== void 0) {
1394
- signal2.__f = void 0;
1508
+ if (subs.size !== prevSize) {
1509
+ if (subs.size === 1) {
1510
+ sig.__f = currentSubscriber;
1511
+ } else if (sig.__f !== void 0) {
1512
+ sig.__f = void 0;
1513
+ }
1395
1514
  }
1396
1515
  }
1397
1516
  function queueSignalNotification(signal2) {
@@ -1406,66 +1525,107 @@ function queueSignalNotification(signal2) {
1406
1525
  }
1407
1526
  }
1408
1527
  }
1409
- var MAX_DRAIN_ITERATIONS = 1e3;
1528
+ var maxSubscriberRepeats = 50;
1529
+ var maxDrainIterations = 1e6;
1530
+ var drainEpoch = 0;
1531
+ function setMaxDrainIterations(n) {
1532
+ const prev = maxDrainIterations;
1533
+ if (Number.isFinite(n) && n > 0) maxDrainIterations = Math.floor(n);
1534
+ return prev;
1535
+ }
1536
+ function tickRepeat(sub2) {
1537
+ const s2 = sub2;
1538
+ if (s2._runEpoch !== drainEpoch) {
1539
+ s2._runEpoch = drainEpoch;
1540
+ s2._runs = 1;
1541
+ return false;
1542
+ }
1543
+ return ++s2._runs > maxSubscriberRepeats;
1544
+ }
1545
+ function cycleError(sub2) {
1546
+ if (typeof console !== "undefined") {
1547
+ const name = sub2.__name ?? "<unnamed>";
1548
+ console.error(
1549
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
1550
+ );
1551
+ }
1552
+ }
1553
+ function absoluteDrainError() {
1554
+ if (typeof console !== "undefined") {
1555
+ console.error(
1556
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
1557
+ );
1558
+ }
1559
+ }
1560
+ function drainQueue() {
1561
+ let i2 = 0;
1562
+ while (i2 < pendingQueue.length) {
1563
+ if (i2 >= maxDrainIterations) {
1564
+ absoluteDrainError();
1565
+ break;
1566
+ }
1567
+ const sub2 = pendingQueue[i2++];
1568
+ if (tickRepeat(sub2)) {
1569
+ cycleError(sub2);
1570
+ break;
1571
+ }
1572
+ pendingSet.delete(sub2);
1573
+ safeInvoke(sub2);
1574
+ }
1575
+ }
1410
1576
  function drainNotificationQueue() {
1411
1577
  if (notifyDepth > 0) return;
1412
1578
  notifyDepth++;
1579
+ drainEpoch++;
1413
1580
  try {
1414
- let i2 = 0;
1415
- while (i2 < pendingQueue.length) {
1416
- if (i2 >= MAX_DRAIN_ITERATIONS) {
1417
- if (typeof console !== "undefined") {
1418
- console.error(
1419
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
1420
- );
1421
- }
1422
- break;
1423
- }
1424
- safeInvoke(pendingQueue[i2]);
1425
- i2++;
1426
- }
1581
+ drainQueue();
1427
1582
  } finally {
1428
- pendingQueue.length = 0;
1429
- pendingSet.clear();
1430
1583
  notifyDepth--;
1584
+ if (notifyDepth === 0) {
1585
+ pendingQueue.length = 0;
1586
+ pendingSet.clear();
1587
+ }
1431
1588
  }
1432
1589
  }
1433
1590
  function propagateDirty(sub2) {
1434
1591
  sub2();
1435
- let sig = sub2._sig;
1436
- while (sig) {
1592
+ const rootSig = sub2._sig;
1593
+ if (!rootSig) return;
1594
+ const stack = propagateStack;
1595
+ const baseLen = stack.length;
1596
+ stack.push(rootSig);
1597
+ while (stack.length > baseLen) {
1598
+ const sig = stack.pop();
1437
1599
  const first = sig.__f;
1438
1600
  if (first) {
1439
1601
  if (first._c) {
1440
1602
  const nSig = first._sig;
1441
- nSig._d = true;
1442
- sig = nSig;
1443
- continue;
1444
- }
1445
- if (!pendingSet.has(first)) {
1603
+ if (!nSig._d) {
1604
+ nSig._d = true;
1605
+ stack.push(nSig);
1606
+ }
1607
+ } else if (!pendingSet.has(first)) {
1446
1608
  pendingSet.add(first);
1447
1609
  pendingQueue.push(first);
1448
1610
  }
1449
- break;
1611
+ continue;
1450
1612
  }
1451
1613
  const subs = sig[SUBS];
1452
- if (!subs) break;
1453
- let nextSig;
1614
+ if (!subs) continue;
1454
1615
  for (const s2 of subs) {
1455
1616
  if (s2._c) {
1456
- s2();
1457
1617
  const nSig = s2._sig;
1458
- if (nSig && !nextSig) {
1459
- nextSig = nSig;
1460
- } else if (nSig) {
1461
- propagateDirty(s2);
1618
+ if (nSig && !nSig._d) {
1619
+ nSig._d = true;
1620
+ stack.push(nSig);
1621
+ } else if (!nSig) {
1622
+ s2();
1462
1623
  }
1463
1624
  } else if (!pendingSet.has(s2)) {
1464
1625
  pendingSet.add(s2);
1465
1626
  pendingQueue.push(s2);
1466
1627
  }
1467
1628
  }
1468
- sig = nextSig;
1469
1629
  }
1470
1630
  }
1471
1631
  function notifySubscribers(signal2) {
@@ -1481,21 +1641,22 @@ function notifySubscribers(signal2) {
1481
1641
  return;
1482
1642
  }
1483
1643
  notifyDepth++;
1644
+ drainEpoch++;
1484
1645
  try {
1485
1646
  if (first._c) {
1486
1647
  propagateDirty(first);
1648
+ } else if (tickRepeat(first)) {
1649
+ cycleError(first);
1487
1650
  } else {
1488
1651
  safeInvoke(first);
1489
1652
  }
1490
- let i2 = 0;
1491
- while (i2 < pendingQueue.length) {
1492
- safeInvoke(pendingQueue[i2]);
1493
- i2++;
1494
- }
1653
+ drainQueue();
1495
1654
  } finally {
1496
- pendingQueue.length = 0;
1497
- pendingSet.clear();
1498
1655
  notifyDepth--;
1656
+ if (notifyDepth === 0) {
1657
+ pendingQueue.length = 0;
1658
+ pendingSet.clear();
1659
+ }
1499
1660
  }
1500
1661
  return;
1501
1662
  }
@@ -1513,57 +1674,45 @@ function notifySubscribers(signal2) {
1513
1674
  return;
1514
1675
  }
1515
1676
  notifyDepth++;
1677
+ drainEpoch++;
1516
1678
  try {
1517
- let directCount = 0;
1518
1679
  for (const sub2 of subs) {
1519
- pendingQueue[directCount++] = sub2;
1520
- }
1521
- for (let i3 = 0; i3 < directCount; i3++) {
1522
- if (pendingQueue[i3]._c) {
1523
- propagateDirty(pendingQueue[i3]);
1524
- }
1525
- }
1526
- for (let i3 = 0; i3 < directCount; i3++) {
1527
- if (!pendingQueue[i3]._c) {
1528
- if (!pendingSet.has(pendingQueue[i3])) {
1529
- safeInvoke(pendingQueue[i3]);
1530
- }
1680
+ if (sub2._c) {
1681
+ propagateDirty(sub2);
1682
+ } else if (!pendingSet.has(sub2)) {
1683
+ pendingSet.add(sub2);
1684
+ pendingQueue.push(sub2);
1531
1685
  }
1532
1686
  }
1533
- let i2 = directCount;
1534
- while (i2 < pendingQueue.length) {
1535
- safeInvoke(pendingQueue[i2]);
1536
- i2++;
1537
- }
1687
+ drainQueue();
1538
1688
  } finally {
1539
- pendingQueue.length = 0;
1540
- pendingSet.clear();
1541
1689
  notifyDepth--;
1690
+ if (notifyDepth === 0) {
1691
+ pendingQueue.length = 0;
1692
+ pendingSet.clear();
1693
+ }
1542
1694
  }
1543
1695
  }
1544
1696
  function cleanup(subscriber) {
1545
1697
  const sub2 = subscriber;
1546
1698
  const singleDep = sub2._dep;
1547
1699
  if (singleDep !== void 0) {
1548
- const subs = singleDep[SUBS];
1549
- if (subs) {
1550
- subs.delete(subscriber);
1551
- if (singleDep.__f === subscriber) {
1552
- singleDep.__f = void 0;
1553
- }
1700
+ const sig = singleDep;
1701
+ const subs = sig[SUBS];
1702
+ if (subs?.delete(subscriber)) {
1703
+ syncFastPath(sig, subs);
1554
1704
  }
1555
1705
  sub2._dep = void 0;
1706
+ sub2._depEpoch = void 0;
1556
1707
  return;
1557
1708
  }
1558
1709
  const deps = sub2._deps;
1559
1710
  if (!deps || deps.size === 0) return;
1560
- for (const signal2 of deps) {
1561
- const subs = signal2[SUBS];
1562
- if (subs) {
1563
- subs.delete(subscriber);
1564
- if (signal2.__f === subscriber) {
1565
- signal2.__f = void 0;
1566
- }
1711
+ for (const signal2 of deps.keys()) {
1712
+ const sig = signal2;
1713
+ const subs = sig[SUBS];
1714
+ if (subs?.delete(subscriber)) {
1715
+ syncFastPath(sig, subs);
1567
1716
  }
1568
1717
  }
1569
1718
  deps.clear();
@@ -1571,6 +1720,9 @@ function cleanup(subscriber) {
1571
1720
 
1572
1721
  // src/reactivity/bindAttribute.ts
1573
1722
  var _isDev3 = isDev();
1723
+ function setProp(el, key, val) {
1724
+ el[key] = val;
1725
+ }
1574
1726
  function isEventHandlerAttr(name) {
1575
1727
  if (name.length < 3) return false;
1576
1728
  const lower = name.toLowerCase();
@@ -1596,7 +1748,7 @@ function bindAttribute(el, attr, getter) {
1596
1748
  }
1597
1749
  if (typeof value === "boolean") {
1598
1750
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
1599
- el[attr] = value;
1751
+ setProp(el, attr, value);
1600
1752
  } else if (value) {
1601
1753
  el.setAttribute(attr, "");
1602
1754
  } else {
@@ -1606,7 +1758,7 @@ function bindAttribute(el, attr, getter) {
1606
1758
  }
1607
1759
  const str = String(value);
1608
1760
  if ((attr === "value" || attr === "checked") && attr in el) {
1609
- el[attr] = attr === "checked" ? Boolean(value) : str;
1761
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
1610
1762
  } else {
1611
1763
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
1612
1764
  }
@@ -1637,7 +1789,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
1637
1789
  }
1638
1790
  const str = String(value);
1639
1791
  if ((name === "value" || name === "checked") && name in el) {
1640
- el[name] = name === "checked" ? Boolean(value) : str;
1792
+ setProp(el, name, name === "checked" ? Boolean(value) : str);
1641
1793
  } else {
1642
1794
  el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
1643
1795
  }
@@ -1680,24 +1832,29 @@ function bindChildNode(placeholder, getter) {
1680
1832
  let newNodes;
1681
1833
  if (Array.isArray(result)) {
1682
1834
  newNodes = [];
1835
+ const seen = /* @__PURE__ */ new Set();
1683
1836
  for (let i2 = 0; i2 < result.length; i2++) {
1684
1837
  const item = result[i2];
1685
1838
  if (item == null || typeof item === "boolean") continue;
1686
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
1839
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
1840
+ if (seen.has(node)) {
1841
+ if (_isDev4)
1842
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
1843
+ continue;
1844
+ }
1845
+ seen.add(node);
1846
+ newNodes.push(node);
1687
1847
  }
1688
1848
  } else {
1689
1849
  const node = result instanceof Node ? result : document.createTextNode(String(result));
1690
1850
  newNodes = [node];
1691
1851
  }
1692
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
1693
- if (reused) {
1852
+ let reused;
1853
+ if (lastNodes.length > 0 && newNodes.length > 0) {
1854
+ const lastSet = new Set(lastNodes);
1855
+ reused = /* @__PURE__ */ new Set();
1694
1856
  for (let i2 = 0; i2 < newNodes.length; i2++) {
1695
- for (let j = 0; j < lastNodes.length; j++) {
1696
- if (newNodes[i2] === lastNodes[j]) {
1697
- reused.add(newNodes[i2]);
1698
- break;
1699
- }
1700
- }
1857
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
1701
1858
  }
1702
1859
  }
1703
1860
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -1740,7 +1897,7 @@ function dispose(node) {
1740
1897
  while (stack.length > 0) {
1741
1898
  const current = stack.pop();
1742
1899
  order.push(current);
1743
- const children = current.childNodes;
1900
+ const children = Array.from(current.childNodes);
1744
1901
  for (let i2 = 0; i2 < children.length; i2++) {
1745
1902
  stack.push(children[i2]);
1746
1903
  }
@@ -1749,8 +1906,10 @@ function dispose(node) {
1749
1906
  const current = order[i2];
1750
1907
  const disposers = elementDisposers.get(current);
1751
1908
  if (disposers) {
1752
- if (_isDev5) activeBindingCount -= disposers.length;
1753
- for (const d of disposers) {
1909
+ const snapshot = disposers.slice();
1910
+ elementDisposers.delete(current);
1911
+ if (_isDev5) activeBindingCount -= snapshot.length;
1912
+ for (const d of snapshot) {
1754
1913
  try {
1755
1914
  d();
1756
1915
  } catch (err) {
@@ -1759,7 +1918,23 @@ function dispose(node) {
1759
1918
  }
1760
1919
  }
1761
1920
  }
1762
- elementDisposers.delete(current);
1921
+ let extraPasses = 0;
1922
+ while (extraPasses++ < 8) {
1923
+ const added = elementDisposers.get(current);
1924
+ if (!added || added.length === 0) break;
1925
+ const moreSnapshot = added.slice();
1926
+ elementDisposers.delete(current);
1927
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
1928
+ for (const d of moreSnapshot) {
1929
+ try {
1930
+ d();
1931
+ } catch (err) {
1932
+ if (_isDev5 && typeof console !== "undefined") {
1933
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
1934
+ }
1935
+ }
1936
+ }
1937
+ }
1763
1938
  }
1764
1939
  }
1765
1940
  }
@@ -1775,6 +1950,30 @@ function checkLeaks(warnThreshold = 0) {
1775
1950
 
1776
1951
  // src/core/rendering/tagFactory.ts
1777
1952
  var SVG_NS = "http://www.w3.org/2000/svg";
1953
+ var _isDev6 = isDev();
1954
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
1955
+ function validateTagName(tag) {
1956
+ const lower = tag.toLowerCase();
1957
+ if (BLOCKED_TAGS.has(lower)) {
1958
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
1959
+ }
1960
+ }
1961
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
1962
+ "config",
1963
+ "location",
1964
+ "history",
1965
+ "document",
1966
+ "window",
1967
+ "navigator",
1968
+ "name",
1969
+ "top",
1970
+ "parent",
1971
+ "self",
1972
+ "frames"
1973
+ ]);
1974
+ function setProp2(el, key, val) {
1975
+ el[key] = val;
1976
+ }
1778
1977
  var kebabCache = /* @__PURE__ */ new Map();
1779
1978
  function toKebab(prop) {
1780
1979
  let cached = kebabCache.get(prop);
@@ -1899,79 +2098,103 @@ function appendChildren(el, nodes) {
1899
2098
  }
1900
2099
  }
1901
2100
  }
1902
- var tagFactory = (tag, ns) => (first, second) => {
1903
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1904
- if (first === void 0) return el;
1905
- if (typeof first === "string") {
1906
- if (second !== void 0) {
1907
- el.setAttribute("class", first);
1908
- appendChildren(el, second);
2101
+ var tagFactory = (tag, ns) => {
2102
+ return (first, second) => {
2103
+ validateTagName(tag);
2104
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
2105
+ if (first === void 0) return el;
2106
+ if (typeof first === "string") {
2107
+ if (second !== void 0) {
2108
+ el.setAttribute("class", first);
2109
+ appendChildren(el, second);
2110
+ return el;
2111
+ }
2112
+ el.textContent = first;
1909
2113
  return el;
1910
2114
  }
1911
- el.textContent = first;
1912
- return el;
1913
- }
1914
- if (typeof first === "number") {
1915
- el.textContent = String(first);
1916
- return el;
1917
- }
1918
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1919
- appendChildren(el, first);
1920
- return el;
1921
- }
1922
- const props = first;
1923
- const pClass = props.class;
1924
- if (pClass != null) applyClass(el, pClass);
1925
- const pId = props.id;
1926
- if (pId != null) el.id = pId;
1927
- const pNodes = second !== void 0 ? second : props.nodes;
1928
- if (pNodes != null) appendChildren(el, pNodes);
1929
- const pOn = props.on;
1930
- if (pOn) {
1931
- for (const ev in pOn) {
1932
- el.addEventListener(ev, pOn[ev]);
1933
- }
1934
- }
1935
- const pStyle = props.style;
1936
- if (pStyle != null) applyStyle(el, pStyle);
1937
- const pRef = props.ref;
1938
- if (pRef) pRef.current = el;
1939
- for (const key in props) {
1940
- switch (key) {
1941
- case "class":
1942
- case "id":
1943
- case "nodes":
1944
- case "on":
1945
- case "style":
1946
- case "ref":
1947
- case "onElement":
1948
- continue;
1949
- // already handled above / below
1950
- default: {
1951
- const value = props[key];
1952
- if (value == null) continue;
1953
- if (key[0] === "o" && key[1] === "n") continue;
1954
- if (typeof value === "function") {
1955
- registerDisposer(el, bindAttribute(el, key, value));
1956
- } else if (typeof value === "boolean") {
1957
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1958
- el[key] = value;
1959
- } else if (value) {
1960
- el.setAttribute(key, "");
2115
+ if (typeof first === "number") {
2116
+ el.textContent = String(first);
2117
+ return el;
2118
+ }
2119
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
2120
+ appendChildren(el, first);
2121
+ return el;
2122
+ }
2123
+ const props = first;
2124
+ const pClass = props.class;
2125
+ if (pClass != null) applyClass(el, pClass);
2126
+ const pId = props.id;
2127
+ if (pId != null) {
2128
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
2129
+ devWarn(
2130
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
2131
+ );
2132
+ }
2133
+ el.id = pId;
2134
+ }
2135
+ const pNodes = second !== void 0 ? second : props.nodes;
2136
+ if (pNodes != null) appendChildren(el, pNodes);
2137
+ const pOn = props.on;
2138
+ if (pOn) {
2139
+ for (const ev in pOn) {
2140
+ const handler = pOn[ev];
2141
+ if (typeof handler === "function") {
2142
+ el.addEventListener(ev, handler);
2143
+ } else if (_isDev6) {
2144
+ devWarn(
2145
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
2146
+ );
2147
+ }
2148
+ }
2149
+ }
2150
+ const pStyle = props.style;
2151
+ if (pStyle != null) applyStyle(el, pStyle);
2152
+ const pRef = props.ref;
2153
+ if (pRef) pRef.current = el;
2154
+ for (const key in props) {
2155
+ switch (key) {
2156
+ case "class":
2157
+ case "id":
2158
+ case "nodes":
2159
+ case "on":
2160
+ case "style":
2161
+ case "ref":
2162
+ case "onElement":
2163
+ continue;
2164
+ // already handled above / below
2165
+ default: {
2166
+ const value = props[key];
2167
+ if (value == null) continue;
2168
+ const lkey = key.toLowerCase();
2169
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
2170
+ if (typeof value === "function") {
2171
+ registerDisposer(el, bindAttribute(el, key, value));
2172
+ } else if (typeof value === "boolean") {
2173
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
2174
+ setProp2(el, key, value);
2175
+ } else if (value) {
2176
+ el.setAttribute(key, "");
2177
+ } else {
2178
+ el.removeAttribute(key);
2179
+ }
1961
2180
  } else {
1962
- el.removeAttribute(key);
2181
+ const str = String(value);
2182
+ if (lkey === "srcset") {
2183
+ el.setAttribute(key, sanitizeSrcset(str));
2184
+ } else if (isUrlAttribute(lkey)) {
2185
+ el.setAttribute(key, sanitizeUrl(str));
2186
+ } else {
2187
+ el.setAttribute(key, str);
2188
+ }
1963
2189
  }
1964
- } else {
1965
- const str = String(value);
1966
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
1967
2190
  }
1968
2191
  }
1969
2192
  }
1970
- }
1971
- if (props.onElement && typeof props.onElement === "function") {
1972
- props.onElement(el);
1973
- }
1974
- return el;
2193
+ if (props.onElement && typeof props.onElement === "function") {
2194
+ props.onElement(el);
2195
+ }
2196
+ return el;
2197
+ };
1975
2198
  };
1976
2199
 
1977
2200
  // src/core/rendering/html.ts
@@ -2114,6 +2337,8 @@ var marquee = tagFactory("marquee");
2114
2337
  var customElement = (tagName) => tagFactory(tagName);
2115
2338
 
2116
2339
  // src/core/rendering/htm.ts
2340
+ var _isDev7 = isDev();
2341
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
2117
2342
  var VOID_ELEMENTS3 = /* @__PURE__ */ new Set([
2118
2343
  "area",
2119
2344
  "base",
@@ -2298,6 +2523,15 @@ function parseTemplate(strings) {
2298
2523
  children.push({ t: 0, el: { tag, svg: SVG_TAGS2.has(tag), attrs, children: [] } });
2299
2524
  } else {
2300
2525
  const inner = parseChildren();
2526
+ if (RAW_TEXT_TAGS.has(tag.toLowerCase())) {
2527
+ for (let i2 = 0; i2 < inner.length; i2++) {
2528
+ if (inner[i2].t === 2) {
2529
+ throw new Error(
2530
+ `html: dynamic \${...} expressions are not allowed inside <${tag}> (raw-text context). Build the content separately and append it as a Node.`
2531
+ );
2532
+ }
2533
+ }
2534
+ }
2301
2535
  if (template2[pos] === "<" && pos + 1 < len && template2[pos + 1] === "/") {
2302
2536
  pos += 2;
2303
2537
  readTagName();
@@ -2324,27 +2558,50 @@ function executeElement(tmpl, values) {
2324
2558
  break;
2325
2559
  case 1: {
2326
2560
  const name = attr.name;
2327
- if (name[0] === "o" && name[1] === "n") break;
2561
+ const lname = name.toLowerCase();
2562
+ if (lname[0] === "o" && lname[1] === "n") break;
2328
2563
  const val = values[attr.idx];
2329
2564
  if (typeof val === "function") {
2330
2565
  registerDisposer(el, bindAttribute(el, name, val));
2331
2566
  } else if (val != null) {
2332
2567
  const str = String(val);
2333
- el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
2568
+ if (lname === "srcset") {
2569
+ el.setAttribute(name, sanitizeSrcset(str));
2570
+ } else if (isUrlAttribute(lname)) {
2571
+ el.setAttribute(name, sanitizeUrl(str));
2572
+ } else {
2573
+ el.setAttribute(name, str);
2574
+ }
2334
2575
  }
2335
2576
  break;
2336
2577
  }
2337
2578
  case 2: {
2338
2579
  let val = attr.statics[0];
2339
2580
  for (let j = 0; j < attr.exprs.length; j++) {
2340
- val += String(values[attr.exprs[j]]) + attr.statics[j + 1];
2581
+ const ev = values[attr.exprs[j]];
2582
+ val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
2583
+ }
2584
+ const lname2 = attr.name.toLowerCase();
2585
+ if (lname2 === "srcset") {
2586
+ el.setAttribute(attr.name, sanitizeSrcset(val));
2587
+ } else if (isUrlAttribute(lname2)) {
2588
+ el.setAttribute(attr.name, sanitizeUrl(val));
2589
+ } else {
2590
+ el.setAttribute(attr.name, val);
2341
2591
  }
2342
- el.setAttribute(attr.name, val);
2343
2592
  break;
2344
2593
  }
2345
- case 3:
2346
- el.addEventListener(attr.name, values[attr.idx]);
2594
+ case 3: {
2595
+ const fn = values[attr.idx];
2596
+ if (typeof fn === "function") {
2597
+ el.addEventListener(attr.name, fn);
2598
+ } else if (_isDev7) {
2599
+ devWarn(
2600
+ `html: on:${attr.name} handler is not a function (got ${typeof fn}). Event listener was not attached.`
2601
+ );
2602
+ }
2347
2603
  break;
2604
+ }
2348
2605
  case 4:
2349
2606
  el.setAttribute(attr.name, "");
2350
2607
  break;
@@ -2438,7 +2695,7 @@ function html2(strings, ...values) {
2438
2695
  function mount(component, container) {
2439
2696
  if (!container) {
2440
2697
  throw new Error(
2441
- "[Sibu] mount: container element not found. Make sure the DOM element exists before calling mount()."
2698
+ "[SibuJS mount] container element not found. Make sure the DOM element exists before calling mount()."
2442
2699
  );
2443
2700
  }
2444
2701
  devAssert(
@@ -2466,7 +2723,7 @@ function mount(component, container) {
2466
2723
  }
2467
2724
 
2468
2725
  // src/core/rendering/each.ts
2469
- var _isDev6 = isDev();
2726
+ var _isDev8 = isDev();
2470
2727
  function resolveNodeChild(child) {
2471
2728
  if (typeof child === "function") {
2472
2729
  return resolveNodeChild(child());
@@ -2561,17 +2818,31 @@ function each(getArray, render, options) {
2561
2818
  node = existing;
2562
2819
  } else {
2563
2820
  const itemKey = key;
2564
- const itemGetter = () => getArray()[keyIndexMap.get(itemKey)];
2821
+ const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
2565
2822
  const indexGetter = () => keyIndexMap.get(itemKey);
2566
2823
  try {
2567
2824
  node = resolveNodeChild(render(itemGetter, indexGetter));
2568
2825
  } catch (err) {
2569
- if (_isDev6) {
2826
+ if (_isDev8) {
2570
2827
  devWarn(
2571
2828
  `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
2572
2829
  );
2573
2830
  }
2574
2831
  node = document.createComment(`each:error:${i2}`);
2832
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2833
+ queueMicrotask(() => {
2834
+ try {
2835
+ const target = anchor.parentNode;
2836
+ if (target?.dispatchEvent) {
2837
+ target.dispatchEvent(
2838
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2839
+ );
2840
+ } else if (_isDev8) {
2841
+ devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ });
2575
2846
  }
2576
2847
  }
2577
2848
  workMap.set(key, node);
@@ -2640,7 +2911,8 @@ function each(getArray, render, options) {
2640
2911
  workMap = tmp;
2641
2912
  initialized = true;
2642
2913
  };
2643
- track(update);
2914
+ const untrack = track(update);
2915
+ registerDisposer(anchor, untrack);
2644
2916
  if (!initialized) {
2645
2917
  queueMicrotask(() => {
2646
2918
  if (!initialized && anchor.parentNode) {
@@ -2687,7 +2959,9 @@ function Portal(nodes, target) {
2687
2959
  const anchor = document.createComment("portal");
2688
2960
  const container = target || document.body;
2689
2961
  let portalContent = null;
2962
+ let disposed = false;
2690
2963
  queueMicrotask(() => {
2964
+ if (disposed) return;
2691
2965
  try {
2692
2966
  portalContent = nodes();
2693
2967
  container.appendChild(portalContent);
@@ -2695,9 +2969,22 @@ function Portal(nodes, target) {
2695
2969
  if (typeof console !== "undefined") {
2696
2970
  console.error("[Portal] Render error:", err);
2697
2971
  }
2972
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2973
+ queueMicrotask(() => {
2974
+ try {
2975
+ const target2 = anchor.parentNode;
2976
+ if (target2?.dispatchEvent) {
2977
+ target2.dispatchEvent(
2978
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
2979
+ );
2980
+ }
2981
+ } catch {
2982
+ }
2983
+ });
2698
2984
  }
2699
2985
  });
2700
2986
  registerDisposer(anchor, () => {
2987
+ disposed = true;
2701
2988
  if (portalContent) {
2702
2989
  dispose(portalContent);
2703
2990
  portalContent.remove();
@@ -2737,7 +3024,8 @@ function DynamicComponent(is) {
2737
3024
  }
2738
3025
  container.replaceChildren(el);
2739
3026
  }
2740
- track(render);
3027
+ const untrack = track(render);
3028
+ registerDisposer(container, untrack);
2741
3029
  return container;
2742
3030
  }
2743
3031
 
@@ -2827,11 +3115,16 @@ function KeepAlive(activeKey, cases, options) {
2827
3115
  const anchor = document.createComment("keep-alive");
2828
3116
  const cache2 = /* @__PURE__ */ new Map();
2829
3117
  const lruOrder = [];
2830
- const max = options?.max ?? 0;
3118
+ const max = options?.max ?? 10;
3119
+ if (max === 0 && isDev()) {
3120
+ devWarn("KeepAlive: unbounded cache (max: 0). Cached subtrees will never be evicted \u2014 set `max` to bound memory.");
3121
+ }
2831
3122
  let currentKey;
2832
3123
  let currentNode = null;
2833
3124
  let initialized = false;
3125
+ let disposed = false;
2834
3126
  const update = () => {
3127
+ if (disposed) return;
2835
3128
  const key = activeKey();
2836
3129
  const parent = anchor.parentNode;
2837
3130
  if (!parent) return;
@@ -2871,12 +3164,23 @@ function KeepAlive(activeKey, cases, options) {
2871
3164
  currentNode = node;
2872
3165
  initialized = true;
2873
3166
  };
2874
- track(update);
3167
+ const untrack = track(update);
2875
3168
  if (!initialized) {
2876
3169
  queueMicrotask(() => {
2877
3170
  if (!initialized && anchor.parentNode) update();
2878
3171
  });
2879
3172
  }
3173
+ registerDisposer(anchor, () => {
3174
+ disposed = true;
3175
+ untrack();
3176
+ for (const node of cache2.values()) {
3177
+ dispose(node);
3178
+ if (node.parentNode) node.parentNode.removeChild(node);
3179
+ }
3180
+ cache2.clear();
3181
+ lruOrder.length = 0;
3182
+ currentNode = null;
3183
+ });
2880
3184
  return anchor;
2881
3185
  }
2882
3186
 
@@ -3038,19 +3342,22 @@ function isBatching() {
3038
3342
  return batchDepth > 0;
3039
3343
  }
3040
3344
  function flushBatch() {
3041
- for (const signal2 of pendingSignals) {
3042
- queueSignalNotification(signal2);
3345
+ try {
3346
+ for (const signal2 of pendingSignals) {
3347
+ queueSignalNotification(signal2);
3348
+ }
3349
+ } finally {
3350
+ pendingSignals.clear();
3043
3351
  }
3044
- pendingSignals.clear();
3045
3352
  drainNotificationQueue();
3046
3353
  }
3047
3354
 
3048
3355
  // src/core/signals/signal.ts
3049
3356
  var _g = globalThis;
3050
- var _isDev7 = isDev();
3357
+ var _isDev9 = isDev();
3051
3358
  function signal(initial, options) {
3052
3359
  const state = { value: initial };
3053
- const debugName = _isDev7 ? options?.name : void 0;
3360
+ const debugName = _isDev9 ? options?.name : void 0;
3054
3361
  const equalsFn = options?.equals;
3055
3362
  if (debugName) {
3056
3363
  state.__name = debugName;
@@ -3064,7 +3371,7 @@ function signal(initial, options) {
3064
3371
  function set(next) {
3065
3372
  const newValue = typeof next === "function" ? next(state.value) : next;
3066
3373
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
3067
- if (_isDev7) {
3374
+ if (_isDev9) {
3068
3375
  const oldValue = state.value;
3069
3376
  state.value = newValue;
3070
3377
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -3076,7 +3383,7 @@ function signal(initial, options) {
3076
3383
  notifySubscribers(state);
3077
3384
  }
3078
3385
  }
3079
- if (_isDev7) {
3386
+ if (_isDev9) {
3080
3387
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3081
3388
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
3082
3389
  }
@@ -3084,23 +3391,59 @@ function signal(initial, options) {
3084
3391
  }
3085
3392
 
3086
3393
  // src/core/ssr-context.ts
3087
- var ssrMode = false;
3394
+ var als = null;
3395
+ try {
3396
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
3397
+ const req = Function("return typeof require==='function'?require:null")();
3398
+ if (req) {
3399
+ const mod = req("node:async_hooks");
3400
+ als = new mod.AsyncLocalStorage();
3401
+ }
3402
+ }
3403
+ } catch {
3404
+ als = null;
3405
+ }
3406
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
3407
+ function getSSRStore() {
3408
+ if (als) {
3409
+ const s2 = als.getStore();
3410
+ if (s2) return s2;
3411
+ }
3412
+ return fallbackStore;
3413
+ }
3088
3414
  function isSSR() {
3089
- return ssrMode;
3415
+ return getSSRStore().ssr;
3090
3416
  }
3091
3417
  function enableSSR() {
3092
- ssrMode = true;
3418
+ getSSRStore().ssr = true;
3093
3419
  }
3094
3420
  function disableSSR() {
3095
- ssrMode = false;
3421
+ getSSRStore().ssr = false;
3422
+ }
3423
+ function runInSSRContext(fn) {
3424
+ const store2 = { ssr: true, suspenseIdCounter: 0 };
3425
+ if (als) {
3426
+ return als.run(store2, fn);
3427
+ }
3428
+ const prevSSR = fallbackStore.ssr;
3429
+ const prevCounter = fallbackStore.suspenseIdCounter;
3430
+ fallbackStore.ssr = true;
3431
+ fallbackStore.suspenseIdCounter = 0;
3432
+ try {
3433
+ return fn();
3434
+ } finally {
3435
+ fallbackStore.ssr = prevSSR;
3436
+ fallbackStore.suspenseIdCounter = prevCounter;
3437
+ }
3096
3438
  }
3097
3439
  function withSSR(fn) {
3098
- const wasSSR = ssrMode;
3099
- enableSSR();
3440
+ const store2 = getSSRStore();
3441
+ const wasSSR = store2.ssr;
3442
+ store2.ssr = true;
3100
3443
  try {
3101
3444
  return fn();
3102
3445
  } finally {
3103
- if (!wasSSR) disableSSR();
3446
+ if (!wasSSR) store2.ssr = false;
3104
3447
  }
3105
3448
  }
3106
3449
 
@@ -3127,26 +3470,114 @@ function effect(effectFn, options) {
3127
3470
  if (isSSR()) return () => {
3128
3471
  };
3129
3472
  const onError = options?.onError;
3473
+ let userCleanups = [];
3474
+ const onCleanup2 = (fn) => {
3475
+ userCleanups.push(fn);
3476
+ };
3477
+ const runUserCleanups = () => {
3478
+ if (userCleanups.length === 0) return;
3479
+ const list = userCleanups;
3480
+ userCleanups = [];
3481
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
3482
+ try {
3483
+ list[i2]();
3484
+ } catch (err) {
3485
+ if (typeof console !== "undefined") {
3486
+ console.warn("[SibuJS effect] onCleanup threw:", err);
3487
+ }
3488
+ }
3489
+ }
3490
+ };
3491
+ const invokeBody = () => effectFn(onCleanup2);
3130
3492
  const wrappedFn = onError ? () => {
3131
3493
  try {
3132
- effectFn();
3494
+ invokeBody();
3133
3495
  } catch (err) {
3134
3496
  onError(err);
3135
3497
  }
3136
- } : effectFn;
3498
+ } : invokeBody;
3137
3499
  let cleanupHandle = () => {
3138
3500
  };
3501
+ let running = false;
3502
+ let rerunPending = false;
3503
+ const MAX_RERUNS = 100;
3139
3504
  const subscriber = () => {
3140
- cleanupHandle();
3141
- cleanupHandle = track(wrappedFn, subscriber);
3505
+ if (running) {
3506
+ rerunPending = true;
3507
+ return;
3508
+ }
3509
+ running = true;
3510
+ try {
3511
+ let reruns = 0;
3512
+ do {
3513
+ rerunPending = false;
3514
+ runUserCleanups();
3515
+ cleanupHandle();
3516
+ cleanupHandle = track(wrappedFn, subscriber);
3517
+ if (++reruns > MAX_RERUNS) {
3518
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
3519
+ console.error(
3520
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
3521
+ );
3522
+ }
3523
+ rerunPending = false;
3524
+ break;
3525
+ }
3526
+ } while (rerunPending);
3527
+ } finally {
3528
+ running = false;
3529
+ rerunPending = false;
3530
+ }
3142
3531
  };
3143
- cleanupHandle = track(wrappedFn, subscriber);
3532
+ running = true;
3533
+ try {
3534
+ let reruns = 0;
3535
+ do {
3536
+ rerunPending = false;
3537
+ runUserCleanups();
3538
+ cleanupHandle();
3539
+ cleanupHandle = track(wrappedFn, subscriber);
3540
+ if (++reruns > MAX_RERUNS) {
3541
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
3542
+ console.error(
3543
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times on initial run \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
3544
+ );
3545
+ }
3546
+ rerunPending = false;
3547
+ break;
3548
+ }
3549
+ } while (rerunPending);
3550
+ } finally {
3551
+ running = false;
3552
+ rerunPending = false;
3553
+ }
3144
3554
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3145
3555
  if (hook) hook.emit("effect:create", { effectFn });
3556
+ let disposed = false;
3146
3557
  return () => {
3558
+ if (disposed) return;
3559
+ disposed = true;
3147
3560
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3148
- if (h) h.emit("effect:destroy", { effectFn });
3149
- cleanupHandle();
3561
+ if (h) {
3562
+ try {
3563
+ h.emit("effect:destroy", { effectFn });
3564
+ } catch {
3565
+ }
3566
+ }
3567
+ try {
3568
+ runUserCleanups();
3569
+ } catch (err) {
3570
+ if (typeof console !== "undefined") {
3571
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
3572
+ }
3573
+ }
3574
+ try {
3575
+ cleanupHandle();
3576
+ } catch (err) {
3577
+ if (typeof console !== "undefined") {
3578
+ console.warn("[SibuJS effect] dispose threw:", err);
3579
+ }
3580
+ }
3150
3581
  };
3151
3582
  }
3152
3583
 
@@ -3154,6 +3585,7 @@ function effect(effectFn, options) {
3154
3585
  function derived(getter, options) {
3155
3586
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
3156
3587
  const debugName = options?.name;
3588
+ const equals = options?.equals;
3157
3589
  const cs = {};
3158
3590
  cs._d = false;
3159
3591
  cs._g = getter;
@@ -3164,8 +3596,14 @@ function derived(getter, options) {
3164
3596
  markDirty._c = 1;
3165
3597
  markDirty._sig = cs;
3166
3598
  track(() => {
3167
- cs._d = false;
3168
- cs._v = getter();
3599
+ let threw = true;
3600
+ try {
3601
+ cs._v = getter();
3602
+ cs._d = false;
3603
+ threw = false;
3604
+ } finally {
3605
+ if (threw) cs._d = true;
3606
+ }
3169
3607
  }, markDirty);
3170
3608
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
3171
3609
  let evaluating = false;
@@ -3178,11 +3616,16 @@ function derived(getter, options) {
3178
3616
  if (trackingSuspended) {
3179
3617
  if (cs._d) {
3180
3618
  evaluating = true;
3619
+ let threw = true;
3181
3620
  try {
3182
- cs._d = false;
3183
- cs._v = getter();
3621
+ retrack(() => {
3622
+ cs._v = getter();
3623
+ cs._d = false;
3624
+ threw = false;
3625
+ }, markDirty);
3184
3626
  } finally {
3185
3627
  evaluating = false;
3628
+ if (threw) cs._d = true;
3186
3629
  }
3187
3630
  }
3188
3631
  return cs._v;
@@ -3191,13 +3634,17 @@ function derived(getter, options) {
3191
3634
  if (cs._d) {
3192
3635
  const oldValue = cs._v;
3193
3636
  evaluating = true;
3637
+ let threw = true;
3194
3638
  try {
3195
- track(() => {
3639
+ retrack(() => {
3640
+ const next = getter();
3641
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
3196
3642
  cs._d = false;
3197
- cs._v = getter();
3643
+ threw = false;
3198
3644
  }, markDirty);
3199
3645
  } finally {
3200
3646
  evaluating = false;
3647
+ if (threw) cs._d = true;
3201
3648
  }
3202
3649
  if (hook && oldValue !== cs._v) {
3203
3650
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -3259,7 +3706,7 @@ function store(initialState) {
3259
3706
  },
3260
3707
  set() {
3261
3708
  throw new Error(
3262
- "[Sibu] store: Direct mutation is not allowed. Use actions.setState() to update store properties."
3709
+ "[SibuJS store] Direct mutation is not allowed. Use actions.setState() to update store properties."
3263
3710
  );
3264
3711
  }
3265
3712
  });
@@ -3273,16 +3720,20 @@ function store(initialState) {
3273
3720
  const setState = (patch) => {
3274
3721
  const current = getSnapshot();
3275
3722
  const nextState = typeof patch === "function" ? patch(current) : patch;
3276
- Object.entries(nextState).forEach(([key, value]) => {
3277
- if (key in signals) {
3278
- signals[key][1](value);
3279
- }
3723
+ batch(() => {
3724
+ Object.entries(nextState).forEach(([key, value]) => {
3725
+ if (key in signals) {
3726
+ signals[key][1](value);
3727
+ }
3728
+ });
3280
3729
  });
3281
3730
  };
3282
3731
  const reset = () => {
3283
- Object.keys(initialState).forEach((key) => {
3284
- const setter = signals[key][1];
3285
- setter(initialState[key]);
3732
+ batch(() => {
3733
+ Object.keys(initialState).forEach((key) => {
3734
+ const setter = signals[key][1];
3735
+ setter(initialState[key]);
3736
+ });
3286
3737
  });
3287
3738
  };
3288
3739
  const subscribe = (callback) => {
@@ -3416,7 +3867,8 @@ function reactiveArray(initial = []) {
3416
3867
  function get() {
3417
3868
  recordDependency(signal2);
3418
3869
  if (snapshot === null) {
3419
- snapshot = Object.freeze([...data2]);
3870
+ const copy = data2.slice();
3871
+ snapshot = Object.freeze(copy);
3420
3872
  }
3421
3873
  return snapshot;
3422
3874
  }
@@ -3612,26 +4064,34 @@ function asyncDerived(factory, initial) {
3612
4064
  effect(() => {
3613
4065
  tick();
3614
4066
  const currentRun = ++runId;
3615
- setLoading(true);
3616
- setError(null);
4067
+ batch(() => {
4068
+ setLoading(true);
4069
+ setError(null);
4070
+ });
3617
4071
  let promise;
3618
4072
  try {
3619
4073
  promise = factory();
3620
4074
  } catch (err) {
3621
- setError(err);
3622
- setLoading(false);
4075
+ batch(() => {
4076
+ setError(err);
4077
+ setLoading(false);
4078
+ });
3623
4079
  return;
3624
4080
  }
3625
4081
  promise.then(
3626
4082
  (result) => {
3627
4083
  if (currentRun !== runId) return;
3628
- setValue(result);
3629
- setLoading(false);
4084
+ batch(() => {
4085
+ setValue(result);
4086
+ setLoading(false);
4087
+ });
3630
4088
  },
3631
4089
  (err) => {
3632
4090
  if (currentRun !== runId) return;
3633
- setError(err);
3634
- setLoading(false);
4091
+ batch(() => {
4092
+ setError(err);
4093
+ setLoading(false);
4094
+ });
3635
4095
  }
3636
4096
  );
3637
4097
  });
@@ -3658,26 +4118,186 @@ function runMountCallback(callback, hookName, element) {
3658
4118
  registerDisposer(element, cleanup2);
3659
4119
  }
3660
4120
  }
4121
+ var mountWatchers = /* @__PURE__ */ new WeakMap();
4122
+ var unmountWatchers = /* @__PURE__ */ new WeakMap();
4123
+ var watchedMountElements = /* @__PURE__ */ new Set();
4124
+ var watchedUnmountElements = /* @__PURE__ */ new Set();
4125
+ var sharedObserver = null;
4126
+ var mutationCounter = 0;
4127
+ var FULL_SWEEP_INTERVAL = 256;
4128
+ function fireMount(el) {
4129
+ const cbs = mountWatchers.get(el);
4130
+ if (!cbs) return;
4131
+ mountWatchers.delete(el);
4132
+ watchedMountElements.delete(el);
4133
+ for (const cb of cbs) {
4134
+ try {
4135
+ cb();
4136
+ } catch {
4137
+ }
4138
+ }
4139
+ }
4140
+ function fireUnmount(el) {
4141
+ const cbs = unmountWatchers.get(el);
4142
+ if (!cbs) return;
4143
+ queueMicrotask(() => {
4144
+ if (el.isConnected) return;
4145
+ const stillCbs = unmountWatchers.get(el);
4146
+ if (!stillCbs) return;
4147
+ unmountWatchers.delete(el);
4148
+ watchedUnmountElements.delete(el);
4149
+ for (const cb of stillCbs) {
4150
+ try {
4151
+ cb();
4152
+ } catch {
4153
+ }
4154
+ }
4155
+ });
4156
+ }
4157
+ function visitAddedNode(node) {
4158
+ if (watchedMountElements.size === 0) return;
4159
+ if (node.nodeType !== 1) return;
4160
+ const el = node;
4161
+ if (watchedMountElements.has(el) && el.isConnected) {
4162
+ fireMount(el);
4163
+ }
4164
+ if (el.firstElementChild) {
4165
+ for (const watched of Array.from(watchedMountElements)) {
4166
+ if (watched !== el && watched.isConnected && el.contains(watched)) {
4167
+ fireMount(watched);
4168
+ }
4169
+ }
4170
+ }
4171
+ }
4172
+ function visitRemovedNode(node) {
4173
+ if (watchedUnmountElements.size === 0) return;
4174
+ if (node.nodeType !== 1) return;
4175
+ const el = node;
4176
+ if (watchedUnmountElements.has(el) && !el.isConnected) {
4177
+ fireUnmount(el);
4178
+ }
4179
+ if (el.firstElementChild) {
4180
+ for (const watched of Array.from(watchedUnmountElements)) {
4181
+ if (watched !== el && !watched.isConnected && el.contains(watched)) {
4182
+ fireUnmount(watched);
4183
+ }
4184
+ }
4185
+ }
4186
+ }
4187
+ function fullSweep() {
4188
+ if (watchedMountElements.size > 0) {
4189
+ for (const el of Array.from(watchedMountElements)) {
4190
+ if (el.isConnected) fireMount(el);
4191
+ }
4192
+ }
4193
+ if (watchedUnmountElements.size > 0) {
4194
+ for (const el of Array.from(watchedUnmountElements)) {
4195
+ if (!el.isConnected) fireUnmount(el);
4196
+ }
4197
+ }
4198
+ }
4199
+ function ensureObserver() {
4200
+ if (sharedObserver || typeof document === "undefined") return;
4201
+ sharedObserver = new MutationObserver((mutations) => {
4202
+ for (const m of mutations) {
4203
+ if (m.type !== "childList") continue;
4204
+ if (m.addedNodes.length > 0) {
4205
+ for (let i2 = 0; i2 < m.addedNodes.length; i2++) {
4206
+ visitAddedNode(m.addedNodes[i2]);
4207
+ }
4208
+ }
4209
+ if (m.removedNodes.length > 0) {
4210
+ for (let i2 = 0; i2 < m.removedNodes.length; i2++) {
4211
+ visitRemovedNode(m.removedNodes[i2]);
4212
+ }
4213
+ }
4214
+ }
4215
+ mutationCounter += mutations.length;
4216
+ if (mutationCounter >= FULL_SWEEP_INTERVAL) {
4217
+ mutationCounter = 0;
4218
+ fullSweep();
4219
+ }
4220
+ maybeDisconnectObserver();
4221
+ });
4222
+ sharedObserver.observe(document.body, { childList: true, subtree: true });
4223
+ }
4224
+ function maybeDisconnectObserver() {
4225
+ if (!sharedObserver) return;
4226
+ if (watchedMountElements.size === 0 && watchedUnmountElements.size === 0) {
4227
+ sharedObserver.disconnect();
4228
+ sharedObserver = null;
4229
+ mutationCounter = 0;
4230
+ }
4231
+ }
4232
+ function registerMountWatcher(element, cb) {
4233
+ let list = mountWatchers.get(element);
4234
+ if (!list) {
4235
+ list = [];
4236
+ mountWatchers.set(element, list);
4237
+ }
4238
+ list.push(cb);
4239
+ watchedMountElements.add(element);
4240
+ ensureObserver();
4241
+ return () => {
4242
+ const cbs = mountWatchers.get(element);
4243
+ if (cbs) {
4244
+ const idx = cbs.indexOf(cb);
4245
+ if (idx !== -1) cbs.splice(idx, 1);
4246
+ if (cbs.length === 0) {
4247
+ mountWatchers.delete(element);
4248
+ watchedMountElements.delete(element);
4249
+ }
4250
+ }
4251
+ maybeDisconnectObserver();
4252
+ };
4253
+ }
4254
+ function registerUnmountWatcher(element, cb) {
4255
+ let list = unmountWatchers.get(element);
4256
+ if (!list) {
4257
+ list = [];
4258
+ unmountWatchers.set(element, list);
4259
+ }
4260
+ list.push(cb);
4261
+ watchedUnmountElements.add(element);
4262
+ ensureObserver();
4263
+ return () => {
4264
+ const cbs = unmountWatchers.get(element);
4265
+ if (cbs) {
4266
+ const idx = cbs.indexOf(cb);
4267
+ if (idx !== -1) cbs.splice(idx, 1);
4268
+ if (cbs.length === 0) {
4269
+ unmountWatchers.delete(element);
4270
+ watchedUnmountElements.delete(element);
4271
+ }
4272
+ }
4273
+ maybeDisconnectObserver();
4274
+ };
4275
+ }
3661
4276
  function onMount(callback, element) {
3662
4277
  if (typeof document === "undefined") return;
3663
4278
  if (element) {
4279
+ let disposed = false;
4280
+ registerDisposer(element, () => {
4281
+ disposed = true;
4282
+ });
3664
4283
  if (element.isConnected) {
3665
- queueMicrotask(() => runMountCallback(callback, "onMount", element));
4284
+ queueMicrotask(() => {
4285
+ if (disposed) return;
4286
+ runMountCallback(callback, "onMount", element);
4287
+ });
3666
4288
  return;
3667
4289
  }
3668
- const observer = new MutationObserver(() => {
3669
- if (element.isConnected) {
3670
- observer.disconnect();
3671
- runMountCallback(callback, "onMount", element);
3672
- }
3673
- });
3674
- registerDisposer(element, () => observer.disconnect());
3675
4290
  queueMicrotask(() => {
4291
+ if (disposed) return;
3676
4292
  if (element.isConnected) {
3677
4293
  runMountCallback(callback, "onMount", element);
3678
- } else {
3679
- observer.observe(document.body, { childList: true, subtree: true });
4294
+ return;
3680
4295
  }
4296
+ const unregister = registerMountWatcher(element, () => {
4297
+ if (disposed) return;
4298
+ runMountCallback(callback, "onMount", element);
4299
+ });
4300
+ registerDisposer(element, unregister);
3681
4301
  });
3682
4302
  } else {
3683
4303
  queueMicrotask(() => {
@@ -3686,22 +4306,24 @@ function onMount(callback, element) {
3686
4306
  }
3687
4307
  }
3688
4308
  function onUnmount(callback, element) {
3689
- registerDisposer(element, () => safeCall(callback, "onUnmount"));
3690
- const startObserving = () => {
3691
- const observer = new MutationObserver(() => {
3692
- if (!element.isConnected) {
3693
- observer.disconnect();
3694
- safeCall(callback, "onUnmount");
3695
- }
3696
- });
3697
- observer.observe(document.body, { childList: true, subtree: true });
3698
- registerDisposer(element, () => observer.disconnect());
4309
+ if (typeof document === "undefined") return;
4310
+ let fired = false;
4311
+ const fireOnce = () => {
4312
+ if (fired) return;
4313
+ fired = true;
4314
+ safeCall(callback, "onUnmount");
4315
+ };
4316
+ registerDisposer(element, fireOnce);
4317
+ const startWatching = () => {
4318
+ if (fired) return;
4319
+ const unregister = registerUnmountWatcher(element, fireOnce);
4320
+ registerDisposer(element, unregister);
3699
4321
  };
3700
4322
  if (element.isConnected) {
3701
- startObserving();
4323
+ startWatching();
3702
4324
  } else {
3703
4325
  onMount(() => {
3704
- startObserving();
4326
+ startWatching();
3705
4327
  return void 0;
3706
4328
  }, element);
3707
4329
  }
@@ -3713,9 +4335,11 @@ function onCleanup(callback, element) {
3713
4335
  // src/core/rendering/context.ts
3714
4336
  function context(defaultValue) {
3715
4337
  const [getValue, setValue] = signal(defaultValue);
3716
- return {
4338
+ const ctx = {
3717
4339
  provide(value) {
4340
+ const previous = getValue();
3718
4341
  setValue(value);
4342
+ return () => setValue(previous);
3719
4343
  },
3720
4344
  use() {
3721
4345
  return getValue;
@@ -3725,8 +4349,18 @@ function context(defaultValue) {
3725
4349
  },
3726
4350
  set(value) {
3727
4351
  setValue(value);
4352
+ },
4353
+ withContext(value, fn) {
4354
+ const previous = getValue();
4355
+ setValue(value);
4356
+ try {
4357
+ return fn();
4358
+ } finally {
4359
+ setValue(previous);
4360
+ }
3728
4361
  }
3729
4362
  };
4363
+ return ctx;
3730
4364
  }
3731
4365
 
3732
4366
  // src/core/strict.ts
@@ -3737,7 +4371,7 @@ function strict(fn) {
3737
4371
  try {
3738
4372
  fn();
3739
4373
  } catch (err) {
3740
- console.warn("[Sibu strict] second run threw:", err);
4374
+ console.warn("[SibuJS strict] second run threw:", err);
3741
4375
  }
3742
4376
  });
3743
4377
  }
@@ -3753,7 +4387,7 @@ function strictEffect(fn) {
3753
4387
  try {
3754
4388
  secondTeardown = effect(fn);
3755
4389
  } catch (err) {
3756
- console.warn("[Sibu strictEffect] second run threw:", err);
4390
+ console.warn("[SibuJS strictEffect] second run threw:", err);
3757
4391
  }
3758
4392
  });
3759
4393
  return () => {
@@ -3779,9 +4413,11 @@ function nextTick() {
3779
4413
  function defer(getter) {
3780
4414
  const [value, setValue] = signal(getter());
3781
4415
  let pending = false;
4416
+ let disposed = false;
3782
4417
  let latest = value();
3783
4418
  const flush = () => {
3784
4419
  pending = false;
4420
+ if (disposed) return;
3785
4421
  setValue(latest);
3786
4422
  };
3787
4423
  const schedule = () => {
@@ -3795,11 +4431,17 @@ function defer(getter) {
3795
4431
  }
3796
4432
  });
3797
4433
  };
3798
- track(() => {
4434
+ const teardown = track(() => {
3799
4435
  latest = getter();
3800
4436
  schedule();
3801
4437
  });
3802
- return value;
4438
+ const accessor = (() => value());
4439
+ accessor.dispose = () => {
4440
+ if (disposed) return;
4441
+ disposed = true;
4442
+ teardown();
4443
+ };
4444
+ return accessor;
3803
4445
  }
3804
4446
  var IDLE_FALLBACK_MS = 16;
3805
4447
  function scheduleIdle(fn) {
@@ -3840,6 +4482,32 @@ function transition() {
3840
4482
  }
3841
4483
 
3842
4484
  // src/core/rendering/lazy.ts
4485
+ var PENDING_ERROR = "__sibuPendingError";
4486
+ function dispatchPropagate(node, error) {
4487
+ const fire = () => {
4488
+ try {
4489
+ if (!node.parentNode) return false;
4490
+ node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
4491
+ return true;
4492
+ } catch {
4493
+ return false;
4494
+ }
4495
+ };
4496
+ if (node.parentNode && fire()) return;
4497
+ queueMicrotask(() => {
4498
+ if (fire()) return;
4499
+ node[PENDING_ERROR] = error;
4500
+ });
4501
+ }
4502
+ function takePendingError(node) {
4503
+ const rec = node;
4504
+ const err = rec[PENDING_ERROR];
4505
+ if (err instanceof Error) {
4506
+ delete rec[PENDING_ERROR];
4507
+ return err;
4508
+ }
4509
+ return void 0;
4510
+ }
3843
4511
  function lazy(importFn) {
3844
4512
  let cached = null;
3845
4513
  return function LazyComponent() {
@@ -3856,14 +4524,14 @@ function lazy(importFn) {
3856
4524
  }).catch((err) => {
3857
4525
  if (disposed) return;
3858
4526
  const errorObj = err instanceof Error ? err : new Error(String(err));
3859
- container.replaceChildren(div("sibu-lazy-error", `Failed to load component: ${errorObj.message}`));
4527
+ devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
4528
+ container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
4529
+ dispatchPropagate(container, errorObj);
3860
4530
  });
3861
4531
  container.appendChild(span("sibu-lazy-loading", "Loading..."));
3862
- const origRemove = container.remove.bind(container);
3863
- container.remove = () => {
4532
+ registerDisposer(container, () => {
3864
4533
  disposed = true;
3865
- origRemove();
3866
- };
4534
+ });
3867
4535
  return container;
3868
4536
  };
3869
4537
  }
@@ -3871,32 +4539,55 @@ function Suspense({ nodes, fallback }) {
3871
4539
  const container = div({ class: "sibu-suspense" });
3872
4540
  const fallbackEl = fallback();
3873
4541
  container.appendChild(fallbackEl);
4542
+ let suspenseDisposed = false;
4543
+ let observer = null;
4544
+ registerDisposer(container, () => {
4545
+ suspenseDisposed = true;
4546
+ if (observer) {
4547
+ observer.disconnect();
4548
+ observer = null;
4549
+ }
4550
+ });
3874
4551
  queueMicrotask(() => {
4552
+ if (suspenseDisposed) return;
3875
4553
  try {
3876
4554
  const childEl = nodes();
3877
4555
  if (childEl.classList.contains("sibu-lazy")) {
3878
- const observer = new MutationObserver(() => {
4556
+ if (!childEl.querySelector(".sibu-lazy-loading")) {
4557
+ container.replaceChildren(childEl);
4558
+ return;
4559
+ }
4560
+ observer = new MutationObserver(() => {
4561
+ if (suspenseDisposed) return;
3879
4562
  const loading = childEl.querySelector(".sibu-lazy-loading");
3880
4563
  if (!loading) {
3881
- observer.disconnect();
4564
+ observer?.disconnect();
4565
+ observer = null;
3882
4566
  container.replaceChildren(childEl);
3883
4567
  }
3884
4568
  });
3885
4569
  observer.observe(childEl, { childList: true, subtree: true });
3886
- if (!childEl.querySelector(".sibu-lazy-loading")) {
3887
- container.replaceChildren(childEl);
3888
- }
3889
4570
  } else {
3890
4571
  container.replaceChildren(childEl);
3891
4572
  }
3892
- } catch {
4573
+ } catch (err) {
4574
+ const errorObj = err instanceof Error ? err : new Error(String(err));
4575
+ devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
4576
+ dispatchPropagate(container, errorObj);
3893
4577
  }
3894
4578
  });
3895
4579
  return container;
3896
4580
  }
3897
4581
 
4582
+ // src/platform/ssr.ts
4583
+ var _isDev10 = isDev();
4584
+ function trustHTML(html3) {
4585
+ return html3;
4586
+ }
4587
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
4588
+
3898
4589
  // src/components/ErrorDisplay.ts
3899
- var _isDev8 = isDev();
4590
+ var _isDev11 = isDev();
3900
4591
  var STYLES = `
3901
4592
  .sibu-error-display {
3902
4593
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -3982,20 +4673,21 @@ var STYLES = `
3982
4673
  font-weight: 600;
3983
4674
  }
3984
4675
  .sibu-error-display .sibu-err-copy-btn {
3985
- background: transparent;
3986
- border: 1px solid #3a3a4e;
4676
+ background: rgba(0, 0, 0, 0.22);
4677
+ border: 1px solid rgba(255, 255, 255, 0.15);
3987
4678
  border-radius: 4px;
3988
- color: #a0a3b8;
4679
+ color: rgba(255, 255, 255, 0.85);
3989
4680
  cursor: pointer;
3990
4681
  padding: 2px 10px;
3991
- font-size: 0.95em;
4682
+ font-size: 0.78em;
3992
4683
  font-family: inherit;
3993
4684
  transition: all 0.12s ease;
4685
+ flex-shrink: 0;
3994
4686
  }
3995
4687
  .sibu-error-display .sibu-err-copy-btn:hover {
3996
- background: #2a2a3e;
3997
- color: #e5e7eb;
3998
- border-color: #4a4a5e;
4688
+ background: rgba(0, 0, 0, 0.35);
4689
+ color: white;
4690
+ border-color: rgba(255, 255, 255, 0.3);
3999
4691
  }
4000
4692
 
4001
4693
  .sibu-error-display .sibu-err-stack {
@@ -4120,21 +4812,25 @@ function normalizeError(err) {
4120
4812
  cause: null
4121
4813
  };
4122
4814
  }
4123
- function buildCopyText(err, meta2) {
4815
+ function buildCopyText(err, meta2, headline) {
4124
4816
  const lines = [];
4817
+ lines.push(headline);
4125
4818
  lines.push(`[${err.code}] ${err.message}`);
4126
4819
  if (err.stack) {
4127
4820
  lines.push("");
4821
+ lines.push("Stack Trace:");
4128
4822
  lines.push(err.stack);
4129
4823
  }
4130
- if (err.cause) {
4824
+ let cause = err.cause;
4825
+ while (cause) {
4131
4826
  lines.push("");
4132
4827
  lines.push("Caused by:");
4133
- lines.push(` [${err.cause.code}] ${err.cause.message}`);
4134
- if (err.cause.stack) {
4135
- const indented = err.cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4828
+ lines.push(` [${cause.code}] ${cause.message}`);
4829
+ if (cause.stack) {
4830
+ const indented = cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
4136
4831
  lines.push(indented);
4137
4832
  }
4833
+ cause = cause.cause;
4138
4834
  }
4139
4835
  if (meta2 && Object.keys(meta2).length > 0) {
4140
4836
  lines.push("");
@@ -4144,9 +4840,13 @@ function buildCopyText(err, meta2) {
4144
4840
  }
4145
4841
  }
4146
4842
  lines.push("");
4147
- lines.push(`At: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4843
+ lines.push("Environment:");
4844
+ lines.push(` Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4845
+ if (typeof location !== "undefined") {
4846
+ lines.push(` URL: ${location.href}`);
4847
+ }
4148
4848
  if (typeof navigator !== "undefined" && navigator.userAgent) {
4149
- lines.push(`UA: ${navigator.userAgent}`);
4849
+ lines.push(` User Agent: ${navigator.userAgent}`);
4150
4850
  }
4151
4851
  return lines.join("\n");
4152
4852
  }
@@ -4198,7 +4898,7 @@ function ErrorDisplay(props) {
4198
4898
  injectStyles();
4199
4899
  const severity = props.severity ?? "error";
4200
4900
  const normalized = normalizeError(props.error);
4201
- const showDetails = props.alwaysShowDetails ?? _isDev8;
4901
+ const showDetails = props.alwaysShowDetails ?? _isDev11;
4202
4902
  const headline = props.title ?? normalized.message;
4203
4903
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
4204
4904
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -4207,7 +4907,7 @@ function ErrorDisplay(props) {
4207
4907
  nodes: () => copyLabel(),
4208
4908
  on: {
4209
4909
  click: () => {
4210
- const text2 = buildCopyText(normalized, props.metadata);
4910
+ const text2 = buildCopyText(normalized, props.metadata, headline);
4211
4911
  if (typeof navigator !== "undefined" && navigator.clipboard) {
4212
4912
  navigator.clipboard.writeText(text2).then(
4213
4913
  () => {
@@ -4228,6 +4928,7 @@ function ErrorDisplay(props) {
4228
4928
  nodes: [
4229
4929
  code({ class: "sibu-err-icon", nodes: normalized.code }),
4230
4930
  h3({ class: "sibu-err-title", nodes: headline }),
4931
+ copyBtn,
4231
4932
  span({ class: "sibu-err-timestamp", nodes: timestamp })
4232
4933
  ]
4233
4934
  });
@@ -4239,25 +4940,12 @@ function ErrorDisplay(props) {
4239
4940
  nodes: [
4240
4941
  div({
4241
4942
  class: "sibu-err-section-head",
4242
- nodes: [span({ nodes: "Stack Trace" }), copyBtn]
4943
+ nodes: [span({ nodes: "Stack Trace" })]
4243
4944
  }),
4244
4945
  renderFrames(normalized.frames)
4245
4946
  ]
4246
4947
  })
4247
4948
  );
4248
- } else if (showDetails) {
4249
- bodyChildren.push(
4250
- div({
4251
- class: "sibu-err-section",
4252
- nodes: [
4253
- div({
4254
- class: "sibu-err-section-head",
4255
- nodes: [span({ nodes: "Details" }), copyBtn]
4256
- }),
4257
- div({ class: "sibu-err-stack", nodes: "(no stack available)" })
4258
- ]
4259
- })
4260
- );
4261
4949
  }
4262
4950
  if (showDetails) {
4263
4951
  bodyChildren.push(...renderCauseChain(normalized.cause));
@@ -4273,37 +4961,6 @@ function ErrorDisplay(props) {
4273
4961
  })
4274
4962
  );
4275
4963
  }
4276
- if (showDetails && typeof navigator !== "undefined" && navigator.userAgent) {
4277
- bodyChildren.push(
4278
- div({
4279
- class: "sibu-err-section",
4280
- nodes: [
4281
- div({ class: "sibu-err-section-head", nodes: [span({ nodes: "Environment" })] }),
4282
- div({
4283
- class: "sibu-err-meta",
4284
- nodes: (() => {
4285
- const dl2 = document.createElement("dl");
4286
- dl2.className = "sibu-err-meta";
4287
- const entries = [
4288
- ["User Agent", navigator.userAgent],
4289
- ["URL", typeof location !== "undefined" ? location.href : "(n/a)"],
4290
- ["Timestamp", (/* @__PURE__ */ new Date()).toISOString()]
4291
- ];
4292
- for (const [k, v] of entries) {
4293
- const dt2 = document.createElement("dt");
4294
- dt2.textContent = k;
4295
- const dd2 = document.createElement("dd");
4296
- dd2.textContent = v;
4297
- dl2.appendChild(dt2);
4298
- dl2.appendChild(dd2);
4299
- }
4300
- return dl2;
4301
- })()
4302
- })
4303
- ]
4304
- })
4305
- );
4306
- }
4307
4964
  const actionButtons = [];
4308
4965
  if (props.onRetry) {
4309
4966
  actionButtons.push(
@@ -4498,6 +5155,7 @@ function injectStyles2() {
4498
5155
  stylesInjected = true;
4499
5156
  }
4500
5157
  }
5158
+ var FALLBACK_CACHE_MAX = 50;
4501
5159
  var fallbackCache = /* @__PURE__ */ new WeakMap();
4502
5160
  function getMemoizedFallback(fallbackFn, error, retry) {
4503
5161
  let cache2 = fallbackCache.get(fallbackFn);
@@ -4506,27 +5164,42 @@ function getMemoizedFallback(fallbackFn, error, retry) {
4506
5164
  fallbackCache.set(fallbackFn, cache2);
4507
5165
  }
4508
5166
  const key = error.message;
4509
- if (!cache2.has(key)) {
4510
- cache2.set(key, fallbackFn(error, retry));
5167
+ let factory = cache2.get(key);
5168
+ if (factory) {
5169
+ cache2.delete(key);
5170
+ cache2.set(key, factory);
5171
+ } else {
5172
+ factory = () => fallbackFn(error, retry);
5173
+ cache2.set(key, factory);
5174
+ if (cache2.size > FALLBACK_CACHE_MAX) {
5175
+ const oldestKey = cache2.keys().next().value;
5176
+ if (oldestKey !== void 0) cache2.delete(oldestKey);
5177
+ }
4511
5178
  }
4512
- return cache2.get(key);
5179
+ return factory();
4513
5180
  }
4514
5181
  function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4515
5182
  injectStyles2();
4516
5183
  const [error, setError] = signal(null);
4517
5184
  const retry = () => {
4518
5185
  if (fallback) {
4519
- fallbackCache.delete(fallback);
5186
+ const cur = error();
5187
+ const inner = fallbackCache.get(fallback);
5188
+ if (cur && inner) inner.delete(cur.message);
4520
5189
  }
4521
5190
  setError(null);
4522
5191
  };
5192
+ let resetKeysTeardown = null;
4523
5193
  if (resetKeys && resetKeys.length > 0) {
4524
5194
  let initialized = false;
4525
- effect(() => {
5195
+ resetKeysTeardown = effect(() => {
4526
5196
  for (const k of resetKeys) {
4527
5197
  try {
4528
5198
  k();
4529
- } catch {
5199
+ } catch (err) {
5200
+ if (typeof console !== "undefined") {
5201
+ console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
5202
+ }
4530
5203
  }
4531
5204
  }
4532
5205
  if (!initialized) {
@@ -4539,7 +5212,15 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4539
5212
  const handleError = (e) => {
4540
5213
  const errorObj = e instanceof Error ? e : new Error(String(e));
4541
5214
  setError(errorObj);
4542
- onError?.(errorObj);
5215
+ if (onError) {
5216
+ try {
5217
+ onError(errorObj);
5218
+ } catch (cbErr) {
5219
+ if (typeof console !== "undefined") {
5220
+ console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
5221
+ }
5222
+ }
5223
+ }
4543
5224
  return errorObj;
4544
5225
  };
4545
5226
  const defaultFallback = (err, retryFn) => {
@@ -4591,7 +5272,7 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4591
5272
  }
4592
5273
  }
4593
5274
  });
4594
- container.addEventListener("sibu:error-propagate", (e) => {
5275
+ const propagateListener = (e) => {
4595
5276
  if (error()) return;
4596
5277
  e.stopPropagation();
4597
5278
  const customEvent = e;
@@ -4599,6 +5280,30 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
4599
5280
  if (propagatedError) {
4600
5281
  handleError(propagatedError);
4601
5282
  }
5283
+ };
5284
+ container.addEventListener("sibu:error-propagate", propagateListener);
5285
+ onMount(() => {
5286
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
5287
+ const collected = [];
5288
+ let node = walker.currentNode;
5289
+ while (node) {
5290
+ const pending = takePendingError(node);
5291
+ if (pending) collected.push(pending);
5292
+ node = walker.nextNode();
5293
+ }
5294
+ if (collected.length === 1) {
5295
+ handleError(collected[0]);
5296
+ } else if (collected.length > 1) {
5297
+ const Agg = globalThis.AggregateError;
5298
+ handleError(
5299
+ Agg ? new Agg(collected, `${collected.length} pre-mount errors caught by ErrorBoundary`) : new Error(collected.map((e) => e.message).join("; "))
5300
+ );
5301
+ }
5302
+ return void 0;
5303
+ }, container);
5304
+ registerDisposer(container, () => {
5305
+ if (resetKeysTeardown) resetKeysTeardown();
5306
+ container.removeEventListener("sibu:error-propagate", propagateListener);
4602
5307
  });
4603
5308
  return container;
4604
5309
  }