@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.7

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 (66) hide show
  1. package/dist/dev.js +2467 -1322
  2. package/dist/node.cjs +2037 -2428
  3. package/dist/node.dev.cjs +13724 -0
  4. package/dist/prod/boundaries.js +3 -1
  5. package/dist/prod/core/async.js +47 -20
  6. package/dist/prod/core/attribution-hooks.js +3 -0
  7. package/dist/prod/core/constants.js +9 -1
  8. package/dist/prod/core/context.js +10 -16
  9. package/dist/prod/core/core.js +260 -188
  10. package/dist/prod/core/dev.js +2 -0
  11. package/dist/prod/core/effect.js +41 -32
  12. package/dist/prod/core/external.js +2 -2
  13. package/dist/prod/core/graph.js +35 -31
  14. package/dist/prod/core/heap.js +34 -40
  15. package/dist/prod/core/lanes.js +44 -30
  16. package/dist/prod/core/optimistic.js +55 -45
  17. package/dist/prod/core/owner.js +35 -35
  18. package/dist/prod/core/scheduler.js +176 -177
  19. package/dist/prod/core/verdict.js +53 -52
  20. package/dist/prod/index.js +0 -2
  21. package/dist/prod/map.js +104 -94
  22. package/dist/prod/signals.js +119 -35
  23. package/dist/prod/store/next/optimistic.js +217 -163
  24. package/dist/prod/store/next/projection.js +2 -2
  25. package/dist/prod/store/next/reconcile.js +140 -288
  26. package/dist/prod/store/next/store.js +338 -252
  27. package/dist/prod/store/store.js +2 -2
  28. package/dist/types/core/attribution-hooks.d.ts +75 -0
  29. package/dist/types/core/attribution.d.ts +313 -9
  30. package/dist/types/core/constants.d.ts +8 -0
  31. package/dist/types/core/core.d.ts +12 -3
  32. package/dist/types/core/dev.d.ts +56 -9
  33. package/dist/types/core/heap.d.ts +5 -3
  34. package/dist/types/core/invariants.d.ts +1 -1
  35. package/dist/types/core/lanes.d.ts +10 -0
  36. package/dist/types/core/scheduler.d.ts +14 -4
  37. package/dist/types/signals.d.ts +10 -11
  38. package/dist/types/store/index.d.ts +3 -6
  39. package/dist/types/store/next/optimistic.d.ts +4 -2
  40. package/dist/types/store/next/reconcile.d.ts +5 -12
  41. package/dist/types/store/next/store.d.ts +3 -9
  42. package/dist/types/store/next/target.d.ts +30 -46
  43. package/dist/types/store/store.d.ts +14 -9
  44. package/dist/types-cjs/core/attribution-hooks.d.cts +75 -0
  45. package/dist/types-cjs/core/attribution.d.cts +313 -9
  46. package/dist/types-cjs/core/constants.d.cts +8 -0
  47. package/dist/types-cjs/core/core.d.cts +12 -3
  48. package/dist/types-cjs/core/dev.d.cts +56 -9
  49. package/dist/types-cjs/core/heap.d.cts +5 -3
  50. package/dist/types-cjs/core/invariants.d.cts +1 -1
  51. package/dist/types-cjs/core/lanes.d.cts +10 -0
  52. package/dist/types-cjs/core/scheduler.d.cts +14 -4
  53. package/dist/types-cjs/signals.d.cts +10 -11
  54. package/dist/types-cjs/store/index.d.cts +3 -6
  55. package/dist/types-cjs/store/next/optimistic.d.cts +4 -2
  56. package/dist/types-cjs/store/next/reconcile.d.cts +5 -12
  57. package/dist/types-cjs/store/next/store.d.cts +3 -9
  58. package/dist/types-cjs/store/next/target.d.cts +30 -46
  59. package/dist/types-cjs/store/store.d.cts +14 -9
  60. package/package.json +3 -2
  61. package/dist/prod/store/next/patch-hooks.js +0 -13
  62. package/dist/prod/store/next/patch.js +0 -614
  63. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  64. package/dist/types/store/next/patch.d.ts +0 -91
  65. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  66. package/dist/types-cjs/store/next/patch.d.cts +0 -91
@@ -8,8 +8,6 @@ import "../../core/verdict.js";
8
8
 
9
9
  import "../../core/effect.js";
10
10
 
11
- import { patchHooks, rowHooks } from "./patch-hooks.js";
12
-
13
11
  import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue, getWriteOverride } from "../store.js";
14
12
 
15
13
  import { materializePB, adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
@@ -35,46 +33,46 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
35
33
  * key mismatch detaches (fresh proxy on next read, recon-snap R18);
36
34
  * keyless items fall back positional; null/primitive slots are legal
37
35
  * members (R11). Kind changes replace wholesale (R10).
38
- */ function reconcileNextState(e, n, t, o = false) {
39
- if (n == null) throw new Error("");
40
- const l = n?.[$TARGET];
41
- if (l === undefined || l.px !== n) throw new Error("");
36
+ */ function reconcileNextState(e, t, n, o = false) {
37
+ if (t == null) throw new Error("");
38
+ const f = t?.[$TARGET];
39
+ if (f === undefined || f.px !== t) throw new Error("");
42
40
  // Reconcile's diff walks need a REAL pending container — a prototype
43
41
  // overlay (#3044) materializes to the clone path first (edge: reconcile
44
42
  // inside a setter that already wrote this target).
45
- if (l.ovl) materializePB(l);
46
- let i = t === null ? null : typeof t === "string" ? e => e?.[t] : t;
43
+ if (f.ovl) materializePB(f);
44
+ let l = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
47
45
  // §7b chained backing: a projection derive returning a LIVE store proxy
48
46
  // adopts the proxy itself as the backing — reads flow through the inner
49
47
  // store's traps, so consumers subscribe to the inner graph and updates
50
48
  // flow with no re-derive (#2941). The adoption diff still notifies THIS
51
49
  // store's existing subscribers of the swap.
52
- if (o && e !== n && e?.[$TARGET] !== undefined) {
53
- const n = l.pb ?? l.v;
54
- if (n === e) return;
50
+ if (o && e !== t && e?.[$TARGET] !== undefined) {
51
+ const t = f.pb ?? f.v;
52
+ if (t === e) return;
55
53
  // already chained to this store
56
- adoptPB(l, e);
54
+ adoptPB(f, e);
57
55
  return;
58
56
  }
59
- const u = unwrapValue(e);
60
- if (i) {
57
+ const i = unwrapValue(e);
58
+ if (l) {
61
59
  // Root identity precondition — checked before ANY mutation, so a throwing
62
60
  // reconcile is atomic by construction (RUL-12 ruling). Projections
63
61
  // (replace=true) relax it: a root entity change merges in place — the
64
62
  // root proxy is stable for life (proj R5/R11) — and children are NOT
65
63
  // key-matched across the entity change (proj R7: keyFn drops to
66
64
  // positional so old-entity subtrees never merge into the new entity's).
67
- const e = l.pb ?? l.v;
68
- const n = i(e);
69
- if (n !== undefined && !sameKey(i(u), n)) {
65
+ const e = f.pb ?? f.v;
66
+ const t = l(e);
67
+ if (t !== undefined && !sameKey(l(i), t)) {
70
68
  if (!o) throw new Error("");
71
69
  // Entity change: wholesale swap. The root proxy is stable for life
72
70
  // (proj R5) but NOTHING below survives — children are never matched
73
71
  // across an entity change even when their own keys align (proj R7).
74
72
  // Displaced-raw unregistration (proj R10): the outgoing raw stops
75
73
  // resolving to this proxy; re-handed later it wraps fresh.
76
- (l.fam?.map ?? storeNextLookup).delete(l.pb ?? l.v);
77
- adoptPB(l, u);
74
+ (f.fam?.map ?? storeNextLookup).delete(f.pb ?? f.v);
75
+ adoptPB(f, i);
78
76
  return;
79
77
  }
80
78
  }
@@ -83,107 +81,95 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
83
81
  // armed nodes (reverting with their transaction); committed raw is never
84
82
  // touched. Key-matched rows keep proxy identity by descending into the
85
83
  // existing child targets instead of overriding their parent slots.
86
- if (l.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
87
- optHooks.applyTentative(l, u, i);
84
+ if (f.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
85
+ optHooks.applyTentative(f, i, l);
88
86
  return;
89
87
  }
90
- applyAdopt(l, u, i, o);
88
+ applyAdopt(f, i, l, o);
91
89
  }
92
90
 
93
- function applyAdopt(e, n, t, o = false) {
94
- const l = e.pb ?? e.v;
91
+ function applyAdopt(e, t, n, o = false) {
92
+ const f = e.pb ?? e.v;
95
93
  // The sound identity skip (O7): same reference AND we never diverged it.
96
- if (n === l && !ownedRaw.has(l)) return;
97
- const i = e.fam;
94
+ if (t === f && !ownedRaw.has(f)) return;
95
+ const l = e.fam;
98
96
  // §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
99
97
  // optimistic rows must be visible to key matching so a landing carrying the
100
98
  // same key recycles their proxies. Raw `prev` keeps the identity/ownership
101
99
  // roles above; only matching reads the view.
102
- const u = i?.opt === true ? optHooks.optimisticView(e, l) : l;
103
- const f = Array.isArray(n);
100
+ const i = l?.opt === true ? optHooks.optimisticView(e, f) : f;
101
+ const r = Array.isArray(t);
104
102
  // Plain stores notify inline AFTER the descent (child registrations feed
105
103
  // the fold diff's identity-preservation check); projections keep deferred
106
104
  // folds (downstream holds can form later in the flush).
107
- const s = i === null;
108
- const r = e.s === true;
109
- const c = e.v;
110
- adoptPB(e, n, s);
111
- // Patch channel (adoption site): this record transitioned queue its
112
- // patches with the pre-adopt prev. No bubbling walk: the adoption walk
113
- // visits parents before children, so ancestors emitted already. EAGER
114
- // only family targets' visibility moment is their fold commit
115
- // (drainFolds emits there; emitting here too would double-fire).
116
- if (patchHooks !== null && s && e.pc !== null && e.pc.p !== null) {
117
- // Accessor demotion at the ADOPTION seam is DEV-ONLY (prod principle:
118
- // explicitly-odd input must not cost correct-input prod — the
119
- // per-adoption scan was ~12% of dbmon's tick since adoptPB resets the
120
- // verdict every adoption). Dev demotes AND warns; prod emits directly,
121
- // so a getter adoptee's OUTSIDE deps (signals) won't re-apply in prod —
122
- // caught loudly during development instead. Registration-time admission
123
- // (patchableRaw) keeps its full one-time scan in both modes.
124
- {
125
- patchHooks.emitPatchLocal(e, n, c);
126
- }
127
- }
105
+ const u = l === null;
106
+ const s = e.s === true;
107
+ // Node-notify base is the view the nodes were last told (#3296): a draft
108
+ // preceding this reconcile already moved them to its pending backing at
109
+ // setter exit (prev, materialized above), so diffing incoming against the
110
+ // committed backing would skip a key the draft changed and the reconcile
111
+ // restores the node would commit the superseded draft value.
112
+ const a = f;
113
+ adoptPB(e, t, u);
128
114
  // Shallow adoption: records are slot values — sticky raw-mark the incoming
129
115
  // set (R41) and never descend; slot notification is the positional diff.
130
- if (r) markRawIngest(n);
131
- if (Array.isArray(u) !== f) {
132
- if (s) notifyFold(e, c, n);
116
+ if (s) markRawIngest(t);
117
+ if (Array.isArray(i) !== r) {
118
+ if (u) notifyFold(e, a, t);
133
119
  return;
134
120
  }
135
- if (f) {
136
- const l = u;
137
- const f = n;
121
+ if (r) {
122
+ const f = i;
123
+ const r = t;
138
124
  // Fused array walk (eager mode): per-index notification rides the same
139
125
  // loop as the descent (descend first — targetsEqual needs the child's
140
126
  // re-registration, R9). Length, trailing removed indexes, and any other
141
127
  // unvisited node keys land in the counted sweep below.
142
- const a = s ? e.n : null;
128
+ const c = u ? e.n : null;
143
129
  let p = 0;
144
- if (t && !r) {
130
+ if (n && !s) {
145
131
  // Positional-prefix fast path (legacy keyedMatch-walk parity): while
146
132
  // rows key-match in place — the steady-state polling shape — descend
147
133
  // directly with zero staging. The prevByKey map is built only for the
148
134
  // misaligned remainder, and never at all on aligned ticks.
149
- const u = l.length;
150
- const s = f.length;
151
- let r = false;
135
+ const i = f.length;
136
+ const u = r.length;
137
+ let s = false;
152
138
  let d = 0;
153
- for (const y = Math.min(u, s); d < y; d++) {
139
+ for (const y = Math.min(i, u); d < y; d++) {
140
+ const i = r[d];
154
141
  const u = f[d];
155
- const s = l[d];
156
142
  // Routing heuristic only (aligned vs keyed remainder) — both routes
157
143
  // notify identically and descend() is the one authoritative
158
144
  // validator, so bare typeof gates suffice here; full isWrappable
159
145
  // per row was the walk's dominant residual cost.
160
- if (s !== u && !(s !== null && typeof s === "object" && u !== null && typeof u === "object" && sameKey(t(s), t(u)))) break;
146
+ if (u !== i && !(u !== null && typeof u === "object" && i !== null && typeof i === "object" && sameKey(n(u), n(i)))) break;
161
147
  // misaligned: fall to the keyed remainder below
162
148
  // Identity skip inline (FINDING-1 guard), then descend the pair.
163
- if ((s !== u || u !== null && typeof u === "object" && ownedRaw.has(u)) && u !== null && typeof u === "object") descend(unwrapValue(s), u, t, i, o);
164
- if (e.dk !== null && !r && !(u !== null && typeof u === "object" ? targetsEqual(s, u) : isEqual(s, u))) {
149
+ if ((u !== i || i !== null && typeof i === "object" && ownedRaw.has(i)) && i !== null && typeof i === "object") descend(unwrapValue(u), i, n, l, o);
150
+ if (e.dk !== null && !s && !(i !== null && typeof i === "object" ? targetsEqual(u, i) : isEqual(u, i))) {
165
151
  bumpDeep(e);
166
- r = true;
152
+ s = true;
167
153
  }
168
- if (a !== null) {
169
- const e = a[d];
154
+ if (c !== null) {
155
+ const e = c[d];
170
156
  if (e !== undefined) {
171
157
  p++;
172
- notifyKeyValue(e, d, c[d], u, c, n);
158
+ notifyKeyValue(e, d, a[d], i, a, t);
173
159
  }
174
160
  }
175
161
  }
176
- if (e.dk !== null && !r && d < f.length) bumpDeep(e);
162
+ if (e.dk !== null && !s && d < r.length) bumpDeep(e);
177
163
  const y = d;
178
164
  // misalignment point (== nlen on aligned ticks)
179
165
  let w = null;
180
- for (;d < f.length; d++) {
181
- const e = f[d];
166
+ for (;d < r.length; d++) {
167
+ const e = r[d];
182
168
  // typeof gates route; descend validates (same contract as the prefix).
183
169
  if (e !== null && typeof e === "object") {
184
- const n = t(e);
185
- let u;
186
- if (n !== undefined) {
170
+ const t = n(e);
171
+ let i;
172
+ if (t !== undefined) {
187
173
  if (w === null) {
188
174
  // Occurrence-aware (re-audit 2, P1-5): duplicate keys queue
189
175
  // their prev INDICES (rows can themselves be arrays, so index
@@ -197,112 +183,67 @@ function applyAdopt(e, n, t, o = false) {
197
183
  // them here let a duplicate key adopt a prefix row AGAIN while
198
184
  // row ops (which correctly window from structStart) retained
199
185
  // the later occurrence's DOM row against a never-adopted target.
200
- for (let e = y; e < l.length; e++) {
201
- const n = unwrapValue(l[e]);
202
- if (n !== null && typeof n === "object") {
203
- const o = t(n);
186
+ for (let e = y; e < f.length; e++) {
187
+ const t = unwrapValue(f[e]);
188
+ if (t !== null && typeof t === "object") {
189
+ const o = n(t);
204
190
  if (o === undefined) continue;
205
- const l = w.get(o);
206
- if (l === undefined) w.set(o, e); else if (Array.isArray(l)) l.push(e); else w.set(o, [ l, e ]);
191
+ const f = w.get(o);
192
+ if (f === undefined) w.set(o, e); else if (Array.isArray(f)) f.push(e); else w.set(o, [ f, e ]);
207
193
  }
208
194
  }
209
195
  }
210
- const e = w.get(n);
211
- if (e === undefined) u = undefined; else if (Array.isArray(e)) {
212
- u = unwrapValue(l[e.shift()]);
213
- if (e.length === 1) w.set(n, e[0]);
196
+ const e = w.get(t);
197
+ if (e === undefined) i = undefined; else if (Array.isArray(e)) {
198
+ i = unwrapValue(f[e.shift()]);
199
+ if (e.length === 1) w.set(t, e[0]);
214
200
  } else {
215
- u = unwrapValue(l[e]);
216
- w.delete(n);
201
+ i = unwrapValue(f[e]);
202
+ w.delete(t);
217
203
  }
218
204
  } else {
219
- u = unwrapValue(l[d]);
205
+ i = unwrapValue(f[d]);
220
206
  // keyless item: positional fallback
221
207
  }
222
- descend(u, e, t, i, o);
208
+ descend(i, e, n, l, o);
223
209
  }
224
- if (a !== null) {
225
- const e = a[d];
210
+ if (c !== null) {
211
+ const e = c[d];
226
212
  if (e !== undefined) {
227
213
  p++;
228
- notifyKeyDiff(e, d, c, n, false);
214
+ notifyKeyDiff(e, d, a, t, false);
229
215
  }
230
216
  }
231
217
  }
232
- // Row ops (PR-B): emit structural ops ONLY when structure changed —
233
- // aligned value ticks pay nothing. Built after the walk so retained
234
- // rows' value patches queue first (adds bind at op-apply).
235
- if (rowHooks !== null && e.pc !== null && e.pc.ro !== null && (y < s || u !== s)) buildAndEmitRowOps(e, l, f, y, t);
236
218
  } else {
237
- const u = Math.min(l.length, f.length);
238
- const s = f.length;
219
+ const i = Math.min(f.length, r.length);
220
+ const u = r.length;
239
221
  let d = false;
240
- const y = rowHooks !== null && e.pc !== null ? e.pc.sp : null;
241
- // Row ops for shallow/positional lists: track the key-aligned prefix
242
- // (keyed) so aligned value ticks emit nothing; keyless lists emit only
243
- // on length change (append/truncate). Slot-patch consumers need the
244
- // alignment tracking too (aligned = value tick, misaligned = ops).
245
- const w = rowHooks !== null && e.pc !== null ? e.pc.ro : null;
246
- let b = t !== null && (w !== null || y !== null);
247
- let m = 0;
248
- for (let w = 0; w < s; w++) {
249
- const s = f[w];
250
- if (b && w < u) {
251
- const e = l[w];
252
- if (e !== null && typeof e === "object" && s !== null && typeof s === "object" &&
253
- // SameValueZero (self-sweep): strict === here broke slot
254
- // alignment on NaN keys while buildRowOps retained the row —
255
- // retained DOM with suppressed value ticks (the round-1 NaN
256
- // staleness, in the shallow branch).
257
- sameKey(t(e), t(s))) m++; else b = false;
258
- }
259
- // Slot-patch dispatch (shallow): a KEY-ALIGNED slot whose value was
260
- // replaced by reference is a value tick — emit through the queue.
261
- // Misaligned/appended slots are STRUCTURE (row ops rebuild or move
262
- // them; new rows initial-apply at bind), so they emit nothing here.
263
- // Keyless positional lists treat same-index replacement as the value
264
- // tick for indices below the common length.
265
- // `i < dlen` is load-bearing for BOTH modes: an appended position
266
- // past a fully-aligned prefix (vacuously aligned when prev is empty)
267
- // has no previous slot — emitting a slot tick for it races the row
268
- // ops that CREATE the row (the slot queue applies first, indexing a
269
- // row that does not exist yet). Equivalence-matrix finding:
270
- // clear-then-refill and pure appends crashed the driver.
271
- if (y !== null && w < u && (t === null || b)) {
272
- const n = l[w];
273
- if (n !== s) rowHooks.emitSlotPatch(e, w, s, n);
274
- }
275
- if (!r && w < u && s !== null && typeof s === "object") descend(unwrapValue(l[w]), s, t, i, o);
276
- if (e.dk !== null && !d && !(s !== null && typeof s === "object" ? targetsEqual(l[w], s) : isEqual(l[w], s))) {
222
+ for (let y = 0; y < u; y++) {
223
+ const u = r[y];
224
+ if (!s && y < i && u !== null && typeof u === "object") descend(unwrapValue(f[y]), u, n, l, o);
225
+ if (e.dk !== null && !d && !(u !== null && typeof u === "object" ? targetsEqual(f[y], u) : isEqual(f[y], u))) {
277
226
  bumpDeep(e);
278
227
  d = true;
279
228
  }
280
- if (a !== null) {
281
- const e = a[w];
229
+ if (c !== null) {
230
+ const e = c[y];
282
231
  if (e !== undefined) {
283
232
  p++;
284
- notifyKeyDiff(e, w, c, n, false);
233
+ notifyKeyDiff(e, y, a, t, false);
285
234
  }
286
235
  }
287
236
  }
288
- if (w !== null) {
289
- const n = l.length;
290
- if (t !== null) {
291
- if (m < s || n !== s) buildAndEmitRowOps(e, l, f, m, t);
292
- } else if (n !== s) {
293
- buildAndEmitRowOps(e, l, f, u, null);
294
- }
295
- }
296
237
  }
297
- if (s) {
298
- if (a !== null && p < e.nc) {
299
- for (const e of Reflect.ownKeys(a)) {
238
+ if (u) {
239
+ if (c !== null && p < e.nc) {
240
+ for (const e of Reflect.ownKeys(c)) {
300
241
  // visited indexes are < nextRows.length; everything else sweeps
301
- const t = typeof e === "string" ? +e : NaN;
302
- if (!(t >= 0 && t < f.length)) notifyKeyDiff(a[e], e, c, n, false);
242
+ const n = typeof e === "string" ? +e : NaN;
243
+ if (!(n >= 0 && n < r.length)) notifyKeyDiff(c[e], e, a, t, false);
303
244
  }
304
245
  }
305
- notifyFoldTail(e, c, n);
246
+ notifyFoldTail(e, a, t);
306
247
  }
307
248
  return;
308
249
  } else {
@@ -312,68 +253,61 @@ function applyAdopt(e, n, t, o = false) {
312
253
  // slots must not notify, R9). This replaces the notifyFold re-walk that
313
254
  // doubled dbmon's diff cost. for-in covers own enumerable string keys
314
255
  // with no key-array allocation; symbols get a pass only when present.
315
- // PROTOTYPE compiled-patch fast path: a pure-patch record (no nodes,
316
- // no presence/key-set/deep subscribers, no family) adopts and hands the
317
- // (next, prev) pair to its compiled patch — no per-key walk at all.
318
- if (e.pc !== null && e.pc.p !== null && s && e.n === null && e.h === null && e.k === null && e.dk === null && i === null) {
319
- // Adoption already ran at applyAdopt entry; emission was queued there.
320
- return;
321
- }
322
- const l = s ? e.n : null;
323
- let f = 0;
324
- let a = false;
256
+ const f = u ? e.n : null;
257
+ let r = 0;
258
+ let c = false;
325
259
  // The per-key body is inlined on purpose (legacy applyStateFast parity:
326
260
  // an extracted helper costs a call per key on the hottest object-diff
327
261
  // site). Reference-identical values early-continue BEFORE any other
328
262
  // work — sound only with the ownership guard (FINDING-1: an owned
329
263
  // backing is setter-diverged and must still diff).
330
- for (const s in n) {
331
- const p = n[s];
332
- const d = c[s];
264
+ for (const u in t) {
265
+ const p = t[u];
266
+ const d = a[u];
333
267
  const y = p !== null && typeof p === "object";
334
- if (d === p && (!y || !ownedRaw.has(p)) && (l === null || l[s] === undefined || !hasAccessorFlag(l[s]))) {
335
- if (l !== null && l[s] !== undefined) f++;
268
+ if (d === p && (!y || !ownedRaw.has(p)) && (f === null || f[u] === undefined || !hasAccessorFlag(f[u]))) {
269
+ if (f !== null && f[u] !== undefined) r++;
336
270
  continue;
337
271
  }
338
- if (y && !r) descend(unwrapValue(u[s]), p, t, i, o);
272
+ if (y && !s) descend(unwrapValue(i[u]), p, n, l, o);
339
273
  // Deep-witness (dk): value changes must notify even with NO per-key
340
274
  // node — deep() subscribes one node per record. Checked after descend
341
275
  // so in-place adoptions (same logical slot) don't bump; child records
342
276
  // carry their own witness. One flag + null check when unused.
343
- if (e.dk !== null && !a && !(y ? targetsEqual(d, p) : isEqual(d, p))) {
277
+ if (e.dk !== null && !c && !(y ? targetsEqual(d, p) : isEqual(d, p))) {
344
278
  bumpDeep(e);
345
- a = true;
279
+ c = true;
346
280
  }
347
- if (l !== null) {
348
- const e = l[s];
281
+ if (f !== null) {
282
+ const e = f[u];
349
283
  if (e !== undefined) {
350
- f++;
351
- notifyKeyValue(e, s, d, p, c, n);
284
+ r++;
285
+ notifyKeyValue(e, u, d, p, a, t);
352
286
  }
353
287
  }
354
288
  }
355
- const p = Object.getOwnPropertySymbols(n);
289
+ const p = Object.getOwnPropertySymbols(t);
356
290
  for (let e = 0; e < p.length; e++) {
357
- const s = p[e];
358
- const a = n[s];
359
- if (!r && a !== null && typeof a === "object") descend(unwrapValue(u[s]), a, t, i, o);
360
- if (l !== null) {
361
- const e = l[s];
291
+ const u = p[e];
292
+ const c = t[u];
293
+ if (!s && c !== null && typeof c === "object") descend(unwrapValue(i[u]), c, n, l, o);
294
+ if (f !== null) {
295
+ const e = f[u];
362
296
  if (e !== undefined) {
363
- f++;
364
- notifyKeyValue(e, s, c[s], a, c, n);
297
+ r++;
298
+ notifyKeyValue(e, u, a[u], c, a, t);
365
299
  }
366
300
  }
367
301
  }
368
- if (s) {
302
+ if (u) {
369
303
  // Deleted-key nodes (in the map but absent from incoming) — counted
370
304
  // fast-out: when every node was visited, skip the sweep entirely.
371
- if (l !== null && f < e.nc) {
372
- for (const e of Reflect.ownKeys(l)) {
373
- if (!hasOwnP.call(n, e)) notifyKeyDiff(l[e], e, c, n, false);
305
+ if (f !== null && r < e.nc) {
306
+ for (const e of Reflect.ownKeys(f)) {
307
+ if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, a, t, false);
374
308
  }
375
309
  }
376
- notifyFoldTail(e, c, n);
310
+ notifyFoldTail(e, a, t);
377
311
  }
378
312
  return;
379
313
  }
@@ -383,123 +317,41 @@ const hasOwnP = Object.prototype.hasOwnProperty;
383
317
 
384
318
  /** Setter-channel row ops (the fold site calls this for array targets with
385
319
  * ops consumers): structural mutation through the setter — push/splice/index
386
- * assignment/permutation — is a visibility transition for the list container
387
- * just like a reconcile walk, and drivers consuming registerRowOps must see
388
- * it. Setter mutations move the SAME row objects around, so RAW IDENTITY is
389
- * the key. Aligned arrays (value-only folds) emit nothing. */ const identityKey = e => unwrapValue(e);
390
-
391
320
  /** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
392
- * SameValueZero, matching the Map-based matchers (buildRowOps, the adoption
393
- * window) — NaN keys are equal to themselves, so aligned NaN rows stay
394
- * aligned in the prefix walk instead of forever misaligning. Adoption and
395
- * row ops MUST agree on key equality or retained DOM rows go stale. */ function sameKey(e, n) {
396
- return e === n || e !== e && n !== n;
397
- }
398
-
399
- function emitSetterRowOps(e, n, t) {
400
- const o = buildIdentityRowOps(n, t);
401
- if (o !== null) rowHooks.emitRowOps(e, t, o);
402
- }
403
-
404
- /** Identity-keyed structural diff, returned rather than emitted: shared by
405
- * the setter channel (regular queue) and the OPTIMISTIC write channel (lane
406
- * queue) — same retention semantics, different dispatch timing. Returns
407
- * null when the lists are identity-aligned (no structure changed). */ function buildIdentityRowOps(e, n) {
408
- let t = 0;
409
- const o = e.length < n.length ? e.length : n.length;
410
- while (t < o && unwrapValue(e[t]) === unwrapValue(n[t])) t++;
411
- if (t === e.length && t === n.length) return null;
412
- return buildRowOps(e, n, t, identityKey);
413
- }
414
-
415
- /** Shared row-ops builder (keyed deep branch + shallow/positional branch):
416
- * key-matches the misaligned window into { prefix, sources, removed }.
417
- * `keyFn === null` degrades to positional ops (append/truncate only). */ function buildAndEmitRowOps(e, n, t, o, l) {
418
- rowHooks.emitRowOps(e, t, buildRowOps(n, t, o, l));
419
- }
420
-
421
- function buildRowOps(e, n, t, o) {
422
- const l = e.length;
423
- const i = n.length;
424
- const u = new Array(i - t);
425
- // Occurrence-aware matching (re-audit): duplicate keys queue their old
426
- // indices and each is consumed ONCE — first-wins reuse would hand the same
427
- // source (and its one DOM row) to multiple next positions. The no-dup fast
428
- // shape stays a bare number; collisions upgrade to a queue.
429
- let f = null;
430
- if (o !== null && t < l) {
431
- f = new Map;
432
- for (let n = t; n < l; n++) {
433
- const t = unwrapValue(e[n]);
434
- if (t !== null && typeof t === "object") {
435
- const e = o(t);
436
- if (e === undefined) continue;
437
- const l = f.get(e);
438
- if (l === undefined) f.set(e, n); else if (Array.isArray(l)) l.push(n); else f.set(e, [ l, n ]);
439
- }
440
- }
441
- }
442
- const s = f !== null ? new Set : null;
443
- for (let e = t; e < i; e++) {
444
- const l = n[e];
445
- let i = -1;
446
- if (l !== null && typeof l === "object" && f !== null) {
447
- const e = o(l);
448
- if (e !== undefined) {
449
- const n = f.get(e);
450
- if (n !== undefined) {
451
- if (Array.isArray(n)) {
452
- i = n.shift();
453
- if (n.length === 1) f.set(e, n[0]);
454
- } else {
455
- i = n;
456
- f.delete(e);
457
- }
458
- s.add(i);
459
- }
460
- }
461
- }
462
- u[e - t] = i;
463
- }
464
- const r = [];
465
- for (let n = t; n < l; n++) {
466
- if (s === null || !s.has(n)) r.push(unwrapValue(e[n]));
467
- }
468
- return {
469
- prefix: t,
470
- sources: u,
471
- removed: r
472
- };
321
+ * SameValueZero, matching the adoption window's Map-based matcher NaN keys
322
+ * are equal to themselves, so aligned NaN rows stay aligned in the prefix
323
+ * walk instead of forever misaligning. */ function sameKey(e, t) {
324
+ return e === t || e !== e && t !== t;
473
325
  }
474
326
 
475
- function descend(e, n, t, o, l = false) {
476
- if (e === null || typeof e !== "object" || n === null || typeof n !== "object") return;
327
+ function descend(e, t, n, o, f = false) {
328
+ if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return;
477
329
  // Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
478
330
  // wrappables acquire targets; rawValues never wrap) — one WeakMap get
479
331
  // replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
480
332
  // subtrees before any further checks.
481
- const i = (o?.map ?? storeNextLookup).get(e);
482
- if (i === undefined) return;
333
+ const l = (o?.map ?? storeNextLookup).get(e);
334
+ if (l === undefined) return;
483
335
  // nothing proxied below this pair
484
336
  // The NEW side still validates fully: a frozen/platform/markRaw'd incoming
485
337
  // value is a leaf for reconcile — replaced by reference, never recursed
486
338
  // into (R42); the parent's slot notification covers the change.
487
- if (!isWrappable(n)) return;
488
- if (rawValuesUsed && isRawValue(n)) return;
489
- n = unwrapValue(n);
339
+ if (!isWrappable(t)) return;
340
+ if (rawValuesUsed && isRawValue(t)) return;
341
+ t = unwrapValue(t);
490
342
  // Kind change replaces wholesale, never merges (R10): a target's carrier
491
343
  // class (array vs object) is fixed at creation, so the slot detaches and a
492
344
  // fresh proxy of the right kind wraps the incoming value on next read.
493
- if (Array.isArray(e) !== Array.isArray(n)) return;
494
- if (t) {
495
- const o = t(e);
496
- const l = t(n);
345
+ if (Array.isArray(e) !== Array.isArray(t)) return;
346
+ if (n) {
347
+ const o = n(e);
348
+ const f = n(t);
497
349
  // Key mismatch detaches: the slot takes the new entity; the old proxy
498
350
  // keeps its (old) backing and a fresh proxy wraps the new value on read.
499
351
  // SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
500
352
  // inequality detached every NaN-keyed slot on every tick while the
501
353
  // Map-based row-ops matcher retained its DOM row (stale forever).
502
- if (o !== undefined && l !== undefined && !sameKey(o, l)) return;
354
+ if (o !== undefined && f !== undefined && !sameKey(o, f)) return;
503
355
  }
504
356
  // Reachability pruning (§6d) is MODE-dependent, both pinned:
505
357
  // - keyed matching descends only where subscriptions exist at/below (`d`) —
@@ -511,8 +363,8 @@ function descend(e, n, t, o, l = false) {
511
363
  // UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
512
364
  // subscriber below); plain keyed reconcile detaches unobserved captures
513
365
  // (recon-snap R18 — staleness is the pinned pruning contract).
514
- if (!l && t !== null && !i.d) return;
515
- applyAdopt(i, n, t, l);
366
+ if (!f && n !== null && !l.d) return;
367
+ applyAdopt(l, t, n, f);
516
368
  }
517
369
 
518
- export { buildIdentityRowOps, emitSetterRowOps, reconcileNextState, sameKey };
370
+ export { reconcileNextState, sameKey };