react-render-detective 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { D as DetectiveConfig, T as TrackedStateChange, R as RenderReason, a as DetectiveOptions, b as RenderEvent, c as RenderPhase, C as ContextChange, d as ComponentStats, A as AppStats, P as PropChange, e as Thresholds, f as Diagnosis, I as Inspected, g as InspectionLimits, h as PropValueType } from './types-CY61yfhK.js';
2
+ export { i as ComponentInfo, j as Confidence, M as Mode, k as PropChangeKind, l as RenderTimings } from './types-CY61yfhK.js';
3
+
4
+ /** What an instrumentation site reports at commit time. */
5
+ interface CommitInput {
6
+ phase: RenderPhase;
7
+ subtreeDuration: number;
8
+ baseDuration: number;
9
+ startTime: number;
10
+ /** `-1` when there is no Profiler; the flush pass resolves it. */
11
+ commitTime: number;
12
+ }
13
+ interface NodeRecord {
14
+ id: string;
15
+ name: string;
16
+ /** Nearest instrumented ancestor, held directly so diagnosis never needs the registry. */
17
+ parent?: NodeRecord;
18
+ depth: number;
19
+ sampled: boolean;
20
+ /** Retained so the next render can be diffed. One props object per component. */
21
+ prevProps?: Record<string, unknown>;
22
+ pendingProps?: Record<string, unknown>;
23
+ /** Render-function invocations not yet matched to a commit. */
24
+ attempts: number;
25
+ /** Named state updates reported by `useTrackedState`, consumed at the next commit. */
26
+ pendingState: TrackedStateChange[];
27
+ /** Set as soon as a commit is queued, so mount detection never waits on flush. */
28
+ seenCommit: boolean;
29
+ /**
30
+ * `useId` of the first component to call `useTrackedState` under this node.
31
+ * Later callers (descendants) cannot claim state attribution from it.
32
+ */
33
+ stateOwner?: string;
34
+ renderNumber: number;
35
+ lastCommitTime: number;
36
+ durations: number[];
37
+ stats: MutableStats;
38
+ }
39
+ interface MutableStats {
40
+ renderCount: number;
41
+ mountCount: number;
42
+ uncommittedAttempts: number;
43
+ devReplays: number;
44
+ totalSelfDuration: number;
45
+ maxSelfDuration: number;
46
+ slowRenders: number;
47
+ potentiallyAvoidableRenders: number;
48
+ reasons: Record<RenderReason, number>;
49
+ }
50
+ declare class Detective {
51
+ config: DetectiveConfig;
52
+ private nodes;
53
+ private events;
54
+ private listeners;
55
+ private pending;
56
+ private contextChanges;
57
+ /** Orders renders and context updates so they can be matched without timers. */
58
+ private seq;
59
+ private flushScheduled;
60
+ private sweepHandle;
61
+ private nextId;
62
+ /** Nodes created by `useRenderDiagnostics`, where props/state cannot be separated. */
63
+ readonly hookModeNodes: WeakSet<NodeRecord>;
64
+ private initialized;
65
+ constructor();
66
+ /** Idempotent: repeated calls reconfigure, they never duplicate anything (§47). */
67
+ init(options?: DetectiveOptions): Detective;
68
+ configure(options?: DetectiveOptions): void;
69
+ get isInitialized(): boolean;
70
+ get enabled(): boolean;
71
+ subscribe(listener: (event: RenderEvent) => void): () => void;
72
+ /**
73
+ * Creates a node without publishing it. StrictMode's double mount renders
74
+ * discards one of the two, and only the surviving fiber's effect attaches —
75
+ * so discarded nodes are simply garbage collected.
76
+ */
77
+ createNode(name: string, parent: NodeRecord | undefined): NodeRecord | undefined;
78
+ attach(node: NodeRecord): void;
79
+ detach(node: NodeRecord): void;
80
+ /** Hot path. Must stay allocation-free and O(1). */
81
+ recordAttempt(node: NodeRecord, props: Record<string, unknown>): void;
82
+ recordStateChange(node: NodeRecord, change: TrackedStateChange): void;
83
+ /**
84
+ * Hot path. Called from Profiler#onRender; only enqueues.
85
+ *
86
+ * `attempts` is the number of times the *wrapper* rendered, i.e. how often
87
+ * this component's props were re-evaluated from above. Zero means the render
88
+ * originated at or below this component — the flush pass works out which.
89
+ */
90
+ recordCommit(node: NodeRecord, commit: CommitInput): void;
91
+ recordContextChange(change: ContextChange): void;
92
+ private scheduleFlush;
93
+ private scheduleSweep;
94
+ /** Attempts that never reached a commit were abandoned or replayed. */
95
+ private sweep;
96
+ /** Runs after the commit, off the render path. Whole batch is available here. */
97
+ flush(): void;
98
+ private buildEvent;
99
+ private updateStats;
100
+ private emit;
101
+ getEvents(): RenderEvent[];
102
+ getComponentStats(name?: string): ComponentStats[];
103
+ getStats(): AppStats;
104
+ clear(): void;
105
+ /** Full teardown — used by tests and by HMR disposal. */
106
+ reset(): void;
107
+ }
108
+
109
+ declare function getDetective(): Detective;
110
+
111
+ /**
112
+ * Best-effort dev detection. No bundler-specific globals are referenced in the
113
+ * core (`import.meta.env` is Vite-only), so this works everywhere and defaults
114
+ * to **off** when it cannot tell — production safety over convenience (§29).
115
+ */
116
+ declare function detectDev(): boolean;
117
+ declare const defaultConfig: DetectiveConfig;
118
+ declare function mergeConfig(base: DetectiveConfig, options?: DetectiveOptions): DetectiveConfig;
119
+ declare function matches(name: string, patterns: Array<string | RegExp>): boolean;
120
+ /** Filter policy: exclude wins over include; empty include means "everything". */
121
+ declare function shouldInstrument(name: string, config: DetectiveConfig): boolean;
122
+
123
+ interface PropsDiff {
124
+ changed: PropChange[];
125
+ unchanged: string[];
126
+ }
127
+ /** Result of a bounded contents comparison. `undefined` = could not determine. */
128
+ type ShallowResult = boolean | undefined;
129
+ declare function diffProps(previous: Record<string, unknown> | undefined, current: Record<string, unknown> | undefined, config: DetectiveConfig): PropsDiff;
130
+ /**
131
+ * Bounded shallow comparison. Returns `undefined` rather than guessing when the
132
+ * values are too large or of a shape we cannot compare cheaply — the diagnostic
133
+ * engine treats that as "unknown", never as "equal".
134
+ */
135
+ declare function shallowEqual(a: unknown, b: unknown, config: DetectiveConfig): ShallowResult;
136
+
137
+ interface DiagnosisInput {
138
+ componentName: string;
139
+ phase: RenderPhase;
140
+ parentName?: string;
141
+ /** Nearest instrumented ancestor re-rendered in this same commit. */
142
+ parentRendered: boolean;
143
+ /** True when we have no instrumented ancestor, so `parentRendered` is unknown. */
144
+ parentUnknown: boolean;
145
+ /**
146
+ * Did something above re-evaluate this component's props?
147
+ * `true` — the wrapper re-rendered, so the render came from above.
148
+ * `false` — it did not, so the render started here or below.
149
+ * `undefined` — hook mode, where the two cannot be told apart.
150
+ */
151
+ propsReevaluated: boolean | undefined;
152
+ /** An instrumented child re-rendered from above, proving this component rendered. */
153
+ selfRenderProven: boolean;
154
+ changedProps: PropChange[];
155
+ contextChanges: ContextChange[];
156
+ /** Named state updates from `useTrackedState`. Empty unless opted in. */
157
+ trackedState: TrackedStateChange[];
158
+ selfDuration: number;
159
+ attempts: number;
160
+ committed: boolean;
161
+ /** How many times this component already produced a "no observable change" render. */
162
+ priorAvoidableRenders: number;
163
+ }
164
+ declare function severityFor(duration: number, t: Thresholds): Diagnosis["severity"];
165
+ /**
166
+ * Pure. No React, no globals, no I/O — so diagnostic correctness can be tested
167
+ * directly (§70).
168
+ */
169
+ declare function diagnose(input: DiagnosisInput, thresholds: Thresholds): Diagnosis;
170
+
171
+ declare function isReactElement(value: unknown): boolean;
172
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
173
+ declare function valueType(value: unknown): PropValueType;
174
+ /**
175
+ * Bounded, cycle-safe, non-retaining snapshot.
176
+ *
177
+ * Deliberately not `JSON.stringify`: that throws on cycles and BigInt, drops
178
+ * `undefined`/functions/symbols, and has no size ceiling.
179
+ */
180
+ declare function inspect(value: unknown, limits: InspectionLimits): Inspected;
181
+ /** Single-line rendering for console output. */
182
+ declare function formatInspected(node: Inspected | undefined): string;
183
+
184
+ /** Fixed-capacity ring buffer. Bounded memory is a hard requirement (§26). */
185
+ declare class RingBuffer<T> {
186
+ private items;
187
+ private head;
188
+ private count;
189
+ constructor(capacity: number);
190
+ push(item: T): void;
191
+ get size(): number;
192
+ /** Oldest → newest. */
193
+ toArray(): T[];
194
+ clear(): void;
195
+ resize(capacity: number): void;
196
+ }
197
+
198
+ export { AppStats, ComponentStats, ContextChange, Detective, DetectiveConfig, DetectiveOptions, Diagnosis, type DiagnosisInput, Inspected, InspectionLimits, PropChange, PropValueType, RenderEvent, RenderPhase, RenderReason, RingBuffer, Thresholds, TrackedStateChange, defaultConfig, detectDev, diagnose, diffProps, formatInspected, getDetective, inspect, isPlainObject, isReactElement, matches, mergeConfig, severityFor, shallowEqual, shouldInstrument, valueType };
package/dist/core.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Detective, RingBuffer, defaultConfig, detectDev, diagnose, diffProps, formatInspected, getDetective, inspect, isPlainObject, isReactElement, matches, mergeConfig, severityFor, shallowEqual, shouldInstrument, valueType } from './chunk-GRPJ4JAB.js';
2
+ //# sourceMappingURL=core.js.map
3
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"core.js"}
package/dist/index.cjs ADDED
@@ -0,0 +1,417 @@
1
+ 'use strict';
2
+
3
+ var chunkOCW2Q3UW_cjs = require('./chunk-OCW2Q3UW.cjs');
4
+ var chunk7FTXUOB6_cjs = require('./chunk-7FTXUOB6.cjs');
5
+ var React = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
27
+
28
+ // src/console/reporter.ts
29
+ var ICON = {
30
+ normal: "\xB7",
31
+ monitor: "\u2022",
32
+ slow: "\u25B2",
33
+ "very-slow": "\u25B2\u25B2",
34
+ critical: "\u25A0"
35
+ };
36
+ function attachConsoleReporter(detective) {
37
+ return detective.subscribe((event) => {
38
+ const mode = detective.config.mode;
39
+ if (mode === "silent") return;
40
+ try {
41
+ if (mode === "verbose") printVerbose(event);
42
+ else printConcise(event, detective.config.slowRenderThreshold);
43
+ } catch {
44
+ }
45
+ });
46
+ }
47
+ function printConcise(event, slowThreshold) {
48
+ const { diagnosis, timings, component } = event;
49
+ const changed = event.changedProps.map((c) => c.key).join(", ");
50
+ const parts = [
51
+ `[RRD] ${component.name} #${event.renderNumber}`,
52
+ `Reason: ${label(event)}`,
53
+ changed ? `Changed: ${changed}` : void 0,
54
+ timings.subtreeDuration > 0 ? `Duration: ${timings.selfDuration.toFixed(1)}ms` : void 0
55
+ ].filter(Boolean);
56
+ const line = `${ICON[diagnosis.severity] ?? "\xB7"} ${parts.join(" ")}`;
57
+ const slow = timings.selfDuration >= slowThreshold;
58
+ if (slow) console.warn(line);
59
+ else console.log(line);
60
+ }
61
+ function printVerbose(event) {
62
+ const { diagnosis, timings } = event;
63
+ const header = `[RRD] ${event.component.name} #${event.renderNumber} \u2014 ${diagnosis.summary}`;
64
+ const group = diagnosis.severity === "normal" ? console.groupCollapsed : console.group;
65
+ group.call(console, header);
66
+ try {
67
+ console.log(`Cause: ${label(event)} (confidence: ${diagnosis.confidence})`);
68
+ if (event.parent) {
69
+ console.log(`Parent: ${event.parent.name}${event.parentRendered ? " (re-rendered in this commit)" : " (did not re-render)"}`);
70
+ }
71
+ console.log(
72
+ `Cost: self ${timings.selfDuration.toFixed(1)}ms \xB7 subtree ${timings.subtreeDuration.toFixed(1)}ms` + (timings.accountedDescendantDuration > 0 ? ` (${timings.accountedDescendantDuration.toFixed(1)}ms in instrumented children)` : "")
73
+ );
74
+ if (event.changedProps.length > 0) {
75
+ console.groupCollapsed(`Props changed (${event.changedProps.length})`);
76
+ for (const c of event.changedProps) console.log(propLine(c));
77
+ console.groupEnd();
78
+ }
79
+ if (event.unchangedProps.length > 0) {
80
+ console.log(`Props same: ${event.unchangedProps.join(", ")}`);
81
+ }
82
+ if (event.trackedState.length > 0) {
83
+ console.log(
84
+ `State: ${event.trackedState.map((s) => `${s.name}: ${chunk7FTXUOB6_cjs.formatInspected(s.previous)} \u2192 ${chunk7FTXUOB6_cjs.formatInspected(s.current)}`).join(", ")}`
85
+ );
86
+ }
87
+ if (event.contextChanges.length > 0) {
88
+ console.log(`Context: ${event.contextChanges.map((c) => c.contextName).join(", ")} changed in this commit`);
89
+ }
90
+ console.groupCollapsed("Evidence");
91
+ for (const line of diagnosis.evidence) console.log(`\u2022 ${line}`);
92
+ console.groupEnd();
93
+ if (diagnosis.suggestion) console.log(`Next step: ${diagnosis.suggestion}`);
94
+ if (diagnosis.potentiallyAvoidable) console.log("Flag: potentially avoidable render");
95
+ } finally {
96
+ console.groupEnd();
97
+ }
98
+ }
99
+ function propLine(c) {
100
+ const head = `${c.key} (${c.valueType}) \u2014 ${describeKind(c)}`;
101
+ if (c.kind === "added") return `${head}: ${chunk7FTXUOB6_cjs.formatInspected(c.current)}`;
102
+ if (c.kind === "removed") return `${head}: was ${chunk7FTXUOB6_cjs.formatInspected(c.previous)}`;
103
+ return `${head}
104
+ previous: ${chunk7FTXUOB6_cjs.formatInspected(c.previous)}
105
+ current: ${chunk7FTXUOB6_cjs.formatInspected(c.current)}`;
106
+ }
107
+ function describeKind(c) {
108
+ switch (c.kind) {
109
+ case "added":
110
+ return "added";
111
+ case "removed":
112
+ return "removed";
113
+ case "value":
114
+ return "value changed";
115
+ case "reference":
116
+ if (c.valueType === "function") return c.sourceEqual ? "new function, identical source" : "new function reference";
117
+ return c.shallowEqual === true ? "reference changed, contents identical" : "reference changed";
118
+ }
119
+ }
120
+ function label(event) {
121
+ switch (event.diagnosis.reason) {
122
+ case "mount":
123
+ return "mount";
124
+ case "props":
125
+ return event.changedProps.every((c) => c.kind === "reference") ? "prop changed (reference only)" : "prop changed";
126
+ case "parent":
127
+ return "parent rendered";
128
+ case "context":
129
+ return "context update";
130
+ case "state":
131
+ return "state changed";
132
+ case "state-or-external":
133
+ return "state or external store";
134
+ case "unknown":
135
+ return "undetermined";
136
+ }
137
+ }
138
+ var AncestryContext = React.createContext(void 0);
139
+ AncestryContext.displayName = "RenderDetectiveAncestry";
140
+ function useInstrumentedNode(name, props) {
141
+ const detective = chunk7FTXUOB6_cjs.getDetective();
142
+ const parent = React.useContext(AncestryContext);
143
+ const [node] = React.useState(
144
+ () => detective.enabled ? detective.createNode(name, parent) : void 0
145
+ );
146
+ React.useLayoutEffect(() => {
147
+ if (!node) return;
148
+ detective.attach(node);
149
+ return () => detective.detach(node);
150
+ }, [detective, node]);
151
+ if (node) {
152
+ try {
153
+ detective.recordAttempt(node, props);
154
+ } catch {
155
+ }
156
+ }
157
+ const onRender = React.useCallback(
158
+ (_id, phase, actualDuration, baseDuration, startTime, commitTime) => {
159
+ if (!node) return;
160
+ try {
161
+ detective.recordCommit(node, {
162
+ phase,
163
+ subtreeDuration: actualDuration,
164
+ baseDuration,
165
+ startTime,
166
+ commitTime
167
+ });
168
+ } catch {
169
+ }
170
+ },
171
+ [detective, node]
172
+ );
173
+ return { node, onRender };
174
+ }
175
+ function renderInstrumented(node, onRender, children) {
176
+ if (!node) return children;
177
+ return /* @__PURE__ */ jsxRuntime.jsx(React.Profiler, { id: node.id, onRender, children: /* @__PURE__ */ jsxRuntime.jsx(AncestryContext.Provider, { value: node, children }) });
178
+ }
179
+ function componentName(component, fallback = "Anonymous") {
180
+ const c = component;
181
+ if (!c) return fallback;
182
+ if (typeof c.displayName === "string" && c.displayName) return c.displayName;
183
+ if (typeof c.name === "string" && c.name) return c.name;
184
+ if (c.type) return componentName(c.type, fallback);
185
+ return fallback;
186
+ }
187
+ function withRenderDetective(Component, options = {}) {
188
+ const name = options.name ?? componentName(Component);
189
+ function RenderDetected(props) {
190
+ const { node, onRender } = useInstrumentedNode(name, props);
191
+ return renderInstrumented(node, onRender, /* @__PURE__ */ jsxRuntime.jsx(Component, { ...props }));
192
+ }
193
+ RenderDetected.displayName = `RenderDetective(${name})`;
194
+ return RenderDetected;
195
+ }
196
+
197
+ // src/react/RenderDetective.tsx
198
+ function RenderDetective({ name, children }) {
199
+ const { node, onRender } = useInstrumentedNode(name, { children });
200
+ return renderInstrumented(node, onRender, children);
201
+ }
202
+ function useRenderDiagnostics(name, props) {
203
+ const detective = chunk7FTXUOB6_cjs.getDetective();
204
+ const parent = React.useContext(AncestryContext);
205
+ const [node] = React.useState(() => {
206
+ if (!detective.enabled) return void 0;
207
+ const created = detective.createNode(name, parent);
208
+ if (created) detective.hookModeNodes.add(created);
209
+ return created;
210
+ });
211
+ const last = React.useRef(void 0);
212
+ React.useEffect(() => {
213
+ if (!node) return;
214
+ detective.attach(node);
215
+ const unsubscribe = detective.subscribe((event) => {
216
+ if (event.component.id === node.id) last.current = event;
217
+ });
218
+ return () => {
219
+ unsubscribe();
220
+ detective.detach(node);
221
+ };
222
+ }, [detective, node]);
223
+ if (node) {
224
+ const isMount = !node.seenCommit;
225
+ detective.recordAttempt(node, props ?? {});
226
+ detective.recordCommit(node, {
227
+ phase: isMount ? "mount" : "update",
228
+ subtreeDuration: 0,
229
+ baseDuration: 0,
230
+ startTime: 0,
231
+ commitTime: -1
232
+ });
233
+ }
234
+ return last.current;
235
+ }
236
+ var useOwnerId = typeof React__namespace.useId === "function" ? React__namespace.useId : () => "";
237
+ function useTrackedState(name, initial) {
238
+ const detective = chunk7FTXUOB6_cjs.getDetective();
239
+ const node = React.useContext(AncestryContext);
240
+ const ownerId = useOwnerId();
241
+ const [state, setState] = React.useState(initial);
242
+ const previous = React.useRef(state);
243
+ if (node && node.stateOwner === void 0) node.stateOwner = ownerId;
244
+ const owns = !node || node.stateOwner === ownerId;
245
+ if (node && owns && !Object.is(previous.current, state)) {
246
+ detective.recordStateChange(node, {
247
+ name,
248
+ previous: chunk7FTXUOB6_cjs.inspect(previous.current, detective.config.inspection),
249
+ current: chunk7FTXUOB6_cjs.inspect(state, detective.config.inspection)
250
+ });
251
+ previous.current = state;
252
+ } else if (!owns) {
253
+ previous.current = state;
254
+ }
255
+ return [state, setState];
256
+ }
257
+ function useTrackedEffect(name, effect, deps) {
258
+ const detective = chunk7FTXUOB6_cjs.getDetective();
259
+ const previous = React.useRef(void 0);
260
+ const changed = React.useRef([]);
261
+ if (detective.enabled) {
262
+ const prev = previous.current;
263
+ changed.current = prev ? deps.map((d, i) => Object.is(d, prev[i]) ? -1 : i).filter((i) => i >= 0) : [];
264
+ previous.current = deps;
265
+ }
266
+ React.useEffect(() => {
267
+ if (detective.enabled && detective.config.mode !== "silent" && changed.current.length > 0) {
268
+ console.debug(`[RRD] effect ${name} ran \u2014 deps changed at index ${changed.current.join(", ")}`);
269
+ }
270
+ return effect();
271
+ }, deps);
272
+ }
273
+ function useTrackedContextValue(contextName, value) {
274
+ const detective = chunk7FTXUOB6_cjs.getDetective();
275
+ const previous = React.useRef(void 0);
276
+ const first = React.useRef(true);
277
+ if (detective.enabled) {
278
+ if (first.current) {
279
+ first.current = false;
280
+ } else if (!Object.is(previous.current, value)) {
281
+ const prev = previous.current;
282
+ const equal = chunk7FTXUOB6_cjs.shallowEqual(prev, value, detective.config);
283
+ const changedKeys = isRecord(prev) && isRecord(value) ? chunk7FTXUOB6_cjs.diffProps(prev, value, detective.config).changed.map((c) => c.key) : [];
284
+ detective.recordContextChange({
285
+ contextName,
286
+ changedKeys,
287
+ referenceOnly: equal === true,
288
+ commitTime: -1
289
+ });
290
+ }
291
+ previous.current = value;
292
+ }
293
+ return value;
294
+ }
295
+ function isRecord(v) {
296
+ return typeof v === "object" && v !== null && !Array.isArray(v);
297
+ }
298
+
299
+ // src/index.ts
300
+ var REPORTER = /* @__PURE__ */ Symbol.for("react-render-detective.reporter");
301
+ function init(options = {}) {
302
+ const detective = chunk7FTXUOB6_cjs.getDetective();
303
+ detective.init(options);
304
+ const g = globalThis;
305
+ g[REPORTER]?.();
306
+ g[REPORTER] = void 0;
307
+ if (detective.enabled && detective.config.mode !== "silent") {
308
+ g[REPORTER] = attachConsoleReporter(detective);
309
+ }
310
+ }
311
+ function configure(options) {
312
+ chunk7FTXUOB6_cjs.getDetective().configure(options);
313
+ }
314
+ function getConfig() {
315
+ return chunk7FTXUOB6_cjs.getDetective().config;
316
+ }
317
+ function isEnabled() {
318
+ return chunk7FTXUOB6_cjs.getDetective().enabled;
319
+ }
320
+ function getEvents() {
321
+ return chunk7FTXUOB6_cjs.getDetective().getEvents();
322
+ }
323
+ function getStats() {
324
+ return chunk7FTXUOB6_cjs.getDetective().getStats();
325
+ }
326
+ function getComponentStats(name) {
327
+ return chunk7FTXUOB6_cjs.getDetective().getComponentStats(name);
328
+ }
329
+ function subscribe(listener) {
330
+ return chunk7FTXUOB6_cjs.getDetective().subscribe(listener);
331
+ }
332
+ function clear() {
333
+ chunk7FTXUOB6_cjs.getDetective().clear();
334
+ }
335
+ function reset() {
336
+ const g = globalThis;
337
+ g[REPORTER]?.();
338
+ g[REPORTER] = void 0;
339
+ chunk7FTXUOB6_cjs.getDetective().reset();
340
+ }
341
+ function explain(componentName2) {
342
+ const explanation = chunkOCW2Q3UW_cjs.explainEvents(componentName2, getEvents());
343
+ return explanation ? chunkOCW2Q3UW_cjs.formatExplanation(explanation) : void 0;
344
+ }
345
+ function explainStructured(componentName2) {
346
+ return chunkOCW2Q3UW_cjs.explainEvents(componentName2, getEvents());
347
+ }
348
+ function printStats() {
349
+ const s = getStats();
350
+ const lines = [
351
+ "React Render Detective",
352
+ "",
353
+ `Components ${s.components}`,
354
+ `Total renders ${s.totalRenders}`,
355
+ `Total render time ${s.totalRenderTime.toFixed(1)}ms`,
356
+ `Slow renders ${s.slowRenders}`,
357
+ `Potentially avoidable ${s.potentiallyAvoidableRenders}`
358
+ ];
359
+ if (s.devReplays > 0) {
360
+ lines.push(`Development replays ${s.devReplays} (StrictMode / discarded \u2014 not counted above)`);
361
+ }
362
+ if (s.mostExpensive.length > 0) {
363
+ lines.push("", "Top by cumulative render time");
364
+ for (const [i, c] of s.mostExpensive.slice(0, 5).entries()) {
365
+ lines.push(
366
+ `${String(i + 1).padStart(2)}. ${c.name.padEnd(22)} ${String(c.renderCount).padStart(6)} renders ${c.totalSelfDuration.toFixed(1).padStart(8)}ms (avg ${c.averageSelfDuration.toFixed(1)}ms, p95 ${c.p95SelfDuration.toFixed(1)}ms)`
367
+ );
368
+ }
369
+ }
370
+ console.log(lines.join("\n"));
371
+ }
372
+ var ReactRenderDetective = {
373
+ init,
374
+ configure,
375
+ getConfig,
376
+ isEnabled,
377
+ getEvents,
378
+ getStats,
379
+ getComponentStats,
380
+ subscribe,
381
+ clear,
382
+ reset,
383
+ explain,
384
+ explainStructured,
385
+ printStats
386
+ };
387
+
388
+ Object.defineProperty(exports, "explainEvents", {
389
+ enumerable: true,
390
+ get: function () { return chunkOCW2Q3UW_cjs.explainEvents; }
391
+ });
392
+ Object.defineProperty(exports, "formatExplanation", {
393
+ enumerable: true,
394
+ get: function () { return chunkOCW2Q3UW_cjs.formatExplanation; }
395
+ });
396
+ exports.ReactRenderDetective = ReactRenderDetective;
397
+ exports.RenderDetective = RenderDetective;
398
+ exports.clear = clear;
399
+ exports.configure = configure;
400
+ exports.explain = explain;
401
+ exports.explainStructured = explainStructured;
402
+ exports.getComponentStats = getComponentStats;
403
+ exports.getConfig = getConfig;
404
+ exports.getEvents = getEvents;
405
+ exports.getStats = getStats;
406
+ exports.init = init;
407
+ exports.isEnabled = isEnabled;
408
+ exports.printStats = printStats;
409
+ exports.reset = reset;
410
+ exports.subscribe = subscribe;
411
+ exports.useRenderDiagnostics = useRenderDiagnostics;
412
+ exports.useTrackedContextValue = useTrackedContextValue;
413
+ exports.useTrackedEffect = useTrackedEffect;
414
+ exports.useTrackedState = useTrackedState;
415
+ exports.withRenderDetective = withRenderDetective;
416
+ //# sourceMappingURL=index.cjs.map
417
+ //# sourceMappingURL=index.cjs.map