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