@solidjs/signals 2.0.0-rc.8 → 2.0.0-rc.9

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 (90) hide show
  1. package/dist/dev-shared.js +1399 -285
  2. package/dist/dev.attribution.js +459 -307
  3. package/dist/dev.js +1864 -435
  4. package/dist/observe/affects.js +3 -1
  5. package/dist/observe/attribution.js +7 -1
  6. package/dist/observe/boundaries.js +209 -154
  7. package/dist/observe/core/action.js +18 -8
  8. package/dist/observe/core/async.js +220 -110
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +23 -1
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +262 -485
  14. package/dist/observe/core/constants.js +34 -1
  15. package/dist/observe/core/context.js +3 -3
  16. package/dist/observe/core/core.js +867 -367
  17. package/dist/observe/core/dev.js +78 -17
  18. package/dist/observe/core/effect.js +67 -51
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/external.js +4 -4
  21. package/dist/observe/core/graph.js +37 -37
  22. package/dist/observe/core/heap.js +45 -45
  23. package/dist/observe/core/invariants.js +2 -0
  24. package/dist/observe/core/lanes.js +86 -49
  25. package/dist/observe/core/optimistic.js +242 -95
  26. package/dist/observe/core/owner.js +98 -84
  27. package/dist/observe/core/scheduler.js +543 -305
  28. package/dist/observe/core/verdict.js +247 -129
  29. package/dist/observe/index.js +7 -3
  30. package/dist/observe/map.js +126 -124
  31. package/dist/observe/signals.js +33 -17
  32. package/dist/observe/store/index.js +2 -0
  33. package/dist/observe/store/next/optimistic.js +141 -132
  34. package/dist/observe/store/next/projection.js +34 -21
  35. package/dist/observe/store/next/reconcile.js +58 -56
  36. package/dist/observe/store/next/store.js +260 -146
  37. package/dist/observe/store/store.js +5 -3
  38. package/dist/observe/store/utils.js +948 -135
  39. package/dist/prod/attribution.js +26 -17
  40. package/dist/prod/boundaries.js +128 -76
  41. package/dist/prod/core/action.js +16 -8
  42. package/dist/prod/core/async.js +251 -143
  43. package/dist/prod/core/constants.js +34 -1
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +843 -347
  46. package/dist/prod/core/dev.js +17 -1
  47. package/dist/prod/core/effect.js +48 -34
  48. package/dist/prod/core/error-hooks.js +71 -0
  49. package/dist/prod/core/external.js +4 -4
  50. package/dist/prod/core/graph.js +37 -37
  51. package/dist/prod/core/heap.js +45 -45
  52. package/dist/prod/core/lanes.js +90 -53
  53. package/dist/prod/core/optimistic.js +247 -100
  54. package/dist/prod/core/owner.js +57 -45
  55. package/dist/prod/core/scheduler.js +543 -305
  56. package/dist/prod/core/verdict.js +245 -127
  57. package/dist/prod/index.js +7 -3
  58. package/dist/prod/map.js +112 -112
  59. package/dist/prod/signals.js +28 -12
  60. package/dist/prod/store/next/optimistic.js +135 -128
  61. package/dist/prod/store/next/projection.js +31 -20
  62. package/dist/prod/store/next/store.js +325 -252
  63. package/dist/prod/store/store.js +2 -2
  64. package/dist/prod/store/utils.js +946 -135
  65. package/dist/types/attribution.d.ts +7 -2
  66. package/dist/types/attribution.prod.d.ts +10 -1
  67. package/dist/types/boundaries.d.ts +10 -1
  68. package/dist/types/core/action.d.ts +12 -5
  69. package/dist/types/core/attribution-costs.d.ts +35 -0
  70. package/dist/types/core/attribution-feedback.d.ts +133 -0
  71. package/dist/types/core/attribution-hooks.d.ts +28 -3
  72. package/dist/types/core/attribution-queries.d.ts +10 -0
  73. package/dist/types/core/attribution.d.ts +51 -172
  74. package/dist/types/core/constants.d.ts +33 -0
  75. package/dist/types/core/core.d.ts +186 -5
  76. package/dist/types/core/dev.d.ts +129 -9
  77. package/dist/types/core/error-hooks.d.ts +71 -0
  78. package/dist/types/core/index.d.ts +3 -1
  79. package/dist/types/core/invariants.d.ts +4 -0
  80. package/dist/types/core/lanes.d.ts +31 -4
  81. package/dist/types/core/scheduler.d.ts +95 -2
  82. package/dist/types/core/types.d.ts +3 -0
  83. package/dist/types/index.d.ts +2 -2
  84. package/dist/types/signals.d.ts +8 -0
  85. package/dist/types/store/index.d.ts +2 -1
  86. package/dist/types/store/next/optimistic.d.ts +1 -1
  87. package/dist/types/store/next/store.d.ts +6 -5
  88. package/dist/types/store/next/target.d.ts +1 -1
  89. package/dist/types/store/utils.d.ts +177 -6
  90. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  import { setAttributionHooks } from "./attribution-hooks.js";
2
2
 
3
- import { $REFRESH, NOT_PENDING } from "./constants.js";
3
+ import { NOT_PENDING, CONFIG_DERIVED_OVERRIDE } from "./constants.js";
4
4
 
5
- import { ownerPath, reportDiagnostic, emitDiagnostic, isSuppressed, anyExcluded, isExcluded, GRAPH_SIZE_WARN_AT } from "./dev.js";
5
+ import { reportDiagnostic, emitDiagnostic, isSuppressed, recordSubject, anyExcluded, isExcluded, GRAPH_SIZE_WARN_AT, ownerPath } from "./dev.js";
6
6
 
7
7
  /**
8
8
  * Under an owner the observer marked as its own (`OBSERVE.exclude`): the
@@ -68,50 +68,22 @@ function clearListeners() {
68
68
  for (const e in recordListeners) recordListeners[e].clear();
69
69
  }
70
70
 
71
- const now = typeof performance !== "undefined" ? () => performance.now() : () => Date.now();
71
+ /** @internal The engine's clock: `performance.now()` where it exists. */ const now = typeof performance !== "undefined" ? () => performance.now() : () => Date.now();
72
72
 
73
73
  const frames = [];
74
74
 
75
- const scopeCosts = new Map;
75
+ const folds = [];
76
76
 
77
- const writeCosts = new Map;
77
+ /** @internal */ function registerFold(e) {
78
+ folds.push(e);
79
+ }
78
80
 
79
- function rootsOf(e, n) {
81
+ /** @internal Root cause names of a cause chain — the writes/landings/refreshes the chain bottoms out in. */ function rootsOf(e, n) {
80
82
  for (const t of e) {
81
83
  if (t.kind === "derived" && t.causes && t.causes.length > 0) rootsOf(t.causes, n); else n.add(t.name);
82
84
  }
83
85
  }
84
86
 
85
- function recordCosts(e) {
86
- let n = scopeCosts.get(e.node);
87
- if (n === undefined) {
88
- n = {
89
- name: e.nodeName,
90
- kind: e.nodeKind,
91
- runs: 0,
92
- selfMs: 0,
93
- wastedMs: 0,
94
- overlayMs: 0
95
- };
96
- scopeCosts.set(e.node, n);
97
- }
98
- n.runs++;
99
- n.selfMs += e.selfMs;
100
- if (e.phase !== "plain") n.overlayMs += e.selfMs; else if (!e.changed && !e.held) n.wastedMs += e.selfMs;
101
- const t = new Set;
102
- rootsOf(e.causes, t);
103
- for (const n of t) {
104
- let t = writeCosts.get(n);
105
- if (t === undefined) writeCosts.set(n, t = {
106
- name: n,
107
- runs: 0,
108
- downstreamMs: 0
109
- });
110
- t.runs++;
111
- t.downstreamMs += e.selfMs;
112
- }
113
- }
114
-
115
87
  function nodeName(e) {
116
88
  return e._name ?? "anonymous";
117
89
  }
@@ -215,6 +187,40 @@ function currentOrigin() {
215
187
  return currentInteraction ?? EXTERNAL_ORIGIN;
216
188
  }
217
189
 
190
+ /** The root origin a cause list traces back to — the stamp of the nearest root write, derived links walked. */ function originIn(e) {
191
+ for (const n of e) {
192
+ const e = n.kind === "derived" ? n.causes !== undefined ? originIn(n.causes) : undefined : n.origin;
193
+ if (e !== undefined && e.kind !== "external") return e;
194
+ }
195
+ return undefined;
196
+ }
197
+
198
+ /**
199
+ * The `currentOrigin` hook: what a runtime recording its own fact right now
200
+ * (a server-function call) should stamp it with. Inside a recompute the
201
+ * fact belongs to the change that caused the run — a `createAsync` calling
202
+ * the server on a navigation's write is the navigation's, and through it the
203
+ * click's — walked down past create runs the way `trackFlightStart` does,
204
+ * since a node born inside a parent's run inherits the parent's causality.
205
+ * Outside one — or when the causes were themselves external (a memo a
206
+ * handler pulls, stale from a timer's write) — it is the write's answer
207
+ * (`currentOrigin`), minus the external sentinel: "none known" is
208
+ * `undefined` on a record, as `HoldEvent.origin` has it. The objects
209
+ * returned are the engine's own frames, so the caller's record joins
210
+ * `InteractionEvent.origin` / `NavigationEvent.origin` by identity.
211
+ */ function ambientOrigin() {
212
+ for (let e = frames.length - 1; e >= 0; e--) {
213
+ const n = frames[e].causes;
214
+ if (n !== null) {
215
+ const e = originIn(n);
216
+ if (e !== undefined) return e;
217
+ break;
218
+ }
219
+ }
220
+ const e = currentOrigin();
221
+ return e === EXTERNAL_ORIGIN ? undefined : e;
222
+ }
223
+
218
224
  const effectFrames = new WeakMap;
219
225
 
220
226
  /** The interaction the innermost open frame runs under, else the ambient one. */ function enclosingInteraction() {
@@ -234,7 +240,7 @@ function pushFrame(e, n, t, i) {
234
240
  if (e.we !== undefined) o.run = e.we;
235
241
  effectFrames.set(o, {
236
242
  node: i,
237
- causes: e.ke
243
+ causes: e.ve
238
244
  });
239
245
  }
240
246
  originFrames.push(o);
@@ -320,7 +326,7 @@ function originEnd() {
320
326
  /** Record a root change (setSignal / refresh / async landing) on the node. */
321
327
  /** Live subscriber count, walked on demand — the core keeps no counter. */ function countSubscribers(e) {
322
328
  let n = 0;
323
- for (let t = e.u; t !== null; t = t.se) n++;
329
+ for (let t = e.u; t !== null; t = t.ie) n++;
324
330
  return n;
325
331
  }
326
332
 
@@ -339,8 +345,8 @@ function originEnd() {
339
345
  const i = countSubscribers(e);
340
346
  const o = e;
341
347
  if (i < t || i >= GRAPH_SIZE_WARN_AT) return;
342
- if (i < (o.ve ?? 0) * 2) return;
343
- o.ve = i;
348
+ if (i < (o.$e ?? 0) * 2) return;
349
+ o.$e = i;
344
350
  const r = n === "refresh" ? "refresh of" : n === "async" ? "async landing on" : "write to";
345
351
  const s = `[WIDE_WRITE] ${r} "${nodeName(e)}" reached ${i} subscribers — every one ` + `re-runs this flush. If consumers ask keyed questions of this value (for example every ` + `row comparing against one selected id), invert it: keep the answer in a store used as a ` + `map keyed by id, so each consumer reads its own key and only the keys that flipped update.`;
346
352
  reportDiagnostic(emitDiagnostic({
@@ -369,10 +375,10 @@ function stampWrite(e, n, t = NO_VALUES, i = NO_VALUES) {
369
375
  o.origin = n === "async" ? asyncOrigin(e) : currentOrigin();
370
376
  o.at = now();
371
377
  o.stack = captureStack();
372
- const r = e.$e;
373
- e.$e = o;
378
+ const r = e.ke;
379
+ e.ke = o;
374
380
  noteNavigationWrite(r, o);
375
- noteInteractionWrite(o.origin);
381
+ noteInteractionWrite(o.origin, excludedNode(e));
376
382
  if (n === "write") trackEffectWrite(e, o, i);
377
383
  // stampWrite is the single funnel for committed root invalidations (sync
378
384
  // writes, refresh(), async landings), which makes it the one place the
@@ -381,7 +387,7 @@ function stampWrite(e, n, t = NO_VALUES, i = NO_VALUES) {
381
387
  }
382
388
 
383
389
  /** Record a derived change (memo produced a new value) with its causes. */ function stampDerived(e, n) {
384
- e.$e = {
390
+ e.ke = {
385
391
  seq: ++changeSeq,
386
392
  kind: "derived",
387
393
  name: nodeName(e),
@@ -397,10 +403,10 @@ function stampWrite(e, n, t = NO_VALUES, i = NO_VALUES) {
397
403
  */ function collectCauses(e) {
398
404
  const n = e.Le ?? 0;
399
405
  const t = [];
400
- const i = e.$e;
406
+ const i = e.ke;
401
407
  if (i !== undefined && i.seq > n && i.kind === "refresh") t.push(i);
402
- for (let i = e.te; i !== null; i = i.ne) {
403
- const e = i.oe.$e;
408
+ for (let i = e.ee; i !== null; i = i.te) {
409
+ const e = i.re.ke;
404
410
  if (e !== undefined && e.seq > n) t.push(e);
405
411
  }
406
412
  return t;
@@ -410,9 +416,19 @@ function stampWrite(e, n, t = NO_VALUES, i = NO_VALUES) {
410
416
  e.Le = changeSeq;
411
417
  }
412
418
 
413
- /** Snapshot the node's current dep identities (call before a run replaces them). */ function captureDeps(e) {
419
+ /**
420
+ * Snapshot the dep identities of the node's last pass (call before a run
421
+ * replaces them, or after it to read the fresh set). The validated prefix
422
+ * [`_deps`..`_depsTail`] is that pass's set; links past the tail are a
423
+ * previous pass's, kept linked while the frame they fed is still the
424
+ * committed one (A30 — a staged memo pass, or an effect pass whose run is
425
+ * still owed) and not part of the subscription diff.
426
+ */ function captureDeps(e) {
414
427
  const n = [];
415
- for (let t = e.te; t !== null; t = t.ne) n.push(t.oe);
428
+ for (let t = e.ee; t !== null; t = t.te) {
429
+ n.push(t.re);
430
+ if (t === e.Fe) break;
431
+ }
416
432
  return n;
417
433
  }
418
434
 
@@ -426,14 +442,16 @@ function stampWrite(e, n, t = NO_VALUES, i = NO_VALUES) {
426
442
  if (n === false) return;
427
443
  let t = 0;
428
444
  const i = [];
429
- for (let n = e.te; n !== null; n = n.ne) {
445
+ for (let n = e.ee; n !== null; n = n.te) {
430
446
  t++;
431
- if (i.length < 12) i.push(nodeName(n.oe));
432
- }
447
+ if (i.length < 12) i.push(nodeName(n.re));
448
+ if (n === e.Fe) break;
449
+ // the validated prefix, as captureDeps
450
+ }
433
451
  const o = e;
434
- if (t < n || t < (o.Me ?? 0) * 1.5) return;
435
- o.Me = t;
436
- const r = e.Re ? "effect" : "memo";
452
+ if (t < n || t < (o.We ?? 0) * 1.5) return;
453
+ o.We = t;
454
+ const r = e.Pe ? "effect" : "memo";
437
455
  const s = `[WIDE_SCOPE_DEPS] ${r} "${nodeName(e)}" is subscribed to ${t} sources — ` + `it re-runs when any of them change. Narrow its reads or split it into smaller memos. ` + `Sources: ${i.join(", ")}${t > i.length ? ", …" : ""}`;
438
456
  reportDiagnostic(emitDiagnostic({
439
457
  code: "WIDE_SCOPE_DEPS",
@@ -462,14 +480,14 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
462
480
  if (t === false) return;
463
481
  const i = e;
464
482
  const o = Date.now();
465
- if (i.Ce === undefined || o - i.Ce > t.windowMs) {
466
- i.Ce = o;
467
- i.Fe = 0;
468
- i.He = false;
483
+ if (i.Me === undefined || o - i.Me > t.windowMs) {
484
+ i.Me = o;
485
+ i.He = 0;
486
+ i.qe = false;
469
487
  }
470
- i.Fe = (i.Fe ?? 0) + 1;
471
- if (i.He || i.Fe < t.count) return;
472
- i.He = true;
488
+ i.He = (i.He ?? 0) + 1;
489
+ if (i.qe || i.He < t.count) return;
490
+ i.qe = true;
473
491
  // Root-cause key: the set of originating writes behind this scope's latest
474
492
  // re-run. Scopes hot from the SAME roots share one aggregation window.
475
493
  const r = new Set;
@@ -486,10 +504,10 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
486
504
  hotCauses.set(s, a);
487
505
  }
488
506
  a.scopes++;
489
- a.runs += i.Fe;
507
+ a.runs += i.He;
490
508
  if (a.scopes === 1) {
491
509
  const r = n.causes.map(e => `"${e.name}" (${e.kind})`).join(", ");
492
- const s = `[HOT_SCOPE_RERUNS] ${n.nodeKind} "${n.nodeName}" re-ran ${i.Fe} times ` + `in ${Math.max(1, o - i.Ce)}ms — a hot signal is likely leaking into this ` + `scope. Latest cause: ${r || "(untracked pull)"}`;
510
+ const s = `[HOT_SCOPE_RERUNS] ${n.nodeKind} "${n.nodeName}" re-ran ${i.He} times ` + `in ${Math.max(1, o - i.Me)}ms — a hot signal is likely leaking into this ` + `scope. Latest cause: ${r || "(untracked pull)"}`;
493
511
  reportDiagnostic(emitDiagnostic({
494
512
  code: "HOT_SCOPE_RERUNS",
495
513
  kind: "perf",
@@ -497,7 +515,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
497
515
  message: s,
498
516
  nodeName: n.nodeName,
499
517
  data: {
500
- runs: i.Fe,
518
+ runs: i.He,
501
519
  windowMs: t.windowMs,
502
520
  causes: n.causes.map(e => e.name)
503
521
  }
@@ -535,16 +553,16 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
535
553
  if (t === false) return;
536
554
  const i = e;
537
555
  const o = now();
538
- if (i.We === undefined || o - i.We > t.windowMs) {
539
- i.We = o;
540
- i.qe = 0;
541
- i.Be = false;
556
+ if (i.xe === undefined || o - i.xe > t.windowMs) {
557
+ i.xe = o;
558
+ i.je = 0;
559
+ i.Ve = false;
542
560
  }
543
- i.qe = (i.qe ?? 0) + n.selfMs;
544
- if (i.Be || i.qe < t.budgetMs) return;
545
- i.Be = true;
561
+ i.je = (i.je ?? 0) + n.selfMs;
562
+ if (i.Ve || i.je < t.budgetMs) return;
563
+ i.Ve = true;
546
564
  const r = n.causes.map(e => `"${e.name}" (${e.kind})`).join(", ");
547
- const s = `[HOT_SCOPE_TIME] ${n.nodeKind} "${n.nodeName}" spent ` + `${i.qe.toFixed(1)}ms of compute inside one ${t.windowMs}ms window ` + `(budget ${t.budgetMs}ms). Latest cause: ${r || "(untracked pull)"}`;
565
+ const s = `[HOT_SCOPE_TIME] ${n.nodeKind} "${n.nodeName}" spent ` + `${i.je.toFixed(1)}ms of compute inside one ${t.windowMs}ms window ` + `(budget ${t.budgetMs}ms). Latest cause: ${r || "(untracked pull)"}`;
548
566
  reportDiagnostic(emitDiagnostic({
549
567
  code: "HOT_SCOPE_TIME",
550
568
  kind: "perf",
@@ -552,7 +570,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
552
570
  message: s,
553
571
  nodeName: n.nodeName,
554
572
  data: {
555
- spentMs: i.qe,
573
+ spentMs: i.je,
556
574
  budgetMs: t.budgetMs,
557
575
  windowMs: t.windowMs,
558
576
  causes: n.causes.map(e => e.name)
@@ -564,34 +582,34 @@ function recordRerun(e, n, t, i, o, r) {
564
582
  const s = n.causes;
565
583
  const a = n.prevDeps;
566
584
  const c = e;
567
- const d = c.ke;
585
+ const f = c.ve;
568
586
  if (excludedNode(e)) {
569
587
  // The observer's own computation: keep the per-node bookkeeping its
570
588
  // effect phase reads (see effectRunStart) and record nothing.
571
- c.xe = n.interaction;
589
+ c.Ye = n.interaction;
572
590
  c.we = undefined;
573
- c.ke = s;
591
+ c.ve = s;
574
592
  return;
575
593
  }
576
594
  // Subscription diff: `prevDeps` was captured at run entry; `_deps` now
577
595
  // holds the fresh set. A changed set is the "helper edit changed distant
578
596
  // call sites" signal — surfaced per-event and in the console format.
579
- const f = captureDeps(e);
597
+ const d = captureDeps(e);
580
598
  const u = new Set(a);
581
- const l = new Set(f);
599
+ const l = new Set(d);
582
600
  const h = [];
583
601
  const m = [];
584
- for (const e of f) if (!u.has(e)) h.push(nodeName(e));
602
+ for (const e of d) if (!u.has(e)) h.push(nodeName(e));
585
603
  for (const e of a) if (!l.has(e)) m.push(nodeName(e));
586
604
  const p = {
587
605
  run: ++runSeq,
588
606
  at: n.start,
589
- nodeRuns: c.je = (c.je ?? 0) + 1,
590
- nodeKind: e.Re ? "effect" : "memo",
607
+ nodeRuns: c.Be = (c.Be ?? 0) + 1,
608
+ nodeKind: e.Pe ? "effect" : "memo",
591
609
  nodeName: nodeName(e),
592
- node: e,
610
+ nodeId: devId(e),
593
611
  causes: s,
594
- depCount: f.length,
612
+ depCount: d.length,
595
613
  depsAdded: h,
596
614
  depsRemoved: m,
597
615
  selfMs: t.selfMs,
@@ -605,16 +623,19 @@ function recordRerun(e, n, t, i, o, r) {
605
623
  // The effect phase runs later in the flush with no cause list of its own:
606
624
  // it inherits this run's interaction and is joined to this run's causes
607
625
  // (see effectRunStart / pushFrame).
608
- c.xe = g;
626
+ c.Ye = g;
609
627
  c.we = p.run;
610
- c.ke = s;
628
+ c.ve = s;
629
+ // The record is serializable and never carries the node; keep the node
630
+ // beside it for `OBSERVE.subjectOf` and the engine's own joins (effect
631
+ // cycles, relay tears, `why()`), for as long as anyone holds the record.
632
+ recordSubject(p, e);
611
633
  history.push(p);
612
634
  if (history.length > options.historyLimit) history.shift();
613
- recordCosts(p);
614
- recordFeedbackRun(p);
635
+ for (const n of folds) n.rerun?.(e, p);
615
636
  noteInteractionRun(g, t.selfMs, false);
616
637
  if (p.nodeKind === "effect") checkEffectCycle(e, s);
617
- checkRelayTear(e, s, d);
638
+ checkRelayTear(e, s, f);
618
639
  checkHotRuns(e, p);
619
640
  checkHotTime(e, p);
620
641
  checkDepWidth(e);
@@ -710,15 +731,15 @@ function shallowEquivalent(e, n) {
710
731
  const o = e;
711
732
  if (n === t || // paranoia: changed runs should never hit this
712
733
  !isPlainShape(n) || !isPlainShape(t) || !shallowEquivalent(n, t)) {
713
- o.Ye = 0;
714
- o.Ve = false;
734
+ o.ze = 0;
735
+ o.Ke = false;
715
736
  return;
716
737
  }
717
- o.Ye = (o.Ye ?? 0) + 1;
718
- if (o.Ve || o.Ye < i) return;
719
- o.Ve = true;
738
+ o.ze = (o.ze ?? 0) + 1;
739
+ if (o.Ke || o.ze < i) return;
740
+ o.Ke = true;
720
741
  const r = Array.isArray(t) ? "array" : "object";
721
- const s = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${r} on ` + `${o.Ye} consecutive runs — its equality gate never closes, so every ` + `subscriber re-runs on every upstream change. Return stable references or pass an ` + `\`equals\` option.`;
742
+ const s = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${r} on ` + `${o.ze} consecutive runs — its equality gate never closes, so every ` + `subscriber re-runs on every upstream change. Return stable references or pass an ` + `\`equals\` option.`;
722
743
  reportDiagnostic(emitDiagnostic({
723
744
  code: "UNSTABLE_MEMO_OUTPUT",
724
745
  kind: "perf",
@@ -726,7 +747,7 @@ function shallowEquivalent(e, n) {
726
747
  message: s,
727
748
  nodeName: nodeName(e),
728
749
  data: {
729
- runs: o.Ye,
750
+ runs: o.ze,
730
751
  shape: r
731
752
  }
732
753
  }, e));
@@ -860,17 +881,17 @@ const copyReported = new WeakSet;
860
881
  const r = o !== undefined && o.kind === "effect" ? effectFrames.get(o) : undefined;
861
882
  const s = r === undefined ? 0 : devId(r.node);
862
883
  recordNodes.set(n, e);
863
- i.Ke = i.Ke === undefined || i.Ke === s ? s : null;
864
- if (r !== undefined && t !== undefined && t === r.node.ge) {
884
+ i.Xe = i.Xe === undefined || i.Xe === s ? s : null;
885
+ if (r !== undefined && t !== undefined && t === r.node.Ae) {
865
886
  copyWrites.add(n);
866
- i.ze = i.Xe === s ? (i.ze ?? 0) + 1 : 1;
867
- i.Xe = s;
868
- if (i.ze >= 2 && i.Ke === s) checkCopyEffect(r.node, e);
869
- } else i.ze = 0;
887
+ i.Ze = i.Je === s ? (i.Ze ?? 0) + 1 : 1;
888
+ i.Je = s;
889
+ if (i.Ze >= 2 && i.Xe === s) checkCopyEffect(r.node, e);
890
+ } else i.Ze = 0;
870
891
  }
871
892
 
872
893
  /** The effect's source whose current value the compute output is, if any (the prop-to-state port). */ function passthroughSource(e) {
873
- for (let n = e.te; n !== null; n = n.ne) if (n.oe.ge === e.ge) return nodeName(n.oe);
894
+ for (let n = e.ee; n !== null; n = n.te) if (n.re.Ae === e.Ae) return nodeName(n.re);
874
895
  return undefined;
875
896
  }
876
897
 
@@ -941,27 +962,27 @@ function checkCopyEffect(e, n) {
941
962
  warned: false
942
963
  });
943
964
  c.count++;
944
- const d = copyWrites.has(r);
945
- const f = recordNodes.get(r);
946
- const u = f !== undefined && f.Ke === devId(o.node);
965
+ const f = copyWrites.has(r);
966
+ const d = recordNodes.get(r);
967
+ const u = d !== undefined && d.Xe === devId(o.node);
947
968
  // Derivable outright: the value is the compute output and nothing else
948
969
  // writes the signal. A copy INTO a signal that has other writers is the
949
970
  // "reset editable state from a source" shape — the tear is real, but a memo
950
971
  // is not the answer, so it stays advisory like any other non-derivable tear.
951
- const l = d && u;
972
+ const l = f && u;
952
973
  const h = l || c.count >= RELAY_WARN_AT ? "warn" : "info";
953
974
  // First sighting always reports (advisory); afterwards only the escalation.
954
975
  if (c.count > 1 && (h !== "warn" || c.warned)) return;
955
976
  // One verdict per derivable signal: the copy report (checkCopyEffect) and
956
977
  // the tear report carry the same repair.
957
- if (l && f !== undefined) {
958
- if (copyReported.has(f)) return;
959
- copyReported.add(f);
978
+ if (l && d !== undefined) {
979
+ if (copyReported.has(d)) return;
980
+ copyReported.add(d);
960
981
  }
961
982
  if (h === "warn") c.warned = true;
962
- const m = e.Re ? "effect" : "memo";
983
+ const m = e.Pe ? "effect" : "memo";
963
984
  const p = nodeName(o.node);
964
- const g = l ? copyRepair(o.node, r.name) : d ? `The written value is the effect's compute output, but "${r.name}" has other ` + `writers — editable state reset from a source. If the reset is the intent, the tear ` + `is its cost; if "${r.name}" only ever mirrors the source, drop the local copy ` + `and read the source.` : u ? `Nothing else writes "${r.name}" — it is derived state: make it a memo over what ` + `the effect reads and every reader gets it in the same flush.` : `If "${r.name}" is computed from what the effect reads, make it a memo so readers ` + `get it in the same flush; if the write reads something outside the graph (layout, ` + `time), the tear is the cost of measuring.`;
985
+ const g = l ? copyRepair(o.node, r.name) : f ? `The written value is the effect's compute output, but "${r.name}" has other ` + `writers — editable state reset from a source. If the reset is the intent, the tear ` + `is its cost; if "${r.name}" only ever mirrors the source, drop the local copy ` + `and read the source.` : u ? `Nothing else writes "${r.name}" — it is derived state: make it a memo over what ` + `the effect reads and every reader gets it in the same flush.` : `If "${r.name}" is computed from what the effect reads, make it a memo so readers ` + `get it in the same flush; if the write reads something outside the graph (layout, ` + `time), the tear is the cost of measuring.`;
965
986
  const w = `[EFFECT_RELAY_TEAR] ${m} "${nodeName(e)}" ran twice for one write of ` + `"${s.name}": once in the flush where "${s.name}" changed, and again after ` + `effect "${p}" relayed it by writing "${r.name}" — the first frame showed the ` + `new "${s.name}" with the stale "${r.name}"` + (c.count > 1 ? ` (${c.count} times so far)` : "") + `. ${g}`;
966
987
  emitDiagnostic({
967
988
  code: "EFFECT_RELAY_TEAR",
@@ -974,8 +995,8 @@ function checkCopyEffect(e, n) {
974
995
  root: s.name,
975
996
  relay: p,
976
997
  wrote: r.name,
977
- copy: d,
978
- passthrough: d ? passthroughSource(o.node) ?? null : null,
998
+ copy: f,
999
+ passthrough: f ? passthroughSource(o.node) ?? null : null,
979
1000
  soleWriter: u,
980
1001
  occurrences: c.count
981
1002
  }
@@ -997,33 +1018,37 @@ function checkCopyEffect(e, n) {
997
1018
  // per store path.
998
1019
  const immutableReported = new Set;
999
1020
 
1000
- function checkImmutableUpdate(e, n, t, i, o) {
1021
+ function checkImmutableUpdate(e, n, t, i, o, r) {
1001
1022
  if (immutableReported.has(e) || t < 2) return;
1002
1023
  // Push/filter/splice copies change the length by a little; a wholesale
1003
1024
  // resize is a different operation even if some items survive.
1004
1025
  if (n && Math.abs(o - t) > Math.max(1, t >> 2)) return;
1005
1026
  // At least half the leaves carried over unchanged, and at least one did.
1006
1027
  if (i === 0 || i * 2 < t) return;
1007
- const r = t - i;
1008
- const s = n ? "array" : "object";
1009
- const a = n ? `mutate the draft in place (push/splice/index assignment) so only the touched ` + `indices notify` : `assign the leaf on the draft (\`${e}.<key> = …\`) so only readers of that key re-run`;
1010
- const c = `[IMMUTABLE_UPDATE_IN_STORE] "${e}" was replaced with a fresh ${s} whose ` + `${n ? "items" : "leaves"} are mostly the same values (${i} of ${t} unchanged` + `${r > 0 ? `, ${r} changed` : ""}) — a spread-copy update. The store already ` + `tracks ${n ? "items" : "leaves"}; a new container makes every reader of "${e}" ` + `re-run for the ${r === 1 ? "one that" : "few that"} moved. Instead, ${a}. For ` + `data arriving from outside (a fetch result), merge it with reconcile(data, key)(${e}).`;
1011
- const d = emitDiagnostic({
1028
+ const s = t - i;
1029
+ const a = n ? "array" : "object";
1030
+ const c = n ? `mutate the draft in place (push/splice/index assignment) so only the touched ` + `indices notify` : `assign the leaf on the draft (\`${e}.<key> = …\`) so only readers of that key re-run`;
1031
+ const f = `[IMMUTABLE_UPDATE_IN_STORE] "${e}" was replaced with a fresh ${a} whose ` + `${n ? "items" : "leaves"} are mostly the same values (${i} of ${t} unchanged` + `${s > 0 ? `, ${s} changed` : ""}) — a spread-copy update. The store already ` + `tracks ${n ? "items" : "leaves"}; a new container makes every reader of "${e}" ` + `re-run for the ${s === 1 ? "one that" : "few that"} moved. Instead, ${c}. For ` + `data arriving from outside (a fetch result), merge it with reconcile(data, key)(${e}).`;
1032
+ // The subject is the store's own owner, not the writer's context: the
1033
+ // finding is about the store, and its writes legitimately arrive from
1034
+ // outside the graph (an event handler, an adapter). Falls back to the
1035
+ // ambient context (emitDiagnostic's default) when the store recorded none.
1036
+ const d = emitDiagnostic({
1012
1037
  code: "IMMUTABLE_UPDATE_IN_STORE",
1013
1038
  kind: "perf",
1014
1039
  severity: "warn",
1015
- message: c,
1040
+ message: f,
1016
1041
  nodeName: e,
1017
1042
  data: {
1018
1043
  path: e,
1019
- shape: s,
1044
+ shape: a,
1020
1045
  total: t,
1021
1046
  unchanged: i,
1022
- changed: r
1047
+ changed: s
1023
1048
  }
1024
- });
1025
- // Paths are not unique across stores: a write under an excluded owner
1026
- // (an adapter's own "store.list") must not spend the app's once-per-path slot.
1049
+ }, r);
1050
+ // Paths are not unique across stores: an excluded owner's store (an
1051
+ // adapter's own "store.list") must not spend the app's once-per-path slot.
1027
1052
  if (isSuppressed(d)) return;
1028
1053
  immutableReported.add(e);
1029
1054
  }
@@ -1046,7 +1071,7 @@ const listIdentityWarned = new WeakSet;
1046
1071
  function recordId(e) {
1047
1072
  if (e === null || typeof e !== "object") return undefined;
1048
1073
  const n = e;
1049
- return n.id ?? n.key ?? n.Ze ?? undefined;
1074
+ return n.id ?? n.key ?? n.Qe ?? undefined;
1050
1075
  }
1051
1076
 
1052
1077
  function checkListIdentity(e, n, t, i, o) {
@@ -1073,15 +1098,15 @@ function checkListIdentity(e, n, t, i, o) {
1073
1098
  }
1074
1099
  if (s === 0 || a * 2 < s) return;
1075
1100
  listIdentityWarned.add(e);
1076
- const d = nodeName(e);
1077
- const f = o ? `The key function returned different keys for equivalent records — return a stable ` + `field (\`keyed: item => item.id\`), not the object or a computed value that changes ` + `with the fetch.` : `Key the list by a stable field (\`keyed: item => item.id\`), or merge the data into ` + `a store with reconcile(data, "id") so the same records keep the same identity.`;
1078
- const u = `[UNSTABLE_LIST_IDENTITY] list "${d}" recreated ${t.length} of ${i} rows on ` + `an update where the entering items are equivalent to the ones they replaced ` + `(${a} of ${s} sampled pairs identical field-for-field) — fresh objects ` + `for the same records, so identity keying threw away every row's DOM and state and ` + `rebuilt it. ${f}`;
1101
+ const f = nodeName(e);
1102
+ const d = o ? `The key function returned different keys for equivalent records — return a stable ` + `field (\`keyed: item => item.id\`), not the object or a computed value that changes ` + `with the fetch.` : `Key the list by a stable field (\`keyed: item => item.id\`), or merge the data into ` + `a store with reconcile(data, "id") so the same records keep the same identity.`;
1103
+ const u = `[UNSTABLE_LIST_IDENTITY] list "${f}" recreated ${t.length} of ${i} rows on ` + `an update where the entering items are equivalent to the ones they replaced ` + `(${a} of ${s} sampled pairs identical field-for-field) — fresh objects ` + `for the same records, so identity keying threw away every row's DOM and state and ` + `rebuilt it. ${d}`;
1079
1104
  reportDiagnostic(emitDiagnostic({
1080
1105
  code: "UNSTABLE_LIST_IDENTITY",
1081
1106
  kind: "perf",
1082
1107
  severity: "warn",
1083
1108
  message: u,
1084
- nodeName: d,
1109
+ nodeName: f,
1085
1110
  data: {
1086
1111
  removed: n.length,
1087
1112
  created: t.length,
@@ -1133,21 +1158,19 @@ function trackFlightStart(e, n) {
1133
1158
  if (i === t) flightOrigins.set(n, t);
1134
1159
  // Census: a flight still in the air when the node starts another was
1135
1160
  // superseded — its answer will be discarded.
1136
- const o = flightBucket(e);
1137
- o.flights++;
1138
- if (liveFlights.has(e)) o.abandoned++;
1161
+ for (const n of folds) n.flightStart?.(e, liveFlights.has(e));
1139
1162
  // Nearest enclosing frame with causes: create runs carry null (a node born
1140
1163
  // inside a parent's recompute inherits the parent's causality — the
1141
1164
  // boundary-reveal case, and the lazy sibling whose first pull is gated
1142
1165
  // behind an earlier not-ready read), so walk down to the first re-run frame.
1143
- let r = null;
1166
+ let o = null;
1144
1167
  for (let e = frames.length - 1; e >= 0; e--) {
1145
1168
  if (frames[e].causes !== null) {
1146
- r = frames[e].causes;
1169
+ o = frames[e].causes;
1147
1170
  break;
1148
1171
  }
1149
1172
  }
1150
- const s = {
1173
+ const r = {
1151
1174
  origin: i,
1152
1175
  startSeq: changeSeq,
1153
1176
  chain: []
@@ -1155,19 +1178,19 @@ function trackFlightStart(e, n) {
1155
1178
  // Provenance: the flight belongs to whatever interaction caused the
1156
1179
  // recompute that started it (a create run under a click's handler — a
1157
1180
  // freshly mounted async node — inherits the ambient interaction instead).
1158
- const a = r !== null ? interactionIn(r) : currentInteraction ?? undefined;
1159
- if (a !== undefined) s.interaction = a;
1160
- if (options.waterfalls !== false && r !== null) {
1161
- let e = flightCauseIn(r);
1181
+ const s = o !== null ? interactionIn(o) : currentInteraction ?? undefined;
1182
+ if (s !== undefined) r.interaction = s;
1183
+ if (options.waterfalls !== false && o !== null) {
1184
+ let e = flightCauseIn(o);
1162
1185
  // The sequentiality test. A marked/previously-seen flight whose origin
1163
1186
  // predates the upstream landing was in the air alongside it: parallel.
1164
1187
  if (e !== null && i < e.landedAt) e = null;
1165
- if (e !== null) s.chain = [ ...e.chain, {
1188
+ if (e !== null) r.chain = [ ...e.chain, {
1166
1189
  name: e.name,
1167
1190
  ms: e.ms
1168
1191
  } ];
1169
1192
  }
1170
- liveFlights.set(e, s);
1193
+ liveFlights.set(e, r);
1171
1194
  }
1172
1195
 
1173
1196
  /**
@@ -1180,14 +1203,11 @@ function trackFlightStart(e, n) {
1180
1203
  liveFlights.delete(e);
1181
1204
  const t = now();
1182
1205
  const i = t - n.origin;
1183
- const o = flightBucket(e);
1184
- o.landed++;
1185
- o.landedMs += i;
1186
- if (i > o.worstMs) o.worstMs = i;
1187
- const r = e.$e;
1206
+ for (const n of folds) n.flightLanded?.(e, i);
1207
+ const o = e.ke;
1188
1208
  // Only a stamp this landing produced may carry the measurement — a stale
1189
1209
  // async record from a previous landing must not be re-labeled.
1190
- if (r !== undefined && r.kind === "async" && r.seq > n.startSeq) landedFlights.set(r, {
1210
+ if (o !== undefined && o.kind === "async" && o.seq > n.startSeq) landedFlights.set(o, {
1191
1211
  name: nodeName(e),
1192
1212
  ms: i,
1193
1213
  chain: n.chain,
@@ -1220,24 +1240,24 @@ function checkWaterfall(e, n, t) {
1220
1240
  }
1221
1241
  if (o < 2) return;
1222
1242
  const s = e;
1223
- if ((s.Je ?? 0) >= o) return;
1224
- s.Je = o;
1243
+ if ((s.en ?? 0) >= o) return;
1244
+ s.en = o;
1225
1245
  const a = [ ...n.slice(n.length - (o - 1)), {
1226
1246
  name: nodeName(e),
1227
1247
  ms: t
1228
1248
  } ];
1229
1249
  const c = a.map(e => `"${e.name}" (${e.ms.toFixed(0)}ms)`).join(" → ");
1230
- const d = `[ASYNC_WATERFALL] ${o} sequential async flights — ${c} — ` + `${r.toFixed(0)}ms serialized: each began only after the previous resolved ` + `(as far as this graph can see). If a later request doesn't need the earlier ` + `response, derive both from the same inputs so they start together; if the ` + `dependency is intrinsic, preload the dependent data or join the requests ` + `server-side. If this work WAS already started elsewhere (a preloader or request ` + `cache), have that layer stamp its promises with attribution.markFlight() ` + `from "@solidjs/signals/attribution".`;
1250
+ const f = `[ASYNC_WATERFALL] ${o} sequential async flights — ${c} — ` + `${r.toFixed(0)}ms serialized: each began only after the previous resolved ` + `(as far as this graph can see). If a later request doesn't need the earlier ` + `response, derive both from the same inputs so they start together; if the ` + `dependency is intrinsic, preload the dependent data or join the requests ` + `server-side. If this work WAS already started elsewhere (a preloader or request ` + `cache), have that layer stamp its promises with attribution.markFlight() ` + `from "@solidjs/signals/attribution".`;
1231
1251
  // Depth 2 is advisory-only (structured consumers see it; the console does
1232
1252
  // not): a 2-chain can be an intrinsic data dependency or an unmarked
1233
1253
  // preload. A 3+ chain that survived the origin test is near-certainly
1234
1254
  // structural — that one earns the console.
1235
- const f = o > 2 ? "warn" : "info";
1255
+ const d = o > 2 ? "warn" : "info";
1236
1256
  emitDiagnostic({
1237
1257
  code: "ASYNC_WATERFALL",
1238
1258
  kind: "perf",
1239
- severity: f,
1240
- message: d,
1259
+ severity: d,
1260
+ message: f,
1241
1261
  nodeName: nodeName(e),
1242
1262
  data: {
1243
1263
  chain: a.map(e => ({
@@ -1255,8 +1275,10 @@ let activeHold = null;
1255
1275
 
1256
1276
  let holdLog = [];
1257
1277
 
1258
- /** Companions are optimistic nodes too; `_parentSource` marks them. */ function isCompanion(e) {
1259
- return !!e.o && e.o.Qe !== undefined;
1278
+ /** Companions are optimistic nodes too; `_parentSource` marks them. So is a
1279
+ * memo carrying a DERIVED override (lanes stage, #3479) — a lane pass's
1280
+ * result, not a write anyone made: neither is an acknowledgement. */ function isCompanion(e) {
1281
+ return !!e.o && e.o.nn !== undefined || (e.C & CONFIG_DERIVED_OVERRIDE) !== 0;
1260
1282
  }
1261
1283
 
1262
1284
  const HOLD_CENSUS_CAP = 1e4;
@@ -1281,8 +1303,8 @@ function censusRegistrations(e, n) {
1281
1303
  const t = {
1282
1304
  left: HOLD_CENSUS_CAP
1283
1305
  };
1284
- for (const i of e.en) if (!isCompanion(i)) acknowledge(n, "optimistic", nodeName(i), reachesEffect(i, t));
1285
- for (const t of e.nn) acknowledge(n, "optimistic", t?._name ?? "store", null);
1306
+ for (const i of e.tn) if (!isCompanion(i)) acknowledge(n, "optimistic", nodeName(i), reachesEffect(i, t));
1307
+ for (const t of e.rn) acknowledge(n, "optimistic", t?._name ?? "store", null);
1286
1308
  for (const i of e.A) acknowledge(n, "affects", nodeName(i), reachesEffect(i, t));
1287
1309
  }
1288
1310
 
@@ -1297,9 +1319,9 @@ function censusRegistrations(e, n) {
1297
1319
  const i = [ e ];
1298
1320
  while (i.length > 0 && n.left-- > 0) {
1299
1321
  const e = i.pop();
1300
- for (let n = e.u; n !== null; n = n.se) {
1301
- const e = n.le;
1302
- if (e.Re) return e;
1322
+ for (let n = e.u; n !== null; n = n.ie) {
1323
+ const e = n.se;
1324
+ if (e.Pe) return e;
1303
1325
  if (!t.has(e)) {
1304
1326
  t.add(e);
1305
1327
  i.push(e);
@@ -1322,11 +1344,11 @@ function censusRegistrations(e, n) {
1322
1344
  const r = e.o;
1323
1345
  if (r) {
1324
1346
  let t;
1325
- if (r.be !== undefined && (t = reachesEffect(r.be, o))) acknowledge(n, "isPending", nodeName(e), t);
1326
- if (r.pe !== undefined && (t = reachesEffect(r.pe, o))) acknowledge(n, "latest", nodeName(e), t);
1347
+ if (r.he !== undefined && (t = reachesEffect(r.he, o))) acknowledge(n, "isPending", nodeName(e), t);
1348
+ if (r.Ge !== undefined && (t = reachesEffect(r.Ge, o))) acknowledge(n, "latest", nodeName(e), t);
1327
1349
  for (let e = r.i ?? null; e !== null; e = e.ue ?? null) i.push(e);
1328
1350
  }
1329
- for (let n = e.u; n !== null; n = n.se) i.push(n.le);
1351
+ for (let n = e.u; n !== null; n = n.ie) i.push(n.se);
1330
1352
  }
1331
1353
  }
1332
1354
 
@@ -1354,8 +1376,8 @@ function trackHoldStart(e) {
1354
1376
  if (options.holds === false) return;
1355
1377
  const n = holdState(e);
1356
1378
  n.flushes++;
1357
- if (e.ut.length > 0) n.action = true;
1358
- for (const [t, i] of e.Ie) if (i.size > 0) n.blockers.add(t);
1379
+ if (e.dt.length > 0) n.action = true;
1380
+ for (const [t, i] of e.ot) if (i.size > 0) n.blockers.add(t);
1359
1381
  censusRegistrations(e, n);
1360
1382
  activeHold = n;
1361
1383
  }
@@ -1392,9 +1414,9 @@ function trackHoldSettled(e) {
1392
1414
  let o;
1393
1415
  let r;
1394
1416
  let s = -Infinity;
1395
- for (const n of e.tn) {
1396
- if (typeof n.ot === "function" || isCompanion(n)) continue;
1397
- const e = n.$e;
1417
+ for (const n of e.sn) {
1418
+ if (typeof n.ht === "function" || isCompanion(n)) continue;
1419
+ const e = n.ke;
1398
1420
  if (e === undefined || e.kind !== "write") continue;
1399
1421
  if (i === null) i = n;
1400
1422
  const a = {
@@ -1421,18 +1443,18 @@ function trackHoldSettled(e) {
1421
1443
  return;
1422
1444
  }
1423
1445
  censusRegistrations(e, n);
1424
- censusCompanions([ ...e.tn, ...n.blockers ], n);
1446
+ censusCompanions([ ...e.sn, ...n.blockers ], n);
1425
1447
  const a = now();
1426
1448
  // The hold began no later than its first parked flush; an interaction stamp
1427
1449
  // reaches further back (dispatch). A node rewritten mid-hold keeps only its
1428
1450
  // latest record, so the surviving interaction may be a later one — the
1429
1451
  // flush clock keeps the first wait from being forgotten.
1430
1452
  const c = Math.min(n.start, o !== undefined ? o.at : Infinity);
1431
- const d = a - c;
1432
- const f = {
1453
+ const f = a - c;
1454
+ const d = {
1433
1455
  at: c,
1434
- holdMs: d,
1435
- tailMs: s === -Infinity ? d : Math.min(d, a - s),
1456
+ holdMs: f,
1457
+ tailMs: s === -Infinity ? f : Math.min(f, a - s),
1436
1458
  flushes: n.flushes,
1437
1459
  heldWrites: t,
1438
1460
  blockers: [ ...n.blockers ].map(nodeName),
@@ -1440,20 +1462,21 @@ function trackHoldSettled(e) {
1440
1462
  paintedDuringHold: n.painted,
1441
1463
  action: n.action
1442
1464
  };
1443
- if (o !== undefined) f.interaction = o;
1444
- if (r !== undefined) f.origin = r;
1445
- holdLog.push(f);
1465
+ if (o !== undefined) d.interaction = o;
1466
+ if (r !== undefined) d.origin = r;
1467
+ holdLog.push(d);
1446
1468
  if (holdLog.length > options.historyLimit) holdLog.shift();
1447
1469
  // Bottom-up delivery: the hold, then the navigations it held, then the
1448
1470
  // interactions those belong to — each record complete when its parent is.
1449
- emitRecord("hold", f);
1450
- settleNavigations(e, f);
1451
- settleInteractionsHeld(e, f);
1452
- recordFeedbackHold(f);
1453
- if (isSilentHold(f)) checkSilentHold(f, i); else checkLongHold(f, i);
1471
+ emitRecord("hold", d);
1472
+ settleNavigations(e, d);
1473
+ settleInteractionsHeld(e, d);
1474
+ for (const e of folds) e.hold?.(d);
1475
+ if (isSilentHold(d)) checkSilentHold(d, i); else checkLongHold(d, i);
1454
1476
  }
1455
1477
 
1456
- /** `isLongHold` — the tail outlasted `longHolds.infoMs`. */ function isLongHold(e) {
1478
+ /** `isLongHold` — the tail outlasted `longHolds.infoMs`. */
1479
+ /** @internal */ function isLongHold(e) {
1457
1480
  const n = options.longHolds;
1458
1481
  return n !== false && n !== undefined && e.tailMs >= n.infoMs;
1459
1482
  }
@@ -1516,15 +1539,15 @@ function checkSilentHold(e, n) {
1516
1539
  const s = holdActor(e);
1517
1540
  const a = s ? `${s} ` : "";
1518
1541
  let c = e.action ? `[SILENT_HOLD] ${a}${a ? "started an action that" : "an action"} held ${o} for ` + `${i}ms${r} and the screen showed nothing for the whole round-trip: no optimistic ` + `value, no isPending() reader, no affects() mark, and no effect ran while it was held. ` + `Pair the action with a createOptimistic/createOptimisticStore write for the expected ` + `outcome (it reverts on failure), or co-write a createOptimistic(false) "saving" flag ` + `the UI reads.` : `[SILENT_HOLD] ${a}${a ? "wrote" : "writes to"} ${o}${a ? "; the write was" : " were"} ` + `held ${i}ms${r} and the screen showed nothing for the wait: no ` + `isPending()/latest() reader downstream, no optimistic value, no affects() mark, and no ` + `effect ran while it was held — the interaction was dead for ${i}ms. Show the wait: ` + `read isPending(() => ${e.blockers[0] ?? "source"}()) to render a busy state, or ` + `latest(${e.heldWrites[0].name}) to reveal the new input immediately while the data ` + `catches up. The hold itself is correct — do not "fix" this by moving the write off the ` + `async path.`;
1519
- const d = isLongHold(e);
1520
- if (d) c += ` ${boundaryRepair(e)}`;
1521
- const f = e.holdMs >= t.warnMs ? "warn" : "info";
1542
+ const f = isLongHold(e);
1543
+ if (f) c += ` ${boundaryRepair(e)}`;
1544
+ const d = e.holdMs >= t.warnMs ? "warn" : "info";
1522
1545
  const u = holdData(e);
1523
- u.long = d;
1546
+ u.long = f;
1524
1547
  emitDiagnostic({
1525
1548
  code: "SILENT_HOLD",
1526
1549
  kind: "responsiveness",
1527
- severity: f,
1550
+ severity: d,
1528
1551
  message: c,
1529
1552
  nodeName: nodeName(n),
1530
1553
  data: u
@@ -1541,8 +1564,8 @@ function checkLongHold(e, n) {
1541
1564
  const s = holdActor(e);
1542
1565
  const a = s ? `${s} ` : "";
1543
1566
  const c = e.acknowledgements.length > 0 ? `${e.acknowledgements.map(e => `"${e.kind}:${e.source}"`).join(", ")} said it was pending` : `an effect painted meanwhile`;
1544
- const d = e.tailMs < e.holdMs - 1 ? ` after the last input (${e.holdMs.toFixed(0)}ms in all)` : "";
1545
- const f = `[LONG_HOLD] ${a}${a ? "wrote" : "writes to"} ${o}; the screen kept the old ` + `content for ${i}ms${d}${r} — ${c}, but the hold ran on well past ` + `the point where "loading" over stale content reads as broken. ${boundaryRepair(e)}`;
1567
+ const f = e.tailMs < e.holdMs - 1 ? ` after the last input (${e.holdMs.toFixed(0)}ms in all)` : "";
1568
+ const d = `[LONG_HOLD] ${a}${a ? "wrote" : "writes to"} ${o}; the screen kept the old ` + `content for ${i}ms${f}${r} — ${c}, but the hold ran on well past ` + `the point where "loading" over stale content reads as broken. ${boundaryRepair(e)}`;
1546
1569
  const u = e.tailMs >= t.warnMs ? "warn" : "info";
1547
1570
  const l = holdData(e);
1548
1571
  l.acknowledgements = e.acknowledgements;
@@ -1550,7 +1573,7 @@ function checkLongHold(e, n) {
1550
1573
  code: "LONG_HOLD",
1551
1574
  kind: "responsiveness",
1552
1575
  severity: u,
1553
- message: f,
1576
+ message: d,
1554
1577
  nodeName: nodeName(n),
1555
1578
  data: l
1556
1579
  }, n);
@@ -1661,8 +1684,8 @@ function closeNavigation(e) {
1661
1684
 
1662
1685
  /** holdStart: `t`'s staged writes are parked — their navigations settle with `t`. */ function markNavigationsHeld(e) {
1663
1686
  if (openNavs.size === 0) return;
1664
- for (const n of e.tn) {
1665
- const e = n.$e?.origin;
1687
+ for (const n of e.sn) {
1688
+ const e = n.ke?.origin;
1666
1689
  if (e?.kind !== "navigation") continue;
1667
1690
  const t = navStates.get(e);
1668
1691
  if (t !== undefined) t.held = true;
@@ -1671,8 +1694,8 @@ function closeNavigation(e) {
1671
1694
 
1672
1695
  /** transitionSettled: `t` commits — the navigations whose writes it staged are done. */ function settleNavigations(e, n) {
1673
1696
  if (openNavs.size === 0) return;
1674
- for (const t of e.tn) {
1675
- const e = t.$e?.origin;
1697
+ for (const t of e.sn) {
1698
+ const e = t.ke?.origin;
1676
1699
  if (e?.kind !== "navigation") continue;
1677
1700
  const i = navStates.get(e);
1678
1701
  if (i === undefined || !openNavs.has(i)) continue;
@@ -1696,7 +1719,7 @@ function settleNavigation(e, n, t) {
1696
1719
  i.settledMs = now() - i.at;
1697
1720
  i.outcome = n;
1698
1721
  if (t !== undefined) i.hold = t;
1699
- recordFeedbackNavigation(i);
1722
+ for (const e of folds) e.navigation?.(i);
1700
1723
  emitRecord("navigation", i);
1701
1724
  // The interaction that performed it may have been waiting only on this.
1702
1725
  const o = openInteractionOf(i.interaction);
@@ -1728,7 +1751,8 @@ function openInteraction(e) {
1728
1751
  open: true,
1729
1752
  writeDrain: drainSeq,
1730
1753
  heldIn: new Set,
1731
- held: false
1754
+ held: false,
1755
+ excludedWrites: 0
1732
1756
  };
1733
1757
  interactionStates.set(e, t);
1734
1758
  openInteractions.add(t);
@@ -1752,11 +1776,15 @@ function openInteraction(e) {
1752
1776
  return t !== undefined && openInteractions.has(t) ? t : undefined;
1753
1777
  }
1754
1778
 
1755
- /** stampWrite: a root write stamped `origin`. */ function noteInteractionWrite(e) {
1756
- const n = openInteractionOf(e);
1757
- if (n === undefined) return;
1758
- n.event.writes++;
1759
- n.writeDrain = drainSeq;
1779
+ /** stampWrite: a root write stamped `origin`. */ function noteInteractionWrite(e, n) {
1780
+ const t = openInteractionOf(e);
1781
+ if (t === undefined) return;
1782
+ if (n) {
1783
+ t.excludedWrites++;
1784
+ return;
1785
+ }
1786
+ t.event.writes++;
1787
+ t.writeDrain = drainSeq;
1760
1788
  }
1761
1789
 
1762
1790
  /** recordRerun / a create run: work attributed to the interaction. */ function noteInteractionRun(e, n, t) {
@@ -1773,8 +1801,8 @@ function openInteraction(e) {
1773
1801
 
1774
1802
  /** holdStart: `t` parked writes — the interactions that performed them wait for `t`. */ function markInteractionsHeld(e) {
1775
1803
  if (openInteractions.size === 0) return;
1776
- for (const n of e.tn) {
1777
- const t = openInteractionOf(n.$e?.origin);
1804
+ for (const n of e.sn) {
1805
+ const t = openInteractionOf(n.ke?.origin);
1778
1806
  if (t !== undefined) {
1779
1807
  t.heldIn.add(e);
1780
1808
  t.held = true;
@@ -1804,6 +1832,14 @@ function maybeSettleInteraction(e, n = now()) {
1804
1832
  if (t.writes > 0 && drainSeq <= e.writeDrain) return;
1805
1833
  for (const e of t.navigations) if (e.outcome === undefined) return;
1806
1834
  openInteractions.delete(e);
1835
+ // Every write went to an excluded subject and nothing of the app's ran: the
1836
+ // click was on the observer's own UI (a devtools panel's button). Not a
1837
+ // fact about the app — forget it rather than report a dead interaction.
1838
+ if (t.writes === 0 && e.excludedWrites > 0 && t.runs === 0 && t.created === 0) {
1839
+ const e = interactionLog.indexOf(t);
1840
+ if (e !== -1) interactionLog.splice(e, 1);
1841
+ return;
1842
+ }
1807
1843
  t.settledMs = n - t.at;
1808
1844
  t.outcome = t.writes === 0 ? "idle" : e.held ? "held" : "committed";
1809
1845
  emitRecord("interaction", t);
@@ -1818,239 +1854,10 @@ function maybeSettleInteraction(e, n = now()) {
1818
1854
  return n;
1819
1855
  }
1820
1856
 
1821
- const feedbackSources = new Map;
1822
-
1823
- const feedbackInteractions = new Map;
1824
-
1825
- const feedbackNavigations = new Map;
1826
-
1827
- const flightStats = new Map;
1828
-
1829
- const fallbackStats = new Map;
1830
-
1831
- const FALLBACK_FLASH_MS = 150;
1832
-
1833
- function flightBucket(e) {
1834
- let n = flightStats.get(e);
1835
- if (n === undefined) {
1836
- n = {
1837
- source: nodeName(e),
1838
- flights: 0,
1839
- landed: 0,
1840
- abandoned: 0,
1841
- landedMs: 0,
1842
- worstMs: 0
1843
- };
1844
- flightStats.set(e, n);
1845
- }
1846
- return n;
1847
- }
1848
-
1849
- function trackFallback(e, n, t) {
1850
- let i = fallbackStats.get(e);
1851
- if (i === undefined) {
1852
- i = {
1853
- row: {
1854
- boundary: "boundary",
1855
- shows: 0,
1856
- shownMs: 0,
1857
- worstMs: 0,
1858
- flashes: 0
1859
- },
1860
- shownAt: null
1861
- };
1862
- fallbackStats.set(e, i);
1863
- }
1864
- // The first show can fire before the subtree exists; name on first sight.
1865
- if (i.row.boundary === "boundary" && n !== undefined) {
1866
- const e = ownerPath(n);
1867
- if (e !== undefined) i.row.boundary = e.join(" › ");
1868
- }
1869
- if (t) {
1870
- if (i.shownAt === null) {
1871
- i.shownAt = now();
1872
- i.row.shows++;
1873
- }
1874
- return;
1875
- }
1876
- if (i.shownAt === null) return;
1877
- const o = now() - i.shownAt;
1878
- i.shownAt = null;
1879
- i.row.shownMs += o;
1880
- if (o > i.row.worstMs) i.row.worstMs = o;
1881
- if (o < FALLBACK_FLASH_MS) i.row.flashes++;
1882
- }
1883
-
1884
- /** No affordance answered and nothing painted while held. */ function isSilentHold(e) {
1857
+ /** @internal No affordance answered and nothing painted while held. */ function isSilentHold(e) {
1885
1858
  return e.paintedDuringHold === 0 && e.acknowledgements.length === 0;
1886
1859
  }
1887
1860
 
1888
- function interactionBucket(e) {
1889
- const n = formatOrigin(e);
1890
- let t = feedbackInteractions.get(n);
1891
- if (t === undefined) {
1892
- t = {
1893
- row: {
1894
- interaction: n,
1895
- dispatches: 0,
1896
- runs: 0,
1897
- selfMs: 0,
1898
- worstDispatchMs: 0,
1899
- holds: 0,
1900
- heldMs: 0,
1901
- silentMs: 0,
1902
- worstHoldMs: 0
1903
- },
1904
- dispatches: new Map
1905
- };
1906
- feedbackInteractions.set(n, t);
1907
- }
1908
- const i = e.at ?? 0;
1909
- if (!t.dispatches.has(i)) {
1910
- t.dispatches.set(i, 0);
1911
- t.row.dispatches++;
1912
- }
1913
- return t;
1914
- }
1915
-
1916
- function recordFeedbackRun(e) {
1917
- if (e.interaction === undefined) return;
1918
- const n = interactionBucket(e.interaction);
1919
- n.row.runs++;
1920
- n.row.selfMs += e.selfMs;
1921
- const t = e.interaction.at ?? 0;
1922
- const i = n.dispatches.get(t) + e.selfMs;
1923
- n.dispatches.set(t, i);
1924
- if (i > n.row.worstDispatchMs) n.row.worstDispatchMs = i;
1925
- }
1926
-
1927
- function recordFeedbackHold(e) {
1928
- const n = [ ...e.blockers ].sort();
1929
- const t = n.join("\0");
1930
- let i = feedbackSources.get(t);
1931
- if (i === undefined) {
1932
- i = {
1933
- row: {
1934
- sources: n,
1935
- holds: 0,
1936
- heldMs: 0,
1937
- worstMs: 0,
1938
- silent: 0,
1939
- silentMs: 0,
1940
- latestOnly: 0,
1941
- long: 0,
1942
- longMs: 0,
1943
- acknowledgedBy: [],
1944
- interactions: [],
1945
- writes: [],
1946
- actions: 0
1947
- },
1948
- acks: new Map,
1949
- interactions: new Map,
1950
- writes: new Set
1951
- };
1952
- feedbackSources.set(t, i);
1953
- }
1954
- const o = i.row;
1955
- const r = isSilentHold(e);
1956
- o.holds++;
1957
- o.heldMs += e.holdMs;
1958
- if (e.holdMs > o.worstMs) o.worstMs = e.holdMs;
1959
- if (r) {
1960
- o.silent++;
1961
- o.silentMs += e.holdMs;
1962
- } else if (e.acknowledgements.length > 0 && e.acknowledgements.every(e => e.kind === "latest")) o.latestOnly++;
1963
- if (isLongHold(e)) {
1964
- o.long++;
1965
- o.longMs += e.tailMs;
1966
- }
1967
- if (e.action) o.actions++;
1968
- for (const n of e.acknowledgements) {
1969
- const e = `${n.kind}:${n.source}`;
1970
- i.acks.set(e, (i.acks.get(e) ?? 0) + 1);
1971
- }
1972
- for (const n of e.heldWrites) i.writes.add(n.name);
1973
- if (e.interaction !== undefined) {
1974
- const n = formatOrigin(e.interaction);
1975
- i.interactions.set(n, (i.interactions.get(n) ?? 0) + 1);
1976
- const t = interactionBucket(e.interaction);
1977
- t.row.holds++;
1978
- t.row.heldMs += e.holdMs;
1979
- if (r) t.row.silentMs += e.holdMs;
1980
- if (e.holdMs > t.row.worstHoldMs) t.row.worstHoldMs = e.holdMs;
1981
- }
1982
- }
1983
-
1984
- function recordFeedbackNavigation(e) {
1985
- const n = e.name ?? e.to;
1986
- if (n === undefined) return;
1987
- let t = feedbackNavigations.get(n);
1988
- if (t === undefined) {
1989
- t = {
1990
- name: n,
1991
- navigations: 0,
1992
- settledMs: 0,
1993
- worstMs: 0,
1994
- held: 0,
1995
- heldMs: 0,
1996
- silent: 0,
1997
- superseded: 0,
1998
- redirected: 0
1999
- };
2000
- feedbackNavigations.set(n, t);
2001
- }
2002
- t.navigations++;
2003
- if (e.redirects !== undefined) t.redirected++;
2004
- if (e.outcome === "superseded") {
2005
- t.superseded++;
2006
- return;
2007
- }
2008
- const i = e.settledMs;
2009
- t.settledMs += i;
2010
- if (i > t.worstMs) t.worstMs = i;
2011
- if (e.outcome === "held") {
2012
- t.held++;
2013
- t.heldMs += e.hold?.holdMs ?? i;
2014
- if (e.hold !== undefined && isSilentHold(e.hold)) t.silent++;
2015
- }
2016
- }
2017
-
2018
- function rankedCounts(e, n) {
2019
- return [ ...e ].sort((e, n) => n[1] - e[1]).map(([e, t]) => ({
2020
- [n]: e,
2021
- holds: t
2022
- }));
2023
- }
2024
-
2025
- function feedbackTables() {
2026
- const e = [ ...feedbackNavigations.values() ].map(e => ({
2027
- ...e
2028
- })).sort((e, n) => n.heldMs - e.heldMs || n.settledMs - e.settledMs);
2029
- const n = [ ...flightStats.values() ].map(e => ({
2030
- ...e
2031
- })).sort((e, n) => n.abandoned - e.abandoned || n.flights - e.flights);
2032
- const t = [ ...fallbackStats.values() ].map(e => ({
2033
- ...e.row
2034
- })).sort((e, n) => n.flashes - e.flashes || n.shownMs - e.shownMs);
2035
- const i = [ ...feedbackSources.values() ].map(e => ({
2036
- ...e.row,
2037
- acknowledgedBy: rankedCounts(e.acks, "by"),
2038
- interactions: rankedCounts(e.interactions, "interaction"),
2039
- writes: [ ...e.writes ]
2040
- })).sort((e, n) => n.silentMs - e.silentMs || n.heldMs - e.heldMs);
2041
- // Ranked by the total time the user spent on it: held plus synchronous work.
2042
- const o = [ ...feedbackInteractions.values() ].map(e => ({
2043
- ...e.row
2044
- })).sort((e, n) => n.heldMs + n.selfMs - (e.heldMs + e.selfMs));
2045
- return {
2046
- sources: i,
2047
- interactions: o,
2048
- navigations: e,
2049
- flights: n,
2050
- fallbacks: t
2051
- };
2052
- }
2053
-
2054
1861
  // The engine's implementation of the core's dev hook points. Installed by
2055
1862
  // enable(), uninstalled by disable() — while uninstalled the core pays one
2056
1863
  // null check per site and nothing else.
@@ -2077,7 +1884,7 @@ const engineHooks = {
2077
1884
  prevDeps: n ? null : captureDeps(e),
2078
1885
  // Mirror recompute's own prev-value resolution: an earlier run in the
2079
1886
  // same flush may still be holding in _pendingValue.
2080
- prevValue: e.Pe !== NOT_PENDING ? e.Pe : e.ge,
1887
+ prevValue: e._e !== NOT_PENDING ? e._e : e.Ae,
2081
1888
  // A create run inherits the interaction of whatever is building it: the
2082
1889
  // enclosing recompute (a parent's fn creating children) or, at the top
2083
1890
  // of the recompute stack, the effect callback / handler frame.
@@ -2103,8 +1910,8 @@ const engineHooks = {
2103
1910
  // committed compute output is an unchanged run. `undefined` outputs are
2104
1911
  // exempt — a side-effect-only compute's work IS its effect phase, and
2105
1912
  // identity of `undefined` proves nothing.
2106
- if (t && s.causes !== null && e.Re) {
2107
- const n = e.Pe !== NOT_PENDING ? e.Pe : e.ge;
1913
+ if (t && s.causes !== null && e.Pe) {
1914
+ const n = e._e !== NOT_PENDING ? e._e : e.Ae;
2108
1915
  if (n !== undefined && n === s.prevValue) t = false;
2109
1916
  }
2110
1917
  // Unstable-output check: memos only, non-create, plain runs with a
@@ -2112,7 +1919,7 @@ const engineHooks = {
2112
1919
  // plain-flush memo commits and in `_value` for direct ones. Overlay runs
2113
1920
  // are excluded — an optimistic re-derive legitimately produces fresh
2114
1921
  // equivalents while the lane settles.
2115
- if (s.causes !== null && t && !i && !o && !e.Re) checkUnstableOutput(e, s.prevValue, e.Pe !== NOT_PENDING ? e.Pe : e.ge);
1922
+ if (s.causes !== null && t && !i && !o && !e.Pe) checkUnstableOutput(e, s.prevValue, e._e !== NOT_PENDING ? e._e : e.Ae);
2116
1923
  if (s.causes !== null) recordRerun(e, s, {
2117
1924
  selfMs: c,
2118
1925
  totalMs: a
@@ -2135,9 +1942,9 @@ const engineHooks = {
2135
1942
  trackFlightStart(e, n);
2136
1943
  },
2137
1944
  asyncStart(e) {
2138
- asyncStartSeq = e.$e?.seq ?? 0;
2139
- asyncStartTime = e.ae;
2140
- asyncStartValue = e.ge;
1945
+ asyncStartSeq = e.ke?.seq ?? 0;
1946
+ asyncStartTime = e.fe;
1947
+ asyncStartValue = e.Ae;
2141
1948
  },
2142
1949
  asyncEnd(e, n, t, i) {
2143
1950
  if (i) {
@@ -2147,7 +1954,7 @@ const engineHooks = {
2147
1954
  // (or `_time`, for a same-reference commit under `equals: false`), and
2148
1955
  // a transition hold parks the value in `_pendingValue`. A landing the
2149
1956
  // equality gate swallowed moves none of them and must leave no stamp.
2150
- const i = e.ge !== asyncStartValue || e.ae !== asyncStartTime || e.Pe === t;
1957
+ const i = e.Ae !== asyncStartValue || e.fe !== asyncStartTime || e._e === t;
2151
1958
  if (i) stampWrite(e, "async", n === undefined ? NO_VALUES : n, t);
2152
1959
  // Flight over either way — an equality-swallowed landing still spent
2153
1960
  // the wall time (finalizeFlight only chains through a fresh stamp).
@@ -2157,12 +1964,12 @@ const engineHooks = {
2157
1964
  // Landed through setSignal: reclassify its "write" stamp as an async
2158
1965
  // landing — but only if it actually stamped (the value changed) since
2159
1966
  // asyncStart; a no-change landing must leave no fresh stamp behind.
2160
- const o = e.$e;
1967
+ const o = e.ke;
2161
1968
  if (o !== undefined && o.seq > asyncStartSeq && o.kind === "write") stampWrite(e, "async", NO_VALUES, t);
2162
1969
  finalizeFlight(e);
2163
1970
  },
2164
1971
  effectRunStart(e) {
2165
- pushFrame("effect", nodeName(e), e.xe, e);
1972
+ pushFrame("effect", nodeName(e), e.Ye, e);
2166
1973
  },
2167
1974
  effectRunEnd() {
2168
1975
  popFrame("effect");
@@ -2194,14 +2001,17 @@ const engineHooks = {
2194
2001
  transitionMerged(e, n) {
2195
2002
  trackHoldMerge(e, n);
2196
2003
  },
2197
- storeReplaced(e, n, t, i, o) {
2198
- checkImmutableUpdate(e, n, t, i, o);
2004
+ storeReplaced(e, n, t, i, o, r) {
2005
+ checkImmutableUpdate(e, n, t, i, o, r);
2199
2006
  },
2200
2007
  listChurn(e, n, t, i, o) {
2201
2008
  checkListIdentity(e, n, t, i, o);
2202
2009
  },
2203
2010
  boundaryFallback(e, n, t) {
2204
- trackFallback(e, n, t);
2011
+ for (const i of folds) i.fallback?.(e, n, t);
2012
+ },
2013
+ currentOrigin() {
2014
+ return ambientOrigin();
2205
2015
  }
2206
2016
  };
2207
2017
 
@@ -2212,14 +2022,9 @@ const attribution = {
2212
2022
  ...e
2213
2023
  };
2214
2024
  frames.length = 0;
2215
- scopeCosts.clear();
2216
- writeCosts.clear();
2217
2025
  waterfallLog = [];
2218
2026
  holdLog = [];
2219
2027
  activeHold = null;
2220
- feedbackSources.clear();
2221
- feedbackInteractions.clear();
2222
- feedbackNavigations.clear();
2223
2028
  navigationLog = [];
2224
2029
  openNavs.clear();
2225
2030
  interactionLog = [];
@@ -2228,26 +2033,20 @@ const attribution = {
2228
2033
  reportedCycles.clear();
2229
2034
  relays.clear();
2230
2035
  immutableReported.clear();
2231
- flightStats.clear();
2232
- fallbackStats.clear();
2233
2036
  originFrames.length = 0;
2234
2037
  interactionStack.length = 0;
2235
2038
  currentInteraction = null;
2236
2039
  hotCauses.clear();
2040
+ for (const e of folds) e.reset?.();
2237
2041
  setAttributionHooks(engineHooks);
2238
2042
  },
2239
2043
  disable() {
2240
2044
  clearListeners();
2241
2045
  history = [];
2242
2046
  frames.length = 0;
2243
- scopeCosts.clear();
2244
- writeCosts.clear();
2245
2047
  waterfallLog = [];
2246
2048
  holdLog = [];
2247
2049
  activeHold = null;
2248
- feedbackSources.clear();
2249
- feedbackInteractions.clear();
2250
- feedbackNavigations.clear();
2251
2050
  navigationLog = [];
2252
2051
  openNavs.clear();
2253
2052
  interactionLog = [];
@@ -2256,12 +2055,11 @@ const attribution = {
2256
2055
  reportedCycles.clear();
2257
2056
  relays.clear();
2258
2057
  immutableReported.clear();
2259
- flightStats.clear();
2260
- fallbackStats.clear();
2261
2058
  originFrames.length = 0;
2262
2059
  interactionStack.length = 0;
2263
2060
  currentInteraction = null;
2264
2061
  hotCauses.clear();
2062
+ for (const e of folds) e.reset?.();
2265
2063
  setAttributionHooks(null);
2266
2064
  },
2267
2065
  subscribe(e, n) {
@@ -2274,22 +2072,6 @@ const attribution = {
2274
2072
  history() {
2275
2073
  return history;
2276
2074
  },
2277
- why(e) {
2278
- const n = e?.[$REFRESH] ?? e;
2279
- return history.filter(e => e.node === n);
2280
- },
2281
- subscriptions(e) {
2282
- const n = e?.[$REFRESH] ?? e;
2283
- const t = [];
2284
- for (let e = n?.te ?? null; e !== null; e = e.ne) t.push(nodeName(e.oe));
2285
- return t;
2286
- },
2287
- costs() {
2288
- return {
2289
- scopes: [ ...scopeCosts.values() ].sort((e, n) => n.selfMs - e.selfMs),
2290
- writes: [ ...writeCosts.values() ].sort((e, n) => n.downstreamMs - e.downstreamMs)
2291
- };
2292
- },
2293
2075
  waterfalls() {
2294
2076
  return waterfallLog;
2295
2077
  },
@@ -2302,17 +2084,12 @@ const attribution = {
2302
2084
  interactions() {
2303
2085
  return interactionLog;
2304
2086
  },
2305
- feedback() {
2306
- return feedbackTables();
2307
- },
2308
2087
  markFlight(e, n = now()) {
2309
2088
  // Earliest wins: re-marking (a cache re-serving the same promise) must
2310
2089
  // not move the origin later.
2311
2090
  const t = flightOrigins.get(e);
2312
2091
  if (t === undefined || n < t) flightOrigins.set(e, n);
2313
- },
2314
- format: formatRerun,
2315
- formatOrigin: formatOrigin
2092
+ }
2316
2093
  };
2317
2094
 
2318
- export { attribution, formatOrigin, formatRerun };
2095
+ export { attribution, formatOrigin, formatRerun, isLongHold, isSilentHold, nodeName, now, registerFold, rootsOf };