@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
@@ -12,6 +12,8 @@ import "../../core/effect.js";
12
12
 
13
13
  import { CONFIG_AUTO_DISPOSE } from "../../core/constants.js";
14
14
 
15
+ import "../../core/dev.js";
16
+
15
17
  import { $TARGET, STORE_VALUE, markRawIngest, setWriteOverride } from "../store.js";
16
18
 
17
19
  import { reconcileNextState } from "./reconcile.js";
@@ -55,22 +57,23 @@ import { storeSetterNext, wrapNext } from "./store.js";
55
57
  * Save/restore projectionWriteActive, never hard-reset: the draft can be
56
58
  * driven from inside an enclosing authoritative-write scope (next-store
57
59
  * optimistic derives), and a hard `false` would clobber it mid-derive.
58
- */ function wrapDraft(e, t, r) {
60
+ */ function wrapDraft(e, t, r, i) {
59
61
  const write = e => r ? r(e) : e();
60
- const i = {
61
- get(i, o) {
62
- let n;
63
- const c = projectionWriteActive;
62
+ const o = {
63
+ get(o, n) {
64
+ let c;
65
+ const s = projectionWriteActive;
64
66
  setWriteOverride(true);
65
67
  setProjectionWriteActive(true);
66
68
  try {
67
- n = e[o];
69
+ c = e[n];
68
70
  } finally {
69
71
  setWriteOverride(false);
70
- setProjectionWriteActive(c);
72
+ setProjectionWriteActive(s);
71
73
  }
72
- if (o === $TARGET) return n;
73
- return typeof n === "object" && n !== null ? wrapDraft(n, t, r) : n;
74
+ // A shallow store's leaves are raw by contract (#3498): no draft proxy
75
+ // over them, so identity holds and a frozen leaf is never trapped.
76
+ return !i && typeof c === "object" && c !== null && n !== $TARGET ? wrapDraft(c, t, r) : c;
74
77
  },
75
78
  has(t, r) {
76
79
  let i;
@@ -160,7 +163,7 @@ import { storeSetterNext, wrapNext } from "./store.js";
160
163
  }
161
164
  };
162
165
  // Matching-kind dummy so Array.isArray(draft) answers like the store.
163
- return new Proxy(Array.isArray(e) ? [] : {}, i);
166
+ return new Proxy(Array.isArray(e) ? [] : {}, o);
164
167
  }
165
168
 
166
169
  function createProjectionNextInternal(e, t, r) {
@@ -188,7 +191,7 @@ function createProjectionNextInternal(e, t, r) {
188
191
  if (!i.node) i.node = getOwner();
189
192
  runProjectionComputedNext(o, e, r?.key === undefined ? "id" : r.key);
190
193
  }, n);
191
- c.T &= ~CONFIG_AUTO_DISPOSE;
194
+ c.C &= ~CONFIG_AUTO_DISPOSE;
192
195
  i.node = c;
193
196
  return {
194
197
  store: o,
@@ -211,30 +214,40 @@ function createProjectionNext(e, t, r) {
211
214
  } ];
212
215
  }
213
216
 
217
+ // A detached copy of projection state: the root container alone for a shallow
218
+ // store — its leaves are raw references by contract and stay so (#3498) — the
219
+ // whole tree otherwise.
220
+ function cloneState(e, t) {
221
+ return t ? Array.isArray(e) ? e.slice() : {
222
+ ...e
223
+ } : JSON.parse(JSON.stringify(e));
224
+ }
225
+
214
226
  function runProjectionComputedNext(e, t, r, i, o) {
215
227
  const n = getOwner();
216
- let c = false;
217
- let s;
228
+ const c = e[$TARGET];
229
+ let s = false;
230
+ let u;
218
231
  // Open loading window (seedLoadingValue): the observable store IS commit #0
219
232
  // for the whole first flight — the derive works a detached shadow of the
220
233
  // seed so draft writes cannot tear through to readers (#2988). Every commit
221
234
  // point reconciles the shadow through the normal commit path.
222
- const u = n.de ? JSON.parse(JSON.stringify(e[$TARGET][STORE_VALUE])) : null;
223
- const l = wrapDraft(e, () => !c || n.o?.Se === s, o);
224
- storeSetterNext(l, o => {
225
- s = t(u ?? o);
226
- c = true;
235
+ const l = n.Se ? cloneState(c[STORE_VALUE], c.s) : null;
236
+ const a = wrapDraft(e, () => !s || n.o?.ce === u, o, c.s);
237
+ storeSetterNext(a, o => {
238
+ u = t(l ?? o);
239
+ s = true;
227
240
  const commit = t => {
228
241
  // Shadow run: commit a detached snapshot, never the shadow itself
229
242
  // (adoption takes the value by identity — handing it the live shadow
230
243
  // would fuse the draft to the observable store).
231
- if (u && (t === undefined || t === u)) t = JSON.parse(JSON.stringify(u));
244
+ if (l && (t === undefined || t === l)) t = cloneState(l, c.s);
232
245
  if (t === o || t === undefined) return;
233
246
  const write = () => storeSetterNext(e, e => reconcileNextState(t, e, r, true), false);
234
247
  i ? i(write, t) : write();
235
248
  };
236
- const l = handleAsync(n, s, commit);
237
- if (!n.de) commit(l);
249
+ const a = handleAsync(n, u, commit);
250
+ if (!n.Se) commit(a);
238
251
  }, false);
239
252
  return n;
240
253
  }
@@ -1,5 +1,7 @@
1
1
  import { isEqual } from "../../core/core.js";
2
2
 
3
+ import "../../core/dev.js";
4
+
3
5
  import { projectionWriteActive } from "../../core/scheduler.js";
4
6
 
5
7
  import "../../core/invariants.js";
@@ -35,46 +37,46 @@ import { isOwned, $OWNER, storeNextLookup, optHooks, lookupTarget } from "./targ
35
37
  * members (R11). Kind changes replace wholesale (R10).
36
38
  */ function reconcileNextState(e, t, n, o = false) {
37
39
  if (t == null) throw new Error("");
38
- const f = t?.[$TARGET];
39
- if (f === undefined || f.px !== t) throw new Error("");
40
+ const i = t?.[$TARGET];
41
+ if (i === undefined || i.px !== t) throw new Error("");
40
42
  // Reconcile's diff walks need a REAL pending container — a prototype
41
43
  // overlay (#3044) materializes to the clone path first (edge: reconcile
42
44
  // inside a setter that already wrote this target).
43
- if (f.ovl) materializePB(f);
44
- let i = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
45
+ if (i.ovl) materializePB(i);
46
+ let f = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
45
47
  // §7b chained backing: a projection derive returning a LIVE store proxy
46
48
  // adopts the proxy itself as the backing — reads flow through the inner
47
49
  // store's traps, so consumers subscribe to the inner graph and updates
48
50
  // flow with no re-derive (#2941). The adoption diff still notifies THIS
49
51
  // store's existing subscribers of the swap.
50
52
  if (o && e !== t && e?.[$TARGET] !== undefined) {
51
- const t = f.pb ?? f.v;
53
+ const t = i.pb ?? i.v;
52
54
  if (t === e) return;
53
55
  // already chained to this store
54
- adoptPB(f, e);
56
+ adoptPB(i, e);
55
57
  return;
56
58
  }
57
59
  const l = unwrapValue(e);
58
- if (i) {
60
+ if (f) {
59
61
  // Root identity precondition — checked before ANY mutation, so a throwing
60
62
  // reconcile is atomic by construction (RUL-12 ruling). Projections
61
63
  // (replace=true) relax it: a root entity change merges in place — the
62
64
  // root proxy is stable for life (proj R5/R11) — and children are NOT
63
65
  // key-matched across the entity change (proj R7: keyFn drops to
64
66
  // positional so old-entity subtrees never merge into the new entity's).
65
- const e = f.pb ?? f.v;
66
- const t = i(e);
67
- if (t !== undefined && !sameKey(i(l), t)) {
67
+ const e = i.pb ?? i.v;
68
+ const t = f(e);
69
+ if (t !== undefined && !sameKey(f(l), t)) {
68
70
  if (!o) throw new Error("");
69
71
  // Entity change: wholesale swap. The root proxy is stable for life
70
72
  // (proj R5) but NOTHING below survives — children are never matched
71
73
  // across an entity change even when their own keys align (proj R7).
72
74
  // Displaced-raw unregistration (proj R10): the outgoing raw stops
73
75
  // resolving to this proxy; re-handed later it wraps fresh.
74
- const e = f.pb ?? f.v;
76
+ const e = i.pb ?? i.v;
75
77
  if (isOwned(e)) delete e[$OWNER]; // disown: wraps fresh if re-handed
76
- else (f.fam?.map ?? storeNextLookup).delete(e);
77
- adoptPB(f, l);
78
+ else (i.fam?.map ?? storeNextLookup).delete(e);
79
+ adoptPB(i, l);
78
80
  return;
79
81
  }
80
82
  }
@@ -83,35 +85,35 @@ import { isOwned, $OWNER, storeNextLookup, optHooks, lookupTarget } from "./targ
83
85
  // armed nodes (reverting with their transaction); committed raw is never
84
86
  // touched. Key-matched rows keep proxy identity by descending into the
85
87
  // existing child targets instead of overriding their parent slots.
86
- if (f.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
87
- optHooks.applyTentative(f, l, i);
88
+ if (i.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
89
+ optHooks.applyTentative(i, l, f);
88
90
  return;
89
91
  }
90
- applyAdopt(f, l, i, o);
92
+ applyAdopt(i, l, f, o);
91
93
  }
92
94
 
93
95
  function applyAdopt(e, t, n, o = false) {
94
- const f = e.pb ?? e.v;
96
+ const i = e.pb ?? e.v;
95
97
  // The sound identity skip (O7): same reference AND we never diverged it.
96
- if (t === f && !isOwned(f)) return;
97
- const i = e.fam;
98
+ if (t === i && !isOwned(i)) return;
99
+ const f = e.fam;
98
100
  // §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
99
101
  // optimistic rows must be visible to key matching so a landing carrying the
100
102
  // same key recycles their proxies. Raw `prev` keeps the identity/ownership
101
103
  // roles above; only matching reads the view.
102
- const l = i?.opt === true ? optHooks.optimisticView(e, f) : f;
104
+ const l = f?.opt === true ? optHooks.optimisticView(e, i) : i;
103
105
  const r = Array.isArray(t);
104
106
  // Plain stores notify inline AFTER the descent (child registrations feed
105
107
  // the fold diff's identity-preservation check); projections keep deferred
106
108
  // folds (downstream holds can form later in the flush).
107
- const s = i === null;
109
+ const s = f === null;
108
110
  const u = e.s === true;
109
111
  // Node-notify base is the view the nodes were last told (#3296): a draft
110
112
  // preceding this reconcile already moved them to its pending backing at
111
113
  // setter exit (prev, materialized above), so diffing incoming against the
112
114
  // committed backing would skip a key the draft changed and the reconcile
113
115
  // restores — the node would commit the superseded draft value.
114
- const a = f;
116
+ const a = i;
115
117
  adoptPB(e, t, s);
116
118
  // Shallow adoption: records are slot values — sticky raw-mark the incoming
117
119
  // set (R41) and never descend; slot notification is the positional diff.
@@ -121,7 +123,7 @@ function applyAdopt(e, t, n, o = false) {
121
123
  return;
122
124
  }
123
125
  if (r) {
124
- const f = l;
126
+ const i = l;
125
127
  const r = t;
126
128
  // Fused array walk (eager mode): per-index notification rides the same
127
129
  // loop as the descent (descend first — targetsEqual needs the child's
@@ -134,13 +136,13 @@ function applyAdopt(e, t, n, o = false) {
134
136
  // rows key-match in place — the steady-state polling shape — descend
135
137
  // directly with zero staging. The prevByKey map is built only for the
136
138
  // misaligned remainder, and never at all on aligned ticks.
137
- const l = f.length;
139
+ const l = i.length;
138
140
  const s = r.length;
139
141
  let u = false;
140
142
  let p = 0;
141
143
  for (const y = Math.min(l, s); p < y; p++) {
142
144
  const l = r[p];
143
- const s = f[p];
145
+ const s = i[p];
144
146
  // Routing heuristic only (aligned vs keyed remainder) — both routes
145
147
  // notify identically and descend() is the one authoritative
146
148
  // validator, so bare typeof gates suffice here; full isWrappable
@@ -148,7 +150,7 @@ function applyAdopt(e, t, n, o = false) {
148
150
  if (s !== l && !(s !== null && typeof s === "object" && l !== null && typeof l === "object" && sameKey(n(s), n(l)))) break;
149
151
  // misaligned: fall to the keyed remainder below
150
152
  // Identity skip inline (FINDING-1 guard), then descend the pair.
151
- if ((s !== l || l !== null && typeof l === "object" && isOwned(l)) && l !== null && typeof l === "object") descend(unwrapValue(s), l, n, i, o);
153
+ if ((s !== l || l !== null && typeof l === "object" && isOwned(l)) && l !== null && typeof l === "object") descend(unwrapValue(s), l, n, f, o);
152
154
  if (e.dk !== null && !u && !(l !== null && typeof l === "object" ? targetsEqual(s, l) : isEqual(s, l))) {
153
155
  bumpDeep(e);
154
156
  u = true;
@@ -185,29 +187,29 @@ function applyAdopt(e, t, n, o = false) {
185
187
  // them here let a duplicate key adopt a prefix row AGAIN while
186
188
  // row ops (which correctly window from structStart) retained
187
189
  // the later occurrence's DOM row against a never-adopted target.
188
- for (let e = y; e < f.length; e++) {
189
- const t = unwrapValue(f[e]);
190
+ for (let e = y; e < i.length; e++) {
191
+ const t = unwrapValue(i[e]);
190
192
  if (t !== null && typeof t === "object") {
191
193
  const o = n(t);
192
194
  if (o === undefined) continue;
193
- const f = w.get(o);
194
- if (f === undefined) w.set(o, e); else if (Array.isArray(f)) f.push(e); else w.set(o, [ f, e ]);
195
+ const i = w.get(o);
196
+ if (i === undefined) w.set(o, e); else if (Array.isArray(i)) i.push(e); else w.set(o, [ i, e ]);
195
197
  }
196
198
  }
197
199
  }
198
200
  const e = w.get(t);
199
201
  if (e === undefined) l = undefined; else if (Array.isArray(e)) {
200
- l = unwrapValue(f[e.shift()]);
202
+ l = unwrapValue(i[e.shift()]);
201
203
  if (e.length === 1) w.set(t, e[0]);
202
204
  } else {
203
- l = unwrapValue(f[e]);
205
+ l = unwrapValue(i[e]);
204
206
  w.delete(t);
205
207
  }
206
208
  } else {
207
- l = unwrapValue(f[p]);
209
+ l = unwrapValue(i[p]);
208
210
  // keyless item: positional fallback
209
211
  }
210
- descend(l, e, n, i, o);
212
+ descend(l, e, n, f, o);
211
213
  }
212
214
  if (c !== null) {
213
215
  const e = c[p];
@@ -218,13 +220,13 @@ function applyAdopt(e, t, n, o = false) {
218
220
  }
219
221
  }
220
222
  } else {
221
- const l = Math.min(f.length, r.length);
223
+ const l = Math.min(i.length, r.length);
222
224
  const s = r.length;
223
225
  let p = false;
224
226
  for (let y = 0; y < s; y++) {
225
227
  const s = r[y];
226
- if (!u && y < l && s !== null && typeof s === "object") descend(unwrapValue(f[y]), s, n, i, o);
227
- if (e.dk !== null && !p && !(s !== null && typeof s === "object" ? targetsEqual(f[y], s) : isEqual(f[y], s))) {
228
+ if (!u && y < l && s !== null && typeof s === "object") descend(unwrapValue(i[y]), s, n, f, o);
229
+ if (e.dk !== null && !p && !(s !== null && typeof s === "object" ? targetsEqual(i[y], s) : isEqual(i[y], s))) {
228
230
  bumpDeep(e);
229
231
  p = true;
230
232
  }
@@ -255,7 +257,7 @@ function applyAdopt(e, t, n, o = false) {
255
257
  // slots must not notify, R9). This replaces the notifyFold re-walk that
256
258
  // doubled dbmon's diff cost. for-in covers own enumerable string keys
257
259
  // with no key-array allocation; symbols get a pass only when present.
258
- const f = s ? e.n : null;
260
+ const i = s ? e.n : null;
259
261
  let r = 0;
260
262
  let c = false;
261
263
  // The per-key body is inlined on purpose (legacy applyStateFast parity:
@@ -267,11 +269,11 @@ function applyAdopt(e, t, n, o = false) {
267
269
  const d = t[s];
268
270
  const p = a[s];
269
271
  const y = d !== null && typeof d === "object";
270
- if (p === d && (!y || !isOwned(d)) && (f === null || f[s] === undefined || !hasAccessorFlag(f[s]))) {
271
- if (f !== null && f[s] !== undefined) r++;
272
+ if (p === d && (!y || !isOwned(d)) && (i === null || i[s] === undefined || !hasAccessorFlag(i[s]))) {
273
+ if (i !== null && i[s] !== undefined) r++;
272
274
  continue;
273
275
  }
274
- if (y && !u) descend(unwrapValue(l[s]), d, n, i, o);
276
+ if (y && !u) descend(unwrapValue(l[s]), d, n, f, o);
275
277
  // Deep-witness (dk): value changes must notify even with NO per-key
276
278
  // node — deep() subscribes one node per record. Checked after descend
277
279
  // so in-place adoptions (same logical slot) don't bump; child records
@@ -280,8 +282,8 @@ function applyAdopt(e, t, n, o = false) {
280
282
  bumpDeep(e);
281
283
  c = true;
282
284
  }
283
- if (f !== null) {
284
- const e = f[s];
285
+ if (i !== null) {
286
+ const e = i[s];
285
287
  if (e !== undefined) {
286
288
  r++;
287
289
  notifyKeyValue(e, s, p, d, a, t);
@@ -293,9 +295,9 @@ function applyAdopt(e, t, n, o = false) {
293
295
  const s = d[e];
294
296
  if (s === $OWNER) continue;
295
297
  const c = t[s];
296
- if (!u && c !== null && typeof c === "object") descend(unwrapValue(l[s]), c, n, i, o);
297
- if (f !== null) {
298
- const e = f[s];
298
+ if (!u && c !== null && typeof c === "object") descend(unwrapValue(l[s]), c, n, f, o);
299
+ if (i !== null) {
300
+ const e = i[s];
299
301
  if (e !== undefined) {
300
302
  r++;
301
303
  notifyKeyValue(e, s, a[s], c, a, t);
@@ -305,9 +307,9 @@ function applyAdopt(e, t, n, o = false) {
305
307
  if (s) {
306
308
  // Deleted-key nodes (in the map but absent from incoming) — counted
307
309
  // fast-out: when every node was visited, skip the sweep entirely.
308
- if (f !== null && r < e.nc) {
309
- for (const e of Reflect.ownKeys(f)) {
310
- if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, a, t, false);
310
+ if (i !== null && r < e.nc) {
311
+ for (const e of Reflect.ownKeys(i)) {
312
+ if (!hasOwnP.call(t, e)) notifyKeyDiff(i[e], e, a, t, false);
311
313
  }
312
314
  }
313
315
  notifyFoldTail(e, a, t);
@@ -327,14 +329,14 @@ const hasOwnP = Object.prototype.hasOwnProperty;
327
329
  return e === t || e !== e && t !== t;
328
330
  }
329
331
 
330
- function descend(e, t, n, o, f = false) {
332
+ function descend(e, t, n, o, i = false) {
331
333
  if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return;
332
334
  // Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
333
335
  // wrappables acquire targets; rawValues never wrap) — one WeakMap get
334
336
  // replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
335
337
  // subtrees before any further checks.
336
- const i = lookupTarget(e, o);
337
- if (i === undefined) return;
338
+ const f = lookupTarget(e, o);
339
+ if (f === undefined) return;
338
340
  // nothing proxied below this pair
339
341
  // The NEW side still validates fully: a frozen/platform/markRaw'd incoming
340
342
  // value is a leaf for reconcile — replaced by reference, never recursed
@@ -348,13 +350,13 @@ function descend(e, t, n, o, f = false) {
348
350
  if (Array.isArray(e) !== Array.isArray(t)) return;
349
351
  if (n) {
350
352
  const o = n(e);
351
- const f = n(t);
353
+ const i = n(t);
352
354
  // Key mismatch detaches: the slot takes the new entity; the old proxy
353
355
  // keeps its (old) backing and a fresh proxy wraps the new value on read.
354
356
  // SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
355
357
  // inequality detached every NaN-keyed slot on every tick while the
356
358
  // Map-based row-ops matcher retained its DOM row (stale forever).
357
- if (o !== undefined && f !== undefined && !sameKey(o, f)) return;
359
+ if (o !== undefined && i !== undefined && !sameKey(o, i)) return;
358
360
  }
359
361
  // Reachability pruning (§6d) is MODE-dependent, both pinned:
360
362
  // - keyed matching descends only where subscriptions exist at/below (`d`) —
@@ -366,8 +368,8 @@ function descend(e, t, n, o, f = false) {
366
368
  // UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
367
369
  // subscriber below); plain keyed reconcile detaches unobserved captures
368
370
  // (recon-snap R18 — staleness is the pinned pruning contract).
369
- if (!f && n !== null && !i.d) return;
370
- applyAdopt(i, t, n, f);
371
+ if (!i && n !== null && !f.d) return;
372
+ applyAdopt(f, t, n, i);
371
373
  }
372
374
 
373
375
  export { reconcileNextState, sameKey };