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/index.cjs CHANGED
@@ -99,6 +99,7 @@ __export(index_exports, {
99
99
  footer: () => footer,
100
100
  form: () => form,
101
101
  g: () => g,
102
+ getSSRStore: () => getSSRStore,
102
103
  getSlot: () => getSlot,
103
104
  h1: () => h1,
104
105
  h2: () => h2,
@@ -168,15 +169,18 @@ __export(index_exports, {
168
169
  registerComponent: () => registerComponent,
169
170
  registerDisposer: () => registerDisposer,
170
171
  resolveComponent: () => resolveComponent,
172
+ retrack: () => retrack,
171
173
  rp: () => rp,
172
174
  rt: () => rt,
173
175
  ruby: () => ruby,
176
+ runInSSRContext: () => runInSSRContext,
174
177
  s: () => s,
175
178
  samp: () => samp,
176
179
  script: () => script,
177
180
  section: () => section,
178
181
  select: () => select,
179
182
  setGlobalErrorHandler: () => setGlobalErrorHandler,
183
+ setMaxDrainIterations: () => setMaxDrainIterations,
180
184
  show: () => show,
181
185
  signal: () => signal,
182
186
  slot: () => slot,
@@ -196,6 +200,7 @@ __export(index_exports, {
196
200
  symbol: () => symbol,
197
201
  table: () => table,
198
202
  tagFactory: () => tagFactory,
203
+ takePendingError: () => takePendingError,
199
204
  tbody: () => tbody,
200
205
  td: () => td,
201
206
  template: () => template,
@@ -210,6 +215,7 @@ __export(index_exports, {
210
215
  track: () => track2,
211
216
  transition: () => transition,
212
217
  trapFocus: () => trapFocus,
218
+ trustHTML: () => trustHTML,
213
219
  tspan: () => tspan,
214
220
  u: () => u,
215
221
  ul: () => ul,
@@ -232,48 +238,108 @@ function isDev() {
232
238
  var _isDev = isDev();
233
239
  function devAssert(condition, message) {
234
240
  if (_isDev && !condition) {
235
- throw new Error(`[Sibu] ${message}`);
241
+ throw new Error(`[SibuJS] ${message}`);
236
242
  }
237
243
  }
238
244
  function devWarn(message) {
239
245
  if (_isDev) {
240
- console.warn(`[Sibu] ${message}`);
246
+ console.warn(`[SibuJS] ${message}`);
241
247
  }
242
248
  }
243
249
 
244
250
  // src/utils/sanitize.ts
251
+ var SAFE_URL_PROTOCOLS = ["http:", "https:", "mailto:", "tel:", "ftp:"];
245
252
  function sanitizeUrl(url) {
246
253
  const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
247
254
  if (!trimmed) return "";
248
255
  const lower = trimmed.toLowerCase();
249
- if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
250
- return "";
256
+ let schemeEnd = -1;
257
+ for (let i2 = 0; i2 < lower.length; i2++) {
258
+ const ch = lower.charCodeAt(i2);
259
+ if (ch === 58) {
260
+ schemeEnd = i2;
261
+ break;
262
+ }
263
+ if (ch === 47 || ch === 63 || ch === 35) break;
251
264
  }
265
+ if (schemeEnd === -1) return trimmed;
266
+ const scheme = lower.slice(0, schemeEnd + 1);
267
+ if (!/^[a-z][a-z0-9+.-]*:$/.test(scheme)) return trimmed;
268
+ if (SAFE_URL_PROTOCOLS.indexOf(scheme) === -1) return "";
252
269
  return trimmed;
253
270
  }
271
+ function sanitizeSrcset(value) {
272
+ const parts = value.split(",");
273
+ const out = [];
274
+ for (let i2 = 0; i2 < parts.length; i2++) {
275
+ const part = parts[i2].trim();
276
+ if (!part) continue;
277
+ const m = part.match(/^(\S+)(\s+.+)?$/);
278
+ if (!m) continue;
279
+ const safe = sanitizeUrl(m[1]);
280
+ if (!safe) continue;
281
+ out.push(m[2] ? `${safe}${m[2]}` : safe);
282
+ }
283
+ return out.join(", ");
284
+ }
254
285
  function sanitizeCSSValue(value) {
255
- const lower = value.toLowerCase().replace(/\s+/g, "");
256
- if (lower.includes("url(") || lower.includes("expression(") || lower.includes("javascript:") || lower.includes("-moz-binding")) {
286
+ const decoded = value.replace(/\\([0-9a-fA-F]{1,6})\s?/g, (_m, hex) => {
287
+ const code2 = Number.parseInt(hex, 16);
288
+ if (!Number.isFinite(code2) || code2 < 0 || code2 > 1114111) return "";
289
+ try {
290
+ return String.fromCodePoint(code2);
291
+ } catch {
292
+ return "";
293
+ }
294
+ });
295
+ const lower = decoded.toLowerCase().replace(/\s+/g, "");
296
+ 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")) {
257
297
  return "";
258
298
  }
259
299
  return value;
260
300
  }
261
- var URL_ATTRIBUTES = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "cite", "poster", "background", "srcset"]);
301
+ var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
302
+ "href",
303
+ "xlink:href",
304
+ "src",
305
+ "action",
306
+ "formaction",
307
+ "formtarget",
308
+ "cite",
309
+ "poster",
310
+ "background",
311
+ "srcset",
312
+ "ping",
313
+ "data"
314
+ ]);
262
315
  function isUrlAttribute(attr) {
263
316
  return URL_ATTRIBUTES.has(attr);
264
317
  }
265
318
 
266
319
  // src/reactivity/track.ts
267
320
  var _isDev2 = isDev();
268
- var subscriberStack = new Array(32);
269
- var stackCapacity = 32;
321
+ var STACK_INITIAL = 32;
322
+ var STACK_SHRINK_THRESHOLD = 128;
323
+ var subscriberStack = new Array(STACK_INITIAL);
324
+ var stackCapacity = STACK_INITIAL;
270
325
  var stackTop = -1;
271
326
  var currentSubscriber = null;
272
- var signalSubscribers = /* @__PURE__ */ new WeakMap();
273
327
  var SUBS = "__s";
328
+ function syncFastPath(signal2, subs) {
329
+ const size = subs.size;
330
+ if (size === 0) {
331
+ signal2.__f = void 0;
332
+ delete signal2[SUBS];
333
+ } else if (size === 1) {
334
+ signal2.__f = subs.values().next().value;
335
+ } else {
336
+ signal2.__f = void 0;
337
+ }
338
+ }
274
339
  var notifyDepth = 0;
275
340
  var pendingQueue = [];
276
341
  var pendingSet = /* @__PURE__ */ new Set();
342
+ var propagateStack = [];
277
343
  function safeInvoke(sub2) {
278
344
  try {
279
345
  sub2();
@@ -283,6 +349,47 @@ function safeInvoke(sub2) {
283
349
  }
284
350
  var suspendDepth = 0;
285
351
  var trackingSuspended = false;
352
+ var subscriberEpochCounter = 0;
353
+ function retrack(effectFn, subscriber) {
354
+ const prev = currentSubscriber;
355
+ currentSubscriber = subscriber;
356
+ const sub2 = subscriber;
357
+ const epoch = ++subscriberEpochCounter;
358
+ sub2._epoch = epoch;
359
+ try {
360
+ effectFn();
361
+ } finally {
362
+ currentSubscriber = prev;
363
+ pruneStaleDeps(sub2, epoch);
364
+ }
365
+ }
366
+ function pruneStaleDeps(sub2, currentEpoch) {
367
+ if (sub2._dep !== void 0) {
368
+ if (sub2._depEpoch !== currentEpoch) {
369
+ const sig = sub2._dep;
370
+ const subs = sig[SUBS];
371
+ if (subs?.delete(sub2)) syncFastPath(sig, subs);
372
+ sub2._dep = void 0;
373
+ sub2._depEpoch = void 0;
374
+ }
375
+ return;
376
+ }
377
+ const deps = sub2._deps;
378
+ if (!deps || deps.size === 0) return;
379
+ let stales;
380
+ for (const [signal2, epoch] of deps) {
381
+ if (epoch !== currentEpoch) {
382
+ (stales ?? (stales = [])).push(signal2);
383
+ }
384
+ }
385
+ if (!stales) return;
386
+ for (const signal2 of stales) {
387
+ deps.delete(signal2);
388
+ const sig = signal2;
389
+ const subs = sig[SUBS];
390
+ if (subs?.delete(sub2)) syncFastPath(sig, subs);
391
+ }
392
+ }
286
393
  function track(effectFn, subscriber) {
287
394
  if (!subscriber) subscriber = effectFn;
288
395
  cleanup(subscriber);
@@ -298,6 +405,10 @@ function track(effectFn, subscriber) {
298
405
  } finally {
299
406
  stackTop--;
300
407
  currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
408
+ if (stackTop < 0 && stackCapacity > STACK_SHRINK_THRESHOLD) {
409
+ stackCapacity = Math.max(STACK_INITIAL, stackCapacity >>> 1);
410
+ subscriberStack.length = stackCapacity;
411
+ }
301
412
  }
302
413
  return () => cleanup(subscriber);
303
414
  }
@@ -333,31 +444,39 @@ function untracked(fn) {
333
444
  function recordDependency(signal2) {
334
445
  if (!currentSubscriber) return;
335
446
  const sub2 = currentSubscriber;
336
- if (sub2._dep === signal2) return;
447
+ const epoch = sub2._epoch;
448
+ if (sub2._dep === signal2) {
449
+ sub2._depEpoch = epoch;
450
+ return;
451
+ }
337
452
  const deps = sub2._deps;
338
453
  if (deps) {
339
- if (deps.has(signal2)) return;
340
- deps.add(signal2);
454
+ deps.set(signal2, epoch);
341
455
  } else if (sub2._dep !== void 0) {
342
- const set = /* @__PURE__ */ new Set();
343
- set.add(sub2._dep);
344
- set.add(signal2);
345
- sub2._deps = set;
456
+ const map2 = /* @__PURE__ */ new Map();
457
+ map2.set(sub2._dep, sub2._depEpoch);
458
+ map2.set(signal2, epoch);
459
+ sub2._deps = map2;
346
460
  sub2._dep = void 0;
461
+ sub2._depEpoch = void 0;
347
462
  } else {
348
463
  sub2._dep = signal2;
464
+ sub2._depEpoch = epoch;
349
465
  }
350
- let subs = signal2[SUBS];
466
+ const sig = signal2;
467
+ let subs = sig[SUBS];
351
468
  if (!subs) {
352
469
  subs = /* @__PURE__ */ new Set();
353
- signalSubscribers.set(signal2, subs);
354
- signal2[SUBS] = subs;
470
+ sig[SUBS] = subs;
355
471
  }
472
+ const prevSize = subs.size;
356
473
  subs.add(currentSubscriber);
357
- if (subs.size === 1) {
358
- signal2.__f = currentSubscriber;
359
- } else if (signal2.__f !== void 0) {
360
- signal2.__f = void 0;
474
+ if (subs.size !== prevSize) {
475
+ if (subs.size === 1) {
476
+ sig.__f = currentSubscriber;
477
+ } else if (sig.__f !== void 0) {
478
+ sig.__f = void 0;
479
+ }
361
480
  }
362
481
  }
363
482
  function queueSignalNotification(signal2) {
@@ -372,66 +491,107 @@ function queueSignalNotification(signal2) {
372
491
  }
373
492
  }
374
493
  }
375
- var MAX_DRAIN_ITERATIONS = 1e3;
494
+ var maxSubscriberRepeats = 50;
495
+ var maxDrainIterations = 1e6;
496
+ var drainEpoch = 0;
497
+ function setMaxDrainIterations(n) {
498
+ const prev = maxDrainIterations;
499
+ if (Number.isFinite(n) && n > 0) maxDrainIterations = Math.floor(n);
500
+ return prev;
501
+ }
502
+ function tickRepeat(sub2) {
503
+ const s2 = sub2;
504
+ if (s2._runEpoch !== drainEpoch) {
505
+ s2._runEpoch = drainEpoch;
506
+ s2._runs = 1;
507
+ return false;
508
+ }
509
+ return ++s2._runs > maxSubscriberRepeats;
510
+ }
511
+ function cycleError(sub2) {
512
+ if (typeof console !== "undefined") {
513
+ const name = sub2.__name ?? "<unnamed>";
514
+ console.error(
515
+ `[SibuJS] subscriber "${name}" fired more than ${maxSubscriberRepeats} times \u2014 likely a write-reads-self cycle between effects/signals. Breaking to prevent infinite loop.`
516
+ );
517
+ }
518
+ }
519
+ function absoluteDrainError() {
520
+ if (typeof console !== "undefined") {
521
+ console.error(
522
+ `[SibuJS] Notification drain exceeded ${maxDrainIterations} iterations \u2014 absolute safety net tripped. Breaking to prevent infinite loop.`
523
+ );
524
+ }
525
+ }
526
+ function drainQueue() {
527
+ let i2 = 0;
528
+ while (i2 < pendingQueue.length) {
529
+ if (i2 >= maxDrainIterations) {
530
+ absoluteDrainError();
531
+ break;
532
+ }
533
+ const sub2 = pendingQueue[i2++];
534
+ if (tickRepeat(sub2)) {
535
+ cycleError(sub2);
536
+ break;
537
+ }
538
+ pendingSet.delete(sub2);
539
+ safeInvoke(sub2);
540
+ }
541
+ }
376
542
  function drainNotificationQueue() {
377
543
  if (notifyDepth > 0) return;
378
544
  notifyDepth++;
545
+ drainEpoch++;
379
546
  try {
380
- let i2 = 0;
381
- while (i2 < pendingQueue.length) {
382
- if (i2 >= MAX_DRAIN_ITERATIONS) {
383
- if (typeof console !== "undefined") {
384
- console.error(
385
- `[SibuJS] Notification queue exceeded ${MAX_DRAIN_ITERATIONS} iterations \u2014 likely an effect that writes to a signal it reads. Breaking to prevent infinite loop.`
386
- );
387
- }
388
- break;
389
- }
390
- safeInvoke(pendingQueue[i2]);
391
- i2++;
392
- }
547
+ drainQueue();
393
548
  } finally {
394
- pendingQueue.length = 0;
395
- pendingSet.clear();
396
549
  notifyDepth--;
550
+ if (notifyDepth === 0) {
551
+ pendingQueue.length = 0;
552
+ pendingSet.clear();
553
+ }
397
554
  }
398
555
  }
399
556
  function propagateDirty(sub2) {
400
557
  sub2();
401
- let sig = sub2._sig;
402
- while (sig) {
558
+ const rootSig = sub2._sig;
559
+ if (!rootSig) return;
560
+ const stack = propagateStack;
561
+ const baseLen = stack.length;
562
+ stack.push(rootSig);
563
+ while (stack.length > baseLen) {
564
+ const sig = stack.pop();
403
565
  const first = sig.__f;
404
566
  if (first) {
405
567
  if (first._c) {
406
568
  const nSig = first._sig;
407
- nSig._d = true;
408
- sig = nSig;
409
- continue;
410
- }
411
- if (!pendingSet.has(first)) {
569
+ if (!nSig._d) {
570
+ nSig._d = true;
571
+ stack.push(nSig);
572
+ }
573
+ } else if (!pendingSet.has(first)) {
412
574
  pendingSet.add(first);
413
575
  pendingQueue.push(first);
414
576
  }
415
- break;
577
+ continue;
416
578
  }
417
579
  const subs = sig[SUBS];
418
- if (!subs) break;
419
- let nextSig;
580
+ if (!subs) continue;
420
581
  for (const s2 of subs) {
421
582
  if (s2._c) {
422
- s2();
423
583
  const nSig = s2._sig;
424
- if (nSig && !nextSig) {
425
- nextSig = nSig;
426
- } else if (nSig) {
427
- propagateDirty(s2);
584
+ if (nSig && !nSig._d) {
585
+ nSig._d = true;
586
+ stack.push(nSig);
587
+ } else if (!nSig) {
588
+ s2();
428
589
  }
429
590
  } else if (!pendingSet.has(s2)) {
430
591
  pendingSet.add(s2);
431
592
  pendingQueue.push(s2);
432
593
  }
433
594
  }
434
- sig = nextSig;
435
595
  }
436
596
  }
437
597
  function notifySubscribers(signal2) {
@@ -447,21 +607,22 @@ function notifySubscribers(signal2) {
447
607
  return;
448
608
  }
449
609
  notifyDepth++;
610
+ drainEpoch++;
450
611
  try {
451
612
  if (first._c) {
452
613
  propagateDirty(first);
614
+ } else if (tickRepeat(first)) {
615
+ cycleError(first);
453
616
  } else {
454
617
  safeInvoke(first);
455
618
  }
456
- let i2 = 0;
457
- while (i2 < pendingQueue.length) {
458
- safeInvoke(pendingQueue[i2]);
459
- i2++;
460
- }
619
+ drainQueue();
461
620
  } finally {
462
- pendingQueue.length = 0;
463
- pendingSet.clear();
464
621
  notifyDepth--;
622
+ if (notifyDepth === 0) {
623
+ pendingQueue.length = 0;
624
+ pendingSet.clear();
625
+ }
465
626
  }
466
627
  return;
467
628
  }
@@ -479,57 +640,45 @@ function notifySubscribers(signal2) {
479
640
  return;
480
641
  }
481
642
  notifyDepth++;
643
+ drainEpoch++;
482
644
  try {
483
- let directCount = 0;
484
645
  for (const sub2 of subs) {
485
- pendingQueue[directCount++] = sub2;
486
- }
487
- for (let i3 = 0; i3 < directCount; i3++) {
488
- if (pendingQueue[i3]._c) {
489
- propagateDirty(pendingQueue[i3]);
490
- }
491
- }
492
- for (let i3 = 0; i3 < directCount; i3++) {
493
- if (!pendingQueue[i3]._c) {
494
- if (!pendingSet.has(pendingQueue[i3])) {
495
- safeInvoke(pendingQueue[i3]);
496
- }
646
+ if (sub2._c) {
647
+ propagateDirty(sub2);
648
+ } else if (!pendingSet.has(sub2)) {
649
+ pendingSet.add(sub2);
650
+ pendingQueue.push(sub2);
497
651
  }
498
652
  }
499
- let i2 = directCount;
500
- while (i2 < pendingQueue.length) {
501
- safeInvoke(pendingQueue[i2]);
502
- i2++;
503
- }
653
+ drainQueue();
504
654
  } finally {
505
- pendingQueue.length = 0;
506
- pendingSet.clear();
507
655
  notifyDepth--;
656
+ if (notifyDepth === 0) {
657
+ pendingQueue.length = 0;
658
+ pendingSet.clear();
659
+ }
508
660
  }
509
661
  }
510
662
  function cleanup(subscriber) {
511
663
  const sub2 = subscriber;
512
664
  const singleDep = sub2._dep;
513
665
  if (singleDep !== void 0) {
514
- const subs = singleDep[SUBS];
515
- if (subs) {
516
- subs.delete(subscriber);
517
- if (singleDep.__f === subscriber) {
518
- singleDep.__f = void 0;
519
- }
666
+ const sig = singleDep;
667
+ const subs = sig[SUBS];
668
+ if (subs?.delete(subscriber)) {
669
+ syncFastPath(sig, subs);
520
670
  }
521
671
  sub2._dep = void 0;
672
+ sub2._depEpoch = void 0;
522
673
  return;
523
674
  }
524
675
  const deps = sub2._deps;
525
676
  if (!deps || deps.size === 0) return;
526
- for (const signal2 of deps) {
527
- const subs = signal2[SUBS];
528
- if (subs) {
529
- subs.delete(subscriber);
530
- if (signal2.__f === subscriber) {
531
- signal2.__f = void 0;
532
- }
677
+ for (const signal2 of deps.keys()) {
678
+ const sig = signal2;
679
+ const subs = sig[SUBS];
680
+ if (subs?.delete(subscriber)) {
681
+ syncFastPath(sig, subs);
533
682
  }
534
683
  }
535
684
  deps.clear();
@@ -537,6 +686,9 @@ function cleanup(subscriber) {
537
686
 
538
687
  // src/reactivity/bindAttribute.ts
539
688
  var _isDev3 = isDev();
689
+ function setProp(el, key, val) {
690
+ el[key] = val;
691
+ }
540
692
  function isEventHandlerAttr(name) {
541
693
  if (name.length < 3) return false;
542
694
  const lower = name.toLowerCase();
@@ -562,7 +714,7 @@ function bindAttribute(el, attr, getter) {
562
714
  }
563
715
  if (typeof value === "boolean") {
564
716
  if (attr in el && (attr === "checked" || attr === "disabled" || attr === "selected")) {
565
- el[attr] = value;
717
+ setProp(el, attr, value);
566
718
  } else if (value) {
567
719
  el.setAttribute(attr, "");
568
720
  } else {
@@ -572,7 +724,7 @@ function bindAttribute(el, attr, getter) {
572
724
  }
573
725
  const str = String(value);
574
726
  if ((attr === "value" || attr === "checked") && attr in el) {
575
- el[attr] = attr === "checked" ? Boolean(value) : str;
727
+ setProp(el, attr, attr === "checked" ? Boolean(value) : str);
576
728
  } else {
577
729
  el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
578
730
  }
@@ -603,7 +755,7 @@ function bindDynamic(el, nameGetter, valueGetter) {
603
755
  }
604
756
  const str = String(value);
605
757
  if ((name === "value" || name === "checked") && name in el) {
606
- el[name] = name === "checked" ? Boolean(value) : str;
758
+ setProp(el, name, name === "checked" ? Boolean(value) : str);
607
759
  } else {
608
760
  el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
609
761
  }
@@ -646,24 +798,29 @@ function bindChildNode(placeholder, getter) {
646
798
  let newNodes;
647
799
  if (Array.isArray(result)) {
648
800
  newNodes = [];
801
+ const seen = /* @__PURE__ */ new Set();
649
802
  for (let i2 = 0; i2 < result.length; i2++) {
650
803
  const item = result[i2];
651
804
  if (item == null || typeof item === "boolean") continue;
652
- newNodes.push(item instanceof Node ? item : document.createTextNode(String(item)));
805
+ const node = item instanceof Node ? item : document.createTextNode(String(item));
806
+ if (seen.has(node)) {
807
+ if (_isDev4)
808
+ devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
809
+ continue;
810
+ }
811
+ seen.add(node);
812
+ newNodes.push(node);
653
813
  }
654
814
  } else {
655
815
  const node = result instanceof Node ? result : document.createTextNode(String(result));
656
816
  newNodes = [node];
657
817
  }
658
- const reused = lastNodes.length > 0 && newNodes.length > 0 ? /* @__PURE__ */ new Set() : void 0;
659
- if (reused) {
818
+ let reused;
819
+ if (lastNodes.length > 0 && newNodes.length > 0) {
820
+ const lastSet = new Set(lastNodes);
821
+ reused = /* @__PURE__ */ new Set();
660
822
  for (let i2 = 0; i2 < newNodes.length; i2++) {
661
- for (let j = 0; j < lastNodes.length; j++) {
662
- if (newNodes[i2] === lastNodes[j]) {
663
- reused.add(newNodes[i2]);
664
- break;
665
- }
666
- }
823
+ if (lastSet.has(newNodes[i2])) reused.add(newNodes[i2]);
667
824
  }
668
825
  }
669
826
  for (let i2 = 0; i2 < lastNodes.length; i2++) {
@@ -706,7 +863,7 @@ function dispose(node) {
706
863
  while (stack.length > 0) {
707
864
  const current = stack.pop();
708
865
  order.push(current);
709
- const children = current.childNodes;
866
+ const children = Array.from(current.childNodes);
710
867
  for (let i2 = 0; i2 < children.length; i2++) {
711
868
  stack.push(children[i2]);
712
869
  }
@@ -715,8 +872,10 @@ function dispose(node) {
715
872
  const current = order[i2];
716
873
  const disposers = elementDisposers.get(current);
717
874
  if (disposers) {
718
- if (_isDev5) activeBindingCount -= disposers.length;
719
- for (const d of disposers) {
875
+ const snapshot = disposers.slice();
876
+ elementDisposers.delete(current);
877
+ if (_isDev5) activeBindingCount -= snapshot.length;
878
+ for (const d of snapshot) {
720
879
  try {
721
880
  d();
722
881
  } catch (err) {
@@ -725,7 +884,23 @@ function dispose(node) {
725
884
  }
726
885
  }
727
886
  }
728
- elementDisposers.delete(current);
887
+ let extraPasses = 0;
888
+ while (extraPasses++ < 8) {
889
+ const added = elementDisposers.get(current);
890
+ if (!added || added.length === 0) break;
891
+ const moreSnapshot = added.slice();
892
+ elementDisposers.delete(current);
893
+ if (_isDev5) activeBindingCount -= moreSnapshot.length;
894
+ for (const d of moreSnapshot) {
895
+ try {
896
+ d();
897
+ } catch (err) {
898
+ if (_isDev5 && typeof console !== "undefined") {
899
+ console.warn("[SibuJS] Disposer threw during cleanup:", err);
900
+ }
901
+ }
902
+ }
903
+ }
729
904
  }
730
905
  }
731
906
  }
@@ -741,6 +916,30 @@ function checkLeaks(warnThreshold = 0) {
741
916
 
742
917
  // src/core/rendering/tagFactory.ts
743
918
  var SVG_NS = "http://www.w3.org/2000/svg";
919
+ var _isDev6 = isDev();
920
+ var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
921
+ function validateTagName(tag) {
922
+ const lower = tag.toLowerCase();
923
+ if (BLOCKED_TAGS.has(lower)) {
924
+ throw new Error(`tagFactory: refusing to create <${tag}> \u2014 tag is blocked for security reasons.`);
925
+ }
926
+ }
927
+ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
928
+ "config",
929
+ "location",
930
+ "history",
931
+ "document",
932
+ "window",
933
+ "navigator",
934
+ "name",
935
+ "top",
936
+ "parent",
937
+ "self",
938
+ "frames"
939
+ ]);
940
+ function setProp2(el, key, val) {
941
+ el[key] = val;
942
+ }
744
943
  var kebabCache = /* @__PURE__ */ new Map();
745
944
  function toKebab(prop) {
746
945
  let cached = kebabCache.get(prop);
@@ -865,79 +1064,103 @@ function appendChildren(el, nodes) {
865
1064
  }
866
1065
  }
867
1066
  }
868
- var tagFactory = (tag, ns) => (first, second) => {
869
- const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
870
- if (first === void 0) return el;
871
- if (typeof first === "string") {
872
- if (second !== void 0) {
873
- el.setAttribute("class", first);
874
- appendChildren(el, second);
1067
+ var tagFactory = (tag, ns) => {
1068
+ return (first, second) => {
1069
+ validateTagName(tag);
1070
+ const el = ns ? document.createElementNS(ns, tag) : document.createElement(tag);
1071
+ if (first === void 0) return el;
1072
+ if (typeof first === "string") {
1073
+ if (second !== void 0) {
1074
+ el.setAttribute("class", first);
1075
+ appendChildren(el, second);
1076
+ return el;
1077
+ }
1078
+ el.textContent = first;
875
1079
  return el;
876
1080
  }
877
- el.textContent = first;
878
- return el;
879
- }
880
- if (typeof first === "number") {
881
- el.textContent = String(first);
882
- return el;
883
- }
884
- if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
885
- appendChildren(el, first);
886
- return el;
887
- }
888
- const props = first;
889
- const pClass = props.class;
890
- if (pClass != null) applyClass(el, pClass);
891
- const pId = props.id;
892
- if (pId != null) el.id = pId;
893
- const pNodes = second !== void 0 ? second : props.nodes;
894
- if (pNodes != null) appendChildren(el, pNodes);
895
- const pOn = props.on;
896
- if (pOn) {
897
- for (const ev in pOn) {
898
- el.addEventListener(ev, pOn[ev]);
899
- }
900
- }
901
- const pStyle = props.style;
902
- if (pStyle != null) applyStyle(el, pStyle);
903
- const pRef = props.ref;
904
- if (pRef) pRef.current = el;
905
- for (const key in props) {
906
- switch (key) {
907
- case "class":
908
- case "id":
909
- case "nodes":
910
- case "on":
911
- case "style":
912
- case "ref":
913
- case "onElement":
914
- continue;
915
- // already handled above / below
916
- default: {
917
- const value = props[key];
918
- if (value == null) continue;
919
- if (key[0] === "o" && key[1] === "n") continue;
920
- if (typeof value === "function") {
921
- registerDisposer(el, bindAttribute(el, key, value));
922
- } else if (typeof value === "boolean") {
923
- if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
924
- el[key] = value;
925
- } else if (value) {
926
- el.setAttribute(key, "");
1081
+ if (typeof first === "number") {
1082
+ el.textContent = String(first);
1083
+ return el;
1084
+ }
1085
+ if (Array.isArray(first) || first instanceof Node || typeof first === "function") {
1086
+ appendChildren(el, first);
1087
+ return el;
1088
+ }
1089
+ const props = first;
1090
+ const pClass = props.class;
1091
+ if (pClass != null) applyClass(el, pClass);
1092
+ const pId = props.id;
1093
+ if (pId != null) {
1094
+ if (_isDev6 && typeof pId === "string" && CLOBBER_RISKY_IDS.has(pId.toLowerCase())) {
1095
+ devWarn(
1096
+ `tagFactory: element id="${pId}" matches a common global and may cause DOM clobbering. Avoid setting ids from untrusted input.`
1097
+ );
1098
+ }
1099
+ el.id = pId;
1100
+ }
1101
+ const pNodes = second !== void 0 ? second : props.nodes;
1102
+ if (pNodes != null) appendChildren(el, pNodes);
1103
+ const pOn = props.on;
1104
+ if (pOn) {
1105
+ for (const ev in pOn) {
1106
+ const handler = pOn[ev];
1107
+ if (typeof handler === "function") {
1108
+ el.addEventListener(ev, handler);
1109
+ } else if (_isDev6) {
1110
+ devWarn(
1111
+ `tagFactory: on.${ev} handler is not a function (got ${typeof handler}). Event listener was not attached.`
1112
+ );
1113
+ }
1114
+ }
1115
+ }
1116
+ const pStyle = props.style;
1117
+ if (pStyle != null) applyStyle(el, pStyle);
1118
+ const pRef = props.ref;
1119
+ if (pRef) pRef.current = el;
1120
+ for (const key in props) {
1121
+ switch (key) {
1122
+ case "class":
1123
+ case "id":
1124
+ case "nodes":
1125
+ case "on":
1126
+ case "style":
1127
+ case "ref":
1128
+ case "onElement":
1129
+ continue;
1130
+ // already handled above / below
1131
+ default: {
1132
+ const value = props[key];
1133
+ if (value == null) continue;
1134
+ const lkey = key.toLowerCase();
1135
+ if (lkey[0] === "o" && lkey[1] === "n") continue;
1136
+ if (typeof value === "function") {
1137
+ registerDisposer(el, bindAttribute(el, key, value));
1138
+ } else if (typeof value === "boolean") {
1139
+ if (key in el && (key === "checked" || key === "disabled" || key === "selected")) {
1140
+ setProp2(el, key, value);
1141
+ } else if (value) {
1142
+ el.setAttribute(key, "");
1143
+ } else {
1144
+ el.removeAttribute(key);
1145
+ }
927
1146
  } else {
928
- el.removeAttribute(key);
1147
+ const str = String(value);
1148
+ if (lkey === "srcset") {
1149
+ el.setAttribute(key, sanitizeSrcset(str));
1150
+ } else if (isUrlAttribute(lkey)) {
1151
+ el.setAttribute(key, sanitizeUrl(str));
1152
+ } else {
1153
+ el.setAttribute(key, str);
1154
+ }
929
1155
  }
930
- } else {
931
- const str = String(value);
932
- el.setAttribute(key, isUrlAttribute(key) ? sanitizeUrl(str) : str);
933
1156
  }
934
1157
  }
935
1158
  }
936
- }
937
- if (props.onElement && typeof props.onElement === "function") {
938
- props.onElement(el);
939
- }
940
- return el;
1159
+ if (props.onElement && typeof props.onElement === "function") {
1160
+ props.onElement(el);
1161
+ }
1162
+ return el;
1163
+ };
941
1164
  };
942
1165
 
943
1166
  // src/core/rendering/html.ts
@@ -1080,6 +1303,8 @@ var marquee = tagFactory("marquee");
1080
1303
  var customElement = (tagName) => tagFactory(tagName);
1081
1304
 
1082
1305
  // src/core/rendering/htm.ts
1306
+ var _isDev7 = isDev();
1307
+ var RAW_TEXT_TAGS = /* @__PURE__ */ new Set(["script", "style"]);
1083
1308
  var VOID_ELEMENTS = /* @__PURE__ */ new Set([
1084
1309
  "area",
1085
1310
  "base",
@@ -1264,6 +1489,15 @@ function parseTemplate(strings) {
1264
1489
  children.push({ t: 0, el: { tag, svg: SVG_TAGS.has(tag), attrs, children: [] } });
1265
1490
  } else {
1266
1491
  const inner = parseChildren();
1492
+ if (RAW_TEXT_TAGS.has(tag.toLowerCase())) {
1493
+ for (let i2 = 0; i2 < inner.length; i2++) {
1494
+ if (inner[i2].t === 2) {
1495
+ throw new Error(
1496
+ `html: dynamic \${...} expressions are not allowed inside <${tag}> (raw-text context). Build the content separately and append it as a Node.`
1497
+ );
1498
+ }
1499
+ }
1500
+ }
1267
1501
  if (template2[pos] === "<" && pos + 1 < len && template2[pos + 1] === "/") {
1268
1502
  pos += 2;
1269
1503
  readTagName();
@@ -1290,27 +1524,50 @@ function executeElement(tmpl, values) {
1290
1524
  break;
1291
1525
  case 1: {
1292
1526
  const name = attr.name;
1293
- if (name[0] === "o" && name[1] === "n") break;
1527
+ const lname = name.toLowerCase();
1528
+ if (lname[0] === "o" && lname[1] === "n") break;
1294
1529
  const val = values[attr.idx];
1295
1530
  if (typeof val === "function") {
1296
1531
  registerDisposer(el, bindAttribute(el, name, val));
1297
1532
  } else if (val != null) {
1298
1533
  const str = String(val);
1299
- el.setAttribute(name, isUrlAttribute(name) ? sanitizeUrl(str) : str);
1534
+ if (lname === "srcset") {
1535
+ el.setAttribute(name, sanitizeSrcset(str));
1536
+ } else if (isUrlAttribute(lname)) {
1537
+ el.setAttribute(name, sanitizeUrl(str));
1538
+ } else {
1539
+ el.setAttribute(name, str);
1540
+ }
1300
1541
  }
1301
1542
  break;
1302
1543
  }
1303
1544
  case 2: {
1304
1545
  let val = attr.statics[0];
1305
1546
  for (let j = 0; j < attr.exprs.length; j++) {
1306
- val += String(values[attr.exprs[j]]) + attr.statics[j + 1];
1547
+ const ev = values[attr.exprs[j]];
1548
+ val += (ev == null ? "" : String(ev)) + attr.statics[j + 1];
1549
+ }
1550
+ const lname2 = attr.name.toLowerCase();
1551
+ if (lname2 === "srcset") {
1552
+ el.setAttribute(attr.name, sanitizeSrcset(val));
1553
+ } else if (isUrlAttribute(lname2)) {
1554
+ el.setAttribute(attr.name, sanitizeUrl(val));
1555
+ } else {
1556
+ el.setAttribute(attr.name, val);
1307
1557
  }
1308
- el.setAttribute(attr.name, val);
1309
1558
  break;
1310
1559
  }
1311
- case 3:
1312
- el.addEventListener(attr.name, values[attr.idx]);
1560
+ case 3: {
1561
+ const fn = values[attr.idx];
1562
+ if (typeof fn === "function") {
1563
+ el.addEventListener(attr.name, fn);
1564
+ } else if (_isDev7) {
1565
+ devWarn(
1566
+ `html: on:${attr.name} handler is not a function (got ${typeof fn}). Event listener was not attached.`
1567
+ );
1568
+ }
1313
1569
  break;
1570
+ }
1314
1571
  case 4:
1315
1572
  el.setAttribute(attr.name, "");
1316
1573
  break;
@@ -1404,7 +1661,7 @@ function html2(strings, ...values) {
1404
1661
  function mount(component, container) {
1405
1662
  if (!container) {
1406
1663
  throw new Error(
1407
- "[Sibu] mount: container element not found. Make sure the DOM element exists before calling mount()."
1664
+ "[SibuJS mount] container element not found. Make sure the DOM element exists before calling mount()."
1408
1665
  );
1409
1666
  }
1410
1667
  devAssert(
@@ -1432,7 +1689,7 @@ function mount(component, container) {
1432
1689
  }
1433
1690
 
1434
1691
  // src/core/rendering/each.ts
1435
- var _isDev6 = isDev();
1692
+ var _isDev8 = isDev();
1436
1693
  function resolveNodeChild(child) {
1437
1694
  if (typeof child === "function") {
1438
1695
  return resolveNodeChild(child());
@@ -1527,17 +1784,31 @@ function each(getArray, render, options) {
1527
1784
  node = existing;
1528
1785
  } else {
1529
1786
  const itemKey = key;
1530
- const itemGetter = () => getArray()[keyIndexMap.get(itemKey)];
1787
+ const itemGetter = () => untracked(() => getArray()[keyIndexMap.get(itemKey)]);
1531
1788
  const indexGetter = () => keyIndexMap.get(itemKey);
1532
1789
  try {
1533
1790
  node = resolveNodeChild(render(itemGetter, indexGetter));
1534
1791
  } catch (err) {
1535
- if (_isDev6) {
1792
+ if (_isDev8) {
1536
1793
  devWarn(
1537
1794
  `each: render threw for item at index ${i2} (key="${newKeys[i2]}"): ${err instanceof Error ? err.message : String(err)}`
1538
1795
  );
1539
1796
  }
1540
1797
  node = document.createComment(`each:error:${i2}`);
1798
+ const errorObj = err instanceof Error ? err : new Error(String(err));
1799
+ queueMicrotask(() => {
1800
+ try {
1801
+ const target = anchor.parentNode;
1802
+ if (target?.dispatchEvent) {
1803
+ target.dispatchEvent(
1804
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
1805
+ );
1806
+ } else if (_isDev8) {
1807
+ devWarn(`each: error not surfaced \u2014 anchor detached: ${errorObj.message}`);
1808
+ }
1809
+ } catch {
1810
+ }
1811
+ });
1541
1812
  }
1542
1813
  }
1543
1814
  workMap.set(key, node);
@@ -1606,7 +1877,8 @@ function each(getArray, render, options) {
1606
1877
  workMap = tmp;
1607
1878
  initialized = true;
1608
1879
  };
1609
- track(update);
1880
+ const untrack = track(update);
1881
+ registerDisposer(anchor, untrack);
1610
1882
  if (!initialized) {
1611
1883
  queueMicrotask(() => {
1612
1884
  if (!initialized && anchor.parentNode) {
@@ -1653,7 +1925,9 @@ function Portal(nodes, target) {
1653
1925
  const anchor = document.createComment("portal");
1654
1926
  const container = target || document.body;
1655
1927
  let portalContent = null;
1928
+ let disposed = false;
1656
1929
  queueMicrotask(() => {
1930
+ if (disposed) return;
1657
1931
  try {
1658
1932
  portalContent = nodes();
1659
1933
  container.appendChild(portalContent);
@@ -1661,9 +1935,22 @@ function Portal(nodes, target) {
1661
1935
  if (typeof console !== "undefined") {
1662
1936
  console.error("[Portal] Render error:", err);
1663
1937
  }
1938
+ const errorObj = err instanceof Error ? err : new Error(String(err));
1939
+ queueMicrotask(() => {
1940
+ try {
1941
+ const target2 = anchor.parentNode;
1942
+ if (target2?.dispatchEvent) {
1943
+ target2.dispatchEvent(
1944
+ new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error: errorObj } })
1945
+ );
1946
+ }
1947
+ } catch {
1948
+ }
1949
+ });
1664
1950
  }
1665
1951
  });
1666
1952
  registerDisposer(anchor, () => {
1953
+ disposed = true;
1667
1954
  if (portalContent) {
1668
1955
  dispose(portalContent);
1669
1956
  portalContent.remove();
@@ -1703,7 +1990,8 @@ function DynamicComponent(is) {
1703
1990
  }
1704
1991
  container.replaceChildren(el);
1705
1992
  }
1706
- track(render);
1993
+ const untrack = track(render);
1994
+ registerDisposer(container, untrack);
1707
1995
  return container;
1708
1996
  }
1709
1997
 
@@ -1793,11 +2081,16 @@ function KeepAlive(activeKey, cases, options) {
1793
2081
  const anchor = document.createComment("keep-alive");
1794
2082
  const cache2 = /* @__PURE__ */ new Map();
1795
2083
  const lruOrder = [];
1796
- const max = options?.max ?? 0;
2084
+ const max = options?.max ?? 10;
2085
+ if (max === 0 && isDev()) {
2086
+ devWarn("KeepAlive: unbounded cache (max: 0). Cached subtrees will never be evicted \u2014 set `max` to bound memory.");
2087
+ }
1797
2088
  let currentKey;
1798
2089
  let currentNode = null;
1799
2090
  let initialized = false;
2091
+ let disposed = false;
1800
2092
  const update = () => {
2093
+ if (disposed) return;
1801
2094
  const key = activeKey();
1802
2095
  const parent = anchor.parentNode;
1803
2096
  if (!parent) return;
@@ -1837,12 +2130,23 @@ function KeepAlive(activeKey, cases, options) {
1837
2130
  currentNode = node;
1838
2131
  initialized = true;
1839
2132
  };
1840
- track(update);
2133
+ const untrack = track(update);
1841
2134
  if (!initialized) {
1842
2135
  queueMicrotask(() => {
1843
2136
  if (!initialized && anchor.parentNode) update();
1844
2137
  });
1845
2138
  }
2139
+ registerDisposer(anchor, () => {
2140
+ disposed = true;
2141
+ untrack();
2142
+ for (const node of cache2.values()) {
2143
+ dispose(node);
2144
+ if (node.parentNode) node.parentNode.removeChild(node);
2145
+ }
2146
+ cache2.clear();
2147
+ lruOrder.length = 0;
2148
+ currentNode = null;
2149
+ });
1846
2150
  return anchor;
1847
2151
  }
1848
2152
 
@@ -2004,19 +2308,22 @@ function isBatching() {
2004
2308
  return batchDepth > 0;
2005
2309
  }
2006
2310
  function flushBatch() {
2007
- for (const signal2 of pendingSignals) {
2008
- queueSignalNotification(signal2);
2311
+ try {
2312
+ for (const signal2 of pendingSignals) {
2313
+ queueSignalNotification(signal2);
2314
+ }
2315
+ } finally {
2316
+ pendingSignals.clear();
2009
2317
  }
2010
- pendingSignals.clear();
2011
2318
  drainNotificationQueue();
2012
2319
  }
2013
2320
 
2014
2321
  // src/core/signals/signal.ts
2015
2322
  var _g = globalThis;
2016
- var _isDev7 = isDev();
2323
+ var _isDev9 = isDev();
2017
2324
  function signal(initial, options) {
2018
2325
  const state = { value: initial };
2019
- const debugName = _isDev7 ? options?.name : void 0;
2326
+ const debugName = _isDev9 ? options?.name : void 0;
2020
2327
  const equalsFn = options?.equals;
2021
2328
  if (debugName) {
2022
2329
  state.__name = debugName;
@@ -2030,7 +2337,7 @@ function signal(initial, options) {
2030
2337
  function set(next) {
2031
2338
  const newValue = typeof next === "function" ? next(state.value) : next;
2032
2339
  if (equalsFn ? equalsFn(state.value, newValue) : Object.is(newValue, state.value)) return;
2033
- if (_isDev7) {
2340
+ if (_isDev9) {
2034
2341
  const oldValue = state.value;
2035
2342
  state.value = newValue;
2036
2343
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
@@ -2042,7 +2349,7 @@ function signal(initial, options) {
2042
2349
  notifySubscribers(state);
2043
2350
  }
2044
2351
  }
2045
- if (_isDev7) {
2352
+ if (_isDev9) {
2046
2353
  const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2047
2354
  if (hook) hook.emit("signal:create", { signal: state, name: debugName, getter: get, initial });
2048
2355
  }
@@ -2050,23 +2357,59 @@ function signal(initial, options) {
2050
2357
  }
2051
2358
 
2052
2359
  // src/core/ssr-context.ts
2053
- var ssrMode = false;
2360
+ var als = null;
2361
+ try {
2362
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
2363
+ const req = Function("return typeof require==='function'?require:null")();
2364
+ if (req) {
2365
+ const mod = req("node:async_hooks");
2366
+ als = new mod.AsyncLocalStorage();
2367
+ }
2368
+ }
2369
+ } catch {
2370
+ als = null;
2371
+ }
2372
+ var fallbackStore = { ssr: false, suspenseIdCounter: 0 };
2373
+ function getSSRStore() {
2374
+ if (als) {
2375
+ const s2 = als.getStore();
2376
+ if (s2) return s2;
2377
+ }
2378
+ return fallbackStore;
2379
+ }
2054
2380
  function isSSR() {
2055
- return ssrMode;
2381
+ return getSSRStore().ssr;
2056
2382
  }
2057
2383
  function enableSSR() {
2058
- ssrMode = true;
2384
+ getSSRStore().ssr = true;
2059
2385
  }
2060
2386
  function disableSSR() {
2061
- ssrMode = false;
2387
+ getSSRStore().ssr = false;
2388
+ }
2389
+ function runInSSRContext(fn) {
2390
+ const store2 = { ssr: true, suspenseIdCounter: 0 };
2391
+ if (als) {
2392
+ return als.run(store2, fn);
2393
+ }
2394
+ const prevSSR = fallbackStore.ssr;
2395
+ const prevCounter = fallbackStore.suspenseIdCounter;
2396
+ fallbackStore.ssr = true;
2397
+ fallbackStore.suspenseIdCounter = 0;
2398
+ try {
2399
+ return fn();
2400
+ } finally {
2401
+ fallbackStore.ssr = prevSSR;
2402
+ fallbackStore.suspenseIdCounter = prevCounter;
2403
+ }
2062
2404
  }
2063
2405
  function withSSR(fn) {
2064
- const wasSSR = ssrMode;
2065
- enableSSR();
2406
+ const store2 = getSSRStore();
2407
+ const wasSSR = store2.ssr;
2408
+ store2.ssr = true;
2066
2409
  try {
2067
2410
  return fn();
2068
2411
  } finally {
2069
- if (!wasSSR) disableSSR();
2412
+ if (!wasSSR) store2.ssr = false;
2070
2413
  }
2071
2414
  }
2072
2415
 
@@ -2093,26 +2436,114 @@ function effect(effectFn, options) {
2093
2436
  if (isSSR()) return () => {
2094
2437
  };
2095
2438
  const onError = options?.onError;
2439
+ let userCleanups = [];
2440
+ const onCleanup2 = (fn) => {
2441
+ userCleanups.push(fn);
2442
+ };
2443
+ const runUserCleanups = () => {
2444
+ if (userCleanups.length === 0) return;
2445
+ const list = userCleanups;
2446
+ userCleanups = [];
2447
+ for (let i2 = list.length - 1; i2 >= 0; i2--) {
2448
+ try {
2449
+ list[i2]();
2450
+ } catch (err) {
2451
+ if (typeof console !== "undefined") {
2452
+ console.warn("[SibuJS effect] onCleanup threw:", err);
2453
+ }
2454
+ }
2455
+ }
2456
+ };
2457
+ const invokeBody = () => effectFn(onCleanup2);
2096
2458
  const wrappedFn = onError ? () => {
2097
2459
  try {
2098
- effectFn();
2460
+ invokeBody();
2099
2461
  } catch (err) {
2100
2462
  onError(err);
2101
2463
  }
2102
- } : effectFn;
2464
+ } : invokeBody;
2103
2465
  let cleanupHandle = () => {
2104
2466
  };
2467
+ let running = false;
2468
+ let rerunPending = false;
2469
+ const MAX_RERUNS = 100;
2105
2470
  const subscriber = () => {
2106
- cleanupHandle();
2107
- cleanupHandle = track(wrappedFn, subscriber);
2471
+ if (running) {
2472
+ rerunPending = true;
2473
+ return;
2474
+ }
2475
+ running = true;
2476
+ try {
2477
+ let reruns = 0;
2478
+ do {
2479
+ rerunPending = false;
2480
+ runUserCleanups();
2481
+ cleanupHandle();
2482
+ cleanupHandle = track(wrappedFn, subscriber);
2483
+ if (++reruns > MAX_RERUNS) {
2484
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
2485
+ console.error(
2486
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
2487
+ );
2488
+ }
2489
+ rerunPending = false;
2490
+ break;
2491
+ }
2492
+ } while (rerunPending);
2493
+ } finally {
2494
+ running = false;
2495
+ rerunPending = false;
2496
+ }
2108
2497
  };
2109
- cleanupHandle = track(wrappedFn, subscriber);
2498
+ running = true;
2499
+ try {
2500
+ let reruns = 0;
2501
+ do {
2502
+ rerunPending = false;
2503
+ runUserCleanups();
2504
+ cleanupHandle();
2505
+ cleanupHandle = track(wrappedFn, subscriber);
2506
+ if (++reruns > MAX_RERUNS) {
2507
+ if (_g2.__SIBU_DEV_WARN__ !== false && typeof console !== "undefined") {
2508
+ console.error(
2509
+ `[SibuJS] effect re-requested itself ${MAX_RERUNS}+ times on initial run \u2014 likely a write-reads-self cycle. Breaking to prevent infinite loop.`
2510
+ );
2511
+ }
2512
+ rerunPending = false;
2513
+ break;
2514
+ }
2515
+ } while (rerunPending);
2516
+ } finally {
2517
+ running = false;
2518
+ rerunPending = false;
2519
+ }
2110
2520
  const hook = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2111
2521
  if (hook) hook.emit("effect:create", { effectFn });
2522
+ let disposed = false;
2112
2523
  return () => {
2524
+ if (disposed) return;
2525
+ disposed = true;
2113
2526
  const h = _g2.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2114
- if (h) h.emit("effect:destroy", { effectFn });
2115
- cleanupHandle();
2527
+ if (h) {
2528
+ try {
2529
+ h.emit("effect:destroy", { effectFn });
2530
+ } catch {
2531
+ }
2532
+ }
2533
+ try {
2534
+ runUserCleanups();
2535
+ } catch (err) {
2536
+ if (typeof console !== "undefined") {
2537
+ console.warn("[SibuJS effect] onCleanup threw during dispose:", err);
2538
+ }
2539
+ }
2540
+ try {
2541
+ cleanupHandle();
2542
+ } catch (err) {
2543
+ if (typeof console !== "undefined") {
2544
+ console.warn("[SibuJS effect] dispose threw:", err);
2545
+ }
2546
+ }
2116
2547
  };
2117
2548
  }
2118
2549
 
@@ -2120,6 +2551,7 @@ function effect(effectFn, options) {
2120
2551
  function derived(getter, options) {
2121
2552
  devAssert(typeof getter === "function", "derived: argument must be a getter function.");
2122
2553
  const debugName = options?.name;
2554
+ const equals = options?.equals;
2123
2555
  const cs = {};
2124
2556
  cs._d = false;
2125
2557
  cs._g = getter;
@@ -2130,8 +2562,14 @@ function derived(getter, options) {
2130
2562
  markDirty._c = 1;
2131
2563
  markDirty._sig = cs;
2132
2564
  track(() => {
2133
- cs._d = false;
2134
- cs._v = getter();
2565
+ let threw = true;
2566
+ try {
2567
+ cs._v = getter();
2568
+ cs._d = false;
2569
+ threw = false;
2570
+ } finally {
2571
+ if (threw) cs._d = true;
2572
+ }
2135
2573
  }, markDirty);
2136
2574
  const hook = globalThis.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
2137
2575
  let evaluating = false;
@@ -2144,11 +2582,16 @@ function derived(getter, options) {
2144
2582
  if (trackingSuspended) {
2145
2583
  if (cs._d) {
2146
2584
  evaluating = true;
2585
+ let threw = true;
2147
2586
  try {
2148
- cs._d = false;
2149
- cs._v = getter();
2587
+ retrack(() => {
2588
+ cs._v = getter();
2589
+ cs._d = false;
2590
+ threw = false;
2591
+ }, markDirty);
2150
2592
  } finally {
2151
2593
  evaluating = false;
2594
+ if (threw) cs._d = true;
2152
2595
  }
2153
2596
  }
2154
2597
  return cs._v;
@@ -2157,13 +2600,17 @@ function derived(getter, options) {
2157
2600
  if (cs._d) {
2158
2601
  const oldValue = cs._v;
2159
2602
  evaluating = true;
2603
+ let threw = true;
2160
2604
  try {
2161
- track(() => {
2605
+ retrack(() => {
2606
+ const next = getter();
2607
+ cs._v = equals && cs._v !== void 0 ? equals(cs._v, next) ? cs._v : next : next;
2162
2608
  cs._d = false;
2163
- cs._v = getter();
2609
+ threw = false;
2164
2610
  }, markDirty);
2165
2611
  } finally {
2166
2612
  evaluating = false;
2613
+ if (threw) cs._d = true;
2167
2614
  }
2168
2615
  if (hook && oldValue !== cs._v) {
2169
2616
  hook.emit("computed:update", { signal: cs, oldValue, newValue: cs._v });
@@ -2225,7 +2672,7 @@ function store(initialState) {
2225
2672
  },
2226
2673
  set() {
2227
2674
  throw new Error(
2228
- "[Sibu] store: Direct mutation is not allowed. Use actions.setState() to update store properties."
2675
+ "[SibuJS store] Direct mutation is not allowed. Use actions.setState() to update store properties."
2229
2676
  );
2230
2677
  }
2231
2678
  });
@@ -2239,16 +2686,20 @@ function store(initialState) {
2239
2686
  const setState = (patch) => {
2240
2687
  const current = getSnapshot();
2241
2688
  const nextState = typeof patch === "function" ? patch(current) : patch;
2242
- Object.entries(nextState).forEach(([key, value]) => {
2243
- if (key in signals) {
2244
- signals[key][1](value);
2245
- }
2689
+ batch(() => {
2690
+ Object.entries(nextState).forEach(([key, value]) => {
2691
+ if (key in signals) {
2692
+ signals[key][1](value);
2693
+ }
2694
+ });
2246
2695
  });
2247
2696
  };
2248
2697
  const reset = () => {
2249
- Object.keys(initialState).forEach((key) => {
2250
- const setter = signals[key][1];
2251
- setter(initialState[key]);
2698
+ batch(() => {
2699
+ Object.keys(initialState).forEach((key) => {
2700
+ const setter = signals[key][1];
2701
+ setter(initialState[key]);
2702
+ });
2252
2703
  });
2253
2704
  };
2254
2705
  const subscribe = (callback) => {
@@ -2382,7 +2833,8 @@ function reactiveArray(initial = []) {
2382
2833
  function get() {
2383
2834
  recordDependency(signal2);
2384
2835
  if (snapshot === null) {
2385
- snapshot = Object.freeze([...data2]);
2836
+ const copy = data2.slice();
2837
+ snapshot = Object.freeze(copy);
2386
2838
  }
2387
2839
  return snapshot;
2388
2840
  }
@@ -2578,26 +3030,34 @@ function asyncDerived(factory, initial) {
2578
3030
  effect(() => {
2579
3031
  tick();
2580
3032
  const currentRun = ++runId;
2581
- setLoading(true);
2582
- setError(null);
3033
+ batch(() => {
3034
+ setLoading(true);
3035
+ setError(null);
3036
+ });
2583
3037
  let promise;
2584
3038
  try {
2585
3039
  promise = factory();
2586
3040
  } catch (err) {
2587
- setError(err);
2588
- setLoading(false);
3041
+ batch(() => {
3042
+ setError(err);
3043
+ setLoading(false);
3044
+ });
2589
3045
  return;
2590
3046
  }
2591
3047
  promise.then(
2592
3048
  (result) => {
2593
3049
  if (currentRun !== runId) return;
2594
- setValue(result);
2595
- setLoading(false);
3050
+ batch(() => {
3051
+ setValue(result);
3052
+ setLoading(false);
3053
+ });
2596
3054
  },
2597
3055
  (err) => {
2598
3056
  if (currentRun !== runId) return;
2599
- setError(err);
2600
- setLoading(false);
3057
+ batch(() => {
3058
+ setError(err);
3059
+ setLoading(false);
3060
+ });
2601
3061
  }
2602
3062
  );
2603
3063
  });
@@ -2624,26 +3084,186 @@ function runMountCallback(callback, hookName, element) {
2624
3084
  registerDisposer(element, cleanup2);
2625
3085
  }
2626
3086
  }
3087
+ var mountWatchers = /* @__PURE__ */ new WeakMap();
3088
+ var unmountWatchers = /* @__PURE__ */ new WeakMap();
3089
+ var watchedMountElements = /* @__PURE__ */ new Set();
3090
+ var watchedUnmountElements = /* @__PURE__ */ new Set();
3091
+ var sharedObserver = null;
3092
+ var mutationCounter = 0;
3093
+ var FULL_SWEEP_INTERVAL = 256;
3094
+ function fireMount(el) {
3095
+ const cbs = mountWatchers.get(el);
3096
+ if (!cbs) return;
3097
+ mountWatchers.delete(el);
3098
+ watchedMountElements.delete(el);
3099
+ for (const cb of cbs) {
3100
+ try {
3101
+ cb();
3102
+ } catch {
3103
+ }
3104
+ }
3105
+ }
3106
+ function fireUnmount(el) {
3107
+ const cbs = unmountWatchers.get(el);
3108
+ if (!cbs) return;
3109
+ queueMicrotask(() => {
3110
+ if (el.isConnected) return;
3111
+ const stillCbs = unmountWatchers.get(el);
3112
+ if (!stillCbs) return;
3113
+ unmountWatchers.delete(el);
3114
+ watchedUnmountElements.delete(el);
3115
+ for (const cb of stillCbs) {
3116
+ try {
3117
+ cb();
3118
+ } catch {
3119
+ }
3120
+ }
3121
+ });
3122
+ }
3123
+ function visitAddedNode(node) {
3124
+ if (watchedMountElements.size === 0) return;
3125
+ if (node.nodeType !== 1) return;
3126
+ const el = node;
3127
+ if (watchedMountElements.has(el) && el.isConnected) {
3128
+ fireMount(el);
3129
+ }
3130
+ if (el.firstElementChild) {
3131
+ for (const watched of Array.from(watchedMountElements)) {
3132
+ if (watched !== el && watched.isConnected && el.contains(watched)) {
3133
+ fireMount(watched);
3134
+ }
3135
+ }
3136
+ }
3137
+ }
3138
+ function visitRemovedNode(node) {
3139
+ if (watchedUnmountElements.size === 0) return;
3140
+ if (node.nodeType !== 1) return;
3141
+ const el = node;
3142
+ if (watchedUnmountElements.has(el) && !el.isConnected) {
3143
+ fireUnmount(el);
3144
+ }
3145
+ if (el.firstElementChild) {
3146
+ for (const watched of Array.from(watchedUnmountElements)) {
3147
+ if (watched !== el && !watched.isConnected && el.contains(watched)) {
3148
+ fireUnmount(watched);
3149
+ }
3150
+ }
3151
+ }
3152
+ }
3153
+ function fullSweep() {
3154
+ if (watchedMountElements.size > 0) {
3155
+ for (const el of Array.from(watchedMountElements)) {
3156
+ if (el.isConnected) fireMount(el);
3157
+ }
3158
+ }
3159
+ if (watchedUnmountElements.size > 0) {
3160
+ for (const el of Array.from(watchedUnmountElements)) {
3161
+ if (!el.isConnected) fireUnmount(el);
3162
+ }
3163
+ }
3164
+ }
3165
+ function ensureObserver() {
3166
+ if (sharedObserver || typeof document === "undefined") return;
3167
+ sharedObserver = new MutationObserver((mutations) => {
3168
+ for (const m of mutations) {
3169
+ if (m.type !== "childList") continue;
3170
+ if (m.addedNodes.length > 0) {
3171
+ for (let i2 = 0; i2 < m.addedNodes.length; i2++) {
3172
+ visitAddedNode(m.addedNodes[i2]);
3173
+ }
3174
+ }
3175
+ if (m.removedNodes.length > 0) {
3176
+ for (let i2 = 0; i2 < m.removedNodes.length; i2++) {
3177
+ visitRemovedNode(m.removedNodes[i2]);
3178
+ }
3179
+ }
3180
+ }
3181
+ mutationCounter += mutations.length;
3182
+ if (mutationCounter >= FULL_SWEEP_INTERVAL) {
3183
+ mutationCounter = 0;
3184
+ fullSweep();
3185
+ }
3186
+ maybeDisconnectObserver();
3187
+ });
3188
+ sharedObserver.observe(document.body, { childList: true, subtree: true });
3189
+ }
3190
+ function maybeDisconnectObserver() {
3191
+ if (!sharedObserver) return;
3192
+ if (watchedMountElements.size === 0 && watchedUnmountElements.size === 0) {
3193
+ sharedObserver.disconnect();
3194
+ sharedObserver = null;
3195
+ mutationCounter = 0;
3196
+ }
3197
+ }
3198
+ function registerMountWatcher(element, cb) {
3199
+ let list = mountWatchers.get(element);
3200
+ if (!list) {
3201
+ list = [];
3202
+ mountWatchers.set(element, list);
3203
+ }
3204
+ list.push(cb);
3205
+ watchedMountElements.add(element);
3206
+ ensureObserver();
3207
+ return () => {
3208
+ const cbs = mountWatchers.get(element);
3209
+ if (cbs) {
3210
+ const idx = cbs.indexOf(cb);
3211
+ if (idx !== -1) cbs.splice(idx, 1);
3212
+ if (cbs.length === 0) {
3213
+ mountWatchers.delete(element);
3214
+ watchedMountElements.delete(element);
3215
+ }
3216
+ }
3217
+ maybeDisconnectObserver();
3218
+ };
3219
+ }
3220
+ function registerUnmountWatcher(element, cb) {
3221
+ let list = unmountWatchers.get(element);
3222
+ if (!list) {
3223
+ list = [];
3224
+ unmountWatchers.set(element, list);
3225
+ }
3226
+ list.push(cb);
3227
+ watchedUnmountElements.add(element);
3228
+ ensureObserver();
3229
+ return () => {
3230
+ const cbs = unmountWatchers.get(element);
3231
+ if (cbs) {
3232
+ const idx = cbs.indexOf(cb);
3233
+ if (idx !== -1) cbs.splice(idx, 1);
3234
+ if (cbs.length === 0) {
3235
+ unmountWatchers.delete(element);
3236
+ watchedUnmountElements.delete(element);
3237
+ }
3238
+ }
3239
+ maybeDisconnectObserver();
3240
+ };
3241
+ }
2627
3242
  function onMount(callback, element) {
2628
3243
  if (typeof document === "undefined") return;
2629
3244
  if (element) {
3245
+ let disposed = false;
3246
+ registerDisposer(element, () => {
3247
+ disposed = true;
3248
+ });
2630
3249
  if (element.isConnected) {
2631
- queueMicrotask(() => runMountCallback(callback, "onMount", element));
3250
+ queueMicrotask(() => {
3251
+ if (disposed) return;
3252
+ runMountCallback(callback, "onMount", element);
3253
+ });
2632
3254
  return;
2633
3255
  }
2634
- const observer = new MutationObserver(() => {
2635
- if (element.isConnected) {
2636
- observer.disconnect();
2637
- runMountCallback(callback, "onMount", element);
2638
- }
2639
- });
2640
- registerDisposer(element, () => observer.disconnect());
2641
3256
  queueMicrotask(() => {
3257
+ if (disposed) return;
2642
3258
  if (element.isConnected) {
2643
3259
  runMountCallback(callback, "onMount", element);
2644
- } else {
2645
- observer.observe(document.body, { childList: true, subtree: true });
3260
+ return;
2646
3261
  }
3262
+ const unregister = registerMountWatcher(element, () => {
3263
+ if (disposed) return;
3264
+ runMountCallback(callback, "onMount", element);
3265
+ });
3266
+ registerDisposer(element, unregister);
2647
3267
  });
2648
3268
  } else {
2649
3269
  queueMicrotask(() => {
@@ -2652,22 +3272,24 @@ function onMount(callback, element) {
2652
3272
  }
2653
3273
  }
2654
3274
  function onUnmount(callback, element) {
2655
- registerDisposer(element, () => safeCall(callback, "onUnmount"));
2656
- const startObserving = () => {
2657
- const observer = new MutationObserver(() => {
2658
- if (!element.isConnected) {
2659
- observer.disconnect();
2660
- safeCall(callback, "onUnmount");
2661
- }
2662
- });
2663
- observer.observe(document.body, { childList: true, subtree: true });
2664
- registerDisposer(element, () => observer.disconnect());
3275
+ if (typeof document === "undefined") return;
3276
+ let fired = false;
3277
+ const fireOnce = () => {
3278
+ if (fired) return;
3279
+ fired = true;
3280
+ safeCall(callback, "onUnmount");
3281
+ };
3282
+ registerDisposer(element, fireOnce);
3283
+ const startWatching = () => {
3284
+ if (fired) return;
3285
+ const unregister = registerUnmountWatcher(element, fireOnce);
3286
+ registerDisposer(element, unregister);
2665
3287
  };
2666
3288
  if (element.isConnected) {
2667
- startObserving();
3289
+ startWatching();
2668
3290
  } else {
2669
3291
  onMount(() => {
2670
- startObserving();
3292
+ startWatching();
2671
3293
  return void 0;
2672
3294
  }, element);
2673
3295
  }
@@ -2679,9 +3301,11 @@ function onCleanup(callback, element) {
2679
3301
  // src/core/rendering/context.ts
2680
3302
  function context(defaultValue) {
2681
3303
  const [getValue, setValue] = signal(defaultValue);
2682
- return {
3304
+ const ctx = {
2683
3305
  provide(value) {
3306
+ const previous = getValue();
2684
3307
  setValue(value);
3308
+ return () => setValue(previous);
2685
3309
  },
2686
3310
  use() {
2687
3311
  return getValue;
@@ -2691,8 +3315,18 @@ function context(defaultValue) {
2691
3315
  },
2692
3316
  set(value) {
2693
3317
  setValue(value);
3318
+ },
3319
+ withContext(value, fn) {
3320
+ const previous = getValue();
3321
+ setValue(value);
3322
+ try {
3323
+ return fn();
3324
+ } finally {
3325
+ setValue(previous);
3326
+ }
2694
3327
  }
2695
3328
  };
3329
+ return ctx;
2696
3330
  }
2697
3331
 
2698
3332
  // src/core/strict.ts
@@ -2703,7 +3337,7 @@ function strict(fn) {
2703
3337
  try {
2704
3338
  fn();
2705
3339
  } catch (err) {
2706
- console.warn("[Sibu strict] second run threw:", err);
3340
+ console.warn("[SibuJS strict] second run threw:", err);
2707
3341
  }
2708
3342
  });
2709
3343
  }
@@ -2719,7 +3353,7 @@ function strictEffect(fn) {
2719
3353
  try {
2720
3354
  secondTeardown = effect(fn);
2721
3355
  } catch (err) {
2722
- console.warn("[Sibu strictEffect] second run threw:", err);
3356
+ console.warn("[SibuJS strictEffect] second run threw:", err);
2723
3357
  }
2724
3358
  });
2725
3359
  return () => {
@@ -2745,9 +3379,11 @@ function nextTick() {
2745
3379
  function defer(getter) {
2746
3380
  const [value, setValue] = signal(getter());
2747
3381
  let pending = false;
3382
+ let disposed = false;
2748
3383
  let latest = value();
2749
3384
  const flush = () => {
2750
3385
  pending = false;
3386
+ if (disposed) return;
2751
3387
  setValue(latest);
2752
3388
  };
2753
3389
  const schedule = () => {
@@ -2761,11 +3397,17 @@ function defer(getter) {
2761
3397
  }
2762
3398
  });
2763
3399
  };
2764
- track(() => {
3400
+ const teardown = track(() => {
2765
3401
  latest = getter();
2766
3402
  schedule();
2767
3403
  });
2768
- return value;
3404
+ const accessor = (() => value());
3405
+ accessor.dispose = () => {
3406
+ if (disposed) return;
3407
+ disposed = true;
3408
+ teardown();
3409
+ };
3410
+ return accessor;
2769
3411
  }
2770
3412
  var IDLE_FALLBACK_MS = 16;
2771
3413
  function scheduleIdle(fn) {
@@ -2806,6 +3448,32 @@ function transition() {
2806
3448
  }
2807
3449
 
2808
3450
  // src/core/rendering/lazy.ts
3451
+ var PENDING_ERROR = "__sibuPendingError";
3452
+ function dispatchPropagate(node, error) {
3453
+ const fire = () => {
3454
+ try {
3455
+ if (!node.parentNode) return false;
3456
+ node.dispatchEvent(new CustomEvent("sibu:error-propagate", { bubbles: true, detail: { error } }));
3457
+ return true;
3458
+ } catch {
3459
+ return false;
3460
+ }
3461
+ };
3462
+ if (node.parentNode && fire()) return;
3463
+ queueMicrotask(() => {
3464
+ if (fire()) return;
3465
+ node[PENDING_ERROR] = error;
3466
+ });
3467
+ }
3468
+ function takePendingError(node) {
3469
+ const rec = node;
3470
+ const err = rec[PENDING_ERROR];
3471
+ if (err instanceof Error) {
3472
+ delete rec[PENDING_ERROR];
3473
+ return err;
3474
+ }
3475
+ return void 0;
3476
+ }
2809
3477
  function lazy(importFn) {
2810
3478
  let cached = null;
2811
3479
  return function LazyComponent() {
@@ -2822,14 +3490,14 @@ function lazy(importFn) {
2822
3490
  }).catch((err) => {
2823
3491
  if (disposed) return;
2824
3492
  const errorObj = err instanceof Error ? err : new Error(String(err));
2825
- container.replaceChildren(div("sibu-lazy-error", `Failed to load component: ${errorObj.message}`));
3493
+ devWarn(`[SibuJS] lazy() failed to load component: ${errorObj.message}`);
3494
+ container.replaceChildren(div({ class: "sibu-lazy-error" }, `Failed to load component: ${errorObj.message}`));
3495
+ dispatchPropagate(container, errorObj);
2826
3496
  });
2827
3497
  container.appendChild(span("sibu-lazy-loading", "Loading..."));
2828
- const origRemove = container.remove.bind(container);
2829
- container.remove = () => {
3498
+ registerDisposer(container, () => {
2830
3499
  disposed = true;
2831
- origRemove();
2832
- };
3500
+ });
2833
3501
  return container;
2834
3502
  };
2835
3503
  }
@@ -2837,32 +3505,55 @@ function Suspense({ nodes, fallback }) {
2837
3505
  const container = div({ class: "sibu-suspense" });
2838
3506
  const fallbackEl = fallback();
2839
3507
  container.appendChild(fallbackEl);
3508
+ let suspenseDisposed = false;
3509
+ let observer = null;
3510
+ registerDisposer(container, () => {
3511
+ suspenseDisposed = true;
3512
+ if (observer) {
3513
+ observer.disconnect();
3514
+ observer = null;
3515
+ }
3516
+ });
2840
3517
  queueMicrotask(() => {
3518
+ if (suspenseDisposed) return;
2841
3519
  try {
2842
3520
  const childEl = nodes();
2843
3521
  if (childEl.classList.contains("sibu-lazy")) {
2844
- const observer = new MutationObserver(() => {
3522
+ if (!childEl.querySelector(".sibu-lazy-loading")) {
3523
+ container.replaceChildren(childEl);
3524
+ return;
3525
+ }
3526
+ observer = new MutationObserver(() => {
3527
+ if (suspenseDisposed) return;
2845
3528
  const loading = childEl.querySelector(".sibu-lazy-loading");
2846
3529
  if (!loading) {
2847
- observer.disconnect();
3530
+ observer?.disconnect();
3531
+ observer = null;
2848
3532
  container.replaceChildren(childEl);
2849
3533
  }
2850
3534
  });
2851
3535
  observer.observe(childEl, { childList: true, subtree: true });
2852
- if (!childEl.querySelector(".sibu-lazy-loading")) {
2853
- container.replaceChildren(childEl);
2854
- }
2855
3536
  } else {
2856
3537
  container.replaceChildren(childEl);
2857
3538
  }
2858
- } catch {
3539
+ } catch (err) {
3540
+ const errorObj = err instanceof Error ? err : new Error(String(err));
3541
+ devWarn(`[SibuJS] Suspense nodes() threw: ${errorObj.message}`);
3542
+ dispatchPropagate(container, errorObj);
2859
3543
  }
2860
3544
  });
2861
3545
  return container;
2862
3546
  }
2863
3547
 
3548
+ // src/platform/ssr.ts
3549
+ var _isDev10 = isDev();
3550
+ function trustHTML(html3) {
3551
+ return html3;
3552
+ }
3553
+ var DEFAULT_MAX_SSR_BYTES = 1024 * 1024;
3554
+
2864
3555
  // src/components/ErrorDisplay.ts
2865
- var _isDev8 = isDev();
3556
+ var _isDev11 = isDev();
2866
3557
  var STYLES = `
2867
3558
  .sibu-error-display {
2868
3559
  border: 1px solid var(--sibu-err-border, #e5484d);
@@ -2948,20 +3639,21 @@ var STYLES = `
2948
3639
  font-weight: 600;
2949
3640
  }
2950
3641
  .sibu-error-display .sibu-err-copy-btn {
2951
- background: transparent;
2952
- border: 1px solid #3a3a4e;
3642
+ background: rgba(0, 0, 0, 0.22);
3643
+ border: 1px solid rgba(255, 255, 255, 0.15);
2953
3644
  border-radius: 4px;
2954
- color: #a0a3b8;
3645
+ color: rgba(255, 255, 255, 0.85);
2955
3646
  cursor: pointer;
2956
3647
  padding: 2px 10px;
2957
- font-size: 0.95em;
3648
+ font-size: 0.78em;
2958
3649
  font-family: inherit;
2959
3650
  transition: all 0.12s ease;
3651
+ flex-shrink: 0;
2960
3652
  }
2961
3653
  .sibu-error-display .sibu-err-copy-btn:hover {
2962
- background: #2a2a3e;
2963
- color: #e5e7eb;
2964
- border-color: #4a4a5e;
3654
+ background: rgba(0, 0, 0, 0.35);
3655
+ color: white;
3656
+ border-color: rgba(255, 255, 255, 0.3);
2965
3657
  }
2966
3658
 
2967
3659
  .sibu-error-display .sibu-err-stack {
@@ -3086,21 +3778,25 @@ function normalizeError(err) {
3086
3778
  cause: null
3087
3779
  };
3088
3780
  }
3089
- function buildCopyText(err, meta2) {
3781
+ function buildCopyText(err, meta2, headline) {
3090
3782
  const lines = [];
3783
+ lines.push(headline);
3091
3784
  lines.push(`[${err.code}] ${err.message}`);
3092
3785
  if (err.stack) {
3093
3786
  lines.push("");
3787
+ lines.push("Stack Trace:");
3094
3788
  lines.push(err.stack);
3095
3789
  }
3096
- if (err.cause) {
3790
+ let cause = err.cause;
3791
+ while (cause) {
3097
3792
  lines.push("");
3098
3793
  lines.push("Caused by:");
3099
- lines.push(` [${err.cause.code}] ${err.cause.message}`);
3100
- if (err.cause.stack) {
3101
- const indented = err.cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
3794
+ lines.push(` [${cause.code}] ${cause.message}`);
3795
+ if (cause.stack) {
3796
+ const indented = cause.stack.split("\n").map((l) => ` ${l}`).join("\n");
3102
3797
  lines.push(indented);
3103
3798
  }
3799
+ cause = cause.cause;
3104
3800
  }
3105
3801
  if (meta2 && Object.keys(meta2).length > 0) {
3106
3802
  lines.push("");
@@ -3110,9 +3806,13 @@ function buildCopyText(err, meta2) {
3110
3806
  }
3111
3807
  }
3112
3808
  lines.push("");
3113
- lines.push(`At: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3809
+ lines.push("Environment:");
3810
+ lines.push(` Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3811
+ if (typeof location !== "undefined") {
3812
+ lines.push(` URL: ${location.href}`);
3813
+ }
3114
3814
  if (typeof navigator !== "undefined" && navigator.userAgent) {
3115
- lines.push(`UA: ${navigator.userAgent}`);
3815
+ lines.push(` User Agent: ${navigator.userAgent}`);
3116
3816
  }
3117
3817
  return lines.join("\n");
3118
3818
  }
@@ -3164,7 +3864,7 @@ function ErrorDisplay(props) {
3164
3864
  injectStyles();
3165
3865
  const severity = props.severity ?? "error";
3166
3866
  const normalized = normalizeError(props.error);
3167
- const showDetails = props.alwaysShowDetails ?? _isDev8;
3867
+ const showDetails = props.alwaysShowDetails ?? _isDev11;
3168
3868
  const headline = props.title ?? normalized.message;
3169
3869
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
3170
3870
  const [copyLabel, setCopyLabel] = signal("Copy");
@@ -3173,7 +3873,7 @@ function ErrorDisplay(props) {
3173
3873
  nodes: () => copyLabel(),
3174
3874
  on: {
3175
3875
  click: () => {
3176
- const text2 = buildCopyText(normalized, props.metadata);
3876
+ const text2 = buildCopyText(normalized, props.metadata, headline);
3177
3877
  if (typeof navigator !== "undefined" && navigator.clipboard) {
3178
3878
  navigator.clipboard.writeText(text2).then(
3179
3879
  () => {
@@ -3194,6 +3894,7 @@ function ErrorDisplay(props) {
3194
3894
  nodes: [
3195
3895
  code({ class: "sibu-err-icon", nodes: normalized.code }),
3196
3896
  h3({ class: "sibu-err-title", nodes: headline }),
3897
+ copyBtn,
3197
3898
  span({ class: "sibu-err-timestamp", nodes: timestamp })
3198
3899
  ]
3199
3900
  });
@@ -3205,25 +3906,12 @@ function ErrorDisplay(props) {
3205
3906
  nodes: [
3206
3907
  div({
3207
3908
  class: "sibu-err-section-head",
3208
- nodes: [span({ nodes: "Stack Trace" }), copyBtn]
3909
+ nodes: [span({ nodes: "Stack Trace" })]
3209
3910
  }),
3210
3911
  renderFrames(normalized.frames)
3211
3912
  ]
3212
3913
  })
3213
3914
  );
3214
- } else if (showDetails) {
3215
- bodyChildren.push(
3216
- div({
3217
- class: "sibu-err-section",
3218
- nodes: [
3219
- div({
3220
- class: "sibu-err-section-head",
3221
- nodes: [span({ nodes: "Details" }), copyBtn]
3222
- }),
3223
- div({ class: "sibu-err-stack", nodes: "(no stack available)" })
3224
- ]
3225
- })
3226
- );
3227
3915
  }
3228
3916
  if (showDetails) {
3229
3917
  bodyChildren.push(...renderCauseChain(normalized.cause));
@@ -3239,37 +3927,6 @@ function ErrorDisplay(props) {
3239
3927
  })
3240
3928
  );
3241
3929
  }
3242
- if (showDetails && typeof navigator !== "undefined" && navigator.userAgent) {
3243
- bodyChildren.push(
3244
- div({
3245
- class: "sibu-err-section",
3246
- nodes: [
3247
- div({ class: "sibu-err-section-head", nodes: [span({ nodes: "Environment" })] }),
3248
- div({
3249
- class: "sibu-err-meta",
3250
- nodes: (() => {
3251
- const dl2 = document.createElement("dl");
3252
- dl2.className = "sibu-err-meta";
3253
- const entries = [
3254
- ["User Agent", navigator.userAgent],
3255
- ["URL", typeof location !== "undefined" ? location.href : "(n/a)"],
3256
- ["Timestamp", (/* @__PURE__ */ new Date()).toISOString()]
3257
- ];
3258
- for (const [k, v] of entries) {
3259
- const dt2 = document.createElement("dt");
3260
- dt2.textContent = k;
3261
- const dd2 = document.createElement("dd");
3262
- dd2.textContent = v;
3263
- dl2.appendChild(dt2);
3264
- dl2.appendChild(dd2);
3265
- }
3266
- return dl2;
3267
- })()
3268
- })
3269
- ]
3270
- })
3271
- );
3272
- }
3273
3930
  const actionButtons = [];
3274
3931
  if (props.onRetry) {
3275
3932
  actionButtons.push(
@@ -3464,6 +4121,7 @@ function injectStyles2() {
3464
4121
  stylesInjected = true;
3465
4122
  }
3466
4123
  }
4124
+ var FALLBACK_CACHE_MAX = 50;
3467
4125
  var fallbackCache = /* @__PURE__ */ new WeakMap();
3468
4126
  function getMemoizedFallback(fallbackFn, error, retry) {
3469
4127
  let cache2 = fallbackCache.get(fallbackFn);
@@ -3472,27 +4130,42 @@ function getMemoizedFallback(fallbackFn, error, retry) {
3472
4130
  fallbackCache.set(fallbackFn, cache2);
3473
4131
  }
3474
4132
  const key = error.message;
3475
- if (!cache2.has(key)) {
3476
- cache2.set(key, fallbackFn(error, retry));
4133
+ let factory = cache2.get(key);
4134
+ if (factory) {
4135
+ cache2.delete(key);
4136
+ cache2.set(key, factory);
4137
+ } else {
4138
+ factory = () => fallbackFn(error, retry);
4139
+ cache2.set(key, factory);
4140
+ if (cache2.size > FALLBACK_CACHE_MAX) {
4141
+ const oldestKey = cache2.keys().next().value;
4142
+ if (oldestKey !== void 0) cache2.delete(oldestKey);
4143
+ }
3477
4144
  }
3478
- return cache2.get(key);
4145
+ return factory();
3479
4146
  }
3480
4147
  function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3481
4148
  injectStyles2();
3482
4149
  const [error, setError] = signal(null);
3483
4150
  const retry = () => {
3484
4151
  if (fallback) {
3485
- fallbackCache.delete(fallback);
4152
+ const cur = error();
4153
+ const inner = fallbackCache.get(fallback);
4154
+ if (cur && inner) inner.delete(cur.message);
3486
4155
  }
3487
4156
  setError(null);
3488
4157
  };
4158
+ let resetKeysTeardown = null;
3489
4159
  if (resetKeys && resetKeys.length > 0) {
3490
4160
  let initialized = false;
3491
- effect(() => {
4161
+ resetKeysTeardown = effect(() => {
3492
4162
  for (const k of resetKeys) {
3493
4163
  try {
3494
4164
  k();
3495
- } catch {
4165
+ } catch (err) {
4166
+ if (typeof console !== "undefined") {
4167
+ console.warn("[SibuJS ErrorBoundary] resetKeys getter threw:", err);
4168
+ }
3496
4169
  }
3497
4170
  }
3498
4171
  if (!initialized) {
@@ -3505,7 +4178,15 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3505
4178
  const handleError = (e) => {
3506
4179
  const errorObj = e instanceof Error ? e : new Error(String(e));
3507
4180
  setError(errorObj);
3508
- onError?.(errorObj);
4181
+ if (onError) {
4182
+ try {
4183
+ onError(errorObj);
4184
+ } catch (cbErr) {
4185
+ if (typeof console !== "undefined") {
4186
+ console.error("[SibuJS ErrorBoundary] onError callback threw:", cbErr);
4187
+ }
4188
+ }
4189
+ }
3509
4190
  return errorObj;
3510
4191
  };
3511
4192
  const defaultFallback = (err, retryFn) => {
@@ -3557,7 +4238,7 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3557
4238
  }
3558
4239
  }
3559
4240
  });
3560
- container.addEventListener("sibu:error-propagate", (e) => {
4241
+ const propagateListener = (e) => {
3561
4242
  if (error()) return;
3562
4243
  e.stopPropagation();
3563
4244
  const customEvent = e;
@@ -3565,6 +4246,30 @@ function ErrorBoundary({ nodes, fallback, onError, resetKeys }) {
3565
4246
  if (propagatedError) {
3566
4247
  handleError(propagatedError);
3567
4248
  }
4249
+ };
4250
+ container.addEventListener("sibu:error-propagate", propagateListener);
4251
+ onMount(() => {
4252
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT);
4253
+ const collected = [];
4254
+ let node = walker.currentNode;
4255
+ while (node) {
4256
+ const pending = takePendingError(node);
4257
+ if (pending) collected.push(pending);
4258
+ node = walker.nextNode();
4259
+ }
4260
+ if (collected.length === 1) {
4261
+ handleError(collected[0]);
4262
+ } else if (collected.length > 1) {
4263
+ const Agg = globalThis.AggregateError;
4264
+ handleError(
4265
+ Agg ? new Agg(collected, `${collected.length} pre-mount errors caught by ErrorBoundary`) : new Error(collected.map((e) => e.message).join("; "))
4266
+ );
4267
+ }
4268
+ return void 0;
4269
+ }, container);
4270
+ registerDisposer(container, () => {
4271
+ if (resetKeysTeardown) resetKeysTeardown();
4272
+ container.removeEventListener("sibu:error-propagate", propagateListener);
3568
4273
  });
3569
4274
  return container;
3570
4275
  }
@@ -3740,6 +4445,7 @@ function Loading(props = {}) {
3740
4445
  footer,
3741
4446
  form,
3742
4447
  g,
4448
+ getSSRStore,
3743
4449
  getSlot,
3744
4450
  h1,
3745
4451
  h2,
@@ -3809,15 +4515,18 @@ function Loading(props = {}) {
3809
4515
  registerComponent,
3810
4516
  registerDisposer,
3811
4517
  resolveComponent,
4518
+ retrack,
3812
4519
  rp,
3813
4520
  rt,
3814
4521
  ruby,
4522
+ runInSSRContext,
3815
4523
  s,
3816
4524
  samp,
3817
4525
  script,
3818
4526
  section,
3819
4527
  select,
3820
4528
  setGlobalErrorHandler,
4529
+ setMaxDrainIterations,
3821
4530
  show,
3822
4531
  signal,
3823
4532
  slot,
@@ -3837,6 +4546,7 @@ function Loading(props = {}) {
3837
4546
  symbol,
3838
4547
  table,
3839
4548
  tagFactory,
4549
+ takePendingError,
3840
4550
  tbody,
3841
4551
  td,
3842
4552
  template,
@@ -3851,6 +4561,7 @@ function Loading(props = {}) {
3851
4561
  track,
3852
4562
  transition,
3853
4563
  trapFocus,
4564
+ trustHTML,
3854
4565
  tspan,
3855
4566
  u,
3856
4567
  ul,