react-debug-updates 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fiber.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Fiber, PendingEntry } from "./types.js";
1
+ import type { Fiber, DetectedRender } from "./types.js";
2
2
  export declare const FiberTag: {
3
3
  readonly FunctionComponent: 0;
4
4
  readonly ClassComponent: 1;
@@ -10,4 +10,4 @@ export declare const FiberTag: {
10
10
  export declare function getComponentName(fiber: Fiber): string | null;
11
11
  export declare function findNearestDOMNode(fiber: Fiber): HTMLElement | null;
12
12
  export declare function getFiberPath(fiber: Fiber, maxDepth?: number): string;
13
- export declare function collectPending(root: Fiber, mode: "self-triggered" | "all", trackCauses: boolean): PendingEntry[];
13
+ export declare function detectRenders(root: Fiber, mode: "self-triggered" | "all"): DetectedRender[];
package/dist/format.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { UpdateCause } from "./types.js";
1
+ import type { PendingEntry, 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
+ /** Log re-renders as a collapsed console group. */
8
+ export declare function logRerendersToConsole(entries: PendingEntry[], showCauses: boolean): void;
@@ -1,64 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const STATE_HOOKS = /* @__PURE__ */ new Set([
4
- "useState",
5
- "useReducer",
6
- "useSyncExternalStore"
7
- ]);
8
- const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
9
- function detectCauses(fiber) {
10
- const alternate = fiber.alternate;
11
- if (!alternate) return [];
12
- const causes = [];
13
- if (fiber.memoizedProps !== alternate.memoizedProps) {
14
- causes.push({ kind: "props" });
15
- }
16
- if (fiber.tag === FiberTag.ClassComponent) {
17
- if (fiber.memoizedState !== alternate.memoizedState) {
18
- causes.push({ kind: "class-state" });
19
- }
20
- return causes;
21
- }
22
- const hookTypes = fiber._debugHookTypes;
23
- if (!hookTypes) {
24
- if (fiber.memoizedState !== alternate.memoizedState) {
25
- causes.push({ kind: "unknown" });
26
- }
27
- return causes;
28
- }
29
- let currentNode = fiber.memoizedState;
30
- let previousNode = alternate.memoizedState;
31
- let hasContextHook = false;
32
- let anyStateHookChanged = false;
33
- for (let i = 0; i < hookTypes.length; i++) {
34
- const type = hookTypes[i];
35
- if (HOOKS_WITHOUT_NODE.has(type)) {
36
- if (type === "useContext") hasContextHook = true;
37
- continue;
38
- }
39
- if (STATE_HOOKS.has(type) && currentNode && previousNode) {
40
- if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
41
- anyStateHookChanged = true;
42
- causes.push({
43
- kind: "hook",
44
- hookIndex: i,
45
- hookType: type,
46
- previousValue: previousNode.memoizedState,
47
- nextValue: currentNode.memoizedState
48
- });
49
- }
50
- }
51
- currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
52
- previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
53
- }
54
- if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
55
- causes.push({ kind: "hook", hookType: "useContext" });
56
- }
57
- if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
58
- causes.push({ kind: "unknown" });
59
- }
60
- return causes;
61
- }
62
3
  const FiberTag = {
63
4
  FunctionComponent: 0,
64
5
  ClassComponent: 1,
@@ -113,25 +54,15 @@ function isSelfTriggered(fiber) {
113
54
  function didFiberRender(nextFiber) {
114
55
  return (nextFiber.flags & PerformedWork) === PerformedWork;
115
56
  }
116
- function collectPending(root, mode, trackCauses) {
117
- const entries = [];
57
+ function detectRenders(root, mode) {
58
+ const results = [];
118
59
  const selfTriggeredOnly = mode === "self-triggered";
119
60
  const previousRoot = root.alternate;
120
- if (!previousRoot) return entries;
61
+ if (!previousRoot) return results;
121
62
  function walk(nextFiber, previousFiber, depth) {
122
63
  if (COMPONENT_TAGS.has(nextFiber.tag) && previousFiber !== null && previousFiber !== nextFiber && // same object → bailed-out subtree
123
64
  didFiberRender(nextFiber) && (!selfTriggeredOnly || isSelfTriggered(nextFiber))) {
124
- const name = getComponentName(nextFiber);
125
- if (name) {
126
- entries.push({
127
- component: name,
128
- path: getFiberPath(nextFiber),
129
- duration: nextFiber.actualDuration ?? 0,
130
- depth,
131
- domNode: findNearestDOMNode(nextFiber),
132
- causes: trackCauses ? detectCauses(nextFiber) : []
133
- });
134
- }
65
+ results.push({ fiber: nextFiber, depth });
135
66
  }
136
67
  let nextChild = nextFiber.child;
137
68
  let previousChildAtSameIndex = (previousFiber == null ? void 0 : previousFiber.child) ?? null;
@@ -148,7 +79,66 @@ function collectPending(root, mode, trackCauses) {
148
79
  }
149
80
  }
150
81
  walk(root, previousRoot, 0);
151
- return entries;
82
+ return results;
83
+ }
84
+ const STATE_HOOKS = /* @__PURE__ */ new Set([
85
+ "useState",
86
+ "useReducer",
87
+ "useSyncExternalStore"
88
+ ]);
89
+ const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
90
+ function detectCauses(fiber) {
91
+ const alternate = fiber.alternate;
92
+ if (!alternate) return [];
93
+ const causes = [];
94
+ if (fiber.memoizedProps !== alternate.memoizedProps) {
95
+ causes.push({ kind: "props" });
96
+ }
97
+ if (fiber.tag === FiberTag.ClassComponent) {
98
+ if (fiber.memoizedState !== alternate.memoizedState) {
99
+ causes.push({ kind: "class-state" });
100
+ }
101
+ return causes;
102
+ }
103
+ const hookTypes = fiber._debugHookTypes;
104
+ if (!hookTypes) {
105
+ if (fiber.memoizedState !== alternate.memoizedState) {
106
+ causes.push({ kind: "unknown" });
107
+ }
108
+ return causes;
109
+ }
110
+ let currentNode = fiber.memoizedState;
111
+ let previousNode = alternate.memoizedState;
112
+ let hasContextHook = false;
113
+ let anyStateHookChanged = false;
114
+ for (let i = 0; i < hookTypes.length; i++) {
115
+ const type = hookTypes[i];
116
+ if (HOOKS_WITHOUT_NODE.has(type)) {
117
+ if (type === "useContext") hasContextHook = true;
118
+ continue;
119
+ }
120
+ if (STATE_HOOKS.has(type) && currentNode && previousNode) {
121
+ if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
122
+ anyStateHookChanged = true;
123
+ causes.push({
124
+ kind: "hook",
125
+ hookIndex: i,
126
+ hookType: type,
127
+ previousValue: previousNode.memoizedState,
128
+ nextValue: currentNode.memoizedState
129
+ });
130
+ }
131
+ }
132
+ currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
133
+ previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
134
+ }
135
+ if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
136
+ causes.push({ kind: "hook", hookType: "useContext" });
137
+ }
138
+ if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
139
+ causes.push({ kind: "unknown" });
140
+ }
141
+ return causes;
152
142
  }
153
143
  function formatValue(value, maxLength = 50) {
154
144
  var _a;
@@ -212,6 +202,29 @@ function formatCausesConsole(causes) {
212
202
  }
213
203
  return lines;
214
204
  }
205
+ function logRerendersToConsole(entries, showCauses) {
206
+ if (entries.length === 0) return;
207
+ console.groupCollapsed(
208
+ `%c⚛ ${entries.length} re-render${entries.length > 1 ? "s" : ""}`,
209
+ "color: #61dafb; font-weight: bold"
210
+ );
211
+ for (let i = 0; i < entries.length; i++) {
212
+ const entry = entries[i];
213
+ const durationText = entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : "";
214
+ console.log(
215
+ `%c${entry.component}%c ${entry.path}${durationText}`,
216
+ "color: #e8e82e; font-weight: bold",
217
+ "color: #888"
218
+ );
219
+ if (showCauses && entry.causes.length > 0) {
220
+ const lines = formatCausesConsole(entry.causes);
221
+ for (const line of lines) {
222
+ console.log(`%c${line}`, "color: #aaa");
223
+ }
224
+ }
225
+ }
226
+ console.groupEnd();
227
+ }
215
228
  const ANIMATION_NAME = "__rdu-fade";
216
229
  const injectedWindows = /* @__PURE__ */ new WeakSet();
217
230
  function ensureStylesheet(win) {
@@ -423,42 +436,36 @@ function attachRenderLogger(options = {}) {
423
436
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
424
437
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
425
438
  previousOnCommit(rendererID, root, priorityLevel);
426
- const pendingEntries = collectPending(root.current, mode, showCauses);
427
- for (let i = 0; i < pendingEntries.length; i++) {
428
- const pendingEntry = pendingEntries[i];
429
- const entry = {
439
+ const detectedRenders = detectRenders(root.current, mode);
440
+ if (detectedRenders.length === 0) return;
441
+ const pendingEntries = [];
442
+ for (let i = 0; i < detectedRenders.length; i++) {
443
+ const { fiber, depth } = detectedRenders[i];
444
+ const name = getComponentName(fiber);
445
+ if (!name) continue;
446
+ const pendingEntry = {
447
+ component: name,
448
+ path: getFiberPath(fiber),
449
+ duration: fiber.actualDuration ?? 0,
450
+ depth,
451
+ domNode: findNearestDOMNode(fiber),
452
+ causes: showCauses ? detectCauses(fiber) : []
453
+ };
454
+ const renderEntry = {
430
455
  component: pendingEntry.component,
431
456
  path: pendingEntry.path,
432
457
  duration: pendingEntry.duration,
433
458
  timestamp: performance.now(),
434
459
  causes: pendingEntry.causes
435
460
  };
436
- if (filter && !filter(entry)) continue;
461
+ if (filter && !filter(renderEntry)) continue;
437
462
  if (entries.length >= bufferSize) entries.shift();
438
- entries.push(entry);
463
+ entries.push(renderEntry);
464
+ pendingEntries.push(pendingEntry);
439
465
  batcher == null ? void 0 : batcher.push(pendingEntry);
440
466
  }
441
- if (!silent && pendingEntries.length > 0) {
442
- console.groupCollapsed(
443
- `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? "s" : ""}`,
444
- "color: #61dafb; font-weight: bold"
445
- );
446
- for (let i = 0; i < pendingEntries.length; i++) {
447
- const pendingEntry = pendingEntries[i];
448
- const durationText = pendingEntry.duration > 0 ? ` (${pendingEntry.duration.toFixed(2)}ms)` : "";
449
- console.log(
450
- `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,
451
- "color: #e8e82e; font-weight: bold",
452
- "color: #888"
453
- );
454
- if (showCauses && pendingEntry.causes.length > 0) {
455
- const lines = formatCausesConsole(pendingEntry.causes);
456
- for (const line of lines) {
457
- console.log(`%c${line}`, "color: #aaa");
458
- }
459
- }
460
- }
461
- console.groupEnd();
467
+ if (!silent) {
468
+ logRerendersToConsole(pendingEntries, showCauses);
462
469
  }
463
470
  };
464
471
  const disconnect = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.cjs","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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// Collect re-rendered components from 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\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\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 entries;\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 const name = getComponentName(nextFiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(nextFiber),\n duration: nextFiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(nextFiber),\n causes: trackCauses ? detectCauses(nextFiber) : [],\n });\n }\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 entries;\n}\n","import type { 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// 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 RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: 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(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\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":";;AAmBA,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;ACrGO,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;AAeO,SAAS,eACd,MACA,MACA,aACgB;AAChB,QAAM,UAA0B,CAAA;AAChC,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,YAAM,OAAO,iBAAiB,SAAS;AACvC,UAAI,MAAM;AACR,gBAAQ,KAAK;AAAA,UACX,WAAW;AAAA,UACX,MAAM,aAAa,SAAS;AAAA,UAC5B,UAAU,UAAU,kBAAkB;AAAA,UACtC;AAAA,UACA,SAAS,mBAAmB,SAAS;AAAA,UACrC,QAAQ,cAAc,aAAa,SAAS,IAAI,CAAA;AAAA,QAAC,CAClD;AAAA,MACH;AAAA,IACF;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;AChLO,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;AC7EA,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;AC5HO,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;AAEhD,UAAM,iBAAiB,eAAe,KAAK,SAAS,MAAM,UAAU;AAEpE,aAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,YAAM,eAAe,eAAe,CAAC;AAErC,YAAM,QAAqB;AAAA,QACzB,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,KAAK,EAAG;AAE9B,UAAI,QAAQ,UAAU,WAAY,SAAQ,MAAA;AAC1C,cAAQ,KAAK,KAAK;AAElB,yCAAS,KAAK;AAAA,IAChB;AAGA,QAAI,CAAC,UAAU,eAAe,SAAS,GAAG;AACxC,cAAQ;AAAA,QACN,OAAO,eAAe,MAAM,aAAa,eAAe,SAAS,IAAI,MAAM,EAAE;AAAA,QAC7E;AAAA,MAAA;AAGF,eAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,cAAM,eAAe,eAAe,CAAC;AACrC,cAAM,eACJ,aAAa,WAAW,IACpB,KAAK,aAAa,SAAS,QAAQ,CAAC,CAAC,QACrC;AACN,gBAAQ;AAAA,UACN,KAAK,aAAa,SAAS,MAAM,aAAa,IAAI,GAAG,YAAY;AAAA,UACjE;AAAA,UACA;AAAA,QAAA;AAGF,YAAI,cAAc,aAAa,OAAO,SAAS,GAAG;AAChD,gBAAM,QAAQ,oBAAoB,aAAa,MAAM;AACrD,qBAAW,QAAQ,OAAO;AACxB,oBAAQ,IAAI,KAAK,IAAI,IAAI,aAAa;AAAA,UACxC;AAAA,QACF;AAAA,MACF;AAEA,cAAQ,SAAA;AAAA,IACV;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/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,62 +1,3 @@
1
- const STATE_HOOKS = /* @__PURE__ */ new Set([
2
- "useState",
3
- "useReducer",
4
- "useSyncExternalStore"
5
- ]);
6
- const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
7
- function detectCauses(fiber) {
8
- const alternate = fiber.alternate;
9
- if (!alternate) return [];
10
- const causes = [];
11
- if (fiber.memoizedProps !== alternate.memoizedProps) {
12
- causes.push({ kind: "props" });
13
- }
14
- if (fiber.tag === FiberTag.ClassComponent) {
15
- if (fiber.memoizedState !== alternate.memoizedState) {
16
- causes.push({ kind: "class-state" });
17
- }
18
- return causes;
19
- }
20
- const hookTypes = fiber._debugHookTypes;
21
- if (!hookTypes) {
22
- if (fiber.memoizedState !== alternate.memoizedState) {
23
- causes.push({ kind: "unknown" });
24
- }
25
- return causes;
26
- }
27
- let currentNode = fiber.memoizedState;
28
- let previousNode = alternate.memoizedState;
29
- let hasContextHook = false;
30
- let anyStateHookChanged = false;
31
- for (let i = 0; i < hookTypes.length; i++) {
32
- const type = hookTypes[i];
33
- if (HOOKS_WITHOUT_NODE.has(type)) {
34
- if (type === "useContext") hasContextHook = true;
35
- continue;
36
- }
37
- if (STATE_HOOKS.has(type) && currentNode && previousNode) {
38
- if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
39
- anyStateHookChanged = true;
40
- causes.push({
41
- kind: "hook",
42
- hookIndex: i,
43
- hookType: type,
44
- previousValue: previousNode.memoizedState,
45
- nextValue: currentNode.memoizedState
46
- });
47
- }
48
- }
49
- currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
50
- previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
51
- }
52
- if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
53
- causes.push({ kind: "hook", hookType: "useContext" });
54
- }
55
- if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
56
- causes.push({ kind: "unknown" });
57
- }
58
- return causes;
59
- }
60
1
  const FiberTag = {
61
2
  FunctionComponent: 0,
62
3
  ClassComponent: 1,
@@ -111,25 +52,15 @@ function isSelfTriggered(fiber) {
111
52
  function didFiberRender(nextFiber) {
112
53
  return (nextFiber.flags & PerformedWork) === PerformedWork;
113
54
  }
114
- function collectPending(root, mode, trackCauses) {
115
- const entries = [];
55
+ function detectRenders(root, mode) {
56
+ const results = [];
116
57
  const selfTriggeredOnly = mode === "self-triggered";
117
58
  const previousRoot = root.alternate;
118
- if (!previousRoot) return entries;
59
+ if (!previousRoot) return results;
119
60
  function walk(nextFiber, previousFiber, depth) {
120
61
  if (COMPONENT_TAGS.has(nextFiber.tag) && previousFiber !== null && previousFiber !== nextFiber && // same object → bailed-out subtree
121
62
  didFiberRender(nextFiber) && (!selfTriggeredOnly || isSelfTriggered(nextFiber))) {
122
- const name = getComponentName(nextFiber);
123
- if (name) {
124
- entries.push({
125
- component: name,
126
- path: getFiberPath(nextFiber),
127
- duration: nextFiber.actualDuration ?? 0,
128
- depth,
129
- domNode: findNearestDOMNode(nextFiber),
130
- causes: trackCauses ? detectCauses(nextFiber) : []
131
- });
132
- }
63
+ results.push({ fiber: nextFiber, depth });
133
64
  }
134
65
  let nextChild = nextFiber.child;
135
66
  let previousChildAtSameIndex = (previousFiber == null ? void 0 : previousFiber.child) ?? null;
@@ -146,7 +77,66 @@ function collectPending(root, mode, trackCauses) {
146
77
  }
147
78
  }
148
79
  walk(root, previousRoot, 0);
149
- return entries;
80
+ return results;
81
+ }
82
+ const STATE_HOOKS = /* @__PURE__ */ new Set([
83
+ "useState",
84
+ "useReducer",
85
+ "useSyncExternalStore"
86
+ ]);
87
+ const HOOKS_WITHOUT_NODE = /* @__PURE__ */ new Set(["useContext"]);
88
+ function detectCauses(fiber) {
89
+ const alternate = fiber.alternate;
90
+ if (!alternate) return [];
91
+ const causes = [];
92
+ if (fiber.memoizedProps !== alternate.memoizedProps) {
93
+ causes.push({ kind: "props" });
94
+ }
95
+ if (fiber.tag === FiberTag.ClassComponent) {
96
+ if (fiber.memoizedState !== alternate.memoizedState) {
97
+ causes.push({ kind: "class-state" });
98
+ }
99
+ return causes;
100
+ }
101
+ const hookTypes = fiber._debugHookTypes;
102
+ if (!hookTypes) {
103
+ if (fiber.memoizedState !== alternate.memoizedState) {
104
+ causes.push({ kind: "unknown" });
105
+ }
106
+ return causes;
107
+ }
108
+ let currentNode = fiber.memoizedState;
109
+ let previousNode = alternate.memoizedState;
110
+ let hasContextHook = false;
111
+ let anyStateHookChanged = false;
112
+ for (let i = 0; i < hookTypes.length; i++) {
113
+ const type = hookTypes[i];
114
+ if (HOOKS_WITHOUT_NODE.has(type)) {
115
+ if (type === "useContext") hasContextHook = true;
116
+ continue;
117
+ }
118
+ if (STATE_HOOKS.has(type) && currentNode && previousNode) {
119
+ if (!Object.is(currentNode.memoizedState, previousNode.memoizedState)) {
120
+ anyStateHookChanged = true;
121
+ causes.push({
122
+ kind: "hook",
123
+ hookIndex: i,
124
+ hookType: type,
125
+ previousValue: previousNode.memoizedState,
126
+ nextValue: currentNode.memoizedState
127
+ });
128
+ }
129
+ }
130
+ currentNode = (currentNode == null ? void 0 : currentNode.next) ?? null;
131
+ previousNode = (previousNode == null ? void 0 : previousNode.next) ?? null;
132
+ }
133
+ if (hasContextHook && !anyStateHookChanged && fiber.memoizedProps === alternate.memoizedProps) {
134
+ causes.push({ kind: "hook", hookType: "useContext" });
135
+ }
136
+ if (causes.length === 0 && fiber.memoizedProps === alternate.memoizedProps) {
137
+ causes.push({ kind: "unknown" });
138
+ }
139
+ return causes;
150
140
  }
151
141
  function formatValue(value, maxLength = 50) {
152
142
  var _a;
@@ -210,6 +200,29 @@ function formatCausesConsole(causes) {
210
200
  }
211
201
  return lines;
212
202
  }
203
+ function logRerendersToConsole(entries, showCauses) {
204
+ if (entries.length === 0) return;
205
+ console.groupCollapsed(
206
+ `%c⚛ ${entries.length} re-render${entries.length > 1 ? "s" : ""}`,
207
+ "color: #61dafb; font-weight: bold"
208
+ );
209
+ for (let i = 0; i < entries.length; i++) {
210
+ const entry = entries[i];
211
+ const durationText = entry.duration > 0 ? ` (${entry.duration.toFixed(2)}ms)` : "";
212
+ console.log(
213
+ `%c${entry.component}%c ${entry.path}${durationText}`,
214
+ "color: #e8e82e; font-weight: bold",
215
+ "color: #888"
216
+ );
217
+ if (showCauses && entry.causes.length > 0) {
218
+ const lines = formatCausesConsole(entry.causes);
219
+ for (const line of lines) {
220
+ console.log(`%c${line}`, "color: #aaa");
221
+ }
222
+ }
223
+ }
224
+ console.groupEnd();
225
+ }
213
226
  const ANIMATION_NAME = "__rdu-fade";
214
227
  const injectedWindows = /* @__PURE__ */ new WeakSet();
215
228
  function ensureStylesheet(win) {
@@ -421,42 +434,36 @@ function attachRenderLogger(options = {}) {
421
434
  const previousOnCommit = hook.onCommitFiberRoot.bind(hook);
422
435
  hook.onCommitFiberRoot = (rendererID, root, priorityLevel) => {
423
436
  previousOnCommit(rendererID, root, priorityLevel);
424
- const pendingEntries = collectPending(root.current, mode, showCauses);
425
- for (let i = 0; i < pendingEntries.length; i++) {
426
- const pendingEntry = pendingEntries[i];
427
- const entry = {
437
+ const detectedRenders = detectRenders(root.current, mode);
438
+ if (detectedRenders.length === 0) return;
439
+ const pendingEntries = [];
440
+ for (let i = 0; i < detectedRenders.length; i++) {
441
+ const { fiber, depth } = detectedRenders[i];
442
+ const name = getComponentName(fiber);
443
+ if (!name) continue;
444
+ const pendingEntry = {
445
+ component: name,
446
+ path: getFiberPath(fiber),
447
+ duration: fiber.actualDuration ?? 0,
448
+ depth,
449
+ domNode: findNearestDOMNode(fiber),
450
+ causes: showCauses ? detectCauses(fiber) : []
451
+ };
452
+ const renderEntry = {
428
453
  component: pendingEntry.component,
429
454
  path: pendingEntry.path,
430
455
  duration: pendingEntry.duration,
431
456
  timestamp: performance.now(),
432
457
  causes: pendingEntry.causes
433
458
  };
434
- if (filter && !filter(entry)) continue;
459
+ if (filter && !filter(renderEntry)) continue;
435
460
  if (entries.length >= bufferSize) entries.shift();
436
- entries.push(entry);
461
+ entries.push(renderEntry);
462
+ pendingEntries.push(pendingEntry);
437
463
  batcher == null ? void 0 : batcher.push(pendingEntry);
438
464
  }
439
- if (!silent && pendingEntries.length > 0) {
440
- console.groupCollapsed(
441
- `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? "s" : ""}`,
442
- "color: #61dafb; font-weight: bold"
443
- );
444
- for (let i = 0; i < pendingEntries.length; i++) {
445
- const pendingEntry = pendingEntries[i];
446
- const durationText = pendingEntry.duration > 0 ? ` (${pendingEntry.duration.toFixed(2)}ms)` : "";
447
- console.log(
448
- `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,
449
- "color: #e8e82e; font-weight: bold",
450
- "color: #888"
451
- );
452
- if (showCauses && pendingEntry.causes.length > 0) {
453
- const lines = formatCausesConsole(pendingEntry.causes);
454
- for (const line of lines) {
455
- console.log(`%c${line}`, "color: #aaa");
456
- }
457
- }
458
- }
459
- console.groupEnd();
465
+ if (!silent) {
466
+ logRerendersToConsole(pendingEntries, showCauses);
460
467
  }
461
468
  };
462
469
  const disconnect = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react-debug-updates.js","sources":["../src/causes.ts","../src/fiber.ts","../src/format.ts","../src/overlay.ts","../src/batcher.ts","../src/logger.ts"],"sourcesContent":["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 { Fiber, PendingEntry } from \"./types.js\";\nimport { detectCauses } from \"./causes.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// Collect re-rendered components from 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\nexport function collectPending(\n root: Fiber,\n mode: \"self-triggered\" | \"all\",\n trackCauses: boolean,\n): PendingEntry[] {\n const entries: PendingEntry[] = [];\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 entries;\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 const name = getComponentName(nextFiber);\n if (name) {\n entries.push({\n component: name,\n path: getFiberPath(nextFiber),\n duration: nextFiber.actualDuration ?? 0,\n depth,\n domNode: findNearestDOMNode(nextFiber),\n causes: trackCauses ? detectCauses(nextFiber) : [],\n });\n }\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 entries;\n}\n","import type { 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// 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 RenderEntry,\n RenderLogger,\n} from \"./types.js\";\nimport { collectPending } from \"./fiber.js\";\nimport { formatCausesConsole } 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 const pendingEntries = collectPending(root.current, mode, showCauses);\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n\n const entry: 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(entry)) continue;\n\n if (entries.length >= bufferSize) entries.shift();\n entries.push(entry);\n\n batcher?.push(pendingEntry);\n }\n\n // Console output\n if (!silent && pendingEntries.length > 0) {\n console.groupCollapsed(\n `%c⚛ ${pendingEntries.length} re-render${pendingEntries.length > 1 ? \"s\" : \"\"}`,\n \"color: #61dafb; font-weight: bold\",\n );\n\n for (let i = 0; i < pendingEntries.length; i++) {\n const pendingEntry = pendingEntries[i];\n const durationText =\n pendingEntry.duration > 0\n ? ` (${pendingEntry.duration.toFixed(2)}ms)`\n : \"\";\n console.log(\n `%c${pendingEntry.component}%c ${pendingEntry.path}${durationText}`,\n \"color: #e8e82e; font-weight: bold\",\n \"color: #888\",\n );\n\n if (showCauses && pendingEntry.causes.length > 0) {\n const lines = formatCausesConsole(pendingEntry.causes);\n for (const line of lines) {\n console.log(`%c${line}`, \"color: #aaa\");\n }\n }\n }\n\n console.groupEnd();\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/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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"}
package/dist/types.d.ts CHANGED
@@ -37,6 +37,11 @@ export interface UpdateCause {
37
37
  previousValue?: unknown;
38
38
  nextValue?: unknown;
39
39
  }
40
+ /** A component fiber that was detected as re-rendered during a commit. */
41
+ export interface DetectedRender {
42
+ fiber: Fiber;
43
+ depth: number;
44
+ }
40
45
  export interface PendingEntry {
41
46
  component: string;
42
47
  path: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-debug-updates",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",