@solidjs/signals 2.0.0-rc.11 → 2.0.0-rc.14

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 (118) hide show
  1. package/dist/dev-shared.js +2056 -3773
  2. package/dist/dev.attribution.js +159 -91
  3. package/dist/dev.js +7263 -8384
  4. package/dist/observe/affects.js +125 -104
  5. package/dist/observe/boundaries.js +458 -665
  6. package/dist/observe/core/action.js +63 -66
  7. package/dist/observe/core/async.js +240 -383
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +271 -199
  10. package/dist/observe/core/constants.js +115 -178
  11. package/dist/observe/core/context.js +3 -3
  12. package/dist/observe/core/core.js +840 -1428
  13. package/dist/observe/core/dev.js +41 -18
  14. package/dist/observe/core/effect.js +75 -86
  15. package/dist/observe/core/external.js +5 -5
  16. package/dist/observe/core/graph.js +61 -70
  17. package/dist/observe/core/heap.js +75 -98
  18. package/dist/observe/core/invariants.js +1 -34
  19. package/dist/observe/core/lanes.js +704 -181
  20. package/dist/observe/core/owner.js +80 -101
  21. package/dist/observe/core/scheduler.js +708 -1351
  22. package/dist/observe/core/verdict.js +302 -621
  23. package/dist/observe/flatten.js +79 -0
  24. package/dist/observe/index.js +15 -13
  25. package/dist/observe/map.js +232 -235
  26. package/dist/observe/reveal.js +212 -0
  27. package/dist/observe/signals.js +133 -215
  28. package/dist/observe/store/affects.js +135 -0
  29. package/dist/observe/store/index.js +29 -24
  30. package/dist/observe/store/optimistic.js +538 -0
  31. package/dist/observe/store/projection.js +302 -0
  32. package/dist/observe/store/reconcile.js +257 -0
  33. package/dist/observe/store/store.js +1945 -277
  34. package/dist/observe/store/storePath.js +1 -1
  35. package/dist/{prod/store/next → observe/store}/target.js +17 -18
  36. package/dist/observe/store/types.js +73 -0
  37. package/dist/observe/store/utils.js +1 -13
  38. package/dist/prod/affects.js +127 -106
  39. package/dist/prod/boundaries.js +442 -654
  40. package/dist/prod/core/action.js +71 -74
  41. package/dist/prod/core/async.js +307 -442
  42. package/dist/prod/core/constants.js +112 -177
  43. package/dist/prod/core/context.js +3 -3
  44. package/dist/prod/core/core.js +852 -1448
  45. package/dist/prod/core/effect.js +88 -99
  46. package/dist/prod/core/external.js +5 -5
  47. package/dist/prod/core/graph.js +61 -70
  48. package/dist/prod/core/heap.js +75 -98
  49. package/dist/prod/core/invariants.js +1 -32
  50. package/dist/prod/core/lanes.js +666 -181
  51. package/dist/prod/core/owner.js +75 -96
  52. package/dist/prod/core/scheduler.js +677 -1332
  53. package/dist/prod/core/verdict.js +297 -621
  54. package/dist/prod/flatten.js +77 -0
  55. package/dist/prod/index.js +15 -13
  56. package/dist/prod/map.js +143 -146
  57. package/dist/prod/reveal.js +212 -0
  58. package/dist/prod/signals.js +144 -226
  59. package/dist/prod/store/affects.js +135 -0
  60. package/dist/prod/store/index.js +28 -19
  61. package/dist/prod/store/optimistic.js +533 -0
  62. package/dist/prod/store/projection.js +295 -0
  63. package/dist/prod/store/reconcile.js +257 -0
  64. package/dist/prod/store/store.js +1840 -272
  65. package/dist/prod/store/storePath.js +1 -1
  66. package/dist/{observe/store/next → prod/store}/target.js +13 -17
  67. package/dist/prod/store/types.js +73 -0
  68. package/dist/prod/store/utils.js +1 -11
  69. package/dist/types/affects.d.ts +11 -30
  70. package/dist/types/boundaries.d.ts +62 -176
  71. package/dist/types/carved.d.ts +1 -0
  72. package/dist/types/core/action.d.ts +5 -6
  73. package/dist/types/core/async.d.ts +10 -5
  74. package/dist/types/core/attribution-hooks.d.ts +48 -1
  75. package/dist/types/core/attribution.d.ts +29 -9
  76. package/dist/types/core/constants.d.ts +125 -171
  77. package/dist/types/core/core.d.ts +104 -264
  78. package/dist/types/core/dev.d.ts +60 -7
  79. package/dist/types/core/effect.d.ts +0 -7
  80. package/dist/types/core/graph.d.ts +7 -1
  81. package/dist/types/core/heap.d.ts +5 -7
  82. package/dist/types/core/index.d.ts +6 -6
  83. package/dist/types/core/invariants.d.ts +0 -29
  84. package/dist/types/core/lanes.d.ts +65 -87
  85. package/dist/types/core/owner.d.ts +3 -3
  86. package/dist/types/core/scheduler.d.ts +249 -331
  87. package/dist/types/core/types.d.ts +51 -85
  88. package/dist/types/core/verdict.d.ts +3 -0
  89. package/dist/types/flatten.d.ts +8 -0
  90. package/dist/types/index.d.ts +7 -5
  91. package/dist/types/reveal.d.ts +19 -0
  92. package/dist/types/signals.d.ts +53 -92
  93. package/dist/types/store/affects.d.ts +1 -0
  94. package/dist/types/store/index.d.ts +35 -15
  95. package/dist/types/store/optimistic.d.ts +23 -0
  96. package/dist/types/store/projection.d.ts +18 -0
  97. package/dist/types/store/reconcile.d.ts +3 -0
  98. package/dist/types/store/store.d.ts +165 -143
  99. package/dist/types/store/storePath.d.ts +1 -1
  100. package/dist/types/store/target.d.ts +151 -0
  101. package/dist/types/store/types.d.ts +84 -0
  102. package/package.json +1 -1
  103. package/dist/observe/core/optimistic.js +0 -669
  104. package/dist/observe/store/next/optimistic.js +0 -717
  105. package/dist/observe/store/next/projection.js +0 -266
  106. package/dist/observe/store/next/reconcile.js +0 -375
  107. package/dist/observe/store/next/store.js +0 -2480
  108. package/dist/prod/core/optimistic.js +0 -658
  109. package/dist/prod/store/next/optimistic.js +0 -710
  110. package/dist/prod/store/next/projection.js +0 -257
  111. package/dist/prod/store/next/reconcile.js +0 -373
  112. package/dist/prod/store/next/store.js +0 -2356
  113. package/dist/types/core/optimistic.d.ts +0 -6
  114. package/dist/types/store/next/optimistic.d.ts +0 -25
  115. package/dist/types/store/next/projection.d.ts +0 -27
  116. package/dist/types/store/next/reconcile.d.ts +0 -10
  117. package/dist/types/store/next/store.d.ts +0 -143
  118. package/dist/types/store/next/target.d.ts +0 -196
@@ -1,24 +1,27 @@
1
1
  import { setAttributionHooks } from "./attribution-hooks.js";
2
2
 
3
- import { NOT_PENDING, CONFIG_PLUMBING, CONFIG_DERIVED_OVERRIDE } from "./constants.js";
3
+ import { NOT_PENDING, CONFIG_PLUMBING, CONFIG_OVERRIDE, CONFIG_GUESS, CONFIG_VERDICT } from "./constants.js";
4
4
 
5
5
  import { OBSERVE, liveRootOwners, emitDiagnostic, ownerPath, reportDiagnostic, isSuppressed, anyExcluded, isExcluded, GRAPH_SIZE_WARN_AT, noteFanOut } from "./dev.js";
6
6
 
7
+ import { blockersOf, GlobalQueue } from "./scheduler.js";
8
+
7
9
  /** A fallback shown for less than this is a flash: feedback for a wait too short to need it. */ const FALLBACK_FLASH_MS = 150;
8
10
 
9
11
  /**
10
- * Under an owner the observer marked as its own (`OBSERVE.exclude`), or
11
- * framework plumbing itself (`CONFIG_PLUMBING` — the HMR memo between a
12
- * component's root and its body, which is nobody's node): the engine records
13
- * nothing about the node. Plumbing is a bit read and excludes the node alone,
14
- * not what it owns; the observer exclusion is cached per node once any
15
- * exists, before that a flag read.
12
+ * Under an owner the observer marked as its own (`OBSERVE.exclude`, and not
13
+ * re-admitted by a nearer `OBSERVE.include`), or framework plumbing itself
14
+ * (`CONFIG_PLUMBING` — the HMR memo between a component's root and its
15
+ * body, which is nobody's node): the engine records nothing about the node.
16
+ * Plumbing is a bit read and excludes the node alone, not what it owns; the
17
+ * observer exclusion is cached per node once any exists, before that a flag
18
+ * read.
16
19
  */ function excludedNode(e) {
17
- if ((e.C & CONFIG_PLUMBING) !== 0) return true;
20
+ if ((e.u & CONFIG_PLUMBING) !== 0) return true;
18
21
  if (!anyExcluded()) return false;
19
22
  const n = e;
20
- if (n.$e === undefined) n.$e = isExcluded(e);
21
- return n.$e;
23
+ if (n.me === undefined) n.me = isExcluded(e);
24
+ return n.me;
22
25
  }
23
26
 
24
27
  let attributionActive = false;
@@ -41,7 +44,7 @@ const defaultOptions = {
41
44
  count: 120,
42
45
  windowMs: 1e3
43
46
  },
44
- wideDeps: 30,
47
+ wideDeps: 200,
45
48
  hotTime: {
46
49
  budgetMs: 8,
47
50
  windowMs: 1e3
@@ -118,7 +121,7 @@ function nodeName(e) {
118
121
  }
119
122
 
120
123
  /** The record vocabulary for a computation's kind: effects carry a `_type`, memos do not. */ function nodeKind(e) {
121
- return e.He ? "effect" : "memo";
124
+ return e.i ? "effect" : "memo";
122
125
  }
123
126
 
124
127
  /**
@@ -223,17 +226,24 @@ const interactionStack = [];
223
226
 
224
227
  function interactionStart(e) {
225
228
  interactionStack.push(currentInteraction);
229
+ // No `ref.event`: the WeakMap answers undefined.
230
+ const n = eventInteractions.get(e.event);
231
+ if (n?.joinable && openInteractions.has(n)) {
232
+ n.open = true;
233
+ currentInteraction = n.event.origin;
234
+ return;
235
+ }
226
236
  // One clock read: the frame opens now; the interaction began at `ref.at`
227
237
  // when the runtime dated it (the event's own timestamp), else now too.
228
- const n = now();
229
- const t = {
238
+ const t = now();
239
+ const o = {
230
240
  kind: "interaction",
231
241
  name: e.type,
232
- at: e.at ?? n
242
+ at: e.at ?? t
233
243
  };
234
- if (e.target) t.target = targetLabel(e.target);
235
- currentInteraction = t;
236
- openInteraction(t, n);
244
+ if (e.target) o.target = targetLabel(e.target);
245
+ currentInteraction = o;
246
+ openInteraction(o, t, e.event);
237
247
  }
238
248
 
239
249
  function interactionEnd(e) {
@@ -317,7 +327,7 @@ const effectFrames = new WeakMap;
317
327
  const n = effectStack[effectStack.length - 1];
318
328
  if (n !== undefined) effectFrames.set(e, {
319
329
  node: n,
320
- causes: n.Ke
330
+ causes: n.ge
321
331
  });
322
332
  }
323
333
 
@@ -335,7 +345,7 @@ function pushFrame(e, n, t, o) {
335
345
  if (s !== undefined) i.interaction = s;
336
346
  if (o !== undefined) {
337
347
  const e = o;
338
- if (e.Ye !== undefined) i.run = e.Ye;
348
+ if (e.we !== undefined) i.run = e.we;
339
349
  // The frame → node map is filled by the first write inside the callback
340
350
  // (noteEffectOrigin), not here: most callbacks never write.
341
351
  effectStack.push(o);
@@ -373,15 +383,22 @@ function popFrame(e) {
373
383
  return;
374
384
  }
375
385
  }
376
- const n = {
386
+ // The initial navigation is the document's: its request is the time origin
387
+ // unless the router says otherwise, and it left nowhere.
388
+ const n = e.initial === true;
389
+ const t = {
377
390
  kind: e.kind,
378
- at: e.at ?? now()
391
+ at: e.at ?? (n ? 0 : now())
379
392
  };
380
- if (e.from !== undefined) n.from = e.from;
381
- const t = enclosingInteraction();
382
- if (t !== undefined) n.interaction = t;
383
- originFrames.push(n);
384
- openNavigation(n, e);
393
+ if (!n && e.from !== undefined) t.from = e.from;
394
+ // Declared beats ambient: a router that awaited before writing hands back
395
+ // the origin it captured in the request (`undefined` when the request ran
396
+ // under none — still a declaration); what is on the stack now is whatever
397
+ // happened to be running.
398
+ const o = "interaction" in e ? interactionOf(e.interaction) : enclosingInteraction();
399
+ if (o !== undefined) t.interaction = o;
400
+ originFrames.push(t);
401
+ openNavigation(t, e);
385
402
  }
386
403
 
387
404
  function originEnd() {
@@ -415,7 +432,8 @@ function originEnd() {
415
432
  if (e.to !== undefined && e.to !== n) t.push(e.to);
416
433
  const o = navStates.get(e)?.event.redirects;
417
434
  if (o !== undefined) t.push(`redirected from ${o.map(e => e.to ?? e.name ?? "?").join(" → ")}`);
418
- return `navigation to ${n}${t.length > 0 ? ` (${t.join(", ")})` : ""}`;
435
+ const i = navStates.get(e)?.event.initial === true;
436
+ return `${i ? "initial " : ""}navigation to ${n}${t.length > 0 ? ` (${t.join(", ")})` : ""}`;
419
437
  }
420
438
 
421
439
  default:
@@ -424,12 +442,6 @@ function originEnd() {
424
442
  }
425
443
 
426
444
  /** Record a root change (setSignal / refresh / async landing) on the node. */
427
- /** Live subscriber count, walked on demand — the core keeps no counter. */ function countSubscribers(e) {
428
- let n = 0;
429
- for (let t = e.u; t !== null; t = t.Pe) n++;
430
- return n;
431
- }
432
-
433
445
  /**
434
446
  * HUGE_FAN_OUT from the engine's lower `fanOut` threshold (see dev.ts): a
435
447
  * committed root invalidation reaching hundreds of subscribers re-runs all
@@ -438,12 +450,20 @@ function originEnd() {
438
450
  * was a post-construction field that forked node shapes). Stops at
439
451
  * GRAPH_SIZE_WARN_AT, where the always-on core check takes over, so the two
440
452
  * never fire for the same write; the once-per-node dedupe is the core's.
453
+ * HMR plumbing subscribers (the refresh memo of every mounted instance, on
454
+ * a component's registration signal) are not counted toward `fanOut`; the
455
+ * handover is on the full list, which is what the core counts.
441
456
  */ function checkFanOut(e, n) {
442
457
  const t = options.fanOut;
443
458
  if (typeof t !== "number") return;
444
- const o = countSubscribers(e);
445
- if (o < t || o >= GRAPH_SIZE_WARN_AT) return;
446
- noteFanOut(e, o, n);
459
+ let o = 0;
460
+ let i = 0;
461
+ for (let n = e.t; n !== null; n = n.l) {
462
+ o++;
463
+ if (!(n.o.u & CONFIG_PLUMBING)) i++;
464
+ }
465
+ if (i < t || o >= GRAPH_SIZE_WARN_AT) return;
466
+ noteFanOut(e, i, n);
447
467
  }
448
468
 
449
469
  function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
@@ -463,8 +483,8 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
463
483
  i.origin = n === "async" ? asyncOrigin(e) : currentOrigin();
464
484
  i.at = now();
465
485
  i.stack = captureStack();
466
- const s = e.ze;
467
- e.ze = i;
486
+ const s = e.ve;
487
+ e.ve = i;
468
488
  noteNavigationWrite(s, i);
469
489
  noteInteractionWrite(i.origin, excludedNode(e));
470
490
  if (n === "write") trackEffectWrite(e, i, o);
@@ -475,7 +495,7 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
475
495
  }
476
496
 
477
497
  /** Record a derived change (memo produced a new value) with its causes. */ function stampDerived(e, n) {
478
- e.ze = {
498
+ e.ve = {
479
499
  seq: ++changeSeq,
480
500
  kind: "derived",
481
501
  name: nodeName(e),
@@ -490,19 +510,19 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
490
510
  * run's links. A refresh() stamp on the node itself also counts — that is a
491
511
  * self-invalidation, not a dep change.
492
512
  */ function collectCauses(e) {
493
- const n = e.Xe ?? 0;
513
+ const n = e.ke ?? 0;
494
514
  const t = [];
495
- const o = e.ze;
515
+ const o = e.ve;
496
516
  if (o !== undefined && o.seq > n && o.kind === "refresh") t.push(o);
497
- for (let o = e.Ee; o !== null; o = o.Ie) {
498
- const e = o.De.ze;
517
+ for (let o = e._; o !== null; o = o.p) {
518
+ const e = o.I.ve;
499
519
  if (e !== undefined && e.seq > n) t.push(e);
500
520
  }
501
521
  return t;
502
522
  }
503
523
 
504
524
  /** Advance the node's seen-cursor to the present. Call after every run. */ function markSeen(e) {
505
- e.Xe = changeSeq;
525
+ e.ke = changeSeq;
506
526
  }
507
527
 
508
528
  /**
@@ -514,32 +534,39 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
514
534
  * still owed) and not part of the subscription diff.
515
535
  */ function captureDeps(e) {
516
536
  const n = [];
517
- for (let t = e.Ee; t !== null; t = t.Ie) {
518
- n.push(t.De);
519
- if (t === e.Je) break;
537
+ for (let t = e._; t !== null; t = t.p) {
538
+ n.push(t.I);
539
+ if (t === e.S) break;
520
540
  }
521
541
  return n;
522
542
  }
523
543
 
524
544
  /**
525
545
  * Wide-scope warning — the coarse-read / helper-leak signature: one scope
526
- * subscribed to dozens of sources re-runs when ANY of them change. Fired from
546
+ * subscribed to hundreds of sources re-runs when ANY of them change. Fired from
527
547
  * recordRerun for re-runs and directly from recompute for creation runs (a
528
- * memo can be born too wide). Re-warns only on 50% further growth.
548
+ * memo can be born too wide). Re-warns only on 50% further growth. Counts
549
+ * what the scope's author can act on: HMR plumbing sources are not counted,
550
+ * and a framework scope wide by construction (CONFIG_WIDE) is not judged —
551
+ * the always-on HUGE_FAN_IN still is.
529
552
  */ function checkDepWidth(e) {
530
553
  const n = options.wideDeps;
531
- if (n === false) return;
554
+ // Only dev sets CONFIG_WIDE (see constants.ts).
555
+ if (n === false || false) return;
532
556
  let t = 0;
533
557
  const o = [];
534
- for (let n = e.Ee; n !== null; n = n.Ie) {
535
- t++;
536
- if (o.length < 12) o.push(nodeName(n.De));
537
- if (n === e.Je) break;
558
+ for (let n = e._; n !== null; n = n.p) {
559
+ // HMR plumbing is a dev-only hop the scope cannot read past: not a source.
560
+ if (!(n.I.u & CONFIG_PLUMBING)) {
561
+ t++;
562
+ if (o.length < 12) o.push(nodeName(n.I));
563
+ }
564
+ if (n === e.S) break;
538
565
  // the validated prefix, as captureDeps
539
566
  }
540
567
  const i = e;
541
- if (t < n || t < (i.en ?? 0) * 1.5) return;
542
- i.en = t;
568
+ if (t < n || t < (i.ye ?? 0) * 1.5) return;
569
+ i.ye = t;
543
570
  const s = `[WIDE_SCOPE_DEPS] ${nodeKind(e)} "${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: ${o.join(", ")}${t > o.length ? ", …" : ""}`;
544
571
  reportDiagnostic(emitDiagnostic({
545
572
  code: "WIDE_SCOPE_DEPS",
@@ -568,14 +595,14 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
568
595
  if (t === false) return;
569
596
  const o = e;
570
597
  const i = Date.now();
571
- if (o.nn === undefined || i - o.nn > t.windowMs) {
572
- o.nn = i;
573
- o.tn = 0;
574
- o.sn = false;
575
- }
576
- o.tn = (o.tn ?? 0) + 1;
577
- if (o.sn || o.tn < t.count) return;
578
- o.sn = true;
598
+ if (o.$e === undefined || i - o.$e > t.windowMs) {
599
+ o.$e = i;
600
+ o.be = 0;
601
+ o.Fe = false;
602
+ }
603
+ o.be = (o.be ?? 0) + 1;
604
+ if (o.Fe || o.be < t.count) return;
605
+ o.Fe = true;
579
606
  // Root-cause key: the set of originating writes behind this scope's latest
580
607
  // re-run. Scopes hot from the SAME roots share one aggregation window.
581
608
  const s = new Set;
@@ -592,10 +619,10 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
592
619
  hotCauses.set(r, a);
593
620
  }
594
621
  a.scopes++;
595
- a.runs += o.tn;
622
+ a.runs += o.be;
596
623
  if (a.scopes === 1) {
597
624
  const s = n.map(e => `"${e.name}" (${e.kind})`).join(", ");
598
- const r = `[HOT_SCOPE_RERUNS] ${nodeKind(e)} "${nodeName(e)}" re-ran ${o.tn} times ` + `in ${Math.max(1, i - o.nn)}ms — a hot signal is likely leaking into this ` + `scope. Latest cause: ${s || "(untracked pull)"}`;
625
+ const r = `[HOT_SCOPE_RERUNS] ${nodeKind(e)} "${nodeName(e)}" re-ran ${o.be} times ` + `in ${Math.max(1, i - o.$e)}ms — a hot signal is likely leaking into this ` + `scope. Latest cause: ${s || "(untracked pull)"}`;
599
626
  reportDiagnostic(emitDiagnostic({
600
627
  code: "HOT_SCOPE_RERUNS",
601
628
  kind: "perf",
@@ -603,7 +630,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
603
630
  message: r,
604
631
  nodeName: nodeName(e),
605
632
  data: {
606
- runs: o.tn,
633
+ runs: o.be,
607
634
  windowMs: t.windowMs,
608
635
  causes: n.map(e => e.name)
609
636
  }
@@ -641,16 +668,16 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
641
668
  if (o === false) return;
642
669
  const i = e;
643
670
  const s = now();
644
- if (i.rn === undefined || s - i.rn > o.windowMs) {
645
- i.rn = s;
646
- i.an = 0;
647
- i.cn = false;
648
- }
649
- i.an = (i.an ?? 0) + n;
650
- if (i.cn || i.an < o.budgetMs) return;
651
- i.cn = true;
671
+ if (i.Le === undefined || s - i.Le > o.windowMs) {
672
+ i.Le = s;
673
+ i.Ce = 0;
674
+ i.Me = false;
675
+ }
676
+ i.Ce = (i.Ce ?? 0) + n;
677
+ if (i.Me || i.Ce < o.budgetMs) return;
678
+ i.Me = true;
652
679
  const r = t.map(e => `"${e.name}" (${e.kind})`).join(", ");
653
- const a = `[HOT_SCOPE_TIME] ${nodeKind(e)} "${nodeName(e)}" spent ` + `${i.an.toFixed(1)}ms of compute inside one ${o.windowMs}ms window ` + `(budget ${o.budgetMs}ms). Latest cause: ${r || "(untracked pull)"}`;
680
+ const a = `[HOT_SCOPE_TIME] ${nodeKind(e)} "${nodeName(e)}" spent ` + `${i.Ce.toFixed(1)}ms of compute inside one ${o.windowMs}ms window ` + `(budget ${o.budgetMs}ms). Latest cause: ${r || "(untracked pull)"}`;
654
681
  reportDiagnostic(emitDiagnostic({
655
682
  code: "HOT_SCOPE_TIME",
656
683
  kind: "perf",
@@ -658,7 +685,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
658
685
  message: a,
659
686
  nodeName: nodeName(e),
660
687
  data: {
661
- spentMs: i.an,
688
+ spentMs: i.Ce,
662
689
  budgetMs: o.budgetMs,
663
690
  windowMs: o.windowMs,
664
691
  causes: t.map(e => e.name)
@@ -673,31 +700,34 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
673
700
  * names it while it happens, with the input that keeps triggering it. The
674
701
  * fix is upstream: an equality boundary on the part of the input the scope
675
702
  * depends on, or a narrower read. Plain runs only — a held or overlay run
676
- * may be replayed and is never blamed as waste.
703
+ * may be replayed and is never blamed as waste. A run that committed
704
+ * `undefined` arrives here as changed (see `recomputeEnd`): its work is a
705
+ * side effect (a projection's draft writes, a write from inside a memo), not
706
+ * its output.
677
707
  */ function checkWastedRecompute(e, n, t, o, i, s) {
678
708
  const r = options.wastedRecompute;
679
709
  if (r === false || t !== "plain") return;
680
710
  // This runs on every re-run: fields on the node (one property read each,
681
711
  // like hotRuns) and the run's own `at` — no clock read, no map lookup.
682
712
  const a = e;
683
- if (a.dn === undefined || n - a.dn > r.windowMs) {
684
- a.dn = n;
685
- a.fn = 0;
686
- a.un = 0;
687
- a.ln = 0;
688
- a.hn = false;
689
- }
690
- a.fn = a.fn + 1;
713
+ if (a.He === undefined || n - a.He > r.windowMs) {
714
+ a.He = n;
715
+ a.We = 0;
716
+ a.Ge = 0;
717
+ a.qe = 0;
718
+ a.Be = false;
719
+ }
720
+ a.We = a.We + 1;
691
721
  if (!o) {
692
- a.un = a.un + 1;
693
- a.ln = a.ln + i;
722
+ a.Ge = a.Ge + 1;
723
+ a.qe = a.qe + i;
694
724
  }
695
- if (a.hn || a.fn < r.minRuns || a.un / a.fn < r.ratio || a.ln < r.budgetMs) return;
696
- a.hn = true;
725
+ if (a.Be || a.We < r.minRuns || a.Ge / a.We < r.ratio || a.qe < r.budgetMs) return;
726
+ a.Be = true;
697
727
  const c = {
698
- runs: a.fn,
699
- wasted: a.un,
700
- wastedMs: a.ln
728
+ runs: a.We,
729
+ wasted: a.Ge,
730
+ wastedMs: a.qe
701
731
  };
702
732
  const d = s.map(e => `"${e.name}" (${e.kind})`).join(", ");
703
733
  const f = `[WASTED_RECOMPUTE] ${nodeKind(e)} "${nodeName(e)}" re-ran ${c.runs} times in ` + `${r.windowMs}ms and ${c.wasted} of those produced the same value — ` + `${c.wastedMs.toFixed(1)}ms of compute the equality gate then discarded. Its inputs ` + `change without changing its result: put an equality boundary upstream (a memo over the ` + `part of the input it reads, or an \`equals\` on the source), or read a narrower slice ` + `(the property, not the object). Latest cause: ${d || "(untracked pull)"}`;
@@ -720,14 +750,14 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
720
750
  function recordRerun(e, n, t, o, i, s) {
721
751
  const r = n.causes;
722
752
  const a = e;
723
- const c = a.Ke;
753
+ const c = a.ge;
724
754
  const d = n.interaction;
725
755
  if (excludedNode(e)) {
726
756
  // The observer's own computation: keep the per-node bookkeeping its
727
757
  // effect phase reads (see effectRunStart) and record nothing.
728
- a.pn = d;
729
- a.Ye = undefined;
730
- a.Ke = r;
758
+ a.xe = d;
759
+ a.we = undefined;
760
+ a.ge = r;
731
761
  return;
732
762
  }
733
763
  // The facts every run leaves on the node, record or not: the run sequence
@@ -736,10 +766,10 @@ function recordRerun(e, n, t, o, i, s) {
736
766
  // cause list of its own, so it takes this run's interaction and causes
737
767
  // (see effectRunStart / pushFrame).
738
768
  const f = ++runSeq;
739
- const u = a.mn = (a.mn ?? 0) + 1;
740
- a.pn = d;
741
- a.Ye = f;
742
- a.Ke = r;
769
+ const u = a.Ke = (a.Ke ?? 0) + 1;
770
+ a.xe = d;
771
+ a.we = f;
772
+ a.ge = r;
743
773
  noteInteractionRun(d, t.selfMs, false);
744
774
  if (openFlush !== null) noteFlushRun(openFlush, false, d);
745
775
  // The checks read the facts, not the record, so they run with or without
@@ -856,8 +886,10 @@ function shallowEquivalent(e, n) {
856
886
  for (let e = 0; e < t.length; e++) if (t[e] !== o[e]) return false;
857
887
  return true;
858
888
  }
859
- const o = Object.keys(e);
860
- if (o.length > UNSTABLE_KEY_CAP || o.length !== Object.keys(n).length) return false;
889
+ // Every own key, symbols included: a symbol-keyed box holding a fresh value
890
+ // each run (web's `dynamic` boxes its in-flight promise) is not equivalent.
891
+ const o = Reflect.ownKeys(e);
892
+ if (o.length > UNSTABLE_KEY_CAP || o.length !== Reflect.ownKeys(n).length) return false;
861
893
  for (const t of o) {
862
894
  if (!(t in n) || e[t] !== n[t]) return false;
863
895
  }
@@ -878,15 +910,15 @@ function shallowEquivalent(e, n) {
878
910
  const i = e;
879
911
  if (n === t || // paranoia: changed runs should never hit this
880
912
  !isPlainShape(n) || !isPlainShape(t) || !shallowEquivalent(n, t)) {
881
- i.gn = 0;
882
- i.wn = false;
913
+ i.Ve = 0;
914
+ i.je = false;
883
915
  return;
884
916
  }
885
- i.gn = (i.gn ?? 0) + 1;
886
- if (i.wn || i.gn < o) return;
887
- i.wn = true;
917
+ i.Ve = (i.Ve ?? 0) + 1;
918
+ if (i.je || i.Ve < o) return;
919
+ i.je = true;
888
920
  const s = Array.isArray(t) ? "array" : "object";
889
- const r = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${s} on ` + `${i.gn} consecutive runs — its equality gate never closes, so every ` + `subscriber re-runs on every upstream change. Return stable references or pass an ` + `\`equals\` option.`;
921
+ const r = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${s} on ` + `${i.Ve} consecutive runs — its equality gate never closes, so every ` + `subscriber re-runs on every upstream change. Return stable references or pass an ` + `\`equals\` option.`;
890
922
  reportDiagnostic(emitDiagnostic({
891
923
  code: "UNSTABLE_MEMO_OUTPUT",
892
924
  kind: "perf",
@@ -894,7 +926,7 @@ function shallowEquivalent(e, n) {
894
926
  message: r,
895
927
  nodeName: nodeName(e),
896
928
  data: {
897
- runs: i.gn,
929
+ runs: i.Ve,
898
930
  shape: s
899
931
  }
900
932
  }, e));
@@ -1033,17 +1065,17 @@ const copyReported = new WeakSet;
1033
1065
  const s = i !== undefined && i.kind === "effect" ? effectFrames.get(i) : undefined;
1034
1066
  const r = s === undefined ? 0 : devId(s.node);
1035
1067
  recordNodes.set(n, e);
1036
- o.vn = o.vn === undefined || o.vn === r ? r : null;
1037
- if (s !== undefined && t !== undefined && t === s.node.ce) {
1068
+ o.Ye = o.Ye === undefined || o.Ye === r ? r : null;
1069
+ if (s !== undefined && t !== undefined && t === s.node.pe) {
1038
1070
  copyWrites.add(n);
1039
- o.kn = o.Sn === r ? (o.kn ?? 0) + 1 : 1;
1040
- o.Sn = r;
1041
- if (o.kn >= 2 && o.vn === r) checkCopyEffect(s.node, e);
1042
- } else o.kn = 0;
1071
+ o.ze = o.Xe === r ? (o.ze ?? 0) + 1 : 1;
1072
+ o.Xe = r;
1073
+ if (o.ze >= 2 && o.Ye === r) checkCopyEffect(s.node, e);
1074
+ } else o.ze = 0;
1043
1075
  }
1044
1076
 
1045
1077
  /** The effect's source whose current value the compute output is, if any (the prop-to-state port). */ function passthroughSource(e) {
1046
- for (let n = e.Ee; n !== null; n = n.Ie) if (n.De.ce === e.ce) return nodeName(n.De);
1078
+ for (let n = e._; n !== null; n = n.p) if (n.I.pe === e.pe) return nodeName(n.I);
1047
1079
  return undefined;
1048
1080
  }
1049
1081
 
@@ -1116,7 +1148,7 @@ function checkCopyEffect(e, n) {
1116
1148
  c.count++;
1117
1149
  const d = copyWrites.has(s);
1118
1150
  const f = recordNodes.get(s);
1119
- const u = f !== undefined && f.vn === devId(i.node);
1151
+ const u = f !== undefined && f.Ye === devId(i.node);
1120
1152
  // Derivable outright: the value is the compute output and nothing else
1121
1153
  // writes the signal. A copy INTO a signal that has other writers is the
1122
1154
  // "reset editable state from a source" shape — the tear is real, but a memo
@@ -1132,7 +1164,7 @@ function checkCopyEffect(e, n) {
1132
1164
  copyReported.add(f);
1133
1165
  }
1134
1166
  if (h === "warn") c.warned = true;
1135
- const p = e.He ? "effect" : "memo";
1167
+ const p = e.i ? "effect" : "memo";
1136
1168
  const m = nodeName(i.node);
1137
1169
  const g = l ? copyRepair(i.node, s.name) : d ? `The written value is the effect's compute output, but "${s.name}" has other ` + `writers — editable state reset from a source. If the reset is the intent, the tear ` + `is its cost; if "${s.name}" only ever mirrors the source, drop the local copy ` + `and read the source.` : u ? `Nothing else writes "${s.name}" — it is derived state: make it a memo over what ` + `the effect reads and every reader gets it in the same flush.` : `If "${s.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.`;
1138
1170
  const w = `[EFFECT_RELAY_TEAR] ${p} "${nodeName(e)}" ran twice for one write of ` + `"${r.name}": once in the flush where "${r.name}" changed, and again after ` + `effect "${m}" relayed it by writing "${s.name}" — the first frame showed the ` + `new "${r.name}" with the stale "${s.name}"` + (c.count > 1 ? ` (${c.count} times so far)` : "") + `. ${g}`;
@@ -1223,7 +1255,7 @@ const listIdentityWarned = new WeakSet;
1223
1255
  function recordId(e) {
1224
1256
  if (e === null || typeof e !== "object") return undefined;
1225
1257
  const n = e;
1226
- return n.id ?? n.key ?? n.yn ?? undefined;
1258
+ return n.id ?? n.key ?? n.Qe ?? undefined;
1227
1259
  }
1228
1260
 
1229
1261
  function checkListIdentity(e, n, t, o, i) {
@@ -1380,7 +1412,7 @@ function trackFlightStart(e, n) {
1380
1412
  const o = t - n.origin;
1381
1413
  for (const n of folds) n.flightLanded?.(e, o);
1382
1414
  if (records.observed("flight")) emitFlight(e, n, t, "landed");
1383
- const i = e.ze;
1415
+ const i = e.ve;
1384
1416
  // Only a stamp this landing produced may carry the measurement — a stale
1385
1417
  // async record from a previous landing must not be re-labeled.
1386
1418
  if (i !== undefined && i.kind === "async" && i.seq > n.startSeq) landedFlights.set(i, {
@@ -1416,8 +1448,8 @@ function checkWaterfall(e, n, t) {
1416
1448
  }
1417
1449
  if (i < 2) return;
1418
1450
  const r = e;
1419
- if ((r.Nn ?? 0) >= i) return;
1420
- r.Nn = i;
1451
+ if ((r.Ze ?? 0) >= i) return;
1452
+ r.Ze = i;
1421
1453
  const a = [ ...n.slice(n.length - (i - 1)), {
1422
1454
  name: nodeName(e),
1423
1455
  ms: t
@@ -1451,10 +1483,10 @@ let activeHold = null;
1451
1483
 
1452
1484
  let holdLog = [];
1453
1485
 
1454
- /** Companions are optimistic nodes too; `_parentSource` marks them. So is a
1455
- * memo carrying a DERIVED override (lanes stage, #3479) — a lane pass's
1456
- * result, not a write anyone made: neither is an acknowledgement. */ function isCompanion(e) {
1457
- return !!e.o && e.o.$n !== undefined || (e.C & CONFIG_DERIVED_OVERRIDE) !== 0;
1486
+ /** A lane's derivation (L2: a node carrying the lane's value that is not a
1487
+ * written guess — lanes.ts) is a lane pass's result, not a write anyone
1488
+ * made: not an acknowledgement. */ function isCompanion(e) {
1489
+ return (e.u & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE;
1458
1490
  }
1459
1491
 
1460
1492
  const HOLD_CENSUS_CAP = 1e4;
@@ -1475,13 +1507,14 @@ function acknowledge(e, n, t, o) {
1475
1507
  }); else if (r !== undefined) s.reader ??= r;
1476
1508
  }
1477
1509
 
1478
- function censusRegistrations(e, n) {
1510
+ /** The declarations made against the hold: the guesses of its lanes
1511
+ * (lanes.ts `_laneGuesses`) and its `affects()` marks (`_marks`). */ function censusRegistrations(e, n) {
1479
1512
  const t = {
1480
1513
  left: HOLD_CENSUS_CAP
1481
1514
  };
1482
- for (const o of e.bn) if (!isCompanion(o)) acknowledge(n, "optimistic", nodeName(o), reachesEffect(o, t));
1483
- for (const t of e.En) acknowledge(n, "optimistic", t?._name ?? "store", null);
1484
- for (const o of e.A) acknowledge(n, "affects", nodeName(o), reachesEffect(o, t));
1515
+ const o = GlobalQueue.Je?.(e);
1516
+ if (o !== undefined) for (const e of o) acknowledge(n, "optimistic", nodeName(e), reachesEffect(e, t));
1517
+ if (e.T !== null) for (const o of e.T) acknowledge(n, "affects", nodeName(o), reachesEffect(o, t));
1485
1518
  }
1486
1519
 
1487
1520
  /**
@@ -1495,9 +1528,9 @@ function censusRegistrations(e, n) {
1495
1528
  const o = [ e ];
1496
1529
  while (o.length > 0 && n.left-- > 0) {
1497
1530
  const e = o.pop();
1498
- for (let n = e.u; n !== null; n = n.Pe) {
1499
- const e = n.Ae;
1500
- if (e.He) return e;
1531
+ for (let n = e.t; n !== null; n = n.l) {
1532
+ const e = n.o;
1533
+ if (e.i) return e;
1501
1534
  if (!t.has(e)) {
1502
1535
  t.add(e);
1503
1536
  o.push(e);
@@ -1507,7 +1540,10 @@ function censusRegistrations(e, n) {
1507
1540
  return null;
1508
1541
  }
1509
1542
 
1510
- /** Companions an effect reads, anywhere downstream of the hold's nodes. */ function censusCompanions(e, n) {
1543
+ /** Verdict readers an effect reads, anywhere downstream of the hold's nodes
1544
+ * (L2: a pass that entered an `isPending`/`latest` window is marked
1545
+ * CONFIG_VERDICT; the observe tier notes which windows — verdict.ts
1546
+ * `_devWindows`). The reader acknowledges the node it derives from. */ function censusCompanions(e, n) {
1511
1547
  const t = new Set;
1512
1548
  const o = [ ...e ];
1513
1549
  const i = {
@@ -1517,14 +1553,16 @@ function censusRegistrations(e, n) {
1517
1553
  const e = o.pop();
1518
1554
  if (t.has(e)) continue;
1519
1555
  t.add(e);
1520
- const s = e.o;
1521
- if (s) {
1522
- let t;
1523
- if (s.xe !== undefined && (t = reachesEffect(s.xe, i))) acknowledge(n, "isPending", nodeName(e), t);
1524
- if (s.Me !== undefined && (t = reachesEffect(s.Me, i))) acknowledge(n, "latest", nodeName(e), t);
1525
- for (let e = s.i ?? null; e !== null; e = e.ge ?? null) o.push(e);
1556
+ for (let t = e.t; t !== null; t = t.l) {
1557
+ const s = t.o;
1558
+ if (s.u & CONFIG_VERDICT) {
1559
+ const t = s.en ?? 0;
1560
+ const o = s.i ? s : reachesEffect(s, i);
1561
+ if (t & 1 && o !== null) acknowledge(n, "isPending", nodeName(e), o);
1562
+ if (t & 2 && o !== null) acknowledge(n, "latest", nodeName(e), o);
1563
+ }
1564
+ o.push(s);
1526
1565
  }
1527
- for (let n = e.u; n !== null; n = n.Pe) o.push(n.Ae);
1528
1566
  }
1529
1567
  }
1530
1568
 
@@ -1552,8 +1590,8 @@ function trackHoldStart(e) {
1552
1590
  if (options.holds === false) return;
1553
1591
  const n = holdState(e);
1554
1592
  n.flushes++;
1555
- if (e.tt.length > 0) n.action = true;
1556
- for (const [t, o] of e.ae) if (o.size > 0) n.blockers.add(t);
1593
+ if (e.tt > 0) n.action = true;
1594
+ for (const t of blockersOf(e)) n.blockers.add(t);
1557
1595
  censusRegistrations(e, n);
1558
1596
  activeHold = n;
1559
1597
  }
@@ -1590,9 +1628,9 @@ function trackHoldSettled(e) {
1590
1628
  let i;
1591
1629
  let s;
1592
1630
  let r = -Infinity;
1593
- for (const n of e.In) {
1594
- if (typeof n.Se === "function" || isCompanion(n)) continue;
1595
- const e = n.ze;
1631
+ for (const n of e.nn) {
1632
+ if (typeof n.re === "function" || isCompanion(n)) continue;
1633
+ const e = n.ve;
1596
1634
  if (e === undefined || e.kind !== "write") continue;
1597
1635
  if (o === null) o = n;
1598
1636
  const a = {
@@ -1619,7 +1657,7 @@ function trackHoldSettled(e) {
1619
1657
  return;
1620
1658
  }
1621
1659
  censusRegistrations(e, n);
1622
- censusCompanions([ ...e.In, ...n.blockers ], n);
1660
+ censusCompanions([ ...e.nn, ...n.blockers ], n);
1623
1661
  const a = now();
1624
1662
  // The hold began no later than its first parked flush; an interaction stamp
1625
1663
  // reaches further back (dispatch). A node rewritten mid-hold keeps only its
@@ -1778,8 +1816,10 @@ function checkLongHold(e, n) {
1778
1816
  // derived override promotes rather than reverts. Same predicate the hold
1779
1817
  // census uses to skip them.
1780
1818
  if (!options.optimisticReverts || isCompanion(e)) return;
1781
- const i = e.Fe;
1782
- if (i && i(n, t)) return;
1819
+ // Method call, as every commit path does: store slot nodes share one
1820
+ // comparator that reads `this._host` (#3687).
1821
+ const i = e.tn;
1822
+ if (i && i.call(e, n, t)) return;
1783
1823
  const s = nodeName(e);
1784
1824
  // The two values are user data: quoted only under `values: "full"`; the
1785
1825
  // other levels say what happened without saying what was shown.
@@ -1835,33 +1875,33 @@ function checkLongHold(e, n) {
1835
1875
  const meet = e => {
1836
1876
  if (t.has(e)) return;
1837
1877
  t.add(e);
1838
- if (typeof e.Se === "function") n.computations++; else n.signals++;
1878
+ if (typeof e.re === "function") n.computations++; else n.signals++;
1839
1879
  o.push(e);
1840
1880
  };
1841
1881
  for (const t of e) {
1842
1882
  n.owners++;
1843
1883
  // Iterative: a deep tree must not grow the call stack.
1844
- let e = t._n;
1884
+ let e = t.sn;
1845
1885
  for (;;) {
1846
1886
  while (e !== null) {
1847
1887
  n.owners++;
1848
- if (typeof e.Se === "function") meet(e);
1849
- if (e._n !== null) i.push(e);
1850
- e = e.Fn;
1888
+ if (typeof e.re === "function") meet(e);
1889
+ if (e.sn !== null) i.push(e);
1890
+ e = e.rn;
1851
1891
  }
1852
1892
  const t = i.pop();
1853
1893
  if (t === undefined) break;
1854
- e = t._n;
1894
+ e = t.sn;
1855
1895
  }
1856
1896
  }
1857
1897
  for (let e = 0; e < o.length; e++) {
1858
1898
  const t = o[e];
1859
1899
  // Whoever reads this node: owned computations are already known; an
1860
1900
  // ownerless one is met only here.
1861
- for (let e = t.u; e !== null; e = e.Pe) meet(e.Ae);
1862
- if (typeof t.Se === "function") for (let e = t.Ee; e !== null; e = e.Ie) {
1901
+ for (let e = t.t; e !== null; e = e.l) meet(e.o);
1902
+ if (typeof t.re === "function") for (let e = t._; e !== null; e = e.p) {
1863
1903
  n.edges++;
1864
- meet(e.De);
1904
+ meet(e.I);
1865
1905
  }
1866
1906
  }
1867
1907
  return n;
@@ -1889,7 +1929,10 @@ function trackGraph(e) {
1889
1929
  };
1890
1930
  if (o !== undefined) i.route = o;
1891
1931
  records.emit("graph", i, undefined);
1892
- if (n === false || o === undefined) return;
1932
+ // The initial declaration is not a visit (it settles before route content
1933
+ // mounts — the same gate the feedback folds apply as `event.initial !== true`);
1934
+ // `initial` is `true | undefined`, set only by openNavigation, so truthiness is exact.
1935
+ if (n === false || o === undefined || e.initial) return;
1893
1936
  let s = routeCounts.get(o);
1894
1937
  if (s === undefined) routeCounts.set(o, s = []);
1895
1938
  s.push(t);
@@ -1942,10 +1985,13 @@ function trackGraph(e) {
1942
1985
  * One source abandoning flight after flight inside a window: every input
1943
1986
  * asked again and the earlier answers were thrown away — the
1944
1987
  * request-per-keystroke signature `feedback().flights[].abandoned` counts,
1945
- * as a finding while it happens. Warned once per window per source.
1988
+ * as a finding while it happens. Warned once per window per source. A
1989
+ * framework node superseding its own flights by design (CONFIG_SUPERSEDES)
1990
+ * is not judged; its flights are still recorded.
1946
1991
  */ function checkAbandonedFlights(e, n) {
1947
1992
  const t = options.abandonedFlights;
1948
- if (t === false || excludedNode(e)) return;
1993
+ // Only dev sets CONFIG_SUPERSEDES (see constants.ts).
1994
+ if (t === false || false || excludedNode(e)) return;
1949
1995
  const o = Date.now();
1950
1996
  let i = abandonWindows.get(e);
1951
1997
  if (i === undefined || o - i.start > t.windowMs) {
@@ -2070,6 +2116,7 @@ function openNavigation(e, n) {
2070
2116
  writes: 0,
2071
2117
  origin: e
2072
2118
  };
2119
+ if (n.initial === true) t.initial = true;
2073
2120
  if (e.from !== undefined) t.from = e.from;
2074
2121
  if (e.interaction !== undefined) t.interaction = e.interaction;
2075
2122
  const o = {
@@ -2139,8 +2186,8 @@ function closeNavigation(e) {
2139
2186
 
2140
2187
  /** holdStart: `t`'s staged writes are parked — their navigations settle with `t`. */ function markNavigationsHeld(e) {
2141
2188
  if (openNavs.size === 0) return;
2142
- for (const n of e.In) {
2143
- const e = n.ze?.origin;
2189
+ for (const n of e.nn) {
2190
+ const e = n.ve?.origin;
2144
2191
  if (e?.kind !== "navigation") continue;
2145
2192
  const t = navStates.get(e);
2146
2193
  if (t !== undefined) t.held = true;
@@ -2149,8 +2196,8 @@ function closeNavigation(e) {
2149
2196
 
2150
2197
  /** transitionSettled: `t` commits — the navigations whose writes it staged are done. */ function settleNavigations(e, n) {
2151
2198
  if (openNavs.size === 0) return;
2152
- for (const t of e.In) {
2153
- const e = t.ze?.origin;
2199
+ for (const t of e.nn) {
2200
+ const e = t.ve?.origin;
2154
2201
  if (e?.kind !== "navigation") continue;
2155
2202
  const o = navStates.get(e);
2156
2203
  if (o === undefined || !openNavs.has(o)) continue;
@@ -2295,7 +2342,10 @@ function settleNavigation(e, n, t) {
2295
2342
  o.settledMs = now() - o.at;
2296
2343
  o.outcome = n;
2297
2344
  if (t !== undefined) o.hold = t;
2298
- for (const e of folds) e.navigation?.(o);
2345
+ // The initial declaration is not a navigation the person waited on: its
2346
+ // `settledMs` is document start → router built, which would read as the
2347
+ // route's responsiveness in the feedback tables. Consumers get the record.
2348
+ if (o.initial !== true) for (const e of folds) e.navigation?.(o);
2299
2349
  records.emit("navigation", o, undefined);
2300
2350
  trackGraph(o);
2301
2351
  // The interaction that performed it may have been waiting only on this.
@@ -2305,12 +2355,14 @@ function settleNavigation(e, n, t) {
2305
2355
 
2306
2356
  const interactionStates = new WeakMap;
2307
2357
 
2358
+ /** `InteractionRef.event` → the record its frames join (while `joinable`). Weak: the Event is never held. */ const eventInteractions = new WeakMap;
2359
+
2308
2360
  /** Opened, not yet settled. */ const openInteractions = new Set;
2309
2361
 
2310
2362
  let interactionLog = [];
2311
2363
 
2312
- function openInteraction(e, n) {
2313
- const t = {
2364
+ function openInteraction(e, n, t) {
2365
+ const o = {
2314
2366
  name: e.name,
2315
2367
  at: e.at,
2316
2368
  handlerMs: 0,
@@ -2322,26 +2374,37 @@ function openInteraction(e, n) {
2322
2374
  navigations: [],
2323
2375
  origin: e
2324
2376
  };
2325
- if (e.target !== undefined) t.target = e.target;
2377
+ if (e.target !== undefined) o.target = e.target;
2326
2378
  // The runtime may date the frame from the event's own timestamp (before
2327
2379
  // any queued task ran); the gap to here is the input delay the browser's
2328
2380
  // INP counts first.
2329
- if (n > t.at) t.inputDelayMs = n - t.at;
2330
- const o = {
2331
- event: t,
2381
+ if (n > o.at) o.inputDelayMs = n - o.at;
2382
+ const i = {
2383
+ event: o,
2332
2384
  open: true,
2333
2385
  opened: n,
2334
2386
  writeDrain: drainSeq,
2335
2387
  heldIn: new Set,
2336
2388
  held: false,
2337
2389
  excludedWrites: 0,
2338
- awaiting: false,
2339
- actioned: false
2390
+ awaiting: 0,
2391
+ actioned: false,
2392
+ joinable: t !== undefined
2340
2393
  };
2341
- interactionStates.set(e, o);
2342
- openInteractions.add(o);
2343
- interactionLog.push(t);
2394
+ interactionStates.set(e, i);
2395
+ openInteractions.add(i);
2396
+ interactionLog.push(o);
2344
2397
  if (interactionLog.length > options.historyLimit) interactionLog.shift();
2398
+ if (t !== undefined) {
2399
+ eventInteractions.set(t, i);
2400
+ // A microtask is too early: native dispatch runs microtasks between
2401
+ // listeners. The callback must not close over `key`: a throttled timer
2402
+ // would hold the Event strongly long past its dispatch.
2403
+ setTimeout(() => {
2404
+ i.joinable = false;
2405
+ if (openInteractions.has(i)) maybeSettleInteraction(i, i.settleAt);
2406
+ });
2407
+ }
2345
2408
  }
2346
2409
 
2347
2410
  /**
@@ -2362,19 +2425,19 @@ function openInteraction(e, n) {
2362
2425
  // screen could have shown the wait.
2363
2426
  const i = n !== null && (typeof n === "object" || typeof n === "function") && typeof n.then === "function" ? n : null;
2364
2427
  if (i !== null) {
2365
- t.awaiting = true;
2428
+ t.awaiting++;
2366
2429
  let e = false;
2367
2430
  let n;
2368
2431
  const settle = () => {
2369
2432
  if (e) return;
2370
2433
  e = true;
2371
2434
  if (n !== undefined) clearTimeout(n);
2372
- if (!openInteractions.has(t)) return;
2373
- t.awaiting = false;
2374
- const i = now();
2375
- t.event.continuationMs = i - o;
2435
+ if (!openInteractions.has(t) || --t.awaiting > 0) return;
2436
+ const o = now();
2437
+ // From the last handler's return: the frames of one event share a record.
2438
+ t.event.continuationMs = o - (t.opened + t.event.handlerMs);
2376
2439
  checkUntrackedAsyncHandler(t);
2377
- maybeSettleInteraction(t, i);
2440
+ maybeSettleInteraction(t, o);
2378
2441
  };
2379
2442
  n = setTimeout(settle, ASYNC_HANDLER_CAP_MS);
2380
2443
  // A pending cap must not hold a Node process (tests, SSR harnesses) open.
@@ -2452,8 +2515,8 @@ function openInteraction(e, n) {
2452
2515
 
2453
2516
  /** holdStart: `t` parked writes — the interactions that performed them wait for `t`. */ function markInteractionsHeld(e) {
2454
2517
  if (openInteractions.size === 0) return;
2455
- for (const n of e.In) {
2456
- const t = openInteractionOf(n.ze?.origin);
2518
+ for (const n of e.nn) {
2519
+ const t = openInteractionOf(n.ve?.origin);
2457
2520
  if (t !== undefined) {
2458
2521
  t.heldIn.add(e);
2459
2522
  t.held = true;
@@ -2476,12 +2539,16 @@ function openInteraction(e, n) {
2476
2539
 
2477
2540
  function maybeSettleInteraction(e, n = now()) {
2478
2541
  const t = e.event;
2479
- if (e.open || e.awaiting || e.heldIn.size > 0) return;
2542
+ if (e.open || e.awaiting > 0 || e.heldIn.size > 0) return;
2480
2543
  // A drain must have committed the last write (the handler's, or a redirect
2481
2544
  // hop's after the click's own drain) — the handler returning is not the
2482
2545
  // screen having it.
2483
2546
  if (t.writes > 0 && drainSeq <= e.writeDrain) return;
2484
2547
  for (const e of t.navigations) if (e.outcome === undefined) return;
2548
+ if (e.joinable) {
2549
+ e.settleAt = n;
2550
+ return;
2551
+ }
2485
2552
  openInteractions.delete(e);
2486
2553
  // Every write went to an excluded subject and nothing of the app's ran: the
2487
2554
  // click was on the observer's own UI (a devtools panel's button). Not a
@@ -2536,7 +2603,7 @@ const engineHooks = {
2536
2603
  prevDeps: n || !wantsRerun() ? null : captureDeps(e),
2537
2604
  // Mirror recompute's own prev-value resolution: an earlier run in the
2538
2605
  // same flush may still be holding in _pendingValue.
2539
- prevValue: e._e !== NOT_PENDING ? e._e : e.ce,
2606
+ prevValue: e.Ae !== NOT_PENDING ? e.Ae : e.pe,
2540
2607
  // A create run inherits the interaction of whatever is building it: the
2541
2608
  // enclosing recompute (a parent's fn creating children) or, at the top
2542
2609
  // of the recompute stack, the effect callback / handler frame.
@@ -2562,8 +2629,8 @@ const engineHooks = {
2562
2629
  // committed compute output is an unchanged run. `undefined` outputs are
2563
2630
  // exempt — a side-effect-only compute's work IS its effect phase, and
2564
2631
  // identity of `undefined` proves nothing.
2565
- if (t && r.causes !== null && e.He) {
2566
- const n = e._e !== NOT_PENDING ? e._e : e.ce;
2632
+ if (t && r.causes !== null && e.i) {
2633
+ const n = e.Ae !== NOT_PENDING ? e.Ae : e.pe;
2567
2634
  if (n !== undefined && n === r.prevValue) t = false;
2568
2635
  }
2569
2636
  // Unstable-output check: memos only, non-create, plain runs with a
@@ -2571,7 +2638,12 @@ const engineHooks = {
2571
2638
  // plain-flush memo commits and in `_value` for direct ones. Overlay runs
2572
2639
  // are excluded — an optimistic re-derive legitimately produces fresh
2573
2640
  // equivalents while the lane settles.
2574
- if (r.causes !== null && t && !o && !i && !e.He) checkUnstableOutput(e, r.prevValue, e._e !== NOT_PENDING ? e._e : e.ce);
2641
+ if (r.causes !== null && t && !o && !i && !e.i) checkUnstableOutput(e, r.prevValue, e.Ae !== NOT_PENDING ? e.Ae : e.pe);
2642
+ // The same exemption for memos: a run that committed `undefined` did its
2643
+ // work as a side effect (a projection's draft writes or reconcile, a
2644
+ // write from inside a memo), so core's `undefined === undefined` gate
2645
+ // proves nothing. Reported as changed, so no consumer counts it as waste.
2646
+ if (!t && (e.Ae !== NOT_PENDING ? e.Ae : e.pe) === undefined) t = true;
2575
2647
  if (r.causes !== null) recordRerun(e, r, {
2576
2648
  selfMs: c,
2577
2649
  totalMs: a
@@ -2583,7 +2655,7 @@ const engineHooks = {
2583
2655
  noteInteractionRun(r.interaction, c, true);
2584
2656
  // The first effect callback runs with no re-run record to inherit from;
2585
2657
  // hand it the interaction that built the node (see effectRunStart).
2586
- e.pn = r.interaction;
2658
+ e.xe = r.interaction;
2587
2659
  if (openFlush !== null) noteFlushRun(openFlush, true, r.interaction);
2588
2660
  if (records.observed("create")) {
2589
2661
  const n = {
@@ -2613,9 +2685,9 @@ const engineHooks = {
2613
2685
  trackFlightStart(e, n);
2614
2686
  },
2615
2687
  asyncStart(e) {
2616
- asyncStartSeq = e.ze?.seq ?? 0;
2617
- asyncStartTime = e.Ge;
2618
- asyncStartValue = e.ce;
2688
+ asyncStartSeq = e.ve?.seq ?? 0;
2689
+ asyncStartTime = e.Ne;
2690
+ asyncStartValue = e.pe;
2619
2691
  },
2620
2692
  asyncEnd(e, n, t, o) {
2621
2693
  if (o) {
@@ -2625,7 +2697,7 @@ const engineHooks = {
2625
2697
  // (or `_time`, for a same-reference commit under `equals: false`), and
2626
2698
  // a transition hold parks the value in `_pendingValue`. A landing the
2627
2699
  // equality gate swallowed moves none of them and must leave no stamp.
2628
- const o = e.ce !== asyncStartValue || e.Ge !== asyncStartTime || e._e === t;
2700
+ const o = e.pe !== asyncStartValue || e.Ne !== asyncStartTime || e.Ae === t;
2629
2701
  if (o) stampWrite(e, "async", n === undefined ? NO_VALUES : n, t);
2630
2702
  // Flight over either way — an equality-swallowed landing still spent
2631
2703
  // the wall time (finalizeFlight only chains through a fresh stamp).
@@ -2635,12 +2707,12 @@ const engineHooks = {
2635
2707
  // Landed through setSignal: reclassify its "write" stamp as an async
2636
2708
  // landing — but only if it actually stamped (the value changed) since
2637
2709
  // asyncStart; a no-change landing must leave no fresh stamp behind.
2638
- const i = e.ze;
2710
+ const i = e.ve;
2639
2711
  if (i !== undefined && i.seq > asyncStartSeq && i.kind === "write") stampWrite(e, "async", NO_VALUES, t);
2640
2712
  finalizeFlight(e);
2641
2713
  },
2642
2714
  effectRunStart(e) {
2643
- pushFrame("effect", nodeName(e), e.pn, e);
2715
+ pushFrame("effect", nodeName(e), e.xe, e);
2644
2716
  // The frame's own `at` doubles as the record's start: set only while
2645
2717
  // listened, so a listener arriving mid-callback finds no start and the
2646
2718
  // end emits nothing for it — and an unlistened callback pays no clock read.