react-render-detective 0.3.0 → 0.5.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/index.d.ts CHANGED
@@ -1,7 +1,130 @@
1
- import { b as RenderEvent, R as RenderReason, j as Confidence, a as DetectiveOptions, D as DetectiveConfig, A as AppStats, d as ComponentStats } from './types-gl13xwmN.js';
1
+ import { b as RenderEvent, j as Confidence, R as RenderReason, a as DetectiveOptions, D as DetectiveConfig, A as AppStats, d as ComponentStats } from './types-gl13xwmN.js';
2
2
  export { i as ComponentInfo, C as ContextChange, f as Diagnosis, I as Inspected, g as InspectionLimits, M as Mode, P as PropChange, k as PropChangeKind, h as PropValueType, c as RenderPhase, l as RenderTimings, e as Thresholds, T as TrackedStateChange } from './types-gl13xwmN.js';
3
+ import { RenderProfile } from './testing.js';
3
4
  import { ComponentType, ReactNode, Dispatch, SetStateAction } from 'react';
4
5
 
6
+ /**
7
+ * Interaction-scoped attribution.
8
+ *
9
+ * Performance is felt per interaction, not in aggregate — and INP is the metric
10
+ * teams are actually judged on. This joins the two halves: the browser says a
11
+ * keystroke took 240ms, and the render events say which components spent it and
12
+ * why.
13
+ *
14
+ * Uses `PerformanceObserver` with `event` timing, a public browser API. Where it
15
+ * is unsupported (older Safari, jsdom) interaction tracking simply stays empty
16
+ * rather than guessing.
17
+ */
18
+ interface InteractionRecord {
19
+ id: string;
20
+ /** `click`, `keydown`, `pointerup`… */
21
+ type: string;
22
+ /** Best-effort description of what was interacted with. */
23
+ target?: string;
24
+ startTime: number;
25
+ /** Browser-reported event duration — the number INP is computed from. */
26
+ durationMs: number;
27
+ /**
28
+ * For a manually measured interaction: how long the synchronous action took.
29
+ * Immune to throttling, unlike the full window, which waits on a frame.
30
+ */
31
+ handlerMs?: number;
32
+ /** Renders committed inside this interaction's window. */
33
+ renders: RenderEvent[];
34
+ /** Sum of self durations for those renders. */
35
+ renderTimeMs: number;
36
+ /** Render time that no observable input change explains. */
37
+ avoidableRenderTimeMs: number;
38
+ }
39
+ interface InteractionSummary {
40
+ interaction: InteractionRecord;
41
+ /** Components ordered by cost within this interaction. */
42
+ contributors: Array<{
43
+ component: string;
44
+ source?: string;
45
+ renders: number;
46
+ totalMs: number;
47
+ cause: string;
48
+ }>;
49
+ headline: string;
50
+ nextStep: string;
51
+ confidence: Confidence;
52
+ }
53
+ interface RawEventTiming {
54
+ name: string;
55
+ startTime: number;
56
+ duration: number;
57
+ target?: string;
58
+ handlerMs?: number;
59
+ }
60
+ declare class InteractionTracker {
61
+ private capacity;
62
+ private records;
63
+ private observer;
64
+ private nextId;
65
+ constructor(capacity?: number);
66
+ /** Returns false when the browser cannot report event timing. */
67
+ start(): boolean;
68
+ stop(): void;
69
+ /** Is the automatic path available in this browser? */
70
+ get automatic(): boolean;
71
+ /**
72
+ * Time an interaction by hand.
73
+ *
74
+ * The automatic path depends on the Event Timing API, which Safari only
75
+ * gained in 16.4 and which does not fire for synthetic input at all — so
76
+ * anything driven by a test harness records nothing. This measures a specific
77
+ * action instead, up to the paint that follows it, and needs no browser
78
+ * support beyond `performance.now`.
79
+ */
80
+ measure<T>(label: string, action: () => T): T;
81
+ /** Exposed for tests and for `measure`. */
82
+ record(timing: RawEventTiming): InteractionRecord;
83
+ clear(): void;
84
+ /**
85
+ * Joins render events to interactions by commit time. A render belongs to an
86
+ * interaction when it committed between the event starting and shortly after
87
+ * it finished — React commits just after the event handler returns.
88
+ */
89
+ attribute(events: RenderEvent[]): InteractionRecord[];
90
+ }
91
+ declare function summarise(record: InteractionRecord): InteractionSummary;
92
+ declare function formatInteraction(summary: InteractionSummary): string;
93
+
94
+ /**
95
+ * "Where should I spend my next hour?"
96
+ *
97
+ * Render counts answer the wrong question — a component rendering 2 000 times
98
+ * for 0.01ms is not the problem, and one rendering 40 times for 12ms might be.
99
+ * This ranks by **estimated recoverable time**, so the top of the list is the
100
+ * biggest win rather than the noisiest component.
101
+ */
102
+ interface Opportunity {
103
+ component: string;
104
+ source?: string;
105
+ /** Milliseconds plausibly recovered by fixing this. The ranking key. */
106
+ estimatedSavingMs: number;
107
+ /** Renders where no observable input changed. */
108
+ avoidableRenders: number;
109
+ /** Times the component was rebuilt rather than re-rendered. */
110
+ remounts: number;
111
+ averageSelfDuration: number;
112
+ /** One line: what to look at. Comes from the diagnostic engine, not from here. */
113
+ summary: string;
114
+ nextStep: string;
115
+ confidence: Confidence;
116
+ }
117
+ interface OpportunityInput {
118
+ events: RenderEvent[];
119
+ lifecycles: Map<string, {
120
+ remounts: number;
121
+ }>;
122
+ /** Ignore anything below this. Noise is worse than silence in a ranked list. */
123
+ minSavingMs?: number;
124
+ }
125
+ declare function rankOpportunities({ events, lifecycles, minSavingMs }: OpportunityInput): Opportunity[];
126
+ declare function formatOpportunities(opportunities: Opportunity[]): string;
127
+
5
128
  interface TrackOptions {
6
129
  /** Overrides the inferred component name. Required for anonymous components. */
7
130
  name?: string;
@@ -155,6 +278,33 @@ declare function reset(): void;
155
278
  declare function explain(componentName: string): string | undefined;
156
279
  /** Structured form of `explain`, for building UIs on top. */
157
280
  declare function explainStructured(componentName: string): Explanation | undefined;
281
+ /** Snapshot render behaviour for regression testing. See `react-render-detective/testing`. */
282
+ declare function getRenderProfile(scenario: string): RenderProfile;
283
+ /**
284
+ * Interactions, slowest first, with the renders that happened inside each.
285
+ *
286
+ * This is the bridge from render causality to what a user actually feels: the
287
+ * browser reports how long the interaction took, and the render events say
288
+ * which components spent that time and why.
289
+ */
290
+ declare function getInteractions(): InteractionRecord[];
291
+ /** Structured analysis of one interaction. Defaults to the slowest recorded. */
292
+ declare function explainInteractionStructured(id?: string): InteractionSummary | undefined;
293
+ declare function explainInteraction(id?: string): string | undefined;
294
+ declare function printInteractions(limit?: number): void;
295
+ /**
296
+ * Time one interaction explicitly, up to the paint that follows it.
297
+ *
298
+ * Needed wherever the automatic path cannot see: Safari before 16.4, and any
299
+ * synthetic input, which never produces Event Timing entries.
300
+ */
301
+ declare function measureInteraction<T>(label: string, action: () => T): T;
302
+ /**
303
+ * Components ranked by estimated recoverable time — the triage view. Render
304
+ * counts answer the wrong question; this answers "what should I fix first?".
305
+ */
306
+ declare function getOpportunities(limit?: number): Opportunity[];
307
+ declare function printOpportunities(limit?: number): void;
158
308
  /** Prints the application-level dashboard (§53). */
159
309
  declare function printStats(): void;
160
310
  /** Namespaced form, matching the documented `ReactRenderDetective.init()` usage. */
@@ -171,7 +321,15 @@ declare const ReactRenderDetective: {
171
321
  reset: typeof reset;
172
322
  explain: typeof explain;
173
323
  explainStructured: typeof explainStructured;
324
+ getOpportunities: typeof getOpportunities;
325
+ printOpportunities: typeof printOpportunities;
326
+ getInteractions: typeof getInteractions;
327
+ explainInteraction: typeof explainInteraction;
328
+ explainInteractionStructured: typeof explainInteractionStructured;
329
+ printInteractions: typeof printInteractions;
330
+ measureInteraction: typeof measureInteraction;
331
+ getRenderProfile: typeof getRenderProfile;
174
332
  printStats: typeof printStats;
175
333
  };
176
334
 
177
- export { AppStats, ComponentStats, Confidence, DetectiveConfig, DetectiveOptions, type Explanation, ReactRenderDetective, RenderDetective, type RenderDetectiveProps, RenderEvent, RenderReason, type TrackOptions, clear, configure, explain, explainEvents, explainStructured, formatExplanation, getComponentStats, getConfig, getEvents, getStats, init, isEnabled, printStats, reset, subscribe, useRenderDiagnostics, useTrackedContextValue, useTrackedEffect, useTrackedState, withRenderDetective };
335
+ export { AppStats, ComponentStats, Confidence, DetectiveConfig, DetectiveOptions, type Explanation, type InteractionRecord, type InteractionSummary, InteractionTracker, type Opportunity, ReactRenderDetective, RenderDetective, type RenderDetectiveProps, RenderEvent, RenderReason, type TrackOptions, clear, configure, explain, explainEvents, explainInteraction, explainInteractionStructured, explainStructured, formatExplanation, formatInteraction, formatOpportunities, getComponentStats, getConfig, getEvents, getInteractions, getOpportunities, getRenderProfile, getStats, init, isEnabled, measureInteraction, printInteractions, printOpportunities, printStats, rankOpportunities, reset, subscribe, summarise as summariseInteraction, useRenderDiagnostics, useTrackedContextValue, useTrackedEffect, useTrackedState, withRenderDetective };