@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/dist/index.mjs ADDED
@@ -0,0 +1,3617 @@
1
+ // src/types.ts
2
+ var DEFAULT_CONFIG = {
3
+ port: 3457,
4
+ appName: "React App",
5
+ enabled: globalThis.process?.env?.NODE_ENV === "development",
6
+ autoReconnect: true,
7
+ reconnectInterval: 2e3,
8
+ trackAllRenders: true,
9
+ includeProps: true,
10
+ trackZustand: true,
11
+ trackRedux: true,
12
+ trackRouter: true,
13
+ trackContext: true,
14
+ trackTanstackQuery: true,
15
+ getAppUrl: void 0
16
+ };
17
+
18
+ // src/serializer.ts
19
+ var MAX_DEPTH = 5;
20
+ var MAX_STRING_LENGTH = 500;
21
+ var MAX_ARRAY_LENGTH = 50;
22
+ var MAX_OBJECT_KEYS = 30;
23
+ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
24
+ if (value === null) {
25
+ return null;
26
+ }
27
+ if (value === void 0) {
28
+ return { __type: "undefined" };
29
+ }
30
+ if (typeof value === "boolean") {
31
+ return value;
32
+ }
33
+ if (typeof value === "number") {
34
+ if (Number.isNaN(value)) return "NaN";
35
+ if (!Number.isFinite(value)) return value > 0 ? "Infinity" : "-Infinity";
36
+ return value;
37
+ }
38
+ if (typeof value === "string") {
39
+ if (value.length > MAX_STRING_LENGTH) {
40
+ return {
41
+ __type: "truncated",
42
+ originalType: "string",
43
+ length: value.length
44
+ };
45
+ }
46
+ return value;
47
+ }
48
+ if (typeof value === "symbol") {
49
+ return {
50
+ __type: "symbol",
51
+ description: value.description
52
+ };
53
+ }
54
+ if (typeof value === "function") {
55
+ return {
56
+ __type: "function",
57
+ name: value.name || "anonymous"
58
+ };
59
+ }
60
+ if (typeof value === "object") {
61
+ if (seen.has(value)) {
62
+ return { __type: "circular" };
63
+ }
64
+ if (depth >= MAX_DEPTH) {
65
+ return {
66
+ __type: "truncated",
67
+ originalType: Array.isArray(value) ? "array" : "object"
68
+ };
69
+ }
70
+ seen.add(value);
71
+ if (Array.isArray(value)) {
72
+ if (value.length > MAX_ARRAY_LENGTH) {
73
+ const truncated = value.slice(0, MAX_ARRAY_LENGTH).map((item) => serializeValue(item, depth + 1, seen));
74
+ return [
75
+ ...truncated,
76
+ {
77
+ __type: "truncated",
78
+ originalType: "array",
79
+ length: value.length
80
+ }
81
+ ];
82
+ }
83
+ return value.map((item) => serializeValue(item, depth + 1, seen));
84
+ }
85
+ if (value instanceof Date) {
86
+ return value.toISOString();
87
+ }
88
+ if (value instanceof Error) {
89
+ return {
90
+ name: value.name,
91
+ message: value.message
92
+ };
93
+ }
94
+ if (value instanceof Map) {
95
+ const obj = {};
96
+ let count = 0;
97
+ for (const [k, v] of value.entries()) {
98
+ if (count >= MAX_OBJECT_KEYS) {
99
+ obj.__truncated = { __type: "truncated", originalType: "Map", length: value.size };
100
+ break;
101
+ }
102
+ obj[String(k)] = serializeValue(v, depth + 1, seen);
103
+ count++;
104
+ }
105
+ return obj;
106
+ }
107
+ if (value instanceof Set) {
108
+ const arr = Array.from(value);
109
+ if (arr.length > MAX_ARRAY_LENGTH) {
110
+ return {
111
+ __type: "truncated",
112
+ originalType: "Set",
113
+ length: arr.length
114
+ };
115
+ }
116
+ return arr.map((item) => serializeValue(item, depth + 1, seen));
117
+ }
118
+ if (value instanceof RegExp) {
119
+ return value.toString();
120
+ }
121
+ const keys = Object.keys(value);
122
+ const result = {};
123
+ for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
124
+ const key = keys[i];
125
+ try {
126
+ result[key] = serializeValue(
127
+ value[key],
128
+ depth + 1,
129
+ seen
130
+ );
131
+ } catch {
132
+ result[key] = { __type: "truncated", originalType: "error" };
133
+ }
134
+ }
135
+ if (keys.length > MAX_OBJECT_KEYS) {
136
+ result.__truncated = {
137
+ __type: "truncated",
138
+ originalType: "object",
139
+ length: keys.length
140
+ };
141
+ }
142
+ return result;
143
+ }
144
+ return { __type: "truncated", originalType: typeof value };
145
+ }
146
+ function serializeProps(props) {
147
+ const result = {};
148
+ for (const [key, value] of Object.entries(props)) {
149
+ if (key === "children" || key === "key" || key === "ref") {
150
+ continue;
151
+ }
152
+ if (key.startsWith("__")) {
153
+ continue;
154
+ }
155
+ try {
156
+ result[key] = serializeValue(value);
157
+ } catch (error) {
158
+ console.error(`[FloTrace] Error serializing prop "${key}":`, error);
159
+ result[key] = { __type: "truncated", originalType: "error" };
160
+ }
161
+ }
162
+ return result;
163
+ }
164
+ function getChangedKeys(prev, next) {
165
+ if (!prev) {
166
+ return Object.keys(next);
167
+ }
168
+ const changed = [];
169
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
170
+ for (const key of allKeys) {
171
+ if (!Object.is(prev[key], next[key])) {
172
+ changed.push(key);
173
+ }
174
+ }
175
+ return changed;
176
+ }
177
+
178
+ // src/websocketClient.ts
179
+ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
180
+ constructor(config = {}) {
181
+ this.ws = null;
182
+ this.messageQueue = [];
183
+ this.flushTimeout = null;
184
+ this.reconnectTimeout = null;
185
+ this.isConnecting = false;
186
+ this.reconnectAttempts = 0;
187
+ // Prevent unbounded queue growth when disconnected
188
+ this.messageHandlers = /* @__PURE__ */ new Set();
189
+ this.connectionHandlers = /* @__PURE__ */ new Set();
190
+ this.config = { ...DEFAULT_CONFIG, ...config };
191
+ }
192
+ /**
193
+ * Connect to the FloTrace WebSocket server
194
+ */
195
+ connect() {
196
+ if (this.ws || this.isConnecting) {
197
+ return;
198
+ }
199
+ if (!this.config.enabled) {
200
+ console.log("[FloTrace] Runtime disabled, skipping connection");
201
+ return;
202
+ }
203
+ if (typeof WebSocket === "undefined") {
204
+ console.log("[FloTrace] WebSocket not available, skipping connection");
205
+ return;
206
+ }
207
+ this.isConnecting = true;
208
+ try {
209
+ const host = this.config.host ?? "127.0.0.1";
210
+ const tokenParam = this.config.authToken ? `?token=${encodeURIComponent(this.config.authToken)}` : "";
211
+ const url = `ws://${host}:${this.config.port}${tokenParam}`;
212
+ console.log(`[FloTrace] Connecting to ${url.replace(/token=[^&]+/, "token=***")}...`);
213
+ this.ws = new WebSocket(url);
214
+ this.ws.onopen = () => {
215
+ this.isConnecting = false;
216
+ this.reconnectAttempts = 0;
217
+ console.log("[FloTrace] Connected to VS Code extension");
218
+ this.notifyConnectionChange(true);
219
+ this.send({
220
+ type: "runtime:ready",
221
+ appName: this.config.appName,
222
+ reactVersion: this.getReactVersion(),
223
+ appUrl: this.config.getAppUrl?.(),
224
+ platform: this.config.platform,
225
+ appId: this.config.appId,
226
+ appVersion: this.config.appVersion
227
+ });
228
+ this.flush();
229
+ };
230
+ this.ws.onmessage = (event) => {
231
+ try {
232
+ const message = JSON.parse(event.data);
233
+ this.handleMessage(message);
234
+ } catch (error) {
235
+ console.error("[FloTrace] Failed to parse message:", error);
236
+ }
237
+ };
238
+ this.ws.onclose = () => {
239
+ this.isConnecting = false;
240
+ this.ws = null;
241
+ console.log("[FloTrace] Disconnected from VS Code extension");
242
+ this.notifyConnectionChange(false);
243
+ if (this.config.autoReconnect) {
244
+ this.scheduleReconnect();
245
+ }
246
+ };
247
+ this.ws.onerror = (error) => {
248
+ this.isConnecting = false;
249
+ console.error("[FloTrace] WebSocket error:", error);
250
+ };
251
+ } catch (error) {
252
+ this.isConnecting = false;
253
+ console.error("[FloTrace] Failed to connect:", error);
254
+ if (this.config.autoReconnect) {
255
+ this.scheduleReconnect();
256
+ }
257
+ }
258
+ }
259
+ /**
260
+ * Disconnect from the server
261
+ */
262
+ disconnect() {
263
+ if (this.reconnectTimeout) {
264
+ clearTimeout(this.reconnectTimeout);
265
+ this.reconnectTimeout = null;
266
+ }
267
+ if (this.flushTimeout) {
268
+ clearTimeout(this.flushTimeout);
269
+ this.flushTimeout = null;
270
+ }
271
+ if (this.ws) {
272
+ try {
273
+ this.send({ type: "runtime:disconnect", reason: "Client disconnect" });
274
+ } catch (error) {
275
+ console.error("[FloTrace] Error sending disconnect message:", error);
276
+ }
277
+ this.ws.close();
278
+ this.ws = null;
279
+ }
280
+ }
281
+ /**
282
+ * Send a message to the extension (queued and batched)
283
+ */
284
+ send(message) {
285
+ if (!this.config.enabled) {
286
+ return;
287
+ }
288
+ this.messageQueue.push(message);
289
+ if (this.messageQueue.length > _FloTraceWebSocketClient.MAX_QUEUE_SIZE) {
290
+ this.messageQueue = this.messageQueue.slice(-_FloTraceWebSocketClient.MAX_QUEUE_SIZE);
291
+ }
292
+ if (!this.flushTimeout) {
293
+ this.flushTimeout = setTimeout(() => {
294
+ this.flush();
295
+ }, _FloTraceWebSocketClient.BATCH_FLUSH_MS);
296
+ }
297
+ if (this.messageQueue.length >= (this.config.trackAllRenders ? 50 : 10)) {
298
+ this.flush();
299
+ }
300
+ }
301
+ /**
302
+ * Send a message immediately (not batched)
303
+ */
304
+ sendImmediate(message) {
305
+ if (!this.config.enabled || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
306
+ return;
307
+ }
308
+ try {
309
+ this.ws.send(JSON.stringify(message));
310
+ } catch (error) {
311
+ console.error("[FloTrace] Failed to send message:", error);
312
+ }
313
+ }
314
+ /**
315
+ * Flush the message queue
316
+ */
317
+ flush() {
318
+ if (this.flushTimeout) {
319
+ clearTimeout(this.flushTimeout);
320
+ this.flushTimeout = null;
321
+ }
322
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || this.messageQueue.length === 0) {
323
+ return;
324
+ }
325
+ try {
326
+ for (const message of this.messageQueue) {
327
+ this.ws.send(JSON.stringify(message));
328
+ }
329
+ this.messageQueue = [];
330
+ } catch (error) {
331
+ console.error("[FloTrace] Failed to flush messages:", error);
332
+ }
333
+ }
334
+ /**
335
+ * Schedule a reconnection attempt
336
+ */
337
+ scheduleReconnect() {
338
+ if (this.reconnectTimeout) {
339
+ return;
340
+ }
341
+ if (this.reconnectAttempts >= _FloTraceWebSocketClient.MAX_RECONNECT_ATTEMPTS) {
342
+ console.warn(
343
+ `[FloTrace] Reconnection budget exhausted (${_FloTraceWebSocketClient.MAX_RECONNECT_ATTEMPTS} attempts). Reload the page or restart the extension to retry.`
344
+ );
345
+ return;
346
+ }
347
+ const baseDelay = this.config.reconnectInterval || 2e3;
348
+ const delay = Math.min(
349
+ baseDelay * Math.pow(2, this.reconnectAttempts),
350
+ _FloTraceWebSocketClient.MAX_RECONNECT_INTERVAL
351
+ );
352
+ this.reconnectAttempts++;
353
+ console.log(
354
+ `[FloTrace] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${_FloTraceWebSocketClient.MAX_RECONNECT_ATTEMPTS})`
355
+ );
356
+ this.reconnectTimeout = setTimeout(() => {
357
+ this.reconnectTimeout = null;
358
+ this.connect();
359
+ }, delay);
360
+ }
361
+ /**
362
+ * Handle incoming message from extension
363
+ */
364
+ handleMessage(message) {
365
+ if (message.type === "ext:ping") {
366
+ this.sendImmediate({ type: "runtime:pong", timestamp: Date.now() });
367
+ return;
368
+ }
369
+ for (const handler of this.messageHandlers) {
370
+ try {
371
+ handler(message);
372
+ } catch (error) {
373
+ console.error("[FloTrace] Message handler error:", error);
374
+ }
375
+ }
376
+ }
377
+ /**
378
+ * Notify connection state change
379
+ */
380
+ notifyConnectionChange(connected) {
381
+ for (const handler of this.connectionHandlers) {
382
+ try {
383
+ handler(connected);
384
+ } catch (error) {
385
+ console.error("[FloTrace] Connection handler error:", error);
386
+ }
387
+ }
388
+ }
389
+ /**
390
+ * Add a message handler
391
+ */
392
+ onMessage(handler) {
393
+ this.messageHandlers.add(handler);
394
+ return () => this.messageHandlers.delete(handler);
395
+ }
396
+ /**
397
+ * Add a connection state handler
398
+ */
399
+ onConnectionChange(handler) {
400
+ this.connectionHandlers.add(handler);
401
+ return () => this.connectionHandlers.delete(handler);
402
+ }
403
+ /**
404
+ * Check if connected
405
+ */
406
+ get connected() {
407
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
408
+ }
409
+ /**
410
+ * Get React version if available
411
+ */
412
+ getReactVersion() {
413
+ try {
414
+ const React = globalThis.React;
415
+ return React?.version;
416
+ } catch {
417
+ return void 0;
418
+ }
419
+ }
420
+ };
421
+ _FloTraceWebSocketClient.MAX_RECONNECT_ATTEMPTS = 10;
422
+ _FloTraceWebSocketClient.MAX_RECONNECT_INTERVAL = 3e4;
423
+ // 30s cap
424
+ _FloTraceWebSocketClient.BATCH_FLUSH_MS = 100;
425
+ // Flush batched messages every 100ms
426
+ _FloTraceWebSocketClient.MAX_QUEUE_SIZE = 500;
427
+ var FloTraceWebSocketClient = _FloTraceWebSocketClient;
428
+ var clientInstance = null;
429
+ function getWebSocketClient(config) {
430
+ if (!clientInstance) {
431
+ clientInstance = new FloTraceWebSocketClient(config);
432
+ }
433
+ return clientInstance;
434
+ }
435
+ function disposeWebSocketClient() {
436
+ if (clientInstance) {
437
+ clientInstance.disconnect();
438
+ clientInstance = null;
439
+ }
440
+ }
441
+
442
+ // src/fiberConstants.ts
443
+ var HOOK_HAS_EFFECT = 1;
444
+ var HOOK_INSERTION = 2;
445
+ var HOOK_LAYOUT = 4;
446
+ var HOOK_PASSIVE = 8;
447
+ function collectCircularList(lastEffect) {
448
+ const list = [];
449
+ let effect = lastEffect.next;
450
+ if (!effect) return list;
451
+ do {
452
+ list.push(effect);
453
+ effect = effect.next;
454
+ } while (effect && effect !== lastEffect.next);
455
+ return list;
456
+ }
457
+
458
+ // src/hookInspector.ts
459
+ function inspectHooks(fiber) {
460
+ const hooks = [];
461
+ let hookState = fiber.memoizedState;
462
+ const effects = fiber.updateQueue?.lastEffect ? collectCircularList(fiber.updateQueue.lastEffect) : [];
463
+ let effectIndex = 0;
464
+ const debugTypes = fiber._debugHookTypes ?? null;
465
+ let index = 0;
466
+ while (hookState) {
467
+ try {
468
+ const debugLabel = debugTypes?.[index] ?? void 0;
469
+ const hookInfo = classifyHook(hookState, index, effects, effectIndex, debugLabel);
470
+ hooks.push(hookInfo);
471
+ if (hookInfo.type === "useEffect" || hookInfo.type === "useLayoutEffect" || hookInfo.type === "useInsertionEffect") {
472
+ effectIndex++;
473
+ }
474
+ } catch (error) {
475
+ hooks.push({ index, type: "unknown", value: { __type: "truncated", originalType: "error" } });
476
+ }
477
+ hookState = hookState.next;
478
+ index++;
479
+ }
480
+ return hooks;
481
+ }
482
+ function classifyHook(state, index, effects, effectIdx, debugLabel) {
483
+ const ms = state.memoizedState;
484
+ if (debugLabel) {
485
+ return classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel);
486
+ }
487
+ if (state.queue !== null) {
488
+ const queue = state.queue;
489
+ const isReducer = queue.lastRenderedReducer && typeof queue.lastRenderedReducer === "function" && queue.lastRenderedReducer.name !== "" && queue.lastRenderedReducer.name !== "basicStateReducer";
490
+ return {
491
+ index,
492
+ type: isReducer ? "useReducer" : "useState",
493
+ value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet())
494
+ };
495
+ }
496
+ if (ms !== null && typeof ms === "object" && !Array.isArray(ms) && "current" in ms) {
497
+ const keys = Object.keys(ms);
498
+ if (keys.length === 1 && keys[0] === "current") {
499
+ return {
500
+ index,
501
+ type: "useRef",
502
+ value: serializeValue(ms.current, 0, /* @__PURE__ */ new WeakSet())
503
+ };
504
+ }
505
+ }
506
+ if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
507
+ const isCallback = typeof ms[0] === "function";
508
+ return {
509
+ index,
510
+ type: isCallback ? "useCallback" : "useMemo",
511
+ value: serializeValue(ms[0], 0, /* @__PURE__ */ new WeakSet()),
512
+ deps: ms[1].map((d) => serializeValue(d, 0, /* @__PURE__ */ new WeakSet()))
513
+ };
514
+ }
515
+ if (effectIdx < effects.length) {
516
+ const effect = effects[effectIdx];
517
+ if (typeof ms === "number" || isEffectShape(ms)) {
518
+ const type = (effect.tag & HOOK_PASSIVE) !== 0 ? "useEffect" : (effect.tag & HOOK_LAYOUT) !== 0 ? "useLayoutEffect" : (effect.tag & HOOK_INSERTION) !== 0 ? "useInsertionEffect" : "useEffect";
519
+ return {
520
+ index,
521
+ type,
522
+ value: { __type: "function", name: "effect" },
523
+ deps: effect.deps ? effect.deps.map((d) => serializeValue(d, 0, /* @__PURE__ */ new WeakSet())) : void 0
524
+ };
525
+ }
526
+ }
527
+ if (Array.isArray(ms) && ms.length === 2 && typeof ms[0] === "boolean" && typeof ms[1] === "function") {
528
+ return {
529
+ index,
530
+ type: "useTransition",
531
+ value: serializeValue(ms[0], 0, /* @__PURE__ */ new WeakSet())
532
+ };
533
+ }
534
+ if (typeof ms === "string" && ms.startsWith(":")) {
535
+ return {
536
+ index,
537
+ type: "useId",
538
+ value: ms
539
+ };
540
+ }
541
+ return { index, type: "unknown", value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()) };
542
+ }
543
+ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
544
+ const ms = state.memoizedState;
545
+ const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
546
+ const labelMap = {
547
+ "usestate": "useState",
548
+ "usereducer": "useReducer",
549
+ "useref": "useRef",
550
+ "usememo": "useMemo",
551
+ "usecallback": "useCallback",
552
+ "useeffect": "useEffect",
553
+ "uselayouteffect": "useLayoutEffect",
554
+ "useinsertioneffect": "useInsertionEffect",
555
+ "usecontext": "useContext",
556
+ "useimperativehandle": "useImperativeHandle",
557
+ "usedebugvalue": "useDebugValue",
558
+ "usetransition": "useTransition",
559
+ "usedeferredvalue": "useDeferredValue",
560
+ "useid": "useId",
561
+ "usesyncexternalstore": "useSyncExternalStore",
562
+ "useoptimistic": "useOptimistic",
563
+ "useformstatus": "useFormStatus"
564
+ };
565
+ const hookType = labelMap[normalizedLabel] ?? "unknown";
566
+ const base = { index, type: hookType, value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()), debugLabel };
567
+ if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
568
+ if (effectIdx < effects.length) {
569
+ const effect = effects[effectIdx];
570
+ base.value = { __type: "function", name: "effect" };
571
+ base.deps = effect.deps ? effect.deps.map((d) => serializeValue(d, 0, /* @__PURE__ */ new WeakSet())) : void 0;
572
+ }
573
+ }
574
+ if ((hookType === "useMemo" || hookType === "useCallback") && Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
575
+ base.value = serializeValue(ms[0], 0, /* @__PURE__ */ new WeakSet());
576
+ base.deps = ms[1].map((d) => serializeValue(d, 0, /* @__PURE__ */ new WeakSet()));
577
+ }
578
+ if (hookType === "useRef" && ms !== null && typeof ms === "object" && "current" in ms) {
579
+ base.value = serializeValue(ms.current, 0, /* @__PURE__ */ new WeakSet());
580
+ }
581
+ return base;
582
+ }
583
+ function isEffectShape(ms) {
584
+ if (ms === null || ms === void 0) return false;
585
+ if (typeof ms === "object" && ms !== null) {
586
+ const obj = ms;
587
+ return "tag" in obj && "create" in obj && "deps" in obj;
588
+ }
589
+ return false;
590
+ }
591
+
592
+ // src/effectInspector.ts
593
+ function inspectEffects(fiber) {
594
+ const results = [];
595
+ const lastEffect = fiber.updateQueue?.lastEffect;
596
+ if (!lastEffect) return results;
597
+ const currEffects = collectCircularList(lastEffect);
598
+ const prevEffects = fiber.alternate?.updateQueue?.lastEffect ? collectCircularList(fiber.alternate.updateQueue.lastEffect) : [];
599
+ const hookIndexMap = buildEffectToHookIndexMap(fiber, currEffects);
600
+ for (let i = 0; i < currEffects.length; i++) {
601
+ try {
602
+ const curr = currEffects[i];
603
+ const prev = prevEffects[i] ?? null;
604
+ const type = (curr.tag & HOOK_PASSIVE) !== 0 ? "useEffect" : (curr.tag & HOOK_LAYOUT) !== 0 ? "useLayoutEffect" : (curr.tag & HOOK_INSERTION) !== 0 ? "useInsertionEffect" : "useEffect";
605
+ const willRun = (curr.tag & HOOK_HAS_EFFECT) !== 0;
606
+ const changedDepIndices = diffDeps(prev?.deps ?? null, curr.deps);
607
+ const hasCleanup = typeof curr.destroy === "function";
608
+ results.push({
609
+ index: i,
610
+ hookIndex: hookIndexMap.get(i) ?? -1,
611
+ type,
612
+ deps: serializeDeps(curr.deps),
613
+ prevDeps: prev ? serializeDeps(prev.deps) : null,
614
+ changedDepIndices,
615
+ willRun,
616
+ hasCleanup
617
+ });
618
+ } catch (error) {
619
+ results.push({
620
+ index: i,
621
+ hookIndex: -1,
622
+ type: "useEffect",
623
+ deps: null,
624
+ prevDeps: null,
625
+ changedDepIndices: [],
626
+ willRun: false,
627
+ hasCleanup: false
628
+ });
629
+ }
630
+ }
631
+ return results;
632
+ }
633
+ function buildEffectToHookIndexMap(fiber, effects) {
634
+ const map = /* @__PURE__ */ new Map();
635
+ let hookState = fiber.memoizedState;
636
+ let hookIndex = 0;
637
+ let effectIndex = 0;
638
+ while (hookState && effectIndex < effects.length) {
639
+ const ms = hookState.memoizedState;
640
+ if (isLikelyEffectHook(ms, hookState)) {
641
+ map.set(effectIndex, hookIndex);
642
+ effectIndex++;
643
+ }
644
+ hookState = hookState.next;
645
+ hookIndex++;
646
+ }
647
+ return map;
648
+ }
649
+ function isLikelyEffectHook(ms, state) {
650
+ if (state.queue !== null) return false;
651
+ if (ms !== null && typeof ms === "object") {
652
+ const obj = ms;
653
+ if ("tag" in obj && "create" in obj && "deps" in obj) return true;
654
+ }
655
+ return false;
656
+ }
657
+ function diffDeps(prev, curr) {
658
+ if (!prev || !curr) return [];
659
+ const changed = [];
660
+ const len = Math.max(prev.length, curr.length);
661
+ for (let i = 0; i < len; i++) {
662
+ if (!Object.is(prev[i], curr[i])) {
663
+ changed.push(i);
664
+ }
665
+ }
666
+ return changed;
667
+ }
668
+ function serializeDeps(deps) {
669
+ if (deps === null) return null;
670
+ return deps.map((d) => serializeValue(d, 0, /* @__PURE__ */ new WeakSet()));
671
+ }
672
+
673
+ // src/timelineTracker.ts
674
+ var MAX_EVENTS_PER_COMPONENT = 100;
675
+ var FLUSH_INTERVAL_MS = 500;
676
+ var MAX_PENDING_EVENTS = 200;
677
+ var timelines = /* @__PURE__ */ new Map();
678
+ var pendingEvents = [];
679
+ var client = null;
680
+ var flushTimer = null;
681
+ var isInstalled = false;
682
+ function installTimelineTracker(wsClient) {
683
+ if (isInstalled) return;
684
+ client = wsClient;
685
+ isInstalled = true;
686
+ flushTimer = setInterval(flushPendingEvents, FLUSH_INTERVAL_MS);
687
+ }
688
+ function uninstallTimelineTracker() {
689
+ if (!isInstalled) return;
690
+ if (flushTimer) {
691
+ clearInterval(flushTimer);
692
+ flushTimer = null;
693
+ }
694
+ flushPendingEvents();
695
+ timelines.clear();
696
+ pendingEvents = [];
697
+ client = null;
698
+ isInstalled = false;
699
+ }
700
+ function recordTimelineEvent(nodeId, componentName, eventType, detail, duration) {
701
+ if (!isInstalled) return;
702
+ const event = {
703
+ type: eventType,
704
+ timestamp: Date.now(),
705
+ duration,
706
+ detail: detail !== void 0 ? serializeValue(detail, 0, /* @__PURE__ */ new WeakSet()) : void 0
707
+ };
708
+ let events = timelines.get(nodeId);
709
+ if (!events) {
710
+ events = [];
711
+ timelines.set(nodeId, events);
712
+ }
713
+ events.push(event);
714
+ if (events.length > MAX_EVENTS_PER_COMPONENT) {
715
+ events.shift();
716
+ }
717
+ if (pendingEvents.length < MAX_PENDING_EVENTS) {
718
+ pendingEvents.push({ nodeId, componentName, event });
719
+ }
720
+ }
721
+ function getTimeline(nodeId) {
722
+ return timelines.get(nodeId) ?? [];
723
+ }
724
+ function flushPendingEvents() {
725
+ if (!client?.connected || pendingEvents.length === 0) return;
726
+ for (const { nodeId, componentName, event } of pendingEvents) {
727
+ client.send({
728
+ type: "runtime:timelineEvent",
729
+ nodeId,
730
+ componentName,
731
+ event
732
+ });
733
+ }
734
+ pendingEvents = [];
735
+ }
736
+
737
+ // src/fiberUtils.ts
738
+ function getFiberDisplayName(type) {
739
+ if (!type) return "Unknown";
740
+ if (typeof type === "function") {
741
+ return type.displayName || type.name || "Anonymous";
742
+ }
743
+ if (typeof type === "object") {
744
+ const t = type;
745
+ return t.type?.displayName || t.type?.name || t.render?.name || t.displayName || t.name || "Unknown";
746
+ }
747
+ return "Unknown";
748
+ }
749
+
750
+ // src/dispatchWrapper.ts
751
+ var MAX_TRIGGERS = 200;
752
+ var triggerBuffer = [];
753
+ var triggerSeq = 0;
754
+ var wrappedDispatchers = /* @__PURE__ */ new WeakSet();
755
+ var currentBatchId = null;
756
+ function nextBatchId() {
757
+ if (!currentBatchId) {
758
+ currentBatchId = String(Date.now()) + "-" + (Math.random() * 65535 | 0).toString(16);
759
+ queueMicrotask(() => {
760
+ currentBatchId = null;
761
+ });
762
+ }
763
+ return currentBatchId;
764
+ }
765
+ function nextTriggerId() {
766
+ return "tr-" + (++triggerSeq).toString(36);
767
+ }
768
+ var STACK_DEPTH_LIMIT = 15;
769
+ var NOISE_PATTERNS = [
770
+ "node_modules",
771
+ "react-dom",
772
+ "react-reconciler",
773
+ "@flotrace/runtime",
774
+ "flotrace/runtime",
775
+ "/runtime/src/",
776
+ "webpack-internal",
777
+ "webpack/bootstrap",
778
+ "<anonymous>"
779
+ ];
780
+ function isUserCodeFrame(fileName) {
781
+ if (!fileName) return false;
782
+ for (const pattern of NOISE_PATTERNS) {
783
+ if (fileName.includes(pattern)) return false;
784
+ }
785
+ return true;
786
+ }
787
+ function captureStack() {
788
+ const frames = [];
789
+ try {
790
+ const originalPrepare = Error.prepareStackTrace;
791
+ Error.prepareStackTrace = (_err, callSites) => {
792
+ for (const site of callSites) {
793
+ if (frames.length >= STACK_DEPTH_LIMIT) break;
794
+ const fileName = site.getFileName();
795
+ frames.push({
796
+ functionName: site.getFunctionName() ?? site.getMethodName(),
797
+ fileName,
798
+ lineNumber: site.getLineNumber(),
799
+ columnNumber: site.getColumnNumber(),
800
+ isUserCode: isUserCodeFrame(fileName)
801
+ });
802
+ }
803
+ return "";
804
+ };
805
+ const err = new Error();
806
+ void err.stack;
807
+ Error.prepareStackTrace = originalPrepare;
808
+ } catch {
809
+ try {
810
+ const raw = new Error().stack ?? "";
811
+ const lines = raw.split("\n").slice(1);
812
+ for (const line of lines) {
813
+ if (frames.length >= STACK_DEPTH_LIMIT) break;
814
+ const match = line.match(/^\s+at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/);
815
+ if (match) {
816
+ const fileName = match[2] ?? null;
817
+ frames.push({
818
+ functionName: match[1] ?? null,
819
+ fileName,
820
+ lineNumber: match[3] ? parseInt(match[3], 10) : null,
821
+ columnNumber: match[4] ? parseInt(match[4], 10) : null,
822
+ isUserCode: isUserCodeFrame(fileName)
823
+ });
824
+ }
825
+ }
826
+ } catch {
827
+ }
828
+ }
829
+ return frames;
830
+ }
831
+ var FIBER_TAG_FUNCTION = 0;
832
+ var FIBER_TAG_CLASS = 1;
833
+ var FIBER_TAG_FORWARD = 11;
834
+ var FIBER_TAG_MEMO = 14;
835
+ var FIBER_TAG_SIMPLEMEMO = 15;
836
+ function getComponentName(fiber) {
837
+ return getFiberDisplayName(fiber.type);
838
+ }
839
+ function wrapFunctionComponentDispatchers(fiber) {
840
+ let hookNode = fiber.memoizedState;
841
+ let hookIndex = 0;
842
+ while (hookNode && hookIndex < 100) {
843
+ try {
844
+ const queue = hookNode.queue;
845
+ if (queue && typeof queue.dispatch === "function") {
846
+ const original = queue.dispatch;
847
+ if (!wrappedDispatchers.has(original)) {
848
+ const componentName = getComponentName(fiber);
849
+ const fiberId = getFiberId(fiber);
850
+ const capturedHookIndex = hookIndex;
851
+ const hookType = typeof queue.lastRenderedReducer === "function" && queue.lastRenderedReducer?.toString().includes("action") ? "reducer" : "state";
852
+ const wrapped = function dispatchWithCapture(action) {
853
+ try {
854
+ const stack = captureStack();
855
+ const record = {
856
+ triggerId: nextTriggerId(),
857
+ fiberId,
858
+ componentName,
859
+ hookIndex: capturedHookIndex,
860
+ hookType,
861
+ stack,
862
+ timestamp: performance.now(),
863
+ action: serializeValue(action, 2),
864
+ batchId: nextBatchId()
865
+ };
866
+ addTrigger(record);
867
+ } catch {
868
+ }
869
+ return original(action);
870
+ };
871
+ wrappedDispatchers.add(wrapped);
872
+ queue.dispatch = wrapped;
873
+ }
874
+ }
875
+ } catch {
876
+ }
877
+ hookNode = hookNode.next;
878
+ hookIndex++;
879
+ }
880
+ }
881
+ function wrapClassComponentInstance(fiber) {
882
+ const instance = fiber.stateNode;
883
+ if (!instance || instance.__ftWrapped) return;
884
+ const componentName = getComponentName(fiber);
885
+ const fiberId = getFiberId(fiber);
886
+ if (typeof instance.setState === "function") {
887
+ const origSetState = instance.setState;
888
+ instance.setState = function wrappedSetState(updater, callback) {
889
+ try {
890
+ const stack = captureStack();
891
+ addTrigger({
892
+ triggerId: nextTriggerId(),
893
+ fiberId,
894
+ componentName,
895
+ hookIndex: 0,
896
+ hookType: "setState",
897
+ stack,
898
+ timestamp: performance.now(),
899
+ action: serializeValue(updater, 2),
900
+ batchId: nextBatchId()
901
+ });
902
+ } catch {
903
+ }
904
+ return origSetState.call(this, updater, callback);
905
+ };
906
+ }
907
+ if (typeof instance.forceUpdate === "function") {
908
+ const origForceUpdate = instance.forceUpdate;
909
+ instance.forceUpdate = function wrappedForceUpdate(callback) {
910
+ try {
911
+ const stack = captureStack();
912
+ addTrigger({
913
+ triggerId: nextTriggerId(),
914
+ fiberId,
915
+ componentName,
916
+ hookIndex: 0,
917
+ hookType: "forceUpdate",
918
+ stack,
919
+ timestamp: performance.now(),
920
+ action: null,
921
+ batchId: nextBatchId()
922
+ });
923
+ } catch {
924
+ }
925
+ return origForceUpdate.call(this, callback);
926
+ };
927
+ }
928
+ instance.__ftWrapped = true;
929
+ }
930
+ var fiberIds = /* @__PURE__ */ new WeakMap();
931
+ var fiberIdSeq = 0;
932
+ function getFiberId(fiber) {
933
+ let id = fiberIds.get(fiber);
934
+ if (!id) {
935
+ id = getComponentName(fiber) + "-" + (++fiberIdSeq).toString(36);
936
+ fiberIds.set(fiber, id);
937
+ }
938
+ return id;
939
+ }
940
+ function addTrigger(record) {
941
+ if (triggerBuffer.length >= MAX_TRIGGERS) {
942
+ triggerBuffer.shift();
943
+ }
944
+ triggerBuffer.push(record);
945
+ }
946
+ function wrapFiberDispatchers(root) {
947
+ try {
948
+ walkAndWrap(root.current);
949
+ } catch {
950
+ }
951
+ }
952
+ function walkAndWrap(rootFiber) {
953
+ if (!rootFiber) return;
954
+ const stack = [rootFiber];
955
+ while (stack.length > 0) {
956
+ const fiber = stack.pop();
957
+ try {
958
+ const tag = fiber.tag;
959
+ if (tag === FIBER_TAG_FUNCTION || tag === FIBER_TAG_FORWARD || tag === FIBER_TAG_MEMO || tag === FIBER_TAG_SIMPLEMEMO) {
960
+ wrapFunctionComponentDispatchers(fiber);
961
+ } else if (tag === FIBER_TAG_CLASS) {
962
+ wrapClassComponentInstance(fiber);
963
+ }
964
+ } catch {
965
+ }
966
+ if (fiber.sibling) stack.push(fiber.sibling);
967
+ if (fiber.child) stack.push(fiber.child);
968
+ }
969
+ }
970
+ function peekTriggers() {
971
+ return triggerBuffer;
972
+ }
973
+ function clearTriggers() {
974
+ triggerBuffer.length = 0;
975
+ }
976
+
977
+ // src/laneDetector.ts
978
+ var SyncHydrationLane = 1;
979
+ var SyncLane = 2;
980
+ var InputContinuousHydrationLane = 4;
981
+ var InputContinuousLane = 8;
982
+ var DefaultHydrationLane = 16;
983
+ var DefaultLane = 32;
984
+ var TransitionLanes = 4194240;
985
+ var RetryLanes = 62914560;
986
+ var SelectiveHydrationLane = 67108864;
987
+ var IdleHydrationLane = 134217728;
988
+ var IdleLane = 268435456;
989
+ var OffscreenLane = 536870912;
990
+ function classifyLanes(lanes) {
991
+ try {
992
+ if (lanes & SyncHydrationLane || lanes & SyncLane) {
993
+ return { priority: "sync", lanes, isTransition: false, isBlocking: true };
994
+ }
995
+ if (lanes & InputContinuousHydrationLane || lanes & InputContinuousLane) {
996
+ return { priority: "discrete", lanes, isTransition: false, isBlocking: true };
997
+ }
998
+ if (lanes & DefaultHydrationLane || lanes & DefaultLane) {
999
+ return { priority: "default", lanes, isTransition: false, isBlocking: false };
1000
+ }
1001
+ if (lanes & TransitionLanes) {
1002
+ return { priority: "transition", lanes, isTransition: true, isBlocking: false };
1003
+ }
1004
+ if (lanes & RetryLanes || lanes & SelectiveHydrationLane) {
1005
+ return { priority: "deferred", lanes, isTransition: false, isBlocking: false };
1006
+ }
1007
+ if (lanes & IdleHydrationLane || lanes & IdleLane) {
1008
+ return { priority: "idle", lanes, isTransition: false, isBlocking: false };
1009
+ }
1010
+ if (lanes & OffscreenLane) {
1011
+ return { priority: "offscreen", lanes, isTransition: false, isBlocking: false };
1012
+ }
1013
+ } catch {
1014
+ }
1015
+ return { priority: "default", lanes, isTransition: false, isBlocking: false };
1016
+ }
1017
+ function getFinishedLanes(root) {
1018
+ try {
1019
+ return root.finishedLanes ?? root.pendingLanes ?? 0;
1020
+ } catch {
1021
+ return 0;
1022
+ }
1023
+ }
1024
+
1025
+ // src/cascadeAnalyzer.ts
1026
+ var PerformedWork = 1;
1027
+ var ForceUpdateFlag = 256;
1028
+ var FunctionComponent = 0;
1029
+ var ClassComponent = 1;
1030
+ var ForwardRef = 11;
1031
+ var MemoComponent = 14;
1032
+ var SimpleMemoComponent = 15;
1033
+ var USER_TAGS = /* @__PURE__ */ new Set([FunctionComponent, ClassComponent, ForwardRef, MemoComponent, SimpleMemoComponent]);
1034
+ function isMemoizedFiber(fiber) {
1035
+ return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
1036
+ }
1037
+ function propsChanged(prev, next) {
1038
+ if (prev === next) return false;
1039
+ if (!prev || !next) return true;
1040
+ const prevKeys = Object.keys(prev);
1041
+ const nextKeys = Object.keys(next);
1042
+ if (prevKeys.length !== nextKeys.length) return true;
1043
+ for (const key of nextKeys) {
1044
+ if (key === "children") continue;
1045
+ if (prev[key] !== next[key]) return true;
1046
+ }
1047
+ return false;
1048
+ }
1049
+ function getChangedPropKeys(prev, next) {
1050
+ if (!prev || !next) return [];
1051
+ const changed = [];
1052
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
1053
+ for (const key of allKeys) {
1054
+ if (key === "children") continue;
1055
+ if (prev[key] !== next[key]) changed.push(key);
1056
+ }
1057
+ return changed;
1058
+ }
1059
+ function hadOwnUpdate(fiber) {
1060
+ try {
1061
+ const uq = fiber.updateQueue;
1062
+ if (!uq) return false;
1063
+ if (uq.shared && uq.shared.pending != null) return true;
1064
+ if (fiber.lanes !== 0) return true;
1065
+ return false;
1066
+ } catch {
1067
+ return false;
1068
+ }
1069
+ }
1070
+ function hadContextUpdate(fiber) {
1071
+ try {
1072
+ return !!fiber.dependencies?.firstContext;
1073
+ } catch {
1074
+ return false;
1075
+ }
1076
+ }
1077
+ function classifyFiber(fiber, didRender, parentRerendered) {
1078
+ if (!didRender) {
1079
+ if (fiber.alternate && isMemoizedFiber(fiber)) return "bailed-out";
1080
+ return null;
1081
+ }
1082
+ if (fiber.flags & ForceUpdateFlag) return "force-update";
1083
+ if (hadContextUpdate(fiber)) return "context-update";
1084
+ if (hadOwnUpdate(fiber)) return "state-update";
1085
+ if (parentRerendered) {
1086
+ const alt = fiber.alternate;
1087
+ if (alt && propsChanged(alt.memoizedProps, fiber.memoizedProps)) {
1088
+ return "props-changed";
1089
+ }
1090
+ return "parent-cascade";
1091
+ }
1092
+ return "state-update";
1093
+ }
1094
+ function computeSubtreeDuration(node) {
1095
+ let total = node.renderDuration;
1096
+ for (const child of node.children) {
1097
+ total += computeSubtreeDuration(child);
1098
+ }
1099
+ node.subtreeDuration = total;
1100
+ return total;
1101
+ }
1102
+ var commitIdSeq = 0;
1103
+ function nextCommitId() {
1104
+ return "c-" + (++commitIdSeq).toString(36) + "-" + (Date.now() % 1e5).toString(36);
1105
+ }
1106
+ function buildCascadeTree(rootFiber, triggers) {
1107
+ const rootCauses = [];
1108
+ let totalComponents = 0;
1109
+ let avoidableCount = 0;
1110
+ let avoidableDuration = 0;
1111
+ const triggerByName = /* @__PURE__ */ new Map();
1112
+ for (const t of triggers) {
1113
+ if (!triggerByName.has(t.componentName)) {
1114
+ triggerByName.set(t.componentName, t);
1115
+ }
1116
+ }
1117
+ const stack = [{
1118
+ fiber: rootFiber,
1119
+ depth: 0,
1120
+ parentRerendered: false,
1121
+ parentNode: null,
1122
+ isRoot: true
1123
+ }];
1124
+ while (stack.length > 0) {
1125
+ const entry = stack.pop();
1126
+ const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
1127
+ if (!fiber) continue;
1128
+ if (depth > 150) continue;
1129
+ const didRender = !!(fiber.flags & PerformedWork);
1130
+ const isNewMount = !fiber.alternate;
1131
+ if (isNewMount && !didRender) {
1132
+ let child2 = fiber.child;
1133
+ while (child2) {
1134
+ stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1135
+ child2 = child2.sibling;
1136
+ }
1137
+ continue;
1138
+ }
1139
+ if (!USER_TAGS.has(fiber.tag)) {
1140
+ let child2 = fiber.child;
1141
+ while (child2) {
1142
+ stack.push({ fiber: child2, depth: depth + 1, parentRerendered: didRender || parentRerendered, parentNode, isRoot: false });
1143
+ child2 = child2.sibling;
1144
+ }
1145
+ continue;
1146
+ }
1147
+ const reason = classifyFiber(fiber, didRender, parentRerendered);
1148
+ if (reason === null) {
1149
+ let child2 = fiber.child;
1150
+ while (child2) {
1151
+ stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1152
+ child2 = child2.sibling;
1153
+ }
1154
+ continue;
1155
+ }
1156
+ const componentName = getFiberDisplayName(fiber.type);
1157
+ const renderDuration = fiber.actualDuration ?? 0;
1158
+ let changedProps;
1159
+ if (reason === "props-changed" && fiber.alternate) {
1160
+ changedProps = getChangedPropKeys(fiber.alternate.memoizedProps, fiber.memoizedProps);
1161
+ }
1162
+ let triggerId;
1163
+ if (reason === "state-update" || reason === "context-update" || reason === "force-update") {
1164
+ triggerId = triggerByName.get(componentName)?.triggerId;
1165
+ }
1166
+ const node = {
1167
+ nodeId: componentName + "-" + depth + "-" + totalComponents,
1168
+ componentName,
1169
+ reason,
1170
+ renderDuration,
1171
+ subtreeDuration: renderDuration,
1172
+ // will be updated from children
1173
+ changedProps,
1174
+ triggerId,
1175
+ children: [],
1176
+ depth,
1177
+ isMemoized: isMemoizedFiber(fiber)
1178
+ };
1179
+ totalComponents++;
1180
+ if (reason === "parent-cascade") {
1181
+ avoidableCount++;
1182
+ avoidableDuration += renderDuration;
1183
+ }
1184
+ if (parentNode) {
1185
+ parentNode.children.push(node);
1186
+ } else if (reason === "state-update" || reason === "context-update" || reason === "force-update" || isRoot) {
1187
+ rootCauses.push(node);
1188
+ } else if (parentRerendered) {
1189
+ rootCauses.push(node);
1190
+ }
1191
+ let child = fiber.child;
1192
+ while (child) {
1193
+ stack.push({
1194
+ fiber: child,
1195
+ depth: depth + 1,
1196
+ parentRerendered: didRender,
1197
+ parentNode: reason !== "bailed-out" ? node : parentNode,
1198
+ isRoot: false
1199
+ });
1200
+ child = child.sibling;
1201
+ }
1202
+ }
1203
+ for (const root of rootCauses) computeSubtreeDuration(root);
1204
+ return { rootCauses, totalComponents, avoidableCount, avoidableDuration };
1205
+ }
1206
+ function analyzeCascade(root, triggers) {
1207
+ try {
1208
+ const finishedLanes = getFinishedLanes(root);
1209
+ const lane = classifyLanes(finishedLanes);
1210
+ const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(root.current, triggers);
1211
+ if (totalComponents === 0) return null;
1212
+ const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
1213
+ const triggerIds = triggers.map((t) => t.triggerId);
1214
+ return {
1215
+ commitId: nextCommitId(),
1216
+ timestamp: performance.now(),
1217
+ totalDuration,
1218
+ totalComponents,
1219
+ avoidableCount,
1220
+ avoidableDuration,
1221
+ rootCauses,
1222
+ lane,
1223
+ triggerIds
1224
+ };
1225
+ } catch {
1226
+ return null;
1227
+ }
1228
+ }
1229
+
1230
+ // src/propDrillingAnalyzer.ts
1231
+ var ANALYZE_INTERVAL_MS = 2e3;
1232
+ var DRILLING_THRESHOLD = 3;
1233
+ var EXCLUDED_PROP_NAMES = /* @__PURE__ */ new Set([
1234
+ // React internals
1235
+ "children",
1236
+ "key",
1237
+ "ref",
1238
+ // Common HTML attributes
1239
+ "className",
1240
+ "style",
1241
+ "id",
1242
+ "name",
1243
+ "type",
1244
+ "value",
1245
+ "placeholder",
1246
+ "disabled",
1247
+ "readOnly",
1248
+ "required",
1249
+ "autoFocus",
1250
+ "tabIndex",
1251
+ "role",
1252
+ "aria-label",
1253
+ "aria-describedby",
1254
+ "aria-hidden",
1255
+ "title",
1256
+ "lang",
1257
+ "dir",
1258
+ "hidden",
1259
+ // Common layout props
1260
+ "width",
1261
+ "height",
1262
+ "size",
1263
+ "variant",
1264
+ "color",
1265
+ "theme",
1266
+ // Test IDs
1267
+ "data-testid",
1268
+ "testID"
1269
+ ]);
1270
+ function isExcluded(propName) {
1271
+ return EXCLUDED_PROP_NAMES.has(propName) || propName.startsWith("on");
1272
+ }
1273
+ var analyzeTimer = null;
1274
+ var lastAnalysisTime = 0;
1275
+ function valueFingerprint(value, depth = 0) {
1276
+ if (depth > 3) return "__deep__";
1277
+ if (value === null || value === void 0) return "null";
1278
+ if (typeof value === "function") return `fn:${value.name || "anon"}`;
1279
+ if (typeof value !== "object") return `${typeof value}:${String(value)}`;
1280
+ if (Array.isArray(value)) {
1281
+ const arr = value;
1282
+ return `arr:${arr.length}:${arr.slice(0, 5).map((v) => valueFingerprint(v, depth + 1)).join(",")}`;
1283
+ }
1284
+ const obj = value;
1285
+ const keys = Object.keys(obj).sort();
1286
+ return `obj:${keys.slice(0, 10).map((k) => `${k}=${valueFingerprint(obj[k], depth + 1)}`).join(",")}`;
1287
+ }
1288
+ function shouldFlagRename(value) {
1289
+ if (value === null || value === void 0) return false;
1290
+ if (typeof value !== "object") return false;
1291
+ if (Array.isArray(value) && value.length === 0) return false;
1292
+ if (!Array.isArray(value) && Object.keys(value).length === 0) return false;
1293
+ return true;
1294
+ }
1295
+ function computePropIntersectionRatio(nodeProps, childrenProps) {
1296
+ const nodeKeys = Object.keys(nodeProps).filter((k) => !isExcluded(k));
1297
+ if (nodeKeys.length === 0) return 0;
1298
+ let forwarded = 0;
1299
+ for (const key of nodeKeys) {
1300
+ const fp = valueFingerprint(nodeProps[key]);
1301
+ const isForwarded = childrenProps.some(
1302
+ (cp) => Object.values(cp).some((v) => valueFingerprint(v) === fp)
1303
+ );
1304
+ if (isForwarded) forwarded++;
1305
+ }
1306
+ return forwarded / nodeKeys.length;
1307
+ }
1308
+ function classifyNode(nodeId, drilledPropFp, parentNodeId, childNodeIds, getProps, hookCounts, contextFlags) {
1309
+ if (!parentNodeId) return "source";
1310
+ const parentProps = getProps(parentNodeId);
1311
+ const parentHasProp = Object.values(parentProps).some(
1312
+ (v) => valueFingerprint(v) === drilledPropFp
1313
+ );
1314
+ if (!parentHasProp) return "source";
1315
+ const forwardsToChild = childNodeIds.some((cid) => {
1316
+ const childProps = getProps(cid);
1317
+ return Object.values(childProps).some((v) => valueFingerprint(v) === drilledPropFp);
1318
+ });
1319
+ if (!forwardsToChild) return "consumer";
1320
+ const hookCount = hookCounts.get(nodeId) ?? 0;
1321
+ const hasContext = contextFlags.get(nodeId) ?? false;
1322
+ if (hookCount === 0) return "passthrough";
1323
+ if (hasContext) return "consumer";
1324
+ const nodeProps = getProps(nodeId);
1325
+ const childrenProps = childNodeIds.map(getProps);
1326
+ const intersectionRatio = computePropIntersectionRatio(nodeProps, childrenProps);
1327
+ if (intersectionRatio > 0.7 && hookCount <= 1) return "passthrough";
1328
+ return "consumer";
1329
+ }
1330
+ function calculateSeverity(depth, passthroughCount, consumerCount) {
1331
+ if (depth >= 5) return "critical";
1332
+ if (passthroughCount >= 3) return "critical";
1333
+ if (consumerCount >= 3 && depth >= 4) return "critical";
1334
+ if (depth >= 4) return "warning";
1335
+ if (passthroughCount >= 2) return "warning";
1336
+ if (consumerCount >= 2) return "warning";
1337
+ return "info";
1338
+ }
1339
+ function makeChainId(sourceNodeId, fp, consumerNodeId) {
1340
+ return `${sourceNodeId}::${fp.slice(0, 20)}::${consumerNodeId}`;
1341
+ }
1342
+ function flattenTree(node, parentId, parentMap, childrenMap, nodeMap) {
1343
+ if (node.isFramework) {
1344
+ for (const child of node.children) {
1345
+ flattenTree(child, parentId, parentMap, childrenMap, nodeMap);
1346
+ }
1347
+ return;
1348
+ }
1349
+ nodeMap.set(node.id, node);
1350
+ if (parentId) {
1351
+ parentMap.set(node.id, parentId);
1352
+ const siblings = childrenMap.get(parentId) ?? [];
1353
+ siblings.push(node.id);
1354
+ childrenMap.set(parentId, siblings);
1355
+ }
1356
+ if (!childrenMap.has(node.id)) {
1357
+ childrenMap.set(node.id, []);
1358
+ }
1359
+ for (const child of node.children) {
1360
+ flattenTree(child, node.id, parentMap, childrenMap, nodeMap);
1361
+ }
1362
+ }
1363
+ function runAnalysis(tree, fiberRefMap2) {
1364
+ const parentMap = /* @__PURE__ */ new Map();
1365
+ const childrenMap = /* @__PURE__ */ new Map();
1366
+ const nodeMap = /* @__PURE__ */ new Map();
1367
+ flattenTree(tree, void 0, parentMap, childrenMap, nodeMap);
1368
+ const allNodeIds = Array.from(nodeMap.keys());
1369
+ function getProps(nodeId) {
1370
+ try {
1371
+ return fiberRefMap2.get(nodeId)?.memoizedProps ?? {};
1372
+ } catch {
1373
+ return {};
1374
+ }
1375
+ }
1376
+ const hookCounts = /* @__PURE__ */ new Map();
1377
+ const contextFlags = /* @__PURE__ */ new Map();
1378
+ for (const nodeId of allNodeIds) {
1379
+ const node = nodeMap.get(nodeId);
1380
+ hookCounts.set(nodeId, node.hookCount ?? 0);
1381
+ contextFlags.set(nodeId, node.hasContextHook ?? false);
1382
+ }
1383
+ const edges = [];
1384
+ for (const nodeId of allNodeIds) {
1385
+ const parentId = parentMap.get(nodeId);
1386
+ if (!parentId) continue;
1387
+ const parentProps = getProps(parentId);
1388
+ const childProps = getProps(nodeId);
1389
+ const childKeys = Object.keys(childProps).filter((k) => !isExcluded(k));
1390
+ const parentKeys = Object.keys(parentProps).filter((k) => !isExcluded(k));
1391
+ for (const childKey of childKeys) {
1392
+ const childVal = childProps[childKey];
1393
+ if (typeof childVal === "function") continue;
1394
+ const childFp = valueFingerprint(childVal);
1395
+ if (childFp === "null") continue;
1396
+ for (const parentKey of parentKeys) {
1397
+ const parentVal = parentProps[parentKey];
1398
+ if (typeof parentVal === "function") continue;
1399
+ const parentFp = valueFingerprint(parentVal);
1400
+ if (parentFp === childFp) {
1401
+ const isRename = parentKey !== childKey;
1402
+ if (!isRename || shouldFlagRename(parentVal)) {
1403
+ edges.push({
1404
+ parentNodeId: parentId,
1405
+ childNodeId: nodeId,
1406
+ propKey: parentKey,
1407
+ childPropKey: childKey,
1408
+ fp: childFp
1409
+ });
1410
+ break;
1411
+ }
1412
+ }
1413
+ }
1414
+ }
1415
+ }
1416
+ const edgesByFp = /* @__PURE__ */ new Map();
1417
+ for (const edge of edges) {
1418
+ const group = edgesByFp.get(edge.fp) ?? [];
1419
+ group.push(edge);
1420
+ edgesByFp.set(edge.fp, group);
1421
+ }
1422
+ const chains = [];
1423
+ const passthroughNodeIdSet = /* @__PURE__ */ new Set();
1424
+ for (const [fp, fpEdges] of edgesByFp) {
1425
+ const outEdges = /* @__PURE__ */ new Map();
1426
+ const inNodes = /* @__PURE__ */ new Set();
1427
+ for (const edge of fpEdges) {
1428
+ const out = outEdges.get(edge.parentNodeId) ?? [];
1429
+ out.push(edge);
1430
+ outEdges.set(edge.parentNodeId, out);
1431
+ inNodes.add(edge.childNodeId);
1432
+ }
1433
+ const sourceNodeIds = /* @__PURE__ */ new Set();
1434
+ for (const edge of fpEdges) {
1435
+ if (!inNodes.has(edge.parentNodeId)) {
1436
+ sourceNodeIds.add(edge.parentNodeId);
1437
+ }
1438
+ }
1439
+ for (const sourceId of sourceNodeIds) {
1440
+ let dfs2 = function(currentId, currentPropKey, currentPath, visited) {
1441
+ if (visited.has(currentId)) return;
1442
+ visited.add(currentId);
1443
+ const outgoing = outEdges.get(currentId);
1444
+ if (!outgoing || outgoing.length === 0) {
1445
+ if (currentPath.length >= DRILLING_THRESHOLD) {
1446
+ allPaths.push([...currentPath]);
1447
+ }
1448
+ visited.delete(currentId);
1449
+ return;
1450
+ }
1451
+ for (const edge of outgoing) {
1452
+ const isRename = edge.propKey !== edge.childPropKey;
1453
+ dfs2(
1454
+ edge.childNodeId,
1455
+ edge.childPropKey,
1456
+ [...currentPath, { nodeId: edge.childNodeId, propKey: edge.childPropKey, isRename }],
1457
+ new Set(visited)
1458
+ );
1459
+ }
1460
+ visited.delete(currentId);
1461
+ };
1462
+ var dfs = dfs2;
1463
+ const firstEdge = outEdges.get(sourceId)?.[0];
1464
+ if (!firstEdge) continue;
1465
+ const sourcePropName = firstEdge.propKey;
1466
+ const allPaths = [];
1467
+ dfs2(
1468
+ sourceId,
1469
+ sourcePropName,
1470
+ [{ nodeId: sourceId, propKey: sourcePropName, isRename: false }],
1471
+ /* @__PURE__ */ new Set()
1472
+ );
1473
+ if (allPaths.length === 0) continue;
1474
+ for (const path of allPaths) {
1475
+ if (path.length < DRILLING_THRESHOLD) continue;
1476
+ const consumerNodeId = path[path.length - 1].nodeId;
1477
+ const consumerNode = nodeMap.get(consumerNodeId);
1478
+ if (!consumerNode) continue;
1479
+ const chainNodes = path.map((p, i) => {
1480
+ const parentIdForNode = i === 0 ? void 0 : path[i - 1].nodeId;
1481
+ const childNodeIds = i < path.length - 1 ? [path[i + 1].nodeId] : [];
1482
+ const role = classifyNode(
1483
+ p.nodeId,
1484
+ fp,
1485
+ parentIdForNode,
1486
+ childNodeIds,
1487
+ getProps,
1488
+ hookCounts,
1489
+ contextFlags
1490
+ );
1491
+ if (role === "passthrough") {
1492
+ passthroughNodeIdSet.add(p.nodeId);
1493
+ }
1494
+ const n = nodeMap.get(p.nodeId);
1495
+ return {
1496
+ nodeId: p.nodeId,
1497
+ componentName: n?.name ?? p.nodeId,
1498
+ propKey: p.propKey,
1499
+ role,
1500
+ hookCount: hookCounts.get(p.nodeId) ?? 0,
1501
+ hasContextHook: contextFlags.get(p.nodeId) ?? false
1502
+ };
1503
+ });
1504
+ const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
1505
+ const sourceNode = nodeMap.get(sourceId);
1506
+ const renames = path.flatMap(
1507
+ (p, idx) => p.isRename ? [{ atNodeId: p.nodeId, fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName, toKey: p.propKey }] : []
1508
+ );
1509
+ chains.push({
1510
+ chainId: makeChainId(sourceId, fp, consumerNodeId),
1511
+ propName: sourcePropName,
1512
+ sourceNodeId: sourceId,
1513
+ sourceComponentName: sourceNode?.name ?? sourceId,
1514
+ consumerNodeIds: [consumerNodeId],
1515
+ consumerComponentNames: [consumerNode.name],
1516
+ path: chainNodes,
1517
+ depth: path.length,
1518
+ passthroughCount,
1519
+ severity: calculateSeverity(path.length, passthroughCount, 1),
1520
+ renames
1521
+ });
1522
+ }
1523
+ }
1524
+ }
1525
+ const seen = /* @__PURE__ */ new Set();
1526
+ const dedupedChains = chains.filter((c) => {
1527
+ if (seen.has(c.chainId)) return false;
1528
+ seen.add(c.chainId);
1529
+ return true;
1530
+ });
1531
+ const severityOrder = { critical: 0, warning: 1, info: 2 };
1532
+ dedupedChains.sort((a, b) => {
1533
+ const s = severityOrder[a.severity] - severityOrder[b.severity];
1534
+ if (s !== 0) return s;
1535
+ return b.depth - a.depth;
1536
+ });
1537
+ return {
1538
+ chains: dedupedChains.slice(0, 50),
1539
+ // cap at 50 chains
1540
+ passthroughNodeIds: Array.from(passthroughNodeIdSet)
1541
+ };
1542
+ }
1543
+ function schedulePropDrillingAnalysis(tree, fiberRefMap2, client2) {
1544
+ if (analyzeTimer) clearTimeout(analyzeTimer);
1545
+ const now = Date.now();
1546
+ const elapsed = now - lastAnalysisTime;
1547
+ const delay = elapsed >= ANALYZE_INTERVAL_MS ? 0 : ANALYZE_INTERVAL_MS - elapsed;
1548
+ analyzeTimer = setTimeout(() => {
1549
+ analyzeTimer = null;
1550
+ if (!client2.connected) return;
1551
+ try {
1552
+ lastAnalysisTime = Date.now();
1553
+ const { chains, passthroughNodeIds } = runAnalysis(tree, fiberRefMap2);
1554
+ client2.sendImmediate({
1555
+ type: "runtime:propDrilling",
1556
+ payload: {
1557
+ chains,
1558
+ passthroughNodeIds,
1559
+ analysisTimestamp: lastAnalysisTime,
1560
+ treeSize: fiberRefMap2.size
1561
+ }
1562
+ });
1563
+ } catch (err) {
1564
+ if (typeof console !== "undefined") {
1565
+ console.warn("[FloTrace] Prop drilling analysis error:", err);
1566
+ }
1567
+ }
1568
+ }, delay);
1569
+ }
1570
+
1571
+ // src/compilerAnalyzer.ts
1572
+ var MEMO_CACHE_SENTINEL = /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel");
1573
+ var FUNCTION_COMPONENT = 0;
1574
+ var SIMPLE_MEMO = 15;
1575
+ function detectCompilerStatus(fiber) {
1576
+ if (fiber.tag === SIMPLE_MEMO) return "manual";
1577
+ if (fiber.tag !== FUNCTION_COMPONENT) return void 0;
1578
+ const firstHook = fiber.memoizedState;
1579
+ if (!firstHook) return "unoptimized";
1580
+ const cache = firstHook.memoizedState;
1581
+ if (!Array.isArray(cache) || cache.length === 0) return "unoptimized";
1582
+ const hasSentinel = cache.some((v) => v === MEMO_CACHE_SENTINEL);
1583
+ if (!hasSentinel) return "unoptimized";
1584
+ const allSentinel = cache.every((v) => v === MEMO_CACHE_SENTINEL);
1585
+ if (allSentinel && fiber.alternate != null) return "de-opted";
1586
+ return "compiled";
1587
+ }
1588
+
1589
+ // src/nextjsDetector.ts
1590
+ var SERVER_COMPONENT_PATTERNS = [
1591
+ /\.server\.[jt]sx?$/,
1592
+ // explicit .server.tsx convention
1593
+ /[\\/]app[\\/].+[\\/]page\.[jt]sx?$/,
1594
+ // Next.js app router page
1595
+ /[\\/]app[\\/].+[\\/]layout\.[jt]sx?$/,
1596
+ // Next.js app router layout
1597
+ /[\\/]app[\\/].+[\\/]loading\.[jt]sx?$/,
1598
+ // Next.js loading UI
1599
+ /[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
1600
+ // Next.js error UI
1601
+ ];
1602
+ var SERVER_REFERENCE_PATTERNS = [
1603
+ /_ServerReference$/,
1604
+ /^RSC_/
1605
+ ];
1606
+ var detectionEmitted = false;
1607
+ function maybeEmitNextjsContext(client2) {
1608
+ if (detectionEmitted) return;
1609
+ try {
1610
+ const win = globalThis;
1611
+ const hasNextData = "__NEXT_DATA__" in win;
1612
+ const hasNextRouter = "__next_router_state_tree__" in win;
1613
+ const hasNext = "next" in win && win.next !== null;
1614
+ if (!hasNextData && !hasNextRouter && !hasNext) return;
1615
+ detectionEmitted = true;
1616
+ let version;
1617
+ let isAppRouter = false;
1618
+ let initialRoute;
1619
+ try {
1620
+ const nextData = win.__NEXT_DATA__;
1621
+ if (nextData) {
1622
+ version = typeof nextData.buildId === "string" ? nextData.buildId : void 0;
1623
+ initialRoute = typeof nextData.page === "string" ? nextData.page : void 0;
1624
+ }
1625
+ isAppRouter = hasNextRouter || !!win.__next_router_state_tree__;
1626
+ } catch {
1627
+ }
1628
+ client2.sendImmediate({
1629
+ type: "runtime:nextjsContext",
1630
+ detected: true,
1631
+ version,
1632
+ isAppRouter,
1633
+ initialRoute,
1634
+ timestamp: Date.now()
1635
+ });
1636
+ } catch {
1637
+ }
1638
+ }
1639
+ function detectServerComponent(fiber) {
1640
+ const type = fiber.type;
1641
+ if (type) {
1642
+ const name = type.displayName || type.name || "";
1643
+ if (SERVER_REFERENCE_PATTERNS.some((p) => p.test(name))) return true;
1644
+ }
1645
+ const fileName = fiber._debugSource?.fileName;
1646
+ if (fileName) {
1647
+ if (SERVER_COMPONENT_PATTERNS.some((p) => p.test(fileName))) return true;
1648
+ }
1649
+ return false;
1650
+ }
1651
+ function resetNextjsDetection() {
1652
+ detectionEmitted = false;
1653
+ }
1654
+
1655
+ // src/actionStateTracker.ts
1656
+ var prevActionStateMap = /* @__PURE__ */ new Map();
1657
+ var ACTION_STATE_HOOK_NAMES = /* @__PURE__ */ new Set(["useActionState"]);
1658
+ var OPTIMISTIC_HOOK_NAMES = /* @__PURE__ */ new Set(["useOptimistic"]);
1659
+ function extractActionEntries(fiber) {
1660
+ const hookTypes = fiber._debugHookTypes;
1661
+ if (!hookTypes) return null;
1662
+ const entries = [];
1663
+ let hookState = fiber.memoizedState;
1664
+ let hookIdx = 0;
1665
+ for (const hookType of hookTypes) {
1666
+ if (!hookState) break;
1667
+ if (ACTION_STATE_HOOK_NAMES.has(hookType)) {
1668
+ const ms = hookState.memoizedState;
1669
+ if (Array.isArray(ms) && ms.length >= 3) {
1670
+ entries.push({
1671
+ hookIndex: hookIdx,
1672
+ hookKind: "action",
1673
+ isPending: ms[2] === true,
1674
+ state: serializeValue(ms[0])
1675
+ });
1676
+ }
1677
+ } else if (OPTIMISTIC_HOOK_NAMES.has(hookType)) {
1678
+ const ms = hookState.memoizedState;
1679
+ if (Array.isArray(ms)) {
1680
+ entries.push({
1681
+ hookIndex: hookIdx,
1682
+ hookKind: "optimistic",
1683
+ isPending: false,
1684
+ // optimistic values are "immediately applied"
1685
+ state: serializeValue(ms[0])
1686
+ });
1687
+ }
1688
+ }
1689
+ hookState = hookState.next;
1690
+ hookIdx++;
1691
+ }
1692
+ return entries.length > 0 ? entries : null;
1693
+ }
1694
+ function scanActionStateChanges(fiberRefMap2, client2) {
1695
+ try {
1696
+ for (const [nodeId, fiber] of fiberRefMap2) {
1697
+ const entries = extractActionEntries(fiber);
1698
+ if (!entries) continue;
1699
+ const snapshot = JSON.stringify(entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state })));
1700
+ if (prevActionStateMap.get(nodeId) === snapshot) continue;
1701
+ prevActionStateMap.set(nodeId, snapshot);
1702
+ const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
1703
+ client2.send({
1704
+ type: "runtime:actionState",
1705
+ nodeId,
1706
+ componentName,
1707
+ actions: entries,
1708
+ timestamp: Date.now()
1709
+ });
1710
+ }
1711
+ } catch {
1712
+ }
1713
+ }
1714
+ function clearActionStateCache() {
1715
+ prevActionStateMap.clear();
1716
+ }
1717
+
1718
+ // src/rscPayloadInterceptor.ts
1719
+ var RSC_URL_PATTERNS = [
1720
+ /\?_rsc=/,
1721
+ // App Router RSC param
1722
+ /\?__RSC__=/,
1723
+ // Older Next.js RSC param
1724
+ /\/_next\/data\//,
1725
+ // Pages Router getServerSideProps / getStaticProps
1726
+ /\/__nextjs_original-stack-frame/
1727
+ ];
1728
+ function parseCacheStatus(headers) {
1729
+ const raw = headers.get("x-nextjs-cache") || headers.get("x-vercel-cache") || "";
1730
+ switch (raw.toUpperCase()) {
1731
+ case "HIT":
1732
+ return "HIT";
1733
+ case "MISS":
1734
+ return "MISS";
1735
+ case "STALE":
1736
+ return "STALE";
1737
+ default:
1738
+ return "unknown";
1739
+ }
1740
+ }
1741
+ function extractRoute(url) {
1742
+ try {
1743
+ const u = new URL(url, globalThis.location?.href ?? "http://localhost");
1744
+ return u.pathname;
1745
+ } catch {
1746
+ return url.split("?")[0] ?? url;
1747
+ }
1748
+ }
1749
+ var originalFetch = null;
1750
+ var interceptorClient = null;
1751
+ var isInstalled2 = false;
1752
+ function installRscPayloadInterceptor(client2) {
1753
+ if (isInstalled2 || typeof globalThis.fetch !== "function") return;
1754
+ isInstalled2 = true;
1755
+ interceptorClient = client2;
1756
+ originalFetch = globalThis.fetch;
1757
+ globalThis.fetch = async function patchedFetch(input, init) {
1758
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1759
+ const isRscRequest = RSC_URL_PATTERNS.some((p) => p.test(url));
1760
+ const response = await originalFetch.call(globalThis, input, init);
1761
+ if (isRscRequest && interceptorClient?.connected) {
1762
+ try {
1763
+ const sizeHeader = response.headers.get("content-length");
1764
+ const payloadSizeBytes = sizeHeader ? parseInt(sizeHeader, 10) : 0;
1765
+ interceptorClient.send({
1766
+ type: "runtime:rscPayload",
1767
+ route: extractRoute(url),
1768
+ payloadSizeBytes: isNaN(payloadSizeBytes) ? 0 : payloadSizeBytes,
1769
+ cacheStatus: parseCacheStatus(response.headers),
1770
+ timestamp: Date.now()
1771
+ });
1772
+ } catch {
1773
+ }
1774
+ }
1775
+ return response;
1776
+ };
1777
+ }
1778
+ function uninstallRscPayloadInterceptor() {
1779
+ if (!isInstalled2 || !originalFetch) return;
1780
+ globalThis.fetch = originalFetch;
1781
+ originalFetch = null;
1782
+ interceptorClient = null;
1783
+ isInstalled2 = false;
1784
+ }
1785
+
1786
+ // src/fetchOriginRegistry.ts
1787
+ var fetchDataOrigin = /* @__PURE__ */ new WeakMap();
1788
+ var requestTagTimestamps = /* @__PURE__ */ new Map();
1789
+ var FETCH_ORIGIN_TTL_MS = 3e3;
1790
+ function tagFetchData(obj, requestId, depth = 0) {
1791
+ if (depth > 2 || obj === null || typeof obj !== "object") return;
1792
+ fetchDataOrigin.set(obj, requestId);
1793
+ if (depth === 0) requestTagTimestamps.set(requestId, Date.now());
1794
+ if (Array.isArray(obj)) {
1795
+ for (let i = 0; i < Math.min(obj.length, 50); i++) tagFetchData(obj[i], requestId, depth + 1);
1796
+ } else {
1797
+ for (const val of Object.values(obj)) tagFetchData(val, requestId, depth + 1);
1798
+ }
1799
+ }
1800
+ function hasActiveTags() {
1801
+ return requestTagTimestamps.size > 0;
1802
+ }
1803
+ function clearFetchOriginTags() {
1804
+ requestTagTimestamps.clear();
1805
+ }
1806
+ function findFetchOrigin(obj, depth = 0) {
1807
+ if (depth > 2 || obj === null || typeof obj !== "object") return void 0;
1808
+ const rid = fetchDataOrigin.get(obj);
1809
+ if (rid) {
1810
+ const tagTime = requestTagTimestamps.get(rid);
1811
+ if (tagTime && Date.now() - tagTime <= FETCH_ORIGIN_TTL_MS) return rid;
1812
+ requestTagTimestamps.delete(rid);
1813
+ }
1814
+ if (Array.isArray(obj)) {
1815
+ for (let i = 0; i < Math.min(obj.length, 20); i++) {
1816
+ const found = findFetchOrigin(obj[i], depth + 1);
1817
+ if (found) return found;
1818
+ }
1819
+ } else {
1820
+ for (const val of Object.values(obj)) {
1821
+ const found = findFetchOrigin(val, depth + 1);
1822
+ if (found) return found;
1823
+ }
1824
+ }
1825
+ return void 0;
1826
+ }
1827
+
1828
+ // src/fiberTreeWalker.ts
1829
+ var FIBER_TAGS = {
1830
+ FunctionComponent: 0,
1831
+ ClassComponent: 1,
1832
+ HostRoot: 3,
1833
+ // Root of a host tree (e.g., #root DOM node)
1834
+ HostComponent: 5,
1835
+ // DOM elements (div, span, etc.) - SKIP these
1836
+ HostText: 6,
1837
+ // Text nodes - SKIP these
1838
+ Fragment: 7,
1839
+ // React.Fragment - SKIP but traverse children
1840
+ Mode: 8,
1841
+ // React.StrictMode, ConcurrentMode - SKIP but traverse children
1842
+ ContextConsumer: 9,
1843
+ ContextProvider: 10,
1844
+ ForwardRef: 11,
1845
+ Profiler: 12,
1846
+ // React.Profiler - SKIP but traverse children
1847
+ SuspenseComponent: 13,
1848
+ MemoComponent: 14,
1849
+ SimpleMemoComponent: 15,
1850
+ LazyComponent: 16,
1851
+ OffscreenComponent: 22
1852
+ // React 18 concurrent features - SKIP but traverse children
1853
+ };
1854
+ var USER_COMPONENT_TAGS = /* @__PURE__ */ new Set([
1855
+ FIBER_TAGS.FunctionComponent,
1856
+ FIBER_TAGS.ClassComponent,
1857
+ FIBER_TAGS.ForwardRef,
1858
+ FIBER_TAGS.MemoComponent,
1859
+ FIBER_TAGS.SimpleMemoComponent
1860
+ ]);
1861
+ function isLikelyQueryObserver(obj) {
1862
+ if (obj === null || typeof obj !== "object") return false;
1863
+ const candidate = obj;
1864
+ return typeof candidate.getCurrentResult === "function" && typeof candidate.subscribe === "function";
1865
+ }
1866
+ function getQueryHashFromObserver(observer) {
1867
+ if (observer.options && typeof observer.options === "object") {
1868
+ const opts = observer.options;
1869
+ if (typeof opts.queryHash === "string") return opts.queryHash;
1870
+ }
1871
+ if (observer.currentQuery && typeof observer.currentQuery === "object") {
1872
+ const q = observer.currentQuery;
1873
+ if (typeof q.queryHash === "string") return q.queryHash;
1874
+ }
1875
+ if (typeof observer.queryHash === "string") return observer.queryHash;
1876
+ return null;
1877
+ }
1878
+ function detectQueryObserverHashes(fiber) {
1879
+ let hookState = fiber.memoizedState;
1880
+ if (!hookState) return void 0;
1881
+ const seen = /* @__PURE__ */ new Set();
1882
+ let iterations = 0;
1883
+ while (hookState && iterations < 100) {
1884
+ iterations++;
1885
+ try {
1886
+ const ms = hookState.memoizedState;
1887
+ if (isLikelyQueryObserver(ms)) {
1888
+ const hash = getQueryHashFromObserver(ms);
1889
+ if (hash) seen.add(hash);
1890
+ } else if (ms !== null && typeof ms === "object" && !Array.isArray(ms)) {
1891
+ const ref = ms.current;
1892
+ if (isLikelyQueryObserver(ref)) {
1893
+ const hash = getQueryHashFromObserver(ref);
1894
+ if (hash) seen.add(hash);
1895
+ }
1896
+ }
1897
+ } catch {
1898
+ }
1899
+ hookState = hookState.next;
1900
+ }
1901
+ return seen.size > 0 ? Array.from(seen) : void 0;
1902
+ }
1903
+ function countFiberHooks(fiber) {
1904
+ if (fiber._debugHookTypes) return fiber._debugHookTypes.length;
1905
+ let count = 0;
1906
+ let state = fiber.memoizedState;
1907
+ while (state && count < 100) {
1908
+ count++;
1909
+ state = state.next;
1910
+ }
1911
+ return count;
1912
+ }
1913
+ function hasFiberContextHook(fiber) {
1914
+ if (fiber.dependencies?.firstContext) return true;
1915
+ if (fiber._debugHookTypes?.includes("useContext")) return true;
1916
+ return false;
1917
+ }
1918
+ function detectTransitionPending(fiber) {
1919
+ let state = fiber.memoizedState;
1920
+ let iterations = 0;
1921
+ while (state && iterations < 100) {
1922
+ iterations++;
1923
+ const ms = state.memoizedState;
1924
+ if (Array.isArray(ms) && ms.length === 2 && typeof ms[0] === "boolean" && typeof ms[1] === "function") {
1925
+ if (ms[0] === true) return true;
1926
+ }
1927
+ state = state.next;
1928
+ }
1929
+ return false;
1930
+ }
1931
+ var MAX_TREE_DEPTH = 100;
1932
+ var MAX_CHILDREN_PER_NODE = 300;
1933
+ var throttleTimer = null;
1934
+ var maxWaitTimer = null;
1935
+ var INTERVAL_MS_SMALL = 200;
1936
+ var INTERVAL_MS_MEDIUM = 500;
1937
+ var INTERVAL_MS_LARGE = 1e3;
1938
+ var snapshotIntervalMs = INTERVAL_MS_SMALL;
1939
+ var cachedFiberRoot = null;
1940
+ var isWalking = false;
1941
+ var pendingLocalStateCorrelations = [];
1942
+ var originalOnCommitFiberRoot = null;
1943
+ var isInstalled3 = false;
1944
+ var hookedRendererID = null;
1945
+ var walkerOptions = {};
1946
+ function defaultFilterConfig() {
1947
+ return {
1948
+ frameworkNames: /* @__PURE__ */ new Set(),
1949
+ frameworkNamePatterns: [],
1950
+ frameworkPathPatterns: [],
1951
+ hostSkipPrefixes: [],
1952
+ userOnlyStrict: false,
1953
+ userAllowPatterns: []
1954
+ };
1955
+ }
1956
+ var walkerFilterConfig = defaultFilterConfig();
1957
+ var activeStrategy = null;
1958
+ var lastSnapshotSentTime = 0;
1959
+ var DEVTOOLS_STALE_THRESHOLD_MS = 2e3;
1960
+ var debugEnabled = false;
1961
+ try {
1962
+ debugEnabled = !!globalThis.__FLOTRACE_DEBUG__;
1963
+ } catch {
1964
+ }
1965
+ function debugLog(...args) {
1966
+ if (debugEnabled) console.log(...args);
1967
+ }
1968
+ var fiberRefMap = /* @__PURE__ */ new Map();
1969
+ function getComponentName2(fiber) {
1970
+ const type = fiber.type;
1971
+ if (!type) return "Unknown";
1972
+ if (typeof type === "function") {
1973
+ return type.displayName || type.name || "Anonymous";
1974
+ }
1975
+ if (typeof type === "object" && type !== null) {
1976
+ const t = type;
1977
+ if (t.type) {
1978
+ return t.type.displayName || t.type.name || "Memo";
1979
+ }
1980
+ if (t.render) {
1981
+ return t.render.displayName || t.render.name || "ForwardRef";
1982
+ }
1983
+ return t.displayName || t.name || "Unknown";
1984
+ }
1985
+ if (typeof type === "string") {
1986
+ return type;
1987
+ }
1988
+ return "Unknown";
1989
+ }
1990
+ function isUserComponent(fiber) {
1991
+ if (!USER_COMPONENT_TAGS.has(fiber.tag)) return false;
1992
+ const name = getComponentName2(fiber);
1993
+ if (name === "Anonymous" || name === "Unknown" || name === "ForwardRef" || name === "Memo")
1994
+ return false;
1995
+ if (name.startsWith("FloTrace")) return false;
1996
+ if (name.startsWith("@") || name.includes("/")) return false;
1997
+ if (/^[$_][A-Za-z0-9]{0,3}$/.test(name)) return false;
1998
+ if (fiber._debugSource?.fileName?.includes("node_modules")) return false;
1999
+ if (walkerFilterConfig.hostSkipPrefixes.length > 0) {
2000
+ for (const prefix of walkerFilterConfig.hostSkipPrefixes) {
2001
+ if (name.startsWith(prefix)) return false;
2002
+ }
2003
+ }
2004
+ return true;
2005
+ }
2006
+ var FRAMEWORK_COMPONENT_NAMES = /* @__PURE__ */ new Set([
2007
+ // Next.js App Router internals (Next.js 13–14)
2008
+ "InnerLayoutRouter",
2009
+ "OuterLayoutRouter",
2010
+ "HotReload",
2011
+ "RedirectBoundary",
2012
+ "NotFoundBoundary",
2013
+ "RenderFromTemplateContext",
2014
+ "ScrollAndFocusHandler",
2015
+ "AppRouter",
2016
+ "ServerRoot",
2017
+ "ReactDevOverlay",
2018
+ "PathnameContextProviderAdapter",
2019
+ "MetadataBoundary",
2020
+ "ViewportBoundary",
2021
+ "NotFoundErrorBoundary",
2022
+ "RedirectErrorBoundary",
2023
+ "InnerScrollAndFocusHandler",
2024
+ "GlobalError",
2025
+ // Next.js 15 / React 19 new internals
2026
+ "ViewTransition",
2027
+ // Next.js 15 shared-element transition wrapper
2028
+ "ActionStateContext",
2029
+ // Next.js 15 server action state context provider
2030
+ "RequestCookiesProvider",
2031
+ "DraftModeProvider",
2032
+ // React Router v6 / v7
2033
+ "Routes",
2034
+ "Route",
2035
+ "Router",
2036
+ "BrowserRouter",
2037
+ "HashRouter",
2038
+ "MemoryRouter",
2039
+ "Outlet",
2040
+ "Navigate",
2041
+ "RenderedRoute",
2042
+ "RouterProvider",
2043
+ // React 19 built-in primitives
2044
+ "Activity",
2045
+ // React 19: show/hide subtrees while preserving state (was <Offscreen>)
2046
+ // Common library wrappers
2047
+ "Suspense",
2048
+ "ErrorBoundary",
2049
+ "QueryClientProvider",
2050
+ "PersistGate"
2051
+ ]);
2052
+ var FRAMEWORK_PATH_PATTERNS = [
2053
+ // React core / Next.js
2054
+ /next[\\/]dist/,
2055
+ /react-dom/,
2056
+ /[\\/]scheduler[\\/]/,
2057
+ // React internal scheduler package
2058
+ // Routing
2059
+ /react-router/,
2060
+ // React Router v6
2061
+ /@react-router[\\/]/,
2062
+ // React Router v7 (scoped package)
2063
+ // State management
2064
+ /@tanstack[\\/]/,
2065
+ // TanStack Query / Table / Router / Form / Virtual
2066
+ /react-redux/,
2067
+ /zustand/,
2068
+ /jotai/,
2069
+ /recoil/,
2070
+ // UI component libraries (for when source maps are available)
2071
+ /@fortawesome[\\/]/,
2072
+ // Font Awesome icons
2073
+ /framer-motion/,
2074
+ // Framer Motion (PresenceChild, AnimatePresence, etc.)
2075
+ /sonner/,
2076
+ // Sonner toast
2077
+ /@radix-ui[\\/]/,
2078
+ // Radix UI primitives
2079
+ /@headlessui[\\/]/,
2080
+ // Headless UI
2081
+ /@mui[\\/]/,
2082
+ // Material UI
2083
+ /@chakra-ui[\\/]/,
2084
+ // Chakra UI
2085
+ /react-spring/,
2086
+ // React Spring
2087
+ /react-transition-group/,
2088
+ // React Transition Group
2089
+ /react-aria/,
2090
+ // Adobe React Aria
2091
+ /react-hook-form/,
2092
+ /formik/
2093
+ ];
2094
+ function resolveEffectiveSourcePath(fiber) {
2095
+ if (fiber._debugSource?.fileName) return fiber._debugSource.fileName;
2096
+ const ownerHit = walkAncestors(
2097
+ fiber._debugOwner ?? null,
2098
+ 3,
2099
+ (f) => f._debugOwner ?? null,
2100
+ (cur) => cur._debugSource?.fileName ?? void 0
2101
+ );
2102
+ if (ownerHit) return ownerHit;
2103
+ const stack = fiber._debugStack?.stack;
2104
+ if (typeof stack === "string") {
2105
+ const parsed = parseFirstNonReactFrame(stack);
2106
+ if (parsed) return parsed;
2107
+ }
2108
+ return null;
2109
+ }
2110
+ var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
2111
+ function walkAncestors(start, maxHops, next, visit) {
2112
+ let cur = start;
2113
+ for (let hops = 0; cur && hops < maxHops; hops++, cur = next(cur) ?? null) {
2114
+ const out = visit(cur);
2115
+ if (out === STOP_WALK) return void 0;
2116
+ if (out !== void 0) return out;
2117
+ }
2118
+ return void 0;
2119
+ }
2120
+ function parseFirstNonReactFrame(stack) {
2121
+ const lines = stack.split("\n");
2122
+ for (const line of lines) {
2123
+ const parened = line.match(/\(([^)]+):\d+:\d+\)/);
2124
+ const hermes = line.match(/@([^\s]+):\d+:\d+$/);
2125
+ const path = parened?.[1] ?? hermes?.[1];
2126
+ if (!path) continue;
2127
+ if (path.includes("react-dom")) continue;
2128
+ if (path.includes("react-native/Libraries")) continue;
2129
+ if (path.includes("/react/cjs/")) continue;
2130
+ if (path.includes("/scheduler/")) continue;
2131
+ return path;
2132
+ }
2133
+ return null;
2134
+ }
2135
+ function isFrameworkComponent(fiber, name) {
2136
+ if (walkerFilterConfig.frameworkNames.has(name)) return true;
2137
+ for (const pattern of walkerFilterConfig.frameworkNamePatterns) {
2138
+ if (pattern.test(name)) return true;
2139
+ }
2140
+ if (walkerFilterConfig.userOnlyStrict) {
2141
+ const effective = resolveEffectiveSourcePath(fiber);
2142
+ if (effective) {
2143
+ for (const allow of walkerFilterConfig.userAllowPatterns) {
2144
+ if (allow.test(effective)) return false;
2145
+ }
2146
+ if (effective.includes("node_modules")) return true;
2147
+ }
2148
+ }
2149
+ const filePath = fiber._debugSource?.fileName;
2150
+ if (filePath) {
2151
+ for (const pattern of walkerFilterConfig.frameworkPathPatterns) {
2152
+ if (pattern.test(filePath)) return true;
2153
+ }
2154
+ }
2155
+ return false;
2156
+ }
2157
+ var KNOWN_LIBRARY_NAMES = /* @__PURE__ */ new Map([
2158
+ // Font Awesome
2159
+ ["FontAwesomeIcon", "fontawesome"],
2160
+ ["FontAwesomeLayers", "fontawesome"],
2161
+ ["FontAwesomeLayersText", "fontawesome"],
2162
+ // Framer Motion
2163
+ ["AnimatePresence", "framer"],
2164
+ ["LazyMotion", "framer"],
2165
+ ["MotionConfig", "framer"],
2166
+ ["PresenceChild", "framer"],
2167
+ ["LayoutGroupContext", "framer"],
2168
+ // Lottie
2169
+ ["Lottie", "lottie"],
2170
+ ["LottiePlayer", "lottie"],
2171
+ // Heroicons / Lucide exported icons sometimes appear as named functions
2172
+ ["HeroIcon", "heroicons"]
2173
+ ]);
2174
+ function detectLibraryName(fiber, name) {
2175
+ if (name.includes(".")) {
2176
+ return name.split(".")[0].toLowerCase();
2177
+ }
2178
+ if (name.startsWith("__")) {
2179
+ return "internal";
2180
+ }
2181
+ const known = KNOWN_LIBRARY_NAMES.get(name);
2182
+ return known;
2183
+ }
2184
+ function buildNodeId(name, sameNameIndex, parentId) {
2185
+ const segment = `${name}-${sameNameIndex}`;
2186
+ return parentId ? `${parentId}/${segment}` : segment;
2187
+ }
2188
+ function shallowPropsChanged(prev, next) {
2189
+ if (prev === next) return false;
2190
+ if (!prev || !next) return true;
2191
+ const prevKeys = Object.keys(prev);
2192
+ const nextKeys = Object.keys(next);
2193
+ if (prevKeys.length !== nextKeys.length) return true;
2194
+ for (const key of nextKeys) {
2195
+ if (key === "children") continue;
2196
+ if (prev[key] !== next[key]) return true;
2197
+ }
2198
+ return false;
2199
+ }
2200
+ function detectRenderReason(fiber, renderPhase) {
2201
+ if (renderPhase === "mount") return "mount";
2202
+ const prev = fiber.alternate;
2203
+ if (!prev) return "mount";
2204
+ if (shallowPropsChanged(prev.memoizedProps, fiber.memoizedProps)) {
2205
+ return "props-changed";
2206
+ }
2207
+ return "state-or-context";
2208
+ }
2209
+ function scanFiberStateForOrigin(fiber, componentName) {
2210
+ let hook = fiber.memoizedState;
2211
+ let hookIndex = 0;
2212
+ while (hook !== null) {
2213
+ try {
2214
+ const ms = hook.memoizedState;
2215
+ if (ms !== null && typeof ms === "object") {
2216
+ const isEffect = "tag" in ms && "create" in ms;
2217
+ if (!isEffect) {
2218
+ const rid = findFetchOrigin(ms);
2219
+ if (rid) {
2220
+ pendingLocalStateCorrelations.push({ requestId: rid, componentName, hookIndex });
2221
+ } else if (hook.queue !== null) {
2222
+ const lastRendered = hook.queue.lastRenderedState;
2223
+ if (lastRendered !== null && typeof lastRendered === "object") {
2224
+ const rid2 = findFetchOrigin(lastRendered);
2225
+ if (rid2) {
2226
+ pendingLocalStateCorrelations.push({ requestId: rid2, componentName, hookIndex });
2227
+ }
2228
+ }
2229
+ }
2230
+ }
2231
+ }
2232
+ } catch {
2233
+ }
2234
+ hook = hook.next;
2235
+ hookIndex++;
2236
+ }
2237
+ }
2238
+ function resolveEffectiveReactKey(fiber) {
2239
+ if (typeof fiber.key === "string") return fiber.key;
2240
+ return walkAncestors(
2241
+ fiber.return ?? null,
2242
+ 6,
2243
+ (f) => f.return ?? null,
2244
+ (cur) => {
2245
+ if (isUserComponent(cur) && !isFrameworkComponent(cur, getComponentName2(cur))) {
2246
+ return STOP_WALK;
2247
+ }
2248
+ return typeof cur.key === "string" ? cur.key : void 0;
2249
+ }
2250
+ );
2251
+ }
2252
+ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFallback = false) {
2253
+ if (!fiber) return [];
2254
+ if (depth >= MAX_TREE_DEPTH) return [];
2255
+ const nodes = [];
2256
+ let current = fiber;
2257
+ const nameCountMap = sharedNameCountMap || /* @__PURE__ */ new Map();
2258
+ while (current) {
2259
+ try {
2260
+ const tag = current.tag;
2261
+ if (isUserComponent(current)) {
2262
+ const name = getComponentName2(current);
2263
+ const nameCount = nameCountMap.get(name) || 0;
2264
+ nameCountMap.set(name, nameCount + 1);
2265
+ const nodeId = buildNodeId(name, nameCount, parentId);
2266
+ fiberRefMap.set(nodeId, current);
2267
+ const renderPhase = current.alternate ? "update" : "mount";
2268
+ const renderReason = detectRenderReason(current, renderPhase);
2269
+ recordTimelineEvent(
2270
+ nodeId,
2271
+ name,
2272
+ renderPhase === "mount" ? "mount" : "render",
2273
+ { reason: renderReason },
2274
+ current.actualDuration
2275
+ );
2276
+ const children = walkFiber(
2277
+ current.child,
2278
+ nodeId,
2279
+ void 0,
2280
+ depth + 1,
2281
+ inSuspenseFallback
2282
+ );
2283
+ const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
2284
+ const framework = isFrameworkComponent(current, name) || void 0;
2285
+ const queryHashes = detectQueryObserverHashes(current);
2286
+ const isTransitionPending = detectTransitionPending(current) || void 0;
2287
+ const compilerStatus = detectCompilerStatus(current);
2288
+ const isServerComponent = detectServerComponent(current) || void 0;
2289
+ const libraryName = framework ? void 0 : detectLibraryName(current, name);
2290
+ const node = {
2291
+ id: nodeId,
2292
+ name,
2293
+ children: truncatedChildren,
2294
+ fiberTag: tag,
2295
+ renderPhase,
2296
+ renderReason,
2297
+ renderDuration: current.actualDuration,
2298
+ filePath: current._debugSource?.fileName,
2299
+ lineNumber: current._debugSource?.lineNumber,
2300
+ isFramework: framework,
2301
+ reactKey: resolveEffectiveReactKey(current),
2302
+ queryHashes,
2303
+ hookCount: countFiberHooks(current),
2304
+ hasContextHook: hasFiberContextHook(current) || void 0,
2305
+ isTransitionPending,
2306
+ isSuspenseFallback: inSuspenseFallback || void 0,
2307
+ compilerStatus,
2308
+ isServerComponent,
2309
+ isLibrary: libraryName !== void 0 ? true : void 0,
2310
+ libraryName
2311
+ };
2312
+ if (!walkerOptions.pruneSubtree?.(node)) {
2313
+ nodes.push(node);
2314
+ }
2315
+ if (hasActiveTags() && current.memoizedState !== null) {
2316
+ scanFiberStateForOrigin(current, name);
2317
+ }
2318
+ } else if (tag === FIBER_TAGS.HostText) {
2319
+ } else if (tag === FIBER_TAGS.SuspenseComponent) {
2320
+ const primary = current.child;
2321
+ if (current.memoizedState === null && primary) {
2322
+ const childNodes = walkFiber(
2323
+ primary.child,
2324
+ parentId,
2325
+ nameCountMap,
2326
+ depth,
2327
+ inSuspenseFallback
2328
+ );
2329
+ nodes.push(...childNodes);
2330
+ } else if (primary?.sibling) {
2331
+ const childNodes = walkFiber(
2332
+ primary.sibling,
2333
+ parentId,
2334
+ nameCountMap,
2335
+ depth,
2336
+ true
2337
+ // all nodes in the fallback branch get isSuspenseFallback
2338
+ );
2339
+ nodes.push(...childNodes);
2340
+ } else {
2341
+ debugLog("[FloTrace] SuspenseComponent has no walkable children");
2342
+ }
2343
+ } else if (tag === FIBER_TAGS.OffscreenComponent) {
2344
+ if (current.memoizedState === null) {
2345
+ const childNodes = walkFiber(
2346
+ current.child,
2347
+ parentId,
2348
+ nameCountMap,
2349
+ depth,
2350
+ inSuspenseFallback
2351
+ );
2352
+ nodes.push(...childNodes);
2353
+ } else {
2354
+ debugLog("[FloTrace] Skipping hidden OffscreenComponent subtree");
2355
+ }
2356
+ } else {
2357
+ const childNodes = walkFiber(
2358
+ current.child,
2359
+ parentId,
2360
+ nameCountMap,
2361
+ depth,
2362
+ inSuspenseFallback
2363
+ );
2364
+ nodes.push(...childNodes);
2365
+ }
2366
+ } catch (error) {
2367
+ console.error("[FloTrace] Error processing fiber node, skipping:", error);
2368
+ }
2369
+ current = current.sibling;
2370
+ }
2371
+ return nodes;
2372
+ }
2373
+ function buildTreeFromFiberRoot(root) {
2374
+ const rootFiber = root.current;
2375
+ if (!rootFiber || !rootFiber.child) {
2376
+ console.warn("[FloTrace] No root fiber or no child:", {
2377
+ hasRoot: !!rootFiber,
2378
+ hasChild: !!rootFiber?.child
2379
+ });
2380
+ return null;
2381
+ }
2382
+ fiberRefMap.clear();
2383
+ const topLevelNodes = walkFiber(rootFiber.child, "");
2384
+ debugLog(
2385
+ "[FloTrace] walkFiber found",
2386
+ topLevelNodes.length,
2387
+ "top-level nodes"
2388
+ );
2389
+ if (topLevelNodes.length === 1) {
2390
+ return topLevelNodes[0];
2391
+ }
2392
+ if (topLevelNodes.length > 0) {
2393
+ return {
2394
+ id: "Root",
2395
+ name: "Root",
2396
+ children: topLevelNodes,
2397
+ fiberTag: FIBER_TAGS.HostRoot
2398
+ };
2399
+ }
2400
+ return null;
2401
+ }
2402
+ function findFiberRootFromDOM() {
2403
+ try {
2404
+ if (typeof document === "undefined") return null;
2405
+ const selectors = ["#root", "#__next", "#app", "#__nuxt", "[data-reactroot]"];
2406
+ for (const selector of selectors) {
2407
+ const element = document.querySelector(selector);
2408
+ if (!element) continue;
2409
+ debugLog(
2410
+ `[FloTrace] Trying selector "${selector}" \u2192 found element`,
2411
+ element.tagName,
2412
+ element.id
2413
+ );
2414
+ const reactKeys = Object.keys(element).filter(
2415
+ (k) => k.startsWith("__react") || k.startsWith("_react")
2416
+ );
2417
+ debugLog(`[FloTrace] React keys on element:`, reactKeys);
2418
+ const fiberRoot = getFiberRootFromElement(element);
2419
+ if (fiberRoot) {
2420
+ debugLog("[FloTrace] Found fiber root from selector:", selector);
2421
+ return fiberRoot;
2422
+ }
2423
+ }
2424
+ const allBodyChildren = document.body?.children;
2425
+ if (allBodyChildren) {
2426
+ debugLog(
2427
+ "[FloTrace] Scanning all",
2428
+ allBodyChildren.length,
2429
+ "body children for React root..."
2430
+ );
2431
+ for (const child of Array.from(allBodyChildren)) {
2432
+ const reactKeys = Object.keys(child).filter(
2433
+ (k) => k.startsWith("__react") || k.startsWith("_react")
2434
+ );
2435
+ if (reactKeys.length > 0) {
2436
+ debugLog(
2437
+ "[FloTrace] React keys on",
2438
+ child.tagName,
2439
+ child.id || "(no id)",
2440
+ ":",
2441
+ reactKeys
2442
+ );
2443
+ }
2444
+ const fiberRoot = getFiberRootFromElement(child);
2445
+ if (fiberRoot) {
2446
+ debugLog(
2447
+ "[FloTrace] Found fiber root from body child scan:",
2448
+ child.tagName,
2449
+ child.id || "(no id)"
2450
+ );
2451
+ return fiberRoot;
2452
+ }
2453
+ }
2454
+ }
2455
+ console.warn(
2456
+ "[FloTrace] Could not find React fiber root from any DOM element"
2457
+ );
2458
+ return null;
2459
+ } catch (error) {
2460
+ console.error("[FloTrace] Error finding fiber root from DOM:", error);
2461
+ return null;
2462
+ }
2463
+ }
2464
+ function getFiberRootFromElement(element) {
2465
+ const keys = Object.keys(element);
2466
+ const containerKey = keys.find((k) => k.startsWith("__reactContainer$"));
2467
+ if (containerKey) {
2468
+ const hostRootFiber = element[containerKey];
2469
+ if (hostRootFiber?.stateNode) {
2470
+ return hostRootFiber.stateNode;
2471
+ }
2472
+ }
2473
+ const fiberKey = keys.find(
2474
+ (k) => k.startsWith("__reactFiber$") || k.startsWith("__reactInternalInstance$")
2475
+ );
2476
+ if (fiberKey) {
2477
+ const fiber = element[fiberKey];
2478
+ if (fiber) {
2479
+ let current = fiber;
2480
+ while (current?.return) {
2481
+ current = current.return;
2482
+ }
2483
+ if (current && current.tag === FIBER_TAGS.HostRoot && current.stateNode) {
2484
+ return current.stateNode;
2485
+ }
2486
+ }
2487
+ }
2488
+ const el = element;
2489
+ if (el._reactRootContainer?._internalRoot) {
2490
+ return el._reactRootContainer._internalRoot;
2491
+ }
2492
+ return null;
2493
+ }
2494
+ function adaptSnapshotInterval(nodeCount) {
2495
+ if (nodeCount >= 200) {
2496
+ snapshotIntervalMs = INTERVAL_MS_LARGE;
2497
+ } else if (nodeCount >= 50) {
2498
+ snapshotIntervalMs = INTERVAL_MS_MEDIUM;
2499
+ } else {
2500
+ snapshotIntervalMs = INTERVAL_MS_SMALL;
2501
+ }
2502
+ }
2503
+ function executeSnapshot(root) {
2504
+ if (isWalking) {
2505
+ debugLog("[FloTrace] Skipped snapshot: already walking");
2506
+ return;
2507
+ }
2508
+ isWalking = true;
2509
+ try {
2510
+ const tree = buildTreeFromFiberRoot(root);
2511
+ if (!tree) {
2512
+ console.warn("[FloTrace] buildTreeFromFiberRoot returned null");
2513
+ return;
2514
+ }
2515
+ const nodeCount = fiberRefMap.size;
2516
+ adaptSnapshotInterval(nodeCount);
2517
+ const client2 = getWebSocketClient();
2518
+ if (!client2.connected) {
2519
+ console.warn(
2520
+ "[FloTrace] WebSocket not connected, cannot send tree snapshot"
2521
+ );
2522
+ return;
2523
+ }
2524
+ const currentFlatTree = flattenTree2(tree);
2525
+ const sendFull = previousFlatTree === null || snapshotCounter % FULL_SNAPSHOT_INTERVAL === 0;
2526
+ if (sendFull) {
2527
+ debugLog(
2528
+ "[FloTrace] Sending FULL tree snapshot, root:",
2529
+ tree.name,
2530
+ "nodes:",
2531
+ nodeCount,
2532
+ "seq:",
2533
+ snapshotCounter,
2534
+ "nextInterval:",
2535
+ snapshotIntervalMs + "ms"
2536
+ );
2537
+ client2.sendImmediate({
2538
+ type: "runtime:treeSnapshot",
2539
+ tree,
2540
+ timestamp: Date.now()
2541
+ });
2542
+ lastSnapshotSentTime = Date.now();
2543
+ diffSeq = 0;
2544
+ } else {
2545
+ const diff = computeTreeDiff(previousFlatTree, currentFlatTree);
2546
+ if (diff) {
2547
+ debugLog(
2548
+ "[FloTrace] Sending tree diff, seq:",
2549
+ diffSeq,
2550
+ "added:",
2551
+ diff.added.length,
2552
+ "removed:",
2553
+ diff.removed.length,
2554
+ "updated:",
2555
+ diff.updated.length
2556
+ );
2557
+ client2.sendImmediate({
2558
+ type: "runtime:treeDiff",
2559
+ seq: diffSeq,
2560
+ added: diff.added,
2561
+ removed: diff.removed,
2562
+ updated: diff.updated,
2563
+ timestamp: Date.now()
2564
+ });
2565
+ lastSnapshotSentTime = Date.now();
2566
+ diffSeq++;
2567
+ } else {
2568
+ debugLog("[FloTrace] Tree unchanged, skipping diff");
2569
+ }
2570
+ }
2571
+ previousFlatTree = currentFlatTree;
2572
+ if (pendingLocalStateCorrelations.length > 0) {
2573
+ const now = Date.now();
2574
+ const toSend = pendingLocalStateCorrelations.splice(0);
2575
+ for (const corr of toSend) {
2576
+ try {
2577
+ client2.sendImmediate({
2578
+ type: "runtime:localStateCorrelation",
2579
+ requestId: corr.requestId,
2580
+ componentName: corr.componentName,
2581
+ hookIndex: corr.hookIndex,
2582
+ timestamp: now
2583
+ });
2584
+ } catch {
2585
+ }
2586
+ }
2587
+ }
2588
+ schedulePropDrillingAnalysis(tree, fiberRefMap, client2);
2589
+ scanActionStateChanges(fiberRefMap, client2);
2590
+ maybeEmitNextjsContext(client2);
2591
+ snapshotCounter++;
2592
+ } catch (error) {
2593
+ console.error("[FloTrace] Error walking fiber tree:", error);
2594
+ } finally {
2595
+ isWalking = false;
2596
+ }
2597
+ }
2598
+ function scheduleSnapshot(root) {
2599
+ cachedFiberRoot = root;
2600
+ if (throttleTimer) {
2601
+ clearTimeout(throttleTimer);
2602
+ }
2603
+ throttleTimer = setTimeout(() => {
2604
+ throttleTimer = null;
2605
+ if (maxWaitTimer) {
2606
+ clearTimeout(maxWaitTimer);
2607
+ maxWaitTimer = null;
2608
+ }
2609
+ executeSnapshot(cachedFiberRoot);
2610
+ }, snapshotIntervalMs);
2611
+ if (!maxWaitTimer) {
2612
+ maxWaitTimer = setTimeout(() => {
2613
+ maxWaitTimer = null;
2614
+ if (throttleTimer) {
2615
+ clearTimeout(throttleTimer);
2616
+ throttleTimer = null;
2617
+ }
2618
+ debugLog("[FloTrace] MaxWait forced snapshot (rapid commits detected)");
2619
+ if (cachedFiberRoot) {
2620
+ executeSnapshot(cachedFiberRoot);
2621
+ }
2622
+ }, snapshotIntervalMs * 2);
2623
+ }
2624
+ }
2625
+ var previousFlatTree = null;
2626
+ var diffSeq = 0;
2627
+ var snapshotCounter = 0;
2628
+ var FULL_SNAPSHOT_INTERVAL = 10;
2629
+ function flattenTree2(root, out = /* @__PURE__ */ new Map()) {
2630
+ out.set(root.id, root);
2631
+ for (const child of root.children) {
2632
+ flattenTree2(child, out);
2633
+ }
2634
+ return out;
2635
+ }
2636
+ function getParentId(nodeId) {
2637
+ const lastSlash = nodeId.lastIndexOf("/");
2638
+ return lastSlash === -1 ? "" : nodeId.substring(0, lastSlash);
2639
+ }
2640
+ function computeTreeDiff(prev, curr) {
2641
+ const added = [];
2642
+ const removed = [];
2643
+ const updated = [];
2644
+ for (const [id, currNode] of curr) {
2645
+ const prevNode = prev.get(id);
2646
+ if (!prevNode) {
2647
+ added.push({ ...currNode, children: [], parentId: getParentId(id) });
2648
+ } else {
2649
+ if (prevNode.renderDuration !== currNode.renderDuration || prevNode.renderPhase !== currNode.renderPhase || prevNode.renderReason !== currNode.renderReason) {
2650
+ updated.push({
2651
+ id,
2652
+ renderDuration: currNode.renderDuration,
2653
+ renderPhase: currNode.renderPhase,
2654
+ renderReason: currNode.renderReason
2655
+ });
2656
+ }
2657
+ }
2658
+ }
2659
+ for (const id of prev.keys()) {
2660
+ if (!curr.has(id)) {
2661
+ removed.push(id);
2662
+ }
2663
+ }
2664
+ if (added.length === 0 && removed.length === 0 && updated.length === 0) {
2665
+ return null;
2666
+ }
2667
+ return { added, removed, updated };
2668
+ }
2669
+ function requestTreeSnapshot() {
2670
+ if (!isInstalled3) {
2671
+ return;
2672
+ }
2673
+ if (activeStrategy === "devtools") {
2674
+ const elapsed = Date.now() - lastSnapshotSentTime;
2675
+ if (elapsed < DEVTOOLS_STALE_THRESHOLD_MS) return;
2676
+ debugLog("[FloTrace] DevTools hook stale (" + elapsed + "ms), falling back to DOM snapshot");
2677
+ }
2678
+ const root = findFiberRootFromDOM();
2679
+ if (root) {
2680
+ scheduleSnapshot(root);
2681
+ }
2682
+ }
2683
+ function requestFullSnapshot() {
2684
+ previousFlatTree = null;
2685
+ snapshotCounter = 0;
2686
+ diffSeq = 0;
2687
+ if (cachedFiberRoot) {
2688
+ scheduleSnapshot(cachedFiberRoot);
2689
+ }
2690
+ }
2691
+ function installFiberTreeWalker(options = {}) {
2692
+ if (isInstalled3) {
2693
+ console.warn("[FloTrace] Fiber tree walker already installed");
2694
+ return () => uninstallFiberTreeWalker();
2695
+ }
2696
+ if (typeof window === "undefined") {
2697
+ console.warn(
2698
+ "[FloTrace] Not in browser environment, cannot install fiber tree walker"
2699
+ );
2700
+ return () => {
2701
+ };
2702
+ }
2703
+ walkerOptions = options;
2704
+ isInstalled3 = true;
2705
+ walkerFilterConfig = {
2706
+ frameworkNames: options.frameworkComponentNames?.length ? /* @__PURE__ */ new Set([...FRAMEWORK_COMPONENT_NAMES, ...options.frameworkComponentNames]) : FRAMEWORK_COMPONENT_NAMES,
2707
+ frameworkNamePatterns: options.frameworkComponentNamePatterns ?? [],
2708
+ frameworkPathPatterns: options.frameworkPathPatterns?.length ? [...FRAMEWORK_PATH_PATTERNS, ...options.frameworkPathPatterns] : FRAMEWORK_PATH_PATTERNS,
2709
+ hostSkipPrefixes: options.hostComponentSkipPrefixes ?? [],
2710
+ userOnlyStrict: options.userOnlyStrict === true,
2711
+ userAllowPatterns: options.userAllowPatterns ?? []
2712
+ };
2713
+ try {
2714
+ const client2 = getWebSocketClient();
2715
+ installRscPayloadInterceptor(client2);
2716
+ } catch {
2717
+ }
2718
+ const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
2719
+ if (hook && typeof hook.onCommitFiberRoot === "function") {
2720
+ originalOnCommitFiberRoot = hook.onCommitFiberRoot;
2721
+ hook.onCommitFiberRoot = (rendererID, root, priority) => {
2722
+ if (originalOnCommitFiberRoot) {
2723
+ try {
2724
+ originalOnCommitFiberRoot(rendererID, root, priority);
2725
+ } catch (error) {
2726
+ console.error(
2727
+ "[FloTrace] Error in original onCommitFiberRoot:",
2728
+ error
2729
+ );
2730
+ }
2731
+ }
2732
+ if (hookedRendererID === null) {
2733
+ hookedRendererID = rendererID;
2734
+ }
2735
+ if (rendererID !== hookedRendererID) return;
2736
+ try {
2737
+ const client2 = getWebSocketClient();
2738
+ if (client2.connected) {
2739
+ const triggers = peekTriggers();
2740
+ for (const trigger of triggers) {
2741
+ client2.sendImmediate({ type: "runtime:renderTrigger", trigger });
2742
+ }
2743
+ const cascade = analyzeCascade(root, triggers);
2744
+ if (cascade) {
2745
+ client2.sendImmediate({ type: "runtime:renderCascade", cascade });
2746
+ }
2747
+ wrapFiberDispatchers(root);
2748
+ clearTriggers();
2749
+ }
2750
+ } catch {
2751
+ }
2752
+ scheduleSnapshot(root);
2753
+ };
2754
+ activeStrategy = "devtools";
2755
+ console.log(
2756
+ "[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
2757
+ );
2758
+ setTimeout(() => {
2759
+ try {
2760
+ const root = findFiberRootFromDOM();
2761
+ if (root) {
2762
+ scheduleSnapshot(root);
2763
+ }
2764
+ } catch (error) {
2765
+ console.error("[FloTrace] Error sending initial DevTools snapshot:", error);
2766
+ }
2767
+ }, 100);
2768
+ } else {
2769
+ activeStrategy = "dom";
2770
+ console.log(
2771
+ "[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
2772
+ );
2773
+ setTimeout(() => {
2774
+ try {
2775
+ const root = findFiberRootFromDOM();
2776
+ if (root) {
2777
+ scheduleSnapshot(root);
2778
+ }
2779
+ } catch (error) {
2780
+ console.error("[FloTrace] Error sending initial DOM fallback snapshot:", error);
2781
+ }
2782
+ }, 100);
2783
+ }
2784
+ return () => uninstallFiberTreeWalker();
2785
+ }
2786
+ function getNodeProps(nodeId) {
2787
+ const fiber = fiberRefMap.get(nodeId);
2788
+ if (!fiber || !fiber.memoizedProps) {
2789
+ return null;
2790
+ }
2791
+ try {
2792
+ return serializeProps(fiber.memoizedProps);
2793
+ } catch (error) {
2794
+ console.error(`[FloTrace] Error serializing props for node "${nodeId}":`, error);
2795
+ return null;
2796
+ }
2797
+ }
2798
+ function detectDetailedRenderReason(fiber) {
2799
+ if (!fiber.alternate) return { type: "mount" };
2800
+ const prev = fiber.alternate;
2801
+ if (shallowPropsChanged(prev.memoizedProps, fiber.memoizedProps)) {
2802
+ const changedProps = diffProps(prev.memoizedProps, fiber.memoizedProps);
2803
+ return { type: "props-changed", changedProps };
2804
+ }
2805
+ const changedHookIndices = diffHookStates(prev.memoizedState, fiber.memoizedState);
2806
+ if (changedHookIndices.length > 0) {
2807
+ return { type: "state-changed", changedHookIndices };
2808
+ }
2809
+ const changedContexts = detectContextChanges(fiber);
2810
+ if (changedContexts.length > 0) {
2811
+ return { type: "context-changed", contextNames: changedContexts };
2812
+ }
2813
+ const parentName = fiber.return ? getComponentName2(fiber.return) : void 0;
2814
+ return { type: "parent-render", parentName };
2815
+ }
2816
+ function diffProps(prev, next) {
2817
+ const changes = [];
2818
+ if (!prev || !next) return changes;
2819
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(next)]);
2820
+ for (const key of allKeys) {
2821
+ if (key === "children") continue;
2822
+ if (!Object.is(prev[key], next[key])) {
2823
+ changes.push({
2824
+ key,
2825
+ prev: serializeValue(prev[key], 0, /* @__PURE__ */ new WeakSet()),
2826
+ next: serializeValue(next[key], 0, /* @__PURE__ */ new WeakSet())
2827
+ });
2828
+ }
2829
+ }
2830
+ return changes;
2831
+ }
2832
+ function diffHookStates(prev, next) {
2833
+ const changed = [];
2834
+ let prevHook = prev;
2835
+ let nextHook = next;
2836
+ let index = 0;
2837
+ while (prevHook && nextHook) {
2838
+ if (prevHook.queue !== null || nextHook.queue !== null) {
2839
+ if (!Object.is(prevHook.memoizedState, nextHook.memoizedState)) {
2840
+ changed.push(index);
2841
+ }
2842
+ }
2843
+ prevHook = prevHook.next;
2844
+ nextHook = nextHook.next;
2845
+ index++;
2846
+ }
2847
+ return changed;
2848
+ }
2849
+ function detectContextChanges(fiber) {
2850
+ const changed = [];
2851
+ if (!fiber.dependencies?.firstContext) return changed;
2852
+ let ctx = fiber.dependencies.firstContext;
2853
+ while (ctx) {
2854
+ try {
2855
+ if (!Object.is(ctx.memoizedValue, ctx.context._currentValue)) {
2856
+ const name = ctx.context.displayName || "UnknownContext";
2857
+ changed.push(name);
2858
+ }
2859
+ } catch {
2860
+ }
2861
+ ctx = ctx.next;
2862
+ }
2863
+ return changed;
2864
+ }
2865
+ function getDetailedRenderReason(nodeId) {
2866
+ const fiber = fiberRefMap.get(nodeId);
2867
+ if (!fiber) return null;
2868
+ try {
2869
+ return detectDetailedRenderReason(fiber);
2870
+ } catch (error) {
2871
+ console.error(`[FloTrace] Error detecting render reason for "${nodeId}":`, error);
2872
+ return null;
2873
+ }
2874
+ }
2875
+ function getNodeHooks(nodeId) {
2876
+ const fiber = fiberRefMap.get(nodeId);
2877
+ if (!fiber) return null;
2878
+ try {
2879
+ return inspectHooks(fiber);
2880
+ } catch (error) {
2881
+ console.error(`[FloTrace] Error inspecting hooks for node "${nodeId}":`, error);
2882
+ return null;
2883
+ }
2884
+ }
2885
+ function getNodeEffects(nodeId) {
2886
+ const fiber = fiberRefMap.get(nodeId);
2887
+ if (!fiber) return null;
2888
+ try {
2889
+ return inspectEffects(fiber);
2890
+ } catch (error) {
2891
+ console.error(`[FloTrace] Error inspecting effects for node "${nodeId}":`, error);
2892
+ return null;
2893
+ }
2894
+ }
2895
+ function getFiberRefMap() {
2896
+ return fiberRefMap;
2897
+ }
2898
+ function uninstallFiberTreeWalker() {
2899
+ if (!isInstalled3) return;
2900
+ if (throttleTimer) {
2901
+ clearTimeout(throttleTimer);
2902
+ throttleTimer = null;
2903
+ }
2904
+ if (maxWaitTimer) {
2905
+ clearTimeout(maxWaitTimer);
2906
+ maxWaitTimer = null;
2907
+ }
2908
+ cachedFiberRoot = null;
2909
+ if (activeStrategy === "devtools") {
2910
+ const hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
2911
+ if (hook) {
2912
+ if (originalOnCommitFiberRoot) {
2913
+ hook.onCommitFiberRoot = originalOnCommitFiberRoot;
2914
+ } else {
2915
+ delete hook.onCommitFiberRoot;
2916
+ }
2917
+ }
2918
+ }
2919
+ originalOnCommitFiberRoot = null;
2920
+ hookedRendererID = null;
2921
+ activeStrategy = null;
2922
+ fiberRefMap = /* @__PURE__ */ new Map();
2923
+ previousFlatTree = null;
2924
+ snapshotCounter = 0;
2925
+ diffSeq = 0;
2926
+ lastSnapshotSentTime = 0;
2927
+ isInstalled3 = false;
2928
+ walkerOptions = {};
2929
+ walkerFilterConfig = defaultFilterConfig();
2930
+ try {
2931
+ uninstallRscPayloadInterceptor();
2932
+ } catch {
2933
+ }
2934
+ clearActionStateCache();
2935
+ resetNextjsDetection();
2936
+ console.log("[FloTrace] Fiber tree walker uninstalled");
2937
+ }
2938
+
2939
+ // src/fiberAttribution.ts
2940
+ function isFiberLike(val) {
2941
+ if (!val || typeof val !== "object") return false;
2942
+ const obj = val;
2943
+ return typeof obj.tag === "number" && "type" in obj && "return" in obj && ("memoizedState" in obj || "stateNode" in obj);
2944
+ }
2945
+ function getCurrentRenderingFiber() {
2946
+ try {
2947
+ const win = window;
2948
+ const secret = win.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
2949
+ if (secret?.ReactCurrentOwner?.current) return secret.ReactCurrentOwner.current;
2950
+ const client2 = win.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
2951
+ if (client2) {
2952
+ for (const val of Object.values(client2)) {
2953
+ if (isFiberLike(val)) return val;
2954
+ }
2955
+ }
2956
+ return null;
2957
+ } catch {
2958
+ return null;
2959
+ }
2960
+ }
2961
+ function getComponentNameFromFiber(fiber) {
2962
+ const type = fiber.type;
2963
+ if (!type) return null;
2964
+ if (typeof type === "function") {
2965
+ return type.displayName || type.name || null;
2966
+ }
2967
+ if (typeof type === "object" && type !== null) {
2968
+ if (type.type) {
2969
+ return type.type.displayName || type.type.name || null;
2970
+ }
2971
+ if (type.render) {
2972
+ return type.render.displayName || type.render.name || null;
2973
+ }
2974
+ return type.displayName || type.name || null;
2975
+ }
2976
+ return null;
2977
+ }
2978
+ function buildAncestorChain(fiber) {
2979
+ const chain = [];
2980
+ let current = fiber;
2981
+ const maxDepth = 10;
2982
+ while (current && chain.length < maxDepth) {
2983
+ const name = getComponentNameFromFiber(current);
2984
+ if (name) {
2985
+ chain.unshift(name);
2986
+ }
2987
+ current = current.return;
2988
+ }
2989
+ return chain;
2990
+ }
2991
+
2992
+ // src/storeUtils.ts
2993
+ function serializeStoreState(state, logPrefix) {
2994
+ const serialized = {};
2995
+ for (const [key, value] of Object.entries(state)) {
2996
+ try {
2997
+ serialized[key] = serializeValue(value);
2998
+ } catch (error) {
2999
+ console.error(`[FloTrace] Error serializing ${logPrefix} key "${key}":`, error);
3000
+ serialized[key] = { __type: "error", value: "Serialization failed" };
3001
+ }
3002
+ }
3003
+ return serialized;
3004
+ }
3005
+ function buildCorrelatedRequests(state, changedKeys) {
3006
+ const byRequestId = /* @__PURE__ */ new Map();
3007
+ for (const key of changedKeys) {
3008
+ try {
3009
+ const rid = findFetchOrigin(state[key]);
3010
+ if (rid) {
3011
+ const keys = byRequestId.get(rid) ?? [];
3012
+ keys.push(key);
3013
+ byRequestId.set(rid, keys);
3014
+ }
3015
+ } catch {
3016
+ }
3017
+ }
3018
+ if (byRequestId.size === 0) return void 0;
3019
+ return Array.from(byRequestId, ([requestId, storeKeys]) => ({ requestId, storeKeys }));
3020
+ }
3021
+
3022
+ // src/zustandTracker.ts
3023
+ var activeUnsubscribers = [];
3024
+ var isInstalled4 = false;
3025
+ var debounceTimers = /* @__PURE__ */ new Map();
3026
+ var DEBOUNCE_MS = 200;
3027
+ function installZustandTracker(stores, client2) {
3028
+ if (isInstalled4) {
3029
+ console.warn("[FloTrace] Zustand tracker already installed, reinstalling");
3030
+ uninstallZustandTracker();
3031
+ }
3032
+ isInstalled4 = true;
3033
+ console.log("[FloTrace] Installing Zustand tracker for stores:", Object.keys(stores));
3034
+ for (const [storeName, store] of Object.entries(stores)) {
3035
+ if (!store || typeof store !== "object" && typeof store !== "function" || typeof store.getState !== "function" || typeof store.subscribe !== "function") {
3036
+ console.warn(
3037
+ `[FloTrace] Skipping "${storeName}" \u2014 not a valid Zustand store (missing getState/subscribe). Ensure you pass Zustand stores like: stores={{ myStore: useMyStore }}`
3038
+ );
3039
+ continue;
3040
+ }
3041
+ try {
3042
+ const initialState = store.getState();
3043
+ sendStoreUpdate(storeName, initialState, Object.keys(initialState), client2);
3044
+ const unsubscribe = store.subscribe((newState, prevState) => {
3045
+ try {
3046
+ scheduleStoreUpdate(storeName, prevState, newState, client2);
3047
+ } catch (error) {
3048
+ console.error(`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`, error);
3049
+ }
3050
+ });
3051
+ activeUnsubscribers.push(unsubscribe);
3052
+ } catch (error) {
3053
+ console.error(`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`, error);
3054
+ }
3055
+ }
3056
+ }
3057
+ function uninstallZustandTracker() {
3058
+ if (!isInstalled4) return;
3059
+ for (const timer of debounceTimers.values()) {
3060
+ clearTimeout(timer);
3061
+ }
3062
+ debounceTimers.clear();
3063
+ for (const unsubscribe of activeUnsubscribers) {
3064
+ try {
3065
+ unsubscribe();
3066
+ } catch (error) {
3067
+ console.error("[FloTrace] Error unsubscribing from Zustand store:", error);
3068
+ }
3069
+ }
3070
+ activeUnsubscribers = [];
3071
+ isInstalled4 = false;
3072
+ console.log("[FloTrace] Zustand tracker uninstalled");
3073
+ }
3074
+ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
3075
+ let changedKeys;
3076
+ try {
3077
+ changedKeys = getChangedKeys(prevState, newState);
3078
+ } catch (error) {
3079
+ console.error(`[FloTrace] Error diffing Zustand state for "${storeName}":`, error);
3080
+ return;
3081
+ }
3082
+ if (changedKeys.length === 0) return;
3083
+ const existing = debounceTimers.get(storeName);
3084
+ if (existing) clearTimeout(existing);
3085
+ debounceTimers.set(storeName, setTimeout(() => {
3086
+ debounceTimers.delete(storeName);
3087
+ sendStoreUpdate(storeName, newState, changedKeys, client2);
3088
+ }, DEBOUNCE_MS));
3089
+ }
3090
+ function sendStoreUpdate(storeName, state, changedKeys, client2) {
3091
+ try {
3092
+ if (!client2.connected) return;
3093
+ client2.sendImmediate({
3094
+ type: "runtime:zustand",
3095
+ storeName,
3096
+ state: serializeStoreState(state, `Zustand "${storeName}"`),
3097
+ changedKeys,
3098
+ correlatedRequests: buildCorrelatedRequests(state, changedKeys),
3099
+ timestamp: Date.now()
3100
+ });
3101
+ } catch (error) {
3102
+ console.error(`[FloTrace] Error sending Zustand update for "${storeName}":`, error);
3103
+ }
3104
+ }
3105
+
3106
+ // src/reduxTracker.ts
3107
+ var activeUnsubscribe = null;
3108
+ var isInstalled5 = false;
3109
+ var debounceTimer = null;
3110
+ var previousState = null;
3111
+ var DEBOUNCE_MS2 = 200;
3112
+ function isReduxStore(obj) {
3113
+ return typeof obj === "object" && obj !== null && typeof obj.getState === "function" && typeof obj.subscribe === "function" && typeof obj.dispatch === "function";
3114
+ }
3115
+ function installReduxTracker(store, client2) {
3116
+ if (isInstalled5) {
3117
+ console.warn("[FloTrace] Redux tracker already installed, reinstalling");
3118
+ uninstallReduxTracker();
3119
+ }
3120
+ isInstalled5 = true;
3121
+ console.log("[FloTrace] Installing Redux tracker");
3122
+ try {
3123
+ const initialState = store.getState();
3124
+ previousState = initialState;
3125
+ sendReduxUpdate(initialState, Object.keys(initialState), client2);
3126
+ activeUnsubscribe = store.subscribe(() => {
3127
+ try {
3128
+ const newState = store.getState();
3129
+ scheduleReduxUpdate(newState, client2);
3130
+ } catch (error) {
3131
+ console.error("[FloTrace] Error in Redux subscribe callback:", error);
3132
+ }
3133
+ });
3134
+ } catch (error) {
3135
+ console.error("[FloTrace] Failed to install Redux tracker:", error);
3136
+ isInstalled5 = false;
3137
+ }
3138
+ }
3139
+ function uninstallReduxTracker() {
3140
+ if (!isInstalled5) return;
3141
+ if (debounceTimer) {
3142
+ clearTimeout(debounceTimer);
3143
+ debounceTimer = null;
3144
+ }
3145
+ if (activeUnsubscribe) {
3146
+ try {
3147
+ activeUnsubscribe();
3148
+ } catch (error) {
3149
+ console.error("[FloTrace] Error unsubscribing from Redux store:", error);
3150
+ }
3151
+ activeUnsubscribe = null;
3152
+ }
3153
+ previousState = null;
3154
+ isInstalled5 = false;
3155
+ console.log("[FloTrace] Redux tracker uninstalled");
3156
+ }
3157
+ function scheduleReduxUpdate(newState, client2) {
3158
+ let changedKeys;
3159
+ try {
3160
+ changedKeys = getChangedKeys(previousState ?? {}, newState);
3161
+ } catch (error) {
3162
+ console.error("[FloTrace] Error diffing Redux state:", error);
3163
+ return;
3164
+ }
3165
+ if (changedKeys.length === 0) return;
3166
+ previousState = newState;
3167
+ if (debounceTimer) clearTimeout(debounceTimer);
3168
+ debounceTimer = setTimeout(() => {
3169
+ debounceTimer = null;
3170
+ sendReduxUpdate(newState, changedKeys, client2);
3171
+ }, DEBOUNCE_MS2);
3172
+ }
3173
+ function sendReduxUpdate(state, changedKeys, client2) {
3174
+ try {
3175
+ if (!client2.connected) return;
3176
+ client2.sendImmediate({
3177
+ type: "runtime:redux",
3178
+ state: serializeStoreState(state, "Redux"),
3179
+ changedKeys,
3180
+ correlatedRequests: buildCorrelatedRequests(state, changedKeys),
3181
+ timestamp: Date.now()
3182
+ });
3183
+ } catch (error) {
3184
+ console.error("[FloTrace] Error sending Redux update:", error);
3185
+ }
3186
+ }
3187
+
3188
+ // src/tanstackQueryTracker.ts
3189
+ var isInstalled6 = false;
3190
+ var queryUnsubscribe = null;
3191
+ var mutationUnsubscribe = null;
3192
+ var debounceTimer2 = null;
3193
+ var DEBOUNCE_MS3 = 300;
3194
+ var MAX_EVENTS_PER_QUERY = 50;
3195
+ var queryTracking = /* @__PURE__ */ new Map();
3196
+ var CORRELATION_WINDOW_MS = 500;
3197
+ var MAX_COMPLETED_CORRELATIONS = 20;
3198
+ var correlationCounter = 0;
3199
+ var pendingCorrelations = /* @__PURE__ */ new Map();
3200
+ var completedCorrelations = [];
3201
+ var mutationPrevStatus = /* @__PURE__ */ new Map();
3202
+ var mutationCorrelationMap = /* @__PURE__ */ new Map();
3203
+ function isTanStackQueryClient(obj) {
3204
+ if (!obj || typeof obj !== "object") return false;
3205
+ const candidate = obj;
3206
+ return typeof candidate.getQueryCache === "function" && typeof candidate.getMutationCache === "function";
3207
+ }
3208
+ function installTanStackQueryTracker(queryClient, client2) {
3209
+ if (isInstalled6) {
3210
+ console.warn("[FloTrace] TanStack Query tracker already installed, reinstalling");
3211
+ uninstallTanStackQueryTracker();
3212
+ }
3213
+ isInstalled6 = true;
3214
+ console.log("[FloTrace] Installing TanStack Query tracker");
3215
+ try {
3216
+ const queryCache = queryClient.getQueryCache();
3217
+ const mutationCache = queryClient.getMutationCache();
3218
+ for (const query of queryCache.getAll()) {
3219
+ if (!queryTracking.has(query.queryHash)) {
3220
+ initQueryTracking(query);
3221
+ }
3222
+ }
3223
+ for (const mutation of mutationCache.getAll()) {
3224
+ mutationPrevStatus.set(mutation.mutationId, mutation.state.status);
3225
+ }
3226
+ sendSnapshot(queryCache, mutationCache, client2);
3227
+ queryUnsubscribe = queryCache.subscribe((event) => {
3228
+ try {
3229
+ if (event.type === "added" || event.type === "removed" || event.type === "updated") {
3230
+ if (event.query) {
3231
+ updateQueryTracking(event.query, event.type);
3232
+ }
3233
+ scheduleSnapshot2(queryCache, mutationCache, client2);
3234
+ }
3235
+ } catch (error) {
3236
+ console.error("[FloTrace] Error in TanStack Query cache subscribe callback:", error);
3237
+ }
3238
+ });
3239
+ mutationUnsubscribe = mutationCache.subscribe((event) => {
3240
+ try {
3241
+ if (event.mutation) {
3242
+ updateMutationTracking(event.mutation, queryCache, mutationCache, client2);
3243
+ }
3244
+ scheduleSnapshot2(queryCache, mutationCache, client2);
3245
+ } catch (error) {
3246
+ console.error("[FloTrace] Error in TanStack Mutation cache subscribe callback:", error);
3247
+ }
3248
+ });
3249
+ } catch (error) {
3250
+ console.error("[FloTrace] Failed to install TanStack Query tracker:", error);
3251
+ isInstalled6 = false;
3252
+ }
3253
+ }
3254
+ function uninstallTanStackQueryTracker() {
3255
+ if (!isInstalled6) return;
3256
+ if (debounceTimer2) {
3257
+ clearTimeout(debounceTimer2);
3258
+ debounceTimer2 = null;
3259
+ }
3260
+ if (queryUnsubscribe) {
3261
+ try {
3262
+ queryUnsubscribe();
3263
+ } catch (e) {
3264
+ console.error("[FloTrace] Error unsubscribing from QueryCache:", e);
3265
+ }
3266
+ queryUnsubscribe = null;
3267
+ }
3268
+ if (mutationUnsubscribe) {
3269
+ try {
3270
+ mutationUnsubscribe();
3271
+ } catch (e) {
3272
+ console.error("[FloTrace] Error unsubscribing from MutationCache:", e);
3273
+ }
3274
+ mutationUnsubscribe = null;
3275
+ }
3276
+ for (const pending of pendingCorrelations.values()) {
3277
+ clearTimeout(pending.timeoutId);
3278
+ }
3279
+ pendingCorrelations.clear();
3280
+ isInstalled6 = false;
3281
+ console.log("[FloTrace] TanStack Query tracker uninstalled");
3282
+ }
3283
+ function computeDataHash(data) {
3284
+ if (data === null || data === void 0) return "__null__";
3285
+ try {
3286
+ return JSON.stringify(data);
3287
+ } catch {
3288
+ return "__unhashable__";
3289
+ }
3290
+ }
3291
+ function initQueryTracking(query) {
3292
+ const state = {
3293
+ lastDataHash: computeDataHash(query.state.data),
3294
+ lastDataUpdatedAt: query.state.dataUpdatedAt,
3295
+ prevStatus: query.state.status,
3296
+ prevFetchStatus: query.state.fetchStatus,
3297
+ totalFetchCount: 0,
3298
+ wastedRefetchCount: 0,
3299
+ events: []
3300
+ };
3301
+ queryTracking.set(query.queryHash, state);
3302
+ return state;
3303
+ }
3304
+ function updateQueryTracking(query, eventType) {
3305
+ let tracking = queryTracking.get(query.queryHash);
3306
+ if (eventType === "removed") {
3307
+ queryTracking.delete(query.queryHash);
3308
+ return;
3309
+ }
3310
+ if (!tracking) {
3311
+ tracking = initQueryTracking(query);
3312
+ }
3313
+ const currentStatus = query.state.status;
3314
+ const currentFetchStatus = query.state.fetchStatus;
3315
+ const statusChanged = tracking.prevStatus !== currentStatus;
3316
+ const fetchStatusChanged = tracking.prevFetchStatus !== currentFetchStatus;
3317
+ if (statusChanged || fetchStatusChanged) {
3318
+ const currentDataHash = computeDataHash(query.state.data);
3319
+ const dataChanged = currentDataHash !== tracking.lastDataHash;
3320
+ const event = {
3321
+ timestamp: Date.now(),
3322
+ fromStatus: tracking.prevStatus,
3323
+ toStatus: currentStatus,
3324
+ fromFetchStatus: tracking.prevFetchStatus,
3325
+ toFetchStatus: currentFetchStatus,
3326
+ dataChanged
3327
+ };
3328
+ tracking.events.push(event);
3329
+ if (tracking.events.length > MAX_EVENTS_PER_QUERY) {
3330
+ tracking.events.shift();
3331
+ }
3332
+ if (tracking.prevFetchStatus === "fetching" && currentFetchStatus === "idle" && currentStatus === "success") {
3333
+ tracking.totalFetchCount++;
3334
+ if (!dataChanged) {
3335
+ tracking.wastedRefetchCount++;
3336
+ }
3337
+ tracking.lastDataHash = currentDataHash;
3338
+ tracking.lastDataUpdatedAt = query.state.dataUpdatedAt;
3339
+ if (query.state.data !== null && query.state.data !== void 0) {
3340
+ const rid = findFetchOrigin(query.state.data);
3341
+ if (rid) tracking.pendingCorrelationId = rid;
3342
+ }
3343
+ }
3344
+ if (tracking.prevFetchStatus === "idle" && currentFetchStatus === "fetching") {
3345
+ const now = Date.now();
3346
+ for (const pending of pendingCorrelations.values()) {
3347
+ if (pending.idleQueryHashes.has(query.queryHash)) {
3348
+ pending.affectedQueries.set(query.queryHash, {
3349
+ fetchStartedAt: now,
3350
+ queryKey: query.queryKey
3351
+ });
3352
+ }
3353
+ }
3354
+ }
3355
+ tracking.prevStatus = currentStatus;
3356
+ tracking.prevFetchStatus = currentFetchStatus;
3357
+ }
3358
+ }
3359
+ function openCorrelationWindow(mutation, queryCache, mutationCache, client2) {
3360
+ const correlationId = `corr-${++correlationCounter}`;
3361
+ const now = Date.now();
3362
+ const idleQueryHashes = /* @__PURE__ */ new Set();
3363
+ for (const query of queryCache.getAll()) {
3364
+ if (query.state.fetchStatus === "idle") {
3365
+ idleQueryHashes.add(query.queryHash);
3366
+ }
3367
+ }
3368
+ const timeoutId = setTimeout(() => {
3369
+ resolveCorrelation(correlationId, queryCache, mutationCache, client2);
3370
+ }, CORRELATION_WINDOW_MS);
3371
+ pendingCorrelations.set(correlationId, {
3372
+ correlationId,
3373
+ mutationId: mutation.mutationId,
3374
+ mutationKey: mutation.options.mutationKey,
3375
+ completedAt: now,
3376
+ idleQueryHashes,
3377
+ affectedQueries: /* @__PURE__ */ new Map(),
3378
+ timeoutId
3379
+ });
3380
+ mutationCorrelationMap.set(mutation.mutationId, correlationId);
3381
+ }
3382
+ function resolveCorrelation(correlationId, queryCache, mutationCache, client2) {
3383
+ const pending = pendingCorrelations.get(correlationId);
3384
+ if (!pending) return;
3385
+ pendingCorrelations.delete(correlationId);
3386
+ if (pending.affectedQueries.size === 0) return;
3387
+ const affectedQueries = [];
3388
+ for (const [queryHash, info] of pending.affectedQueries) {
3389
+ const tracking = queryTracking.get(queryHash);
3390
+ let queryKeySerialized;
3391
+ try {
3392
+ queryKeySerialized = serializeValue(info.queryKey);
3393
+ } catch {
3394
+ queryKeySerialized = "[serialization failed]";
3395
+ }
3396
+ affectedQueries.push({
3397
+ queryHash,
3398
+ queryKey: queryKeySerialized,
3399
+ fetchStartedAt: info.fetchStartedAt,
3400
+ latencyMs: info.fetchStartedAt - pending.completedAt,
3401
+ // dataChanged is resolved from the latest tracking state if the fetch completed
3402
+ dataChanged: tracking?.events.length ? tracking.events[tracking.events.length - 1].dataChanged : void 0
3403
+ });
3404
+ }
3405
+ let mutationKeySerialized;
3406
+ if (pending.mutationKey) {
3407
+ try {
3408
+ mutationKeySerialized = serializeValue(pending.mutationKey);
3409
+ } catch {
3410
+ mutationKeySerialized = "[serialization failed]";
3411
+ }
3412
+ }
3413
+ const correlation = {
3414
+ correlationId,
3415
+ mutationId: pending.mutationId,
3416
+ mutationKey: mutationKeySerialized,
3417
+ mutationCompletedAt: pending.completedAt,
3418
+ affectedQueries,
3419
+ resolvedAt: Date.now()
3420
+ };
3421
+ completedCorrelations.push(correlation);
3422
+ if (completedCorrelations.length > MAX_COMPLETED_CORRELATIONS) {
3423
+ completedCorrelations = completedCorrelations.slice(-MAX_COMPLETED_CORRELATIONS);
3424
+ }
3425
+ scheduleSnapshot2(queryCache, mutationCache, client2);
3426
+ }
3427
+ function updateMutationTracking(mutation, queryCache, mutationCache, client2) {
3428
+ const currentStatus = mutation.state.status;
3429
+ const prevStatus = mutationPrevStatus.get(mutation.mutationId);
3430
+ mutationPrevStatus.set(mutation.mutationId, currentStatus);
3431
+ if (prevStatus && prevStatus !== "success" && currentStatus === "success") {
3432
+ openCorrelationWindow(mutation, queryCache, mutationCache, client2);
3433
+ }
3434
+ }
3435
+ function scheduleSnapshot2(queryCache, mutationCache, client2) {
3436
+ if (debounceTimer2) clearTimeout(debounceTimer2);
3437
+ debounceTimer2 = setTimeout(() => {
3438
+ debounceTimer2 = null;
3439
+ sendSnapshot(queryCache, mutationCache, client2);
3440
+ }, DEBOUNCE_MS3);
3441
+ }
3442
+ function serializeQueryData(data) {
3443
+ if (data === null || data === void 0) return null;
3444
+ try {
3445
+ return serializeValue(data);
3446
+ } catch {
3447
+ return { __type: "truncated", originalType: typeof data };
3448
+ }
3449
+ }
3450
+ function extractErrorMessage(error) {
3451
+ try {
3452
+ return error instanceof Error ? error.message : String(error);
3453
+ } catch {
3454
+ return "Unknown error";
3455
+ }
3456
+ }
3457
+ function serializeQuery(query) {
3458
+ let queryKeySerialized;
3459
+ try {
3460
+ queryKeySerialized = serializeValue(query.queryKey);
3461
+ } catch {
3462
+ queryKeySerialized = "[serialization failed]";
3463
+ }
3464
+ const errorMessage = query.state.error ? extractErrorMessage(query.state.error) : void 0;
3465
+ const tracking = queryTracking.get(query.queryHash);
3466
+ const correlatedRequestId = tracking?.pendingCorrelationId;
3467
+ if (correlatedRequestId && tracking) {
3468
+ tracking.pendingCorrelationId = void 0;
3469
+ }
3470
+ return {
3471
+ queryKey: queryKeySerialized,
3472
+ queryHash: query.queryHash,
3473
+ status: query.state.status,
3474
+ fetchStatus: query.state.fetchStatus,
3475
+ dataUpdatedAt: query.state.dataUpdatedAt,
3476
+ errorUpdatedAt: query.state.errorUpdatedAt,
3477
+ isInvalidated: query.state.isInvalidated,
3478
+ isStale: safeCall(() => query.isStale(), false),
3479
+ isActive: safeCall(() => query.isActive(), false),
3480
+ isDisabled: safeCall(() => query.isDisabled(), false),
3481
+ failureCount: query.state.fetchFailureCount,
3482
+ errorMessage,
3483
+ observerCount: safeCall(() => query.getObserversCount(), 0),
3484
+ staleTime: query.options.staleTime,
3485
+ gcTime: query.options.gcTime,
3486
+ // Phase 1: additional config for health analysis
3487
+ refetchInterval: query.options.refetchInterval,
3488
+ refetchOnWindowFocus: query.options.refetchOnWindowFocus,
3489
+ refetchOnMount: query.options.refetchOnMount,
3490
+ refetchOnReconnect: query.options.refetchOnReconnect,
3491
+ networkMode: query.options.networkMode,
3492
+ enabled: query.options.enabled,
3493
+ retry: query.options.retry,
3494
+ dataShape: serializeQueryData(query.state.data),
3495
+ // Phase 2: wasted refetch tracking
3496
+ wastedRefetchCount: tracking?.wastedRefetchCount,
3497
+ totalFetchCount: tracking?.totalFetchCount,
3498
+ // Phase 3: query timeline
3499
+ events: tracking?.events.length ? [...tracking.events] : void 0,
3500
+ correlatedRequestId
3501
+ };
3502
+ }
3503
+ function serializeMutation(mutation) {
3504
+ const errorMessage = mutation.state.error ? extractErrorMessage(mutation.state.error) : void 0;
3505
+ let mutationKey;
3506
+ if (mutation.options.mutationKey) {
3507
+ try {
3508
+ mutationKey = serializeValue(mutation.options.mutationKey);
3509
+ } catch {
3510
+ mutationKey = "[serialization failed]";
3511
+ }
3512
+ }
3513
+ return {
3514
+ mutationId: mutation.mutationId,
3515
+ status: mutation.state.status,
3516
+ isPaused: mutation.state.isPaused,
3517
+ submittedAt: mutation.state.submittedAt,
3518
+ failureCount: mutation.state.failureCount,
3519
+ errorMessage,
3520
+ mutationKey,
3521
+ scope: mutation.options.scope?.id,
3522
+ lastCorrelationId: mutationCorrelationMap.get(mutation.mutationId)
3523
+ };
3524
+ }
3525
+ function sendSnapshot(queryCache, mutationCache, client2) {
3526
+ try {
3527
+ if (!client2.connected) return;
3528
+ const queries = [];
3529
+ for (const query of queryCache.getAll()) {
3530
+ try {
3531
+ queries.push(serializeQuery(query));
3532
+ } catch (error) {
3533
+ console.error(`[FloTrace] Error serializing query "${query.queryHash}":`, error);
3534
+ }
3535
+ }
3536
+ const mutations = [];
3537
+ const activeMutationIds = /* @__PURE__ */ new Set();
3538
+ for (const mutation of mutationCache.getAll()) {
3539
+ try {
3540
+ activeMutationIds.add(mutation.mutationId);
3541
+ mutations.push(serializeMutation(mutation));
3542
+ } catch (error) {
3543
+ console.error(`[FloTrace] Error serializing mutation ${mutation.mutationId}:`, error);
3544
+ }
3545
+ }
3546
+ for (const id of mutationPrevStatus.keys()) {
3547
+ if (!activeMutationIds.has(id)) {
3548
+ mutationPrevStatus.delete(id);
3549
+ mutationCorrelationMap.delete(id);
3550
+ }
3551
+ }
3552
+ const correlations = completedCorrelations.length > 0 ? [...completedCorrelations] : void 0;
3553
+ if (correlations) {
3554
+ completedCorrelations = [];
3555
+ }
3556
+ client2.sendImmediate({
3557
+ type: "runtime:tanstackQuery",
3558
+ queries,
3559
+ mutations,
3560
+ correlations,
3561
+ timestamp: Date.now()
3562
+ });
3563
+ } catch (error) {
3564
+ console.error("[FloTrace] Error sending TanStack Query snapshot:", error);
3565
+ }
3566
+ }
3567
+ function safeCall(fn, fallback) {
3568
+ try {
3569
+ return fn();
3570
+ } catch {
3571
+ return fallback;
3572
+ }
3573
+ }
3574
+ export {
3575
+ DEFAULT_CONFIG,
3576
+ FloTraceWebSocketClient,
3577
+ buildAncestorChain,
3578
+ clearFetchOriginTags,
3579
+ detectServerComponent,
3580
+ disposeWebSocketClient,
3581
+ findFetchOrigin,
3582
+ getChangedKeys,
3583
+ getComponentNameFromFiber,
3584
+ getCurrentRenderingFiber,
3585
+ getDetailedRenderReason,
3586
+ getFiberRefMap,
3587
+ getNodeEffects,
3588
+ getNodeHooks,
3589
+ getNodeProps,
3590
+ getTimeline,
3591
+ getWebSocketClient,
3592
+ hasActiveTags,
3593
+ inspectEffects,
3594
+ inspectHooks,
3595
+ installFiberTreeWalker,
3596
+ installReduxTracker,
3597
+ installRscPayloadInterceptor,
3598
+ installTanStackQueryTracker,
3599
+ installTimelineTracker,
3600
+ installZustandTracker,
3601
+ isReduxStore,
3602
+ isTanStackQueryClient,
3603
+ maybeEmitNextjsContext,
3604
+ recordTimelineEvent,
3605
+ requestFullSnapshot,
3606
+ requestTreeSnapshot,
3607
+ resetNextjsDetection,
3608
+ serializeProps,
3609
+ serializeValue,
3610
+ tagFetchData,
3611
+ uninstallFiberTreeWalker,
3612
+ uninstallReduxTracker,
3613
+ uninstallRscPayloadInterceptor,
3614
+ uninstallTanStackQueryTracker,
3615
+ uninstallTimelineTracker,
3616
+ uninstallZustandTracker
3617
+ };