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