@flotrace/runtime-core 2.2.4 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,3 +1,22 @@
1
+ import {
2
+ FLOTRACE_SOURCE,
3
+ JSX_RUNTIME_ACTIVE_KEY,
4
+ clearCallSiteRenders,
5
+ computeCallSiteId,
6
+ computeCallSiteMetricsPayload,
7
+ detectInlineLiterals,
8
+ getCallSiteRenderRate,
9
+ getCallSiteRenders,
10
+ isJsxRuntimeActive,
11
+ isUserComponent,
12
+ markJsxRuntimeActive,
13
+ normalizeJsxSourcePath,
14
+ parseFirstNonReactFrame,
15
+ readJsxSourceFromFiber,
16
+ recordCallSiteRender,
17
+ setDuplicateKeyEmitter
18
+ } from "./chunk-5LSFLPGP.mjs";
19
+
1
20
  // src/types.ts
2
21
  var DEFAULT_CONFIG = {
3
22
  port: 3457,
@@ -129,11 +148,7 @@ function serializeValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet())
129
148
  for (let i = 0; i < Math.min(keys.length, MAX_OBJECT_KEYS); i++) {
130
149
  const key = keys[i];
131
150
  try {
132
- result[key] = serializeValue(
133
- value[key],
134
- depth + 1,
135
- seen
136
- );
151
+ result[key] = serializeValue(value[key], depth + 1, seen);
137
152
  } catch {
138
153
  result[key] = { __type: "truncated", originalType: "error" };
139
154
  }
@@ -234,7 +249,12 @@ var _FloTraceWebSocketClient = class _FloTraceWebSocketClient {
234
249
  frameworkName: this.config.frameworkName,
235
250
  frameworkVersion: this.config.frameworkVersion,
236
251
  reactNativeVersion: this.config.reactNativeVersion,
237
- runtimeVersion: this.config.runtimeVersion
252
+ runtimeVersion: this.config.runtimeVersion,
253
+ // P5: JSX runtime adoption signal — read at WS-open time so
254
+ // multiple fibers have already rendered by the moment we report.
255
+ // `isJsxRuntimeActive` reads `globalThis[Symbol.for('flotrace.jsx-runtime-active')]`,
256
+ // which the dev jsx-runtime sets on first jsxDEV call.
257
+ jsxRuntimeActive: isJsxRuntimeActive()
238
258
  });
239
259
  this.flush();
240
260
  };
@@ -560,26 +580,31 @@ function classifyFromDebugLabel(state, index, effects, effectIdx, debugLabel) {
560
580
  const ms = state.memoizedState;
561
581
  const normalizedLabel = debugLabel.toLowerCase().replace(/\s/g, "");
562
582
  const labelMap = {
563
- "usestate": "useState",
564
- "usereducer": "useReducer",
565
- "useref": "useRef",
566
- "usememo": "useMemo",
567
- "usecallback": "useCallback",
568
- "useeffect": "useEffect",
569
- "uselayouteffect": "useLayoutEffect",
570
- "useinsertioneffect": "useInsertionEffect",
571
- "usecontext": "useContext",
572
- "useimperativehandle": "useImperativeHandle",
573
- "usedebugvalue": "useDebugValue",
574
- "usetransition": "useTransition",
575
- "usedeferredvalue": "useDeferredValue",
576
- "useid": "useId",
577
- "usesyncexternalstore": "useSyncExternalStore",
578
- "useoptimistic": "useOptimistic",
579
- "useformstatus": "useFormStatus"
583
+ usestate: "useState",
584
+ usereducer: "useReducer",
585
+ useref: "useRef",
586
+ usememo: "useMemo",
587
+ usecallback: "useCallback",
588
+ useeffect: "useEffect",
589
+ uselayouteffect: "useLayoutEffect",
590
+ useinsertioneffect: "useInsertionEffect",
591
+ usecontext: "useContext",
592
+ useimperativehandle: "useImperativeHandle",
593
+ usedebugvalue: "useDebugValue",
594
+ usetransition: "useTransition",
595
+ usedeferredvalue: "useDeferredValue",
596
+ useid: "useId",
597
+ usesyncexternalstore: "useSyncExternalStore",
598
+ useoptimistic: "useOptimistic",
599
+ useformstatus: "useFormStatus"
580
600
  };
581
601
  const hookType = labelMap[normalizedLabel] ?? "unknown";
582
- const base = { index, type: hookType, value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()), debugLabel };
602
+ const base = {
603
+ index,
604
+ type: hookType,
605
+ value: serializeValue(ms, 0, /* @__PURE__ */ new WeakSet()),
606
+ debugLabel
607
+ };
583
608
  if (hookType === "useEffect" || hookType === "useLayoutEffect" || hookType === "useInsertionEffect") {
584
609
  if (effectIdx < effects.length) {
585
610
  const effect = effects[effectIdx];
@@ -1046,7 +1071,13 @@ var ClassComponent = 1;
1046
1071
  var ForwardRef = 11;
1047
1072
  var MemoComponent = 14;
1048
1073
  var SimpleMemoComponent = 15;
1049
- var USER_TAGS = /* @__PURE__ */ new Set([FunctionComponent, ClassComponent, ForwardRef, MemoComponent, SimpleMemoComponent]);
1074
+ var USER_TAGS = /* @__PURE__ */ new Set([
1075
+ FunctionComponent,
1076
+ ClassComponent,
1077
+ ForwardRef,
1078
+ MemoComponent,
1079
+ SimpleMemoComponent
1080
+ ]);
1050
1081
  function isMemoizedFiber(fiber) {
1051
1082
  return fiber.tag === MemoComponent || fiber.tag === SimpleMemoComponent;
1052
1083
  }
@@ -1130,13 +1161,15 @@ function buildCascadeTree(rootFiber, triggers) {
1130
1161
  triggerByName.set(t.componentName, t);
1131
1162
  }
1132
1163
  }
1133
- const stack = [{
1134
- fiber: rootFiber,
1135
- depth: 0,
1136
- parentRerendered: false,
1137
- parentNode: null,
1138
- isRoot: true
1139
- }];
1164
+ const stack = [
1165
+ {
1166
+ fiber: rootFiber,
1167
+ depth: 0,
1168
+ parentRerendered: false,
1169
+ parentNode: null,
1170
+ isRoot: true
1171
+ }
1172
+ ];
1140
1173
  while (stack.length > 0) {
1141
1174
  const entry = stack.pop();
1142
1175
  const { fiber, depth, parentRerendered, parentNode, isRoot } = entry;
@@ -1147,7 +1180,13 @@ function buildCascadeTree(rootFiber, triggers) {
1147
1180
  if (isNewMount && !didRender) {
1148
1181
  let child2 = fiber.child;
1149
1182
  while (child2) {
1150
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1183
+ stack.push({
1184
+ fiber: child2,
1185
+ depth: depth + 1,
1186
+ parentRerendered: false,
1187
+ parentNode,
1188
+ isRoot: false
1189
+ });
1151
1190
  child2 = child2.sibling;
1152
1191
  }
1153
1192
  continue;
@@ -1155,7 +1194,13 @@ function buildCascadeTree(rootFiber, triggers) {
1155
1194
  if (!USER_TAGS.has(fiber.tag)) {
1156
1195
  let child2 = fiber.child;
1157
1196
  while (child2) {
1158
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: didRender || parentRerendered, parentNode, isRoot: false });
1197
+ stack.push({
1198
+ fiber: child2,
1199
+ depth: depth + 1,
1200
+ parentRerendered: didRender || parentRerendered,
1201
+ parentNode,
1202
+ isRoot: false
1203
+ });
1159
1204
  child2 = child2.sibling;
1160
1205
  }
1161
1206
  continue;
@@ -1164,7 +1209,13 @@ function buildCascadeTree(rootFiber, triggers) {
1164
1209
  if (reason === null) {
1165
1210
  let child2 = fiber.child;
1166
1211
  while (child2) {
1167
- stack.push({ fiber: child2, depth: depth + 1, parentRerendered: false, parentNode, isRoot: false });
1212
+ stack.push({
1213
+ fiber: child2,
1214
+ depth: depth + 1,
1215
+ parentRerendered: false,
1216
+ parentNode,
1217
+ isRoot: false
1218
+ });
1168
1219
  child2 = child2.sibling;
1169
1220
  }
1170
1221
  continue;
@@ -1190,7 +1241,11 @@ function buildCascadeTree(rootFiber, triggers) {
1190
1241
  triggerId,
1191
1242
  children: [],
1192
1243
  depth,
1193
- isMemoized: isMemoizedFiber(fiber)
1244
+ isMemoized: isMemoizedFiber(fiber),
1245
+ // P6: JSX-runtime attribution — read from fiber.memoizedProps directly.
1246
+ // Same source the walker uses, so cascade nodes align with LiveTreeNode
1247
+ // attribution for the same user component.
1248
+ jsxSource: readJsxSourceFromFiber(fiber)
1194
1249
  };
1195
1250
  totalComponents++;
1196
1251
  if (reason === "parent-cascade") {
@@ -1223,7 +1278,10 @@ function analyzeCascade(root, triggers) {
1223
1278
  try {
1224
1279
  const finishedLanes = getFinishedLanes(root);
1225
1280
  const lane = classifyLanes(finishedLanes);
1226
- const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(root.current, triggers);
1281
+ const { rootCauses, totalComponents, avoidableCount, avoidableDuration } = buildCascadeTree(
1282
+ root.current,
1283
+ triggers
1284
+ );
1227
1285
  if (totalComponents === 0) return null;
1228
1286
  const totalDuration = rootCauses.reduce((sum, n) => sum + n.subtreeDuration, 0);
1229
1287
  const triggerIds = triggers.map((t) => t.triggerId);
@@ -1305,7 +1363,8 @@ function shouldFlagRename(value) {
1305
1363
  if (value === null || value === void 0) return false;
1306
1364
  if (typeof value !== "object") return false;
1307
1365
  if (Array.isArray(value) && value.length === 0) return false;
1308
- if (!Array.isArray(value) && Object.keys(value).length === 0) return false;
1366
+ if (!Array.isArray(value) && Object.keys(value).length === 0)
1367
+ return false;
1309
1368
  return true;
1310
1369
  }
1311
1370
  function computePropIntersectionRatio(nodeProps, childrenProps) {
@@ -1453,7 +1512,11 @@ function runAnalysis(tree, fiberRefMap2) {
1453
1512
  }
1454
1513
  }
1455
1514
  for (const sourceId of sourceNodeIds) {
1456
- let dfs2 = function(currentId, currentPropKey, currentPath, visited) {
1515
+ const firstEdge = outEdges.get(sourceId)?.[0];
1516
+ if (!firstEdge) continue;
1517
+ const sourcePropName = firstEdge.propKey;
1518
+ const allPaths = [];
1519
+ const dfs = (currentId, currentPropKey, currentPath, visited) => {
1457
1520
  if (visited.has(currentId)) return;
1458
1521
  visited.add(currentId);
1459
1522
  const outgoing = outEdges.get(currentId);
@@ -1466,7 +1529,7 @@ function runAnalysis(tree, fiberRefMap2) {
1466
1529
  }
1467
1530
  for (const edge of outgoing) {
1468
1531
  const isRename = edge.propKey !== edge.childPropKey;
1469
- dfs2(
1532
+ dfs(
1470
1533
  edge.childNodeId,
1471
1534
  edge.childPropKey,
1472
1535
  [...currentPath, { nodeId: edge.childNodeId, propKey: edge.childPropKey, isRename }],
@@ -1475,12 +1538,7 @@ function runAnalysis(tree, fiberRefMap2) {
1475
1538
  }
1476
1539
  visited.delete(currentId);
1477
1540
  };
1478
- var dfs = dfs2;
1479
- const firstEdge = outEdges.get(sourceId)?.[0];
1480
- if (!firstEdge) continue;
1481
- const sourcePropName = firstEdge.propKey;
1482
- const allPaths = [];
1483
- dfs2(
1541
+ dfs(
1484
1542
  sourceId,
1485
1543
  sourcePropName,
1486
1544
  [{ nodeId: sourceId, propKey: sourcePropName, isRename: false }],
@@ -1514,13 +1572,24 @@ function runAnalysis(tree, fiberRefMap2) {
1514
1572
  propKey: p.propKey,
1515
1573
  role,
1516
1574
  hookCount: hookCounts.get(p.nodeId) ?? 0,
1517
- hasContextHook: contextFlags.get(p.nodeId) ?? false
1575
+ hasContextHook: contextFlags.get(p.nodeId) ?? false,
1576
+ // P6: propagate JSX-runtime attribution from the tree node so the
1577
+ // drill-chain detail can render `(file:line)` per step + click-
1578
+ // to-IDE on each component along the chain. Undefined when the
1579
+ // user hasn't opted into the JSX runtime.
1580
+ jsxSource: n?.jsxSource
1518
1581
  };
1519
1582
  });
1520
1583
  const passthroughCount = chainNodes.filter((n) => n.role === "passthrough").length;
1521
1584
  const sourceNode = nodeMap.get(sourceId);
1522
1585
  const renames = path.flatMap(
1523
- (p, idx) => p.isRename ? [{ atNodeId: p.nodeId, fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName, toKey: p.propKey }] : []
1586
+ (p, idx) => p.isRename ? [
1587
+ {
1588
+ atNodeId: p.nodeId,
1589
+ fromKey: idx > 0 ? path[idx - 1].propKey : sourcePropName,
1590
+ toKey: p.propKey
1591
+ }
1592
+ ] : []
1524
1593
  );
1525
1594
  chains.push({
1526
1595
  chainId: makeChainId(sourceId, fp, consumerNodeId),
@@ -1615,10 +1684,7 @@ var SERVER_COMPONENT_PATTERNS = [
1615
1684
  /[\\/]app[\\/].+[\\/]error\.[jt]sx?$/
1616
1685
  // Next.js error UI
1617
1686
  ];
1618
- var SERVER_REFERENCE_PATTERNS = [
1619
- /_ServerReference$/,
1620
- /^RSC_/
1621
- ];
1687
+ var SERVER_REFERENCE_PATTERNS = [/_ServerReference$/, /^RSC_/];
1622
1688
  var detectionEmitted = false;
1623
1689
  function maybeEmitNextjsContext(client2) {
1624
1690
  if (detectionEmitted) return;
@@ -1712,7 +1778,9 @@ function scanActionStateChanges(fiberRefMap2, client2) {
1712
1778
  for (const [nodeId, fiber] of fiberRefMap2) {
1713
1779
  const entries = extractActionEntries(fiber);
1714
1780
  if (!entries) continue;
1715
- const snapshot = JSON.stringify(entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state })));
1781
+ const snapshot = JSON.stringify(
1782
+ entries.map((e) => ({ i: e.hookIndex, p: e.isPending, s: e.state }))
1783
+ );
1716
1784
  if (prevActionStateMap.get(nodeId) === snapshot) continue;
1717
1785
  prevActionStateMap.set(nodeId, snapshot);
1718
1786
  const componentName = nodeId.split("/").pop()?.replace(/-\d+$/, "") ?? "Unknown";
@@ -1822,7 +1890,8 @@ function tagFetchData(obj, requestId, depth = 0) {
1822
1890
  const limit = Math.min(obj.length, FETCH_ORIGIN_TAG_ARRAY_LIMIT);
1823
1891
  for (let i = 0; i < limit; i++) tagFetchData(obj[i], requestId, depth + 1);
1824
1892
  } else {
1825
- for (const val of Object.values(obj)) tagFetchData(val, requestId, depth + 1);
1893
+ for (const val of Object.values(obj))
1894
+ tagFetchData(val, requestId, depth + 1);
1826
1895
  }
1827
1896
  }
1828
1897
  function hasActiveTags() {
@@ -1858,6 +1927,344 @@ function scanForOrigin(obj, depth, ignoreTTL) {
1858
1927
  return void 0;
1859
1928
  }
1860
1929
 
1930
+ // src/fiberDebugLogger.ts
1931
+ var MAX_FIBER_RECORDS = 500;
1932
+ var MAX_TREE_RECORDS = 50;
1933
+ var MAX_TOP_NAMES_PER_SNAPSHOT = 10;
1934
+ var MAX_CONTEXTS_PER_FIBER = 8;
1935
+ var totalFiberEvents = 0;
1936
+ var recordingStartedAt = null;
1937
+ var fiberRecords = /* @__PURE__ */ new Map();
1938
+ var treeRecords = [];
1939
+ function isRecording() {
1940
+ return Boolean(globalThis.__FT_DEBUG);
1941
+ }
1942
+ function setFiberDebug(enabled) {
1943
+ globalThis.__FT_DEBUG = enabled;
1944
+ if (enabled) {
1945
+ console.info(
1946
+ "%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",
1947
+ "background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
1948
+ "color:#94a3b8;",
1949
+ "color:#a78bfa;font-weight:600;",
1950
+ "color:#94a3b8;",
1951
+ "color:#a78bfa;font-weight:600;",
1952
+ "color:#94a3b8;",
1953
+ "color:#a78bfa;font-weight:600;",
1954
+ "color:#94a3b8;"
1955
+ );
1956
+ }
1957
+ }
1958
+ function isFiberFunctionType(t) {
1959
+ return typeof t === "function";
1960
+ }
1961
+ function isFiberObjectType(t) {
1962
+ return typeof t === "object" && t !== null;
1963
+ }
1964
+ function isClassComponentType(t) {
1965
+ if (typeof t !== "function") return false;
1966
+ const proto = t.prototype;
1967
+ return typeof proto?.isReactComponent !== "undefined";
1968
+ }
1969
+ function describeFiberType(fiber) {
1970
+ const type = fiber.type;
1971
+ if (typeof type === "string") {
1972
+ return {
1973
+ kind: "host",
1974
+ name: type,
1975
+ displayName: void 0,
1976
+ resolved: type,
1977
+ looksMinified: false
1978
+ };
1979
+ }
1980
+ if (isFiberFunctionType(type)) {
1981
+ const isClass = isClassComponentType(type);
1982
+ const resolved = type.displayName ?? type.name ?? "Anonymous";
1983
+ return {
1984
+ kind: isClass ? "class" : "function",
1985
+ name: type.name,
1986
+ displayName: type.displayName,
1987
+ resolved,
1988
+ looksMinified: looksMinified(resolved)
1989
+ };
1990
+ }
1991
+ if (isFiberObjectType(type)) {
1992
+ if (type.render) {
1993
+ const resolved2 = type.render.displayName ?? type.render.name ?? "ForwardRef";
1994
+ return {
1995
+ kind: "forwardRef",
1996
+ name: type.render.name,
1997
+ displayName: type.render.displayName,
1998
+ resolved: resolved2,
1999
+ looksMinified: looksMinified(resolved2)
2000
+ };
2001
+ }
2002
+ if (type.type) {
2003
+ const resolved2 = type.type.displayName ?? type.type.name ?? "Memo";
2004
+ return {
2005
+ kind: "memo",
2006
+ name: type.type.name,
2007
+ displayName: type.type.displayName,
2008
+ resolved: resolved2,
2009
+ looksMinified: looksMinified(resolved2)
2010
+ };
2011
+ }
2012
+ const resolved = type.displayName ?? type.name ?? "Unknown";
2013
+ return {
2014
+ kind: "unknown",
2015
+ name: type.name,
2016
+ displayName: type.displayName,
2017
+ resolved,
2018
+ looksMinified: looksMinified(resolved)
2019
+ };
2020
+ }
2021
+ return {
2022
+ kind: "unknown",
2023
+ name: void 0,
2024
+ displayName: void 0,
2025
+ resolved: "Unknown",
2026
+ looksMinified: false
2027
+ };
2028
+ }
2029
+ function looksMinified(name) {
2030
+ return /^[a-z_$][a-z0-9_$]?$/i.test(name);
2031
+ }
2032
+ function logFiberType(fiber, context) {
2033
+ if (!isRecording()) return;
2034
+ const info = describeFiberType(fiber);
2035
+ const key = info.resolved;
2036
+ const now2 = Date.now();
2037
+ if (recordingStartedAt === null) recordingStartedAt = now2;
2038
+ totalFiberEvents += 1;
2039
+ const existing = fiberRecords.get(key);
2040
+ if (existing) {
2041
+ existing.count += 1;
2042
+ existing.lastSeen = now2;
2043
+ if (context && existing.contexts.size < MAX_CONTEXTS_PER_FIBER) {
2044
+ existing.contexts.add(context);
2045
+ }
2046
+ if (existing.exampleKey === void 0 && typeof fiber.key === "string") {
2047
+ existing.exampleKey = fiber.key;
2048
+ }
2049
+ return;
2050
+ }
2051
+ if (fiberRecords.size >= MAX_FIBER_RECORDS) {
2052
+ evictOldestFiber();
2053
+ }
2054
+ fiberRecords.set(key, {
2055
+ name: info.resolved,
2056
+ rawName: info.name,
2057
+ rawDisplayName: info.displayName,
2058
+ kind: info.kind,
2059
+ fiberTag: fiber.tag,
2060
+ looksMinified: info.looksMinified,
2061
+ count: 1,
2062
+ contexts: new Set(context ? [context] : []),
2063
+ firstSeen: now2,
2064
+ lastSeen: now2,
2065
+ source: fiber._debugSource ?? void 0,
2066
+ exampleKey: typeof fiber.key === "string" ? fiber.key : void 0
2067
+ });
2068
+ }
2069
+ function evictOldestFiber() {
2070
+ let oldestKey;
2071
+ let oldestTime = Infinity;
2072
+ for (const [key, rec] of fiberRecords) {
2073
+ if (rec.lastSeen < oldestTime) {
2074
+ oldestTime = rec.lastSeen;
2075
+ oldestKey = key;
2076
+ }
2077
+ }
2078
+ if (oldestKey) fiberRecords.delete(oldestKey);
2079
+ }
2080
+ function walkStats(node, depth, acc) {
2081
+ acc.total += 1;
2082
+ if (depth > acc.maxDepth) acc.maxDepth = depth;
2083
+ if (looksMinified(node.name)) acc.minifiedLike += 1;
2084
+ acc.byName.set(node.name, (acc.byName.get(node.name) ?? 0) + 1);
2085
+ for (const child of node.children) walkStats(child, depth + 1, acc);
2086
+ }
2087
+ function logTreeSnapshot(tree, context) {
2088
+ if (!isRecording() || !tree) return;
2089
+ const stats = { total: 0, minifiedLike: 0, byName: /* @__PURE__ */ new Map(), maxDepth: 0 };
2090
+ walkStats(tree, 0, stats);
2091
+ 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(",");
2092
+ treeRecords.push({
2093
+ ts: Date.now(),
2094
+ ctx: context ?? "",
2095
+ rootName: tree.name,
2096
+ totalNodes: stats.total,
2097
+ maxDepth: stats.maxDepth,
2098
+ minifiedLike: stats.minifiedLike,
2099
+ topNames
2100
+ });
2101
+ if (treeRecords.length > MAX_TREE_RECORDS) treeRecords.shift();
2102
+ }
2103
+ var logTreeSummary = logTreeSnapshot;
2104
+ function installConsoleApi() {
2105
+ if (globalThis.__ft) return;
2106
+ const api = {
2107
+ dump() {
2108
+ const fiberRows = serializeFiberRecords();
2109
+ const snapRows = serializeTreeRecords();
2110
+ const minifiedCount = fiberRows.filter((r) => r.looksMinified).length;
2111
+ const elapsedSec = recordingStartedAt === null ? 0 : (Date.now() - recordingStartedAt) / 1e3;
2112
+ const summary = [
2113
+ {
2114
+ metric: "uniqueComponents",
2115
+ value: fiberRecords.size
2116
+ },
2117
+ {
2118
+ metric: "totalFiberEvents",
2119
+ value: totalFiberEvents
2120
+ },
2121
+ {
2122
+ metric: "minifiedLike",
2123
+ value: `${minifiedCount} / ${fiberRecords.size}`
2124
+ },
2125
+ {
2126
+ metric: "snapshots",
2127
+ value: treeRecords.length
2128
+ },
2129
+ {
2130
+ metric: "recordingSec",
2131
+ value: elapsedSec.toFixed(1)
2132
+ }
2133
+ ];
2134
+ console.groupCollapsed(
2135
+ `%c[FT debug] dump%c \u2014 ${fiberRecords.size} components, ${totalFiberEvents} events, ${treeRecords.length} snapshots`,
2136
+ "background:#1e293b;color:#7dd3fc;padding:1px 6px;border-radius:3px;font-weight:600;",
2137
+ "color:#94a3b8;"
2138
+ );
2139
+ console.log("Summary:");
2140
+ console.table(summary, ["metric", "value"]);
2141
+ console.log("Fibers \u2014 every observed component (sorted by call count):");
2142
+ console.table(fiberRows, [
2143
+ "name",
2144
+ "rawName",
2145
+ "rawDisplayName",
2146
+ "kind",
2147
+ "fiberTag",
2148
+ "count",
2149
+ "looksMinified",
2150
+ "exampleKey",
2151
+ "contexts",
2152
+ "file",
2153
+ "line",
2154
+ "firstSeenAt",
2155
+ "lastSeenAt",
2156
+ "lastAgoSec"
2157
+ ]);
2158
+ console.log("Tree snapshots \u2014 newest last:");
2159
+ console.table(snapRows, [
2160
+ "ts",
2161
+ "ctx",
2162
+ "rootName",
2163
+ "totalNodes",
2164
+ "maxDepth",
2165
+ "minifiedLike",
2166
+ "topNames"
2167
+ ]);
2168
+ console.groupEnd();
2169
+ },
2170
+ fibers() {
2171
+ console.table(serializeFiberRecords(), [
2172
+ "name",
2173
+ "rawName",
2174
+ "rawDisplayName",
2175
+ "kind",
2176
+ "fiberTag",
2177
+ "count",
2178
+ "looksMinified",
2179
+ "exampleKey",
2180
+ "contexts",
2181
+ "file",
2182
+ "line",
2183
+ "firstSeenAt",
2184
+ "lastSeenAt",
2185
+ "lastAgoSec"
2186
+ ]);
2187
+ },
2188
+ snapshots() {
2189
+ console.table(serializeTreeRecords());
2190
+ },
2191
+ tail(n = 20) {
2192
+ console.table(treeRecords.slice(-n));
2193
+ },
2194
+ clear() {
2195
+ fiberRecords.clear();
2196
+ treeRecords.length = 0;
2197
+ console.info("[FT debug] cleared");
2198
+ },
2199
+ size() {
2200
+ return { fibers: fiberRecords.size, snapshots: treeRecords.length };
2201
+ },
2202
+ export() {
2203
+ return { fibers: serializeFiberRecords(), snapshots: treeRecords.slice() };
2204
+ },
2205
+ download(filename) {
2206
+ const data = api.export();
2207
+ const json = JSON.stringify(data, null, 2);
2208
+ const docRef = globalThis.document;
2209
+ const URLRef = globalThis.URL;
2210
+ if (!docRef || !URLRef || typeof URLRef.createObjectURL !== "function") {
2211
+ console.warn(
2212
+ "[FT debug] download() requires a browser environment \u2014 printing JSON instead"
2213
+ );
2214
+ console.log(json);
2215
+ return;
2216
+ }
2217
+ const blob = new Blob([json], { type: "application/json" });
2218
+ const url = URLRef.createObjectURL(blob);
2219
+ const a = docRef.createElement("a");
2220
+ a.href = url;
2221
+ a.download = filename ?? `flotrace-debug-${Date.now()}.json`;
2222
+ a.click();
2223
+ URLRef.revokeObjectURL(url);
2224
+ }
2225
+ };
2226
+ globalThis.__ft = api;
2227
+ }
2228
+ installConsoleApi();
2229
+ function formatTime(ms) {
2230
+ const d = new Date(ms);
2231
+ const hh = String(d.getHours()).padStart(2, "0");
2232
+ const mm = String(d.getMinutes()).padStart(2, "0");
2233
+ const ss = String(d.getSeconds()).padStart(2, "0");
2234
+ const mss = String(d.getMilliseconds()).padStart(3, "0");
2235
+ return `${hh}:${mm}:${ss}.${mss}`;
2236
+ }
2237
+ function serializeFiberRecords() {
2238
+ const now2 = Date.now();
2239
+ return [...fiberRecords.values()].sort((a, b) => b.count - a.count).map((r) => ({
2240
+ name: r.name,
2241
+ rawName: r.rawName ?? "",
2242
+ rawDisplayName: r.rawDisplayName ?? "",
2243
+ kind: r.kind,
2244
+ fiberTag: r.fiberTag ?? "",
2245
+ count: r.count,
2246
+ looksMinified: r.looksMinified,
2247
+ exampleKey: r.exampleKey ?? "",
2248
+ contexts: [...r.contexts].join(","),
2249
+ file: r.source?.fileName ?? "",
2250
+ line: r.source?.lineNumber ?? "",
2251
+ firstSeenAt: formatTime(r.firstSeen),
2252
+ lastSeenAt: formatTime(r.lastSeen),
2253
+ lastAgoSec: ((now2 - r.lastSeen) / 1e3).toFixed(1)
2254
+ }));
2255
+ }
2256
+ function serializeTreeRecords() {
2257
+ return treeRecords.map((t) => ({
2258
+ ts: formatTime(t.ts),
2259
+ ctx: t.ctx,
2260
+ rootName: t.rootName,
2261
+ totalNodes: t.totalNodes,
2262
+ maxDepth: t.maxDepth,
2263
+ minifiedLike: t.minifiedLike,
2264
+ topNames: t.topNames
2265
+ }));
2266
+ }
2267
+
1861
2268
  // src/fiberTreeWalker.ts
1862
2269
  var FIBER_TAGS = {
1863
2270
  FunctionComponent: 0,
@@ -2000,6 +2407,7 @@ function debugLog(...args) {
2000
2407
  }
2001
2408
  var fiberRefMap = /* @__PURE__ */ new Map();
2002
2409
  function getComponentName2(fiber) {
2410
+ logFiberType(fiber, "getName");
2003
2411
  const type = fiber.type;
2004
2412
  if (!type) return "Unknown";
2005
2413
  if (typeof type === "function") {
@@ -2020,7 +2428,7 @@ function getComponentName2(fiber) {
2020
2428
  }
2021
2429
  return "Unknown";
2022
2430
  }
2023
- function isUserComponent(fiber) {
2431
+ function isLikelyUserComponent(fiber) {
2024
2432
  if (!USER_COMPONENT_TAGS.has(fiber.tag)) return false;
2025
2433
  const name = getComponentName2(fiber);
2026
2434
  if (name === "Anonymous" || name === "Unknown" || name === "ForwardRef" || name === "Memo")
@@ -2124,15 +2532,28 @@ var FRAMEWORK_PATH_PATTERNS = [
2124
2532
  /react-hook-form/,
2125
2533
  /formik/
2126
2534
  ];
2127
- function resolveEffectiveSourcePath(fiber) {
2128
- if (fiber._debugSource?.fileName) return fiber._debugSource.fileName;
2535
+ function resolveEffectiveSourceLocation(fiber) {
2536
+ const jsxSrc = readJsxSourceFromFiber(fiber);
2537
+ if (jsxSrc) {
2538
+ return {
2539
+ fileName: jsxSrc.fileName,
2540
+ lineNumber: jsxSrc.lineNumber,
2541
+ columnNumber: jsxSrc.columnNumber
2542
+ };
2543
+ }
2544
+ if (fiber._debugSource?.fileName) {
2545
+ return {
2546
+ fileName: fiber._debugSource.fileName,
2547
+ lineNumber: fiber._debugSource.lineNumber
2548
+ };
2549
+ }
2129
2550
  const ownerHit = walkAncestors(
2130
2551
  fiber._debugOwner ?? null,
2131
2552
  3,
2132
2553
  (f) => f._debugOwner ?? null,
2133
2554
  (cur) => cur._debugSource?.fileName ?? void 0
2134
2555
  );
2135
- if (ownerHit) return ownerHit;
2556
+ if (ownerHit) return { fileName: ownerHit };
2136
2557
  const stack = fiber._debugStack?.stack;
2137
2558
  if (typeof stack === "string") {
2138
2559
  const parsed = parseFirstNonReactFrame(stack);
@@ -2140,6 +2561,18 @@ function resolveEffectiveSourcePath(fiber) {
2140
2561
  }
2141
2562
  return null;
2142
2563
  }
2564
+ function resolveEffectiveSourcePath(fiber) {
2565
+ return resolveEffectiveSourceLocation(fiber)?.fileName ?? null;
2566
+ }
2567
+ function resolveSourceConfidence(fiber, isFramework, isLibrary, precomputedJsxSource) {
2568
+ if (isUserComponent(fiber)) return "exact";
2569
+ if (isFramework || isLibrary) return "package";
2570
+ const jsxSrc = precomputedJsxSource ?? readJsxSourceFromFiber(fiber);
2571
+ if (jsxSrc) return "exact";
2572
+ if (fiber._debugSource?.fileName) return "exact";
2573
+ if (resolveEffectiveSourcePath(fiber)) return "inferred";
2574
+ return "unknown";
2575
+ }
2143
2576
  var STOP_WALK = /* @__PURE__ */ Symbol("stop-walk");
2144
2577
  function walkAncestors(start, maxHops, next, visit) {
2145
2578
  let cur = start;
@@ -2150,22 +2583,8 @@ function walkAncestors(start, maxHops, next, visit) {
2150
2583
  }
2151
2584
  return void 0;
2152
2585
  }
2153
- function parseFirstNonReactFrame(stack) {
2154
- const lines = stack.split("\n");
2155
- for (const line of lines) {
2156
- const parened = line.match(/\(([^)]+):\d+:\d+\)/);
2157
- const hermes = line.match(/@([^\s]+):\d+:\d+$/);
2158
- const path = parened?.[1] ?? hermes?.[1];
2159
- if (!path) continue;
2160
- if (path.includes("react-dom")) continue;
2161
- if (path.includes("react-native/Libraries")) continue;
2162
- if (path.includes("/react/cjs/")) continue;
2163
- if (path.includes("/scheduler/")) continue;
2164
- return path;
2165
- }
2166
- return null;
2167
- }
2168
2586
  function isFrameworkComponent(fiber, name) {
2587
+ if (isUserComponent(fiber)) return false;
2169
2588
  if (walkerFilterConfig.frameworkNames.has(name)) return true;
2170
2589
  for (const pattern of walkerFilterConfig.frameworkNamePatterns) {
2171
2590
  if (pattern.test(name)) return true;
@@ -2205,6 +2624,7 @@ var KNOWN_LIBRARY_NAMES = /* @__PURE__ */ new Map([
2205
2624
  ["HeroIcon", "heroicons"]
2206
2625
  ]);
2207
2626
  function detectLibraryName(fiber, name) {
2627
+ if (isUserComponent(fiber)) return void 0;
2208
2628
  if (name.includes(".")) {
2209
2629
  return name.split(".")[0].toLowerCase();
2210
2630
  }
@@ -2275,7 +2695,7 @@ function resolveEffectiveReactKey(fiber) {
2275
2695
  6,
2276
2696
  (f) => f.return ?? null,
2277
2697
  (cur) => {
2278
- if (isUserComponent(cur) && !isFrameworkComponent(cur, getComponentName2(cur))) {
2698
+ if (isLikelyUserComponent(cur) && !isFrameworkComponent(cur, getComponentName2(cur))) {
2279
2699
  return STOP_WALK;
2280
2700
  }
2281
2701
  return typeof cur.key === "string" ? cur.key : void 0;
@@ -2291,7 +2711,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2291
2711
  while (current) {
2292
2712
  try {
2293
2713
  const tag = current.tag;
2294
- if (isUserComponent(current)) {
2714
+ if (isLikelyUserComponent(current)) {
2295
2715
  const name = getComponentName2(current);
2296
2716
  const nameCount = nameCountMap.get(name) || 0;
2297
2717
  nameCountMap.set(name, nameCount + 1);
@@ -2306,13 +2726,7 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2306
2726
  { reason: renderReason },
2307
2727
  current.actualDuration
2308
2728
  );
2309
- const children = walkFiber(
2310
- current.child,
2311
- nodeId,
2312
- void 0,
2313
- depth + 1,
2314
- inSuspenseFallback
2315
- );
2729
+ const children = walkFiber(current.child, nodeId, void 0, depth + 1, inSuspenseFallback);
2316
2730
  const truncatedChildren = children.length > MAX_CHILDREN_PER_NODE ? children.slice(0, MAX_CHILDREN_PER_NODE) : children;
2317
2731
  const framework = isFrameworkComponent(current, name) || void 0;
2318
2732
  const queryHashes = detectQueryObserverHashes(current);
@@ -2320,6 +2734,15 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2320
2734
  const compilerStatus = detectCompilerStatus(current);
2321
2735
  const isServerComponent = detectServerComponent(current) || void 0;
2322
2736
  const libraryName = framework ? void 0 : detectLibraryName(current, name);
2737
+ const jsxSource = readJsxSourceFromFiber(current);
2738
+ const sourceConfidence = resolveSourceConfidence(
2739
+ current,
2740
+ framework === true,
2741
+ libraryName !== void 0,
2742
+ jsxSource
2743
+ );
2744
+ const needsStackFallback = (jsxSource?.fileName ?? current._debugSource?.fileName) === void 0 || (jsxSource?.lineNumber ?? current._debugSource?.lineNumber) === void 0;
2745
+ const stackLocation = needsStackFallback ? resolveEffectiveSourceLocation(current) : null;
2323
2746
  const node = {
2324
2747
  id: nodeId,
2325
2748
  name,
@@ -2328,8 +2751,8 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2328
2751
  renderPhase,
2329
2752
  renderReason,
2330
2753
  renderDuration: current.actualDuration,
2331
- filePath: current._debugSource?.fileName,
2332
- lineNumber: current._debugSource?.lineNumber,
2754
+ filePath: jsxSource?.fileName ?? current._debugSource?.fileName ?? stackLocation?.fileName,
2755
+ lineNumber: jsxSource?.lineNumber ?? current._debugSource?.lineNumber ?? stackLocation?.lineNumber,
2333
2756
  isFramework: framework,
2334
2757
  reactKey: resolveEffectiveReactKey(current),
2335
2758
  queryHashes,
@@ -2340,7 +2763,9 @@ function walkFiber(fiber, parentId, sharedNameCountMap, depth = 0, inSuspenseFal
2340
2763
  compilerStatus,
2341
2764
  isServerComponent,
2342
2765
  isLibrary: libraryName !== void 0 ? true : void 0,
2343
- libraryName
2766
+ libraryName,
2767
+ jsxSource,
2768
+ sourceConfidence
2344
2769
  };
2345
2770
  if (!walkerOptions.pruneSubtree?.(node)) {
2346
2771
  nodes.push(node);
@@ -2414,11 +2839,7 @@ function buildTreeFromFiberRoot(root) {
2414
2839
  }
2415
2840
  fiberRefMap.clear();
2416
2841
  const topLevelNodes = walkFiber(rootFiber.child, "");
2417
- debugLog(
2418
- "[FloTrace] walkFiber found",
2419
- topLevelNodes.length,
2420
- "top-level nodes"
2421
- );
2842
+ debugLog("[FloTrace] walkFiber found", topLevelNodes.length, "top-level nodes");
2422
2843
  if (topLevelNodes.length === 1) {
2423
2844
  return topLevelNodes[0];
2424
2845
  }
@@ -2485,9 +2906,7 @@ function findFiberRootFromDOM() {
2485
2906
  }
2486
2907
  }
2487
2908
  }
2488
- console.warn(
2489
- "[FloTrace] Could not find React fiber root from any DOM element"
2490
- );
2909
+ console.warn("[FloTrace] Could not find React fiber root from any DOM element");
2491
2910
  return null;
2492
2911
  } catch (error) {
2493
2912
  console.error("[FloTrace] Error finding fiber root from DOM:", error);
@@ -2549,9 +2968,7 @@ function executeSnapshot(root) {
2549
2968
  adaptSnapshotInterval(nodeCount);
2550
2969
  const client2 = getWebSocketClient();
2551
2970
  if (!client2.connected) {
2552
- console.warn(
2553
- "[FloTrace] WebSocket not connected, cannot send tree snapshot"
2554
- );
2971
+ console.warn("[FloTrace] WebSocket not connected, cannot send tree snapshot");
2555
2972
  return;
2556
2973
  }
2557
2974
  const currentFlatTree = flattenTree2(tree);
@@ -2567,6 +2984,7 @@ function executeSnapshot(root) {
2567
2984
  "nextInterval:",
2568
2985
  snapshotIntervalMs + "ms"
2569
2986
  );
2987
+ logTreeSnapshot(tree, `send seq=${snapshotCounter}`);
2570
2988
  client2.sendImmediate({
2571
2989
  type: "runtime:treeSnapshot",
2572
2990
  tree,
@@ -2587,6 +3005,7 @@ function executeSnapshot(root) {
2587
3005
  "updated:",
2588
3006
  diff.updated.length
2589
3007
  );
3008
+ logTreeSummary(tree, `diff seq=${diffSeq}`);
2590
3009
  client2.sendImmediate({
2591
3010
  type: "runtime:treeDiff",
2592
3011
  seq: diffSeq,
@@ -2727,9 +3146,7 @@ function installFiberTreeWalker(options = {}) {
2727
3146
  return () => uninstallFiberTreeWalker();
2728
3147
  }
2729
3148
  if (typeof window === "undefined") {
2730
- console.warn(
2731
- "[FloTrace] Not in browser environment, cannot install fiber tree walker"
2732
- );
3149
+ console.warn("[FloTrace] Not in browser environment, cannot install fiber tree walker");
2733
3150
  return () => {
2734
3151
  };
2735
3152
  }
@@ -2756,10 +3173,7 @@ function installFiberTreeWalker(options = {}) {
2756
3173
  try {
2757
3174
  originalOnCommitFiberRoot(rendererID, root, priority);
2758
3175
  } catch (error) {
2759
- console.error(
2760
- "[FloTrace] Error in original onCommitFiberRoot:",
2761
- error
2762
- );
3176
+ console.error("[FloTrace] Error in original onCommitFiberRoot:", error);
2763
3177
  }
2764
3178
  }
2765
3179
  if (hookedRendererID === null) {
@@ -2785,9 +3199,7 @@ function installFiberTreeWalker(options = {}) {
2785
3199
  scheduleSnapshot(root);
2786
3200
  };
2787
3201
  activeStrategy = "devtools";
2788
- console.log(
2789
- "[FloTrace] Fiber tree walker installed (DevTools hook strategy)"
2790
- );
3202
+ console.log("[FloTrace] Fiber tree walker installed (DevTools hook strategy)");
2791
3203
  setTimeout(() => {
2792
3204
  try {
2793
3205
  const root = findFiberRootFromDOM();
@@ -2800,9 +3212,7 @@ function installFiberTreeWalker(options = {}) {
2800
3212
  }, 100);
2801
3213
  } else {
2802
3214
  activeStrategy = "dom";
2803
- console.log(
2804
- "[FloTrace] Fiber tree walker installed (DOM fallback strategy)"
2805
- );
3215
+ console.log("[FloTrace] Fiber tree walker installed (DOM fallback strategy)");
2806
3216
  setTimeout(() => {
2807
3217
  try {
2808
3218
  const root = findFiberRootFromDOM();
@@ -3080,12 +3490,18 @@ function installZustandTracker(stores, client2) {
3080
3490
  try {
3081
3491
  scheduleStoreUpdate(storeName, prevState, newState, client2);
3082
3492
  } catch (error) {
3083
- console.error(`[FloTrace] Error in Zustand subscribe callback for "${storeName}":`, error);
3493
+ console.error(
3494
+ `[FloTrace] Error in Zustand subscribe callback for "${storeName}":`,
3495
+ error
3496
+ );
3084
3497
  }
3085
3498
  });
3086
3499
  activeUnsubscribers.push(unsubscribe);
3087
3500
  } catch (error) {
3088
- console.error(`[FloTrace] Failed to install tracker for Zustand store "${storeName}":`, error);
3501
+ console.error(
3502
+ `[FloTrace] Failed to install tracker for Zustand store "${storeName}":`,
3503
+ error
3504
+ );
3089
3505
  }
3090
3506
  }
3091
3507
  }
@@ -3128,10 +3544,13 @@ function scheduleStoreUpdate(storeName, prevState, newState, client2) {
3128
3544
  if (changedKeys.length === 0) return;
3129
3545
  const existing = debounceTimers.get(storeName);
3130
3546
  if (existing) clearTimeout(existing);
3131
- debounceTimers.set(storeName, setTimeout(() => {
3132
- debounceTimers.delete(storeName);
3133
- sendStoreUpdate(storeName, newState, changedKeys, client2);
3134
- }, DEBOUNCE_MS));
3547
+ debounceTimers.set(
3548
+ storeName,
3549
+ setTimeout(() => {
3550
+ debounceTimers.delete(storeName);
3551
+ sendStoreUpdate(storeName, newState, changedKeys, client2);
3552
+ }, DEBOUNCE_MS)
3553
+ );
3135
3554
  }
3136
3555
  function sendStoreUpdate(storeName, state, changedKeys, client2) {
3137
3556
  try {
@@ -3704,7 +4123,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
3704
4123
  const child = container[i];
3705
4124
  const directMatch = valuesMatch(target, targetFp, child, cache);
3706
4125
  if (directMatch) return { path: [...currentPath, String(i)], confidence: directMatch };
3707
- const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, String(i)], depth + 1, cache);
4126
+ const nested = findMatchingPathInObject(
4127
+ target,
4128
+ targetFp,
4129
+ child,
4130
+ [...currentPath, String(i)],
4131
+ depth + 1,
4132
+ cache
4133
+ );
3708
4134
  if (nested) return nested;
3709
4135
  }
3710
4136
  } else {
@@ -3712,7 +4138,14 @@ function findMatchingPathInObject(target, targetFp, container, currentPath, dept
3712
4138
  const child = container[key];
3713
4139
  const directMatch = valuesMatch(target, targetFp, child, cache);
3714
4140
  if (directMatch) return { path: [...currentPath, key], confidence: directMatch };
3715
- const nested = findMatchingPathInObject(target, targetFp, child, [...currentPath, key], depth + 1, cache);
4141
+ const nested = findMatchingPathInObject(
4142
+ target,
4143
+ targetFp,
4144
+ child,
4145
+ [...currentPath, key],
4146
+ depth + 1,
4147
+ cache
4148
+ );
3716
4149
  if (nested) return nested;
3717
4150
  }
3718
4151
  }
@@ -3817,7 +4250,12 @@ function resolveValueTrace(input) {
3817
4250
  nodeId: input.nodeId,
3818
4251
  componentName: rootComponentName,
3819
4252
  propPath: input.propPath,
3820
- confidence: "exact"
4253
+ confidence: "exact",
4254
+ // P6: `fiber.memoizedProps[FLOTRACE_SOURCE]` is the JSX call site where
4255
+ // this fiber was created — i.e., the `<Consumer .../>` JSX in the
4256
+ // PARENT's source file. That's exactly the "drilled from `Parent.tsx:42`"
4257
+ // attribution the user wants on the leaf prop step.
4258
+ callSiteOfParentJsx: readJsxSourceFromFiber(fiber)
3821
4259
  });
3822
4260
  } else if (input.hookPath) {
3823
4261
  steps.push({
@@ -3857,7 +4295,12 @@ function resolveValueTrace(input) {
3857
4295
  nodeId: ancestorNodeId,
3858
4296
  componentName: ancestorName,
3859
4297
  propPath: trailingSubPath.length > 0 ? [...matchPath, ...trailingSubPath] : matchPath,
3860
- confidence: matchConfidence
4298
+ confidence: matchConfidence,
4299
+ // P6: attribute the parent JSX call site that drilled this
4300
+ // prop. When the user opted into the JSX runtime AND this
4301
+ // ancestor's fiber went through jsxDEV, the consumer sees
4302
+ // "drilled from `Parent.tsx:42:8`" with a click-to-IDE link.
4303
+ callSiteOfParentJsx: readJsxSourceFromFiber(current)
3861
4304
  });
3862
4305
  }
3863
4306
  } else {
@@ -3906,7 +4349,11 @@ function resolveValueTrace(input) {
3906
4349
  const contextMatch = findContextMatch(fiber, rootValue, rootFp, fiberToNodeId, fpCache);
3907
4350
  if (contextMatch) {
3908
4351
  steps.push(contextMatch.step);
3909
- const providerStoreMatch = findStoreMatch(contextMatch.providerValue, cachedFp(contextMatch.providerValue, fpCache), fpCache);
4352
+ const providerStoreMatch = findStoreMatch(
4353
+ contextMatch.providerValue,
4354
+ cachedFp(contextMatch.providerValue, fpCache),
4355
+ fpCache
4356
+ );
3910
4357
  if (providerStoreMatch) {
3911
4358
  steps.push({
3912
4359
  kind: "store",
@@ -4099,13 +4546,22 @@ function detectWebFramework() {
4099
4546
  }
4100
4547
  export {
4101
4548
  DEFAULT_CONFIG,
4549
+ FLOTRACE_SOURCE,
4102
4550
  FloTraceWebSocketClient,
4551
+ JSX_RUNTIME_ACTIVE_KEY,
4103
4552
  buildAncestorChain,
4553
+ clearCallSiteRenders,
4104
4554
  clearFetchOriginTags,
4555
+ computeCallSiteId,
4556
+ computeCallSiteMetricsPayload,
4557
+ describeFiberType,
4558
+ detectInlineLiterals,
4105
4559
  detectServerComponent,
4106
4560
  detectWebFramework,
4107
4561
  disposeWebSocketClient,
4108
4562
  findFetchOrigin,
4563
+ getCallSiteRenderRate,
4564
+ getCallSiteRenders,
4109
4565
  getChangedKeys,
4110
4566
  getComponentNameFromFiber,
4111
4567
  getCurrentRenderingFiber,
@@ -4128,9 +4584,16 @@ export {
4128
4584
  installTanStackQueryTracker,
4129
4585
  installTimelineTracker,
4130
4586
  installZustandTracker,
4587
+ isJsxRuntimeActive,
4131
4588
  isReduxStore,
4132
4589
  isTanStackQueryClient,
4590
+ isUserComponent,
4591
+ logTreeSnapshot,
4592
+ logTreeSummary,
4593
+ markJsxRuntimeActive,
4133
4594
  maybeEmitNextjsContext,
4595
+ normalizeJsxSourcePath,
4596
+ recordCallSiteRender,
4134
4597
  recordTimelineEvent,
4135
4598
  requestFullSnapshot,
4136
4599
  requestTreeSnapshot,
@@ -4138,6 +4601,8 @@ export {
4138
4601
  resolveValueTrace,
4139
4602
  serializeProps,
4140
4603
  serializeValue,
4604
+ setDuplicateKeyEmitter,
4605
+ setFiberDebug,
4141
4606
  tagFetchData,
4142
4607
  uninstallFiberTreeWalker,
4143
4608
  uninstallReduxTracker,