rerender-lens 0.2.0 → 0.3.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +146 -5
  3. package/dist/budget-COBu7jBU.d.cts +64 -0
  4. package/dist/budget-LkNjGtRc.d.ts +64 -0
  5. package/dist/cli.cjs +458 -0
  6. package/dist/cli.cjs.map +1 -0
  7. package/dist/cli.d.cts +4 -0
  8. package/dist/cli.d.ts +4 -0
  9. package/dist/cli.js +452 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/devtools-BHGUUo-p.d.ts +244 -0
  12. package/dist/devtools-DmhWiWcN.d.cts +244 -0
  13. package/dist/index.cjs +652 -23
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +40 -336
  16. package/dist/index.d.ts +40 -336
  17. package/dist/index.js +636 -24
  18. package/dist/index.js.map +1 -1
  19. package/dist/notifiers-BGQtWKfX.d.cts +90 -0
  20. package/dist/notifiers-BjjGSHpp.d.ts +90 -0
  21. package/dist/playwright.cjs +196 -0
  22. package/dist/playwright.cjs.map +1 -0
  23. package/dist/playwright.d.cts +35 -0
  24. package/dist/playwright.d.ts +35 -0
  25. package/dist/playwright.js +184 -0
  26. package/dist/playwright.js.map +1 -0
  27. package/dist/relay.cjs +166 -0
  28. package/dist/relay.cjs.map +1 -0
  29. package/dist/relay.d.cts +41 -0
  30. package/dist/relay.d.ts +41 -0
  31. package/dist/relay.js +161 -0
  32. package/dist/relay.js.map +1 -0
  33. package/dist/rerender-lens.iife.js +1717 -0
  34. package/dist/setup.cjs +1510 -0
  35. package/dist/setup.cjs.map +1 -0
  36. package/dist/setup.d.cts +2 -0
  37. package/dist/setup.d.ts +2 -0
  38. package/dist/setup.js +1508 -0
  39. package/dist/setup.js.map +1 -0
  40. package/dist/types-BzUEVkxJ.d.cts +177 -0
  41. package/dist/types-BzUEVkxJ.d.ts +177 -0
  42. package/dist/vite.cjs +113 -0
  43. package/dist/vite.cjs.map +1 -0
  44. package/dist/vite.d.cts +84 -0
  45. package/dist/vite.d.ts +84 -0
  46. package/dist/vite.js +103 -0
  47. package/dist/vite.js.map +1 -0
  48. package/dist/vitest-setup.cjs +1299 -0
  49. package/dist/vitest-setup.cjs.map +1 -0
  50. package/dist/vitest-setup.d.cts +8 -0
  51. package/dist/vitest-setup.d.ts +8 -0
  52. package/dist/vitest-setup.js +1297 -0
  53. package/dist/vitest-setup.js.map +1 -0
  54. package/dist/vitest.cjs +1360 -0
  55. package/dist/vitest.cjs.map +1 -0
  56. package/dist/vitest.d.cts +52 -0
  57. package/dist/vitest.d.ts +52 -0
  58. package/dist/vitest.js +1351 -0
  59. package/dist/vitest.js.map +1 -0
  60. package/package.json +84 -4
  61. package/panel/panel.css +418 -0
  62. package/panel/panel.html +12 -0
  63. package/panel/panel.js +3325 -0
package/dist/index.cjs CHANGED
@@ -147,6 +147,17 @@ function firstDifferentPath(a, b, path, depth = 0) {
147
147
 
148
148
  // src/report.ts
149
149
  var now = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
150
+ var hookLabel = (c) => c.custom && c.custom.length ? `${c.custom.join(" \u203A ")} \u203A ${c.hook}#${c.index}` : `${c.hook} #${c.index}`;
151
+ function storeAdvice(custom) {
152
+ const chain = custom ?? [];
153
+ if (chain.some((n) => /^useSelector$/.test(n)))
154
+ return "the Redux selector returns a new object on every call: return a stored slice, pass shallowEqual as the equality function, or memoize it with createSelector";
155
+ if (chain.some((n) => /^useAppSelector$/.test(n)))
156
+ return "the selector returns a new object on every call: return a stored slice, pass shallowEqual, or memoize it with createSelector";
157
+ if (chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore"))
158
+ return "the store selector returns a new object on every call: select a primitive, or wrap the selector with useShallow (Zustand) / an equality function";
159
+ return "getSnapshot returns a new reference with the same contents: cache the snapshot in the store and return the same object while the data is unchanged";
160
+ }
150
161
  var isGenuine = (c) => c.kind === "different" || c.kind === "added" || c.kind === "removed";
151
162
  var isChildren = (change) => change.path === "children";
152
163
  function fixFor(change) {
@@ -222,10 +233,17 @@ function buildReport(input) {
222
233
  const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : "";
223
234
  const keys = c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys ? `: only ${c.changedKeys.map((k) => `"${k}"`).join(", ")} of ${c.totalKeys} keys changed, yet every consumer re-renders. Split the context or memoize the slices consumers read` : "";
224
235
  reasons.push(`${c.path} changed${where}${keys}.`);
225
- } else if (isGenuine(c)) reasons.push(`${c.hook} #${c.index} changed.`);
236
+ } else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);
226
237
  else if (isStateHook(c))
227
- reasons.push(`${c.hook} #${c.index} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);
228
- else reasons.push(`${c.hook} #${c.index} returned a new reference that is deep-equal to the previous value: memoize the context/store value where it is produced.`);
238
+ reasons.push(`${hookLabel(c)} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);
239
+ else if (c.hook === "useSyncExternalStore") reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: ${storeAdvice(c.custom)}.`);
240
+ else reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: memoize the context/store value where it is produced.`);
241
+ }
242
+ if (input.commitCause === "effect-after-commit") {
243
+ const who = input.updaters && input.updaters.length ? input.updaters.map((u) => `<${u}>`).join(", ") : "a component that rendered in it";
244
+ reasons.push(`this commit was scheduled right after commit #${input.afterCommit ?? "?"} by ${who}: an effect there set state (effect \u2192 setState loop). Derive the value during render or compute it before setting state.`);
245
+ } else if (input.commitCause === "suspense-resolved") {
246
+ reasons.push("this commit shows content that was suspended (a Suspense boundary resolved).");
229
247
  }
230
248
  const report = {
231
249
  component: input.component,
@@ -249,6 +267,13 @@ function buildReport(input) {
249
267
  if (input.treeDuration !== void 0) report.treeDuration = input.treeDuration;
250
268
  if (input.commitPriority) report.commitPriority = input.commitPriority;
251
269
  if (input.source) report.source = input.source;
270
+ if (input.hookState) report.hookState = input.hookState;
271
+ if (input.contexts) report.contexts = input.contexts;
272
+ if (input.state) report.state = input.state;
273
+ if (input.updaters && input.updaters.length) report.updaters = input.updaters;
274
+ if (input.commitCause) report.commitCause = input.commitCause;
275
+ if (input.afterCommit !== void 0) report.afterCommit = input.afterCommit;
276
+ if (input.key !== void 0) report.key = input.key;
252
277
  return report;
253
278
  }
254
279
  function describeTrigger(t) {
@@ -293,6 +318,9 @@ function printReport(report, options) {
293
318
  c.log(`${ch.path} (${KIND_LABEL[ch.kind]})`, { prev: ch.prev, next: ch.next });
294
319
  }
295
320
  c.log("props", report.props);
321
+ if (report.hookState && report.hookState.length) c.log("hooks", Object.fromEntries(report.hookState.map((h) => [h.path, h.value])));
322
+ if (report.contexts && report.contexts.length) c.log("contexts", Object.fromEntries(report.contexts.map((x) => [x.name, x.value])));
323
+ if (report.state) c.log("state", report.state);
296
324
  c.groupEnd();
297
325
  }
298
326
 
@@ -302,7 +330,7 @@ function getState() {
302
330
  const g = globalThis;
303
331
  let s = g[KEY];
304
332
  if (!s) {
305
- s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0 };
333
+ s = { options: {}, enabled: false, printed: /* @__PURE__ */ new Map(), detach: null, nextInstanceId: 1, nextCommitId: 1, warnedOnce: /* @__PURE__ */ new Set(), scheduled: 0, commits: 0, overheadMs: 0, maxCommitMs: 0 };
306
334
  g[KEY] = s;
307
335
  }
308
336
  return s;
@@ -337,6 +365,171 @@ function dispatch(report, override) {
337
365
  }
338
366
  }
339
367
 
368
+ // src/hookNames.ts
369
+ var noop = () => {
370
+ };
371
+ function customHooksFromStack(stack, componentName, marker) {
372
+ const names = [];
373
+ let past = false;
374
+ for (const line of stack.split("\n")) {
375
+ const m = /^\s*at\s+(?:async\s+)?([^\s(]+)|^([^@\s]+)@/.exec(line);
376
+ const name = m ? (m[1] || m[2] || "").replace(/^Object\./, "").replace(/^Proxy\./, "") : "";
377
+ if (!name) continue;
378
+ if (!past) {
379
+ if (name.includes(marker)) past = true;
380
+ continue;
381
+ }
382
+ if (name === componentName || name.endsWith("." + componentName)) break;
383
+ if (/^use[A-Z0-9_]/.test(name)) names.push(name);
384
+ if (names.length > 8) break;
385
+ }
386
+ return names;
387
+ }
388
+ var cache = /* @__PURE__ */ new WeakMap();
389
+ function makeDispatcher(fiber, componentName, out, marker) {
390
+ let node = fiber.memoizedState;
391
+ let index = 0;
392
+ const take = (count) => {
393
+ const first = node;
394
+ for (let i = 0; i < count && node; i++) node = node.next;
395
+ index += count;
396
+ return first;
397
+ };
398
+ const record = (primitive, count) => {
399
+ const stack = new Error().stack || "";
400
+ const chain = customHooksFromStack(stack, componentName, marker);
401
+ const start = index;
402
+ const first = take(count);
403
+ if (chain.length) for (let i = 0; i < count; i++) out.set(start + i, chain);
404
+ return first;
405
+ };
406
+ const memoized = (n) => n ? n.memoizedState : void 0;
407
+ const dispatcher = {};
408
+ const define = (name, impl) => {
409
+ const fn = { [marker + name]: (...a) => impl(...a) }[marker + name];
410
+ dispatcher[name] = fn;
411
+ };
412
+ define("useState", () => {
413
+ const n = record("useState", 1);
414
+ return [memoized(n), noop];
415
+ });
416
+ define("useReducer", () => {
417
+ const n = record("useReducer", 1);
418
+ return [memoized(n), noop];
419
+ });
420
+ define("useRef", (init2) => {
421
+ const n = record("useRef", 1);
422
+ return n ? n.memoizedState : { current: init2 };
423
+ });
424
+ define("useMemo", (fn) => {
425
+ const n = record("useMemo", 1);
426
+ const s = memoized(n);
427
+ return Array.isArray(s) ? s[0] : typeof fn === "function" ? fn() : void 0;
428
+ });
429
+ define("useCallback", (fn) => {
430
+ const n = record("useCallback", 1);
431
+ const s = memoized(n);
432
+ return Array.isArray(s) ? s[0] : fn;
433
+ });
434
+ for (const effect of ["useEffect", "useLayoutEffect", "useInsertionEffect", "useImperativeHandle"]) define(effect, () => void record(effect, 1));
435
+ define("useContext", (ctx) => {
436
+ record("useContext", 0);
437
+ const c = ctx;
438
+ return c ? c._currentValue : void 0;
439
+ });
440
+ define("useDebugValue", () => void record("useDebugValue", 0));
441
+ define("useSyncExternalStore", (_subscribe, getSnapshot) => {
442
+ const n = record("useSyncExternalStore", 2);
443
+ return n ? n.memoizedState : typeof getSnapshot === "function" ? getSnapshot() : void 0;
444
+ });
445
+ define("useTransition", () => {
446
+ record("useTransition", 2);
447
+ return [false, noop];
448
+ });
449
+ define("useDeferredValue", (v) => {
450
+ const n = record("useDeferredValue", 1);
451
+ return n ? n.memoizedState : v;
452
+ });
453
+ define("useId", () => {
454
+ const n = record("useId", 1);
455
+ return n ? n.memoizedState : "id";
456
+ });
457
+ define("useOptimistic", (v) => {
458
+ const n = record("useOptimistic", 1);
459
+ return [n ? n.memoizedState : v, noop];
460
+ });
461
+ for (const action of ["useActionState", "useFormState"]) {
462
+ define(action, (_fn, init2) => {
463
+ const n = record(action, 3);
464
+ return [n ? n.memoizedState : init2, noop, false];
465
+ });
466
+ }
467
+ define("useEffectEvent", (fn) => {
468
+ record("useEffectEvent", 1);
469
+ return fn;
470
+ });
471
+ define("useCacheRefresh", () => {
472
+ record("useCacheRefresh", 1);
473
+ return noop;
474
+ });
475
+ define("useHostTransitionStatus", () => ({ pending: false, data: null, method: null, action: null }));
476
+ define("useMemoCache", (size) => new Array(Number(size) || 0).fill(/* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")));
477
+ define("use", (usable) => {
478
+ record("use", 0);
479
+ const u = usable;
480
+ if (u && typeof u.then === "function") {
481
+ if (u.status === "fulfilled") return u.value;
482
+ throw new Error("rerender-lens: cannot replay use() on a pending promise");
483
+ }
484
+ return u ? u._currentValue : void 0;
485
+ });
486
+ define("readContext", (ctx) => ctx?._currentValue);
487
+ return { dispatcher, nodesConsumed: () => index };
488
+ }
489
+ function renderFunctionOf(type) {
490
+ let t = type;
491
+ while (t && typeof t === "object" && t.type) t = t.type;
492
+ if (t && typeof t === "object" && typeof t.render === "function") return { fn: t.render, name: t.render.name || t.displayName || "Component" };
493
+ if (typeof t === "function") return { fn: t, name: t.name || "Component" };
494
+ return { fn: null, name: "Component" };
495
+ }
496
+ function resolveHookNames(fiber, ref) {
497
+ const type = fiber.type;
498
+ if (!type || !ref) return /* @__PURE__ */ new Map();
499
+ const cached = cache.get(type);
500
+ if (cached) return cached.byNode;
501
+ const result = { byNode: /* @__PURE__ */ new Map(), ok: false };
502
+ cache.set(type, result);
503
+ const { fn, name } = renderFunctionOf(type);
504
+ if (!fn) return result.byNode;
505
+ const useH = "H" in ref;
506
+ const marker = "__rl_";
507
+ const previous = useH ? ref.H : ref.current;
508
+ const { dispatcher, nodesConsumed } = makeDispatcher(fiber, name, result.byNode, marker);
509
+ const proxy = new Proxy(dispatcher, {
510
+ get(target, key) {
511
+ if (key in target) return target[key];
512
+ return () => void 0;
513
+ }
514
+ });
515
+ let total = 0;
516
+ for (let n = fiber.memoizedState; n && typeof n === "object" && "next" in n; n = n.next) total++;
517
+ try {
518
+ if (useH) ref.H = proxy;
519
+ else ref.current = proxy;
520
+ const secondArg = fiber.tag === 11 ? fiber.stateNode?.ref ?? null : void 0;
521
+ fn(fiber.memoizedProps ?? {}, secondArg);
522
+ result.ok = nodesConsumed() === total;
523
+ if (!result.ok) result.byNode.clear();
524
+ } catch {
525
+ result.byNode.clear();
526
+ } finally {
527
+ if (useH) ref.H = previous;
528
+ else ref.current = previous;
529
+ }
530
+ return result.byNode;
531
+ }
532
+
340
533
  // src/fiber.ts
341
534
  var FunctionComponent = 0;
342
535
  var ClassComponent = 1;
@@ -344,10 +537,12 @@ var HostRoot = 3;
344
537
  var HostComponent = 5;
345
538
  var ContextProvider = 10;
346
539
  var ForwardRef = 11;
540
+ var SuspenseComponent = 13;
347
541
  var MemoComponent = 14;
348
542
  var SimpleMemoComponent = 15;
349
543
  var PerformedWork = 1;
350
544
  var HOOK = "__REACT_DEVTOOLS_GLOBAL_HOOK__";
545
+ var nowMs = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
351
546
  function ensureDevtoolsHook() {
352
547
  const g = globalThis;
353
548
  let hook = g[HOOK];
@@ -376,7 +571,9 @@ function ensureDevtoolsHook() {
376
571
  return hook;
377
572
  }
378
573
  var capturedRenderers = /* @__PURE__ */ new Map();
574
+ var dispatcherRefs = /* @__PURE__ */ new Map();
379
575
  var INJECT_WRAPPED = /* @__PURE__ */ Symbol.for("rerender-lens.injectWrapped");
576
+ var ATTACHED = /* @__PURE__ */ Symbol.for("rerender-lens.attached");
380
577
  function pickRenderer(r) {
381
578
  const info = r ?? {};
382
579
  return { version: info.version, bundleType: info.bundleType, rendererPackageName: info.rendererPackageName };
@@ -387,20 +584,45 @@ function wrapInject(hook) {
387
584
  const original = hook.inject;
388
585
  hook.inject = function(renderer) {
389
586
  const id = original.call(this, renderer);
390
- capturedRenderers.set(typeof id === "number" ? id : capturedRenderers.size + 1, pickRenderer(renderer));
587
+ const key = typeof id === "number" ? id : capturedRenderers.size + 1;
588
+ capturedRenderers.set(key, pickRenderer(renderer));
589
+ const ref = renderer?.currentDispatcherRef;
590
+ if (ref && typeof ref === "object") dispatcherRefs.set(key, ref);
391
591
  return id;
392
592
  };
393
593
  h[INJECT_WRAPPED] = true;
394
594
  }
595
+ function getDispatcherRef() {
596
+ for (const ref of dispatcherRefs.values()) return ref;
597
+ const g = globalThis;
598
+ const hook = g[HOOK];
599
+ let found = null;
600
+ if (hook && hook.renderers && typeof hook.renderers.forEach === "function") {
601
+ hook.renderers.forEach((r) => {
602
+ const ref = r?.currentDispatcherRef;
603
+ if (!found && ref && typeof ref === "object") found = ref;
604
+ });
605
+ }
606
+ return found;
607
+ }
395
608
  function attach() {
396
609
  const hook = ensureDevtoolsHook();
397
610
  wrapInject(hook);
611
+ if (hook[ATTACHED]) return () => {
612
+ };
613
+ hook[ATTACHED] = true;
398
614
  const previous = hook.onCommitFiberRoot;
399
615
  const patched = function(id, root, priority, didError) {
616
+ const started = nowMs();
400
617
  try {
401
618
  onCommit(root, priorityLabel(priority));
402
619
  } catch (err) {
403
620
  warnOnce("commit", `failed to inspect a commit: ${String(err)}`);
621
+ } finally {
622
+ const spent = nowMs() - started;
623
+ const s = getState();
624
+ s.overheadMs += spent;
625
+ if (spent > s.maxCommitMs) s.maxCommitMs = spent;
404
626
  }
405
627
  if (typeof previous === "function") return previous.call(this, id, root, priority, didError);
406
628
  };
@@ -412,6 +634,7 @@ function attach() {
412
634
  };
413
635
  hook.onScheduleFiberRoot = patchedSchedule;
414
636
  return () => {
637
+ hook[ATTACHED] = false;
415
638
  if (hook.onCommitFiberRoot === patched) hook.onCommitFiberRoot = previous;
416
639
  if (hook.onScheduleFiberRoot === patchedSchedule) hook.onScheduleFiberRoot = previousSchedule;
417
640
  };
@@ -581,7 +804,7 @@ function ownerName(fiber) {
581
804
  }
582
805
  var isStateNode = (n) => !!n.queue && typeof n.queue.lastRenderedReducer === "function";
583
806
  var isStoreNode = (n) => !!n.queue && typeof n.queue.getSnapshot === "function";
584
- function hookLabel(node) {
807
+ function hookLabel2(node) {
585
808
  if (isStoreNode(node)) return "useSyncExternalStore";
586
809
  if (isStateNode(node)) {
587
810
  const r = node.queue.lastRenderedReducer;
@@ -590,20 +813,43 @@ function hookLabel(node) {
590
813
  return "state";
591
814
  }
592
815
  var NODELESS_HOOKS = /* @__PURE__ */ new Set(["useContext", "useDebugValue", "use"]);
816
+ function hookLabelsFor(fiber, first) {
817
+ const types = fiber._debugHookTypes?.filter((t) => !NODELESS_HOOKS.has(t));
818
+ let nodeCount = 0;
819
+ for (let n = first; n; n = n.next) nodeCount++;
820
+ return types && types.length === nodeCount ? types : null;
821
+ }
822
+ var isHookList = (v) => !!v && typeof v === "object" && "next" in v;
823
+ function snapshotHooks(fiber) {
824
+ const out = [];
825
+ if (fiber.tag === ClassComponent) return out;
826
+ const first = fiber.memoizedState;
827
+ if (!isHookList(first)) return out;
828
+ const labels = hookLabelsFor(fiber, first);
829
+ let i = 0;
830
+ for (let n = first; n; n = n.next, i++) {
831
+ if (!isStateNode(n) && !isStoreNode(n)) continue;
832
+ const hook = labels?.[i] ?? hookLabel2(n);
833
+ out.push({ path: `${hook}#${i}`, hook, index: i, value: n.memoizedState });
834
+ }
835
+ return out;
836
+ }
837
+ function snapshotContexts(fiber) {
838
+ const out = [];
839
+ for (let d = fiber.dependencies?.firstContext ?? null; d; d = d.next) out.push({ name: d.context.displayName ?? "Context", value: d.memoizedValue });
840
+ return out;
841
+ }
593
842
  function diffHooks(fiber, alt) {
594
843
  const out = [];
595
844
  if (fiber.tag === ClassComponent) return out;
596
845
  let a = alt.memoizedState;
597
846
  let b = fiber.memoizedState;
598
847
  if (!a || !b || typeof b !== "object" || !("next" in b)) return out;
599
- const types = fiber._debugHookTypes?.filter((t) => !NODELESS_HOOKS.has(t));
600
- let nodeCount = 0;
601
- for (let n = b; n; n = n.next) nodeCount++;
602
- const labels = types && types.length === nodeCount ? types : null;
848
+ const labels = hookLabelsFor(fiber, b);
603
849
  let i = 0;
604
850
  while (a && b) {
605
851
  if ((isStateNode(b) || isStoreNode(b)) && !Object.is(a.memoizedState, b.memoizedState)) {
606
- const hook = labels?.[i] ?? hookLabel(b);
852
+ const hook = labels?.[i] ?? hookLabel2(b);
607
853
  out.push({
608
854
  path: `${hook}#${i}`,
609
855
  hook,
@@ -692,14 +938,14 @@ function analyze(fiber, alt, trackHooks) {
692
938
  const trigger = causes.length === 0 ? "parent" : causes.length === 1 ? causes[0] : "mixed";
693
939
  return { propChanges, stateChanges, hookChanges, trigger };
694
940
  }
695
- function nearestRenderedAncestor(fiber, cache, trackHooks) {
941
+ function nearestRenderedAncestor(fiber, cache2, trackHooks) {
696
942
  let f = fiber.return;
697
943
  while (f && f.tag !== HostRoot) {
698
944
  if (isComponentTag(f.tag) && f.alternate && didRender(f)) {
699
- let info = cache.get(f);
945
+ let info = cache2.get(f);
700
946
  if (info === void 0) {
701
947
  info = { name: fiberName(f), trigger: analyze(f, f.alternate, trackHooks).trigger };
702
- cache.set(f, info);
948
+ cache2.set(f, info);
703
949
  }
704
950
  return info;
705
951
  }
@@ -707,6 +953,21 @@ function nearestRenderedAncestor(fiber, cache, trackHooks) {
707
953
  }
708
954
  return null;
709
955
  }
956
+ function updatersOf(root) {
957
+ const set = root.memoizedUpdaters;
958
+ if (!set || typeof set.forEach !== "function") return [];
959
+ const names = [];
960
+ set.forEach((f) => {
961
+ const comp = nearestComponent(f);
962
+ if (comp) {
963
+ const name = fiberName(comp);
964
+ if (!names.includes(name)) names.push(name);
965
+ }
966
+ });
967
+ return names;
968
+ }
969
+ var lastCommit = null;
970
+ var EFFECT_LOOP_WINDOW_MS = 50;
710
971
  function onCommit(root, commitPriority) {
711
972
  const s = getState();
712
973
  if (!s.enabled) return;
@@ -714,28 +975,53 @@ function onCommit(root, commitPriority) {
714
975
  const o = s.options;
715
976
  const trackHooks = o.trackHooks !== false;
716
977
  const rendered = [];
978
+ const renderedNames = /* @__PURE__ */ new Set();
717
979
  let hot = false;
980
+ let suspenseResolved = false;
718
981
  const stack = [root.current];
719
982
  while (stack.length) {
720
983
  const fiber = stack.pop();
721
984
  const alt = fiber.alternate;
722
985
  if (alt && isComponentTag(fiber.tag) && didRender(fiber)) {
723
986
  if (isHotSwapped(fiber, alt)) hot = true;
987
+ renderedNames.add(fiberName(fiber));
724
988
  if (shouldTrack(fiberType(fiber), o)) rendered.push(fiber);
725
989
  }
990
+ if (fiber.tag === SuspenseComponent && alt && alt.memoizedState !== null && fiber.memoizedState === null) suspenseResolved = true;
726
991
  if (!alt || fiber.child !== alt.child) {
727
992
  for (let c = fiber.child; c; c = c.sibling) stack.push(c);
728
993
  }
729
994
  }
730
995
  if (hot && o.ignoreHotReload !== false) return;
731
996
  rendered.reverse();
732
- if (rendered.length === 0) return;
997
+ const at = nowMs();
998
+ const updaters = updatersOf(root);
999
+ let commitCause;
1000
+ let afterCommit;
1001
+ if (lastCommit && at - lastCommit.at < EFFECT_LOOP_WINDOW_MS && updaters.some((u) => lastCommit.rendered.has(u))) {
1002
+ commitCause = "effect-after-commit";
1003
+ afterCommit = lastCommit.id;
1004
+ } else if (suspenseResolved) commitCause = "suspense-resolved";
1005
+ if (rendered.length === 0) {
1006
+ lastCommit = { id: s.nextCommitId, at, rendered: renderedNames };
1007
+ return;
1008
+ }
733
1009
  const commitId = s.nextCommitId++;
1010
+ lastCommit = { id: commitId, at, rendered: renderedNames };
1011
+ const dispatcherRef = o.resolveHookNames ? getDispatcherRef() : null;
734
1012
  const parentCache = /* @__PURE__ */ new Map();
735
1013
  for (const fiber of rendered) {
736
1014
  const alt = fiber.alternate;
737
1015
  const a = analyze(fiber, alt, trackHooks);
738
1016
  const durations = durationsOf(fiber);
1017
+ let hookState = o.includeState !== false && trackHooks ? snapshotHooks(fiber) : void 0;
1018
+ if (dispatcherRef && fiber.tag !== ClassComponent && (a.hookChanges.length || hookState && hookState.length)) {
1019
+ const names = resolveHookNames(fiber, dispatcherRef);
1020
+ if (names.size) {
1021
+ for (const c of a.hookChanges) if (c.hook !== "useContext" && names.has(c.index)) c.custom = names.get(c.index);
1022
+ if (hookState) hookState = hookState.map((h) => names.has(h.index) ? { ...h, custom: names.get(h.index) } : h);
1023
+ }
1024
+ }
739
1025
  const report = buildReport({
740
1026
  component: fiberName(fiber),
741
1027
  instanceId: instanceId(fiber),
@@ -745,6 +1031,15 @@ function onCommit(root, commitPriority) {
745
1031
  propChanges: a.propChanges,
746
1032
  stateChanges: a.stateChanges,
747
1033
  hookChanges: a.hookChanges,
1034
+ ...o.includeState !== false ? {
1035
+ hookState,
1036
+ contexts: trackHooks ? snapshotContexts(fiber) : void 0,
1037
+ state: fiber.tag === ClassComponent && fiber.memoizedState && typeof fiber.memoizedState === "object" ? fiber.memoizedState : void 0
1038
+ } : {},
1039
+ updaters,
1040
+ commitCause,
1041
+ afterCommit,
1042
+ key: fiber.key === null || fiber.key === void 0 ? null : String(fiber.key),
748
1043
  parent: a.trigger === "parent" ? nearestRenderedAncestor(fiber, parentCache, trackHooks) : null,
749
1044
  owner: ownerName(fiber),
750
1045
  path: componentPath(fiber),
@@ -857,6 +1152,201 @@ function useWhyRerender(name, values, options) {
857
1152
  });
858
1153
  }
859
1154
 
1155
+ // src/fixes.ts
1156
+ var AVOIDABLE = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
1157
+ var rootOf = (path) => path.split(/[.[]/)[0] || path;
1158
+ function fixesFor(r) {
1159
+ const out = [];
1160
+ const owner = r.owner || r.parent && r.parent.name || "?";
1161
+ const changes = [...r.propChanges, ...r.stateChanges, ...r.hookChanges];
1162
+ if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
1163
+ out.push({
1164
+ kind: "memo",
1165
+ owner: r.component,
1166
+ target: r.component,
1167
+ prop: null,
1168
+ label: `Wrap <${r.component}> in React.memo`,
1169
+ detail: changes.length === 0 ? `<${r.component}> re-rendered with identical props because <${r.parent && r.parent.name || "its parent"}> re-rendered.` : `<${r.component}> is not memoized: fixing its props alone will not stop the re-render.`
1170
+ });
1171
+ }
1172
+ for (const c of r.propChanges) {
1173
+ if (!AVOIDABLE.has(c.kind)) continue;
1174
+ const root = rootOf(c.path);
1175
+ if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
1176
+ out.push({ kind: "children", owner, target: r.component, prop: "children", label: `memoize children of <${r.component}> in <${owner}>`, detail: `<${owner}> re-creates the children of <${r.component}> on every render.` });
1177
+ } else if (c.kind === "function") {
1178
+ out.push({ kind: "useCallback", owner, target: r.component, prop: root, label: `useCallback(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new function on every render of <${owner}>.` });
1179
+ } else if (c.kind === "element") {
1180
+ out.push({ kind: "useMemoElement", owner, target: r.component, prop: root, label: `memoize element prop ${root} in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.` });
1181
+ } else {
1182
+ out.push({ kind: "useMemo", owner, target: r.component, prop: root, label: `useMemo(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new ${Array.isArray(c.next) ? "array" : "object"} with the same contents on every render of <${owner}>.` });
1183
+ }
1184
+ }
1185
+ for (const c of [...r.stateChanges, ...r.hookChanges]) {
1186
+ const isContext = c.hook === "useContext";
1187
+ const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
1188
+ const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
1189
+ if (isContext && c.kind === "different" && c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {
1190
+ out.push({
1191
+ kind: "splitContext",
1192
+ owner: providerOwner || `${ctxName}.Provider`,
1193
+ target: r.component,
1194
+ prop: ctxName,
1195
+ label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ""}: only ${c.changedKeys.join(", ")} changed`,
1196
+ detail: `${c.changedKeys.length} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer re-rendered.`
1197
+ });
1198
+ continue;
1199
+ }
1200
+ if (!AVOIDABLE.has(c.kind)) continue;
1201
+ if (isContext) {
1202
+ out.push({ kind: "contextValue", owner: providerOwner || `${ctxName}.Provider`, target: r.component, prop: ctxName, label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`, detail: `${ctxName} produced a new value that is deep-equal to the previous one.` });
1203
+ } else if (c.hook === "useSyncExternalStore") {
1204
+ const chain = c.custom || [];
1205
+ const redux = chain.some((n) => /^use(App)?Selector$/.test(n));
1206
+ const zustand = !redux && chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore");
1207
+ out.push({ kind: "storeSnapshot", owner: r.component, target: r.component, prop: c.path, label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`, detail: storeAdvice(c.custom) });
1208
+ } else {
1209
+ out.push({ kind: "bailout", owner: r.component, target: r.component, prop: c.path, label: `bail out before setting ${c.path} in <${r.component}>`, detail: `${c.path} was set to a value deep-equal to the current one (new reference).` });
1210
+ }
1211
+ }
1212
+ return out;
1213
+ }
1214
+ var fixKey = (f) => `${f.kind}|${f.owner}|${f.prop || f.target}`;
1215
+ function rankFixes(reports) {
1216
+ const byKey = /* @__PURE__ */ new Map();
1217
+ for (const r of reports) {
1218
+ if (!r.avoidable) continue;
1219
+ for (const f of fixesFor(r)) {
1220
+ const key = fixKey(f);
1221
+ let agg = byKey.get(key);
1222
+ if (!agg) {
1223
+ agg = { ...f, key, count: 0, components: {} };
1224
+ byKey.set(key, agg);
1225
+ }
1226
+ agg.count++;
1227
+ agg.components[r.component] = (agg.components[r.component] || 0) + 1;
1228
+ }
1229
+ }
1230
+ return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
1231
+ }
1232
+ function formatFixes(reports, limit = 10) {
1233
+ const ranked = rankFixes(reports);
1234
+ if (!ranked.length) return "No avoidable re-renders.";
1235
+ const lines = ranked.slice(0, limit).map((f, i) => `${String(i + 1).padStart(2)}. ${f.label} (removes ${f.count}: ${Object.entries(f.components).map(([c, n]) => `<${c}>${n > 1 ? " x" + n : ""}`).join(", ")})`);
1236
+ if (ranked.length > limit) lines.push(` \u2026 ${ranked.length - limit} more`);
1237
+ return lines.join("\n");
1238
+ }
1239
+
1240
+ // src/sessions.ts
1241
+ function summarizeReports(reports, meta = {}) {
1242
+ var _a;
1243
+ const byComponent = {};
1244
+ let avoidable = 0;
1245
+ let wasted = 0;
1246
+ for (const r of reports) {
1247
+ const c = byComponent[_a = r.component] || (byComponent[_a] = { total: 0, avoidable: 0, wasted: 0 });
1248
+ c.total++;
1249
+ if (r.avoidable) {
1250
+ c.avoidable++;
1251
+ avoidable++;
1252
+ if (typeof r.selfDuration === "number") {
1253
+ c.wasted += r.selfDuration;
1254
+ wasted += r.selfDuration;
1255
+ }
1256
+ }
1257
+ }
1258
+ return {
1259
+ id: meta.id ?? `s${Date.now().toString(36)}`,
1260
+ name: meta.name ?? "session",
1261
+ startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),
1262
+ endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),
1263
+ total: reports.length,
1264
+ avoidable,
1265
+ wasted,
1266
+ byComponent,
1267
+ fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count }))
1268
+ };
1269
+ }
1270
+ function compareSummaries(before, after) {
1271
+ const names = /* @__PURE__ */ new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);
1272
+ const rows = [];
1273
+ for (const component of names) {
1274
+ const b = before.byComponent[component]?.avoidable || 0;
1275
+ const a = after.byComponent[component]?.avoidable || 0;
1276
+ if (b || a) rows.push({ component, before: b, after: a, delta: a - b });
1277
+ }
1278
+ rows.sort((x, y) => x.delta - y.delta || y.before - x.before || x.component.localeCompare(y.component));
1279
+ const afterKeys = new Set(after.fixes.map((f) => f.key));
1280
+ const beforeKeys = new Set(before.fixes.map((f) => f.key));
1281
+ return {
1282
+ before,
1283
+ after,
1284
+ rows,
1285
+ total: { before: before.total, after: after.total, delta: after.total - before.total },
1286
+ avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },
1287
+ wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },
1288
+ resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),
1289
+ newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),
1290
+ regressions: rows.filter((r) => r.delta > 0)
1291
+ };
1292
+ }
1293
+ function formatComparison(c) {
1294
+ const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));
1295
+ const line = (label, b, a, d) => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;
1296
+ const delta = (n, suffix = "") => n === 0 ? "\xB10" : `${n > 0 ? "+" : ""}${Number.isInteger(n) ? n : n.toFixed(1)}${suffix}`;
1297
+ const out = [line("avoidable", c.before.name.slice(0, 8), c.after.name.slice(0, 8), "\u0394"), line("all components", c.avoidable.before, c.avoidable.after, delta(c.avoidable.delta))];
1298
+ if (c.wasted.before || c.wasted.after) out.push(line("wasted ms", c.wasted.before.toFixed(1), c.wasted.after.toFixed(1), delta(Number(c.wasted.delta.toFixed(1)))));
1299
+ for (const r of c.rows) out.push(line(`<${r.component}>`, r.before, r.after, delta(r.delta)));
1300
+ if (c.resolvedFixes.length) out.push("", "no longer needed:", ...c.resolvedFixes.map((f) => ` - ${f.label}`));
1301
+ if (c.newFixes.length) out.push("", "new:", ...c.newFixes.map((f) => ` - ${f.label}`));
1302
+ return out.join("\n");
1303
+ }
1304
+ function parseExport(data) {
1305
+ if (Array.isArray(data)) return { reports: data, sessions: [], summary: null };
1306
+ if (data && typeof data === "object") {
1307
+ const d = data;
1308
+ if (Array.isArray(d.reports)) return { reports: d.reports, sessions: Array.isArray(d.sessions) ? d.sessions : [], summary: null };
1309
+ if (d.byComponent && typeof d.byComponent === "object") return { reports: [], sessions: [], summary: data };
1310
+ }
1311
+ throw new Error("not a rerender-lens export or session summary");
1312
+ }
1313
+
1314
+ // src/budget.ts
1315
+ function avoidableCounts(reports) {
1316
+ const counts2 = {};
1317
+ for (const r of reports) if (r.avoidable) counts2[r.component] = (counts2[r.component] || 0) + 1;
1318
+ return counts2;
1319
+ }
1320
+ function checkBudget(reports, budget) {
1321
+ const b = typeof budget === "number" ? { "*": budget } : budget;
1322
+ const fallback = typeof b["*"] === "number" ? b["*"] : 0;
1323
+ const counts2 = avoidableCounts(reports);
1324
+ const violations = [];
1325
+ const slack = [];
1326
+ const names = /* @__PURE__ */ new Set([...Object.keys(counts2), ...Object.keys(b).filter((k) => k !== "*")]);
1327
+ for (const component of names) {
1328
+ const allowed = typeof b[component] === "number" ? b[component] : fallback;
1329
+ const avoidable = counts2[component] || 0;
1330
+ if (avoidable > allowed) violations.push({ component, avoidable, allowed });
1331
+ else if (avoidable < allowed) slack.push({ component, avoidable, allowed });
1332
+ }
1333
+ violations.sort((x, y) => y.avoidable - y.allowed - (x.avoidable - x.allowed));
1334
+ return { ok: violations.length === 0, violations, slack, counts: counts2 };
1335
+ }
1336
+ function toBudget(reports) {
1337
+ return { "*": 0, ...avoidableCounts(reports) };
1338
+ }
1339
+ function assertWithinBudget(reports, budget) {
1340
+ const result = checkBudget(reports, budget);
1341
+ if (result.ok) return result;
1342
+ const lines = result.violations.map((v) => ` <${v.component}>: ${v.avoidable} avoidable re-render${v.avoidable === 1 ? "" : "s"} (budget ${v.allowed})`);
1343
+ throw new Error(`Re-render budget exceeded:
1344
+ ${lines.join("\n")}
1345
+
1346
+ Fixes, most impact first:
1347
+ ${formatFixes(reports)}`);
1348
+ }
1349
+
860
1350
  // src/notifiers.ts
861
1351
  function createCollector() {
862
1352
  const reports = [];
@@ -877,8 +1367,14 @@ function createCollector() {
877
1367
  const lines = bad.map((r) => ` <${r.component}> render #${r.renderCount}:
878
1368
  ${r.reasons.map((x) => ` - ${x}`).join("\n")}`);
879
1369
  throw new Error(`${bad.length} avoidable re-render${bad.length === 1 ? "" : "s"} detected:
880
- ${lines.join("\n")}`);
881
- }
1370
+ ${lines.join("\n")}
1371
+
1372
+ Fixes, most impact first:
1373
+ ${formatFixes(reports)}`);
1374
+ },
1375
+ fixes: () => rankFixes(reports),
1376
+ summary: (name) => summarizeReports(reports, name ? { name } : {}),
1377
+ assertWithinBudget: (budget) => assertWithinBudget(reports, budget)
882
1378
  };
883
1379
  }
884
1380
  function combineNotifiers(...notifiers) {
@@ -976,11 +1472,12 @@ function flash(target, duration = 500) {
976
1472
  }
977
1473
 
978
1474
  // src/version.ts
979
- var VERSION = "0.2.0" ;
1475
+ var VERSION = "0.3.0" ;
980
1476
 
981
1477
  // src/devtools.ts
982
1478
  var DEVTOOLS_MARKER = "__rerenderLens";
983
1479
  var PROTOCOL_VERSION = 2;
1480
+ var DEFAULT_CHANNEL = "rerender-lens";
984
1481
  function serialize(value, maxDepth = 6, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
985
1482
  if (value === null || value === void 0) return value;
986
1483
  const t = typeof value;
@@ -1031,7 +1528,7 @@ var stringToMatcher = (s) => {
1031
1528
  };
1032
1529
  function serializeOptions(o) {
1033
1530
  const out = {};
1034
- const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "logAll", "silent", "collapse", "ignoreHotReload"];
1531
+ const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "includeState", "resolveHookNames", "logAll", "silent", "collapse", "ignoreHotReload"];
1035
1532
  for (const k of bools) if (typeof o[k] === "boolean") out[k] = o[k];
1036
1533
  if (typeof o.maxReportsPerComponent === "number") out.maxReportsPerComponent = o.maxReportsPerComponent;
1037
1534
  if (o.include) out.include = o.include.map(matcherToString).filter((x) => x !== null);
@@ -1040,7 +1537,7 @@ function serializeOptions(o) {
1040
1537
  }
1041
1538
  function deserializeOptions(o) {
1042
1539
  const out = {};
1043
- const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "logAll", "silent", "collapse", "ignoreHotReload"];
1540
+ const bools = ["trackAllMemoized", "trackAllComponents", "trackHooks", "includeState", "resolveHookNames", "logAll", "silent", "collapse", "ignoreHotReload"];
1044
1541
  for (const k of bools) if (typeof o[k] === "boolean") out[k] = o[k];
1045
1542
  if (typeof o.maxReportsPerComponent === "number") out.maxReportsPerComponent = o.maxReportsPerComponent;
1046
1543
  if (Array.isArray(o.include)) out.include = o.include.filter((s) => typeof s === "string" && s).map(stringToMatcher);
@@ -1071,7 +1568,8 @@ function createDevtoolsNotifier(options = {}) {
1071
1568
  source,
1072
1569
  injected: typeof window !== "undefined" && typeof window.__RERENDER_LENS_INJECTED__ === "string",
1073
1570
  scheduled: getState().scheduled,
1074
- commits: getState().commits
1571
+ commits: getState().commits,
1572
+ overhead: { totalMs: getState().overheadMs, maxCommitMs: getState().maxCommitMs }
1075
1573
  });
1076
1574
  const bridge = {
1077
1575
  replay: () => {
@@ -1136,12 +1634,126 @@ function createDevtoolsNotifier(options = {}) {
1136
1634
  }
1137
1635
  };
1138
1636
  if (typeof window !== "undefined") window.__RERENDER_LENS_DEVTOOLS__ = bridge;
1139
- post("hello", info());
1637
+ const runCommand = (data) => {
1638
+ const reply = { __rerenderLensReply: true, id: data.id };
1639
+ try {
1640
+ switch (data.cmd) {
1641
+ case "info":
1642
+ reply.result = info();
1643
+ break;
1644
+ case "pull":
1645
+ reply.result = bridge.pull(typeof data.arg === "number" ? data.arg : 0);
1646
+ break;
1647
+ case "replay":
1648
+ bridge.replay();
1649
+ reply.result = true;
1650
+ break;
1651
+ case "clear":
1652
+ bridge.clear();
1653
+ reply.result = true;
1654
+ break;
1655
+ case "configure":
1656
+ reply.result = bridge.configure(data.arg ?? {});
1657
+ break;
1658
+ case "highlight":
1659
+ reply.result = bridge.highlight(typeof data.arg === "number" ? data.arg : null);
1660
+ break;
1661
+ case "flash":
1662
+ bridge.flashAvoidable(!!data.arg);
1663
+ reply.result = true;
1664
+ break;
1665
+ default:
1666
+ reply.error = `unknown command ${String(data.cmd)}`;
1667
+ }
1668
+ } catch (e) {
1669
+ reply.error = String(e.message || e);
1670
+ }
1671
+ return reply;
1672
+ };
1673
+ const isCommand = (data) => !!data && typeof data === "object" && data.__rerenderLensCmd === true && typeof data.id === "string";
1674
+ const envelope = (type, payload) => ({ [DEVTOOLS_MARKER]: true, version: PROTOCOL_VERSION, type, payload });
1675
+ const sinks = [];
1676
+ if (options.channel && typeof BroadcastChannel === "function") {
1677
+ const name = typeof options.channel === "string" ? options.channel : DEFAULT_CHANNEL;
1678
+ try {
1679
+ const channel = new BroadcastChannel(name);
1680
+ channel.onmessage = (event) => {
1681
+ if (isCommand(event.data)) channel.postMessage(runCommand(event.data));
1682
+ };
1683
+ sinks.push((message) => {
1684
+ try {
1685
+ channel.postMessage(message);
1686
+ } catch {
1687
+ }
1688
+ });
1689
+ } catch {
1690
+ }
1691
+ }
1692
+ const relayUrl = options.relay ?? (typeof window !== "undefined" ? window.__RERENDER_LENS_RELAY__ : void 0);
1693
+ const ES = options.eventSource ?? (typeof EventSource === "function" ? EventSource : null);
1694
+ if (relayUrl && ES && typeof fetch === "function") {
1695
+ const base = relayUrl.replace(/\/$/, "");
1696
+ let queue = [];
1697
+ let scheduled = false;
1698
+ const flush = () => {
1699
+ scheduled = false;
1700
+ const batch = queue;
1701
+ queue = [];
1702
+ fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(batch), keepalive: true }).catch(() => {
1703
+ });
1704
+ };
1705
+ const relaySend = (message) => {
1706
+ queue.push(message);
1707
+ if (!scheduled) {
1708
+ scheduled = true;
1709
+ setTimeout(flush, 0);
1710
+ }
1711
+ };
1712
+ try {
1713
+ const stream = new ES(`${base}/events?role=app`);
1714
+ stream.onmessage = (event) => {
1715
+ let parsed;
1716
+ try {
1717
+ parsed = JSON.parse(event.data);
1718
+ } catch {
1719
+ return;
1720
+ }
1721
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) if (isCommand(m)) relaySend(runCommand(m));
1722
+ };
1723
+ stream.onerror = () => {
1724
+ };
1725
+ sinks.push(relaySend);
1726
+ } catch {
1727
+ }
1728
+ }
1729
+ const postAll = (type, payload) => {
1730
+ post(type, payload);
1731
+ if (sinks.length) {
1732
+ const message = envelope(type, payload);
1733
+ for (const sink of sinks) sink(message);
1734
+ }
1735
+ };
1736
+ if (sinks.length) {
1737
+ const replay = bridge.replay;
1738
+ const clear = bridge.clear;
1739
+ bridge.replay = () => {
1740
+ for (const sink of sinks) {
1741
+ sink(envelope("hello", info()));
1742
+ for (const e of buffer) sink(envelope("report", e.payload));
1743
+ }
1744
+ replay();
1745
+ };
1746
+ bridge.clear = () => {
1747
+ clear();
1748
+ for (const sink of sinks) sink(envelope("clear"));
1749
+ };
1750
+ }
1751
+ postAll("hello", info());
1140
1752
  return (report) => {
1141
1753
  const payload = serialize(report, maxDepth);
1142
1754
  buffer.push({ seq: ++seq, instanceId: report.instanceId, payload });
1143
1755
  if (buffer.length > bufferSize) buffer.splice(0, buffer.length - bufferSize);
1144
- post("report", payload);
1756
+ postAll("report", payload);
1145
1757
  if (flashOn && report.avoidable && report.instanceId) {
1146
1758
  const fiber = fiberById(report.instanceId);
1147
1759
  if (fiber) flash({ nodes: hostNodesOf(fiber), label: report.component });
@@ -1149,31 +1761,48 @@ function createDevtoolsNotifier(options = {}) {
1149
1761
  };
1150
1762
  }
1151
1763
 
1764
+ exports.DEFAULT_CHANNEL = DEFAULT_CHANNEL;
1152
1765
  exports.DEVTOOLS_MARKER = DEVTOOLS_MARKER;
1153
1766
  exports.MARKER = MARKER;
1154
1767
  exports.PROTOCOL_VERSION = PROTOCOL_VERSION;
1155
1768
  exports.VERSION = VERSION;
1769
+ exports.assertWithinBudget = assertWithinBudget;
1770
+ exports.avoidableCounts = avoidableCounts;
1156
1771
  exports.buildReport = buildReport;
1772
+ exports.checkBudget = checkBudget;
1157
1773
  exports.classify = classify;
1158
1774
  exports.combineNotifiers = combineNotifiers;
1775
+ exports.compareSummaries = compareSummaries;
1159
1776
  exports.configure = configure;
1160
1777
  exports.createCollector = createCollector;
1161
1778
  exports.createDevtoolsNotifier = createDevtoolsNotifier;
1779
+ exports.customHooksFromStack = customHooksFromStack;
1162
1780
  exports.deepEqual = deepEqual;
1163
1781
  exports.deserializeOptions = deserializeOptions;
1164
1782
  exports.diffRecords = diffRecords;
1165
1783
  exports.disable = disable;
1166
1784
  exports.ensureDevtoolsHook = ensureDevtoolsHook;
1785
+ exports.fixKey = fixKey;
1786
+ exports.fixesFor = fixesFor;
1787
+ exports.formatComparison = formatComparison;
1788
+ exports.formatFixes = formatFixes;
1167
1789
  exports.getDisplayName = getDisplayName;
1168
1790
  exports.getRenderers = getRenderers;
1791
+ exports.hookLabel = hookLabel;
1169
1792
  exports.init = init;
1170
1793
  exports.isEnabled = isEnabled;
1171
1794
  exports.isProductionReact = isProductionReact;
1795
+ exports.parseExport = parseExport;
1172
1796
  exports.printReport = printReport;
1797
+ exports.rankFixes = rankFixes;
1798
+ exports.resolveHookNames = resolveHookNames;
1173
1799
  exports.serialize = serialize;
1174
1800
  exports.serializeOptions = serializeOptions;
1175
1801
  exports.shouldTrack = shouldTrack;
1802
+ exports.storeAdvice = storeAdvice;
1176
1803
  exports.summarize = summarize;
1804
+ exports.summarizeReports = summarizeReports;
1805
+ exports.toBudget = toBudget;
1177
1806
  exports.track = track;
1178
1807
  exports.useWhyRerender = useWhyRerender;
1179
1808
  //# sourceMappingURL=index.cjs.map