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

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 (150) hide show
  1. package/dist/dev-shared.js +7155 -0
  2. package/dist/dev.attribution.js +3307 -0
  3. package/dist/dev.js +5765 -4496
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +793 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +849 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +280 -0
  11. package/dist/observe/core/attribution-hooks.js +83 -0
  12. package/dist/observe/core/attribution-queries.js +43 -0
  13. package/dist/observe/core/attribution.js +2904 -0
  14. package/dist/observe/core/constants.js +283 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1999 -0
  17. package/dist/observe/core/dev.js +343 -0
  18. package/dist/observe/core/effect.js +200 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +669 -0
  27. package/dist/observe/core/owner.js +353 -0
  28. package/dist/observe/core/scheduler.js +1561 -0
  29. package/dist/observe/core/verdict.js +674 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +507 -0
  32. package/dist/observe/signals.js +731 -0
  33. package/dist/observe/store/index.js +44 -0
  34. package/dist/observe/store/next/optimistic.js +717 -0
  35. package/dist/observe/store/next/projection.js +266 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2480 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +331 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1070 -0
  42. package/dist/prod/affects.js +10 -9
  43. package/dist/prod/attribution.js +45 -0
  44. package/dist/prod/boundaries.js +388 -178
  45. package/dist/prod/core/action.js +65 -37
  46. package/dist/prod/core/async.js +378 -163
  47. package/dist/prod/core/attribution-hooks.js +3 -0
  48. package/dist/prod/core/constants.js +186 -1
  49. package/dist/prod/core/context.js +10 -16
  50. package/dist/prod/core/core.js +1546 -416
  51. package/dist/prod/core/dev.js +18 -1
  52. package/dist/prod/core/effect.js +119 -79
  53. package/dist/prod/core/error-hooks.js +71 -0
  54. package/dist/prod/core/error.js +13 -1
  55. package/dist/prod/core/external.js +4 -4
  56. package/dist/prod/core/graph.js +105 -54
  57. package/dist/prod/core/heap.js +72 -65
  58. package/dist/prod/core/invariants.js +3 -2
  59. package/dist/prod/core/lanes.js +117 -52
  60. package/dist/prod/core/optimistic.js +500 -104
  61. package/dist/prod/core/owner.js +131 -84
  62. package/dist/prod/core/scheduler.js +1012 -241
  63. package/dist/prod/core/verdict.js +417 -112
  64. package/dist/prod/index.js +20 -8
  65. package/dist/prod/map.js +355 -171
  66. package/dist/prod/signals.js +363 -32
  67. package/dist/prod/store/index.js +2 -0
  68. package/dist/prod/store/next/optimistic.js +530 -197
  69. package/dist/prod/store/next/projection.js +142 -57
  70. package/dist/prod/store/next/reconcile.js +149 -103
  71. package/dist/prod/store/next/store.js +1470 -346
  72. package/dist/prod/store/next/target.js +40 -9
  73. package/dist/prod/store/store.js +23 -19
  74. package/dist/prod/store/utils.js +997 -130
  75. package/dist/types/attribution.d.ts +19 -0
  76. package/dist/types/attribution.prod.d.ts +22 -0
  77. package/dist/types/boundaries.d.ts +78 -5
  78. package/dist/types/core/action.d.ts +12 -5
  79. package/dist/types/core/async.d.ts +3 -1
  80. package/dist/types/core/attribution-costs.d.ts +35 -0
  81. package/dist/types/core/attribution-feedback.d.ts +133 -0
  82. package/dist/types/core/attribution-hooks.d.ts +304 -0
  83. package/dist/types/core/attribution-queries.d.ts +24 -0
  84. package/dist/types/core/attribution.d.ts +892 -0
  85. package/dist/types/core/constants.d.ts +179 -0
  86. package/dist/types/core/core.d.ts +299 -25
  87. package/dist/types/core/dev.d.ts +421 -8
  88. package/dist/types/core/effect.d.ts +8 -1
  89. package/dist/types/core/error-hooks.d.ts +71 -0
  90. package/dist/types/core/error.d.ts +9 -0
  91. package/dist/types/core/graph.d.ts +23 -0
  92. package/dist/types/core/heap.d.ts +5 -3
  93. package/dist/types/core/index.d.ts +6 -3
  94. package/dist/types/core/invariants.d.ts +5 -1
  95. package/dist/types/core/lanes.d.ts +51 -19
  96. package/dist/types/core/owner.d.ts +15 -3
  97. package/dist/types/core/scheduler.d.ts +252 -1
  98. package/dist/types/core/types.d.ts +124 -41
  99. package/dist/types/index.d.ts +18 -5
  100. package/dist/types/map.d.ts +2 -0
  101. package/dist/types/signals.d.ts +145 -13
  102. package/dist/types/store/index.d.ts +7 -6
  103. package/dist/types/store/next/optimistic.d.ts +18 -13
  104. package/dist/types/store/next/projection.d.ts +5 -2
  105. package/dist/types/store/next/reconcile.d.ts +7 -0
  106. package/dist/types/store/next/store.d.ts +83 -11
  107. package/dist/types/store/next/target.d.ts +108 -11
  108. package/dist/types/store/store.d.ts +20 -11
  109. package/dist/types/store/utils.d.ts +188 -0
  110. package/package.json +31 -26
  111. package/dist/node.cjs +0 -8137
  112. package/dist/types/store/optimistic.d.ts +0 -45
  113. package/dist/types/store/projection.d.ts +0 -70
  114. package/dist/types/store/reconcile.d.ts +0 -46
  115. package/dist/types-cjs/affects.d.cts +0 -47
  116. package/dist/types-cjs/boundaries.d.cts +0 -175
  117. package/dist/types-cjs/core/action.d.cts +0 -64
  118. package/dist/types-cjs/core/async.d.cts +0 -21
  119. package/dist/types-cjs/core/constants.d.cts +0 -71
  120. package/dist/types-cjs/core/core.d.cts +0 -156
  121. package/dist/types-cjs/core/dev.d.cts +0 -64
  122. package/dist/types-cjs/core/effect.d.cts +0 -30
  123. package/dist/types-cjs/core/error.d.cts +0 -47
  124. package/dist/types-cjs/core/external.d.cts +0 -15
  125. package/dist/types-cjs/core/graph.d.cts +0 -5
  126. package/dist/types-cjs/core/heap.d.cts +0 -22
  127. package/dist/types-cjs/core/index.d.cts +0 -13
  128. package/dist/types-cjs/core/invariants.d.cts +0 -59
  129. package/dist/types-cjs/core/lanes.d.cts +0 -56
  130. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  131. package/dist/types-cjs/core/owner.d.cts +0 -124
  132. package/dist/types-cjs/core/scheduler.d.cts +0 -172
  133. package/dist/types-cjs/core/types.d.cts +0 -148
  134. package/dist/types-cjs/core/verdict.d.cts +0 -2
  135. package/dist/types-cjs/index.d.cts +0 -10
  136. package/dist/types-cjs/map.d.cts +0 -69
  137. package/dist/types-cjs/package.json +0 -3
  138. package/dist/types-cjs/signals.d.cts +0 -624
  139. package/dist/types-cjs/store/index.d.cts +0 -19
  140. package/dist/types-cjs/store/next/optimistic.d.cts +0 -20
  141. package/dist/types-cjs/store/next/projection.d.cts +0 -24
  142. package/dist/types-cjs/store/next/reconcile.d.cts +0 -3
  143. package/dist/types-cjs/store/next/store.d.cts +0 -71
  144. package/dist/types-cjs/store/next/target.d.cts +0 -99
  145. package/dist/types-cjs/store/optimistic.d.cts +0 -45
  146. package/dist/types-cjs/store/projection.d.cts +0 -70
  147. package/dist/types-cjs/store/reconcile.d.cts +0 -46
  148. package/dist/types-cjs/store/store.d.cts +0 -138
  149. package/dist/types-cjs/store/storePath.d.cts +0 -58
  150. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -10,9 +10,9 @@ import "../../core/effect.js";
10
10
 
11
11
  import { $TARGET, markRawIngest, isWrappable, rawValuesUsed, isRawValue, getWriteOverride } from "../store.js";
12
12
 
13
- import { adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
13
+ import { materializePB, adoptPB, unwrapValue, notifyFold, targetsEqual, bumpDeep, notifyKeyValue, notifyKeyDiff, notifyFoldTail, hasAccessorFlag } from "./store.js";
14
14
 
15
- import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
15
+ import { isOwned, $OWNER, storeNextLookup, optHooks, lookupTarget } from "./target.js";
16
16
 
17
17
  /**
18
18
  * Store rewrite — reconcile, the adoption channel (INTERNALS-STORE-STATE.md
@@ -37,7 +37,11 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
37
37
  if (t == null) throw new Error("");
38
38
  const f = t?.[$TARGET];
39
39
  if (f === undefined || f.px !== t) throw new Error("");
40
- let l = n === null ? null : typeof n === "string" ? e => e?.[n] : n;
40
+ // Reconcile's diff walks need a REAL pending container — a prototype
41
+ // overlay (#3044) materializes to the clone path first (edge: reconcile
42
+ // 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;
41
45
  // §7b chained backing: a projection derive returning a LIVE store proxy
42
46
  // adopts the proxy itself as the backing — reads flow through the inner
43
47
  // store's traps, so consumers subscribe to the inner graph and updates
@@ -50,8 +54,8 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
50
54
  adoptPB(f, e);
51
55
  return;
52
56
  }
53
- const i = unwrapValue(e);
54
- if (l) {
57
+ const l = unwrapValue(e);
58
+ if (i) {
55
59
  // Root identity precondition — checked before ANY mutation, so a throwing
56
60
  // reconcile is atomic by construction (RUL-12 ruling). Projections
57
61
  // (replace=true) relax it: a root entity change merges in place — the
@@ -59,16 +63,18 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
59
63
  // key-matched across the entity change (proj R7: keyFn drops to
60
64
  // positional so old-entity subtrees never merge into the new entity's).
61
65
  const e = f.pb ?? f.v;
62
- const t = l(e);
63
- if (t !== undefined && l(i) !== t) {
66
+ const t = i(e);
67
+ if (t !== undefined && !sameKey(i(l), t)) {
64
68
  if (!o) throw new Error("");
65
69
  // Entity change: wholesale swap. The root proxy is stable for life
66
70
  // (proj R5) but NOTHING below survives — children are never matched
67
71
  // across an entity change even when their own keys align (proj R7).
68
72
  // Displaced-raw unregistration (proj R10): the outgoing raw stops
69
73
  // resolving to this proxy; re-handed later it wraps fresh.
70
- (f.fam?.map ?? storeNextLookup).delete(f.pb ?? f.v);
71
- adoptPB(f, i);
74
+ const e = f.pb ?? f.v;
75
+ if (isOwned(e)) delete e[$OWNER]; // disown: wraps fresh if re-handed
76
+ else (f.fam?.map ?? storeNextLookup).delete(e);
77
+ adoptPB(f, l);
72
78
  return;
73
79
  }
74
80
  }
@@ -78,141 +84,168 @@ import { storeNextLookup, optHooks, ownedRaw } from "./target.js";
78
84
  // touched. Key-matched rows keep proxy identity by descending into the
79
85
  // existing child targets instead of overriding their parent slots.
80
86
  if (f.fam?.opt === true && !projectionWriteActive && !getWriteOverride()) {
81
- optHooks.applyTentative(f, i, l);
87
+ optHooks.applyTentative(f, l, i);
82
88
  return;
83
89
  }
84
- applyAdopt(f, i, l, o);
90
+ applyAdopt(f, l, i, o);
85
91
  }
86
92
 
87
93
  function applyAdopt(e, t, n, o = false) {
88
94
  const f = e.pb ?? e.v;
89
95
  // The sound identity skip (O7): same reference AND we never diverged it.
90
- if (t === f && !ownedRaw.has(f)) return;
91
- const l = e.fam;
96
+ if (t === f && !isOwned(f)) return;
97
+ const i = e.fam;
92
98
  // §6b (R28): the diff's previous-arrangement baseline is the LANE VIEW —
93
99
  // optimistic rows must be visible to key matching so a landing carrying the
94
100
  // same key recycles their proxies. Raw `prev` keeps the identity/ownership
95
101
  // roles above; only matching reads the view.
96
- const i = l?.opt === true ? optHooks.optimisticView(e, f) : f;
102
+ const l = i?.opt === true ? optHooks.optimisticView(e, f) : f;
97
103
  const r = Array.isArray(t);
98
104
  // Plain stores notify inline AFTER the descent (child registrations feed
99
105
  // the fold diff's identity-preservation check); projections keep deferred
100
106
  // folds (downstream holds can form later in the flush).
101
- const u = l === null;
102
- const s = e.s === true;
103
- const c = e.v;
104
- adoptPB(e, t, u);
107
+ const s = i === null;
108
+ const u = e.s === true;
109
+ // Node-notify base is the view the nodes were last told (#3296): a draft
110
+ // preceding this reconcile already moved them to its pending backing at
111
+ // setter exit (prev, materialized above), so diffing incoming against the
112
+ // committed backing would skip a key the draft changed and the reconcile
113
+ // restores — the node would commit the superseded draft value.
114
+ const a = f;
115
+ adoptPB(e, t, s);
105
116
  // Shallow adoption: records are slot values — sticky raw-mark the incoming
106
117
  // set (R41) and never descend; slot notification is the positional diff.
107
- if (s) markRawIngest(t);
108
- if (Array.isArray(i) !== r) {
109
- if (u) notifyFold(e, c, t);
118
+ if (u) markRawIngest(t);
119
+ if (Array.isArray(l) !== r) {
120
+ if (s) notifyFold(e, a, t);
110
121
  return;
111
122
  }
112
123
  if (r) {
113
- const f = i;
124
+ const f = l;
114
125
  const r = t;
115
126
  // Fused array walk (eager mode): per-index notification rides the same
116
127
  // loop as the descent (descend first — targetsEqual needs the child's
117
128
  // re-registration, R9). Length, trailing removed indexes, and any other
118
129
  // unvisited node keys land in the counted sweep below.
119
- const a = u ? e.n : null;
120
- let p = 0;
121
- if (n && !s) {
130
+ const c = s ? e.n : null;
131
+ let d = 0;
132
+ if (n && !u) {
122
133
  // Positional-prefix fast path (legacy keyedMatch-walk parity): while
123
134
  // rows key-match in place — the steady-state polling shape — descend
124
135
  // directly with zero staging. The prevByKey map is built only for the
125
136
  // misaligned remainder, and never at all on aligned ticks.
126
- const i = f.length;
127
- const u = r.length;
128
- let s = false;
129
- let d = 0;
130
- for (const y = Math.min(i, u); d < y; d++) {
131
- const i = r[d];
132
- const u = f[d];
137
+ const l = f.length;
138
+ const s = r.length;
139
+ let u = false;
140
+ let p = 0;
141
+ for (const y = Math.min(l, s); p < y; p++) {
142
+ const l = r[p];
143
+ const s = f[p];
133
144
  // Routing heuristic only (aligned vs keyed remainder) — both routes
134
145
  // notify identically and descend() is the one authoritative
135
146
  // validator, so bare typeof gates suffice here; full isWrappable
136
147
  // per row was the walk's dominant residual cost.
137
- if (u !== i && !(u !== null && typeof u === "object" && i !== null && typeof i === "object" && n(u) === n(i))) break;
148
+ if (s !== l && !(s !== null && typeof s === "object" && l !== null && typeof l === "object" && sameKey(n(s), n(l)))) break;
138
149
  // misaligned: fall to the keyed remainder below
139
150
  // Identity skip inline (FINDING-1 guard), then descend the pair.
140
- if ((u !== i || i !== null && typeof i === "object" && ownedRaw.has(i)) && i !== null && typeof i === "object") descend(unwrapValue(u), i, n, l, o);
141
- if (e.dk !== null && !s && !(i !== null && typeof i === "object" ? targetsEqual(u, i) : isEqual(u, i))) {
151
+ if ((s !== l || l !== null && typeof l === "object" && isOwned(l)) && l !== null && typeof l === "object") descend(unwrapValue(s), l, n, i, o);
152
+ if (e.dk !== null && !u && !(l !== null && typeof l === "object" ? targetsEqual(s, l) : isEqual(s, l))) {
142
153
  bumpDeep(e);
143
- s = true;
154
+ u = true;
144
155
  }
145
- if (a !== null) {
146
- const e = a[d];
156
+ if (c !== null) {
157
+ const e = c[p];
147
158
  if (e !== undefined) {
148
- p++;
149
- notifyKeyValue(e, d, c[d], i, c, t);
159
+ d++;
160
+ notifyKeyValue(e, p, a[p], l, a, t);
150
161
  }
151
162
  }
152
163
  }
153
- if (e.dk !== null && !s && d < r.length) bumpDeep(e);
154
- let y = null;
155
- for (;d < r.length; d++) {
156
- const e = r[d];
164
+ if (e.dk !== null && !u && p < r.length) bumpDeep(e);
165
+ const y = p;
166
+ // misalignment point (== nlen on aligned ticks)
167
+ let w = null;
168
+ for (;p < r.length; p++) {
169
+ const e = r[p];
157
170
  // typeof gates route; descend validates (same contract as the prefix).
158
171
  if (e !== null && typeof e === "object") {
159
172
  const t = n(e);
160
- let i;
173
+ let l;
161
174
  if (t !== undefined) {
162
- if (y === null) {
163
- y = new Map;
164
- for (let e = 0; e < f.length; e++) {
175
+ if (w === null) {
176
+ // Occurrence-aware (re-audit 2, P1-5): duplicate keys queue
177
+ // their prev INDICES (rows can themselves be arrays, so index
178
+ // queues are the unambiguous encoding — same as buildRowOps)
179
+ // and each is consumed ONCE. First-wins would adopt two next
180
+ // rows into the SAME prev target while row ops retain two
181
+ // separate DOM rows (the second one stale).
182
+ w = new Map;
183
+ // From structStart, not 0 (re-audit 3, P1-2): prefix-aligned
184
+ // rows already adopted their incoming counterparts — re-offering
185
+ // them here let a duplicate key adopt a prefix row AGAIN while
186
+ // row ops (which correctly window from structStart) retained
187
+ // the later occurrence's DOM row against a never-adopted target.
188
+ for (let e = y; e < f.length; e++) {
165
189
  const t = unwrapValue(f[e]);
166
190
  if (t !== null && typeof t === "object") {
167
- const e = n(t);
168
- if (e !== undefined && !y.has(e)) y.set(e, t);
191
+ const o = n(t);
192
+ 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 ]);
169
195
  }
170
196
  }
171
197
  }
172
- i = y.get(t);
198
+ const e = w.get(t);
199
+ if (e === undefined) l = undefined; else if (Array.isArray(e)) {
200
+ l = unwrapValue(f[e.shift()]);
201
+ if (e.length === 1) w.set(t, e[0]);
202
+ } else {
203
+ l = unwrapValue(f[e]);
204
+ w.delete(t);
205
+ }
173
206
  } else {
174
- i = unwrapValue(f[d]);
207
+ l = unwrapValue(f[p]);
175
208
  // keyless item: positional fallback
176
209
  }
177
- descend(i, e, n, l, o);
210
+ descend(l, e, n, i, o);
178
211
  }
179
- if (a !== null) {
180
- const e = a[d];
212
+ if (c !== null) {
213
+ const e = c[p];
181
214
  if (e !== undefined) {
182
- p++;
183
- notifyKeyDiff(e, d, c, t, false);
215
+ d++;
216
+ notifyKeyDiff(e, p, a, t, false);
184
217
  }
185
218
  }
186
219
  }
187
220
  } else {
188
- const i = Math.min(f.length, r.length);
189
- const u = r.length;
190
- let d = false;
191
- for (let y = 0; y < u; y++) {
192
- const u = r[y];
193
- if (!s && y < i && u !== null && typeof u === "object") descend(unwrapValue(f[y]), u, n, l, o);
194
- if (e.dk !== null && !d && !(u !== null && typeof u === "object" ? targetsEqual(f[y], u) : isEqual(f[y], u))) {
221
+ const l = Math.min(f.length, r.length);
222
+ const s = r.length;
223
+ let p = false;
224
+ for (let y = 0; y < s; y++) {
225
+ 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))) {
195
228
  bumpDeep(e);
196
- d = true;
229
+ p = true;
197
230
  }
198
- if (a !== null) {
199
- const e = a[y];
231
+ if (c !== null) {
232
+ const e = c[y];
200
233
  if (e !== undefined) {
201
- p++;
202
- notifyKeyDiff(e, y, c, t, false);
234
+ d++;
235
+ notifyKeyDiff(e, y, a, t, false);
203
236
  }
204
237
  }
205
238
  }
206
239
  }
207
- if (u) {
208
- if (a !== null && p < e.nc) {
209
- for (const e of Reflect.ownKeys(a)) {
240
+ if (s) {
241
+ if (c !== null && d < e.nc) {
242
+ for (const e of Reflect.ownKeys(c)) {
210
243
  // visited indexes are < nextRows.length; everything else sweeps
211
244
  const n = typeof e === "string" ? +e : NaN;
212
- if (!(n >= 0 && n < r.length)) notifyKeyDiff(a[e], e, c, t, false);
245
+ if (!(n >= 0 && n < r.length)) notifyKeyDiff(c[e], e, a, t, false);
213
246
  }
214
247
  }
215
- notifyFoldTail(e, c, t);
248
+ notifyFoldTail(e, a, t);
216
249
  }
217
250
  return;
218
251
  } else {
@@ -222,61 +255,62 @@ function applyAdopt(e, t, n, o = false) {
222
255
  // slots must not notify, R9). This replaces the notifyFold re-walk that
223
256
  // doubled dbmon's diff cost. for-in covers own enumerable string keys
224
257
  // with no key-array allocation; symbols get a pass only when present.
225
- const f = u ? e.n : null;
258
+ const f = s ? e.n : null;
226
259
  let r = 0;
227
- let a = false;
260
+ let c = false;
228
261
  // The per-key body is inlined on purpose (legacy applyStateFast parity:
229
262
  // an extracted helper costs a call per key on the hottest object-diff
230
263
  // site). Reference-identical values early-continue BEFORE any other
231
264
  // work — sound only with the ownership guard (FINDING-1: an owned
232
265
  // backing is setter-diverged and must still diff).
233
- for (const u in t) {
234
- const p = t[u];
235
- const d = c[u];
236
- const y = p !== null && typeof p === "object";
237
- if (d === p && (!y || !ownedRaw.has(p)) && (f === null || f[u] === undefined || !hasAccessorFlag(f[u]))) {
238
- if (f !== null && f[u] !== undefined) r++;
266
+ for (const s in t) {
267
+ const d = t[s];
268
+ const p = a[s];
269
+ 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++;
239
272
  continue;
240
273
  }
241
- if (y && !s) descend(unwrapValue(i[u]), p, n, l, o);
274
+ if (y && !u) descend(unwrapValue(l[s]), d, n, i, o);
242
275
  // Deep-witness (dk): value changes must notify even with NO per-key
243
276
  // node — deep() subscribes one node per record. Checked after descend
244
277
  // so in-place adoptions (same logical slot) don't bump; child records
245
278
  // carry their own witness. One flag + null check when unused.
246
- if (e.dk !== null && !a && !(y ? targetsEqual(d, p) : isEqual(d, p))) {
279
+ if (e.dk !== null && !c && !(y ? targetsEqual(p, d) : isEqual(p, d))) {
247
280
  bumpDeep(e);
248
- a = true;
281
+ c = true;
249
282
  }
250
283
  if (f !== null) {
251
- const e = f[u];
284
+ const e = f[s];
252
285
  if (e !== undefined) {
253
286
  r++;
254
- notifyKeyValue(e, u, d, p, c, t);
287
+ notifyKeyValue(e, s, p, d, a, t);
255
288
  }
256
289
  }
257
290
  }
258
- const p = Object.getOwnPropertySymbols(t);
259
- for (let e = 0; e < p.length; e++) {
260
- const u = p[e];
261
- const a = t[u];
262
- if (!s && a !== null && typeof a === "object") descend(unwrapValue(i[u]), a, n, l, o);
291
+ const d = Object.getOwnPropertySymbols(t);
292
+ for (let e = 0; e < d.length; e++) {
293
+ const s = d[e];
294
+ if (s === $OWNER) continue;
295
+ const c = t[s];
296
+ if (!u && c !== null && typeof c === "object") descend(unwrapValue(l[s]), c, n, i, o);
263
297
  if (f !== null) {
264
- const e = f[u];
298
+ const e = f[s];
265
299
  if (e !== undefined) {
266
300
  r++;
267
- notifyKeyValue(e, u, c[u], a, c, t);
301
+ notifyKeyValue(e, s, a[s], c, a, t);
268
302
  }
269
303
  }
270
304
  }
271
- if (u) {
305
+ if (s) {
272
306
  // Deleted-key nodes (in the map but absent from incoming) — counted
273
307
  // fast-out: when every node was visited, skip the sweep entirely.
274
308
  if (f !== null && r < e.nc) {
275
309
  for (const e of Reflect.ownKeys(f)) {
276
- if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, c, t, false);
310
+ if (!hasOwnP.call(t, e)) notifyKeyDiff(f[e], e, a, t, false);
277
311
  }
278
312
  }
279
- notifyFoldTail(e, c, t);
313
+ notifyFoldTail(e, a, t);
280
314
  }
281
315
  return;
282
316
  }
@@ -284,14 +318,23 @@ function applyAdopt(e, t, n, o = false) {
284
318
 
285
319
  const hasOwnP = Object.prototype.hasOwnProperty;
286
320
 
321
+ /** Setter-channel row ops (the fold site calls this for array targets with
322
+ * ops consumers): structural mutation through the setter — push/splice/index
323
+ /** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
324
+ * SameValueZero, matching the adoption window's Map-based matcher — NaN keys
325
+ * are equal to themselves, so aligned NaN rows stay aligned in the prefix
326
+ * walk instead of forever misaligning. */ function sameKey(e, t) {
327
+ return e === t || e !== e && t !== t;
328
+ }
329
+
287
330
  function descend(e, t, n, o, f = false) {
288
331
  if (e === null || typeof e !== "object" || t === null || typeof t !== "object") return;
289
332
  // Lookup FIRST: a hit implies pv was wrappable and never raw-marked (only
290
333
  // wrappables acquire targets; rawValues never wrap) — one WeakMap get
291
334
  // replaces isWrappable(pv) + isRawValue(pv), and a miss prunes untracked
292
335
  // subtrees before any further checks.
293
- const l = (o?.map ?? storeNextLookup).get(e);
294
- if (l === undefined) return;
336
+ const i = lookupTarget(e, o);
337
+ if (i === undefined) return;
295
338
  // nothing proxied below this pair
296
339
  // The NEW side still validates fully: a frozen/platform/markRaw'd incoming
297
340
  // value is a leaf for reconcile — replaced by reference, never recursed
@@ -308,7 +351,10 @@ function descend(e, t, n, o, f = false) {
308
351
  const f = n(t);
309
352
  // Key mismatch detaches: the slot takes the new entity; the old proxy
310
353
  // keeps its (old) backing and a fresh proxy wraps the new value on read.
311
- if (o !== undefined && f !== undefined && o !== f) return;
354
+ // SameValueZero (re-audit 2, P1-5): NaN keys are self-equal — strict
355
+ // inequality detached every NaN-keyed slot on every tick while the
356
+ // Map-based row-ops matcher retained its DOM row (stale forever).
357
+ if (o !== undefined && f !== undefined && !sameKey(o, f)) return;
312
358
  }
313
359
  // Reachability pruning (§6d) is MODE-dependent, both pinned:
314
360
  // - keyed matching descends only where subscriptions exist at/below (`d`) —
@@ -320,8 +366,8 @@ function descend(e, t, n, o, f = false) {
320
366
  // UNCONDITIONALLY (proj R6: the slot keeps its proxy without needing a
321
367
  // subscriber below); plain keyed reconcile detaches unobserved captures
322
368
  // (recon-snap R18 — staleness is the pinned pruning contract).
323
- if (!f && n !== null && !l.d) return;
324
- applyAdopt(l, t, n, f);
369
+ if (!f && n !== null && !i.d) return;
370
+ applyAdopt(i, t, n, f);
325
371
  }
326
372
 
327
- export { reconcileNextState };
373
+ export { reconcileNextState, sameKey };