react-debug-updates 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,23 @@
1
1
  # react-debug-updates
2
2
 
3
- Visual debugging overlays and console logging for React re-renders. See exactly which components re-render, how often, how long they take, and *why* they re-rendered — all without modifying your components.
3
+ See exactly which React components re-render, how often, how long they take, and *why* — all without modifying your components.
4
4
 
5
5
  ![highlight overlays](https://img.shields.io/badge/overlays-visual%20highlights-61dafb) ![zero config](https://img.shields.io/badge/setup-zero%20config-green)
6
6
 
7
7
  <img src="demo.gif" alt="demo" width="852" height="476" />
8
8
 
9
+ ## Why?
10
+
11
+ I was working on an Electron app and spent hours trying to get the official React DevTools to work with it. DevTools' Electron integration is fragile, poorly documented, and breaks between versions. I just needed to see which components were re-rendering so I could fix performance issues.
12
+
13
+ So I wrote this — a plug-and-play one-liner that gives you visual highlight overlays and console logging for React re-renders. No browser extension, no Electron hacks, no configuration. Works in any React web environment — browsers, Electron, iframes.
14
+
9
15
  ## How it works
10
16
 
11
- Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__` to intercept every React commit. Works in any React web environment browsers, Electron, iframes no React DevTools extension required. No wrappers, no HOCs, no code changes — just call `attachRenderLogger()` and you get:
17
+ Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__` to intercept every React commit. Uses the same fiber tree diffing approach as React DevTools to detect which components actually re-rendered. No React DevTools extension required. No wrappers, no HOCs, no code changes — just call `monitor()` and you get:
12
18
 
13
- - **Console logging** — grouped, color-coded re-render reports with component tree paths and render durations
14
19
  - **Visual overlays** — highlight boxes on re-rendered DOM nodes with a heat-map color scale (blue → red as render count increases)
20
+ - **Console logging** — grouped, color-coded re-render reports with component tree paths and render durations
15
21
  - **Cause detection** — pinpoint *which* `useState`, `useReducer`, `useSyncExternalStore`, or `useContext` hook triggered each re-render, with previous→next values
16
22
 
17
23
  ## Install
@@ -26,30 +32,38 @@ pnpm add react-debug-updates
26
32
 
27
33
  ## Quick start
28
34
 
29
- Import and call `attachRenderLogger` **before** React renders anything — ideally at the very top of your entry point. This ensures the hook is in place before the first commit.
35
+ Import and call `monitor` **before** React renders anything — ideally at the very top of your entry point. This ensures the hook is in place before the first commit.
30
36
 
31
37
  ```ts
32
- import { attachRenderLogger } from "react-debug-updates";
38
+ import { monitor } from "react-debug-updates";
33
39
 
34
- // Call before React renders top of your entry point
35
- const logger = attachRenderLogger({
36
- highlight: true,
37
- showCauses: true,
38
- });
40
+ // One-liner overlays + console logging out of the box
41
+ const updates = monitor();
39
42
 
40
43
  // Later, to clean up:
41
- logger?.disconnect();
44
+ updates?.stop();
42
45
  ```
43
46
 
44
47
  ### Dev-only guard
45
48
 
46
49
  ```ts
47
50
  if (process.env.NODE_ENV === "development") {
48
- const { attachRenderLogger } = await import("react-debug-updates");
49
- attachRenderLogger({ highlight: true, showCauses: true });
51
+ const { monitor } = await import("react-debug-updates");
52
+ monitor();
50
53
  }
51
54
  ```
52
55
 
56
+ ### With options
57
+
58
+ ```ts
59
+ monitor({
60
+ showCauses: true,
61
+ opacity: 0.5,
62
+ showLabels: false,
63
+ silent: true, // overlays only, no console output
64
+ });
65
+ ```
66
+
53
67
  ## Requirements
54
68
 
55
69
  - A **React dev build** (which automatically creates `__REACT_DEVTOOLS_GLOBAL_HOOK__`) — no browser extension needed
@@ -57,36 +71,31 @@ if (process.env.NODE_ENV === "development") {
57
71
 
58
72
  ## API
59
73
 
60
- ### `attachRenderLogger(options?): RenderLogger | null`
74
+ ### `monitor(options?): UpdateMonitor | null`
61
75
 
62
- Returns a `RenderLogger` handle, or `null` if the DevTools hook is not available.
76
+ Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not available.
63
77
 
64
78
  #### Options
65
79
 
66
80
  | Option | Type | Default | Description |
67
81
  | --- | --- | --- | --- |
68
- | `silent` | `boolean` | `false` | Suppress console output |
69
82
  | `mode` | `"self-triggered" \| "all"` | `"self-triggered"` | `"self-triggered"` tracks only components whose own state changed. `"all"` includes children swept by parent updates |
70
- | `bufferSize` | `number` | `500` | Max entries kept in the ring buffer |
71
- | `filter` | `(entry: RenderEntry) => boolean` | — | Return `false` to skip an entry |
72
- | `highlight` | `boolean \| HighlightOptions` | `false` | Enable visual overlay highlights |
73
83
  | `showCauses` | `boolean` | `false` | Detect and display why each component re-rendered |
74
-
75
- #### `HighlightOptions`
76
-
77
- | Option | Type | Default | Description |
78
- | --- | --- | --- | --- |
79
- | `flushInterval` | `number` | `250` | Milliseconds between overlay flush cycles |
80
- | `animationDuration` | `number` | `1200` | Overlay fade-out animation duration (ms) |
84
+ | `silent` | `boolean` | `false` | Suppress console output |
85
+ | `overlay` | `boolean` | `true` | Enable visual highlight overlays |
81
86
  | `showLabels` | `boolean` | `true` | Show text labels (name, count, duration, cause) above overlays |
82
87
  | `opacity` | `number` | `0.3` | Peak opacity of overlay highlights (0–1) |
88
+ | `flushInterval` | `number` | `250` | Milliseconds between overlay flush cycles |
89
+ | `animationDuration` | `number` | `1200` | Overlay fade-out animation duration (ms) |
90
+ | `bufferSize` | `number` | `500` | Max entries kept in the ring buffer |
91
+ | `filter` | `(entry: RenderEntry) => boolean` | — | Return `false` to skip an entry |
83
92
 
84
- ### `RenderLogger`
93
+ ### `UpdateMonitor`
85
94
 
86
95
  | Property | Type | Description |
87
96
  | --- | --- | --- |
88
97
  | `entries` | `RenderEntry[]` | Ring buffer of recorded re-render entries |
89
- | `disconnect` | `() => void` | Unhook from React and remove all overlays |
98
+ | `stop` | `() => void` | Unhook from React and remove all overlays |
90
99
 
91
100
  ### `RenderEntry`
92
101
 
package/dist/format.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { PendingEntry, UpdateCause } from "./types.js";
1
+ import type { HighlightEntry, UpdateCause } from "./types.js";
2
2
  export declare function formatValue(value: unknown, maxLength?: number): string;
3
3
  /** Compact summary for overlay labels. */
4
4
  export declare function formatCausesShort(causes: UpdateCause[]): string;
5
5
  /** Detailed lines for console output. */
6
6
  export declare function formatCausesConsole(causes: UpdateCause[]): string[];
7
7
  /** Log re-renders as a collapsed console group. */
8
- export declare function logRerendersToConsole(entries: PendingEntry[], showCauses: boolean): void;
8
+ export declare function logRerendersToConsole(entries: HighlightEntry[], showCauses: boolean): void;
@@ -0,0 +1,5 @@
1
+ import type { OverlayConfig, HighlightEntry } from "./types.js";
2
+ export declare function createHighlighter(config: OverlayConfig): {
3
+ push: (entry: HighlightEntry) => void;
4
+ dispose: () => void;
5
+ };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { attachRenderLogger } from "./logger.js";
2
- export type { LoggerOptions, RenderLogger, RenderEntry, HighlightOptions, UpdateCause, } from "./types.js";
1
+ export { monitor } from "./monitor.js";
2
+ export type { MonitorOptions, UpdateMonitor, RenderEntry, UpdateCause, } from "./types.js";
@@ -0,0 +1,14 @@
1
+ import type { MonitorOptions, UpdateMonitor } from "./types.js";
2
+ /**
3
+ * Start monitoring React re-renders.
4
+ *
5
+ * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept
6
+ * every React commit. Records re-render entries, optionally logs them to the
7
+ * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.
8
+ *
9
+ * Call this **before** React renders anything — ideally at the very top of
10
+ * your entry point.
11
+ *
12
+ * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.
13
+ */
14
+ export declare function monitor(options?: MonitorOptions): UpdateMonitor | null;
@@ -322,13 +322,7 @@ function heatColor(count, alpha) {
322
322
  const lightness = 55 - normalizedCount * 10;
323
323
  return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
324
324
  }
325
- const HIGHLIGHT_DEFAULTS = {
326
- flushInterval: 250,
327
- animationDuration: 1200,
328
- showLabels: true,
329
- opacity: 0.3
330
- };
331
- function createBatcher(options) {
325
+ function createHighlighter(config) {
332
326
  let pending = [];
333
327
  let timer = null;
334
328
  function flush() {
@@ -381,10 +375,10 @@ function createBatcher(options) {
381
375
  style.height = `${rect.height}px`;
382
376
  style.backgroundColor = fillColor;
383
377
  style.border = `1.5px solid ${borderColor}`;
384
- style.setProperty("--rdu-opacity", String(options.opacity));
385
- style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;
378
+ style.setProperty("--rdu-opacity", String(config.opacity));
379
+ style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;
386
380
  const label = element.firstElementChild;
387
- if (options.showLabels) {
381
+ if (config.showLabels) {
388
382
  const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : "";
389
383
  const durationText = coalesced.totalDuration > 0 ? ` ${coalesced.totalDuration.toFixed(1)}ms` : "";
390
384
  const causeText = coalesced.causeSummary ? ` (${coalesced.causeSummary})` : "";
@@ -399,7 +393,7 @@ function createBatcher(options) {
399
393
  function push(entry) {
400
394
  pending.push(entry);
401
395
  if (!timer) {
402
- timer = setInterval(flush, options.flushInterval);
396
+ timer = setInterval(flush, config.flushInterval);
403
397
  }
404
398
  }
405
399
  function dispose() {
@@ -411,14 +405,18 @@ function createBatcher(options) {
411
405
  }
412
406
  return { push, dispose };
413
407
  }
414
- function attachRenderLogger(options = {}) {
408
+ function monitor(options = {}) {
415
409
  const {
416
410
  silent = false,
417
411
  bufferSize = 500,
418
412
  filter,
419
- highlight = false,
413
+ overlay = true,
420
414
  mode = "self-triggered",
421
- showCauses = false
415
+ showCauses = false,
416
+ flushInterval = 250,
417
+ animationDuration = 1200,
418
+ showLabels = true,
419
+ opacity = 0.3
422
420
  } = options;
423
421
  const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
424
422
  if (!hook) {
@@ -427,23 +425,19 @@ function attachRenderLogger(options = {}) {
427
425
  );
428
426
  return null;
429
427
  }
430
- const highlightOptions = highlight ? {
431
- ...HIGHLIGHT_DEFAULTS,
432
- ...typeof highlight === "object" ? highlight : {}
433
- } : null;
434
- const batcher = highlightOptions ? createBatcher(highlightOptions) : null;
428
+ const highlighter = overlay ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity }) : null;
435
429
  const entries = [];
436
430
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
437
431
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
438
432
  previousOnCommit(rendererID, root, priorityLevel);
439
433
  const detectedRenders = detectRenders(root.current, mode);
440
434
  if (detectedRenders.length === 0) return;
441
- const pendingEntries = [];
435
+ const highlightEntries = [];
442
436
  for (let i = 0; i < detectedRenders.length; i++) {
443
437
  const { fiber, depth } = detectedRenders[i];
444
438
  const name = getComponentName(fiber);
445
439
  if (!name) continue;
446
- const pendingEntry = {
440
+ const highlightEntry = {
447
441
  component: name,
448
442
  path: getFiberPath(fiber),
449
443
  duration: fiber.actualDuration ?? 0,
@@ -452,25 +446,25 @@ function attachRenderLogger(options = {}) {
452
446
  causes: showCauses ? detectCauses(fiber) : []
453
447
  };
454
448
  const renderEntry = {
455
- component: pendingEntry.component,
456
- path: pendingEntry.path,
457
- duration: pendingEntry.duration,
449
+ component: highlightEntry.component,
450
+ path: highlightEntry.path,
451
+ duration: highlightEntry.duration,
458
452
  timestamp: performance.now(),
459
- causes: pendingEntry.causes
453
+ causes: highlightEntry.causes
460
454
  };
461
455
  if (filter && !filter(renderEntry)) continue;
462
456
  if (entries.length >= bufferSize) entries.shift();
463
457
  entries.push(renderEntry);
464
- pendingEntries.push(pendingEntry);
465
- batcher == null ? void 0 : batcher.push(pendingEntry);
458
+ highlightEntries.push(highlightEntry);
459
+ highlighter == null ? void 0 : highlighter.push(highlightEntry);
466
460
  }
467
461
  if (!silent) {
468
- logRerendersToConsole(pendingEntries, showCauses);
462
+ logRerendersToConsole(highlightEntries, showCauses);
469
463
  }
470
464
  };
471
- const disconnect = () => {
465
+ const stop = () => {
472
466
  hook.onCommitFiberRoot = previousOnCommit;
473
- batcher == null ? void 0 : batcher.dispose();
467
+ highlighter == null ? void 0 : highlighter.dispose();
474
468
  disposeAllOverlays();
475
469
  };
476
470
  if (!silent) {
@@ -479,7 +473,7 @@ function attachRenderLogger(options = {}) {
479
473
  "color: #61dafb; font-weight: bold"
480
474
  );
481
475
  }
482
- return { entries, disconnect };
476
+ return { entries, stop };
483
477
  }
484
- exports.attachRenderLogger = attachRenderLogger;
478
+ exports.monitor = monitor;
485
479
  //# sourceMappingURL=react-debug-updates.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.cjs","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { PendingEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: PendingEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { HighlightOptions, PendingEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n opacity: 0.3,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(options.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n LoggerOptions,\n PendingEntry,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const pendingEntries: PendingEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const pendingEntry: PendingEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n pendingEntries.push(pendingEntry);\n batcher?.push(pendingEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(pendingEntries, showCauses);\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, disconnect };\n}\n"],"names":[],"mappings":";;AAMO,MAAM,WAAW;AAAA,EACtB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,YAAY;AAAA,EACZ,qBAAqB;AAEvB;AAOA,MAAM,qCAAqB,IAAY;AAAA,EACrC,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX,CAAC;AAED,MAAM,gBAAgB;AAMf,SAAS,iBAAiB,OAA6B;AAC5D,QAAM,EAAE,SAAS;AACjB,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,SAAO,KAAK,eAAe,KAAK,QAAQ;AAC1C;AAEA,SAAS,cAAc,OAAsC;AAC3D,SACE,OAAO,UAAU,YACjB,UAAU,QACT,MAAe,aAAa,KAC7B,OAAQ,MAAsB,0BAA0B;AAE5D;AAEO,SAAS,mBAAmB,OAAkC;AACnE,MAAI,MAAM,QAAQ,SAAS,iBAAiB,cAAc,MAAM,SAAS,GAAG;AAC1E,WAAO,MAAM;AAAA,EACf;AACA,MAAI,QAAsB,MAAM;AAChC,SAAO,OAAO;AACZ,UAAM,QAAQ,mBAAmB,KAAK;AACtC,QAAI,MAAO,QAAO;AAClB,YAAQ,MAAM;AAAA,EAChB;AACA,SAAO;AACT;AAEO,SAAS,aAAa,OAAc,WAAW,GAAW;AAC/D,QAAM,QAAkB,CAAA;AACxB,MAAI,UAAwB,MAAM;AAClC,MAAI,QAAQ;AACZ,SAAO,WAAW,QAAQ,UAAU;AAClC,UAAM,OAAO,iBAAiB,OAAO;AACrC,QAAI,KAAM,OAAM,QAAQ,IAAI;AAC5B,cAAU,QAAQ;AAClB;AAAA,EACF;AACA,SAAO,MAAM,SAAS,MAAM,KAAK,KAAK,IAAI;AAC5C;AAMA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,YAAY,MAAM;AACxB,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,MAAM,kBAAkB,UAAU;AAC3C;AAgBA,SAAS,eAAe,WAA2B;AACjD,UAAQ,UAAU,QAAQ,mBAAmB;AAC/C;AAkBO,SAAS,cACd,MACA,MACkB;AAClB,QAAM,UAA4B,CAAA;AAClC,QAAM,oBAAoB,SAAS;AAInC,QAAM,eAAe,KAAK;AAC1B,MAAI,CAAC,aAAc,QAAO;AAE1B,WAAS,KACP,WACA,eACA,OACA;AAEA,QACE,eAAe,IAAI,UAAU,GAAG,KAChC,kBAAkB,QAClB,kBAAkB;AAAA,IAClB,eAAe,SAAS,MACvB,CAAC,qBAAqB,gBAAgB,SAAS,IAChD;AACA,cAAQ,KAAK,EAAE,OAAO,WAAW,OAAO;AAAA,IAC1C;AAGA,QAAI,YAAY,UAAU;AAC1B,QAAI,4BAA2B,+CAAe,UAAS;AAEvD,WAAO,WAAW;AAChB,UAAI;AAEJ,UAAI,6BAA6B,WAAW;AAI1C,0BAAkB;AAAA,MACpB,OAAO;AAGL,0BAAkB,UAAU;AAAA,MAC9B;AAEA,WAAK,WAAW,iBAAiB,QAAQ,CAAC;AAE1C,kBAAY,UAAU;AACtB,kCAA2B,qEAA0B,YAAW;AAAA,IAClE;AAAA,EACF;AAEA,OAAK,MAAM,cAAc,CAAC;AAC1B,SAAO;AACT;AC1JA,MAAM,kCAAkB,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,MAAM,qBAAqB,oBAAI,IAAI,CAAC,YAAY,CAAC;AAE1C,SAAS,aAAa,OAA6B;AACxD,QAAM,YAAY,MAAM;AACxB,MAAI,CAAC,UAAW,QAAO,CAAA;AAEvB,QAAM,SAAwB,CAAA;AAG9B,MAAI,MAAM,kBAAkB,UAAU,eAAe;AACnD,WAAO,KAAK,EAAE,MAAM,QAAA,CAAS;AAAA,EAC/B;AAGA,MAAI,MAAM,QAAQ,SAAS,gBAAgB;AACzC,QAAI,MAAM,kBAAkB,UAAU,eAAe;AACnD,aAAO,KAAK,EAAE,MAAM,cAAA,CAAe;AAAA,IACrC;AACA,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,MAAM;AAExB,MAAI,CAAC,WAAW;AAEd,QAAI,MAAM,kBAAkB,UAAU,eAAe;AACnD,aAAO,KAAK,EAAE,MAAM,UAAA,CAAW;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,cAAc,MAAM;AACxB,MAAI,eAAe,UAAU;AAC7B,MAAI,iBAAiB;AACrB,MAAI,sBAAsB;AAE1B,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAM,OAAO,UAAU,CAAC;AAGxB,QAAI,mBAAmB,IAAI,IAAI,GAAG;AAChC,UAAI,SAAS,aAAc,kBAAiB;AAC5C;AAAA,IACF;AAEA,QAAI,YAAY,IAAI,IAAI,KAAK,eAAe,cAAc;AACxD,UAAI,CAAC,OAAO,GAAG,YAAY,eAAe,aAAa,aAAa,GAAG;AACrE,8BAAsB;AACtB,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,WAAW;AAAA,UACX,UAAU;AAAA,UACV,eAAe,aAAa;AAAA,UAC5B,WAAW,YAAY;AAAA,QAAA,CACxB;AAAA,MACH;AAAA,IACF;AAEA,mBAAc,2CAAa,SAAQ;AACnC,oBAAe,6CAAc,SAAQ;AAAA,EACvC;AAIA,MACE,kBACA,CAAC,uBACD,MAAM,kBAAkB,UAAU,eAClC;AACA,WAAO,KAAK,EAAE,MAAM,QAAQ,UAAU,cAAc;AAAA,EACtD;AAGA,MAAI,OAAO,WAAW,KAAK,MAAM,kBAAkB,UAAU,eAAe;AAC1E,WAAO,KAAK,EAAE,MAAM,UAAA,CAAW;AAAA,EACjC;AAEA,SAAO;AACT;ACtGO,SAAS,YAAY,OAAgB,YAAY,IAAY;;AAClE,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,UAAW,QAAO,OAAO,KAAK;AACnD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAClD,MAAI,OAAO,UAAU;AACnB,WAAO,KAAM,MAA4B,QAAQ,WAAW;AAE9D,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,YACJ,MAAM,SAAS,YAAY,MAAM,MAAM,GAAG,SAAS,IAAI,MAAM;AAC/D,WAAO,KAAK,UAAU,SAAS;AAAA,EACjC;AAEA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,SAAS,MAAM,MAAM;AAEtD,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,QAAQ,WAAiB,gBAAjB,mBAA8B;AAC5C,QAAI,QAAQ,SAAS,SAAU,QAAO;AACtC,UAAM,OAAO,OAAO,KAAK,KAAe;AACxC,QAAI,KAAK,UAAU,EAAG,QAAO,KAAK,KAAK,KAAK,IAAI,CAAC;AACjD,WAAO,KAAK,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACzC;AAEA,SAAO,OAAO,KAAK;AACrB;AAOO,SAAS,kBAAkB,QAA+B;AAC/D,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,QAAkB,CAAA;AACxB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,KAAK,OAAO;AAAA,IACpB,WAAW,MAAM,SAAS,eAAe;AACvC,YAAM,KAAK,OAAO;AAAA,IACpB,WAAW,MAAM,SAAS,UAAU,MAAM,UAAU;AAClD,YAAM,cAAc,MAAM,aAAa,OAAO,IAAI,MAAM,SAAS,MAAM;AACvE,YAAM,KAAK,GAAG,MAAM,QAAQ,GAAG,WAAW,EAAE;AAAA,IAC9C,OAAO;AACL,YAAM,KAAK,GAAG;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAGO,SAAS,oBAAoB,QAAiC;AACnE,QAAM,QAAkB,CAAA;AAExB,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,KAAK,wCAAwC;AAAA,IACrD,WAAW,MAAM,SAAS,eAAe;AACvC,YAAM,KAAK,yBAAyB;AAAA,IACtC,WAAW,MAAM,SAAS,UAAU,MAAM,UAAU;AAClD,YAAM,cACJ,MAAM,aAAa,OAAO,IAAI,MAAM,SAAS,MAAM;AACrD,YAAM,OAAO,GAAG,MAAM,QAAQ,GAAG,WAAW;AAE5C,UAAI,MAAM,kBAAkB,UAAa,MAAM,cAAc,QAAW;AACtE,cAAM;AAAA,UACJ,OAAO,IAAI,KAAK,YAAY,MAAM,aAAa,CAAC,MAAM,YAAY,MAAM,SAAS,CAAC;AAAA,QAAA;AAAA,MAEtF,OAAO;AACL,cAAM,KAAK,OAAO,IAAI,UAAU;AAAA,MAClC;AAAA,IACF,OAAO;AACL,YAAM,KAAK,mBAAmB;AAAA,IAChC;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,sBACd,SACA,YACA;AACA,MAAI,QAAQ,WAAW,EAAG;AAE1B,UAAQ;AAAA,IACN,OAAO,QAAQ,MAAM,aAAa,QAAQ,SAAS,IAAI,MAAM,EAAE;AAAA,IAC/D;AAAA,EAAA;AAGF,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,QAAQ,QAAQ,CAAC;AACvB,UAAM,eACJ,MAAM,WAAW,IAAI,KAAK,MAAM,SAAS,QAAQ,CAAC,CAAC,QAAQ;AAC7D,YAAQ;AAAA,MACN,KAAK,MAAM,SAAS,MAAM,MAAM,IAAI,GAAG,YAAY;AAAA,MACnD;AAAA,MACA;AAAA,IAAA;AAGF,QAAI,cAAc,MAAM,OAAO,SAAS,GAAG;AACzC,YAAM,QAAQ,oBAAoB,MAAM,MAAM;AAC9C,iBAAW,QAAQ,OAAO;AACxB,gBAAQ,IAAI,KAAK,IAAI,IAAI,aAAa;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,SAAA;AACV;AClHA,MAAM,iBAAiB;AAEvB,MAAM,sCAAsB,QAAA;AAE5B,SAAS,iBAAiB,KAAa;AACrC,MAAI,gBAAgB,IAAI,GAAG,EAAG;AAC9B,kBAAgB,IAAI,GAAG;AAEvB,QAAM,QAAQ,IAAI,SAAS,cAAc,OAAO;AAChD,QAAM,cAAc;AAAA,iBACL,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMxB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAQd,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYnB,MAAI,SAAS,KAAK,YAAY,KAAK;AACrC;AAMA,MAAM,mCAAmB,QAAA;AAEzB,SAAS,eAAe,KAAoC;AAC1D,MAAI,IAAI,OAAQ,QAAO;AAEvB,QAAM,WAAW,aAAa,IAAI,GAAG;AACrC,MAAI,qCAAU,YAAa,QAAO;AAElC,mBAAiB,GAAG;AAEpB,QAAM,OAAO,IAAI,SAAS,cAAc,KAAK;AAC7C,OAAK,KAAK;AACV,SAAO,OAAO,KAAK,OAAO;AAAA,IACxB,UAAU;AAAA,IACV,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,eAAe;AAAA,IACf,QAAQ;AAAA,EAAA,CAC8B;AACxC,MAAI,SAAS,KAAK,YAAY,IAAI;AAElC,eAAa,IAAI,KAAK,IAAI;AAC1B,SAAO;AACT;AAMA,MAAM,4BAAY,QAAA;AAEX,SAAS,eAAe,KAAoC;AACjE,QAAM,OAAO,eAAe,GAAG;AAC/B,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,OAAO,MAAM,IAAI,GAAG;AACxB,MAAI,CAAC,MAAM;AACT,WAAO,CAAA;AACP,UAAM,IAAI,KAAK,IAAI;AAAA,EACrB;AAEA,QAAM,WAAW,IAAI;AACrB,MAAI,UAAU,KAAK,IAAA;AAEnB,MAAI,CAAC,SAAS;AACZ,cAAU,SAAS,cAAc,KAAK;AACtC,YAAQ,YAAY,GAAG,cAAc;AAErC,UAAM,QAAQ,SAAS,cAAc,MAAM;AAC3C,UAAM,YAAY,GAAG,cAAc;AACnC,YAAQ,YAAY,KAAK;AAEzB,YAAQ,iBAAiB,gBAAgB,MAAM;AAC7C,cAAS,MAAM,YAAY;AAC3B,cAAS,OAAA;AACT,WAAM,KAAK,OAAQ;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,OAAK,YAAY,OAAO;AACxB,SAAO;AACT;AAEO,SAAS,qBAAqB;AACnC,QAAM,WAAW,aAAa,IAAI,MAAM;AACxC,uCAAU;AACV,eAAa,OAAO,MAAM;AAC1B,QAAM,OAAO,MAAM;AACrB;AAEO,MAAM,yBAAyB;ACpHtC,SAAS,UAAU,OAAe,OAAuB;AACvD,QAAM,kBAAkB,KAAK,KAAK,QAAQ,KAAK,GAAG,CAAC;AACnD,QAAM,MAAM,MAAM,kBAAkB;AACpC,QAAM,aAAa,KAAK,kBAAkB;AAC1C,QAAM,YAAY,KAAK,kBAAkB;AACzC,SAAO,QAAQ,GAAG,KAAK,UAAU,MAAM,SAAS,MAAM,KAAK;AAC7D;AASO,MAAM,qBAAiD;AAAA,EAC5D,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,SAAS;AACX;AAaO,SAAS,cAAc,SAAqC;AACjE,MAAI,UAA0B,CAAA;AAC9B,MAAI,QAA+C;AAEnD,WAAS,QAAQ;;AACf,QAAI,QAAQ,WAAW,EAAG;AAE1B,UAAM,QAAQ;AACd,cAAU,CAAA;AAGV,UAAM,0BAAU,IAAA;AAEhB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,QAAQ,MAAM,CAAC;AACrB,UAAI,CAAC,MAAM,QAAS;AAEpB,YAAM,WAAW,IAAI,IAAI,MAAM,OAAO;AACtC,UAAI,UAAU;AACZ,iBAAS;AACT,iBAAS,iBAAiB,MAAM;AAChC,iBAAS,QAAQ,KAAK,IAAI,SAAS,OAAO,MAAM,KAAK;AAAA,MACvD,OAAO;AACL,cAAM,OAAM,WAAM,QAAQ,kBAAd,mBAA6B;AACzC,YAAI,CAAC,OAAO,IAAI,OAAQ;AACxB,YAAI,IAAI,MAAM,SAAS;AAAA,UACrB,OAAO;AAAA,UACP,eAAe,MAAM;AAAA,UACrB,WAAW,MAAM;AAAA,UACjB,OAAO,MAAM;AAAA,UACb,SAAS,MAAM;AAAA,UACf,aAAa;AAAA,UACb,cAAc,kBAAkB,MAAM,MAAM;AAAA,QAAA,CAC7C;AAAA,MACH;AAAA,IACF;AAGA,UAAM,SAA8D,CAAA;AACpE,eAAW,aAAa,IAAI,UAAU;AACpC,YAAM,OAAO,UAAU,QAAQ,sBAAA;AAC/B,UAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;AACrC,eAAO,KAAK,EAAE,WAAW,KAAA,CAAM;AAAA,MACjC;AAAA,IACF;AAGA,WAAO,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,QAAQ,EAAE,UAAU,KAAK;AAG3D,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,EAAE,WAAW,SAAS,OAAO,CAAC;AACpC,YAAM,UAAU,eAAe,UAAU,WAAW;AACpD,UAAI,CAAC,QAAS;AAEd,YAAM,YAAY,UAAU,UAAU,OAAO,IAAI;AACjD,YAAM,cAAc,UAAU,UAAU,OAAO,IAAI;AACnD,YAAM,kBAAkB,UAAU,UAAU,OAAO,GAAG;AAEtD,YAAM,QAAQ,QAAQ;AACtB,YAAM,MAAM,GAAG,KAAK,GAAG;AACvB,YAAM,OAAO,GAAG,KAAK,IAAI;AACzB,YAAM,QAAQ,GAAG,KAAK,KAAK;AAC3B,YAAM,SAAS,GAAG,KAAK,MAAM;AAC7B,YAAM,kBAAkB;AACxB,YAAM,SAAS,eAAe,WAAW;AACzC,YAAM,YAAY,iBAAiB,OAAO,QAAQ,OAAO,CAAC;AAC1D,YAAM,YAAY,GAAG,sBAAsB,IAAI,QAAQ,iBAAiB;AAExE,YAAM,QAAQ,QAAQ;AACtB,UAAI,QAAQ,YAAY;AACtB,cAAM,YAAY,UAAU,QAAQ,IAAI,KAAK,UAAU,KAAK,KAAK;AACjE,cAAM,eACJ,UAAU,gBAAgB,IACtB,IAAI,UAAU,cAAc,QAAQ,CAAC,CAAC,OACtC;AACN,cAAM,YAAY,UAAU,eACxB,KAAK,UAAU,YAAY,MAC3B;AACJ,cAAM,cAAc,GAAG,UAAU,SAAS,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS;AACjF,cAAM,MAAM,kBAAkB;AAAA,MAChC,OAAO;AACL,cAAM,cAAc;AACpB,cAAM,MAAM,kBAAkB;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,WAAS,KAAK,OAAqB;AACjC,YAAQ,KAAK,KAAK;AAClB,QAAI,CAAC,OAAO;AACV,cAAQ,YAAY,OAAO,QAAQ,aAAa;AAAA,IAClD;AAAA,EACF;AAEA,WAAS,UAAU;AACjB,QAAI,OAAO;AACT,oBAAc,KAAK;AACnB,cAAQ;AAAA,IACV;AACA,cAAU,CAAA;AAAA,EACZ;AAEA,SAAO,EAAE,MAAM,QAAA;AACjB;ACrHO,SAAS,mBACd,UAAyB,IACJ;AACrB,QAAM;AAAA,IACJ,SAAS;AAAA,IACT,aAAa;AAAA,IACb;AAAA,IACA,YAAY;AAAA,IACZ,OAAO;AAAA,IACP,aAAa;AAAA,EAAA,IACX;AAEJ,QAAM,OACJ,OACA;AAEF,MAAI,CAAC,MAAM;AACT,YAAQ;AAAA,MACN;AAAA,IAAA;AAGF,WAAO;AAAA,EACT;AAEA,QAAM,mBAAmB,YACrB;AAAA,IACE,GAAG;AAAA,IACH,GAAI,OAAO,cAAc,WAAW,YAAY,CAAA;AAAA,EAAC,IAEnD;AAEJ,QAAM,UAAU,mBAAmB,cAAc,gBAAgB,IAAI;AAErE,QAAM,UAAyB,CAAA;AAC/B,QAAM,mBAAmB,KAAK,kBAAkB,KAAK,IAAI;AAEzD,OAAK,oBAAoB,CACvB,YACA,MACA,kBACG;AACH,qBAAiB,YAAY,MAAM,aAAa;AAGhD,UAAM,kBAAkB,cAAc,KAAK,SAAS,IAAI;AACxD,QAAI,gBAAgB,WAAW,EAAG;AAGlC,UAAM,iBAAiC,CAAA;AAEvC,aAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,YAAM,EAAE,OAAO,UAAU,gBAAgB,CAAC;AAC1C,YAAM,OAAO,iBAAiB,KAAK;AACnC,UAAI,CAAC,KAAM;AAEX,YAAM,eAA6B;AAAA,QACjC,WAAW;AAAA,QACX,MAAM,aAAa,KAAK;AAAA,QACxB,UAAU,MAAM,kBAAkB;AAAA,QAClC;AAAA,QACA,SAAS,mBAAmB,KAAK;AAAA,QACjC,QAAQ,aAAa,aAAa,KAAK,IAAI,CAAA;AAAA,MAAC;AAG9C,YAAM,cAA2B;AAAA,QAC/B,WAAW,aAAa;AAAA,QACxB,MAAM,aAAa;AAAA,QACnB,UAAU,aAAa;AAAA,QACvB,WAAW,YAAY,IAAA;AAAA,QACvB,QAAQ,aAAa;AAAA,MAAA;AAGvB,UAAI,UAAU,CAAC,OAAO,WAAW,EAAG;AAEpC,UAAI,QAAQ,UAAU,WAAY,SAAQ,MAAA;AAC1C,cAAQ,KAAK,WAAW;AAExB,qBAAe,KAAK,YAAY;AAChC,yCAAS,KAAK;AAAA,IAChB;AAGA,QAAI,CAAC,QAAQ;AACX,4BAAsB,gBAAgB,UAAU;AAAA,IAClD;AAAA,EACF;AAEA,QAAM,aAAa,MAAM;AACvB,SAAK,oBAAoB;AACzB,uCAAS;AACT,uBAAA;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO,EAAE,SAAS,WAAA;AACpB;;"}
1
+ {"version":3,"file":"react-debug-updates.cjs","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n RenderEntry,\n UpdateMonitor,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.\n */\nexport function monitor(options: MonitorOptions = {}): UpdateMonitor | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n overlay = true,\n mode = \"self-triggered\",\n showCauses = false,\n flushInterval = 250,\n animationDuration = 1200,\n showLabels = true,\n opacity = 0.3,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlighter = overlay\n ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity })\n : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const highlightEntry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: highlightEntry.component,\n path: highlightEntry.path,\n duration: highlightEntry.duration,\n timestamp: performance.now(),\n causes: highlightEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n highlightEntries.push(highlightEntry);\n highlighter?.push(highlightEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(highlightEntries, showCauses);\n }\n };\n\n const stop = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, stop };\n}\n"],"names":[],"mappings":"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"}
@@ -320,13 +320,7 @@ function heatColor(count, alpha) {
320
320
  const lightness = 55 - normalizedCount * 10;
321
321
  return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;
322
322
  }
323
- const HIGHLIGHT_DEFAULTS = {
324
- flushInterval: 250,
325
- animationDuration: 1200,
326
- showLabels: true,
327
- opacity: 0.3
328
- };
329
- function createBatcher(options) {
323
+ function createHighlighter(config) {
330
324
  let pending = [];
331
325
  let timer = null;
332
326
  function flush() {
@@ -379,10 +373,10 @@ function createBatcher(options) {
379
373
  style.height = `${rect.height}px`;
380
374
  style.backgroundColor = fillColor;
381
375
  style.border = `1.5px solid ${borderColor}`;
382
- style.setProperty("--rdu-opacity", String(options.opacity));
383
- style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;
376
+ style.setProperty("--rdu-opacity", String(config.opacity));
377
+ style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;
384
378
  const label = element.firstElementChild;
385
- if (options.showLabels) {
379
+ if (config.showLabels) {
386
380
  const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : "";
387
381
  const durationText = coalesced.totalDuration > 0 ? ` ${coalesced.totalDuration.toFixed(1)}ms` : "";
388
382
  const causeText = coalesced.causeSummary ? ` (${coalesced.causeSummary})` : "";
@@ -397,7 +391,7 @@ function createBatcher(options) {
397
391
  function push(entry) {
398
392
  pending.push(entry);
399
393
  if (!timer) {
400
- timer = setInterval(flush, options.flushInterval);
394
+ timer = setInterval(flush, config.flushInterval);
401
395
  }
402
396
  }
403
397
  function dispose() {
@@ -409,14 +403,18 @@ function createBatcher(options) {
409
403
  }
410
404
  return { push, dispose };
411
405
  }
412
- function attachRenderLogger(options = {}) {
406
+ function monitor(options = {}) {
413
407
  const {
414
408
  silent = false,
415
409
  bufferSize = 500,
416
410
  filter,
417
- highlight = false,
411
+ overlay = true,
418
412
  mode = "self-triggered",
419
- showCauses = false
413
+ showCauses = false,
414
+ flushInterval = 250,
415
+ animationDuration = 1200,
416
+ showLabels = true,
417
+ opacity = 0.3
420
418
  } = options;
421
419
  const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
422
420
  if (!hook) {
@@ -425,23 +423,19 @@ function attachRenderLogger(options = {}) {
425
423
  );
426
424
  return null;
427
425
  }
428
- const highlightOptions = highlight ? {
429
- ...HIGHLIGHT_DEFAULTS,
430
- ...typeof highlight === "object" ? highlight : {}
431
- } : null;
432
- const batcher = highlightOptions ? createBatcher(highlightOptions) : null;
426
+ const highlighter = overlay ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity }) : null;
433
427
  const entries = [];
434
428
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
435
429
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
436
430
  previousOnCommit(rendererID, root, priorityLevel);
437
431
  const detectedRenders = detectRenders(root.current, mode);
438
432
  if (detectedRenders.length === 0) return;
439
- const pendingEntries = [];
433
+ const highlightEntries = [];
440
434
  for (let i = 0; i < detectedRenders.length; i++) {
441
435
  const { fiber, depth } = detectedRenders[i];
442
436
  const name = getComponentName(fiber);
443
437
  if (!name) continue;
444
- const pendingEntry = {
438
+ const highlightEntry = {
445
439
  component: name,
446
440
  path: getFiberPath(fiber),
447
441
  duration: fiber.actualDuration ?? 0,
@@ -450,25 +444,25 @@ function attachRenderLogger(options = {}) {
450
444
  causes: showCauses ? detectCauses(fiber) : []
451
445
  };
452
446
  const renderEntry = {
453
- component: pendingEntry.component,
454
- path: pendingEntry.path,
455
- duration: pendingEntry.duration,
447
+ component: highlightEntry.component,
448
+ path: highlightEntry.path,
449
+ duration: highlightEntry.duration,
456
450
  timestamp: performance.now(),
457
- causes: pendingEntry.causes
451
+ causes: highlightEntry.causes
458
452
  };
459
453
  if (filter && !filter(renderEntry)) continue;
460
454
  if (entries.length >= bufferSize) entries.shift();
461
455
  entries.push(renderEntry);
462
- pendingEntries.push(pendingEntry);
463
- batcher == null ? void 0 : batcher.push(pendingEntry);
456
+ highlightEntries.push(highlightEntry);
457
+ highlighter == null ? void 0 : highlighter.push(highlightEntry);
464
458
  }
465
459
  if (!silent) {
466
- logRerendersToConsole(pendingEntries, showCauses);
460
+ logRerendersToConsole(highlightEntries, showCauses);
467
461
  }
468
462
  };
469
- const disconnect = () => {
463
+ const stop = () => {
470
464
  hook.onCommitFiberRoot = previousOnCommit;
471
- batcher == null ? void 0 : batcher.dispose();
465
+ highlighter == null ? void 0 : highlighter.dispose();
472
466
  disposeAllOverlays();
473
467
  };
474
468
  if (!silent) {
@@ -477,9 +471,9 @@ function attachRenderLogger(options = {}) {
477
471
  "color: #61dafb; font-weight: bold"
478
472
  );
479
473
  }
480
- return { entries, disconnect };
474
+ return { entries, stop };
481
475
  }
482
476
  export {
483
- attachRenderLogger
477
+ monitor
484
478
  };
485
479
  //# sourceMappingURL=react-debug-updates.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.js","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { PendingEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: PendingEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { HighlightOptions, PendingEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Batched flush\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight refs (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\nexport const HIGHLIGHT_DEFAULTS: Required<HighlightOptions> = {\n flushInterval: 250,\n animationDuration: 1200,\n showLabels: true,\n opacity: 0.3,\n};\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createBatcher(options: Required<HighlightOptions>) {\n let pending: PendingEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(options.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${options.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (options.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: PendingEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, options.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n LoggerOptions,\n PendingEntry,\n RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { HIGHLIGHT_DEFAULTS, createBatcher } from \"./batcher.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Attach a render logger to React's DevTools hook.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.\n */\nexport function attachRenderLogger(\n options: LoggerOptions = {},\n): RenderLogger | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n highlight = false,\n mode = \"self-triggered\",\n showCauses = false,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlightOptions = highlight\n ? {\n ...HIGHLIGHT_DEFAULTS,\n ...(typeof highlight === \"object\" ? highlight : {}),\n }\n : null;\n\n const batcher = highlightOptions ? createBatcher(highlightOptions) : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const pendingEntries: PendingEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const pendingEntry: PendingEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: pendingEntry.component,\n path: pendingEntry.path,\n duration: pendingEntry.duration,\n timestamp: performance.now(),\n causes: pendingEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n pendingEntries.push(pendingEntry);\n batcher?.push(pendingEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(pendingEntries, showCauses);\n }\n };\n\n const disconnect = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n batcher?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, disconnect };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"react-debug-updates.js","sources":["../src/fiber.ts","../src/causes.ts","../src/format.ts","../src/overlay.ts","../src/highlights.ts","../src/monitor.ts"],"sourcesContent":["import type { Fiber, DetectedRender } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Fiber tag constants\n// ────────────────────────────────────────────\n\nexport const FiberTag = {\n FunctionComponent: 0,\n ClassComponent: 1,\n HostComponent: 5,\n ForwardRef: 11,\n SimpleMemoComponent: 15,\n MemoComponent: 14,\n} as const;\n\n// Component tags we report as re-renders.\n// MemoComponent (14) is excluded to avoid double-reporting — it's a wrapper\n// fiber around the inner component. The inner component (FunctionComponent,\n// ForwardRef, or SimpleMemoComponent) has its own PerformedWork flag and\n// will be reported correctly on its own.\nconst COMPONENT_TAGS = new Set<number>([\n FiberTag.FunctionComponent,\n FiberTag.ClassComponent,\n FiberTag.ForwardRef,\n FiberTag.SimpleMemoComponent,\n]);\n\nconst PerformedWork = 0b0000001;\n\n// ────────────────────────────────────────────\n// Fiber tree helpers\n// ────────────────────────────────────────────\n\nexport function getComponentName(fiber: Fiber): string | null {\n const { type } = fiber;\n if (!type || typeof type === \"string\") return null;\n return type.displayName ?? type.name ?? null;\n}\n\nfunction isHTMLElement(value: unknown): value is HTMLElement {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (value as Node).nodeType === 1 &&\n typeof (value as HTMLElement).getBoundingClientRect === \"function\"\n );\n}\n\nexport function findNearestDOMNode(fiber: Fiber): HTMLElement | null {\n if (fiber.tag === FiberTag.HostComponent && isHTMLElement(fiber.stateNode)) {\n return fiber.stateNode;\n }\n let child: Fiber | null = fiber.child;\n while (child) {\n const found = findNearestDOMNode(child);\n if (found) return found;\n child = child.sibling;\n }\n return null;\n}\n\nexport function getFiberPath(fiber: Fiber, maxDepth = 5): string {\n const parts: string[] = [];\n let current: Fiber | null = fiber.return;\n let depth = 0;\n while (current && depth < maxDepth) {\n const name = getComponentName(current);\n if (name) parts.unshift(name);\n current = current.return;\n depth++;\n }\n return parts.length ? parts.join(\" → \") : \"(root)\";\n}\n\n// ────────────────────────────────────────────\n// Self-triggered detection\n// ────────────────────────────────────────────\n\nfunction isSelfTriggered(fiber: Fiber): boolean {\n const alternate = fiber.alternate;\n if (!alternate) return false;\n return fiber.memoizedProps === alternate.memoizedProps;\n}\n\n// ────────────────────────────────────────────\n// didFiberRender — mirrors React DevTools\n// ────────────────────────────────────────────\n//\n// See: react-devtools-shared/src/backend/fiber/shared/DevToolsFiberChangeDetection.js\n//\n// For component fibers (function, class, memo, forwardRef), React sets the\n// PerformedWork flag (bit 0) only when user code actually executes.\n// createWorkInProgress resets flags to NoFlags, so PerformedWork on a\n// work-in-progress fiber is always fresh — never stale from a prior commit.\n//\n// This check must only be called AFTER confirming prevFiber !== nextFiber\n// (i.e. the fiber was actually processed, not a bailed-out subtree).\n\nfunction didFiberRender(nextFiber: Fiber): boolean {\n return (nextFiber.flags & PerformedWork) === PerformedWork;\n}\n\n// ────────────────────────────────────────────\n// Detect which components re-rendered in a commit\n// ────────────────────────────────────────────\n//\n// Mirrors React DevTools' updateFiberRecursively / updateChildrenRecursively.\n//\n// React double-buffers fibers. After a commit, root.current is the committed\n// tree and root.current.alternate is the previous tree. We walk both in\n// parallel. At each level, if prevChild and nextChild are the same object,\n// React bailed out that entire subtree (via cloneChildFibers) — we skip it.\n// Otherwise, we use nextChild.alternate as the previous fiber and check\n// didFiberRender (PerformedWork) to see if user code actually ran.\n//\n// Returns lightweight DetectedRender objects — just the fiber and its depth.\n// DOM lookup, cause detection, and formatting are the caller's responsibility.\n\nexport function detectRenders(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n): DetectedRender[] {\n const results: DetectedRender[] = [];\n const selfTriggeredOnly = mode === \"self-triggered\";\n\n // The alternate of the committed root is the previous tree's root.\n // On initial mount this is null — nothing to report.\n const previousRoot = root.alternate;\n if (!previousRoot) return results;\n\n function walk(\n nextFiber: Fiber,\n previousFiber: Fiber | null,\n depth: number,\n ) {\n // ── Check this fiber ──\n if (\n COMPONENT_TAGS.has(nextFiber.tag) &&\n previousFiber !== null &&\n previousFiber !== nextFiber && // same object → bailed-out subtree\n didFiberRender(nextFiber) &&\n (!selfTriggeredOnly || isSelfTriggered(nextFiber))\n ) {\n results.push({ fiber: nextFiber, depth });\n }\n\n // ── Walk children, matching with previous tree ──\n let nextChild = nextFiber.child;\n let previousChildAtSameIndex = previousFiber?.child ?? null;\n\n while (nextChild) {\n let matchedPrevious: Fiber | null;\n\n if (previousChildAtSameIndex === nextChild) {\n // Same object identity — React shared this fiber via cloneChildFibers.\n // The entire subtree was bailed out; passing the same object as both\n // prev and next causes the prevFiber !== nextFiber guard to skip it.\n matchedPrevious = nextChild;\n } else {\n // Different object — this fiber was processed. The alternate is the\n // corresponding fiber from the previous tree.\n matchedPrevious = nextChild.alternate;\n }\n\n walk(nextChild, matchedPrevious, depth + 1);\n\n nextChild = nextChild.sibling;\n previousChildAtSameIndex = previousChildAtSameIndex?.sibling ?? null;\n }\n }\n\n walk(root, previousRoot, 0);\n return results;\n}\n","import type { Fiber, HookNode, UpdateCause } from \"./types.js\";\nimport { FiberTag } from \"./fiber.js\";\n\n// ────────────────────────────────────────────\n// Update cause detection\n// ────────────────────────────────────────────\n//\n// For function components, React stores hooks as a linked list on\n// fiber.memoizedState. In dev builds, _debugHookTypes lists hook names\n// in call order. We walk both in parallel, comparing memoizedState\n// on each node to find which hook's state actually changed.\n//\n// Caveat: useContext does NOT create a linked list node, so we skip it\n// when advancing the pointer, and detect it by elimination.\n\n/**\n * Hooks whose memoizedState changing can trigger a re-render.\n * We only report these — useEffect/useMemo/etc. don't cause re-renders.\n */\nconst STATE_HOOKS = new Set([\n \"useState\",\n \"useReducer\",\n \"useSyncExternalStore\",\n]);\n\n/**\n * Hooks that do NOT create a node in the memoizedState linked list.\n * Currently only useContext — every other hook allocates a node.\n */\nconst HOOKS_WITHOUT_NODE = new Set([\"useContext\"]);\n\nexport function detectCauses(fiber: Fiber): UpdateCause[] {\n const alternate = fiber.alternate;\n if (!alternate) return [];\n\n const causes: UpdateCause[] = [];\n\n // ── Props changed (parent re-rendered us with new props) ──\n if (fiber.memoizedProps !== alternate.memoizedProps) {\n causes.push({ kind: \"props\" });\n }\n\n // ── Class component ──\n if (fiber.tag === FiberTag.ClassComponent) {\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"class-state\" });\n }\n return causes;\n }\n\n // ── Function component hooks ──\n const hookTypes = fiber._debugHookTypes;\n\n if (!hookTypes) {\n // No debug info (prod build) — best effort\n if (fiber.memoizedState !== alternate.memoizedState) {\n causes.push({ kind: \"unknown\" });\n }\n return causes;\n }\n\n let currentNode = fiber.memoizedState as HookNode | null;\n let previousNode = alternate.memoizedState as HookNode | null;\n let hasContextHook = false;\n let anyStateHookChanged = false;\n\n for (let i = 0; i < hookTypes.length; i++) {\n const type = hookTypes[i];\n\n // useContext has no linked list node — skip pointer advance\n if (HOOKS_WITHOUT_NODE.has(type)) {\n if (type === \"useContext\") hasContextHook = true;\n continue;\n }\n\n if (STATE_HOOKS.has(type) && currentNode && previousNode) {\n if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {\n anyStateHookChanged = true;\n causes.push({\n kind: \"hook\",\n hookIndex: i,\n hookType: type,\n previousValue: previousNode.memoizedState,\n nextValue: currentNode.memoizedState,\n });\n }\n }\n\n currentNode = currentNode?.next ?? null;\n previousNode = previousNode?.next ?? null;\n }\n\n // If no state hook changed but component is self-triggered and has\n // useContext → the context value must have changed.\n if (\n hasContextHook &&\n !anyStateHookChanged &&\n fiber.memoizedProps === alternate.memoizedProps\n ) {\n causes.push({ kind: \"hook\", hookType: \"useContext\" });\n }\n\n // If still nothing and self-triggered, mark unknown\n if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {\n causes.push({ kind: \"unknown\" });\n }\n\n return causes;\n}\n","import type { HighlightEntry, UpdateCause } from \"./types.js\";\n\n// ────────────────────────────────────────────\n// Value formatting\n// ────────────────────────────────────────────\n\nexport function formatValue(value: unknown, maxLength = 50): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"boolean\") return String(value);\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"function\")\n return `ƒ ${(value as { name?: string }).name || \"anonymous\"}`;\n\n if (typeof value === \"string\") {\n const truncated =\n value.length > maxLength ? value.slice(0, maxLength) + \"…\" : value;\n return JSON.stringify(truncated);\n }\n\n if (Array.isArray(value)) return `Array(${value.length})`;\n\n if (typeof value === \"object\") {\n const name = (value as object).constructor?.name;\n if (name && name !== \"Object\") return name;\n const keys = Object.keys(value as object);\n if (keys.length <= 3) return `{ ${keys.join(\", \")} }`;\n return `{ ${keys.slice(0, 3).join(\", \")}, … }`;\n }\n\n return String(value);\n}\n\n// ────────────────────────────────────────────\n// Cause formatting\n// ────────────────────────────────────────────\n\n/** Compact summary for overlay labels. */\nexport function formatCausesShort(causes: UpdateCause[]): string {\n if (causes.length === 0) return \"\";\n\n const parts: string[] = [];\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n parts.push(\"props\");\n } else if (cause.kind === \"class-state\") {\n parts.push(\"state\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix = cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n parts.push(`${cause.hookType}${indexSuffix}`);\n } else {\n parts.push(\"?\");\n }\n }\n\n return parts.join(\", \");\n}\n\n/** Detailed lines for console output. */\nexport function formatCausesConsole(causes: UpdateCause[]): string[] {\n const lines: string[] = [];\n\n for (const cause of causes) {\n if (cause.kind === \"props\") {\n lines.push(\" ↳ props changed (parent re-rendered)\");\n } else if (cause.kind === \"class-state\") {\n lines.push(\" ↳ class state changed\");\n } else if (cause.kind === \"hook\" && cause.hookType) {\n const indexSuffix =\n cause.hookIndex != null ? `[${cause.hookIndex}]` : \"\";\n const name = `${cause.hookType}${indexSuffix}`;\n\n if (cause.previousValue !== undefined && cause.nextValue !== undefined) {\n lines.push(\n ` ↳ ${name}: ${formatValue(cause.previousValue)} → ${formatValue(cause.nextValue)}`,\n );\n } else {\n lines.push(` ↳ ${name} changed`);\n }\n } else {\n lines.push(\" ↳ unknown cause\");\n }\n }\n\n return lines;\n}\n\n// ────────────────────────────────────────────\n// Console output\n// ────────────────────────────────────────────\n\n/** Log re-renders as a collapsed console group. */\nexport function logRerendersToConsole(\n entries: HighlightEntry[],\n showCauses: boolean,\n) {\n if (entries.length === 0) return;\n\n console.groupCollapsed(\n `%c⚛ ${entries.length} re-render${entries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const durationText =\n entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : \"\";\n console.log(\n `%c${entry.component}%c ${entry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && entry.causes.length > 0) {\n const lines = formatCausesConsole(entry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\n}\n","// ────────────────────────────────────────────\n// Per-window overlay infrastructure\n// ────────────────────────────────────────────\n//\n// Performance:\n// - Overlay elements pooled per window, reused via animationend\n// - Single CSS @keyframes per window, no JS timers per element\n\nconst ANIMATION_NAME = \"__rdu-fade\";\n\nconst injectedWindows = new WeakSet<Window>();\n\nfunction ensureStylesheet(win: Window) {\n if (injectedWindows.has(win)) return;\n injectedWindows.add(win);\n\n const style = win.document.createElement(\"style\");\n style.textContent = `\n @keyframes ${ANIMATION_NAME} {\n 0% { opacity: 0; }\n 8% { opacity: var(--rdu-opacity, 1); }\n 40% { opacity: var(--rdu-opacity, 1); }\n 100% { opacity: 0; }\n }\n .${ANIMATION_NAME}-box {\n position: fixed;\n pointer-events: none;\n box-sizing: border-box;\n border-radius: 3px;\n opacity: 0;\n will-change: opacity;\n }\n .${ANIMATION_NAME}-label {\n position: absolute;\n top: -18px;\n left: -1px;\n font: 10px/16px ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace;\n padding: 0 4px;\n color: #fff;\n border-radius: 2px 2px 0 0;\n white-space: nowrap;\n pointer-events: none;\n }\n `;\n win.document.head.appendChild(style);\n}\n\n// ────────────────────────────────────────────\n// Overlay root container\n// ────────────────────────────────────────────\n\nconst overlayRoots = new WeakMap<Window, HTMLDivElement>();\n\nfunction getOverlayRoot(win: Window): HTMLDivElement | null {\n if (win.closed) return null;\n\n const existing = overlayRoots.get(win);\n if (existing?.isConnected) return existing;\n\n ensureStylesheet(win);\n\n const root = win.document.createElement(\"div\");\n root.id = \"__react-debug-updates\";\n Object.assign(root.style, {\n position: \"fixed\",\n top: \"0\",\n left: \"0\",\n width: \"0\",\n height: \"0\",\n overflow: \"visible\",\n pointerEvents: \"none\",\n zIndex: \"2147483647\",\n } satisfies Partial<CSSStyleDeclaration>);\n win.document.body.appendChild(root);\n\n overlayRoots.set(win, root);\n return root;\n}\n\n// ────────────────────────────────────────────\n// Element pool\n// ────────────────────────────────────────────\n\nconst pools = new WeakMap<Window, HTMLDivElement[]>();\n\nexport function acquireOverlay(win: Window): HTMLDivElement | null {\n const root = getOverlayRoot(win);\n if (!root) return null;\n\n let pool = pools.get(win);\n if (!pool) {\n pool = [];\n pools.set(win, pool);\n }\n\n const document = win.document;\n let element = pool.pop();\n\n if (!element) {\n element = document.createElement(\"div\");\n element.className = `${ANIMATION_NAME}-box`;\n\n const label = document.createElement(\"span\");\n label.className = `${ANIMATION_NAME}-label`;\n element.appendChild(label);\n\n element.addEventListener(\"animationend\", () => {\n element!.style.animation = \"none\";\n element!.remove();\n pool!.push(element!);\n });\n }\n\n root.appendChild(element);\n return element;\n}\n\nexport function disposeAllOverlays() {\n const mainRoot = overlayRoots.get(window);\n mainRoot?.remove();\n overlayRoots.delete(window);\n pools.delete(window);\n}\n\nexport const OVERLAY_ANIMATION_NAME = ANIMATION_NAME;\n","import type { OverlayConfig, HighlightEntry } from \"./types.js\";\nimport { formatCausesShort } from \"./format.js\";\nimport { acquireOverlay, OVERLAY_ANIMATION_NAME } from \"./overlay.js\";\n\n// ────────────────────────────────────────────\n// Heat color\n// ────────────────────────────────────────────\n\nfunction heatColor(count: number, alpha: number): string {\n const normalizedCount = Math.min((count - 1) / 7, 1);\n const hue = 200 - normalizedCount * 200;\n const saturation = 85 + normalizedCount * 15;\n const lightness = 55 - normalizedCount * 10;\n return `hsla(${hue}, ${saturation}%, ${lightness}%, ${alpha})`;\n}\n\n// ────────────────────────────────────────────\n// Highlight scheduler\n// ────────────────────────────────────────────\n//\n// Commit path just pushes lightweight entries (no DOM reads, no formatting).\n// Flush (setInterval): batched rect reads → batched DOM writes.\n\ninterface CoalescedEntry {\n count: number;\n totalDuration: number;\n component: string;\n /** Shallowest fiber depth among coalesced entries (for z-ordering). */\n depth: number;\n domNode: HTMLElement;\n ownerWindow: Window;\n causeSummary: string;\n}\n\nexport function createHighlighter(config: OverlayConfig) {\n let pending: HighlightEntry[] = [];\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function flush() {\n if (pending.length === 0) return;\n\n const batch = pending;\n pending = [];\n\n // Coalesce by DOM node identity\n const map = new Map<HTMLElement, CoalescedEntry>();\n\n for (let i = 0; i < batch.length; i++) {\n const entry = batch[i];\n if (!entry.domNode) continue;\n\n const existing = map.get(entry.domNode);\n if (existing) {\n existing.count++;\n existing.totalDuration += entry.duration;\n existing.depth = Math.min(existing.depth, entry.depth);\n } else {\n const win = entry.domNode.ownerDocument?.defaultView;\n if (!win || win.closed) continue;\n map.set(entry.domNode, {\n count: 1,\n totalDuration: entry.duration,\n component: entry.component,\n depth: entry.depth,\n domNode: entry.domNode,\n ownerWindow: win,\n causeSummary: formatCausesShort(entry.causes),\n });\n }\n }\n\n // Read phase: batch all rect reads\n const toShow: Array<{ coalesced: CoalescedEntry; rect: DOMRect }> = [];\n for (const coalesced of map.values()) {\n const rect = coalesced.domNode.getBoundingClientRect();\n if (rect.width > 0 || rect.height > 0) {\n toShow.push({ coalesced, rect });\n }\n }\n\n // Sort deepest first so parents are appended last (render on top)\n toShow.sort((a, b) => b.coalesced.depth - a.coalesced.depth);\n\n // Write phase: position overlays\n for (let i = 0; i < toShow.length; i++) {\n const { coalesced, rect } = toShow[i];\n const element = acquireOverlay(coalesced.ownerWindow);\n if (!element) continue;\n\n const fillColor = heatColor(coalesced.count, 0.18);\n const borderColor = heatColor(coalesced.count, 0.75);\n const labelBackground = heatColor(coalesced.count, 0.9);\n\n const style = element.style;\n style.top = `${rect.top}px`;\n style.left = `${rect.left}px`;\n style.width = `${rect.width}px`;\n style.height = `${rect.height}px`;\n style.backgroundColor = fillColor;\n style.border = `1.5px solid ${borderColor}`;\n style.setProperty(\"--rdu-opacity\", String(config.opacity));\n style.animation = `${OVERLAY_ANIMATION_NAME} ${config.animationDuration}ms ease-out forwards`;\n\n const label = element.firstElementChild as HTMLElement;\n if (config.showLabels) {\n const countText = coalesced.count > 1 ? ` ×${coalesced.count}` : \"\";\n const durationText =\n coalesced.totalDuration > 0\n ? ` ${coalesced.totalDuration.toFixed(1)}ms`\n : \"\";\n const causeText = coalesced.causeSummary\n ? ` (${coalesced.causeSummary})`\n : \"\";\n label.textContent = `${coalesced.component}${countText}${durationText}${causeText}`;\n label.style.backgroundColor = labelBackground;\n } else {\n label.textContent = \"\";\n label.style.backgroundColor = \"transparent\";\n }\n }\n }\n\n function push(entry: HighlightEntry) {\n pending.push(entry);\n if (!timer) {\n timer = setInterval(flush, config.flushInterval);\n }\n }\n\n function dispose() {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n pending = [];\n }\n\n return { push, dispose };\n}\n","import type {\n DevToolsHook,\n FiberRoot,\n HighlightEntry,\n MonitorOptions,\n RenderEntry,\n UpdateMonitor,\n} from \"./types.js\";\nimport {\n detectRenders,\n findNearestDOMNode,\n getComponentName,\n getFiberPath,\n} from \"./fiber.js\";\nimport { detectCauses } from \"./causes.js\";\nimport { logRerendersToConsole } from \"./format.js\";\nimport { createHighlighter } from \"./highlights.js\";\nimport { disposeAllOverlays } from \"./overlay.js\";\n\n/**\n * Start monitoring React re-renders.\n *\n * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept\n * every React commit. Records re-render entries, optionally logs them to the\n * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.\n *\n * Call this **before** React renders anything — ideally at the very top of\n * your entry point.\n *\n * Returns an `UpdateMonitor` handle, or `null` if the DevTools hook is not found.\n */\nexport function monitor(options: MonitorOptions = {}): UpdateMonitor | null {\n const {\n silent = false,\n bufferSize = 500,\n filter,\n overlay = true,\n mode = \"self-triggered\",\n showCauses = false,\n flushInterval = 250,\n animationDuration = 1200,\n showLabels = true,\n opacity = 0.3,\n } = options;\n\n const hook = (\n window as unknown as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook }\n ).__REACT_DEVTOOLS_GLOBAL_HOOK__;\n\n if (!hook) {\n console.warn(\n \"[react-debug-updates] __REACT_DEVTOOLS_GLOBAL_HOOK__ not found. \" +\n \"Make sure React DevTools or a dev build of React is active.\",\n );\n return null;\n }\n\n const highlighter = overlay\n ? createHighlighter({ flushInterval, animationDuration, showLabels, opacity })\n : null;\n\n const entries: RenderEntry[] = [];\n const previousOnCommit = hook.onCommitFiberRoot.bind(hook);\n\n hook.onCommitFiberRoot = (\n rendererID: number,\n root: FiberRoot,\n priorityLevel?: unknown,\n ) => {\n previousOnCommit(rendererID, root, priorityLevel);\n\n // 1. Detect which component fibers actually re-rendered (pure fiber analysis)\n const detectedRenders = detectRenders(root.current, mode);\n if (detectedRenders.length === 0) return;\n\n // 2. Build full entries: resolve names, DOM nodes, causes\n const highlightEntries: HighlightEntry[] = [];\n\n for (let i = 0; i < detectedRenders.length; i++) {\n const { fiber, depth } = detectedRenders[i];\n const name = getComponentName(fiber);\n if (!name) continue;\n\n const highlightEntry: HighlightEntry = {\n component: name,\n path: getFiberPath(fiber),\n duration: fiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(fiber),\n causes: showCauses ? detectCauses(fiber) : [],\n };\n\n const renderEntry: RenderEntry = {\n component: highlightEntry.component,\n path: highlightEntry.path,\n duration: highlightEntry.duration,\n timestamp: performance.now(),\n causes: highlightEntry.causes,\n };\n\n if (filter && !filter(renderEntry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(renderEntry);\n\n highlightEntries.push(highlightEntry);\n highlighter?.push(highlightEntry);\n }\n\n // 3. Console output\n if (!silent) {\n logRerendersToConsole(highlightEntries, showCauses);\n }\n };\n\n const stop = () => {\n hook.onCommitFiberRoot = previousOnCommit;\n highlighter?.dispose();\n disposeAllOverlays();\n };\n\n if (!silent) {\n console.log(\n \"%c⚛ react-debug-updates attached\",\n \"color: #61dafb; font-weight: bold\",\n );\n }\n\n return { entries, stop };\n}\n"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -42,7 +42,7 @@ export interface DetectedRender {
42
42
  fiber: Fiber;
43
43
  depth: number;
44
44
  }
45
- export interface PendingEntry {
45
+ export interface HighlightEntry {
46
46
  component: string;
47
47
  path: string;
48
48
  duration: number;
@@ -50,15 +50,11 @@ export interface PendingEntry {
50
50
  domNode: HTMLElement | null;
51
51
  causes: UpdateCause[];
52
52
  }
53
- export interface HighlightOptions {
54
- /** Time between flushes (ms). Default: 250 */
55
- flushInterval?: number;
56
- /** Total animation duration (ms). Default: 1200 */
57
- animationDuration?: number;
58
- /** Show text labels (component name, count, duration, cause) above overlays. Default: true */
59
- showLabels?: boolean;
60
- /** Peak opacity of overlay highlights (0–1). Default: 0.3 */
61
- opacity?: number;
53
+ export interface OverlayConfig {
54
+ flushInterval: number;
55
+ animationDuration: number;
56
+ showLabels: boolean;
57
+ opacity: number;
62
58
  }
63
59
  export interface RenderEntry {
64
60
  component: string;
@@ -67,7 +63,7 @@ export interface RenderEntry {
67
63
  timestamp: number;
68
64
  causes: UpdateCause[];
69
65
  }
70
- export interface LoggerOptions {
66
+ export interface MonitorOptions {
71
67
  /** Suppress console output. Default: false */
72
68
  silent?: boolean;
73
69
  /**
@@ -82,8 +78,6 @@ export interface LoggerOptions {
82
78
  bufferSize?: number;
83
79
  /** Filter — return `false` to skip an entry. */
84
80
  filter?: (entry: RenderEntry) => boolean;
85
- /** Enable visual highlight overlays. `true` for defaults, or pass options. */
86
- highlight?: boolean | HighlightOptions;
87
81
  /**
88
82
  * Detect and display *why* each component re-rendered.
89
83
  *
@@ -92,10 +86,20 @@ export interface LoggerOptions {
92
86
  * Requires a React dev build (`_debugHookTypes`). Default: `false`
93
87
  */
94
88
  showCauses?: boolean;
89
+ /** Enable visual highlight overlays. Default: true */
90
+ overlay?: boolean;
91
+ /** Show text labels (component name, count, duration, cause) above overlays. Default: true */
92
+ showLabels?: boolean;
93
+ /** Peak opacity of overlay highlights (0–1). Default: 0.3 */
94
+ opacity?: number;
95
+ /** Time between overlay flush cycles (ms). Default: 250 */
96
+ flushInterval?: number;
97
+ /** Overlay fade-out animation duration (ms). Default: 1200 */
98
+ animationDuration?: number;
95
99
  }
96
- export interface RenderLogger {
100
+ export interface UpdateMonitor {
97
101
  /** Ring buffer of recorded re-render entries. */
98
102
  entries: RenderEntry[];
99
103
  /** Unhook from React and clean up overlays. */
100
- disconnect: () => void;
104
+ stop: () => void;
101
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-debug-updates",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "See why React components re-render — visual overlays, console logging, and hook-level cause detection with zero code changes",
5
5
  "type": "module",
6
6
  "main": "./dist/react-debug-updates.cjs",
package/dist/batcher.d.ts DELETED
@@ -1,6 +0,0 @@
1
- import type { HighlightOptions, PendingEntry } from "./types.js";
2
- export declare const HIGHLIGHT_DEFAULTS: Required<HighlightOptions>;
3
- export declare function createBatcher(options: Required<HighlightOptions>): {
4
- push: (entry: PendingEntry) => void;
5
- dispose: () => void;
6
- };
package/dist/logger.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import type { LoggerOptions, RenderLogger } from "./types.js";
2
- /**
3
- * Attach a render logger to React's DevTools hook.
4
- *
5
- * Hooks into `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` to intercept
6
- * every React commit. Records re-render entries, optionally logs them to the
7
- * console, and optionally shows visual highlight overlays on re-rendered DOM nodes.
8
- *
9
- * Returns a `RenderLogger` handle, or `null` if the DevTools hook is not found.
10
- */
11
- export declare function attachRenderLogger(options?: LoggerOptions): RenderLogger | null;