@flotrace/runtime 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.
@@ -0,0 +1,1364 @@
1
+ import React, { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Types for @flotrace/runtime package
5
+ * These mirror the shared types from the extension but are standalone
6
+ * to avoid importing from the extension package.
7
+ */
8
+ /**
9
+ * Serialized value for safe transmission over WebSocket
10
+ */
11
+ type SerializedValue = null | boolean | number | string | SerializedValue[] | {
12
+ [key: string]: SerializedValue;
13
+ } | {
14
+ __type: 'function';
15
+ name?: string;
16
+ } | {
17
+ __type: 'undefined';
18
+ } | {
19
+ __type: 'symbol';
20
+ description?: string;
21
+ } | {
22
+ __type: 'circular';
23
+ } | {
24
+ __type: 'truncated';
25
+ originalType: string;
26
+ length?: number;
27
+ };
28
+ /**
29
+ * Messages sent from runtime to extension
30
+ */
31
+ type RuntimeMessage = RuntimeReadyMessage | RuntimeRenderMessage | RuntimePropsUpdateMessage | RuntimeNodePropsMessage | RuntimeZustandUpdateMessage | RuntimeReduxUpdateMessage | RuntimeRouterUpdateMessage | RuntimeContextUpdateMessage | RuntimeDisconnectMessage | RuntimeTreeSnapshotMessage | RuntimeTreeDiffMessage | RuntimeNodeHooksMessage | RuntimeNodeEffectsMessage | RuntimeDetailedRenderReasonMessage | RuntimeTimelineEventMessage | RuntimeTanStackQueryUpdateMessage | RuntimeRenderTriggerMessage | RuntimeRenderCascadeMessage | RuntimePropDrillingMessage | RuntimeActionStateMessage | RuntimeOptimisticDiffMessage | RuntimeNextjsContextMessage | RuntimeRscPayloadMessage | RuntimeHydrationEventMessage | RuntimeNetworkRequestMessage | RuntimeLocalStateCorrelationMessage;
32
+ interface RuntimeReadyMessage {
33
+ type: 'runtime:ready';
34
+ appName?: string;
35
+ reactVersion?: string;
36
+ appUrl?: string;
37
+ }
38
+ interface RuntimeRenderMessage {
39
+ type: 'runtime:render';
40
+ componentName: string;
41
+ filePath?: string;
42
+ phase: 'mount' | 'update';
43
+ actualDuration: number;
44
+ baseDuration: number;
45
+ timestamp: number;
46
+ instanceId?: string;
47
+ }
48
+ interface RuntimePropsUpdateMessage {
49
+ type: 'runtime:props';
50
+ componentName: string;
51
+ instanceId?: string;
52
+ props: Record<string, SerializedValue>;
53
+ changedKeys?: string[];
54
+ timestamp: number;
55
+ }
56
+ interface RuntimeNodePropsMessage {
57
+ type: 'runtime:nodeProps';
58
+ /** Path-based node ID (e.g., "App-0/Dashboard-0/Card-2") */
59
+ nodeId: string;
60
+ /** Serialized props from fiber.memoizedProps */
61
+ props: Record<string, SerializedValue>;
62
+ timestamp: number;
63
+ }
64
+ interface RuntimeZustandUpdateMessage {
65
+ type: 'runtime:zustand';
66
+ storeName: string;
67
+ state: Record<string, SerializedValue>;
68
+ changedKeys: string[];
69
+ /** Per-request causal correlation: each entry maps a requestId to the specific store keys
70
+ * whose values came from that fetch response (WeakMap causal correlation). */
71
+ correlatedRequests?: Array<{
72
+ requestId: string;
73
+ storeKeys: string[];
74
+ }>;
75
+ timestamp: number;
76
+ }
77
+ interface RuntimeReduxUpdateMessage {
78
+ type: 'runtime:redux';
79
+ /** Current state snapshot */
80
+ state: Record<string, SerializedValue>;
81
+ /** Keys that changed */
82
+ changedKeys: string[];
83
+ /** Per-request causal correlation: each entry maps a requestId to the specific store keys
84
+ * whose values came from that fetch response (WeakMap causal correlation). */
85
+ correlatedRequests?: Array<{
86
+ requestId: string;
87
+ storeKeys: string[];
88
+ }>;
89
+ timestamp: number;
90
+ }
91
+ interface RuntimeRouterUpdateMessage {
92
+ type: 'runtime:router';
93
+ pathname: string;
94
+ params: Record<string, string>;
95
+ searchParams: Record<string, string>;
96
+ timestamp: number;
97
+ }
98
+ interface RuntimeContextUpdateMessage {
99
+ type: 'runtime:context';
100
+ contextName: string;
101
+ value: SerializedValue;
102
+ consumers?: string[];
103
+ timestamp: number;
104
+ }
105
+ interface RuntimeDisconnectMessage {
106
+ type: 'runtime:disconnect';
107
+ reason?: string;
108
+ }
109
+ interface RuntimeTreeSnapshotMessage {
110
+ type: 'runtime:treeSnapshot';
111
+ /** Full component tree from fiber traversal */
112
+ tree: LiveTreeNode;
113
+ /** Timestamp when snapshot was taken */
114
+ timestamp: number;
115
+ }
116
+ /**
117
+ * Incremental tree diff — sent instead of a full snapshot when the tree
118
+ * structure hasn't changed dramatically. Reduces WebSocket payload by ~80-95%
119
+ * compared to sending the full tree every time.
120
+ *
121
+ * The extension reconstructs the full tree by applying diffs to its cached copy.
122
+ * A full snapshot is sent every FULL_SNAPSHOT_INTERVAL (10) diffs to prevent drift,
123
+ * and whenever the extension detects a sequence gap.
124
+ */
125
+ interface RuntimeTreeDiffMessage {
126
+ type: 'runtime:treeDiff';
127
+ /** Monotonic sequence number — extension uses this to detect missed diffs */
128
+ seq: number;
129
+ /** Nodes added since last snapshot (includes parentId for tree insertion) */
130
+ added: Array<LiveTreeNode & {
131
+ parentId: string;
132
+ }>;
133
+ /** Node IDs removed since last snapshot */
134
+ removed: string[];
135
+ /** Nodes whose mutable fields changed (renderDuration, renderPhase, renderReason) */
136
+ updated: Array<{
137
+ id: string;
138
+ renderDuration?: number;
139
+ renderPhase?: 'mount' | 'update';
140
+ renderReason?: 'mount' | 'props-changed' | 'state-or-context' | 'parent';
141
+ }>;
142
+ timestamp: number;
143
+ }
144
+ /**
145
+ * React Compiler memoization status for a component.
146
+ * Detected by checking for the React Compiler memo cache sentinel in fiber state.
147
+ * Mirrors the CompilerStatus type in src/shared/liveMessages.ts.
148
+ */
149
+ type CompilerStatus = 'compiled' | 'manual' | 'unoptimized' | 'de-opted';
150
+ /**
151
+ * A node in the live component tree captured from React fiber tree.
152
+ * Path-based IDs ensure stability across snapshots for React Flow animations.
153
+ */
154
+ interface LiveTreeNode {
155
+ /** Path-based ID: "App-0/Dashboard-0/Card-2" (component name + child index among same-type siblings) */
156
+ id: string;
157
+ /** Component display name */
158
+ name: string;
159
+ /** Child components (host elements like div/span are filtered out) */
160
+ children: LiveTreeNode[];
161
+ /** Serialized props (functions filtered, values truncated) */
162
+ props?: Record<string, SerializedValue>;
163
+ /** Fiber tag: 0=Function, 1=Class, 11=ForwardRef, 14=Memo, 15=SimpleMemo */
164
+ fiberTag: number;
165
+ /** Mount on first render, update on re-render */
166
+ renderPhase?: 'mount' | 'update';
167
+ /** Render duration in ms (from Profiler) */
168
+ renderDuration?: number;
169
+ /** Source file path from _debugSource (dev mode only) */
170
+ filePath?: string;
171
+ /** Source line number from _debugSource (dev mode only) */
172
+ lineNumber?: number;
173
+ /** Why this component rendered (detected via fiber.alternate props comparison) */
174
+ renderReason?: 'mount' | 'props-changed' | 'state-or-context' | 'parent';
175
+ /** True if this component is a framework/library wrapper (Next.js, React Router, etc.) */
176
+ isFramework?: boolean;
177
+ /** React key prop (only string keys, used to differentiate same-name siblings in search) */
178
+ reactKey?: string;
179
+ /** TanStack Query hashes observed by this component (detected from useRef → QueryObserver) */
180
+ queryHashes?: string[];
181
+ /** Number of hooks in this component (counted from memoizedState linked list) */
182
+ hookCount?: number;
183
+ /** True if any hook is useContext (indicates data may come from context, not just props) */
184
+ hasContextHook?: boolean;
185
+ /** True if a useTransition hook on this component currently has isPending=true */
186
+ isTransitionPending?: boolean;
187
+ /** True if this component is currently rendering inside a Suspense fallback branch */
188
+ isSuspenseFallback?: boolean;
189
+ /** React Compiler memoization status (undefined = not analyzed / compiler not detected) */
190
+ compilerStatus?: CompilerStatus;
191
+ /** True if this is detected as a Next.js Server Component (heuristic) */
192
+ isServerComponent?: boolean;
193
+ /** True if this is the first client component below a server component boundary */
194
+ isClientBoundary?: boolean;
195
+ /** True if this component is from a third-party library, not user-defined code */
196
+ isLibrary?: boolean;
197
+ /** Short display label for the library source (e.g. 'framer', 'fontawesome', 'sonner') */
198
+ libraryName?: string;
199
+ }
200
+ /**
201
+ * Enhanced render reason with specific prop/state/context changes.
202
+ */
203
+ type DetailedRenderReasonType = 'mount' | 'props-changed' | 'state-changed' | 'context-changed' | 'parent-render' | 'force-update';
204
+ interface PropChange {
205
+ key: string;
206
+ prev: SerializedValue;
207
+ next: SerializedValue;
208
+ }
209
+ type DetailedRenderReason = {
210
+ type: 'mount';
211
+ } | {
212
+ type: 'props-changed';
213
+ changedProps: PropChange[];
214
+ } | {
215
+ type: 'state-changed';
216
+ changedHookIndices: number[];
217
+ } | {
218
+ type: 'context-changed';
219
+ contextNames: string[];
220
+ } | {
221
+ type: 'parent-render';
222
+ parentName?: string;
223
+ } | {
224
+ type: 'force-update';
225
+ };
226
+ /**
227
+ * Hook type classification — inferred from fiber.memoizedState shape.
228
+ */
229
+ type HookType = 'useState' | 'useReducer' | 'useRef' | 'useMemo' | 'useCallback' | 'useEffect' | 'useLayoutEffect' | 'useInsertionEffect' | 'useContext' | 'useImperativeHandle' | 'useDebugValue' | 'useTransition' | 'useDeferredValue' | 'useId' | 'useSyncExternalStore' | 'useOptimistic' | 'useFormStatus' | 'unknown';
230
+ /**
231
+ * Information about a single hook in a component's hook linked list.
232
+ */
233
+ interface HookInfo {
234
+ /** Position in the hook linked list (0-based) */
235
+ index: number;
236
+ /** Classified hook type */
237
+ type: HookType;
238
+ /** Serialized current value (state for useState, ref.current for useRef, etc.) */
239
+ value: SerializedValue;
240
+ /** For useMemo/useCallback/useEffect: serialized dependency array */
241
+ deps?: SerializedValue[];
242
+ /** Hook name hint from _debugHookTypes if available */
243
+ debugLabel?: string;
244
+ }
245
+ /**
246
+ * Information about a single effect (useEffect/useLayoutEffect/useInsertionEffect).
247
+ */
248
+ interface EffectInfo {
249
+ /** Position in the effect circular list (0-based) */
250
+ index: number;
251
+ /** Corresponding hook index in the memoizedState list */
252
+ hookIndex: number;
253
+ /** Effect type derived from tag bitmask */
254
+ type: 'useEffect' | 'useLayoutEffect' | 'useInsertionEffect';
255
+ /** Current dependency array (null = no deps, runs every render) */
256
+ deps: SerializedValue[] | null;
257
+ /** Previous dependency array from fiber.alternate */
258
+ prevDeps: SerializedValue[] | null;
259
+ /** Indices of deps that changed (triggering this effect to run) */
260
+ changedDepIndices: number[];
261
+ /** Whether this effect will execute on this render */
262
+ willRun: boolean;
263
+ /** Whether the previous effect returned a cleanup function */
264
+ hasCleanup: boolean;
265
+ }
266
+ /**
267
+ * Component lifecycle event types for the timeline.
268
+ */
269
+ type TimelineEventType = 'mount' | 'unmount' | 'render' | 'effect-run' | 'effect-cleanup' | 'state-update' | 'props-change';
270
+ /**
271
+ * A single event in a component's lifecycle timeline.
272
+ */
273
+ interface TimelineEvent {
274
+ type: TimelineEventType;
275
+ timestamp: number;
276
+ /** Render duration in ms (for render events) */
277
+ duration?: number;
278
+ /** Additional context (e.g., which hook, which prop) */
279
+ detail?: SerializedValue;
280
+ }
281
+ interface RuntimeNodeHooksMessage {
282
+ type: 'runtime:nodeHooks';
283
+ nodeId: string;
284
+ hooks: HookInfo[];
285
+ timestamp: number;
286
+ }
287
+ interface RuntimeNodeEffectsMessage {
288
+ type: 'runtime:nodeEffects';
289
+ nodeId: string;
290
+ effects: EffectInfo[];
291
+ timestamp: number;
292
+ }
293
+ interface RuntimeDetailedRenderReasonMessage {
294
+ type: 'runtime:detailedRenderReason';
295
+ nodeId: string;
296
+ reason: DetailedRenderReason;
297
+ timestamp: number;
298
+ }
299
+ interface RuntimeTimelineEventMessage {
300
+ type: 'runtime:timelineEvent';
301
+ nodeId: string;
302
+ componentName: string;
303
+ event: TimelineEvent;
304
+ }
305
+ /** Serialized query info sent over WebSocket */
306
+ interface TanStackQueryInfo {
307
+ queryKey: SerializedValue;
308
+ queryHash: string;
309
+ status: 'pending' | 'error' | 'success';
310
+ fetchStatus: 'idle' | 'fetching' | 'paused';
311
+ dataUpdatedAt: number;
312
+ errorUpdatedAt: number;
313
+ isInvalidated: boolean;
314
+ isStale: boolean;
315
+ isActive: boolean;
316
+ isDisabled: boolean;
317
+ failureCount: number;
318
+ errorMessage?: string;
319
+ observerCount: number;
320
+ /** Config values */
321
+ staleTime?: number;
322
+ gcTime?: number;
323
+ /** Additional config for health analysis */
324
+ refetchInterval?: number | false;
325
+ refetchOnWindowFocus?: boolean | 'always';
326
+ refetchOnMount?: boolean | 'always';
327
+ refetchOnReconnect?: boolean | 'always';
328
+ networkMode?: string;
329
+ enabled?: boolean;
330
+ retry?: number | boolean;
331
+ /** Data shape descriptor (key names + types, no values) */
332
+ dataShape?: SerializedValue;
333
+ /** Number of times query refetched but data was identical */
334
+ wastedRefetchCount?: number;
335
+ /** Total number of fetches tracked */
336
+ totalFetchCount?: number;
337
+ /** Per-query state transition history (ring buffer, max 50) */
338
+ events?: TanStackQueryEvent[];
339
+ /** requestId of the API call whose response was stored in this query's cache (WeakMap causal) */
340
+ correlatedRequestId?: string;
341
+ }
342
+ /** A state transition event for a TanStack Query */
343
+ interface TanStackQueryEvent {
344
+ timestamp: number;
345
+ /** Status before the transition */
346
+ fromStatus: string;
347
+ /** Status after the transition */
348
+ toStatus: string;
349
+ /** Fetch status before the transition */
350
+ fromFetchStatus: string;
351
+ /** Fetch status after the transition */
352
+ toFetchStatus: string;
353
+ /** Whether the data changed during this transition */
354
+ dataChanged: boolean;
355
+ }
356
+ /** Serialized mutation info sent over WebSocket */
357
+ interface TanStackMutationInfo {
358
+ mutationId: number;
359
+ status: 'idle' | 'pending' | 'error' | 'success';
360
+ isPaused: boolean;
361
+ submittedAt: number;
362
+ failureCount: number;
363
+ errorMessage?: string;
364
+ mutationKey?: SerializedValue;
365
+ scope?: string;
366
+ /** Correlation ID linking this mutation to queries it triggered */
367
+ lastCorrelationId?: string;
368
+ }
369
+ /** Mutation → query invalidation → refetch correlation event */
370
+ interface MutationCorrelation {
371
+ /** Unique ID for this correlation event */
372
+ correlationId: string;
373
+ /** The mutation that triggered the cascade */
374
+ mutationId: number;
375
+ /** Mutation key (if provided) for display */
376
+ mutationKey?: SerializedValue;
377
+ /** Timestamp when mutation completed (status → 'success') */
378
+ mutationCompletedAt: number;
379
+ /** Queries that started fetching within the correlation window */
380
+ affectedQueries: Array<{
381
+ queryHash: string;
382
+ queryKey: SerializedValue;
383
+ /** When the query started fetching */
384
+ fetchStartedAt: number;
385
+ /** Latency: fetchStartedAt - mutationCompletedAt */
386
+ latencyMs: number;
387
+ /** Whether the refetch actually changed data */
388
+ dataChanged?: boolean;
389
+ }>;
390
+ /** Timestamp when the correlation window closed */
391
+ resolvedAt: number;
392
+ }
393
+ interface RuntimeTanStackQueryUpdateMessage {
394
+ type: 'runtime:tanstackQuery';
395
+ queries: TanStackQueryInfo[];
396
+ mutations: TanStackMutationInfo[];
397
+ /** New correlation events since last snapshot */
398
+ correlations?: MutationCorrelation[];
399
+ timestamp: number;
400
+ }
401
+ interface StackFrame {
402
+ functionName: string | null;
403
+ fileName: string | null;
404
+ lineNumber: number | null;
405
+ columnNumber: number | null;
406
+ /** false for node_modules / react-dom / react-reconciler frames */
407
+ isUserCode: boolean;
408
+ }
409
+ interface TriggerRecord {
410
+ triggerId: string;
411
+ fiberId: string;
412
+ componentName: string;
413
+ hookIndex: number;
414
+ hookType: 'state' | 'reducer' | 'setState' | 'forceUpdate';
415
+ stack: StackFrame[];
416
+ timestamp: number;
417
+ action: SerializedValue | null;
418
+ batchId: string | null;
419
+ }
420
+ type CascadeReason = 'state-update' | 'context-update' | 'props-changed' | 'parent-cascade' | 'force-update' | 'bailed-out';
421
+ interface CascadeNode {
422
+ nodeId: string;
423
+ componentName: string;
424
+ reason: CascadeReason;
425
+ renderDuration: number;
426
+ subtreeDuration: number;
427
+ changedProps?: string[];
428
+ hookIndex?: number;
429
+ triggerId?: string;
430
+ children: CascadeNode[];
431
+ depth: number;
432
+ isMemoized: boolean;
433
+ }
434
+ type LanePriority = 'sync' | 'discrete' | 'continuous' | 'default' | 'transition' | 'deferred' | 'idle' | 'offscreen';
435
+ interface LaneInfo {
436
+ priority: LanePriority;
437
+ lanes: number;
438
+ isTransition: boolean;
439
+ isBlocking: boolean;
440
+ }
441
+ interface CascadeRecord {
442
+ commitId: string;
443
+ timestamp: number;
444
+ totalDuration: number;
445
+ totalComponents: number;
446
+ avoidableCount: number;
447
+ avoidableDuration: number;
448
+ rootCauses: CascadeNode[];
449
+ lane: LaneInfo;
450
+ triggerIds: string[];
451
+ }
452
+ interface RuntimeRenderTriggerMessage {
453
+ type: 'runtime:renderTrigger';
454
+ trigger: TriggerRecord;
455
+ }
456
+ interface RuntimeRenderCascadeMessage {
457
+ type: 'runtime:renderCascade';
458
+ cascade: CascadeRecord;
459
+ }
460
+ interface PropDrillingChainNode {
461
+ nodeId: string;
462
+ componentName: string;
463
+ propKey: string;
464
+ role: 'source' | 'passthrough' | 'consumer';
465
+ hookCount: number;
466
+ hasContextHook: boolean;
467
+ }
468
+ interface PropDrillingChain {
469
+ chainId: string;
470
+ propName: string;
471
+ sourceNodeId: string;
472
+ sourceComponentName: string;
473
+ consumerNodeIds: string[];
474
+ consumerComponentNames: string[];
475
+ path: PropDrillingChainNode[];
476
+ depth: number;
477
+ passthroughCount: number;
478
+ severity: 'info' | 'warning' | 'critical';
479
+ renames: Array<{
480
+ atNodeId: string;
481
+ fromKey: string;
482
+ toKey: string;
483
+ }>;
484
+ }
485
+ interface RuntimePropDrillingMessage {
486
+ type: 'runtime:propDrilling';
487
+ payload: {
488
+ chains: PropDrillingChain[];
489
+ passthroughNodeIds: string[];
490
+ analysisTimestamp: number;
491
+ treeSize: number;
492
+ };
493
+ }
494
+ /** Sent whenever a useActionState or useOptimistic hook changes on any fiber */
495
+ interface RuntimeActionStateMessage {
496
+ type: 'runtime:actionState';
497
+ nodeId: string;
498
+ componentName: string;
499
+ /** One entry per useActionState / useOptimistic hook on this fiber */
500
+ actions: Array<{
501
+ hookIndex: number;
502
+ hookKind: 'action' | 'optimistic';
503
+ isPending: boolean;
504
+ state: SerializedValue;
505
+ error?: SerializedValue;
506
+ pendingSince?: number;
507
+ durationMs?: number;
508
+ }>;
509
+ timestamp: number;
510
+ }
511
+ /** Sent when a useOptimistic value diverges from its underlying actual value */
512
+ interface RuntimeOptimisticDiffMessage {
513
+ type: 'runtime:optimisticDiff';
514
+ nodeId: string;
515
+ componentName: string;
516
+ hookIndex: number;
517
+ optimisticValue: SerializedValue;
518
+ actualValue: SerializedValue;
519
+ timestamp: number;
520
+ }
521
+ /** Sent once on mount when the Next.js environment is detected */
522
+ interface RuntimeNextjsContextMessage {
523
+ type: 'runtime:nextjsContext';
524
+ detected: boolean;
525
+ version?: string;
526
+ isAppRouter: boolean;
527
+ initialRoute?: string;
528
+ timestamp: number;
529
+ }
530
+ /** Sent when an RSC / Next.js data fetch is intercepted (metadata only, no values) */
531
+ interface RuntimeRscPayloadMessage {
532
+ type: 'runtime:rscPayload';
533
+ route: string;
534
+ payloadSizeBytes: number;
535
+ cacheStatus: 'HIT' | 'MISS' | 'STALE' | 'unknown';
536
+ timestamp: number;
537
+ }
538
+ /** Sent when React hydration completes or a mismatch is detected */
539
+ interface RuntimeHydrationEventMessage {
540
+ type: 'runtime:hydrationEvent';
541
+ kind: 'complete' | 'mismatch';
542
+ durationMs?: number;
543
+ errorMessage?: string;
544
+ timestamp: number;
545
+ }
546
+ /** Metadata for a single intercepted network request. Privacy-first: no bodies, no query params, no auth headers. */
547
+ interface NetworkRequestEntry {
548
+ /** Incrementing request ID */
549
+ requestId: string;
550
+ /** HTTP method (GET, POST, PUT, DELETE, PATCH, etc.) */
551
+ method: string;
552
+ /** URL path only — query params stripped for privacy */
553
+ urlPath: string;
554
+ /** URL host for endpoint grouping */
555
+ urlHost: string;
556
+ /** HTTP status code (0 if pending/aborted) */
557
+ status: number;
558
+ /** Request duration in ms (null if still pending) */
559
+ durationMs: number | null;
560
+ /** Response size from Content-Length header (null if unavailable) */
561
+ responseSizeBytes: number | null;
562
+ /** React component that initiated this request (if attributable) */
563
+ componentName?: string;
564
+ /** Ancestor chain of the initiating component (last 3) */
565
+ ancestorChain?: string[];
566
+ /** True if fetch was called during React render phase (anti-pattern) */
567
+ initiatedDuringRender: boolean;
568
+ /** True if fetch was called inside a useEffect callback */
569
+ initiatedInEffect: boolean;
570
+ /** Request lifecycle state */
571
+ state: 'pending' | 'success' | 'error' | 'aborted';
572
+ /** Deduplication key: `${method}:${normalizedPath}` for duplicate detection */
573
+ dedupeKey: string;
574
+ /** True if another request with same dedupeKey was made within 2s */
575
+ isDuplicate?: boolean;
576
+ /** True if this is a Next.js Server Action (POST with Next-Action header) */
577
+ isServerAction?: boolean;
578
+ /** True if this is a Next.js RSC prefetch (Next-Router-Prefetch header) */
579
+ isPrefetch?: boolean;
580
+ /** Error message if request failed */
581
+ errorMessage?: string;
582
+ /** Timestamp (Date.now()) */
583
+ timestamp: number;
584
+ }
585
+ /** Batched network request message sent to FloTrace server */
586
+ interface RuntimeNetworkRequestMessage {
587
+ type: 'runtime:networkRequest';
588
+ requests: NetworkRequestEntry[];
589
+ timestamp: number;
590
+ }
591
+ /** Emitted when a fiber's useState/useReducer hook holds API response data (WeakMap causal) */
592
+ interface RuntimeLocalStateCorrelationMessage {
593
+ type: 'runtime:localStateCorrelation';
594
+ requestId: string;
595
+ componentName: string;
596
+ hookIndex: number;
597
+ timestamp: number;
598
+ }
599
+ /**
600
+ * Messages received from extension
601
+ */
602
+ type ExtensionToRuntimeMessage = {
603
+ type: 'ext:ping';
604
+ } | {
605
+ type: 'ext:startTracking';
606
+ options?: TrackingOptions;
607
+ } | {
608
+ type: 'ext:stopTracking';
609
+ } | {
610
+ type: 'ext:requestState';
611
+ componentName?: string;
612
+ } | {
613
+ type: 'ext:requestNodeProps';
614
+ nodeId: string;
615
+ } | {
616
+ type: 'ext:startTreeTracking';
617
+ } | {
618
+ type: 'ext:stopTreeTracking';
619
+ } | {
620
+ type: 'ext:requestFullSnapshot';
621
+ } | {
622
+ type: 'ext:requestNodeHooks';
623
+ nodeId: string;
624
+ } | {
625
+ type: 'ext:requestNodeEffects';
626
+ nodeId: string;
627
+ } | {
628
+ type: 'ext:requestDetailedRenderReason';
629
+ nodeId: string;
630
+ } | {
631
+ type: 'ext:requestTimeline';
632
+ nodeId: string;
633
+ } | {
634
+ type: 'ext:startNetworkCapture';
635
+ } | {
636
+ type: 'ext:stopNetworkCapture';
637
+ } | {
638
+ type: 'ext:startReduxTracking';
639
+ } | {
640
+ type: 'ext:stopReduxTracking';
641
+ } | {
642
+ type: 'ext:startRouterTracking';
643
+ } | {
644
+ type: 'ext:stopRouterTracking';
645
+ } | {
646
+ type: 'ext:startZustandTracking';
647
+ } | {
648
+ type: 'ext:stopZustandTracking';
649
+ } | {
650
+ type: 'ext:startTanstackTracking';
651
+ } | {
652
+ type: 'ext:stopTanstackTracking';
653
+ };
654
+ interface TrackingOptions {
655
+ trackAllRenders?: boolean;
656
+ componentFilter?: string[];
657
+ includeProps?: boolean;
658
+ trackZustand?: boolean;
659
+ trackRedux?: boolean;
660
+ trackRouter?: boolean;
661
+ trackContext?: boolean;
662
+ trackTanstackQuery?: boolean;
663
+ trackNetwork?: boolean;
664
+ batchSize?: number;
665
+ batchDelayMs?: number;
666
+ }
667
+ /**
668
+ * FloTrace provider configuration
669
+ */
670
+ interface FloTraceConfig {
671
+ /** WebSocket server port (default: 3457) */
672
+ port?: number;
673
+ /** App name to display in FloTrace */
674
+ appName?: string;
675
+ /** Enable/disable tracking (default: true in development) */
676
+ enabled?: boolean;
677
+ /** Auto-reconnect on disconnect (default: true) */
678
+ autoReconnect?: boolean;
679
+ /** Reconnect interval in ms (default: 2000) */
680
+ reconnectInterval?: number;
681
+ /** Track all renders or only specific components */
682
+ trackAllRenders?: boolean;
683
+ /** Include props in render events (default: true) */
684
+ includeProps?: boolean;
685
+ /** Track Zustand stores (default: true) */
686
+ trackZustand?: boolean;
687
+ /** Track Redux store (default: true) */
688
+ trackRedux?: boolean;
689
+ /** Track React Router (default: true) */
690
+ trackRouter?: boolean;
691
+ /** Track Context (default: true) */
692
+ trackContext?: boolean;
693
+ /** Track TanStack Query (default: true) */
694
+ trackTanstackQuery?: boolean;
695
+ }
696
+ /**
697
+ * Default configuration
698
+ */
699
+ declare const DEFAULT_CONFIG: Required<FloTraceConfig>;
700
+
701
+ type MessageHandler = (message: ExtensionToRuntimeMessage) => void;
702
+ type ConnectionHandler = (connected: boolean) => void;
703
+ /**
704
+ * WebSocket client for connecting to FloTrace VS Code extension.
705
+ * Handles connection, reconnection, and message batching.
706
+ */
707
+ declare class FloTraceWebSocketClient {
708
+ private ws;
709
+ private config;
710
+ private messageQueue;
711
+ private flushTimeout;
712
+ private reconnectTimeout;
713
+ private isConnecting;
714
+ private reconnectAttempts;
715
+ private static readonly MAX_RECONNECT_ATTEMPTS;
716
+ private static readonly MAX_RECONNECT_INTERVAL;
717
+ private static readonly BATCH_FLUSH_MS;
718
+ private static readonly MAX_QUEUE_SIZE;
719
+ private messageHandlers;
720
+ private connectionHandlers;
721
+ constructor(config?: FloTraceConfig);
722
+ /**
723
+ * Connect to the FloTrace WebSocket server
724
+ */
725
+ connect(): void;
726
+ /**
727
+ * Disconnect from the server
728
+ */
729
+ disconnect(): void;
730
+ /**
731
+ * Send a message to the extension (queued and batched)
732
+ */
733
+ send(message: RuntimeMessage): void;
734
+ /**
735
+ * Send a message immediately (not batched)
736
+ */
737
+ sendImmediate(message: RuntimeMessage): void;
738
+ /**
739
+ * Flush the message queue
740
+ */
741
+ private flush;
742
+ /**
743
+ * Schedule a reconnection attempt
744
+ */
745
+ private scheduleReconnect;
746
+ /**
747
+ * Handle incoming message from extension
748
+ */
749
+ private handleMessage;
750
+ /**
751
+ * Notify connection state change
752
+ */
753
+ private notifyConnectionChange;
754
+ /**
755
+ * Add a message handler
756
+ */
757
+ onMessage(handler: MessageHandler): () => void;
758
+ /**
759
+ * Add a connection state handler
760
+ */
761
+ onConnectionChange(handler: ConnectionHandler): () => void;
762
+ /**
763
+ * Check if connected
764
+ */
765
+ get connected(): boolean;
766
+ /**
767
+ * Get React version if available
768
+ */
769
+ private getReactVersion;
770
+ }
771
+ /**
772
+ * Get or create the singleton WebSocket client
773
+ */
774
+ declare function getWebSocketClient(config?: FloTraceConfig): FloTraceWebSocketClient;
775
+ /**
776
+ * Dispose the singleton client
777
+ */
778
+ declare function disposeWebSocketClient(): void;
779
+
780
+ /**
781
+ * Redux Store Tracker for @flotrace/runtime
782
+ *
783
+ * Subscribes to a Redux store and sends state change notifications
784
+ * to the FloTrace VS Code extension.
785
+ *
786
+ * Design: User passes their Redux store via the `reduxStore` prop on <FloTraceProvider>.
787
+ * We subscribe via store.subscribe() and use store.getState() to capture state.
788
+ *
789
+ * Key difference from Zustand tracker:
790
+ * - Redux store.subscribe() callback receives no arguments
791
+ * - We must call store.getState() manually and diff against previous snapshot
792
+ * - Redux is a single global store (no multi-store record)
793
+ */
794
+
795
+ /** Minimal Redux store interface — only what we need to subscribe */
796
+ interface ReduxStoreApi {
797
+ subscribe: (listener: () => void) => () => void;
798
+ getState: () => Record<string, unknown>;
799
+ }
800
+ /**
801
+ * Validate that an object looks like a Redux store.
802
+ * Checks for getState, subscribe, and dispatch as functions.
803
+ */
804
+ declare function isReduxStore(obj: unknown): obj is ReduxStoreApi;
805
+ /**
806
+ * Install Redux store tracking.
807
+ * Subscribes to the store and sends runtime:redux messages on state change.
808
+ */
809
+ declare function installReduxTracker(store: ReduxStoreApi, client: FloTraceWebSocketClient): void;
810
+ /**
811
+ * Uninstall Redux store tracking.
812
+ */
813
+ declare function uninstallReduxTracker(): void;
814
+
815
+ /**
816
+ * TanStack Query Tracker for @flotrace/runtime
817
+ *
818
+ * Subscribes to QueryCache and MutationCache events and sends
819
+ * query/mutation state snapshots to the FloTrace desktop app.
820
+ *
821
+ * Features:
822
+ * - Cache state snapshots with config for health analysis
823
+ * - Wasted refetch detection (data unchanged across fetches)
824
+ * - Per-query state transition timeline (ring buffer)
825
+ * - Mutation → query correlation (detects invalidation cascades)
826
+ *
827
+ * Uses duck-typed interface — no @tanstack/react-query dependency needed.
828
+ */
829
+
830
+ /** Minimal Query interface — only what we need to read state */
831
+ interface DuckQuery {
832
+ queryKey: unknown[];
833
+ queryHash: string;
834
+ state: {
835
+ status: 'pending' | 'success' | 'error';
836
+ fetchStatus: 'idle' | 'fetching' | 'paused';
837
+ data: unknown;
838
+ error: unknown;
839
+ dataUpdatedAt: number;
840
+ errorUpdatedAt: number;
841
+ isInvalidated: boolean;
842
+ fetchFailureCount: number;
843
+ fetchFailureReason: unknown;
844
+ };
845
+ options: {
846
+ staleTime?: number;
847
+ gcTime?: number;
848
+ retry?: number | boolean;
849
+ refetchInterval?: number | false;
850
+ refetchOnWindowFocus?: boolean | 'always';
851
+ refetchOnMount?: boolean | 'always';
852
+ refetchOnReconnect?: boolean | 'always';
853
+ networkMode?: 'online' | 'always' | 'offlineFirst';
854
+ enabled?: boolean;
855
+ meta?: Record<string, unknown>;
856
+ };
857
+ getObserversCount(): number;
858
+ isStale(): boolean;
859
+ isActive(): boolean;
860
+ isDisabled(): boolean;
861
+ }
862
+ /** Minimal Mutation interface */
863
+ interface DuckMutation {
864
+ mutationId: number;
865
+ state: {
866
+ status: 'idle' | 'pending' | 'error' | 'success';
867
+ isPaused: boolean;
868
+ submittedAt: number;
869
+ variables: unknown;
870
+ error: unknown;
871
+ failureCount: number;
872
+ };
873
+ options: {
874
+ mutationKey?: unknown[];
875
+ scope?: {
876
+ id: string;
877
+ };
878
+ };
879
+ }
880
+ /** Minimal QueryCache interface */
881
+ interface DuckQueryCache {
882
+ getAll(): DuckQuery[];
883
+ subscribe(cb: (event: {
884
+ type: string;
885
+ query?: DuckQuery;
886
+ }) => void): () => void;
887
+ }
888
+ /** Minimal MutationCache interface */
889
+ interface DuckMutationCache {
890
+ getAll(): DuckMutation[];
891
+ subscribe(cb: (event: {
892
+ type: string;
893
+ mutation?: DuckMutation;
894
+ }) => void): () => void;
895
+ }
896
+ /** Duck-typed QueryClient — what we need from the user */
897
+ interface TanStackQueryClientApi {
898
+ getQueryCache(): DuckQueryCache;
899
+ getMutationCache(): DuckMutationCache;
900
+ }
901
+ /** Validate that an object looks like a TanStack QueryClient */
902
+ declare function isTanStackQueryClient(obj: unknown): obj is TanStackQueryClientApi;
903
+ /**
904
+ * Install TanStack Query tracking.
905
+ * Subscribes to QueryCache and MutationCache and sends runtime:tanstackQuery messages.
906
+ */
907
+ declare function installTanStackQueryTracker(queryClient: TanStackQueryClientApi, client: FloTraceWebSocketClient): void;
908
+ /**
909
+ * Uninstall TanStack Query tracking.
910
+ */
911
+ declare function uninstallTanStackQueryTracker(): void;
912
+
913
+ /**
914
+ * Context for FloTrace runtime state
915
+ */
916
+ interface FloTraceContextValue {
917
+ connected: boolean;
918
+ enabled: boolean;
919
+ config: Required<FloTraceConfig>;
920
+ }
921
+ /**
922
+ * Hook to access FloTrace context
923
+ */
924
+ declare function useFloTrace(): FloTraceContextValue | null;
925
+ /**
926
+ * Props for FloTraceProvider
927
+ */
928
+ interface FloTraceProviderProps {
929
+ children: ReactNode;
930
+ config?: FloTraceConfig;
931
+ /**
932
+ * Optional Zustand stores to track. Keys become the store names shown in FloTrace.
933
+ * Each value must be a Zustand store with .subscribe() and .getState() methods.
934
+ *
935
+ * @example
936
+ * ```tsx
937
+ * import { useBearStore } from './store/bearStore';
938
+ *
939
+ * <FloTraceProvider stores={{ bearStore: useBearStore }}>
940
+ * <App />
941
+ * </FloTraceProvider>
942
+ * ```
943
+ */
944
+ stores?: Record<string, {
945
+ subscribe: (...args: unknown[]) => () => void;
946
+ getState: () => Record<string, unknown>;
947
+ }>;
948
+ /**
949
+ * Optional Redux store to track. State changes are shown in FloTrace's Redux panel.
950
+ *
951
+ * @example
952
+ * ```tsx
953
+ * import { store } from './store';
954
+ *
955
+ * <FloTraceProvider reduxStore={store}>
956
+ * <App />
957
+ * </FloTraceProvider>
958
+ * ```
959
+ */
960
+ reduxStore?: ReduxStoreApi;
961
+ /**
962
+ * Optional TanStack Query client to track. Query and mutation state
963
+ * is shown in FloTrace's TanStack Query panel.
964
+ *
965
+ * @example
966
+ * ```tsx
967
+ * import { queryClient } from './queryClient';
968
+ *
969
+ * <FloTraceProvider queryClient={queryClient}>
970
+ * <QueryClientProvider client={queryClient}>
971
+ * <App />
972
+ * </QueryClientProvider>
973
+ * </FloTraceProvider>
974
+ * ```
975
+ */
976
+ queryClient?: TanStackQueryClientApi;
977
+ }
978
+ /**
979
+ * FloTraceProvider wraps your React app to enable real-time render tracking.
980
+ *
981
+ * @example
982
+ * ```tsx
983
+ * import { FloTraceProvider } from '@flotrace/runtime';
984
+ *
985
+ * createRoot(document.getElementById('root')).render(
986
+ * <FloTraceProvider config={{ appName: 'My App' }}>
987
+ * <App />
988
+ * </FloTraceProvider>
989
+ * );
990
+ * ```
991
+ */
992
+ declare function FloTraceProvider({ children, config, stores, reduxStore, queryClient }: FloTraceProviderProps): JSX.Element;
993
+ /**
994
+ * Higher-order component to wrap a component with FloTrace profiling.
995
+ * Use this for targeted profiling of specific components.
996
+ *
997
+ * @example
998
+ * ```tsx
999
+ * import { withFloTrace } from '@flotrace/runtime';
1000
+ *
1001
+ * const ProfiledComponent = withFloTrace(MyComponent, 'MyComponent');
1002
+ * ```
1003
+ */
1004
+ declare function withFloTrace<P extends object>(Component: React.ComponentType<P>, displayName?: string): React.FC<P>;
1005
+ /**
1006
+ * Hook to track props changes for a component.
1007
+ * Call this at the top of your component to track its props.
1008
+ *
1009
+ * @example
1010
+ * ```tsx
1011
+ * function MyComponent(props: MyProps) {
1012
+ * useTrackProps('MyComponent', props);
1013
+ * // ... rest of component
1014
+ * }
1015
+ * ```
1016
+ */
1017
+ declare function useTrackProps(componentName: string, props: Record<string, unknown>): void;
1018
+
1019
+ /**
1020
+ * Fiber Tree Walker for @flotrace/runtime
1021
+ *
1022
+ * Captures the full React component hierarchy and sends tree snapshots
1023
+ * to the FloTrace VS Code extension via WebSocket.
1024
+ *
1025
+ * Two strategies for accessing the fiber tree:
1026
+ * 1. DevTools Hook: Wraps __REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot (event-driven)
1027
+ * 2. DOM Fallback: Finds fibers via __reactFiber$ keys on DOM elements (works without DevTools)
1028
+ *
1029
+ * The DOM fallback is triggered by requestTreeSnapshot() which is called
1030
+ * from the Profiler's onRender callback - so tree walks happen after each React commit.
1031
+ */
1032
+
1033
+ /**
1034
+ * Minimal fiber type - only the fields we access.
1035
+ * React fibers are internal, so we type just what we use.
1036
+ */
1037
+ interface Fiber {
1038
+ tag: number;
1039
+ key: string | null;
1040
+ type: FiberType | null;
1041
+ child: Fiber | null;
1042
+ sibling: Fiber | null;
1043
+ return: Fiber | null;
1044
+ memoizedProps: Record<string, unknown> | null;
1045
+ pendingProps: Record<string, unknown> | null;
1046
+ actualDuration?: number;
1047
+ alternate?: Fiber | null;
1048
+ stateNode?: unknown;
1049
+ _debugSource?: {
1050
+ fileName: string;
1051
+ lineNumber: number;
1052
+ } | null;
1053
+ /** Hook state linked list head (useState, useRef, useMemo, etc.) */
1054
+ memoizedState: FiberHookState | null;
1055
+ /** Effect queue (useEffect, useLayoutEffect circular list) */
1056
+ updateQueue: FiberUpdateQueue | null;
1057
+ /** Fiber flags (for detecting force updates, etc.) */
1058
+ flags: number;
1059
+ /** Pending work lanes on this fiber */
1060
+ lanes: number;
1061
+ /** Pending work lanes on this fiber's subtree */
1062
+ childLanes: number;
1063
+ /** Element type (used for context detection) */
1064
+ elementType: unknown;
1065
+ /** Context dependencies (React 18+) */
1066
+ dependencies: FiberDependencies | null;
1067
+ /** Debug hook types array (dev mode: ["useState", "useEffect", ...]) */
1068
+ _debugHookTypes?: string[] | null;
1069
+ }
1070
+ /**
1071
+ * Hook state linked list node from fiber.memoizedState.
1072
+ * Each hook call creates one node in this linked list.
1073
+ */
1074
+ interface FiberHookState {
1075
+ memoizedState: unknown;
1076
+ baseState: unknown;
1077
+ baseQueue: unknown;
1078
+ queue: {
1079
+ pending: unknown;
1080
+ lastRenderedReducer: ((...args: unknown[]) => unknown) | null;
1081
+ lastRenderedState: unknown;
1082
+ dispatch?: (...args: unknown[]) => void;
1083
+ } | null;
1084
+ next: FiberHookState | null;
1085
+ }
1086
+ /**
1087
+ * Effect structure in the updateQueue circular linked list.
1088
+ * Tag bitmask: HookHasEffect=0b0001, Insertion=0b0010, Layout=0b0100, Passive=0b1000
1089
+ */
1090
+ interface FiberEffect {
1091
+ tag: number;
1092
+ create: (() => (() => void) | void) | null;
1093
+ destroy: (() => void) | null;
1094
+ deps: unknown[] | null;
1095
+ next: FiberEffect | null;
1096
+ }
1097
+ interface FiberUpdateQueue {
1098
+ lastEffect: FiberEffect | null;
1099
+ /** Class component update queue — pending updates linked list */
1100
+ shared?: {
1101
+ pending?: unknown;
1102
+ };
1103
+ /** Lane bits for this queue */
1104
+ lanes?: number;
1105
+ }
1106
+ interface FiberDependencies {
1107
+ firstContext: FiberContextDependency | null;
1108
+ }
1109
+ interface FiberContextDependency {
1110
+ context: {
1111
+ _currentValue: unknown;
1112
+ displayName?: string;
1113
+ };
1114
+ memoizedValue: unknown;
1115
+ next: FiberContextDependency | null;
1116
+ }
1117
+ type FiberType = {
1118
+ name?: string;
1119
+ displayName?: string;
1120
+ render?: {
1121
+ name?: string;
1122
+ displayName?: string;
1123
+ };
1124
+ type?: {
1125
+ name?: string;
1126
+ displayName?: string;
1127
+ };
1128
+ };
1129
+ /**
1130
+ * FiberRoot from React internals (what onCommitFiberRoot receives)
1131
+ */
1132
+ interface FiberRoot {
1133
+ current: Fiber;
1134
+ }
1135
+ /**
1136
+ * The global hook React DevTools uses. We piggyback on it.
1137
+ */
1138
+ interface DevToolsHook {
1139
+ onCommitFiberRoot?: (rendererID: number, root: FiberRoot, priority?: number) => void;
1140
+ }
1141
+ declare global {
1142
+ interface Window {
1143
+ __REACT_DEVTOOLS_GLOBAL_HOOK__?: DevToolsHook & Record<string, unknown>;
1144
+ }
1145
+ }
1146
+ /**
1147
+ * Request a tree snapshot using the DOM fallback approach.
1148
+ * Called from FloTraceProvider's Profiler onRender callback after each React commit.
1149
+ * This is the primary way to trigger tree walks when DevTools hook isn't available.
1150
+ *
1151
+ * When the DevTools hook strategy is active, this acts as a safety net:
1152
+ * if no snapshot has been sent via onCommitFiberRoot within DEVTOOLS_STALE_THRESHOLD_MS,
1153
+ * we fall back to DOM-based snapshots. This handles React 19 compatibility issues
1154
+ * where onCommitFiberRoot may not fire reliably for all commits.
1155
+ */
1156
+ declare function requestTreeSnapshot(): void;
1157
+ /**
1158
+ * Install the fiber tree walker.
1159
+ *
1160
+ * Strategy 1 (preferred): Hook into __REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot
1161
+ * Strategy 2 (fallback): DOM-based fiber access, triggered by requestTreeSnapshot()
1162
+ *
1163
+ * Tree snapshots are always sent WITHOUT props (structure only).
1164
+ * Props are fetched on demand via getNodeProps() when the user selects a node.
1165
+ *
1166
+ * @returns Cleanup function to uninstall
1167
+ */
1168
+ declare function installFiberTreeWalker(): () => void;
1169
+ /**
1170
+ * Get detailed render reason for a specific node by ID.
1171
+ * Uses fiberRefMap to look up the cached fiber reference.
1172
+ */
1173
+ declare function getDetailedRenderReason(nodeId: string): DetailedRenderReason | null;
1174
+ /**
1175
+ * Get all hooks for a specific node by ID.
1176
+ * Returns null if the node is not found (e.g., unmounted).
1177
+ */
1178
+ declare function getNodeHooks(nodeId: string): HookInfo[] | null;
1179
+ /**
1180
+ * Get all effects for a specific node by ID.
1181
+ * Returns null if the node is not found (e.g., unmounted).
1182
+ */
1183
+ declare function getNodeEffects(nodeId: string): EffectInfo[] | null;
1184
+ /**
1185
+ * Get the fiberRefMap for external use (e.g., console tracker fiber attribution).
1186
+ */
1187
+ declare function getFiberRefMap(): Map<string, Fiber>;
1188
+ /**
1189
+ * Uninstall the fiber tree walker, restoring the original hook.
1190
+ */
1191
+ declare function uninstallFiberTreeWalker(): void;
1192
+
1193
+ /**
1194
+ * Hook Inspector for @flotrace/runtime
1195
+ *
1196
+ * Walks fiber.memoizedState linked list, classifies each hook by type,
1197
+ * and serializes values for display in FloTrace.
1198
+ *
1199
+ * Hook classification uses a combination of:
1200
+ * 1. fiber._debugHookTypes (available in dev builds — most reliable)
1201
+ * 2. Shape-based inference from memoizedState structure (fallback)
1202
+ *
1203
+ * Shape heuristics (from React internals):
1204
+ * - useState/useReducer: queue !== null (has dispatch/reducer)
1205
+ * - useRef: memoizedState is { current: <value> } with single key
1206
+ * - useMemo/useCallback: memoizedState is [computedValue, deps]
1207
+ * - useEffect/useLayoutEffect: matched against effect circular list via tag bitmask
1208
+ */
1209
+
1210
+ /**
1211
+ * Inspect all hooks in a fiber's memoizedState linked list.
1212
+ * Returns an array of HookInfo objects with type, value, and deps.
1213
+ */
1214
+ declare function inspectHooks(fiber: Fiber): HookInfo[];
1215
+
1216
+ /**
1217
+ * Effect Dependency Tracker for @flotrace/runtime
1218
+ *
1219
+ * Walks fiber.updateQueue.lastEffect circular linked list to extract
1220
+ * all effects (useEffect, useLayoutEffect, useInsertionEffect) with:
1221
+ * - Current and previous dependency arrays
1222
+ * - Which specific deps changed (triggering the effect)
1223
+ * - Whether the effect will run on this render
1224
+ * - Whether a cleanup function exists
1225
+ *
1226
+ * Effect tag bitmask (from React's HookFlags):
1227
+ * HookHasEffect = 0b0001 (1) — effect will execute
1228
+ * HookInsertion = 0b0010 (2) — useInsertionEffect
1229
+ * HookLayout = 0b0100 (4) — useLayoutEffect
1230
+ * HookPassive = 0b1000 (8) — useEffect
1231
+ */
1232
+
1233
+ /**
1234
+ * Inspect all effects in a fiber's updateQueue.
1235
+ * Compares current deps with previous (from fiber.alternate) to detect changes.
1236
+ */
1237
+ declare function inspectEffects(fiber: Fiber): EffectInfo[];
1238
+
1239
+ /**
1240
+ * Zustand Store Tracker for @flotrace/runtime
1241
+ *
1242
+ * Subscribes to explicitly registered Zustand stores and sends
1243
+ * state change notifications to the FloTrace VS Code extension.
1244
+ *
1245
+ * Design: User registers stores via the `stores` prop on <FloTraceProvider>.
1246
+ * We subscribe to each store's .subscribe() method and use .getState()
1247
+ * to capture state on change.
1248
+ *
1249
+ * Why explicit registration vs. automatic detection:
1250
+ * - Automatic detection requires patching Zustand internals (fragile across versions)
1251
+ * - Explicit registration is simple, reliable, and version-independent
1252
+ * - Users can control exactly which stores are tracked
1253
+ */
1254
+
1255
+ /** Minimal Zustand store interface — only what we need to subscribe */
1256
+ interface ZustandStoreApi {
1257
+ subscribe: (listener: (state: Record<string, unknown>, prevState: Record<string, unknown>) => void) => () => void;
1258
+ getState: () => Record<string, unknown>;
1259
+ }
1260
+ /**
1261
+ * Install Zustand store tracking.
1262
+ * Subscribes to each store and sends runtime:zustand messages on state change.
1263
+ */
1264
+ declare function installZustandTracker(stores: Record<string, ZustandStoreApi>, client: FloTraceWebSocketClient): void;
1265
+ /**
1266
+ * Uninstall Zustand store tracking, unsubscribing from all stores.
1267
+ */
1268
+ declare function uninstallZustandTracker(): void;
1269
+
1270
+ /**
1271
+ * Router Tracker for @flotrace/runtime
1272
+ *
1273
+ * Automatically tracks URL navigation by patching the browser's History API.
1274
+ * Sends pathname and search params to the FloTrace VS Code extension on every
1275
+ * navigation event (pushState, replaceState, popstate).
1276
+ *
1277
+ * Why History API patching:
1278
+ * - React Router (and all SPA routers) ultimately use history.pushState/replaceState
1279
+ * - Works with any router library (React Router, TanStack Router, Next.js, etc.)
1280
+ * - Zero user code changes — no hooks or components to install
1281
+ * - Covers programmatic navigation, link clicks, and back/forward buttons
1282
+ *
1283
+ * Limitation: We get pathname + search params but NOT matched route params
1284
+ * (e.g., /users/:id → { id: '123' }). Those require React Router context access
1285
+ * and can be added as a future enhancement.
1286
+ */
1287
+
1288
+ /**
1289
+ * Install router tracking.
1290
+ * Patches history.pushState/replaceState and listens for popstate events
1291
+ * to detect all navigation in the browser.
1292
+ */
1293
+ declare function installRouterTracker(wsClient: FloTraceWebSocketClient): void;
1294
+ /**
1295
+ * Uninstall router tracking, restoring original History methods.
1296
+ */
1297
+ declare function uninstallRouterTracker(): void;
1298
+
1299
+ /**
1300
+ * Component Event Timeline for @flotrace/runtime
1301
+ *
1302
+ * Emits lifecycle events (mount, unmount, render, effect-run, effect-cleanup,
1303
+ * state-update, props-change) for each component. Events are stored in a
1304
+ * ring buffer per component (max 100 events) to prevent memory growth.
1305
+ *
1306
+ * Events are batched and flushed every 500ms to avoid flooding the WebSocket.
1307
+ */
1308
+
1309
+ /**
1310
+ * Install the timeline tracker.
1311
+ * Call once during ext:startTracking.
1312
+ */
1313
+ declare function installTimelineTracker(wsClient: FloTraceWebSocketClient): void;
1314
+ /**
1315
+ * Uninstall the timeline tracker and clean up resources.
1316
+ */
1317
+ declare function uninstallTimelineTracker(): void;
1318
+ /**
1319
+ * Record a lifecycle event for a component.
1320
+ * Called from the fiber tree walker during tree walks and from effect trackers.
1321
+ *
1322
+ * @param nodeId - Path-based node ID (e.g., "App-0/Dashboard-0/Card-2")
1323
+ * @param componentName - Component display name
1324
+ * @param eventType - Lifecycle event type
1325
+ * @param detail - Optional additional context (serialized)
1326
+ * @param duration - Optional duration in ms (for render events)
1327
+ */
1328
+ declare function recordTimelineEvent(nodeId: string, componentName: string, eventType: TimelineEventType, detail?: unknown, duration?: number): void;
1329
+ /**
1330
+ * Get the timeline for a specific component (for on-demand requests).
1331
+ */
1332
+ declare function getTimeline(nodeId: string): TimelineEvent[];
1333
+
1334
+ /**
1335
+ * Network Request Tracker for @flotrace/runtime
1336
+ *
1337
+ * Patches globalThis.fetch and XMLHttpRequest to capture all network requests
1338
+ * with React component attribution. Designed to chain properly with the existing
1339
+ * RSC payload interceptor (which patches fetch first for Next.js RSC requests).
1340
+ *
1341
+ * Key design decisions:
1342
+ * - Metadata only: URL path, method, status, timing, size (no bodies, no query params, no auth)
1343
+ * - Chains with existing fetch patches (RSC interceptor) — stores current globalThis.fetch, not native
1344
+ * - Component attribution via getCurrentRenderingFiber() + effect context (React 18 + 19)
1345
+ * - Duplicate detection via sliding 5-second window keyed by method:path
1346
+ * - Noise filtering: analytics, HMR, extensions, static assets, FloTrace's own WS
1347
+ * - Batched sending: 500ms flush, max 50 entries per batch, 300 entry ring buffer
1348
+ * - AbortController support: detects aborted requests
1349
+ */
1350
+
1351
+ declare function installNetworkTracker(wsClient: FloTraceWebSocketClient): void;
1352
+ declare function uninstallNetworkTracker(): void;
1353
+
1354
+ /**
1355
+ * Serialize a value for safe transmission over WebSocket.
1356
+ * Handles circular references, functions, symbols, and large values.
1357
+ */
1358
+ declare function serializeValue(value: unknown, depth?: number, seen?: WeakSet<object>): SerializedValue;
1359
+ /**
1360
+ * Serialize props object, filtering out React internals and children
1361
+ */
1362
+ declare function serializeProps(props: Record<string, unknown>): Record<string, SerializedValue>;
1363
+
1364
+ export { DEFAULT_CONFIG, type DetailedRenderReason, type DetailedRenderReasonType, type EffectInfo, type Fiber, type FiberEffect, type FiberHookState, type FloTraceConfig, FloTraceProvider, type FloTraceProviderProps, FloTraceWebSocketClient, type HookInfo, type HookType, type LiveTreeNode, type NetworkRequestEntry, type PropChange, type ReduxStoreApi, type SerializedValue, type TanStackMutationInfo, type TanStackQueryClientApi, type TanStackQueryInfo, type TimelineEvent, type TimelineEventType, type TrackingOptions, disposeWebSocketClient, getDetailedRenderReason, getFiberRefMap, getNodeEffects, getNodeHooks, getTimeline, getWebSocketClient, inspectEffects, inspectHooks, installFiberTreeWalker, installNetworkTracker, installReduxTracker, installRouterTracker, installTanStackQueryTracker, installTimelineTracker, installZustandTracker, isReduxStore, isTanStackQueryClient, recordTimelineEvent, requestTreeSnapshot, serializeProps, serializeValue, uninstallFiberTreeWalker, uninstallNetworkTracker, uninstallReduxTracker, uninstallRouterTracker, uninstallTanStackQueryTracker, uninstallTimelineTracker, uninstallZustandTracker, useFloTrace, useTrackProps, withFloTrace };