@pyreon/reactivity 0.18.0 → 0.20.0

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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"a1cf068b-1","name":"batch.ts"},{"uid":"a1cf068b-3","name":"cell.ts"},{"uid":"a1cf068b-5","name":"scope.ts"},{"uid":"a1cf068b-7","name":"tracking.ts"},{"uid":"a1cf068b-9","name":"effect.ts"},{"uid":"a1cf068b-11","name":"computed.ts"},{"uid":"a1cf068b-13","name":"createSelector.ts"},{"uid":"a1cf068b-15","name":"debug.ts"},{"uid":"a1cf068b-17","name":"signal.ts"},{"uid":"a1cf068b-19","name":"store.ts"},{"uid":"a1cf068b-21","name":"reconcile.ts"},{"uid":"a1cf068b-23","name":"resource.ts"},{"uid":"a1cf068b-25","name":"watch.ts"},{"uid":"a1cf068b-27","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"a1cf068b-1":{"renderedLength":3016,"gzipLength":1167,"brotliLength":0,"metaUid":"a1cf068b-0"},"a1cf068b-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"a1cf068b-2"},"a1cf068b-5":{"renderedLength":3026,"gzipLength":1226,"brotliLength":0,"metaUid":"a1cf068b-4"},"a1cf068b-7":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"a1cf068b-6"},"a1cf068b-9":{"renderedLength":7391,"gzipLength":2397,"brotliLength":0,"metaUid":"a1cf068b-8"},"a1cf068b-11":{"renderedLength":4548,"gzipLength":1464,"brotliLength":0,"metaUid":"a1cf068b-10"},"a1cf068b-13":{"renderedLength":2244,"gzipLength":981,"brotliLength":0,"metaUid":"a1cf068b-12"},"a1cf068b-15":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"a1cf068b-14"},"a1cf068b-17":{"renderedLength":2818,"gzipLength":1191,"brotliLength":0,"metaUid":"a1cf068b-16"},"a1cf068b-19":{"renderedLength":5143,"gzipLength":1835,"brotliLength":0,"metaUid":"a1cf068b-18"},"a1cf068b-21":{"renderedLength":2109,"gzipLength":867,"brotliLength":0,"metaUid":"a1cf068b-20"},"a1cf068b-23":{"renderedLength":1205,"gzipLength":524,"brotliLength":0,"metaUid":"a1cf068b-22"},"a1cf068b-25":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"a1cf068b-24"},"a1cf068b-27":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"a1cf068b-26"}},"nodeMetas":{"a1cf068b-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"a1cf068b-1"},"imported":[],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-10"},{"uid":"a1cf068b-16"},{"uid":"a1cf068b-6"}]},"a1cf068b-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"a1cf068b-3"},"imported":[],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-4":{"id":"/src/scope.ts","moduleParts":{"index.js":"a1cf068b-5"},"imported":[],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-10"},{"uid":"a1cf068b-8"}]},"a1cf068b-6":{"id":"/src/tracking.ts","moduleParts":{"index.js":"a1cf068b-7"},"imported":[{"uid":"a1cf068b-0"}],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-10"},{"uid":"a1cf068b-12"},{"uid":"a1cf068b-8"},{"uid":"a1cf068b-22"},{"uid":"a1cf068b-16"}]},"a1cf068b-8":{"id":"/src/effect.ts","moduleParts":{"index.js":"a1cf068b-9"},"imported":[{"uid":"a1cf068b-4"},{"uid":"a1cf068b-6"}],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-10"},{"uid":"a1cf068b-12"},{"uid":"a1cf068b-22"},{"uid":"a1cf068b-24"}]},"a1cf068b-10":{"id":"/src/computed.ts","moduleParts":{"index.js":"a1cf068b-11"},"imported":[{"uid":"a1cf068b-0"},{"uid":"a1cf068b-8"},{"uid":"a1cf068b-4"},{"uid":"a1cf068b-6"}],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-12":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"a1cf068b-13"},"imported":[{"uid":"a1cf068b-8"},{"uid":"a1cf068b-6"}],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-14":{"id":"/src/debug.ts","moduleParts":{"index.js":"a1cf068b-15"},"imported":[],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-16"}]},"a1cf068b-16":{"id":"/src/signal.ts","moduleParts":{"index.js":"a1cf068b-17"},"imported":[{"uid":"a1cf068b-0"},{"uid":"a1cf068b-14"},{"uid":"a1cf068b-6"}],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-22"},{"uid":"a1cf068b-18"}]},"a1cf068b-18":{"id":"/src/store.ts","moduleParts":{"index.js":"a1cf068b-19"},"imported":[{"uid":"a1cf068b-16"}],"importedBy":[{"uid":"a1cf068b-26"},{"uid":"a1cf068b-20"}]},"a1cf068b-20":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"a1cf068b-21"},"imported":[{"uid":"a1cf068b-18"}],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-22":{"id":"/src/resource.ts","moduleParts":{"index.js":"a1cf068b-23"},"imported":[{"uid":"a1cf068b-8"},{"uid":"a1cf068b-16"},{"uid":"a1cf068b-6"}],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-24":{"id":"/src/watch.ts","moduleParts":{"index.js":"a1cf068b-25"},"imported":[{"uid":"a1cf068b-8"}],"importedBy":[{"uid":"a1cf068b-26"}]},"a1cf068b-26":{"id":"/src/index.ts","moduleParts":{"index.js":"a1cf068b-27"},"imported":[{"uid":"a1cf068b-0"},{"uid":"a1cf068b-2"},{"uid":"a1cf068b-10"},{"uid":"a1cf068b-12"},{"uid":"a1cf068b-14"},{"uid":"a1cf068b-8"},{"uid":"a1cf068b-20"},{"uid":"a1cf068b-22"},{"uid":"a1cf068b-4"},{"uid":"a1cf068b-16"},{"uid":"a1cf068b-18"},{"uid":"a1cf068b-6"},{"uid":"a1cf068b-24"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"c60e3531-1","name":"batch.ts"},{"uid":"c60e3531-3","name":"cell.ts"},{"uid":"c60e3531-5","name":"reactive-devtools.ts"},{"uid":"c60e3531-7","name":"scope.ts"},{"uid":"c60e3531-9","name":"tracking.ts"},{"uid":"c60e3531-11","name":"effect.ts"},{"uid":"c60e3531-13","name":"computed.ts"},{"uid":"c60e3531-15","name":"createSelector.ts"},{"uid":"c60e3531-17","name":"debug.ts"},{"uid":"c60e3531-19","name":"reactive-trace.ts"},{"uid":"c60e3531-21","name":"signal.ts"},{"uid":"c60e3531-23","name":"store.ts"},{"uid":"c60e3531-25","name":"reconcile.ts"},{"uid":"c60e3531-27","name":"resource.ts"},{"uid":"c60e3531-29","name":"watch.ts"},{"uid":"c60e3531-31","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"c60e3531-1":{"renderedLength":3016,"gzipLength":1167,"brotliLength":0,"metaUid":"c60e3531-0"},"c60e3531-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"c60e3531-2"},"c60e3531-5":{"renderedLength":4438,"gzipLength":1940,"brotliLength":0,"metaUid":"c60e3531-4"},"c60e3531-7":{"renderedLength":3026,"gzipLength":1226,"brotliLength":0,"metaUid":"c60e3531-6"},"c60e3531-9":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"c60e3531-8"},"c60e3531-11":{"renderedLength":7605,"gzipLength":2433,"brotliLength":0,"metaUid":"c60e3531-10"},"c60e3531-13":{"renderedLength":4983,"gzipLength":1524,"brotliLength":0,"metaUid":"c60e3531-12"},"c60e3531-15":{"renderedLength":2244,"gzipLength":981,"brotliLength":0,"metaUid":"c60e3531-14"},"c60e3531-17":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"c60e3531-16"},"c60e3531-19":{"renderedLength":2721,"gzipLength":1363,"brotliLength":0,"metaUid":"c60e3531-18"},"c60e3531-21":{"renderedLength":3535,"gzipLength":1513,"brotliLength":0,"metaUid":"c60e3531-20"},"c60e3531-23":{"renderedLength":5232,"gzipLength":1867,"brotliLength":0,"metaUid":"c60e3531-22"},"c60e3531-25":{"renderedLength":2278,"gzipLength":940,"brotliLength":0,"metaUid":"c60e3531-24"},"c60e3531-27":{"renderedLength":1205,"gzipLength":524,"brotliLength":0,"metaUid":"c60e3531-26"},"c60e3531-29":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"c60e3531-28"},"c60e3531-31":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"c60e3531-30"}},"nodeMetas":{"c60e3531-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"c60e3531-1"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}]},"c60e3531-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"c60e3531-3"},"imported":[],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-4":{"id":"/src/reactive-devtools.ts","moduleParts":{"index.js":"c60e3531-5"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"},{"uid":"c60e3531-20"}]},"c60e3531-6":{"id":"/src/scope.ts","moduleParts":{"index.js":"c60e3531-7"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"}]},"c60e3531-8":{"id":"/src/tracking.ts","moduleParts":{"index.js":"c60e3531-9"},"imported":[{"uid":"c60e3531-0"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-10"},{"uid":"c60e3531-26"},{"uid":"c60e3531-20"}]},"c60e3531-10":{"id":"/src/effect.ts","moduleParts":{"index.js":"c60e3531-11"},"imported":[{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-26"},{"uid":"c60e3531-28"}]},"c60e3531-12":{"id":"/src/computed.ts","moduleParts":{"index.js":"c60e3531-13"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-10"},{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-14":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"c60e3531-15"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-16":{"id":"/src/debug.ts","moduleParts":{"index.js":"c60e3531-17"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-18":{"id":"/src/reactive-trace.ts","moduleParts":{"index.js":"c60e3531-19"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-20":{"id":"/src/signal.ts","moduleParts":{"index.js":"c60e3531-21"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-26"},{"uid":"c60e3531-22"}]},"c60e3531-22":{"id":"/src/store.ts","moduleParts":{"index.js":"c60e3531-23"},"imported":[{"uid":"c60e3531-20"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-24"}]},"c60e3531-24":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"c60e3531-25"},"imported":[{"uid":"c60e3531-22"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-26":{"id":"/src/resource.ts","moduleParts":{"index.js":"c60e3531-27"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-28":{"id":"/src/watch.ts","moduleParts":{"index.js":"c60e3531-29"},"imported":[{"uid":"c60e3531-10"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-30":{"id":"/src/index.ts","moduleParts":{"index.js":"c60e3531-31"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-2"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-10"},{"uid":"c60e3531-24"},{"uid":"c60e3531-26"},{"uid":"c60e3531-6"},{"uid":"c60e3531-20"},{"uid":"c60e3531-22"},{"uid":"c60e3531-8"},{"uid":"c60e3531-28"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -149,6 +149,169 @@ function cell(value) {
149
149
  return new Cell(value);
150
150
  }
151
151
 
152
+ //#endregion
153
+ //#region src/reactive-devtools.ts
154
+ let _active = false;
155
+ let _nextId = 1;
156
+ const _byId = /* @__PURE__ */ new Map();
157
+ const _subId = /* @__PURE__ */ new WeakMap();
158
+ /** @internal — finalizer callback; prunes the record when a node is GC'd. */
159
+ function _rdPrune(id) {
160
+ _byId.delete(id);
161
+ }
162
+ const _finalizer = new FinalizationRegistry(_rdPrune);
163
+ const FIRE_CAP = 512;
164
+ let _fireBuf = null;
165
+ let _fireCount = 0;
166
+ const PREVIEW_MAX$1 = 60;
167
+ function preview$1(v) {
168
+ let s;
169
+ try {
170
+ if (v === null) return "null";
171
+ if (v === void 0) return "undefined";
172
+ const t = typeof v;
173
+ if (t === "string") s = JSON.stringify(v);
174
+ else if (t === "number" || t === "boolean" || t === "bigint") s = String(v);
175
+ else if (t === "function") s = `[Function ${v.name || "anonymous"}]`;
176
+ else if (t === "symbol") s = v.toString();
177
+ else if (Array.isArray(v)) s = `Array(${v.length})`;
178
+ else {
179
+ const ctor = v.constructor?.name;
180
+ let keys = [];
181
+ try {
182
+ keys = Object.keys(v).slice(0, 3);
183
+ } catch {
184
+ keys = [];
185
+ }
186
+ s = `${ctor && ctor !== "Object" ? `${ctor} ` : ""}{${keys.join(", ")}${keys.length === 3 ? ", …" : ""}}`;
187
+ }
188
+ } catch {
189
+ s = "[unstringifiable]";
190
+ }
191
+ return s.length > PREVIEW_MAX$1 ? `${s.slice(0, PREVIEW_MAX$1)}…` : s;
192
+ }
193
+ /** Activate the bridge. Idempotent. Called when a devtools client attaches. */
194
+ function activateReactiveDevtools() {
195
+ _active = true;
196
+ }
197
+ /**
198
+ * Deactivate + drop all retained state. Called when the devtools client
199
+ * disconnects so a closed panel leaves zero residue.
200
+ */
201
+ function deactivateReactiveDevtools() {
202
+ _active = false;
203
+ _byId.clear();
204
+ _fireBuf = null;
205
+ _fireCount = 0;
206
+ }
207
+ function isReactiveDevtoolsActive() {
208
+ return _active;
209
+ }
210
+ /**
211
+ * Register a signal/computed/effect node. `host` is the object carrying
212
+ * the `_s` subscriber Set (the signal read fn itself, or a computed's
213
+ * internal host). `sub` is the notify closure (`recompute`/`run`) whose
214
+ * identity appears in upstream `_s` Sets — used to resolve edges.
215
+ *
216
+ * @internal
217
+ */
218
+ function _rdRegister(node, kind, host, sub, label) {
219
+ if (!_active) return void 0;
220
+ const id = _nextId++;
221
+ _byId.set(id, {
222
+ id,
223
+ kind,
224
+ name: label ?? `${kind === "signal" ? "signal" : kind}#${id}`,
225
+ ref: new WeakRef(node),
226
+ hostRef: host ? new WeakRef(host) : null,
227
+ fires: 0,
228
+ lastFire: null
229
+ });
230
+ if (sub) _subId.set(sub, id);
231
+ _finalizer.register(node, id);
232
+ Object.defineProperty(node, "__pxRdId", {
233
+ value: id,
234
+ enumerable: false,
235
+ configurable: true
236
+ });
237
+ return id;
238
+ }
239
+ /**
240
+ * Record that a node fired (signal write / computed recompute / effect
241
+ * run). Bumps counters + appends to the bounded fire buffer.
242
+ *
243
+ * @internal
244
+ */
245
+ function _rdRecordFire(node) {
246
+ if (!_active) return;
247
+ const id = node.__pxRdId;
248
+ if (id === void 0) return;
249
+ const rec = _byId.get(id);
250
+ const ts = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
251
+ if (rec) {
252
+ rec.fires++;
253
+ rec.lastFire = ts;
254
+ }
255
+ if (_fireBuf === null) _fireBuf = new Array(FIRE_CAP);
256
+ _fireBuf[_fireCount % FIRE_CAP] = {
257
+ id,
258
+ ts
259
+ };
260
+ _fireCount++;
261
+ }
262
+ function resolveSubId(sub) {
263
+ const direct = sub.__pxRdId;
264
+ if (direct !== void 0) return direct;
265
+ return _subId.get(sub);
266
+ }
267
+ /**
268
+ * Fresh snapshot of the live reactive graph. Edges are recomputed from
269
+ * each live node's current subscriber Set — always consistent with the
270
+ * framework's real subscription state, no incremental drift.
271
+ */
272
+ function getReactiveGraph() {
273
+ const nodes = [];
274
+ const edges = [];
275
+ for (const rec of _byId.values()) {
276
+ const node = rec.ref.deref();
277
+ if (!node) continue;
278
+ const subs = (rec.hostRef?.deref() ?? null)?._s ?? null;
279
+ const valueStr = rec.kind === "effect" ? "" : preview$1(node._v);
280
+ nodes.push({
281
+ id: rec.id,
282
+ kind: rec.kind,
283
+ name: rec.name,
284
+ value: valueStr,
285
+ subscribers: subs?.size ?? 0,
286
+ fires: rec.fires,
287
+ lastFire: rec.lastFire
288
+ });
289
+ if (subs) for (const cb of subs) {
290
+ const to = resolveSubId(cb);
291
+ if (to !== void 0) edges.push({
292
+ from: rec.id,
293
+ to
294
+ });
295
+ }
296
+ }
297
+ return {
298
+ nodes,
299
+ edges
300
+ };
301
+ }
302
+ /** Bounded recent-fire timeline (oldest → newest). Fresh copy. */
303
+ function getReactiveFires() {
304
+ if (_fireBuf === null || _fireCount === 0) return [];
305
+ if (_fireCount <= FIRE_CAP) return _fireBuf.slice(0, _fireCount);
306
+ const start = _fireCount % FIRE_CAP;
307
+ const out = [];
308
+ for (let i = 0; i < FIRE_CAP; i++) {
309
+ const e = _fireBuf[(start + i) % FIRE_CAP];
310
+ if (e) out.push(e);
311
+ }
312
+ return out;
313
+ }
314
+
152
315
  //#endregion
153
316
  //#region src/scope.ts
154
317
  var EffectScope = class {
@@ -437,7 +600,10 @@ function effect(fn) {
437
600
  };
438
601
  const run = () => {
439
602
  if (disposed) return;
440
- if (process.env.NODE_ENV !== "production") _countSink$2.__pyreon_count__?.("reactivity.effectRun");
603
+ if (process.env.NODE_ENV !== "production") {
604
+ _countSink$2.__pyreon_count__?.("reactivity.effectRun");
605
+ _rdRecordFire(run);
606
+ }
441
607
  runCleanup();
442
608
  const outerCollector = _innerEffectCollector;
443
609
  const myInners = [];
@@ -462,6 +628,7 @@ function effect(fn) {
462
628
  if (!isFirstRun) scope?.notifyEffectRan();
463
629
  isFirstRun = false;
464
630
  };
631
+ if (process.env.NODE_ENV !== "production") _rdRegister(run, "effect", null, run, void 0);
465
632
  run();
466
633
  const e = { dispose() {
467
634
  runCleanup();
@@ -560,6 +727,7 @@ function renderEffect(fn) {
560
727
  }
561
728
  } else renderEffectFullTrack(deps, run, trackedFn);
562
729
  };
730
+ if (process.env.NODE_ENV !== "production") _rdRegister(run, "effect", null, run, void 0);
563
731
  run();
564
732
  const dispose = () => {
565
733
  if (disposed) return;
@@ -615,13 +783,16 @@ function computedLazy(fn) {
615
783
  if (disposed || dirty) return;
616
784
  dirty = true;
617
785
  if (host._s) notifySubscribers(host._s);
618
- if (directFns) for (const f of directFns) f?.();
786
+ if (directFns) for (const f of directFns) f();
619
787
  };
620
788
  _markRecompute(recompute);
621
789
  const read = () => {
622
790
  trackSubscriber(host);
623
791
  if (dirty) {
624
- if (process.env.NODE_ENV !== "production") _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
792
+ if (process.env.NODE_ENV !== "production") {
793
+ _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
794
+ _rdRecordFire(read);
795
+ }
625
796
  try {
626
797
  if (tracked) {
627
798
  setSkipDepsCollection(true);
@@ -649,15 +820,19 @@ function computedLazy(fn) {
649
820
  },
650
821
  enumerable: false
651
822
  });
823
+ Object.defineProperty(read, "_d", {
824
+ get: () => directFns,
825
+ enumerable: false
826
+ });
652
827
  read.direct = (updater) => {
653
- if (!directFns) directFns = [];
654
- const arr = directFns;
655
- const idx = arr.length;
656
- arr.push(updater);
828
+ if (!directFns) directFns = /* @__PURE__ */ new Set();
829
+ const set = directFns;
830
+ set.add(updater);
657
831
  return () => {
658
- arr[idx] = null;
832
+ set.delete(updater);
659
833
  };
660
834
  };
835
+ if (process.env.NODE_ENV !== "production") _rdRegister(read, "derived", host, recompute, void 0);
661
836
  getCurrentScope()?.add({ dispose: read.dispose });
662
837
  return read;
663
838
  }
@@ -677,7 +852,10 @@ function computedWithEquals(fn, equals) {
677
852
  let directFns = null;
678
853
  const recompute = () => {
679
854
  if (disposed) return;
680
- if (process.env.NODE_ENV !== "production") _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
855
+ if (process.env.NODE_ENV !== "production") {
856
+ _countSink$1.__pyreon_count__?.("reactivity.computedRecompute");
857
+ _rdRecordFire(read);
858
+ }
681
859
  cleanupLocalDeps(deps, recompute);
682
860
  try {
683
861
  const next = trackWithLocalDeps(deps, recompute, fn);
@@ -690,7 +868,7 @@ function computedWithEquals(fn, equals) {
690
868
  return;
691
869
  }
692
870
  if (host._s) notifySubscribers(host._s);
693
- if (directFns) for (const f of directFns) f?.();
871
+ if (directFns) for (const f of directFns) f();
694
872
  };
695
873
  _markRecompute(recompute);
696
874
  const read = () => {
@@ -720,15 +898,19 @@ function computedWithEquals(fn, equals) {
720
898
  },
721
899
  enumerable: false
722
900
  });
901
+ Object.defineProperty(read, "_d", {
902
+ get: () => directFns,
903
+ enumerable: false
904
+ });
723
905
  read.direct = (updater) => {
724
- if (!directFns) directFns = [];
725
- const arr = directFns;
726
- const idx = arr.length;
727
- arr.push(updater);
906
+ if (!directFns) directFns = /* @__PURE__ */ new Set();
907
+ const set = directFns;
908
+ set.add(updater);
728
909
  return () => {
729
- arr[idx] = null;
910
+ set.delete(updater);
730
911
  };
731
912
  };
913
+ if (process.env.NODE_ENV !== "production") _rdRegister(read, "derived", host, recompute, void 0);
732
914
  getCurrentScope()?.add({ dispose: read.dispose });
733
915
  return read;
734
916
  }
@@ -909,6 +1091,93 @@ function inspectSignal(sig) {
909
1091
  return info;
910
1092
  }
911
1093
 
1094
+ //#endregion
1095
+ //#region src/reactive-trace.ts
1096
+ /**
1097
+ * Ring-buffer capacity. 50 entries is enough to see the causal chain
1098
+ * for a crash (the writes in the few ticks before the throw) without
1099
+ * the buffer itself becoming a memory concern — each entry is a small
1100
+ * object with two short strings.
1101
+ */
1102
+ const CAP = 50;
1103
+ let _buf = null;
1104
+ let _count = 0;
1105
+ /** Max characters of a value preview before truncation. Keeps the buffer + serialized report small. */
1106
+ const PREVIEW_MAX = 80;
1107
+ /**
1108
+ * Safe, bounded stringification. Never throws (a getter or `toJSON`
1109
+ * that throws must not break the trace recorder), never returns more
1110
+ * than `PREVIEW_MAX` chars + an ellipsis marker.
1111
+ */
1112
+ function preview(v) {
1113
+ let s;
1114
+ try {
1115
+ if (v === null) return "null";
1116
+ if (v === void 0) return "undefined";
1117
+ const t = typeof v;
1118
+ if (t === "string") s = JSON.stringify(v);
1119
+ else if (t === "number" || t === "boolean" || t === "bigint") s = String(v);
1120
+ else if (t === "function") s = `[Function ${v.name || "anonymous"}]`;
1121
+ else if (t === "symbol") s = v.toString();
1122
+ else if (Array.isArray(v)) s = `Array(${v.length})`;
1123
+ else {
1124
+ const ctor = v.constructor?.name;
1125
+ const keys = (() => {
1126
+ try {
1127
+ return Object.keys(v).slice(0, 4);
1128
+ } catch {
1129
+ return [];
1130
+ }
1131
+ })();
1132
+ s = `${ctor && ctor !== "Object" ? ctor + " " : ""}{${keys.join(", ")}${keys.length === 4 ? ", …" : ""}}`;
1133
+ }
1134
+ } catch {
1135
+ s = "[unstringifiable]";
1136
+ }
1137
+ return s.length > PREVIEW_MAX ? s.slice(0, PREVIEW_MAX) + "…" : s;
1138
+ }
1139
+ /**
1140
+ * Record one signal write. Called from `signal.ts` `_set`, already
1141
+ * inside the prod-gate, so this never runs in production builds.
1142
+ *
1143
+ * @internal
1144
+ */
1145
+ function _recordSignalWrite(name, prev, next) {
1146
+ if (_buf === null) _buf = new Array(CAP);
1147
+ _buf[_count % CAP] = {
1148
+ name,
1149
+ prev: preview(prev),
1150
+ next: preview(next),
1151
+ timestamp: typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now()
1152
+ };
1153
+ _count++;
1154
+ }
1155
+ /**
1156
+ * Returns the recorded writes in chronological order (oldest → newest),
1157
+ * at most `CAP` entries. Empty array when nothing has been recorded.
1158
+ * The returned array is a fresh copy — safe to retain / serialize
1159
+ * without pinning the ring buffer.
1160
+ *
1161
+ * Consumed by `@pyreon/core`'s `reportError` to attach `reactiveTrace`
1162
+ * to the error context.
1163
+ */
1164
+ function getReactiveTrace() {
1165
+ if (_buf === null || _count === 0) return [];
1166
+ if (_count <= CAP) return _buf.slice(0, _count);
1167
+ const start = _count % CAP;
1168
+ const out = [];
1169
+ for (let i = 0; i < CAP; i++) {
1170
+ const e = _buf[(start + i) % CAP];
1171
+ if (e) out.push(e);
1172
+ }
1173
+ return out;
1174
+ }
1175
+ /** Clears the buffer. For test isolation; not part of the app-facing API. */
1176
+ function clearReactiveTrace() {
1177
+ _buf = null;
1178
+ _count = 0;
1179
+ }
1180
+
912
1181
  //#endregion
913
1182
  //#region src/signal.ts
914
1183
  const _countSink = globalThis;
@@ -920,6 +1189,10 @@ function _set(newValue) {
920
1189
  if (process.env.NODE_ENV !== "production") _countSink.__pyreon_count__?.("reactivity.signalWrite");
921
1190
  const prev = this._v;
922
1191
  this._v = newValue;
1192
+ if (process.env.NODE_ENV !== "production") {
1193
+ _recordSignalWrite(this.label, prev, newValue);
1194
+ _rdRecordFire(this);
1195
+ }
923
1196
  if (isTracing()) try {
924
1197
  _notifyTraceListeners(this, prev, newValue);
925
1198
  } catch (err) {
@@ -943,28 +1216,33 @@ function _subscribe(listener) {
943
1216
  }
944
1217
  /**
945
1218
  * Register a direct updater — lighter than subscribe().
946
- * Uses a flat array instead of Set. Disposal nulls the slot (no Set.delete overhead).
947
1219
  * Used by compiler-emitted _bindText/_bindDirect for zero-overhead DOM bindings.
1220
+ *
1221
+ * Backed by a `Set` (same as `_s`), NOT a flat array. The array form
1222
+ * disposed by nulling the slot (`arr[idx] = null`) but never compacted —
1223
+ * so a long-lived signal (theme/locale/auth, or a signal read inside
1224
+ * `<For>` rows) bound by churning components accumulated one permanent
1225
+ * dead slot per ever-mounted binding. That is an app-lifetime memory
1226
+ * leak AND degrades the signal-write hot path: `notifyDirect` iterated
1227
+ * O(total-ever-registered), not O(live). A Set bounds growth to the live
1228
+ * set and keeps disposal + iteration O(live); the "Set.delete overhead"
1229
+ * the array form optimised for is negligible against an unbounded array.
948
1230
  */
949
1231
  function _directFn(updater) {
950
- if (!this._d) this._d = [];
951
- const arr = this._d;
952
- const idx = arr.length;
953
- arr.push(updater);
1232
+ if (!this._d) this._d = /* @__PURE__ */ new Set();
1233
+ const set = this._d;
1234
+ set.add(updater);
954
1235
  return () => {
955
- arr[idx] = null;
1236
+ set.delete(updater);
956
1237
  };
957
1238
  }
958
1239
  /**
959
- * Notify direct updaters — flat array iteration, batch-aware.
960
- * Null slots (from disposed updaters) are skipped.
1240
+ * Notify direct updaters — set iteration, batch-aware. Disposed updaters
1241
+ * are already absent from the set (O(1) delete on disposal).
961
1242
  */
962
1243
  function notifyDirect(updaters) {
963
- if (isBatching()) for (let i = 0; i < updaters.length; i++) {
964
- const fn = updaters[i];
965
- if (fn) enqueuePendingNotification(fn);
966
- }
967
- else for (let i = 0; i < updaters.length; i++) updaters[i]?.();
1244
+ if (isBatching()) for (const fn of updaters) enqueuePendingNotification(fn);
1245
+ else for (const fn of updaters) fn();
968
1246
  }
969
1247
  function _debug() {
970
1248
  return {
@@ -997,6 +1275,7 @@ function signal(initialValue, options) {
997
1275
  read.direct = _directFn;
998
1276
  read.debug = _debug;
999
1277
  read.label = options?.name;
1278
+ if (process.env.NODE_ENV !== "production") _rdRegister(read, "signal", read, null, read.label);
1000
1279
  return read;
1001
1280
  }
1002
1281
 
@@ -1111,6 +1390,7 @@ function wrap(raw, shallow) {
1111
1390
  target[key] = value;
1112
1391
  return true;
1113
1392
  }
1393
+ if (key === "__proto__" || key === "constructor" || key === "prototype") return true;
1114
1394
  const prevLength = isArray ? target.length : 0;
1115
1395
  target[key] = value;
1116
1396
  if (isArray && key === "length") {
@@ -1164,6 +1444,11 @@ function wrap(raw, shallow) {
1164
1444
  * Arrays are reconciled by index — elements at the same index are recursively
1165
1445
  * diffed rather than replaced wholesale. Excess old elements are removed.
1166
1446
  */
1447
+ const DANGEROUS_KEYS = new Set([
1448
+ "__proto__",
1449
+ "constructor",
1450
+ "prototype"
1451
+ ]);
1167
1452
  function reconcile(source, target) {
1168
1453
  _reconcileInner(source, target, /* @__PURE__ */ new WeakSet());
1169
1454
  }
@@ -1188,6 +1473,7 @@ function _reconcileObject(source, target, seen) {
1188
1473
  const sourceKeys = Object.keys(source);
1189
1474
  const targetKeys = new Set(Object.keys(target));
1190
1475
  for (const key of sourceKeys) {
1476
+ if (DANGEROUS_KEYS.has(key)) continue;
1191
1477
  const sv = source[key];
1192
1478
  const tv = target[key];
1193
1479
  if (sv !== null && typeof sv === "object" && tv !== null && typeof tv === "object") if (isStore(tv)) _reconcileInner(sv, tv, seen);
@@ -1195,7 +1481,10 @@ function _reconcileObject(source, target, seen) {
1195
1481
  else target[key] = sv;
1196
1482
  targetKeys.delete(key);
1197
1483
  }
1198
- for (const key of targetKeys) delete target[key];
1484
+ for (const key of targetKeys) {
1485
+ if (DANGEROUS_KEYS.has(key)) continue;
1486
+ delete target[key];
1487
+ }
1199
1488
  }
1200
1489
 
1201
1490
  //#endregion
@@ -1307,5 +1596,5 @@ function watch(source, callback, opts = {}) {
1307
1596
  }
1308
1597
 
1309
1598
  //#endregion
1310
- export { Cell, EffectScope, _bind, batch, cell, computed, createResource, createSelector, createStore, effect, effectScope, getCurrentScope, inspectSignal, isStore, markRaw, nextTick, onCleanup, onScopeDispose, onSignalUpdate, reconcile, renderEffect, runUntracked, runUntracked as untrack, setCurrentScope, setErrorHandler, setSnapshotCapture, shallowReactive, signal, watch, why };
1599
+ export { Cell, EffectScope, _bind, activateReactiveDevtools, batch, cell, clearReactiveTrace, computed, createResource, createSelector, createStore, deactivateReactiveDevtools, effect, effectScope, getCurrentScope, getReactiveFires, getReactiveGraph, getReactiveTrace, inspectSignal, isReactiveDevtoolsActive, isStore, markRaw, nextTick, onCleanup, onScopeDispose, onSignalUpdate, reconcile, renderEffect, runUntracked, runUntracked as untrack, setCurrentScope, setErrorHandler, setSnapshotCapture, shallowReactive, signal, watch, why };
1311
1600
  //# sourceMappingURL=index.js.map