@flotrace/runtime-core 2.2.4 → 2.3.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
@@ -31,13 +31,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DEFAULT_CONFIG: () => DEFAULT_CONFIG,
34
+ FLOTRACE_SOURCE: () => FLOTRACE_SOURCE,
34
35
  FloTraceWebSocketClient: () => FloTraceWebSocketClient,
36
+ JSX_RUNTIME_ACTIVE_KEY: () => JSX_RUNTIME_ACTIVE_KEY,
35
37
  buildAncestorChain: () => buildAncestorChain,
38
+ clearCallSiteRenders: () => clearCallSiteRenders,
36
39
  clearFetchOriginTags: () => clearFetchOriginTags,
40
+ computeCallSiteId: () => computeCallSiteId,
41
+ computeCallSiteMetricsPayload: () => computeCallSiteMetricsPayload,
42
+ describeFiberType: () => describeFiberType,
43
+ detectInlineLiterals: () => detectInlineLiterals,
37
44
  detectServerComponent: () => detectServerComponent,
38
45
  detectWebFramework: () => detectWebFramework,
39
46
  disposeWebSocketClient: () => disposeWebSocketClient,
40
47
  findFetchOrigin: () => findFetchOrigin,
48
+ getCallSiteRenderRate: () => getCallSiteRenderRate,
49
+ getCallSiteRenders: () => getCallSiteRenders,
41
50
  getChangedKeys: () => getChangedKeys,
42
51
  getComponentNameFromFiber: () => getComponentNameFromFiber,
43
52
  getCurrentRenderingFiber: () => getCurrentRenderingFiber,
@@ -60,9 +69,15 @@ __export(index_exports, {
60
69
  installTanStackQueryTracker: () => installTanStackQueryTracker,
61
70
  installTimelineTracker: () => installTimelineTracker,
62
71
  installZustandTracker: () => installZustandTracker,
72
+ isJsxRuntimeActive: () => isJsxRuntimeActive,
63
73
  isReduxStore: () => isReduxStore,
64
74
  isTanStackQueryClient: () => isTanStackQueryClient,
75
+ logTreeSnapshot: () => logTreeSnapshot,
76
+ logTreeSummary: () => logTreeSummary,
77
+ markJsxRuntimeActive: () => markJsxRuntimeActive,
65
78
  maybeEmitNextjsContext: () => maybeEmitNextjsContext,
79
+ normalizeJsxSourcePath: () => normalizeJsxSourcePath,
80
+ recordCallSiteRender: () => recordCallSiteRender,
66
81
  recordTimelineEvent: () => recordTimelineEvent,
67
82
  requestFullSnapshot: () => requestFullSnapshot,
68
83
  requestTreeSnapshot: () => requestTreeSnapshot,
@@ -70,6 +85,8 @@ __export(index_exports, {
70
85
  resolveValueTrace: () => resolveValueTrace,
71
86
  serializeProps: () => serializeProps,
72
87
  serializeValue: () => serializeValue,
88
+ setDuplicateKeyEmitter: () => setDuplicateKeyEmitter,
89
+ setFiberDebug: () => setFiberDebug,
73
90
  tagFetchData: () => tagFetchData,
74
91
  uninstallFiberTreeWalker: () => uninstallFiberTreeWalker,
75
92
  uninstallReduxTracker: () => uninstallReduxTracker,
@@ -103,6 +120,130 @@ var DEFAULT_CONFIG = {
103
120
  getAppUrl: void 0
104
121
  };
105
122
 
123
+ // src/jsxRuntimeUtils.ts
124
+ var FLOTRACE_SOURCE = /* @__PURE__ */ Symbol.for("flotrace.source");
125
+ var JSX_RUNTIME_ACTIVE_KEY = /* @__PURE__ */ Symbol.for("flotrace.jsx-runtime-active");
126
+ function normalizeJsxSourcePath(fileName) {
127
+ let p = fileName;
128
+ if (p.startsWith("file://")) p = p.slice("file://".length);
129
+ if (p.startsWith("webpack-internal:///./")) p = p.slice("webpack-internal:///./".length);
130
+ if (p.startsWith("[project]/")) p = p.slice("[project]/".length);
131
+ if (p.startsWith("./")) p = p.slice(2);
132
+ if (/^\/[a-zA-Z]:[\\/]/.test(p)) p = p.slice(1);
133
+ if (/^[a-zA-Z]:[\\/]/.test(p)) p = p[0].toLowerCase() + p.slice(1);
134
+ return p;
135
+ }
136
+ function computeCallSiteId(source) {
137
+ const normPath = normalizeJsxSourcePath(source.fileName);
138
+ const key = `${normPath}:${source.lineNumber}:${source.columnNumber}`;
139
+ let hash = 2166136261;
140
+ for (let i = 0; i < key.length; i++) {
141
+ hash ^= key.charCodeAt(i);
142
+ hash = Math.imul(hash, 16777619);
143
+ }
144
+ return (hash >>> 0).toString(16).padStart(8, "0");
145
+ }
146
+ function readJsxSourceFromFiber(fiber) {
147
+ const props = fiber.memoizedProps;
148
+ if (!props) return void 0;
149
+ const raw = props[FLOTRACE_SOURCE];
150
+ if (!raw || typeof raw !== "object") return void 0;
151
+ const obj = raw;
152
+ if (typeof obj.fileName !== "string" || typeof obj.lineNumber !== "number" || typeof obj.columnNumber !== "number" || typeof obj.callSiteId !== "string") {
153
+ return void 0;
154
+ }
155
+ return raw;
156
+ }
157
+ var callSiteRenders = /* @__PURE__ */ new Map();
158
+ var RING_BUFFER_MAX = 60;
159
+ function recordCallSiteRender(callSiteId, now2 = performance.now()) {
160
+ const arr = callSiteRenders.get(callSiteId);
161
+ if (arr === void 0) {
162
+ callSiteRenders.set(callSiteId, [now2]);
163
+ return;
164
+ }
165
+ arr.push(now2);
166
+ if (arr.length > RING_BUFFER_MAX) arr.shift();
167
+ }
168
+ function getCallSiteRenders(callSiteId) {
169
+ return callSiteRenders.get(callSiteId) ?? [];
170
+ }
171
+ function getCallSiteRenderRate(callSiteId, windowMs = 5e3, now2 = performance.now()) {
172
+ const arr = callSiteRenders.get(callSiteId);
173
+ if (!arr || arr.length === 0) return 0;
174
+ const cutoff = now2 - windowMs;
175
+ let count = 0;
176
+ for (let i = arr.length - 1; i >= 0; i--) {
177
+ if (arr[i] >= cutoff) count++;
178
+ else break;
179
+ }
180
+ return count / windowMs * 1e3;
181
+ }
182
+ function clearCallSiteRenders() {
183
+ callSiteRenders.clear();
184
+ }
185
+ function computeCallSiteMetricsPayload(windowMs = 5e3, now2 = performance.now()) {
186
+ let out = null;
187
+ const cutoff = now2 - windowMs;
188
+ for (const [callSiteId, arr] of callSiteRenders) {
189
+ if (arr.length === 0) continue;
190
+ let count = 0;
191
+ for (let i = arr.length - 1; i >= 0; i--) {
192
+ if (arr[i] >= cutoff) count++;
193
+ else break;
194
+ }
195
+ if (count > 0) {
196
+ (out ?? (out = {}))[callSiteId] = count / windowMs * 1e3;
197
+ }
198
+ }
199
+ return out;
200
+ }
201
+ var duplicateKeyEmitter = null;
202
+ function setDuplicateKeyEmitter(emitter) {
203
+ duplicateKeyEmitter = emitter;
204
+ }
205
+ function markJsxRuntimeActive() {
206
+ globalThis[JSX_RUNTIME_ACTIVE_KEY] = true;
207
+ }
208
+ function isJsxRuntimeActive() {
209
+ return globalThis[JSX_RUNTIME_ACTIVE_KEY] === true;
210
+ }
211
+ var KNOWN_REACT_PROPS = /* @__PURE__ */ new Set(["key", "ref", "children", "className"]);
212
+ var REACT_ELEMENT_TYPEOF_LEGACY = /* @__PURE__ */ Symbol.for("react.element");
213
+ var REACT_ELEMENT_TYPEOF_R19 = /* @__PURE__ */ Symbol.for("react.transitional.element");
214
+ function isReactElement(v) {
215
+ const typeOf = v.$$typeof;
216
+ return typeOf === REACT_ELEMENT_TYPEOF_LEGACY || typeOf === REACT_ELEMENT_TYPEOF_R19;
217
+ }
218
+ function detectInlineLiterals(props) {
219
+ let out;
220
+ for (const k in props) {
221
+ if (KNOWN_REACT_PROPS.has(k)) continue;
222
+ const v = props[k];
223
+ if (typeof v === "function") {
224
+ if (!v.name) {
225
+ (out ?? (out = {}))[k] = "fn";
226
+ }
227
+ } else if (Array.isArray(v)) {
228
+ if (v.length > 0) {
229
+ (out ?? (out = {}))[k] = "arr";
230
+ }
231
+ } else if (v !== null && typeof v === "object" && // Skip elements processed by our own runtime (marker present).
232
+ !(FLOTRACE_SOURCE in v) && // Skip React elements processed by ANY runtime — `$$typeof` is set on
233
+ // every element regardless of which jsx-runtime created it, so this
234
+ // catches mixed-runtime codebases. Without this guard, an inline
235
+ // `<Outer child={<Inner/>} />` would false-positive on `child` when
236
+ // Inner went through a different jsxImportSource.
237
+ !isReactElement(v)) {
238
+ const proto = Object.getPrototypeOf(v);
239
+ if (proto === Object.prototype || proto === null) {
240
+ (out ?? (out = {}))[k] = "obj";
241
+ }
242
+ }
243
+ }
244
+ return out;
245
+ }
246
+
106
247
  // src/serializer.ts
107
248
  var MAX_DEPTH = 5;
108
249
  var MAX_STRING_LENGTH = 500;
@@ -211,11 +352,7 @@ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet())
211
352
  for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
212
353
  const key = keys[i];
213
354
  try {
214
- result[key] = serializeValue(
215
- value[key],
216
- depth + 1,
217
- seen
218
- );
355
+ result[key] = serializeValue(value[key], depth + 1, seen);
219
356
  } catch {
220
357
  result[key] = { __type: "truncated", originalType: "error" };
221
358
  }
@@ -316,7 +453,12 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
316
453
  frameworkName: this.config.frameworkName,
317
454
  frameworkVersion: this.config.frameworkVersion,
318
455
  reactNativeVersion: this.config.reactNativeVersion,
319
- runtimeVersion: this.config.runtimeVersion
456
+ runtimeVersion: this.config.runtimeVersion,
457
+ // P5: JSX runtime adoption signal — read at WS-open time so
458
+ // multiple fibers have already rendered by the moment we report.
459
+ // `isJsxRuntimeActive` reads `globalThis[Symbol.for('flotrace.jsx-runtime-active')]`,
460
+ // which the dev jsx-runtime sets on first jsxDEV call.
461
+ jsxRuntimeActive: isJsxRuntimeActive()
320
462
  });
321
463
  this.flush();
322
464
  };
@@ -642,26 +784,31 @@ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
642
784
  const ms = state.memoizedState;
643
785
  const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
644
786
  const labelMap = {
645
- "usestate": "useState",
646
- "usereducer": "useReducer",
647
- "useref": "useRef",
648
- "usememo": "useMemo",
649
- "usecallback": "useCallback",
650
- "useeffect": "useEffect",
651
- "uselayouteffect": "useLayoutEffect",
652
- "useinsertioneffect": "useInsertionEffect",
653
- "usecontext": "useContext",
654
- "useimperativehandle": "useImperativeHandle",
655
- "usedebugvalue": "useDebugValue",
656
- "usetransition": "useTransition",
657
- "usedeferredvalue": "useDeferredValue",
658
- "useid": "useId",
659
- "usesyncexternalstore": "useSyncExternalStore",
660
- "useoptimistic": "useOptimistic",
661
- "useformstatus": "useFormStatus"
787
+ usestate: "useState",
788
+ usereducer: "useReducer",
789
+ useref: "useRef",
790
+ usememo: "useMemo",
791
+ usecallback: "useCallback",
792
+ useeffect: "useEffect",
793
+ uselayouteffect: "useLayoutEffect",
794
+ useinsertioneffect: "useInsertionEffect",
795
+ usecontext: "useContext",
796
+ useimperativehandle: "useImperativeHandle",
797
+ usedebugvalue: "useDebugValue",
798
+ usetransition: "useTransition",
799
+ usedeferredvalue: "useDeferredValue",
800
+ useid: "useId",
801
+ usesyncexternalstore: "useSyncExternalStore",
802
+ useoptimistic: "useOptimistic",
803
+ useformstatus: "useFormStatus"
662
804
  };
663
805
  const hookType = labelMap[normalizedLabel] ?? "unknown";
664
- const base = { index, type: hookType, value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()), debugLabel };
806
+ const base = {
807
+ index,
808
+ type: hookType,
809
+ value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()),
810
+ debugLabel
811
+ };
665
812
  if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
666
813
  if (effectIdx < effects.length) {
667
814
  const effect = effects[effectIdx];
@@ -1128,7 +1275,13 @@ var ClassComponent = 1;
1128
1275
  var ForwardRef = 11;
1129
1276
  var MemoComponent = 14;
1130
1277
  var SimpleMemoComponent = 15;
1131
- var USER_TAGS = /* @__PURE__ */ new Set([FunctionComponent, ClassComponent, ForwardRef, MemoComponent, SimpleMemoComponent]);
1278
+ var USER_TAGS = /* @__PURE__ */ new Set([
1279
+ FunctionComponent,
1280
+ ClassComponent,
1281
+ ForwardRef,
1282
+ MemoComponent,
1283
+ SimpleMemoComponent
1284
+ ]);
1132
1285
  function isMemoizedFiber(fiber) {
1133
1286
  return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
1134
1287
  }
@@ -1212,13 +1365,15 @@ function buildCascadeTree(rootFiber, triggers) {
1212
1365
  triggerByName.set(t.componentName, t);
1213
1366
  }
1214
1367
  }
1215
- const stack = [{
1216
- fiber: rootFiber,
1217
- depth: 0,
1218
- parentRerendered: false,
1219
- parentNode: null,
1220
- isRoot: true
1221
- }];
1368
+ const stack = [
1369
+ {
1370
+ fiber: rootFiber,
1371
+ depth: 0,
1372
+ parentRerendered: false,
1373
+ parentNode: null,
1374
+ isRoot: true
1375
+ }
1376
+ ];
1222
1377
  while (stack.length > 0) {
1223
1378
  const entry = stack.pop();
1224
1379
  const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
@@ -1229,7 +1384,13 @@ function buildCascadeTree(rootFiber, triggers) {
1229
1384
  if (isNewMount && !didRender) {
1230
1385
  let child2 = fiber.child;
1231
1386
  while (child2) {
1232
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1387
+ stack.push({
1388
+ fiber: child2,
1389
+ depth: depth + 1,
1390
+ parentRerendered: false,
1391
+ parentNode,
1392
+ isRoot: false
1393
+ });
1233
1394
  child2 = child2.sibling;
1234
1395
  }
1235
1396
  continue;
@@ -1237,7 +1398,13 @@ function buildCascadeTree(rootFiber, triggers) {
1237
1398
  if (!USER_TAGS.has(fiber.tag)) {
1238
1399
  let child2 = fiber.child;
1239
1400
  while (child2) {
1240
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: didRender || parentRerendered, parentNode, isRoot: false });
1401
+ stack.push({
1402
+ fiber: child2,
1403
+ depth: depth + 1,
1404
+ parentRerendered: didRender || parentRerendered,
1405
+ parentNode,
1406
+ isRoot: false
1407
+ });
1241
1408
  child2 = child2.sibling;
1242
1409
  }
1243
1410
  continue;
@@ -1246,7 +1413,13 @@ function buildCascadeTree(rootFiber, triggers) {
1246
1413
  if (reason === null) {
1247
1414
  let child2 = fiber.child;
1248
1415
  while (child2) {
1249
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1416
+ stack.push({
1417
+ fiber: child2,
1418
+ depth: depth + 1,
1419
+ parentRerendered: false,
1420
+ parentNode,
1421
+ isRoot: false
1422
+ });
1250
1423
  child2 = child2.sibling;
1251
1424
  }
1252
1425
  continue;
@@ -1272,7 +1445,11 @@ function buildCascadeTree(rootFiber, triggers) {
1272
1445
  triggerId,
1273
1446
  children: [],
1274
1447
  depth,
1275
- isMemoized: isMemoizedFiber(fiber)
1448
+ isMemoized: isMemoizedFiber(fiber),
1449
+ // P6: JSX-runtime attribution — read from fiber.memoizedProps directly.
1450
+ // Same source the walker uses, so cascade nodes align with LiveTreeNode
1451
+ // attribution for the same user component.
1452
+ jsxSource: readJsxSourceFromFiber(fiber)
1276
1453
  };
1277
1454
  totalComponents++;
1278
1455
  if (reason === "parent-cascade") {
@@ -1305,7 +1482,10 @@ function analyzeCascade(root, triggers) {
1305
1482
  try {
1306
1483
  const finishedLanes = getFinishedLanes(root);
1307
1484
  const lane = classifyLanes(finishedLanes);
1308
- const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(root.current, triggers);
1485
+ const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
1486
+ root.current,
1487
+ triggers
1488
+ );
1309
1489
  if (totalComponents === 0) return null;
1310
1490
  const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
1311
1491
  const triggerIds = triggers.map((t) => t.triggerId);
@@ -1387,7 +1567,8 @@ function shouldFlagRename(value) {
1387
1567
  if (value === null || value === void 0) return false;
1388
1568
  if (typeof value !== "object") return false;
1389
1569
  if (Array.isArray(value) && value.length === 0) return false;
1390
- if (!Array.isArray(value) && Object.keys(value).length === 0) return false;
1570
+ if (!Array.isArray(value) && Object.keys(value).length === 0)
1571
+ return false;
1391
1572
  return true;
1392
1573
  }
1393
1574
  function computePropIntersectionRatio(nodeProps, childrenProps) {
@@ -1596,13 +1777,24 @@ function runAnalysis(tree, fiberRefMap2) {
1596
1777
  propKey: p.propKey,
1597
1778
  role,
1598
1779
  hookCount: hookCounts.get(p.nodeId) ?? 0,
1599
- hasContextHook: contextFlags.get(p.nodeId) ?? false
1780
+ hasContextHook: contextFlags.get(p.nodeId) ?? false,
1781
+ // P6: propagate JSX-runtime attribution from the tree node so the
1782
+ // drill-chain detail can render `(file:line)` per step + click-
1783
+ // to-IDE on each component along the chain. Undefined when the
1784
+ // user hasn't opted into the JSX runtime.
1785
+ jsxSource: n?.jsxSource
1600
1786
  };
1601
1787
  });
1602
1788
  const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
1603
1789
  const sourceNode = nodeMap.get(sourceId);
1604
1790
  const renames = path.flatMap(
1605
- (p, idx) => p.isRename ? [{ atNodeId: p.nodeId, fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName, toKey: p.propKey }] : []
1791
+ (p, idx) => p.isRename ? [
1792
+ {
1793
+ atNodeId: p.nodeId,
1794
+ fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName,
1795
+ toKey: p.propKey
1796
+ }
1797
+ ] : []
1606
1798
  );
1607
1799
  chains.push({
1608
1800
  chainId: makeChainId(sourceId, fp, consumerNodeId),
@@ -1697,10 +1889,7 @@ var SERVER_COMPONENT_PATTERNS = [
1697
1889
  /[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
1698
1890
  // Next.js error UI
1699
1891
  ];
1700
- var SERVER_REFERENCE_PATTERNS = [
1701
- /_ServerReference$/,
1702
- /^RSC_/
1703
- ];
1892
+ var SERVER_REFERENCE_PATTERNS = [/_ServerReference$/, /^RSC_/];
1704
1893
  var detectionEmitted = false;
1705
1894
  function maybeEmitNextjsContext(client2) {
1706
1895
  if (detectionEmitted) return;
@@ -1794,7 +1983,9 @@ function scanActionStateChanges(fiberRefMap2, client2) {
1794
1983
  for (const [nodeId, fiber] of fiberRefMap2) {
1795
1984
  const entries = extractActionEntries(fiber);
1796
1985
  if (!entries) continue;
1797
- const snapshot = JSON.stringify(entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state })));
1986
+ const snapshot = JSON.stringify(
1987
+ entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state }))
1988
+ );
1798
1989
  if (prevActionStateMap.get(nodeId) === snapshot) continue;
1799
1990
  prevActionStateMap.set(nodeId, snapshot);
1800
1991
  const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
@@ -1904,7 +2095,8 @@ function tagFetchData(obj, requestId, depth = 0) {
1904
2095
  const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
1905
2096
  for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
1906
2097
  } else {
1907
- for (const val of Object.values(obj)) tagFetchData(val, requestId, depth + 1);
2098
+ for (const val of Object.values(obj))
2099
+ tagFetchData(val, requestId, depth + 1);
1908
2100
  }
1909
2101
  }
1910
2102
  function hasActiveTags() {
@@ -1940,6 +2132,344 @@ function scanForOrigin(obj, depth, ignoreTTL) {
1940
2132
  return void 0;
1941
2133
  }
1942
2134
 
2135
+ // src/fiberDebugLogger.ts
2136
+ var MAX_FIBER_RECORDS = 500;
2137
+ var MAX_TREE_RECORDS = 50;
2138
+ var MAX_TOP_NAMES_PER_SNAPSHOT = 10;
2139
+ var MAX_CONTEXTS_PER_FIBER = 8;
2140
+ var totalFiberEvents = 0;
2141
+ var recordingStartedAt = null;
2142
+ var fiberRecords = /* @__PURE__ */ new Map();
2143
+ var treeRecords = [];
2144
+ function isRecording() {
2145
+ return Boolean(globalThis.__FT_DEBUG);
2146
+ }
2147
+ function setFiberDebug(enabled) {
2148
+ globalThis.__FT_DEBUG = enabled;
2149
+ if (enabled) {
2150
+ console.info(
2151
+ "%c[FT debug]%c recording started \u2014 call %c__ft.dump()%c to view, %c__ft.clear()%c to reset, %c__ft.download()%c to export",
2152
+ "background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
2153
+ "color:#94a3b8;",
2154
+ "color:#a78bfa;font-weight:600;",
2155
+ "color:#94a3b8;",
2156
+ "color:#a78bfa;font-weight:600;",
2157
+ "color:#94a3b8;",
2158
+ "color:#a78bfa;font-weight:600;",
2159
+ "color:#94a3b8;"
2160
+ );
2161
+ }
2162
+ }
2163
+ function isFiberFunctionType(t) {
2164
+ return typeof t === "function";
2165
+ }
2166
+ function isFiberObjectType(t) {
2167
+ return typeof t === "object" && t !== null;
2168
+ }
2169
+ function isClassComponentType(t) {
2170
+ if (typeof t !== "function") return false;
2171
+ const proto = t.prototype;
2172
+ return typeof proto?.isReactComponent !== "undefined";
2173
+ }
2174
+ function describeFiberType(fiber) {
2175
+ const type = fiber.type;
2176
+ if (typeof type === "string") {
2177
+ return {
2178
+ kind: "host",
2179
+ name: type,
2180
+ displayName: void 0,
2181
+ resolved: type,
2182
+ looksMinified: false
2183
+ };
2184
+ }
2185
+ if (isFiberFunctionType(type)) {
2186
+ const isClass = isClassComponentType(type);
2187
+ const resolved = type.displayName ?? type.name ?? "Anonymous";
2188
+ return {
2189
+ kind: isClass ? "class" : "function",
2190
+ name: type.name,
2191
+ displayName: type.displayName,
2192
+ resolved,
2193
+ looksMinified: looksMinified(resolved)
2194
+ };
2195
+ }
2196
+ if (isFiberObjectType(type)) {
2197
+ if (type.render) {
2198
+ const resolved2 = type.render.displayName ?? type.render.name ?? "ForwardRef";
2199
+ return {
2200
+ kind: "forwardRef",
2201
+ name: type.render.name,
2202
+ displayName: type.render.displayName,
2203
+ resolved: resolved2,
2204
+ looksMinified: looksMinified(resolved2)
2205
+ };
2206
+ }
2207
+ if (type.type) {
2208
+ const resolved2 = type.type.displayName ?? type.type.name ?? "Memo";
2209
+ return {
2210
+ kind: "memo",
2211
+ name: type.type.name,
2212
+ displayName: type.type.displayName,
2213
+ resolved: resolved2,
2214
+ looksMinified: looksMinified(resolved2)
2215
+ };
2216
+ }
2217
+ const resolved = type.displayName ?? type.name ?? "Unknown";
2218
+ return {
2219
+ kind: "unknown",
2220
+ name: type.name,
2221
+ displayName: type.displayName,
2222
+ resolved,
2223
+ looksMinified: looksMinified(resolved)
2224
+ };
2225
+ }
2226
+ return {
2227
+ kind: "unknown",
2228
+ name: void 0,
2229
+ displayName: void 0,
2230
+ resolved: "Unknown",
2231
+ looksMinified: false
2232
+ };
2233
+ }
2234
+ function looksMinified(name) {
2235
+ return /^[a-z_$][a-z0-9_$]?$/i.test(name);
2236
+ }
2237
+ function logFiberType(fiber, context) {
2238
+ if (!isRecording()) return;
2239
+ const info = describeFiberType(fiber);
2240
+ const key = info.resolved;
2241
+ const now2 = Date.now();
2242
+ if (recordingStartedAt === null) recordingStartedAt = now2;
2243
+ totalFiberEvents += 1;
2244
+ const existing = fiberRecords.get(key);
2245
+ if (existing) {
2246
+ existing.count += 1;
2247
+ existing.lastSeen = now2;
2248
+ if (context && existing.contexts.size < MAX_CONTEXTS_PER_FIBER) {
2249
+ existing.contexts.add(context);
2250
+ }
2251
+ if (existing.exampleKey === void 0 && typeof fiber.key === "string") {
2252
+ existing.exampleKey = fiber.key;
2253
+ }
2254
+ return;
2255
+ }
2256
+ if (fiberRecords.size >= MAX_FIBER_RECORDS) {
2257
+ evictOldestFiber();
2258
+ }
2259
+ fiberRecords.set(key, {
2260
+ name: info.resolved,
2261
+ rawName: info.name,
2262
+ rawDisplayName: info.displayName,
2263
+ kind: info.kind,
2264
+ fiberTag: fiber.tag,
2265
+ looksMinified: info.looksMinified,
2266
+ count: 1,
2267
+ contexts: new Set(context ? [context] : []),
2268
+ firstSeen: now2,
2269
+ lastSeen: now2,
2270
+ source: fiber._debugSource ?? void 0,
2271
+ exampleKey: typeof fiber.key === "string" ? fiber.key : void 0
2272
+ });
2273
+ }
2274
+ function evictOldestFiber() {
2275
+ let oldestKey;
2276
+ let oldestTime = Infinity;
2277
+ for (const [key, rec] of fiberRecords) {
2278
+ if (rec.lastSeen < oldestTime) {
2279
+ oldestTime = rec.lastSeen;
2280
+ oldestKey = key;
2281
+ }
2282
+ }
2283
+ if (oldestKey) fiberRecords.delete(oldestKey);
2284
+ }
2285
+ function walkStats(node, depth, acc) {
2286
+ acc.total += 1;
2287
+ if (depth > acc.maxDepth) acc.maxDepth = depth;
2288
+ if (looksMinified(node.name)) acc.minifiedLike += 1;
2289
+ acc.byName.set(node.name, (acc.byName.get(node.name) ?? 0) + 1);
2290
+ for (const child of node.children) walkStats(child, depth + 1, acc);
2291
+ }
2292
+ function logTreeSnapshot(tree, context) {
2293
+ if (!isRecording() || !tree) return;
2294
+ const stats = { total: 0, minifiedLike: 0, byName: /* @__PURE__ */ new Map(), maxDepth: 0 };
2295
+ walkStats(tree, 0, stats);
2296
+ const topNames = [...stats.byName.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_TOP_NAMES_PER_SNAPSHOT).map(([n, c]) => `${n}:${c}`).join(",");
2297
+ treeRecords.push({
2298
+ ts: Date.now(),
2299
+ ctx: context ?? "",
2300
+ rootName: tree.name,
2301
+ totalNodes: stats.total,
2302
+ maxDepth: stats.maxDepth,
2303
+ minifiedLike: stats.minifiedLike,
2304
+ topNames
2305
+ });
2306
+ if (treeRecords.length > MAX_TREE_RECORDS) treeRecords.shift();
2307
+ }
2308
+ var logTreeSummary = logTreeSnapshot;
2309
+ function installConsoleApi() {
2310
+ if (globalThis.__ft) return;
2311
+ const api = {
2312
+ dump() {
2313
+ const fiberRows = serializeFiberRecords();
2314
+ const snapRows = serializeTreeRecords();
2315
+ const minifiedCount = fiberRows.filter((r) => r.looksMinified).length;
2316
+ const elapsedSec = recordingStartedAt === null ? 0 : (Date.now() - recordingStartedAt) / 1e3;
2317
+ const summary = [
2318
+ {
2319
+ metric: "uniqueComponents",
2320
+ value: fiberRecords.size
2321
+ },
2322
+ {
2323
+ metric: "totalFiberEvents",
2324
+ value: totalFiberEvents
2325
+ },
2326
+ {
2327
+ metric: "minifiedLike",
2328
+ value: `${minifiedCount} / ${fiberRecords.size}`
2329
+ },
2330
+ {
2331
+ metric: "snapshots",
2332
+ value: treeRecords.length
2333
+ },
2334
+ {
2335
+ metric: "recordingSec",
2336
+ value: elapsedSec.toFixed(1)
2337
+ }
2338
+ ];
2339
+ console.groupCollapsed(
2340
+ `%c[FT debug] dump%c \u2014 ${fiberRecords.size} components, ${totalFiberEvents} events, ${treeRecords.length} snapshots`,
2341
+ "background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
2342
+ "color:#94a3b8;"
2343
+ );
2344
+ console.log("Summary:");
2345
+ console.table(summary, ["metric", "value"]);
2346
+ console.log("Fibers \u2014 every observed component (sorted by call count):");
2347
+ console.table(fiberRows, [
2348
+ "name",
2349
+ "rawName",
2350
+ "rawDisplayName",
2351
+ "kind",
2352
+ "fiberTag",
2353
+ "count",
2354
+ "looksMinified",
2355
+ "exampleKey",
2356
+ "contexts",
2357
+ "file",
2358
+ "line",
2359
+ "firstSeenAt",
2360
+ "lastSeenAt",
2361
+ "lastAgoSec"
2362
+ ]);
2363
+ console.log("Tree snapshots \u2014 newest last:");
2364
+ console.table(snapRows, [
2365
+ "ts",
2366
+ "ctx",
2367
+ "rootName",
2368
+ "totalNodes",
2369
+ "maxDepth",
2370
+ "minifiedLike",
2371
+ "topNames"
2372
+ ]);
2373
+ console.groupEnd();
2374
+ },
2375
+ fibers() {
2376
+ console.table(serializeFiberRecords(), [
2377
+ "name",
2378
+ "rawName",
2379
+ "rawDisplayName",
2380
+ "kind",
2381
+ "fiberTag",
2382
+ "count",
2383
+ "looksMinified",
2384
+ "exampleKey",
2385
+ "contexts",
2386
+ "file",
2387
+ "line",
2388
+ "firstSeenAt",
2389
+ "lastSeenAt",
2390
+ "lastAgoSec"
2391
+ ]);
2392
+ },
2393
+ snapshots() {
2394
+ console.table(serializeTreeRecords());
2395
+ },
2396
+ tail(n = 20) {
2397
+ console.table(treeRecords.slice(-n));
2398
+ },
2399
+ clear() {
2400
+ fiberRecords.clear();
2401
+ treeRecords.length = 0;
2402
+ console.info("[FT debug] cleared");
2403
+ },
2404
+ size() {
2405
+ return { fibers: fiberRecords.size, snapshots: treeRecords.length };
2406
+ },
2407
+ export() {
2408
+ return { fibers: serializeFiberRecords(), snapshots: treeRecords.slice() };
2409
+ },
2410
+ download(filename) {
2411
+ const data = api.export();
2412
+ const json = JSON.stringify(data, null, 2);
2413
+ const docRef = globalThis.document;
2414
+ const URLRef = globalThis.URL;
2415
+ if (!docRef || !URLRef || typeof URLRef.createObjectURL !== "function") {
2416
+ console.warn(
2417
+ "[FT debug] download() requires a browser environment \u2014 printing JSON instead"
2418
+ );
2419
+ console.log(json);
2420
+ return;
2421
+ }
2422
+ const blob = new Blob([json], { type: "application/json" });
2423
+ const url = URLRef.createObjectURL(blob);
2424
+ const a = docRef.createElement("a");
2425
+ a.href = url;
2426
+ a.download = filename ?? `flotrace-debug-${Date.now()}.json`;
2427
+ a.click();
2428
+ URLRef.revokeObjectURL(url);
2429
+ }
2430
+ };
2431
+ globalThis.__ft = api;
2432
+ }
2433
+ installConsoleApi();
2434
+ function formatTime(ms) {
2435
+ const d = new Date(ms);
2436
+ const hh = String(d.getHours()).padStart(2, "0");
2437
+ const mm = String(d.getMinutes()).padStart(2, "0");
2438
+ const ss = String(d.getSeconds()).padStart(2, "0");
2439
+ const mss = String(d.getMilliseconds()).padStart(3, "0");
2440
+ return `${hh}:${mm}:${ss}.${mss}`;
2441
+ }
2442
+ function serializeFiberRecords() {
2443
+ const now2 = Date.now();
2444
+ return [...fiberRecords.values()].sort((a, b) => b.count - a.count).map((r) => ({
2445
+ name: r.name,
2446
+ rawName: r.rawName ?? "",
2447
+ rawDisplayName: r.rawDisplayName ?? "",
2448
+ kind: r.kind,
2449
+ fiberTag: r.fiberTag ?? "",
2450
+ count: r.count,
2451
+ looksMinified: r.looksMinified,
2452
+ exampleKey: r.exampleKey ?? "",
2453
+ contexts: [...r.contexts].join(","),
2454
+ file: r.source?.fileName ?? "",
2455
+ line: r.source?.lineNumber ?? "",
2456
+ firstSeenAt: formatTime(r.firstSeen),
2457
+ lastSeenAt: formatTime(r.lastSeen),
2458
+ lastAgoSec: ((now2 - r.lastSeen) / 1e3).toFixed(1)
2459
+ }));
2460
+ }
2461
+ function serializeTreeRecords() {
2462
+ return treeRecords.map((t) => ({
2463
+ ts: formatTime(t.ts),
2464
+ ctx: t.ctx,
2465
+ rootName: t.rootName,
2466
+ totalNodes: t.totalNodes,
2467
+ maxDepth: t.maxDepth,
2468
+ minifiedLike: t.minifiedLike,
2469
+ topNames: t.topNames
2470
+ }));
2471
+ }
2472
+
1943
2473
  // src/fiberTreeWalker.ts
1944
2474
  var FIBER_TAGS = {
1945
2475
  FunctionComponent: 0,
@@ -2082,6 +2612,7 @@ function debugLog(...args) {
2082
2612
  }
2083
2613
  var fiberRefMap = /* @__PURE__ */ new Map();
2084
2614
  function getComponentName2(fiber) {
2615
+ logFiberType(fiber, "getName");
2085
2616
  const type = fiber.type;
2086
2617
  if (!type) return "Unknown";
2087
2618
  if (typeof type === "function") {
@@ -2207,6 +2738,8 @@ var FRAMEWORK_PATH_PATTERNS = [
2207
2738
  /formik/
2208
2739
  ];
2209
2740
  function resolveEffectiveSourcePath(fiber) {
2741
+ const jsxSrc = readJsxSourceFromFiber(fiber);
2742
+ if (jsxSrc) return jsxSrc.fileName;
2210
2743
  if (fiber._debugSource?.fileName) return fiber._debugSource.fileName;
2211
2744
  const ownerHit = walkAncestors(
2212
2745
  fiber._debugOwner ?? null,
@@ -2222,6 +2755,14 @@ function resolveEffectiveSourcePath(fiber) {
2222
2755
  }
2223
2756
  return null;
2224
2757
  }
2758
+ function resolveSourceConfidence(fiber, isFramework, isLibrary, precomputedJsxSource) {
2759
+ if (isFramework || isLibrary) return "package";
2760
+ const jsxSrc = precomputedJsxSource ?? readJsxSourceFromFiber(fiber);
2761
+ if (jsxSrc) return "exact";
2762
+ if (fiber._debugSource?.fileName) return "exact";
2763
+ if (resolveEffectiveSourcePath(fiber)) return "inferred";
2764
+ return "unknown";
2765
+ }
2225
2766
  var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
2226
2767
  function walkAncestors(start, maxHops, next, visit) {
2227
2768
  let cur = start;
@@ -2388,13 +2929,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2388
2929
  { reason: renderReason },
2389
2930
  current.actualDuration
2390
2931
  );
2391
- const children = walkFiber(
2392
- current.child,
2393
- nodeId,
2394
- void 0,
2395
- depth + 1,
2396
- inSuspenseFallback
2397
- );
2932
+ const children = walkFiber(current.child, nodeId, void 0, depth + 1, inSuspenseFallback);
2398
2933
  const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
2399
2934
  const framework = isFrameworkComponent(current, name) || void 0;
2400
2935
  const queryHashes = detectQueryObserverHashes(current);
@@ -2402,6 +2937,13 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2402
2937
  const compilerStatus = detectCompilerStatus(current);
2403
2938
  const isServerComponent = detectServerComponent(current) || void 0;
2404
2939
  const libraryName = framework ? void 0 : detectLibraryName(current, name);
2940
+ const jsxSource = readJsxSourceFromFiber(current);
2941
+ const sourceConfidence = resolveSourceConfidence(
2942
+ current,
2943
+ framework === true,
2944
+ libraryName !== void 0,
2945
+ jsxSource
2946
+ );
2405
2947
  const node = {
2406
2948
  id: nodeId,
2407
2949
  name,
@@ -2410,8 +2952,8 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2410
2952
  renderPhase,
2411
2953
  renderReason,
2412
2954
  renderDuration: current.actualDuration,
2413
- filePath: current._debugSource?.fileName,
2414
- lineNumber: current._debugSource?.lineNumber,
2955
+ filePath: jsxSource?.fileName ?? current._debugSource?.fileName,
2956
+ lineNumber: jsxSource?.lineNumber ?? current._debugSource?.lineNumber,
2415
2957
  isFramework: framework,
2416
2958
  reactKey: resolveEffectiveReactKey(current),
2417
2959
  queryHashes,
@@ -2422,7 +2964,9 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2422
2964
  compilerStatus,
2423
2965
  isServerComponent,
2424
2966
  isLibrary: libraryName !== void 0 ? true : void 0,
2425
- libraryName
2967
+ libraryName,
2968
+ jsxSource,
2969
+ sourceConfidence
2426
2970
  };
2427
2971
  if (!walkerOptions.pruneSubtree?.(node)) {
2428
2972
  nodes.push(node);
@@ -2496,11 +3040,7 @@ function buildTreeFromFiberRoot(root) {
2496
3040
  }
2497
3041
  fiberRefMap.clear();
2498
3042
  const topLevelNodes = walkFiber(rootFiber.child, "");
2499
- debugLog(
2500
- "[FloTrace] walkFiber found",
2501
- topLevelNodes.length,
2502
- "top-level nodes"
2503
- );
3043
+ debugLog("[FloTrace] walkFiber found", topLevelNodes.length, "top-level nodes");
2504
3044
  if (topLevelNodes.length === 1) {
2505
3045
  return topLevelNodes[0];
2506
3046
  }
@@ -2567,9 +3107,7 @@ function findFiberRootFromDOM() {
2567
3107
  }
2568
3108
  }
2569
3109
  }
2570
- console.warn(
2571
- "[FloTrace] Could not find React fiber root from any DOM element"
2572
- );
3110
+ console.warn("[FloTrace] Could not find React fiber root from any DOM element");
2573
3111
  return null;
2574
3112
  } catch (error) {
2575
3113
  console.error("[FloTrace] Error finding fiber root from DOM:", error);
@@ -2631,9 +3169,7 @@ function executeSnapshot(root) {
2631
3169
  adaptSnapshotInterval(nodeCount);
2632
3170
  const client2 = getWebSocketClient();
2633
3171
  if (!client2.connected) {
2634
- console.warn(
2635
- "[FloTrace] WebSocket not connected, cannot send tree snapshot"
2636
- );
3172
+ console.warn("[FloTrace] WebSocket not connected, cannot send tree snapshot");
2637
3173
  return;
2638
3174
  }
2639
3175
  const currentFlatTree = flattenTree2(tree);
@@ -2649,6 +3185,7 @@ function executeSnapshot(root) {
2649
3185
  "nextInterval:",
2650
3186
  snapshotIntervalMs + "ms"
2651
3187
  );
3188
+ logTreeSnapshot(tree, `send seq=${snapshotCounter}`);
2652
3189
  client2.sendImmediate({
2653
3190
  type: "runtime:treeSnapshot",
2654
3191
  tree,
@@ -2669,6 +3206,7 @@ function executeSnapshot(root) {
2669
3206
  "updated:",
2670
3207
  diff.updated.length
2671
3208
  );
3209
+ logTreeSummary(tree, `diff seq=${diffSeq}`);
2672
3210
  client2.sendImmediate({
2673
3211
  type: "runtime:treeDiff",
2674
3212
  seq: diffSeq,
@@ -2809,9 +3347,7 @@ function installFiberTreeWalker(options = {}) {
2809
3347
  return () => uninstallFiberTreeWalker();
2810
3348
  }
2811
3349
  if (typeof window === "undefined") {
2812
- console.warn(
2813
- "[FloTrace] Not in browser environment, cannot install fiber tree walker"
2814
- );
3350
+ console.warn("[FloTrace] Not in browser environment, cannot install fiber tree walker");
2815
3351
  return () => {
2816
3352
  };
2817
3353
  }
@@ -2838,10 +3374,7 @@ function installFiberTreeWalker(options = {}) {
2838
3374
  try {
2839
3375
  originalOnCommitFiberRoot(rendererID, root, priority);
2840
3376
  } catch (error) {
2841
- console.error(
2842
- "[FloTrace] Error in original onCommitFiberRoot:",
2843
- error
2844
- );
3377
+ console.error("[FloTrace] Error in original onCommitFiberRoot:", error);
2845
3378
  }
2846
3379
  }
2847
3380
  if (hookedRendererID === null) {
@@ -2867,9 +3400,7 @@ function installFiberTreeWalker(options = {}) {
2867
3400
  scheduleSnapshot(root);
2868
3401
  };
2869
3402
  activeStrategy = "devtools";
2870
- console.log(
2871
- "[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
2872
- );
3403
+ console.log("[FloTrace] Fiber tree walker installed (DevTools hook strategy)");
2873
3404
  setTimeout(() => {
2874
3405
  try {
2875
3406
  const root = findFiberRootFromDOM();
@@ -2882,9 +3413,7 @@ function installFiberTreeWalker(options = {}) {
2882
3413
  }, 100);
2883
3414
  } else {
2884
3415
  activeStrategy = "dom";
2885
- console.log(
2886
- "[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
2887
- );
3416
+ console.log("[FloTrace] Fiber tree walker installed (DOM fallback strategy)");
2888
3417
  setTimeout(() => {
2889
3418
  try {
2890
3419
  const root = findFiberRootFromDOM();
@@ -3162,12 +3691,18 @@ function installZustandTracker(stores, client2) {
3162
3691
  try {
3163
3692
  scheduleStoreUpdate(storeName, prevState, newState, client2);
3164
3693
  } catch (error) {
3165
- console.error(`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`, error);
3694
+ console.error(
3695
+ `[FloTrace] Error in Zustand subscribe callback for "${storeName}":`,
3696
+ error
3697
+ );
3166
3698
  }
3167
3699
  });
3168
3700
  activeUnsubscribers.push(unsubscribe);
3169
3701
  } catch (error) {
3170
- console.error(`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`, error);
3702
+ console.error(
3703
+ `[FloTrace] Failed to install tracker for Zustand store "${storeName}":`,
3704
+ error
3705
+ );
3171
3706
  }
3172
3707
  }
3173
3708
  }
@@ -3210,10 +3745,13 @@ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
3210
3745
  if (changedKeys.length === 0) return;
3211
3746
  const existing = debounceTimers.get(storeName);
3212
3747
  if (existing) clearTimeout(existing);
3213
- debounceTimers.set(storeName, setTimeout(() => {
3214
- debounceTimers.delete(storeName);
3215
- sendStoreUpdate(storeName, newState, changedKeys, client2);
3216
- }, DEBOUNCE_MS));
3748
+ debounceTimers.set(
3749
+ storeName,
3750
+ setTimeout(() => {
3751
+ debounceTimers.delete(storeName);
3752
+ sendStoreUpdate(storeName, newState, changedKeys, client2);
3753
+ }, DEBOUNCE_MS)
3754
+ );
3217
3755
  }
3218
3756
  function sendStoreUpdate(storeName, state, changedKeys, client2) {
3219
3757
  try {
@@ -3786,7 +4324,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
3786
4324
  const child = container[i];
3787
4325
  const directMatch = valuesMatch(target, targetFp, child, cache);
3788
4326
  if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
3789
- const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, cache);
4327
+ const nested = findMatchingPathInObject(
4328
+ target,
4329
+ targetFp,
4330
+ child,
4331
+ [...currentPath, String(i)],
4332
+ depth + 1,
4333
+ cache
4334
+ );
3790
4335
  if (nested) return nested;
3791
4336
  }
3792
4337
  } else {
@@ -3794,7 +4339,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
3794
4339
  const child = container[key];
3795
4340
  const directMatch = valuesMatch(target, targetFp, child, cache);
3796
4341
  if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
3797
- const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, cache);
4342
+ const nested = findMatchingPathInObject(
4343
+ target,
4344
+ targetFp,
4345
+ child,
4346
+ [...currentPath, key],
4347
+ depth + 1,
4348
+ cache
4349
+ );
3798
4350
  if (nested) return nested;
3799
4351
  }
3800
4352
  }
@@ -3899,7 +4451,12 @@ function resolveValueTrace(input) {
3899
4451
  nodeId: input.nodeId,
3900
4452
  componentName: rootComponentName,
3901
4453
  propPath: input.propPath,
3902
- confidence: "exact"
4454
+ confidence: "exact",
4455
+ // P6: `fiber.memoizedProps[FLOTRACE_SOURCE]` is the JSX call site where
4456
+ // this fiber was created — i.e., the `<Consumer .../>` JSX in the
4457
+ // PARENT's source file. That's exactly the "drilled from `Parent.tsx:42`"
4458
+ // attribution the user wants on the leaf prop step.
4459
+ callSiteOfParentJsx: readJsxSourceFromFiber(fiber)
3903
4460
  });
3904
4461
  } else if (input.hookPath) {
3905
4462
  steps.push({
@@ -3939,7 +4496,12 @@ function resolveValueTrace(input) {
3939
4496
  nodeId: ancestorNodeId,
3940
4497
  componentName: ancestorName,
3941
4498
  propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
3942
- confidence: matchConfidence
4499
+ confidence: matchConfidence,
4500
+ // P6: attribute the parent JSX call site that drilled this
4501
+ // prop. When the user opted into the JSX runtime AND this
4502
+ // ancestor's fiber went through jsxDEV, the consumer sees
4503
+ // "drilled from `Parent.tsx:42:8`" with a click-to-IDE link.
4504
+ callSiteOfParentJsx: readJsxSourceFromFiber(current)
3943
4505
  });
3944
4506
  }
3945
4507
  } else {
@@ -3988,7 +4550,11 @@ function resolveValueTrace(input) {
3988
4550
  const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
3989
4551
  if (contextMatch) {
3990
4552
  steps.push(contextMatch.step);
3991
- const providerStoreMatch = findStoreMatch(contextMatch.providerValue, cachedFp(contextMatch.providerValue, fpCache), fpCache);
4553
+ const providerStoreMatch = findStoreMatch(
4554
+ contextMatch.providerValue,
4555
+ cachedFp(contextMatch.providerValue, fpCache),
4556
+ fpCache
4557
+ );
3992
4558
  if (providerStoreMatch) {
3993
4559
  steps.push({
3994
4560
  kind: "store",
@@ -4182,13 +4748,22 @@ function detectWebFramework() {
4182
4748
  // Annotate the CommonJS export names for ESM import in node:
4183
4749
  0 && (module.exports = {
4184
4750
  DEFAULT_CONFIG,
4751
+ FLOTRACE_SOURCE,
4185
4752
  FloTraceWebSocketClient,
4753
+ JSX_RUNTIME_ACTIVE_KEY,
4186
4754
  buildAncestorChain,
4755
+ clearCallSiteRenders,
4187
4756
  clearFetchOriginTags,
4757
+ computeCallSiteId,
4758
+ computeCallSiteMetricsPayload,
4759
+ describeFiberType,
4760
+ detectInlineLiterals,
4188
4761
  detectServerComponent,
4189
4762
  detectWebFramework,
4190
4763
  disposeWebSocketClient,
4191
4764
  findFetchOrigin,
4765
+ getCallSiteRenderRate,
4766
+ getCallSiteRenders,
4192
4767
  getChangedKeys,
4193
4768
  getComponentNameFromFiber,
4194
4769
  getCurrentRenderingFiber,
@@ -4211,9 +4786,15 @@ function detectWebFramework() {
4211
4786
  installTanStackQueryTracker,
4212
4787
  installTimelineTracker,
4213
4788
  installZustandTracker,
4789
+ isJsxRuntimeActive,
4214
4790
  isReduxStore,
4215
4791
  isTanStackQueryClient,
4792
+ logTreeSnapshot,
4793
+ logTreeSummary,
4794
+ markJsxRuntimeActive,
4216
4795
  maybeEmitNextjsContext,
4796
+ normalizeJsxSourcePath,
4797
+ recordCallSiteRender,
4217
4798
  recordTimelineEvent,
4218
4799
  requestFullSnapshot,
4219
4800
  requestTreeSnapshot,
@@ -4221,6 +4802,8 @@ function detectWebFramework() {
4221
4802
  resolveValueTrace,
4222
4803
  serializeProps,
4223
4804
  serializeValue,
4805
+ setDuplicateKeyEmitter,
4806
+ setFiberDebug,
4224
4807
  tagFetchData,
4225
4808
  uninstallFiberTreeWalker,
4226
4809
  uninstallReduxTracker,