sibujs 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +118 -15
  3. package/dist/browser.cjs +410 -101
  4. package/dist/browser.d.cts +50 -5
  5. package/dist/browser.d.ts +50 -5
  6. package/dist/browser.js +8 -6
  7. package/dist/build.cjs +777 -385
  8. package/dist/build.js +15 -15
  9. package/dist/cdn.global.js +11 -7
  10. package/dist/{chunk-JCO7AHNU.js → chunk-3EKJK5FZ.js} +73 -34
  11. package/dist/chunk-4MFZMLUU.js +147 -0
  12. package/dist/chunk-52XFPGSN.js +123 -0
  13. package/dist/chunk-5INI7D2L.js +194 -0
  14. package/dist/{chunk-OAVXAFAY.js → chunk-6LG2T7LO.js} +5 -5
  15. package/dist/{chunk-OOUQUCNN.js → chunk-ACBT6UZS.js} +57 -22
  16. package/dist/{chunk-F7HW2NI5.js → chunk-AW6HPJ3L.js} +1 -1
  17. package/dist/chunk-DHDZ7IVN.js +109 -0
  18. package/dist/{chunk-JNGGM2XE.js → chunk-DQVUBVNX.js} +16 -25
  19. package/dist/{chunk-AXFWGY32.js → chunk-DR7H6UGM.js} +232 -67
  20. package/dist/{chunk-WSGLJYFI.js → chunk-E62UADKL.js} +9 -3
  21. package/dist/{chunk-3Z2NGWQI.js → chunk-ES2GERY2.js} +1 -1
  22. package/dist/{chunk-S3V6ZXPI.js → chunk-EWEE7NBF.js} +115 -41
  23. package/dist/{chunk-3DVGCW7O.js → chunk-F64ZPCFE.js} +8 -8
  24. package/dist/{chunk-APFRCIRI.js → chunk-GCSGGA3Q.js} +295 -198
  25. package/dist/{chunk-EX77FXTT.js → chunk-HYCCIYNS.js} +228 -51
  26. package/dist/{chunk-ELXMRSVN.js → chunk-NF3LYCQH.js} +259 -124
  27. package/dist/chunk-O3QQ3XOL.js +61 -0
  28. package/dist/{chunk-BUKYCWHX.js → chunk-PBQEW5VW.js} +14 -10
  29. package/dist/{chunk-H5FI5WDP.js → chunk-RRIDJQ5C.js} +3 -3
  30. package/dist/{chunk-S3NFJO6L.js → chunk-TIRZCERI.js} +36 -5
  31. package/dist/{chunk-ODXVQ3CS.js → chunk-VFHIVEPF.js} +309 -188
  32. package/dist/chunk-VPP2FONR.js +142 -0
  33. package/dist/{chunk-KZBEVFXZ.js → chunk-W55YHPEP.js} +5 -3
  34. package/dist/{chunk-FKQG7FTY.js → chunk-WN6WDFCB.js} +4 -4
  35. package/dist/{chunk-CEBWDD63.js → chunk-WOOMUJFT.js} +228 -86
  36. package/dist/{chunk-ITWCTZYH.js → chunk-Z2CE42DB.js} +19 -25
  37. package/dist/{contracts-ey_Qh8ef.d.cts → contracts-Hg6ITAEF.d.cts} +6 -6
  38. package/dist/{contracts-ey_Qh8ef.d.ts → contracts-Hg6ITAEF.d.ts} +6 -6
  39. package/dist/{customElement-CPfIrbvg.d.ts → customElement-CKLoO63w.d.cts} +73 -11
  40. package/dist/{customElement-CPfIrbvg.d.cts → customElement-CKLoO63w.d.ts} +73 -11
  41. package/dist/data.cjs +483 -205
  42. package/dist/data.d.cts +139 -7
  43. package/dist/data.d.ts +139 -7
  44. package/dist/data.js +9 -9
  45. package/dist/devtools.cjs +222 -87
  46. package/dist/devtools.js +8 -8
  47. package/dist/dispose-4FZ6OFXR.js +19 -0
  48. package/dist/ecosystem.cjs +419 -175
  49. package/dist/ecosystem.js +11 -11
  50. package/dist/extras.cjs +1981 -765
  51. package/dist/extras.d.cts +7 -7
  52. package/dist/extras.d.ts +7 -7
  53. package/dist/extras.js +29 -26
  54. package/dist/index.cjs +784 -385
  55. package/dist/index.d.cts +299 -28
  56. package/dist/index.d.ts +299 -28
  57. package/dist/index.js +33 -18
  58. package/dist/motion.cjs +180 -50
  59. package/dist/motion.d.cts +26 -0
  60. package/dist/motion.d.ts +26 -0
  61. package/dist/motion.js +5 -5
  62. package/dist/patterns.cjs +437 -170
  63. package/dist/patterns.d.cts +60 -13
  64. package/dist/patterns.d.ts +60 -13
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.cjs +333 -148
  67. package/dist/performance.d.cts +55 -7
  68. package/dist/performance.d.ts +55 -7
  69. package/dist/performance.js +9 -9
  70. package/dist/plugins.cjs +1374 -576
  71. package/dist/plugins.d.cts +227 -9
  72. package/dist/plugins.d.ts +227 -9
  73. package/dist/plugins.js +613 -321
  74. package/dist/{ssr-D62yFwuw.d.cts → ssr-BiPRdZ6n.d.cts} +44 -15
  75. package/dist/{ssr-D62yFwuw.d.ts → ssr-BiPRdZ6n.d.ts} +44 -15
  76. package/dist/{ssr-2PPULEK2.js → ssr-JFP5535P.js} +5 -4
  77. package/dist/ssr.cjs +1104 -443
  78. package/dist/ssr.d.cts +230 -20
  79. package/dist/ssr.d.ts +230 -20
  80. package/dist/ssr.js +15 -12
  81. package/dist/{startup-0Qv6aosO.d.cts → startup-BMpaiMhP.d.cts} +1 -1
  82. package/dist/{startup-0Qv6aosO.d.ts → startup-BMpaiMhP.d.ts} +1 -1
  83. package/dist/testing.cjs +205 -63
  84. package/dist/testing.d.cts +1 -1
  85. package/dist/testing.d.ts +1 -1
  86. package/dist/testing.js +8 -10
  87. package/dist/ui.cjs +640 -199
  88. package/dist/ui.d.cts +2 -2
  89. package/dist/ui.d.ts +2 -2
  90. package/dist/ui.js +10 -10
  91. package/dist/widgets.cjs +207 -82
  92. package/dist/widgets.js +9 -9
  93. package/package.json +12 -7
  94. package/dist/chunk-5VGSK6D2.js +0 -80
  95. package/dist/chunk-COY6PUD2.js +0 -34
  96. package/dist/chunk-K35BAR4P.js +0 -96
  97. package/dist/chunk-L3GAGWCC.js +0 -111
  98. package/dist/chunk-RT3IYFN2.js +0 -38
package/dist/index.d.cts CHANGED
@@ -1,12 +1,16 @@
1
1
  import { N as NodeChild, a as NodeChildren, T as TagProps } from './tagFactory-Bzupt4Pj.cjs';
2
2
  export { D as Dispose, S as SVG_NS, t as tagFactory } from './tagFactory-Bzupt4Pj.cjs';
3
- export { T as TrustedHTML, t as trustHTML } from './ssr-D62yFwuw.cjs';
3
+ export { T as TrustedHTML, t as trustHTML } from './ssr-BiPRdZ6n.cjs';
4
4
  import { R as ReactiveSignal } from './signal-BnWpq6WB.cjs';
5
5
 
6
6
  interface ErrorBoundaryOptions {
7
7
  /**
8
- * Fallback renderer given an Error and retry callback.
9
- * Memoized internally — only re-created when the error changes.
8
+ * Fallback renderer, called with this boundary's Error and its own `retry`.
9
+ *
10
+ * Safe to share one fallback function across many boundaries: the Error and
11
+ * the retry callback always belong to the boundary that invoked it, even when
12
+ * two boundaries fail with identical messages. It is re-invoked whenever the
13
+ * boundary re-renders its error state; do not rely on it running only once.
10
14
  */
11
15
  fallback?: (error: Error, retry: () => void) => Element;
12
16
  /**
@@ -14,11 +18,27 @@ interface ErrorBoundaryOptions {
14
18
  */
15
19
  onError?: (error: Error) => void;
16
20
  /**
17
- * A list of reactive getters. Whenever any of these values change
18
- * after an error has been caught, the boundary automatically resets
19
- * (clears the error and re-renders). Useful for recovering from a
20
- * failed render after the user navigates, changes filters, or
21
- * otherwise picks a new input that might not fail this time.
21
+ * A list of reactive getters. Whenever any of these VALUES changes AFTER an
22
+ * error has been caught, the boundary automatically resets (clears the error
23
+ * and re-renders). Useful for recovering from a failed render after the user
24
+ * navigates, changes filters, or otherwise picks a new input that might not
25
+ * fail this time.
26
+ *
27
+ * Getters are selectors, compared by `Object.is` against the values captured
28
+ * when the error was caught. Replacing the source object without changing the
29
+ * selected result does not reset the boundary:
30
+ *
31
+ * ```ts
32
+ * resetKeys: [() => route().pathname] // only the pathname matters
33
+ * ```
34
+ *
35
+ * "After" is literal: a key change that is itself the reason the children
36
+ * threw belongs to that failure and will not undo it. Only a change observed
37
+ * once the boundary is already showing its fallback triggers recovery.
38
+ *
39
+ * The getters are evaluated only while the boundary is failed, so a getter
40
+ * that throws is reported through the runtime error pipeline at that point
41
+ * rather than at construction.
22
42
  *
23
43
  * @example
24
44
  * ```ts
@@ -44,7 +64,8 @@ type ErrorBoundaryProps = ErrorBoundaryOptions;
44
64
  * `unhandledrejection` events instead.
45
65
  * - Supports nested ErrorBoundaries (inner catches first, outer catches propagation)
46
66
  * - Retry functionality to clear error and re-render children
47
- * - Memoized fallback to avoid re-creating fallback UI on every render
67
+ * - Fallback state is per-boundary: a shared fallback function never leaks one
68
+ * boundary's Error or retry callback into another
48
69
  * - onError callback for logging/telemetry
49
70
  * - Improved CSS styling
50
71
  */
@@ -122,6 +143,77 @@ interface LoadingProps {
122
143
  */
123
144
  declare function Loading(props?: LoadingProps): HTMLElement;
124
145
 
146
+ /**
147
+ * Central runtime error pipeline.
148
+ *
149
+ * WHY THIS EXISTS
150
+ * ---------------
151
+ * The reactive runtime has to *contain* application exceptions: a subscriber
152
+ * that throws must not abort the notification drain, or one broken component
153
+ * would freeze every unrelated binding on the page. Containment, however, was
154
+ * previously implemented as a bare `catch` that only warned when a dev-mode
155
+ * flag was on — so in a production build an application exception thrown from
156
+ * an effect re-run became literally nothing: no log, no handler, no signal that
157
+ * anything had happened.
158
+ *
159
+ * Containment is not silence. This module keeps the containment and makes the
160
+ * failure observable, with ONE resolution order for every phase of the runtime:
161
+ *
162
+ * 1. the nearest `ErrorBoundary`, but only if it EXPLICITLY claims the error
163
+ * 2. the application's configured runtime error handler
164
+ * 3. `console.error` — in production builds too, never gated on dev mode
165
+ *
166
+ * Every `catch` in the runtime that is not re-thrown routes here, so
167
+ * applications have exactly one place to install reporting/telemetry.
168
+ */
169
+ /** The runtime activity that was executing when the error escaped. */
170
+ type RuntimeErrorPhase = "effect" | "binding" | "derived" | "cleanup" | "event" | "async" | "render" | "scheduler";
171
+ interface RuntimeErrorContext {
172
+ /** Which part of the runtime was executing. */
173
+ phase: RuntimeErrorPhase;
174
+ /** Debug label of the failing effect/computed/binding, when it has one. */
175
+ name?: string;
176
+ /**
177
+ * DOM node the failure is associated with, when one is known. Given a node,
178
+ * an enclosing `ErrorBoundary` gets first refusal — but only a boundary that
179
+ * explicitly claims the error stops the fallback chain. See `reportError`.
180
+ */
181
+ node?: unknown;
182
+ }
183
+ type RuntimeErrorHandler = (error: unknown, context: RuntimeErrorContext) => void;
184
+ /**
185
+ * Install an application-wide handler for errors the runtime caught and
186
+ * contained. Returns the previously installed handler so callers can restore
187
+ * it (tests, nested frameworks). Pass `null` to clear.
188
+ *
189
+ * The handler replaces the default `console.error` reporting. Throwing from
190
+ * inside the handler falls back to the default, so a broken reporter cannot
191
+ * itself silence the runtime.
192
+ *
193
+ * This is application/runtime-global state, NOT request-scoped: under SSR it is
194
+ * shared by every concurrent request in the process. Install it once at startup
195
+ * and derive any request-specific detail from the error/context instead.
196
+ */
197
+ declare function setRuntimeErrorHandler(handler: RuntimeErrorHandler | null): RuntimeErrorHandler | null;
198
+ /** The currently installed runtime error handler, if any. */
199
+ declare function getRuntimeErrorHandler(): RuntimeErrorHandler | null;
200
+ /**
201
+ * Report an error the runtime caught and contained.
202
+ *
203
+ * Resolution order: nearest claiming `ErrorBoundary` → configured runtime
204
+ * handler → `console.error`. Exactly one of those runs for a given report.
205
+ *
206
+ * Never throws — callers are inside `catch`/`finally` blocks where a secondary
207
+ * failure would corrupt teardown or abort a drain.
208
+ *
209
+ * @public Framework primitive, also usable by plugin/integration code that
210
+ * catches an application exception on SibuJS's behalf (custom rendering
211
+ * primitives, adapters) and wants it to follow the same boundary/handler
212
+ * resolution as a built-in one. Ordinary application code should prefer
213
+ * `ErrorBoundary` or `setRuntimeErrorHandler`.
214
+ */
215
+ declare function reportError(error: unknown, context: RuntimeErrorContext): void;
216
+
125
217
  /**
126
218
  * An action is a reusable element-level behavior.
127
219
  * It receives the element and an optional parameter, and may return
@@ -163,6 +255,7 @@ declare function getAction<T = unknown>(name: string): ActionFn<T> | undefined;
163
255
  * ```
164
256
  */
165
257
  declare function action<T>(element: HTMLElement, action: ActionFn<T> | string, param: T): void;
258
+ declare function action<T>(element: HTMLElement, action: ActionFn<T | undefined> | string): void;
166
259
  declare function action(element: HTMLElement, action: ActionFn<void> | string): void;
167
260
  /**
168
261
  * Fires a callback when the user clicks outside the element.
@@ -414,11 +507,50 @@ declare function when<T>(condition: () => T, thenBranch: () => NodeChild, elseBr
414
507
  */
415
508
  declare function match<T extends string | number>(value: () => T, cases: Record<string, () => NodeChild>, fallback?: () => NodeChild): Comment;
416
509
 
510
+ /**
511
+ * Safety ceiling on the **total number of teardowns a single drain may execute**.
512
+ *
513
+ * A cleanup registering another cleanup is legitimate — a parent teardown
514
+ * releasing a child, a lifecycle hook re-arming — so a drain keeps going until
515
+ * the queue is stable, and ordinary finite chains of practical depth complete.
516
+ *
517
+ * The ceiling counts total teardown executions, not iterations over the queue.
518
+ * Capping iterations abandons finite work purely for crossing the boundary,
519
+ * which is indistinguishable from a leak; capping total work distinguishes
520
+ * ordinary reentrant chains from typical runaway behaviour far better.
521
+ *
522
+ * It is nonetheless an **absolute work bound**, not a recursion detector. It
523
+ * primarily protects against cleanup production that does not terminate — most
524
+ * often recursive self-registration — but an exceptionally large *finite* chain
525
+ * needing more than this many executions reaches it just the same. Either way
526
+ * the condition is reported rather than silently swallowed, so bounded
527
+ * protection never passes as completed cleanup — see {@link reportDrainRunaway}.
528
+ */
529
+ declare const MAX_DRAIN_TEARDOWNS = 10000;
530
+ /**
531
+ * Report a cleanup queue that did not stabilise within {@link MAX_DRAIN_TEARDOWNS}.
532
+ *
533
+ * Uses the existing console-based lifecycle convention; deliberately not a new
534
+ * public error API. Reported unconditionally (not dev-gated) because it means
535
+ * the framework stopped doing teardown work it was asked to do.
536
+ */
537
+ declare function reportDrainRunaway(label: string, executed: number, remaining: number): void;
417
538
  /**
418
539
  * Register a teardown function for a DOM node.
419
540
  * When dispose(node) is called, all registered teardowns run.
420
541
  */
421
542
  declare function registerDisposer(node: Node, teardown: () => void): void;
543
+ /**
544
+ * Drop a previously registered teardown for a node without running it.
545
+ *
546
+ * For owners that can be released independently of their node — an enhancement
547
+ * disposed while its server markup stays on the page, and possibly re-enhanced
548
+ * afterwards — the node-level entry would otherwise accumulate one dead closure
549
+ * per generation, since `dispose()` is the only thing that clears the map.
550
+ * The teardown is assumed to have already run (or to be deliberately abandoned);
551
+ * this only releases the reference.
552
+ */
553
+ declare function unregisterDisposer(node: Node, teardown: () => void): void;
422
554
  /**
423
555
  * Run all registered teardowns for a node and its descendants,
424
556
  * cleaning up reactive subscriptions to prevent memory leaks.
@@ -428,6 +560,25 @@ declare function registerDisposer(node: Node, teardown: () => void): void;
428
560
  * on deeply nested DOM trees.
429
561
  */
430
562
  declare function dispose(node: Node): void;
563
+ /**
564
+ * Replace every child of `parent` with `next`, disposing the outgoing children
565
+ * first.
566
+ *
567
+ * Native `replaceChildren()` detaches nodes without running SibuJS teardown, so
568
+ * any reactive binding, lifecycle hook, or listener inside the removed subtree
569
+ * survives as an unreachable zombie: it keeps firing against detached DOM and
570
+ * is never collected. This helper enforces the disposal invariant — a
571
+ * SibuJS-owned node removed permanently from the DOM is disposed exactly once.
572
+ *
573
+ * **A node in `next` is never disposed, even when it currently sits somewhere
574
+ * inside an outgoing subtree.** Native `replaceChildren()` would move such a
575
+ * node out of the content being replaced and keep it alive, and this helper
576
+ * preserves those semantics: incoming nodes are detached *before* the outgoing
577
+ * roots are disposed, so the dispose-walk cannot reach them. Everything else in
578
+ * those outgoing subtrees is still torn down, so preserving one descendant does
579
+ * not leak its former siblings or ancestors.
580
+ */
581
+ declare function replaceChildrenSafely(parent: ParentNode, ...next: Node[]): void;
431
582
  /**
432
583
  * Check for potential binding leaks. Returns the number of active DOM bindings.
433
584
  * In dev mode, logs a warning if the count exceeds the threshold.
@@ -499,9 +650,19 @@ declare function DynamicComponent(is: () => string | Component$1): HTMLElement;
499
650
  * skipping DOM operations for nodes that are part of the LIS.
500
651
  *
501
652
  * The render callback receives reactive getters `() => T` and `() => number`
502
- * instead of plain values. This ensures the callback always reads fresh data
503
- * when a keyed item's data changes but its key stays the same, since the DOM
504
- * is reused without re-calling render.
653
+ * instead of plain values. Each keyed row owns its own item and index cells,
654
+ * written by reconciliation when the row is reused. `render` therefore runs
655
+ * exactly ONCE per key — identity is preserved across updates — while anything
656
+ * inside the row that reads `item()` or `index()` re-runs when reconciliation
657
+ * assigns that row a new item or position.
658
+ *
659
+ * Identity is not value freshness: a row keeps its DOM node when its key is
660
+ * unchanged, and still updates its contents when the item behind that key is
661
+ * replaced.
662
+ *
663
+ * Reading `item()` subscribes to the ROW's cell, not to the whole-array signal,
664
+ * so a row only re-renders when its own item/index actually changes — mutating
665
+ * an unrelated row does not disturb it.
505
666
  *
506
667
  * @param getArray A reactive getter returning an array.
507
668
  * @param render A function that receives reactive item and index getters and returns a NodeChild.
@@ -1105,8 +1266,26 @@ interface AsyncDerivedState<T> {
1105
1266
  loading: () => boolean;
1106
1267
  /** The last caught error, or `null`. */
1107
1268
  error: () => unknown | null;
1108
- /** Manually re-run the async computation. */
1269
+ /** Manually re-run the async computation. No-op after `dispose()`. */
1109
1270
  refresh: () => void;
1271
+ /**
1272
+ * Stop the derivation permanently: unsubscribe from every tracked source,
1273
+ * abort the in-flight run, and ignore any promise that resolves afterwards.
1274
+ *
1275
+ * Idempotent. An `asyncDerived` created inside a component or route must be
1276
+ * disposed when that scope tears down — otherwise it stays subscribed to its
1277
+ * sources for the lifetime of the page and keeps re-running.
1278
+ */
1279
+ dispose: () => void;
1280
+ }
1281
+ /** Per-run context handed to the factory. */
1282
+ interface AsyncDerivedContext {
1283
+ /**
1284
+ * Aborted when a newer run supersedes this one, or when the derivation is
1285
+ * disposed. Forward it to `fetch` (or any abortable API) to cancel work that
1286
+ * can no longer affect the result.
1287
+ */
1288
+ signal: AbortSignal;
1110
1289
  }
1111
1290
  /**
1112
1291
  * `asyncDerived` is the async counterpart of `derived`: it takes a factory
@@ -1116,7 +1295,35 @@ interface AsyncDerivedState<T> {
1116
1295
  *
1117
1296
  * Stale responses are dropped: if a new run starts before an older one
1118
1297
  * resolves, the older one's result is ignored. This prevents flicker when
1119
- * dependencies change rapidly (e.g. typing in a search box).
1298
+ * dependencies change rapidly (e.g. typing in a search box). The superseded
1299
+ * run's `AbortSignal` is also aborted, so abortable work (e.g. `fetch`) is
1300
+ * cancelled rather than merely ignored. The run-id guard is retained
1301
+ * regardless, because not every async API honours `AbortSignal` — aborting
1302
+ * alone does not close the stale-completion race.
1303
+ *
1304
+ * ## Dependency tracking and `await`
1305
+ *
1306
+ * Reactive reads are tracked **synchronously**, so only the reads that happen
1307
+ * BEFORE the factory's first `await` become dependencies:
1308
+ *
1309
+ * ```ts
1310
+ * asyncDerived(async () => {
1311
+ * const a = sourceA(); // tracked — runs synchronously
1312
+ * await something();
1313
+ * const b = sourceB(); // NOT tracked — the tracking context is gone
1314
+ * });
1315
+ * ```
1316
+ *
1317
+ * This is a property of synchronous tracking, not a bug: SibuJS does not
1318
+ * install async-context machinery to follow reads across suspension points.
1319
+ * Read every dependency up front (before the first `await`) if the derivation
1320
+ * must react to it, or call `refresh()` explicitly.
1321
+ *
1322
+ * ## Ownership
1323
+ *
1324
+ * `asyncDerived` subscribes to its sources until `dispose()` is called. It is
1325
+ * not attached to an enclosing scope automatically — dispose it from the
1326
+ * owning component's cleanup.
1120
1327
  *
1121
1328
  * Unlike `query()` or `resource()`, `asyncDerived` has no caching or retry
1122
1329
  * logic — it's a minimal async-reactivity primitive suited for ad-hoc
@@ -1136,7 +1343,7 @@ interface AsyncDerivedState<T> {
1136
1343
  * }, []);
1137
1344
  * ```
1138
1345
  */
1139
- declare function asyncDerived<T>(factory: () => Promise<T>, initial: T): AsyncDerivedState<T>;
1346
+ declare function asyncDerived<T>(factory: (context: AsyncDerivedContext) => Promise<T>, initial: T): AsyncDerivedState<T>;
1140
1347
 
1141
1348
  /**
1142
1349
  * Deep equality comparison for objects and arrays.
@@ -1179,13 +1386,23 @@ declare function deepSignal<T>(initial: T): [Accessor<T>, (next: T | ((prev: T)
1179
1386
  * derived-of-derived chains propagate correctly without paying the full
1180
1387
  * Set-delete + re-add cost of track()'s cleanup phase.
1181
1388
  *
1182
- * NOTE: a previous revision experimented with three-color (CLEAN/CHECK/DIRTY)
1183
- * state for read-side value-change short-circuiting. It regressed every
1184
- * benchmark except Memory (Deep Chain +122%, Component Tree +20%) because
1185
- * the workloads always produce a new downstream value and CHECK had no
1186
- * work to skip only overhead to add. Keeping the simpler boolean flag
1187
- * here; revisit CHECK propagation when we have benchmarks that exercise
1188
- * stabilisation on diamond / conditional-branch patterns.
1389
+ * STABILIZATION why a dirty flag is enough:
1390
+ *
1391
+ * A dirty computed does NOT imply a changed value. Downstream effects are
1392
+ * enqueued by `propagateDirty` at write time, before this computed has had a
1393
+ * chance to recompute and compare. Rather than adding a three-color
1394
+ * (CLEAN/CHECK/DIRTY) propagation pass which an earlier revision measured as
1395
+ * a regression on every benchmark, because the extra state has nothing to skip
1396
+ * when values genuinely change — the engine settles the question lazily at
1397
+ * DRAIN time: `cs._validate` recomputes a dirty computed and `cs.__v` is bumped
1398
+ * ONLY when the new value differs under this computed's comparator. The
1399
+ * scheduler compares that version against what each subscriber last observed
1400
+ * and suppresses the run when nothing changed (see `depsChanged` in
1401
+ * ../../reactivity/track-core.ts).
1402
+ *
1403
+ * That keeps the cheap boolean dirty flag AND makes `equals` actually stop
1404
+ * propagation, with recomputation still fully lazy: `_validate` only ever runs
1405
+ * when an effect is genuinely about to observe the value.
1189
1406
  */
1190
1407
  declare function derived<T>(getter: () => T, options?: {
1191
1408
  name?: string;
@@ -1373,10 +1590,6 @@ declare function writable<T>(get: () => T, set: (value: T) => void, options?: {
1373
1590
  * withSSR(() => renderToString(...));
1374
1591
  * runInSSRContext(() => renderToString(...));
1375
1592
  */
1376
- /**
1377
- * Per-request SSR store. Currently holds the SSR flag plus a
1378
- * suspense-id counter so concurrent streaming renders never collide.
1379
- */
1380
1593
  interface SSRStore {
1381
1594
  ssr: boolean;
1382
1595
  suspenseIdCounter: number;
@@ -1386,6 +1599,17 @@ interface SSRStore {
1386
1599
  * server renders. Typed loosely to avoid a dependency cycle with data/.
1387
1600
  */
1388
1601
  caches?: Map<string, Map<string, unknown>>;
1602
+ /**
1603
+ * The locale this request has selected, or `undefined` when it has not
1604
+ * chosen one and should follow the application default.
1605
+ *
1606
+ * A plain string rather than a signal: reactive locale switching is a client
1607
+ * concern (one page, one active locale, subscribers to notify), while a
1608
+ * server render reads the value once per request and never re-renders. Giving
1609
+ * every request its own signal would allocate subscriber machinery nothing
1610
+ * will ever use. See `plugins/i18n.ts`.
1611
+ */
1612
+ locale?: string;
1389
1613
  }
1390
1614
  /** Returns the active store (ALS or fallback). */
1391
1615
  declare function getSSRStore(): SSRStore;
@@ -1503,9 +1727,26 @@ type EnhanceSetup = (ctx: EnhanceContext) => void | (() => void);
1503
1727
  * replacing it. Returns a dispose function; disposal is also wired to the
1504
1728
  * element, so removing its subtree cleans everything up.
1505
1729
  *
1730
+ * **Setup is a transaction.** If `setup` throws, every binding, listener and
1731
+ * cleanup it registered through the {@link EnhanceContext} is torn down and the
1732
+ * original error is rethrown unchanged — the element is left exactly as
1733
+ * unenhanced as it started, so a retry is legal. The rollback covers
1734
+ * framework-owned resources only (`ctx.on`, `ctx.text`, `ctx.attr`,
1735
+ * `ctx.classed`, `ctx.show`, `ctx.model`, `ctx.cleanup`, and a cleanup returned
1736
+ * from setup); work the setup performed outside those helpers — `innerHTML`
1737
+ * writes, network calls, global mutation — cannot be reversed generically, so
1738
+ * register its undo with `ctx.cleanup()` as you go.
1739
+ *
1740
+ * **Ownership.** A successfully enhanced root is marked
1741
+ * `data-sibu-enhanced="true"`; disposal removes it. The marker tracks *current*
1742
+ * ownership rather than history, so a **disposed root can be enhanced again**,
1743
+ * while enhancing a root that is still active is refused (dev-warns) to prevent
1744
+ * two competing sets of bindings. See `docs/architecture/enhancement-lifecycle.md`.
1745
+ *
1506
1746
  * @param target An `Element` or a CSS selector resolved against `document`
1507
1747
  * (the first match is used; see {@link enhanceAll} for many).
1508
1748
  * @param setup Wires reactivity via the {@link EnhanceContext}.
1749
+ * @throws Whatever `setup` throws, after rolling its resources back.
1509
1750
  *
1510
1751
  * @example
1511
1752
  * ```ts
@@ -1521,6 +1762,19 @@ declare function enhance(target: Element | string, setup: EnhanceSetup): () => v
1521
1762
  /**
1522
1763
  * Enhance every element matching a selector. Returns a single dispose that
1523
1764
  * tears down all of them.
1765
+ *
1766
+ * The whole collection is one transaction: if any element's setup throws, the
1767
+ * elements already enhanced are rolled back (newest first, mirroring stack
1768
+ * unwinding) and the original error is rethrown. A caller that never received
1769
+ * the aggregate disposer is therefore never left holding live enhancements it
1770
+ * has no way to release. A teardown that fails during that rollback is reported
1771
+ * and skipped — it neither aborts the remaining rollback nor replaces the
1772
+ * original setup error.
1773
+ *
1774
+ * The returned disposer is idempotent, and the collection may be enhanced again
1775
+ * afterwards (see {@link enhance} on marker lifetime).
1776
+ *
1777
+ * @throws Whatever the failing element's setup threw, after rolling back.
1524
1778
  */
1525
1779
  declare function enhanceAll(selector: string, setup: EnhanceSetup): () => void;
1526
1780
 
@@ -1575,6 +1829,23 @@ interface MountIslandsOptions {
1575
1829
  * - `visible` — when the element scrolls into view (IntersectionObserver).
1576
1830
  * - `interaction` — on first pointer/focus/key/touch interaction.
1577
1831
  * - `media` — when `data-sibu-media` (a media query) matches.
1832
+ *
1833
+ * **Error isolation is lifecycle isolation.** Each island is one `enhance()`
1834
+ * transaction, so an island whose setup throws is reported and leaves zero live
1835
+ * bindings and zero live listeners; siblings activate normally, and the failed
1836
+ * island can be mounted again once its setup is fixed.
1837
+ *
1838
+ * **Remounting is supported.** Cleanup disposes each island and releases its
1839
+ * `data-sibu-enhanced` marker, so the same server markup can be mounted again
1840
+ * with a fresh generation. Mounting twice *without* cleanup is also safe:
1841
+ * islands that currently own an enhancement are skipped, never double-wired.
1842
+ *
1843
+ * Activation can never outlive its owner. Cleanup landing before a lazy chunk
1844
+ * resolves prevents activation entirely, and cleanup landing *during* a setup
1845
+ * disposes the island rather than stranding it — DISPOSED never becomes ACTIVE.
1846
+ * `data-sibu-hydrated` is hydration provenance rather than ownership, and is
1847
+ * therefore not cleared on disposal; see
1848
+ * `docs/architecture/enhancement-lifecycle.md`.
1578
1849
  */
1579
1850
  declare function mountIslands(root?: ParentNode | null, options?: MountIslandsOptions): () => void;
1580
1851
 
@@ -1710,7 +1981,7 @@ declare function isTrackingSuspended(): boolean;
1710
1981
  declare function untracked$1<T>(fn: () => T): T;
1711
1982
  declare function retrack$1(effectFn: () => void, subscriber: Subscriber): void;
1712
1983
  declare function track(effectFn: () => void, subscriber?: Subscriber): () => void;
1713
- declare function reactiveBinding(commit: () => void): () => void;
1984
+ declare function reactiveBinding(commit: () => void, ownerNode?: unknown): () => void;
1714
1985
  declare function recordDependency(signal: ReactiveSignal): void;
1715
1986
  declare function cleanup(subscriber: Subscriber): void;
1716
1987
  declare function setMaxSubscriberRepeats(n: number): number;
@@ -1750,4 +2021,4 @@ declare const untracked: ReactiveApi["untracked"];
1750
2021
  declare const retrack: ReactiveApi["retrack"];
1751
2022
  declare const setMaxDrainIterations: ReactiveApi["setMaxDrainIterations"];
1752
2023
 
1753
- export { type Accessor, type ActionFn, type AnchorProps, type ArrayActions, type AsyncDerivedState, type AudioProps, type ButtonProps, type Context, DynamicComponent, type EffectBody, type EffectOptions, type EnhanceContext, type EnhanceSetup, ErrorBoundary, type ErrorBoundaryOptions, type ErrorBoundaryProps, ErrorDisplay, type ErrorDisplayProps, type ErrorSeverity, type FormProps, Fragment, type ImgProps, type InputProps, type InputType, type IslandLoader, type IslandRegistration, type IslandStrategy, KeepAlive, type KeepAliveOptions, type LabelProps, Loading, type LoadingProps, type LongPressOptions, type MediaProps, type MountIslandsOptions, NodeChild, NodeChildren, type OnCleanup, type OptionProps, Portal, type Ref, type SSRStore, type SelectProps, type SignalOptions, type SlotFn, type Slots, type StoreActions, Suspense, type SuspenseProps, TagProps, type TextareaProps, type TypedTagFunction, type VideoProps, __resetIdCounter, a, abbr, action, address, area, array, article, aside, asyncDerived, audio, autoResize, b, base, batch, bdi, bdo, bindDynamic, blockquote, body, br, button, canvas, caption, catchError, catchErrorAsync, center, checkLeaks, circle, cite, clickOutside, clipPath, code, col, colgroup, context, copyOnClick, createId, customElement, data, datalist, dd, deepEqual, deepSignal, defer, defs, del, derived, details, dfn, dialog, disableSSR, dispose, div, dl, dt, each, effect, ellipse, em, embed, enableSSR, enhance, enhanceAll, enqueueBatchedSignal, fieldset, figcaption, figure, font, footer, form, g, getAction, getRequestScopedCache, getSSRStore, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, lazyIsland, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, menu, meta, meter, mount, mountIslands, nav, nextTick, noscript, object, ol, on, onCleanup, onMount, onUnmount, optgroup, option, output, p, param, path, pattern, picture, polygon, polyline, portal, pre, progress, q, radialGradient, reactiveArray, rect, ref, registerAction, registerComponent, registerDisposer, registerIsland, resolveComponent, retrack, rp, rt, ruby, runInSSRContext, s, samp, script, section, select, setGlobalErrorHandler, setMaxDrainIterations, show, signal, slot, small, source, span, stop, store, strict, strictEffect, strong, style, sub, summary, sup, svg, symbol, table, takePendingError, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track$1 as track, transition, trapFocus, tspan, u, ul, unregisterComponent, unregisterIsland, untracked, use, var_, video, watch, when, withSSR, writable };
2024
+ export { type Accessor, type ActionFn, type AnchorProps, type ArrayActions, type AsyncDerivedContext, type AsyncDerivedState, type AudioProps, type ButtonProps, type Context, DynamicComponent, type EffectBody, type EffectOptions, type EnhanceContext, type EnhanceSetup, ErrorBoundary, type ErrorBoundaryOptions, type ErrorBoundaryProps, ErrorDisplay, type ErrorDisplayProps, type ErrorSeverity, type FormProps, Fragment, type ImgProps, type InputProps, type InputType, type IslandLoader, type IslandRegistration, type IslandStrategy, KeepAlive, type KeepAliveOptions, type LabelProps, Loading, type LoadingProps, type LongPressOptions, MAX_DRAIN_TEARDOWNS, type MediaProps, type MountIslandsOptions, NodeChild, NodeChildren, type OnCleanup, type OptionProps, Portal, type Ref, type RuntimeErrorContext, type RuntimeErrorHandler, type RuntimeErrorPhase, type SSRStore, type SelectProps, type SignalOptions, type SlotFn, type Slots, type StoreActions, Suspense, type SuspenseProps, TagProps, type TextareaProps, type TypedTagFunction, type VideoProps, __resetIdCounter, a, abbr, action, address, area, array, article, aside, asyncDerived, audio, autoResize, b, base, batch, bdi, bdo, bindDynamic, blockquote, body, br, button, canvas, caption, catchError, catchErrorAsync, center, checkLeaks, circle, cite, clickOutside, clipPath, code, col, colgroup, context, copyOnClick, createId, customElement, data, datalist, dd, deepEqual, deepSignal, defer, defs, del, derived, details, dfn, dialog, disableSSR, dispose, div, dl, dt, each, effect, ellipse, em, embed, enableSSR, enhance, enhanceAll, enqueueBatchedSignal, fieldset, figcaption, figure, font, footer, form, g, getAction, getRequestScopedCache, getRuntimeErrorHandler, getSSRStore, getSlot, h1, h2, h3, h4, h5, h6, head, header, hr, html, i, iframe, img, input, ins, isBatching, isSSR, kbd, label, lazy, lazyIsland, legend, li, line, linearGradient, link, longPress, main, map, mark, marker, marquee, mask, match, math, menu, meta, meter, mount, mountIslands, nav, nextTick, noscript, object, ol, on, onCleanup, onMount, onUnmount, optgroup, option, output, p, param, path, pattern, picture, polygon, polyline, portal, pre, progress, q, radialGradient, reactiveArray, rect, ref, registerAction, registerComponent, registerDisposer, registerIsland, replaceChildrenSafely, reportDrainRunaway, reportError, resolveComponent, retrack, rp, rt, ruby, runInSSRContext, s, samp, script, section, select, setGlobalErrorHandler, setMaxDrainIterations, setRuntimeErrorHandler, show, signal, slot, small, source, span, stop, store, strict, strictEffect, strong, style, sub, summary, sup, svg, symbol, table, takePendingError, tbody, td, template, text, textarea, tfoot, th, thead, time, title, tr, track$1 as track, transition, trapFocus, tspan, u, ul, unregisterComponent, unregisterDisposer, unregisterIsland, untracked, use, var_, video, watch, when, withSSR, writable };