@solidjs/signals 2.0.0-rc.10 → 2.0.0-rc.13
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.
- package/dist/dev-shared.js +128 -60
- package/dist/dev.attribution.js +27 -12
- package/dist/dev.js +242 -32
- package/dist/observe/core/attribution.js +36 -21
- package/dist/observe/core/constants.js +21 -12
- package/dist/observe/core/core.js +44 -34
- package/dist/observe/core/dev.js +38 -15
- package/dist/observe/core/optimistic.js +65 -23
- package/dist/observe/core/owner.js +11 -11
- package/dist/observe/core/scheduler.js +26 -12
- package/dist/observe/core/verdict.js +2 -2
- package/dist/observe/map.js +241 -218
- package/dist/observe/store/next/store.js +159 -31
- package/dist/prod/core/constants.js +21 -12
- package/dist/prod/core/core.js +27 -17
- package/dist/prod/core/optimistic.js +62 -20
- package/dist/prod/core/owner.js +2 -2
- package/dist/prod/core/scheduler.js +29 -15
- package/dist/prod/core/verdict.js +1 -1
- package/dist/prod/map.js +200 -177
- package/dist/prod/store/next/optimistic.js +2 -2
- package/dist/prod/store/next/store.js +201 -73
- package/dist/types/core/attribution-hooks.d.ts +28 -0
- package/dist/types/core/attribution.d.ts +9 -0
- package/dist/types/core/constants.d.ts +21 -12
- package/dist/types/core/dev.d.ts +59 -6
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/scheduler.d.ts +5 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/signals.d.ts +18 -1
- package/dist/types/store/next/store.d.ts +4 -2
- package/package.json +1 -1
package/dist/dev-shared.js
CHANGED
|
@@ -226,20 +226,29 @@ const CONFIG_OVERRIDE_SUPERSEDED = 1 << 19;
|
|
|
226
226
|
* transaction. A parked node (status propagation stamps `_transition`
|
|
227
227
|
* without recomputing) recomputed when its source lands otherwise disposed
|
|
228
228
|
* its committed children mid-hold, running their cleanups before the
|
|
229
|
-
* transaction's atomic reveal. Cleared by `commitPendingNode`.
|
|
229
|
+
* transaction's atomic reveal. Cleared by `commitPendingNode`. Transaction
|
|
230
|
+
* work only (A15 lane work and transaction work, #3698): zombies are parked
|
|
231
|
+
* by a pass under a held transaction; a pass over a lane parks a LANE frame
|
|
232
|
+
* instead (`CONFIG_LANE_FRAME`), whatever the node's kind. */
|
|
230
233
|
const CONFIG_HELD_CHILDREN = 1 << 20;
|
|
231
234
|
/** The frame parked in `_pendingFirstChild` / `_pendingDisposal` is a LANE
|
|
232
|
-
* frame (#3662, A15
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
235
|
+
* frame (#3662, #3698; A15 lane work and transaction work): a lane pass —
|
|
236
|
+
* on an effect or a memo alike — publishes into the lane's frame (an
|
|
237
|
+
* effect's run; a memo's derived override, A17), so the frame it replaces
|
|
238
|
+
* leaves the screen when the lane applies (A30) — not at the action's
|
|
239
|
+
* commit like #3404's transaction zombies, and not at the pass (a held lane
|
|
240
|
+
* defers the apply with the frame still displayed). Drained by the lane's
|
|
241
|
+
* render entry the parking site pushed ahead of the new frame's effects
|
|
242
|
+
* (cleanups before side effects), by `commitPendingNode` if a hold commits
|
|
243
|
+
* the node first, or with the owner's death. While set the parked frame is
|
|
244
|
+
* not a hold (the node is not queued or stamped for it — lane work never
|
|
245
|
+
* makes its node transaction work), a superseding pass disposes the
|
|
246
|
+
* never-shown live children on the spot, and a lane-channel dirty on a
|
|
247
|
+
* member is cancelled (`laneZombie`). Ruled 2026-09-28 (#3698): a pass is
|
|
248
|
+
* lane work or transaction work by its owner, never by node kind. #3662
|
|
249
|
+
* flagged effects only, and a memo's lane pass parked a transaction zombie
|
|
250
|
+
* that queued and stamped the memo as the action's pending node, so its
|
|
251
|
+
* next mainline recompute re-entered the hold. */
|
|
243
252
|
const CONFIG_LANE_FRAME = 1 << 26;
|
|
244
253
|
/** In-flight async node whose inputs were PUBLISHED while it was pending: a
|
|
245
254
|
* batch or transaction committed with the node still `STATUS_PENDING` (an
|
|
@@ -461,22 +470,33 @@ function recordsChannel() {
|
|
|
461
470
|
const g = globalThis;
|
|
462
471
|
if (g[RECORDS]) return g[RECORDS];
|
|
463
472
|
const listeners = new Map();
|
|
473
|
+
const bodyListeners = new Map();
|
|
464
474
|
return (g[RECORDS] = {
|
|
465
|
-
subscribe(type, listener) {
|
|
475
|
+
subscribe(type, listener, options) {
|
|
466
476
|
const current = listeners.get(type);
|
|
467
|
-
// Set semantics: one entry per function, however often it is passed
|
|
468
|
-
|
|
469
|
-
|
|
477
|
+
// Set semantics: one entry per function, however often it is passed —
|
|
478
|
+
// the first subscription's options stand for it.
|
|
479
|
+
if (current === undefined || !current.includes(listener)) {
|
|
480
|
+
listeners.set(type, current === undefined ? [listener] : [...current, listener]);
|
|
481
|
+
if (options !== undefined && options.bodies) {
|
|
482
|
+
let wanting = bodyListeners.get(type);
|
|
483
|
+
if (wanting === undefined) bodyListeners.set(type, (wanting = new Set()));
|
|
484
|
+
wanting.add(listener);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
470
487
|
return () => {
|
|
471
488
|
const list = listeners.get(type);
|
|
472
|
-
if (list === undefined) return;
|
|
489
|
+
if (list === undefined || !list.includes(listener)) return;
|
|
473
490
|
const next = list.filter(l => l !== listener);
|
|
474
491
|
if (next.length > 0) listeners.set(type, next);
|
|
475
492
|
else listeners.delete(type);
|
|
493
|
+
const wanting = bodyListeners.get(type);
|
|
494
|
+
if (wanting !== undefined && wanting.delete(listener) && wanting.size === 0)
|
|
495
|
+
bodyListeners.delete(type);
|
|
476
496
|
};
|
|
477
497
|
},
|
|
478
|
-
observed(type) {
|
|
479
|
-
return listeners.has(type);
|
|
498
|
+
observed(type, facet) {
|
|
499
|
+
return facet === "bodies" ? bodyListeners.has(type) : listeners.has(type);
|
|
480
500
|
},
|
|
481
501
|
emit(type, event, live) {
|
|
482
502
|
const list = listeners.get(type);
|
|
@@ -502,9 +522,14 @@ const OBSERVE = {
|
|
|
502
522
|
// empty HERE and gains its members by augmentation downstream.
|
|
503
523
|
server: {},
|
|
504
524
|
exclude(owner) {
|
|
505
|
-
|
|
525
|
+
markedOwners.set(owner, true);
|
|
506
526
|
hasExclusions = true;
|
|
507
527
|
},
|
|
528
|
+
include(owner) {
|
|
529
|
+
// No flag flip: with nothing excluded there is nothing to re-admit,
|
|
530
|
+
// and the walk stays short-circuited.
|
|
531
|
+
markedOwners.set(owner, false);
|
|
532
|
+
},
|
|
508
533
|
isExcluded,
|
|
509
534
|
ownerPath
|
|
510
535
|
};
|
|
@@ -512,16 +537,23 @@ const OBSERVE = {
|
|
|
512
537
|
//
|
|
513
538
|
// An observer that lives inside the observed app (an adapter's panel,
|
|
514
539
|
// devtools) marks its root; both channels check the subject's owner chain —
|
|
515
|
-
// the same walk `ownerPath` already makes — and stay silent under it.
|
|
516
|
-
//
|
|
517
|
-
|
|
540
|
+
// the same walk `ownerPath` already makes — and stay silent under it. An
|
|
541
|
+
// observer that WRAPS the app (a toolbar around it) excludes its own root
|
|
542
|
+
// and includes the app's back: one map, `true` excluded / `false` included,
|
|
543
|
+
// and the nearest marker up the chain decides. The flag short-circuits the
|
|
544
|
+
// walk for the common case of no exclusions (an include with nothing
|
|
545
|
+
// excluded changes no verdict, so it does not flip it).
|
|
546
|
+
const markedOwners = new WeakMap();
|
|
518
547
|
let hasExclusions = false;
|
|
519
548
|
/** Events built for an excluded subject: never delivered, never reported. */
|
|
520
549
|
const suppressedEvents = new WeakSet();
|
|
521
550
|
function isExcluded(subject) {
|
|
522
551
|
if (!hasExclusions || !subject) return false;
|
|
523
552
|
let owner = "_parent" in subject ? subject : (subject._owner ?? null);
|
|
524
|
-
for (; owner !== null; owner = owner._parent)
|
|
553
|
+
for (; owner !== null; owner = owner._parent) {
|
|
554
|
+
const marked = markedOwners.get(owner);
|
|
555
|
+
if (marked !== undefined) return marked;
|
|
556
|
+
}
|
|
525
557
|
return false;
|
|
526
558
|
}
|
|
527
559
|
/** For engines that cache the verdict per node: is anything excluded at all? */
|
|
@@ -688,8 +720,14 @@ function throwPendingUntrackedRead(strictReadLabel, fields) {
|
|
|
688
720
|
throw new Error(message);
|
|
689
721
|
}
|
|
690
722
|
function warnStrictReadUntracked(strictReadLabel, fields) {
|
|
723
|
+
// Name the value when the name says something — a `name` option or a store
|
|
724
|
+
// key (#3675). The constructors' defaults ("signal", "computed") would only
|
|
725
|
+
// restate what "reactive value" already says.
|
|
726
|
+
const nodeName = fields?.nodeName;
|
|
727
|
+
const named = nodeName !== undefined && nodeName !== "signal" && nodeName !== "computed";
|
|
691
728
|
const message =
|
|
692
|
-
`[STRICT_READ_UNTRACKED] Reactive value
|
|
729
|
+
`[STRICT_READ_UNTRACKED] Reactive value${named ? ` "${nodeName}"` : ""} read directly in ` +
|
|
730
|
+
`${strictReadLabel} will not update. ` +
|
|
693
731
|
`Move it into a tracking scope (JSX, a memo, or an effect's compute function).`;
|
|
694
732
|
reportDiagnostic(
|
|
695
733
|
emitDiagnostic({
|
|
@@ -1696,7 +1734,7 @@ function haltReactivity(cause) {
|
|
|
1696
1734
|
let message = "[REACTIVITY_HALTED]";
|
|
1697
1735
|
{
|
|
1698
1736
|
message +=
|
|
1699
|
-
" An uncaught error halted the reactive system. No further updates will be processed. Handle errors with
|
|
1737
|
+
" An uncaught error halted the reactive system. No further updates will be processed. Handle errors with <Errored> or treat this as a crash.";
|
|
1700
1738
|
emitDiagnostic({
|
|
1701
1739
|
code: "REACTIVITY_HALTED",
|
|
1702
1740
|
kind: "error",
|
|
@@ -1932,7 +1970,11 @@ class GlobalQueue extends Queue {
|
|
|
1932
1970
|
* commit whatever its relation to the committed value — a landing equal to
|
|
1933
1971
|
* committed still differs from the override — then _supersedeOverride
|
|
1934
1972
|
* decides. Installed with the optimistic engine; only reachable on a node
|
|
1935
|
-
* that has an override
|
|
1973
|
+
* that has an override — or, from `mapArray` once a lane pass has run over
|
|
1974
|
+
* the map, on a per-slot signal (never CONFIG_OPTIMISTIC): the slot arm
|
|
1975
|
+
* publishes a lane pass's write as the slot's derived override, lands a
|
|
1976
|
+
* plain pass's write over one, and is the plain `setSignal` otherwise (F1,
|
|
1977
|
+
* see optimistic.ts landOnOverride). */
|
|
1936
1978
|
static _landOnOverride = null;
|
|
1937
1979
|
static _trackOptimisticStore = null;
|
|
1938
1980
|
flush() {
|
|
@@ -2073,8 +2115,16 @@ class GlobalQueue extends Queue {
|
|
|
2073
2115
|
// sweep or recompute wrote a node it owns): effects computed under it
|
|
2074
2116
|
// since are its to apply, not this flush's — runEffect leaves them queued
|
|
2075
2117
|
// and the next pass parks them with it (#3319). Everything computed
|
|
2076
|
-
// mainline applies now.
|
|
2077
|
-
|
|
2118
|
+
// mainline applies now. A write the finalize staged in the ambient
|
|
2119
|
+
// batch with no subscriber to dirty (an optimistic store settle's
|
|
2120
|
+
// keyset bump under a reader that never tracks the key set) is work
|
|
2121
|
+
// too — the fast drain and the park exit already count it — so the
|
|
2122
|
+
// next round commits it and the woken re-entry below does not adopt
|
|
2123
|
+
// it into a parked transaction it never belonged to (matrix F6).
|
|
2124
|
+
scheduled =
|
|
2125
|
+
dirtyQueue._max >= dirtyQueue._min ||
|
|
2126
|
+
activeTransition !== null ||
|
|
2127
|
+
this._batch._pendingNodes.length !== 0;
|
|
2078
2128
|
// Run lane effects first (for ready lanes), then regular effects
|
|
2079
2129
|
activeLanes.size && GlobalQueue._runLaneEffects(EFFECT_RENDER);
|
|
2080
2130
|
this.run(EFFECT_RENDER);
|
|
@@ -2105,9 +2155,14 @@ class GlobalQueue extends Queue {
|
|
|
2105
2155
|
// idle pass: entering adopts the ambient batch, and staged or dirty
|
|
2106
2156
|
// ambient work would be held behind flights it never read. `scheduled`
|
|
2107
2157
|
// is that test here — after the park exit as well as the normal one:
|
|
2108
|
-
// it was recomputed from the heap
|
|
2109
|
-
//
|
|
2110
|
-
//
|
|
2158
|
+
// it was recomputed from the heap and the ambient batch's staged nodes
|
|
2159
|
+
// this pass, every write since re-armed it, and optimistic ambient
|
|
2160
|
+
// nodes reverted with the finalize — so a wake in a pass with work
|
|
2161
|
+
// simply falls to the next. (A staged node with no subscriber — the
|
|
2162
|
+
// finalize's keyset bump under a length-only reader — used to be
|
|
2163
|
+
// missed here: the wake adopted it, stamped it, and a later ambient
|
|
2164
|
+
// write to the same node joined the parked transaction and never
|
|
2165
|
+
// reverted; matrix F6.) Entering re-arms
|
|
2111
2166
|
// it itself; a dead (completed) wake is a bare return in
|
|
2112
2167
|
// initTransition, and the loop moves on to the next.
|
|
2113
2168
|
while (!scheduled && !activeTransition && wokenTransitions.length)
|
|
@@ -4688,8 +4743,8 @@ function recompute(el, create = false) {
|
|
|
4688
4743
|
let devChanged = false;
|
|
4689
4744
|
if (attrHooks !== null) attrHooks.recomputeStart(el, create);
|
|
4690
4745
|
// Lane posture is resolved BEFORE the previous frame is parked below: a
|
|
4691
|
-
// lane pass
|
|
4692
|
-
// the decision must be known there. `lane` is applied to
|
|
4746
|
+
// lane pass parks a lane frame, not a transaction zombie (#3662, #3698;
|
|
4747
|
+
// see the parking site), so the decision must be known there. `lane` is applied to
|
|
4693
4748
|
// `currentOptimisticLane` further down, once the previous posture is saved.
|
|
4694
4749
|
let isOptimisticDirty = !!(el._flags & REACTIVE_OPTIMISTIC_DIRTY);
|
|
4695
4750
|
let lane = null;
|
|
@@ -4738,20 +4793,30 @@ function recompute(el, create = false) {
|
|
|
4738
4793
|
// Tracked effects run after finalizePureQueue, so dispose immediately
|
|
4739
4794
|
// instead of deferring. Children built by an uncommitted recompute
|
|
4740
4795
|
// (CONFIG_HELD_CHILDREN) die immediately too: no frame ever showed them.
|
|
4741
|
-
// Everything else is the committed frame's
|
|
4742
|
-
//
|
|
4743
|
-
//
|
|
4744
|
-
//
|
|
4745
|
-
//
|
|
4746
|
-
//
|
|
4747
|
-
//
|
|
4748
|
-
//
|
|
4749
|
-
//
|
|
4750
|
-
// the
|
|
4751
|
-
//
|
|
4752
|
-
//
|
|
4753
|
-
//
|
|
4754
|
-
//
|
|
4796
|
+
// Everything else is the committed frame's. Which frame retires it is
|
|
4797
|
+
// decided by who owns the pass, never by the node's kind (A15 lane work
|
|
4798
|
+
// and transaction work, ruled 2026-09-28, #3698). Transaction work — a
|
|
4799
|
+
// pass under a held transaction — defers them as zombies until this
|
|
4800
|
+
// node's commit, a transaction-owned node included (#3404): a parked
|
|
4801
|
+
// node's children predate the hold, and tearing them down when the
|
|
4802
|
+
// source lands ran cleanups before the transaction's atomic reveal.
|
|
4803
|
+
// Lane work — a pass over a lane, effect or memo alike — parks a LANE
|
|
4804
|
+
// frame instead (CONFIG_LANE_FRAME, #3662, #3698): the frame it replaces
|
|
4805
|
+
// leaves the screen when the lane's queue applies this pass (A30) — not
|
|
4806
|
+
// at the action's commit — and a held lane defers that with the frame
|
|
4807
|
+
// still displayed. The drain is the lane's first render entry for this
|
|
4808
|
+
// pass, pushed before the pass builds the new frame: cleanups before
|
|
4809
|
+
// side effects — the retired frame's `onCleanup`s run ahead of every
|
|
4810
|
+
// effect callback of the new frame. While the frame waits, the live
|
|
4811
|
+
// children were never shown: a superseding pass disposes them here like
|
|
4812
|
+
// held children, and the parked frame stays. Zombies exist for
|
|
4813
|
+
// transaction work only: #3662 gated the lane frame on `isEffect`, and a
|
|
4814
|
+
// memo's lane pass (its value a derived override, A17) parked a
|
|
4815
|
+
// transaction zombie that made the memo a pending node of the action,
|
|
4816
|
+
// stamped it, and sent its next mainline recompute through the
|
|
4817
|
+
// stamped-memo arm above — a `Show` whose `when` getter owns a
|
|
4818
|
+
// compiler-emitted memo held an unrelated sync write for the action's
|
|
4819
|
+
// lifetime, against the #3460 ruling that a lane never holds a sync write.
|
|
4755
4820
|
if (isEffect === EFFECT_TRACKED || el._config & (CONFIG_HELD_CHILDREN | CONFIG_LANE_FRAME))
|
|
4756
4821
|
disposeChildren(el);
|
|
4757
4822
|
else if (el._firstChild !== null || el._disposal !== null) {
|
|
@@ -4762,7 +4827,7 @@ function recompute(el, create = false) {
|
|
|
4762
4827
|
el._disposal = null;
|
|
4763
4828
|
el._firstChild = null;
|
|
4764
4829
|
el._childCount = 0;
|
|
4765
|
-
if (
|
|
4830
|
+
if (lane) {
|
|
4766
4831
|
el._config |= CONFIG_LANE_FRAME;
|
|
4767
4832
|
findLane(lane)._effectQueues[0].push(() => {
|
|
4768
4833
|
el._config &= ~CONFIG_LANE_FRAME;
|
|
@@ -6441,6 +6506,15 @@ function read(el) {
|
|
|
6441
6506
|
return read(el);
|
|
6442
6507
|
} else throw owner._x?._error;
|
|
6443
6508
|
}
|
|
6509
|
+
// Ahead of the snapshot serve below: a component body's direct read is
|
|
6510
|
+
// wrong in the same way whether the pass is hydrating or not, and the
|
|
6511
|
+
// hydration pass is the console nobody is watching (#3675).
|
|
6512
|
+
if (strictRead)
|
|
6513
|
+
warnStrictReadUntracked(strictRead, {
|
|
6514
|
+
ownerId: c?.id,
|
|
6515
|
+
ownerName: c?._name,
|
|
6516
|
+
nodeName: owner?._name
|
|
6517
|
+
});
|
|
6444
6518
|
if (snapshotCaptureActive && c && c._config & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
6445
6519
|
const sv = el._x?._snapshotValue;
|
|
6446
6520
|
if (sv !== undefined) {
|
|
@@ -6450,12 +6524,6 @@ function read(el) {
|
|
|
6450
6524
|
return snapshot;
|
|
6451
6525
|
}
|
|
6452
6526
|
}
|
|
6453
|
-
if (strictRead)
|
|
6454
|
-
warnStrictReadUntracked(strictRead, {
|
|
6455
|
-
ownerId: c?.id,
|
|
6456
|
-
ownerName: c?._name,
|
|
6457
|
-
nodeName: owner?._name
|
|
6458
|
-
});
|
|
6459
6527
|
const value = serve(el, c, owner, el._value);
|
|
6460
6528
|
if (
|
|
6461
6529
|
!c &&
|
|
@@ -6967,13 +7035,13 @@ export {
|
|
|
6967
7035
|
currentTransition as M,
|
|
6968
7036
|
NoOwnerError as N,
|
|
6969
7037
|
OVERRIDE_UNDEFINED as O,
|
|
6970
|
-
|
|
6971
|
-
|
|
7038
|
+
assignOrMergeLane as P,
|
|
7039
|
+
queuePendingNode as Q,
|
|
6972
7040
|
REACTIVE_DIRTY as R,
|
|
6973
7041
|
STATUS_UNINITIALIZED as S,
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
7042
|
+
latestReadActive as T,
|
|
7043
|
+
REACTIVE_MANUAL_WRITE as U,
|
|
7044
|
+
REACTIVE_OPTIMISTIC_DIRTY as V,
|
|
6977
7045
|
LANE_RUN as W,
|
|
6978
7046
|
attrHooks as X,
|
|
6979
7047
|
EFFECT_USER as Y,
|
package/dist/dev.attribution.js
CHANGED
|
@@ -19,12 +19,13 @@ import {
|
|
|
19
19
|
/** A fallback shown for less than this is a flash: feedback for a wait too short to need it. */
|
|
20
20
|
const FALLBACK_FLASH_MS = 150;
|
|
21
21
|
/**
|
|
22
|
-
* Under an owner the observer marked as its own (`OBSERVE.exclude
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* exists, before that a flag
|
|
22
|
+
* Under an owner the observer marked as its own (`OBSERVE.exclude`, and not
|
|
23
|
+
* re-admitted by a nearer `OBSERVE.include`), or framework plumbing itself
|
|
24
|
+
* (`CONFIG_PLUMBING` — the HMR memo between a component's root and its
|
|
25
|
+
* body, which is nobody's node): the engine records nothing about the node.
|
|
26
|
+
* Plumbing is a bit read and excludes the node alone, not what it owns; the
|
|
27
|
+
* observer exclusion is cached per node once any exists, before that a flag
|
|
28
|
+
* read.
|
|
28
29
|
*/
|
|
29
30
|
function excludedNode(el) {
|
|
30
31
|
if ((el._config & CONFIG_PLUMBING) !== 0) return true;
|
|
@@ -333,9 +334,16 @@ function originStart(ref) {
|
|
|
333
334
|
return;
|
|
334
335
|
}
|
|
335
336
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
337
|
+
// The initial navigation is the document's: its request is the time origin
|
|
338
|
+
// unless the router says otherwise, and it left nowhere.
|
|
339
|
+
const initial = ref.initial === true;
|
|
340
|
+
const frame = { kind: ref.kind, at: ref.at ?? (initial ? 0 : now()) };
|
|
341
|
+
if (!initial && ref.from !== undefined) frame.from = ref.from;
|
|
342
|
+
// Declared beats ambient: a router that awaited before writing hands back
|
|
343
|
+
// the origin it captured in the request (`undefined` when the request ran
|
|
344
|
+
// under none — still a declaration); what is on the stack now is whatever
|
|
345
|
+
// happened to be running.
|
|
346
|
+
const under = "interaction" in ref ? interactionOf(ref.interaction) : enclosingInteraction();
|
|
339
347
|
if (under !== undefined) frame.interaction = under;
|
|
340
348
|
originFrames.push(frame);
|
|
341
349
|
openNavigation(frame, ref);
|
|
@@ -369,7 +377,8 @@ function formatOrigin(origin) {
|
|
|
369
377
|
notes.push(
|
|
370
378
|
`redirected from ${redirects.map(hop => hop.to ?? hop.name ?? "?").join(" → ")}`
|
|
371
379
|
);
|
|
372
|
-
|
|
380
|
+
const initial = navStates.get(origin)?.event.initial === true;
|
|
381
|
+
return `${initial ? "initial " : ""}navigation to ${name}${notes.length > 0 ? ` (${notes.join(", ")})` : ""}`;
|
|
373
382
|
}
|
|
374
383
|
default:
|
|
375
384
|
return "outside the reactive system";
|
|
@@ -1844,8 +1853,10 @@ function checkOptimisticRevert(el, shown, truth, how) {
|
|
|
1844
1853
|
// derived override promotes rather than reverts. Same predicate the hold
|
|
1845
1854
|
// census uses to skip them.
|
|
1846
1855
|
if (!options.optimisticReverts || isCompanion(el)) return;
|
|
1856
|
+
// Method call, as every commit path does: store slot nodes share one
|
|
1857
|
+
// comparator that reads `this._host` (#3687).
|
|
1847
1858
|
const equals = el._equals;
|
|
1848
|
-
if (equals && equals(shown, truth)) return;
|
|
1859
|
+
if (equals && equals.call(el, shown, truth)) return;
|
|
1849
1860
|
const source = nodeName(el);
|
|
1850
1861
|
// The two values are user data: quoted only under `values: "full"`; the
|
|
1851
1862
|
// other levels say what happened without saying what was shown.
|
|
@@ -2142,6 +2153,7 @@ function syncNavigation(state) {
|
|
|
2142
2153
|
}
|
|
2143
2154
|
function openNavigation(frame, ref) {
|
|
2144
2155
|
const event = { at: frame.at, writes: 0, origin: frame };
|
|
2156
|
+
if (ref.initial === true) event.initial = true;
|
|
2145
2157
|
if (frame.from !== undefined) event.from = frame.from;
|
|
2146
2158
|
if (frame.interaction !== undefined) event.interaction = frame.interaction;
|
|
2147
2159
|
const state = {
|
|
@@ -2354,7 +2366,10 @@ function settleNavigation(state, outcome, hold) {
|
|
|
2354
2366
|
event.settledMs = now() - event.at;
|
|
2355
2367
|
event.outcome = outcome;
|
|
2356
2368
|
if (hold !== undefined) event.hold = hold;
|
|
2357
|
-
|
|
2369
|
+
// The initial declaration is not a navigation the person waited on: its
|
|
2370
|
+
// `settledMs` is document start → router built, which would read as the
|
|
2371
|
+
// route's responsiveness in the feedback tables. Consumers get the record.
|
|
2372
|
+
if (event.initial !== true) for (const f of folds) f.navigation?.(event);
|
|
2358
2373
|
records.emit("navigation", event, undefined);
|
|
2359
2374
|
trackGraph(event);
|
|
2360
2375
|
// The interaction that performed it may have been waiting only on this.
|