@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/README.md +94 -0
- package/babel-plugin.d.ts +36 -0
- package/babel-plugin.js +302 -0
- package/dist/chunk-5LSFLPGP.mjs +276 -0
- package/dist/index.d.mts +435 -17
- package/dist/index.d.ts +435 -17
- package/dist/index.js +801 -120
- package/dist/index.mjs +585 -120
- package/dist/jsx-dev-runtime.d.mts +64 -0
- package/dist/jsx-dev-runtime.d.ts +64 -0
- package/dist/jsx-dev-runtime.js +179 -0
- package/dist/jsx-dev-runtime.mjs +46 -0
- package/dist/jsx-runtime.d.mts +1 -0
- package/dist/jsx-runtime.d.ts +1 -0
- package/dist/jsx-runtime.js +34 -0
- package/dist/jsx-runtime.mjs +7 -0
- package/package.json +18 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for the @flotrace/runtime-core JSX runtime entries.
|
|
3
|
+
*
|
|
4
|
+
* The JSX runtime (jsx-dev-runtime.ts) writes the FLOTRACE_SOURCE symbol onto
|
|
5
|
+
* a JSX element's props at creation time. The fiber walker reads the same
|
|
6
|
+
* symbol back from `fiber.memoizedProps` during tree walking. Centralising
|
|
7
|
+
* the symbol identity + helpers here means there's exactly one place to look
|
|
8
|
+
* for "what shape is fiber.memoizedProps[FLOTRACE_SOURCE]".
|
|
9
|
+
*
|
|
10
|
+
* Per PRD-JSX-RUNTIME.md §8 + IMPLEMENTATION-PLAN-JSX-RUNTIME.md Phase 1.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Global symbol so the same identity is reachable from any module — including
|
|
14
|
+
* dynamically-loaded chunks and multiple bundled copies of runtime-core that
|
|
15
|
+
* may end up linked into the same app (workspace + npm registry mix). The
|
|
16
|
+
* `Symbol.for()` registry guarantees one global slot.
|
|
17
|
+
*/
|
|
18
|
+
declare const FLOTRACE_SOURCE: unique symbol;
|
|
19
|
+
/**
|
|
20
|
+
* Adoption sentinel — the dev runtime sets this on first jsxDEV call so the
|
|
21
|
+
* walker (and `runtime:ready` event) can detect that the user opted in via
|
|
22
|
+
* `"jsxImportSource": "@flotrace/runtime-core"`. No per-call telemetry; this
|
|
23
|
+
* is a one-time boolean.
|
|
24
|
+
*/
|
|
25
|
+
declare const JSX_RUNTIME_ACTIVE_KEY: unique symbol;
|
|
26
|
+
/**
|
|
27
|
+
* Source attribution attached to a React element's props at JSX-creation time.
|
|
28
|
+
* Stored under the `FLOTRACE_SOURCE` symbol key so it doesn't appear in
|
|
29
|
+
* `Object.keys(props)` or React's unknown-DOM-prop warnings.
|
|
30
|
+
*/
|
|
31
|
+
interface FlotraceJsxSource {
|
|
32
|
+
/** Normalized file path (bundler prefixes stripped). */
|
|
33
|
+
fileName: string;
|
|
34
|
+
/** 1-indexed line number. */
|
|
35
|
+
lineNumber: number;
|
|
36
|
+
/** 1-indexed column number. */
|
|
37
|
+
columnNumber: number;
|
|
38
|
+
/** FNV-1a 32-bit hash of `${fileName}:${lineNumber}:${columnNumber}`, 8 hex chars. */
|
|
39
|
+
callSiteId: string;
|
|
40
|
+
/** Map of prop key → inline-literal kind, only present when literals detected. */
|
|
41
|
+
inline?: Record<string, 'fn' | 'obj' | 'arr'>;
|
|
42
|
+
}
|
|
43
|
+
/** Subset of the compiler-supplied `source` argument passed to jsxDEV. */
|
|
44
|
+
interface JsxSourceArg {
|
|
45
|
+
fileName: string;
|
|
46
|
+
lineNumber: number;
|
|
47
|
+
columnNumber: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Normalize a bundler-specific file path to a canonical form. Different
|
|
51
|
+
* bundlers emit different path styles for the SAME line of source code:
|
|
52
|
+
*
|
|
53
|
+
* Vite dev → `file:///abs/src/Foo.tsx`
|
|
54
|
+
* Webpack dev → `webpack-internal:///./src/Foo.tsx`
|
|
55
|
+
* esbuild → `./src/Foo.tsx`
|
|
56
|
+
* Next.js Turbopack → `[project]/src/Foo.tsx` (left as-is — no known prefix)
|
|
57
|
+
* Windows → `C:\Users\foo\src\Foo.tsx`
|
|
58
|
+
*
|
|
59
|
+
* Without normalization, the same line of code produces a different
|
|
60
|
+
* `callSiteId` after a bundler swap (or even after switching between Next.js
|
|
61
|
+
* Webpack and Turbopack), breaking per-callsite metrics + HMR-stable watches.
|
|
62
|
+
*
|
|
63
|
+
* Normalization rules (order matters):
|
|
64
|
+
* 1. Strip `file://` prefix.
|
|
65
|
+
* 2. Strip `webpack-internal:///./` prefix.
|
|
66
|
+
* 3. Trim leading `./`.
|
|
67
|
+
* 4. Lowercase Windows drive letters (`C:\` → `c:\`).
|
|
68
|
+
*/
|
|
69
|
+
declare function normalizeJsxSourcePath(fileName: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* FNV-1a 32-bit hash → 8-char hex string. Fast, stable, and sufficient for
|
|
72
|
+
* a non-cryptographic per-callsite identity. Operates on the NORMALIZED path
|
|
73
|
+
* so the same source line produces the same hash regardless of bundler.
|
|
74
|
+
*
|
|
75
|
+
* Collision probability across a 5000-callsite app via the birthday paradox
|
|
76
|
+
* on a 32-bit hash space: 1 − e^(−5000²/(2 × 2³²)) ≈ 0.3%. Acceptable for a
|
|
77
|
+
* UI key — a one-in-300 chance of two callsites sharing a row in the Hot
|
|
78
|
+
* Call Sites table is preferable to the wire-format weight of a longer hash.
|
|
79
|
+
* Not suitable as a security token.
|
|
80
|
+
*/
|
|
81
|
+
declare function computeCallSiteId(source: JsxSourceArg): string;
|
|
82
|
+
/**
|
|
83
|
+
* Top-priority "is this fiber a user-defined component?" check.
|
|
84
|
+
*
|
|
85
|
+
* Three routes, checked in order of precision (fastest + most reliable
|
|
86
|
+
* first). Returns `true` as soon as ANY route produces positive evidence
|
|
87
|
+
* that the fiber's source code lives outside `node_modules`.
|
|
88
|
+
*
|
|
89
|
+
* Route A — `fiber.type[FLOTRACE_SRC_ATTR]` (babel plugin):
|
|
90
|
+
* The `@flotrace/runtime-core/babel-plugin` declaration-tagging pass
|
|
91
|
+
* writes a JSON `{f,l,c}` payload onto every PascalCase function /
|
|
92
|
+
* class / variable in user code. Works for: React Native (Babel),
|
|
93
|
+
* Vite + React (Babel), CRA, Webpack + Babel, Next.js Pages Router
|
|
94
|
+
* (if Babel is opted in). Doesn't work for: Next.js with SWC.
|
|
95
|
+
*
|
|
96
|
+
* Route B — `fiber._debugSource.fileName` (React 18 + Babel JSX source):
|
|
97
|
+
* The `@babel/plugin-transform-react-jsx-source` plugin (auto-included
|
|
98
|
+
* by RN's preset and CRA/Vite's React preset) injects `__source` on
|
|
99
|
+
* every JSX element, which React 18 captures onto `fiber._debugSource`.
|
|
100
|
+
* Empty under React 19+ (the field was deprecated upstream).
|
|
101
|
+
*
|
|
102
|
+
* Route C — `fiber._debugStack.stack` (React 19+ web — Next.js SWC, Vite):
|
|
103
|
+
* React 19 replaced `_debugSource` with an Error captured at JSX
|
|
104
|
+
* creation time. We parse the first non-React stack frame for a path.
|
|
105
|
+
* This is what makes Next.js (SWC, no babel config possible without
|
|
106
|
+
* losing SWC) work — the React 19 reconciler attaches stack-frame
|
|
107
|
+
* info regardless of the compiler used.
|
|
108
|
+
*
|
|
109
|
+
* For ALL routes the "is user code" criterion is the same: the resolved
|
|
110
|
+
* file path must not include `node_modules`. That string check is enough
|
|
111
|
+
* because every modern bundler resolves third-party deps through a path
|
|
112
|
+
* containing `/node_modules/` — there's no realistic false-negative.
|
|
113
|
+
*
|
|
114
|
+
* Returns `false` for:
|
|
115
|
+
* - Host components (string `fiber.type` like `'View'` / `'div'`)
|
|
116
|
+
* - Library / framework components (paths in `node_modules`, plus
|
|
117
|
+
* bundle-URL frames which are skipped by `parseFirstNonReactFrame`)
|
|
118
|
+
* - Fibers with no source signal at all (degrade to existing heuristics)
|
|
119
|
+
*
|
|
120
|
+
* Used by `fiberTreeWalker.ts` as a top-priority short-circuit before
|
|
121
|
+
* name-list / regex / path heuristics fire. A fiber that returns `true`
|
|
122
|
+
* here is authoritatively user code — every framework/library
|
|
123
|
+
* classification downstream should bypass.
|
|
124
|
+
*
|
|
125
|
+
* Generic over the fiber-like shape so cascadeAnalyzer / propDrillingAnalyzer
|
|
126
|
+
* / valueTraceResolver can all share one definition without importing the
|
|
127
|
+
* walker's `Fiber` type.
|
|
128
|
+
*/
|
|
129
|
+
declare function isUserComponent(fiber: {
|
|
130
|
+
type?: unknown;
|
|
131
|
+
memoizedProps?: Record<string, unknown> | null;
|
|
132
|
+
_debugSource?: {
|
|
133
|
+
fileName: string;
|
|
134
|
+
lineNumber?: number;
|
|
135
|
+
} | null;
|
|
136
|
+
_debugStack?: {
|
|
137
|
+
stack?: string;
|
|
138
|
+
} | null;
|
|
139
|
+
}): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Record a render timestamp for a call site. Caller may inject `now` for
|
|
142
|
+
* deterministic tests; production callers use the default `performance.now()`.
|
|
143
|
+
*/
|
|
144
|
+
declare function recordCallSiteRender(callSiteId: string, now?: number): void;
|
|
145
|
+
/** Read-only snapshot of recorded timestamps for a call site. */
|
|
146
|
+
declare function getCallSiteRenders(callSiteId: string): readonly number[];
|
|
147
|
+
/**
|
|
148
|
+
* Renders-per-second over the last `windowMs` ms. Walks backwards through the
|
|
149
|
+
* ring buffer; stops at the first entry older than the cutoff (entries are
|
|
150
|
+
* monotonically non-decreasing because the runtime only ever appends).
|
|
151
|
+
*
|
|
152
|
+
* Caller may inject `now` for deterministic tests.
|
|
153
|
+
*/
|
|
154
|
+
declare function getCallSiteRenderRate(callSiteId: string, windowMs?: number, now?: number): number;
|
|
155
|
+
/**
|
|
156
|
+
* Clear all ring-buffer state. Called by the walker uninstall path so HMR
|
|
157
|
+
* rapid-reconnect cycles don't accumulate stale entries across sessions.
|
|
158
|
+
*/
|
|
159
|
+
declare function clearCallSiteRenders(): void;
|
|
160
|
+
/**
|
|
161
|
+
* Compute a `runtime:callSiteMetrics` payload from the ring buffer — one
|
|
162
|
+
* entry per callsite that has rendered within the last `windowMs` ms (5s
|
|
163
|
+
* default). Callsites with zero recent activity are omitted so the wire-
|
|
164
|
+
* format payload stays small on idle apps.
|
|
165
|
+
*
|
|
166
|
+
* Returns `null` when no callsite has activity → caller skips the WebSocket
|
|
167
|
+
* send entirely. This is load-bearing: a 5000-callsite app with no live
|
|
168
|
+
* renders should NOT emit a 5000-entry map of zeros every second.
|
|
169
|
+
*
|
|
170
|
+
* The `now` arg lets tests inject deterministic timestamps.
|
|
171
|
+
*/
|
|
172
|
+
declare function computeCallSiteMetricsPayload(windowMs?: number, now?: number): Record<string, number> | null;
|
|
173
|
+
/**
|
|
174
|
+
* Payload emitted whenever a JSX call site fires twice in the same commit
|
|
175
|
+
* with the same React `key`. Shape mirrors `RuntimeDuplicateKeyMessage` minus
|
|
176
|
+
* the wire-format envelope (`type` + `timestamp`) — those are added by the
|
|
177
|
+
* caller (typically `FloTraceProvider`) at WS-send time.
|
|
178
|
+
*/
|
|
179
|
+
interface DuplicateKeyEvent {
|
|
180
|
+
callSiteId: string;
|
|
181
|
+
fileName: string;
|
|
182
|
+
lineNumber: number;
|
|
183
|
+
columnNumber: number;
|
|
184
|
+
duplicateKey: string;
|
|
185
|
+
occurrences: number;
|
|
186
|
+
}
|
|
187
|
+
declare function setDuplicateKeyEmitter(emitter: ((evt: DuplicateKeyEvent) => void) | null): void;
|
|
188
|
+
/**
|
|
189
|
+
* One-time boolean stored on `globalThis` under `JSX_RUNTIME_ACTIVE_KEY`. The
|
|
190
|
+
* dev jsx-runtime sets it on first `jsxDEV` call; the walker reads it to
|
|
191
|
+
* include `jsxRuntimeActive: true` on `runtime:ready` for adoption telemetry.
|
|
192
|
+
*
|
|
193
|
+
* No per-call telemetry — this is a single boolean, set once.
|
|
194
|
+
*/
|
|
195
|
+
declare function markJsxRuntimeActive(): void;
|
|
196
|
+
declare function isJsxRuntimeActive(): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Detect props that look like fresh-each-render literals — the #1 React perf
|
|
199
|
+
* footgun (an inline `onClick={() => ...}` invalidates `memo` + breaks
|
|
200
|
+
* `useEffect` dep arrays). This signal can ONLY be observed at JSX-creation
|
|
201
|
+
* time; after React commits, the prop on the fiber looks identical whether it
|
|
202
|
+
* was a literal or `useCallback`/`useMemo`'d ref.
|
|
203
|
+
*
|
|
204
|
+
* Heuristics (conservative — false-negatives are acceptable; false-positives
|
|
205
|
+
* train users to ignore the warning, so we err toward silence):
|
|
206
|
+
*
|
|
207
|
+
* - **Functions**: only flagged when the fn has no `.name`. `useCallback`'d
|
|
208
|
+
* fns preserve the inner fn's name, named methods are hoisted — the
|
|
209
|
+
* anonymous-arrow case is what catches `{() => doX()}` and `{e => h(e)}`.
|
|
210
|
+
* - **Arrays**: only non-empty arrays. `[]` literals are usually intentional
|
|
211
|
+
* empty defaults and not a perf concern in their own right.
|
|
212
|
+
* - **Plain objects**: detected via prototype check (`Object.prototype` or
|
|
213
|
+
* `null` prototype). React elements (`$$typeof`), class instances, dates,
|
|
214
|
+
* maps, sets — all skipped.
|
|
215
|
+
*
|
|
216
|
+
* Returns `undefined` when nothing is flagged, so callers can use a single
|
|
217
|
+
* truthy check before serialising.
|
|
218
|
+
*/
|
|
219
|
+
declare function detectInlineLiterals(props: Record<string, unknown>): Record<string, 'fn' | 'obj' | 'arr'> | undefined;
|
|
220
|
+
|
|
1
221
|
/**
|
|
2
222
|
* Types for @flotrace/runtime package
|
|
3
223
|
* These mirror the shared types from the extension but are standalone
|
|
4
224
|
* to avoid importing from the extension package.
|
|
5
225
|
*/
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Confidence tier for `LiveTreeNode` source attribution. Drives the
|
|
229
|
+
* OriginBadge variant in the renderer:
|
|
230
|
+
*
|
|
231
|
+
* - `'exact'` — JSX-runtime symbol present OR `_debugSource.fileName`
|
|
232
|
+
* populated (Babel JSX plugin / React 17–18 dev).
|
|
233
|
+
* - `'inferred'` — path resolved via the owner-chain walk or
|
|
234
|
+
* `_debugStack.stack` first-non-react frame (R19+).
|
|
235
|
+
* - `'package'` — fiber classified as framework/library; user clicks won't
|
|
236
|
+
* land in user code anyway.
|
|
237
|
+
* - `'unknown'` — no signal at any tier. Renders an amber `?` pill so the
|
|
238
|
+
* UI is honest about what it doesn't know.
|
|
239
|
+
*/
|
|
240
|
+
type SourceConfidence = 'exact' | 'inferred' | 'package' | 'unknown';
|
|
6
241
|
/**
|
|
7
242
|
* Serialized value for safe transmission over WebSocket
|
|
8
243
|
*/
|
|
@@ -26,7 +261,7 @@ type SerializedValue = null | boolean | number | string | SerializedValue[] | {
|
|
|
26
261
|
/**
|
|
27
262
|
* Messages sent from runtime to extension
|
|
28
263
|
*/
|
|
29
|
-
type RuntimeMessage = RuntimeReadyMessage | RuntimeRenderMessage | RuntimePropsUpdateMessage | RuntimeNodePropsMessage | RuntimeZustandUpdateMessage | RuntimeReduxUpdateMessage | RuntimeRouterUpdateMessage | RuntimeContextUpdateMessage | RuntimeDisconnectMessage | RuntimeTreeSnapshotMessage | RuntimeTreeDiffMessage | RuntimeNodeHooksMessage | RuntimeNodeEffectsMessage | RuntimeDetailedRenderReasonMessage | RuntimeTimelineEventMessage | RuntimeTanStackQueryUpdateMessage | RuntimeRenderTriggerMessage | RuntimeRenderCascadeMessage | RuntimePropDrillingMessage | RuntimeActionStateMessage | RuntimeOptimisticDiffMessage | RuntimeNextjsContextMessage | RuntimeRscPayloadMessage | RuntimeHydrationEventMessage | RuntimeNetworkRequestMessage | RuntimeLocalStateCorrelationMessage | RuntimeValueTraceMessage | RuntimePongMessage;
|
|
264
|
+
type RuntimeMessage = RuntimeReadyMessage | RuntimeRenderMessage | RuntimePropsUpdateMessage | RuntimeNodePropsMessage | RuntimeZustandUpdateMessage | RuntimeReduxUpdateMessage | RuntimeRouterUpdateMessage | RuntimeContextUpdateMessage | RuntimeDisconnectMessage | RuntimeTreeSnapshotMessage | RuntimeTreeDiffMessage | RuntimeNodeHooksMessage | RuntimeNodeEffectsMessage | RuntimeDetailedRenderReasonMessage | RuntimeTimelineEventMessage | RuntimeTanStackQueryUpdateMessage | RuntimeRenderTriggerMessage | RuntimeRenderCascadeMessage | RuntimePropDrillingMessage | RuntimeActionStateMessage | RuntimeOptimisticDiffMessage | RuntimeNextjsContextMessage | RuntimeRscPayloadMessage | RuntimeHydrationEventMessage | RuntimeCallSiteMetricsMessage | RuntimeDuplicateKeyMessage | RuntimeNetworkRequestMessage | RuntimeLocalStateCorrelationMessage | RuntimeValueTraceMessage | RuntimePongMessage;
|
|
30
265
|
interface RuntimeReadyMessage {
|
|
31
266
|
type: 'runtime:ready';
|
|
32
267
|
appName?: string;
|
|
@@ -55,6 +290,14 @@ interface RuntimeReadyMessage {
|
|
|
55
290
|
* release script keeps runtime-core pinned identically, so a separate
|
|
56
291
|
* core-version field would be redundant. */
|
|
57
292
|
runtimeVersion?: string;
|
|
293
|
+
/**
|
|
294
|
+
* Milestone 8 Phase 5 — adoption signal for the JSX-runtime opt-in
|
|
295
|
+
* (`"jsxImportSource": "@flotrace/runtime-core"`). `true` when the dev
|
|
296
|
+
* runtime's global sentinel has been set (i.e. at least one user
|
|
297
|
+
* component went through `jsxDEV`). Desktop forwards to telemetry so
|
|
298
|
+
* the admin dashboard can track rollout rate. No PII; one-time boolean.
|
|
299
|
+
*/
|
|
300
|
+
jsxRuntimeActive?: boolean;
|
|
58
301
|
}
|
|
59
302
|
interface RuntimeRenderMessage {
|
|
60
303
|
type: 'runtime:render';
|
|
@@ -226,6 +469,22 @@ interface LiveTreeNode {
|
|
|
226
469
|
isLibrary?: boolean;
|
|
227
470
|
/** Short display label for the library source (e.g. 'framer', 'fontawesome', 'sonner') */
|
|
228
471
|
libraryName?: string;
|
|
472
|
+
/**
|
|
473
|
+
* Source attribution captured at JSX-creation time by the optional
|
|
474
|
+
* `@flotrace/runtime-core/jsx-dev-runtime` opt-in. Present only when the
|
|
475
|
+
* user has set `"jsxImportSource": "@flotrace/runtime-core"` in their
|
|
476
|
+
* tsconfig.json — the highest-confidence source signal available.
|
|
477
|
+
*
|
|
478
|
+
* When present, `filePath` / `lineNumber` are filled from `jsxSource` so
|
|
479
|
+
* existing consumers (click-to-IDE, breadcrumb path display) keep working
|
|
480
|
+
* without changes.
|
|
481
|
+
*/
|
|
482
|
+
jsxSource?: FlotraceJsxSource;
|
|
483
|
+
/**
|
|
484
|
+
* Confidence tier of the resolved source. See `SourceConfidence` doc-comment
|
|
485
|
+
* for the four tiers and what each means for the UI.
|
|
486
|
+
*/
|
|
487
|
+
sourceConfidence?: SourceConfidence;
|
|
229
488
|
}
|
|
230
489
|
/**
|
|
231
490
|
* Enhanced render reason with specific prop/state/context changes.
|
|
@@ -460,6 +719,8 @@ interface CascadeNode {
|
|
|
460
719
|
children: CascadeNode[];
|
|
461
720
|
depth: number;
|
|
462
721
|
isMemoized: boolean;
|
|
722
|
+
/** JSX-runtime attribution (Milestone 8 Phase 6) — mirror of shared LiveTreeNode field. */
|
|
723
|
+
jsxSource?: FlotraceJsxSource;
|
|
463
724
|
}
|
|
464
725
|
type LanePriority = 'sync' | 'discrete' | 'continuous' | 'default' | 'transition' | 'deferred' | 'idle' | 'offscreen';
|
|
465
726
|
interface LaneInfo {
|
|
@@ -494,6 +755,8 @@ interface PropDrillingChainNode {
|
|
|
494
755
|
role: 'source' | 'passthrough' | 'consumer';
|
|
495
756
|
hookCount: number;
|
|
496
757
|
hasContextHook: boolean;
|
|
758
|
+
/** JSX-runtime attribution (Milestone 8 Phase 6). */
|
|
759
|
+
jsxSource?: FlotraceJsxSource;
|
|
497
760
|
}
|
|
498
761
|
interface PropDrillingChain {
|
|
499
762
|
chainId: string;
|
|
@@ -521,21 +784,27 @@ interface RuntimePropDrillingMessage {
|
|
|
521
784
|
treeSize: number;
|
|
522
785
|
};
|
|
523
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* State of a single useActionState / useOptimistic hook instance on a fiber.
|
|
789
|
+
* Mirror of `ActionStateEntry` in flotrace-desktop's `shared/liveMessages.ts`
|
|
790
|
+
* — keep the field set in sync.
|
|
791
|
+
*/
|
|
792
|
+
interface ActionStateEntry {
|
|
793
|
+
hookIndex: number;
|
|
794
|
+
hookKind: 'action' | 'optimistic';
|
|
795
|
+
isPending: boolean;
|
|
796
|
+
state: SerializedValue;
|
|
797
|
+
error?: SerializedValue;
|
|
798
|
+
pendingSince?: number;
|
|
799
|
+
durationMs?: number;
|
|
800
|
+
}
|
|
524
801
|
/** Sent whenever a useActionState or useOptimistic hook changes on any fiber */
|
|
525
802
|
interface RuntimeActionStateMessage {
|
|
526
803
|
type: 'runtime:actionState';
|
|
527
804
|
nodeId: string;
|
|
528
805
|
componentName: string;
|
|
529
806
|
/** One entry per useActionState / useOptimistic hook on this fiber */
|
|
530
|
-
actions:
|
|
531
|
-
hookIndex: number;
|
|
532
|
-
hookKind: 'action' | 'optimistic';
|
|
533
|
-
isPending: boolean;
|
|
534
|
-
state: SerializedValue;
|
|
535
|
-
error?: SerializedValue;
|
|
536
|
-
pendingSince?: number;
|
|
537
|
-
durationMs?: number;
|
|
538
|
-
}>;
|
|
807
|
+
actions: ActionStateEntry[];
|
|
539
808
|
timestamp: number;
|
|
540
809
|
}
|
|
541
810
|
/** Sent when a useOptimistic value diverges from its underlying actual value */
|
|
@@ -557,12 +826,17 @@ interface RuntimeNextjsContextMessage {
|
|
|
557
826
|
initialRoute?: string;
|
|
558
827
|
timestamp: number;
|
|
559
828
|
}
|
|
829
|
+
/**
|
|
830
|
+
* RSC / Next.js cache header status. Mirror of the union in
|
|
831
|
+
* `shared/liveMessages.ts`'s `RscPayloadEntry.cacheStatus` — keep in sync.
|
|
832
|
+
*/
|
|
833
|
+
type RscCacheStatus = 'HIT' | 'MISS' | 'STALE' | 'unknown';
|
|
560
834
|
/** Sent when an RSC / Next.js data fetch is intercepted (metadata only, no values) */
|
|
561
835
|
interface RuntimeRscPayloadMessage {
|
|
562
836
|
type: 'runtime:rscPayload';
|
|
563
837
|
route: string;
|
|
564
838
|
payloadSizeBytes: number;
|
|
565
|
-
cacheStatus:
|
|
839
|
+
cacheStatus: RscCacheStatus;
|
|
566
840
|
timestamp: number;
|
|
567
841
|
}
|
|
568
842
|
/** Sent when React hydration completes or a mismatch is detected */
|
|
@@ -573,6 +847,48 @@ interface RuntimeHydrationEventMessage {
|
|
|
573
847
|
errorMessage?: string;
|
|
574
848
|
timestamp: number;
|
|
575
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* Per-callsite render frequency snapshot. Emitted at most once per second by
|
|
852
|
+
* the runtime when the JSX-runtime opt-in is active — the ring buffer in
|
|
853
|
+
* `jsxRuntimeUtils.ts` is the source of truth, this message is a periodic
|
|
854
|
+
* compaction of the buffer into "renders/sec over last 5s" so the desktop
|
|
855
|
+
* doesn't need to mirror the buffer.
|
|
856
|
+
*
|
|
857
|
+
* Independent of the React Profiler — works in concurrent rendering where the
|
|
858
|
+
* Profiler may miss commits. Only emitted when there's at least one callsite
|
|
859
|
+
* with non-zero recent activity (no metric-flood on idle apps).
|
|
860
|
+
*/
|
|
861
|
+
interface RuntimeCallSiteMetricsMessage {
|
|
862
|
+
type: 'runtime:callSiteMetrics';
|
|
863
|
+
/** Map of callSiteId → renders/sec over the last 5-second window. */
|
|
864
|
+
metrics: Record<string, number>;
|
|
865
|
+
timestamp: number;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Duplicate-key warning. Emitted by the JSX runtime when it observes the same
|
|
869
|
+
* `(callSiteId, key)` pair on two or more JSX calls within a single commit —
|
|
870
|
+
* the classic `{items.map(item => <Row key={item.id} />)}` pattern where
|
|
871
|
+
* `item.id` repeats. React logs a console warning for this; we surface it
|
|
872
|
+
* with full file:line attribution so the user can navigate directly to the
|
|
873
|
+
* map call site.
|
|
874
|
+
*
|
|
875
|
+
* One message per (callSiteId, duplicateKey) per emission window — the
|
|
876
|
+
* runtime de-duplicates so a list with 100 duplicate rows doesn't spam 100
|
|
877
|
+
* messages.
|
|
878
|
+
*/
|
|
879
|
+
interface RuntimeDuplicateKeyMessage {
|
|
880
|
+
type: 'runtime:duplicateKey';
|
|
881
|
+
/** The JSX call site that produced the duplicates. */
|
|
882
|
+
callSiteId: string;
|
|
883
|
+
fileName: string;
|
|
884
|
+
lineNumber: number;
|
|
885
|
+
columnNumber: number;
|
|
886
|
+
/** The key value that appeared more than once. */
|
|
887
|
+
duplicateKey: string;
|
|
888
|
+
/** How many times the same key fired at this call site in the commit. */
|
|
889
|
+
occurrences: number;
|
|
890
|
+
timestamp: number;
|
|
891
|
+
}
|
|
576
892
|
/** Metadata for a single intercepted network request. Privacy-first: no bodies, no query params, no auth headers. */
|
|
577
893
|
interface NetworkRequestEntry {
|
|
578
894
|
/** Incrementing request ID */
|
|
@@ -646,6 +962,13 @@ type TraceStep = {
|
|
|
646
962
|
/** If this step came via a rename edge in the drilling graph. */
|
|
647
963
|
renamedFrom?: string;
|
|
648
964
|
confidence: TraceConfidence;
|
|
965
|
+
/**
|
|
966
|
+
* JSX-runtime attribution of the PARENT fiber that wrote the value into
|
|
967
|
+
* this prop (Milestone 8 Phase 6). Captured by `readJsxSourceFromFiber`
|
|
968
|
+
* on the ancestor at trace-resolution time. Undefined when the parent
|
|
969
|
+
* fiber lacks attribution.
|
|
970
|
+
*/
|
|
971
|
+
callSiteOfParentJsx?: FlotraceJsxSource;
|
|
649
972
|
} | {
|
|
650
973
|
kind: 'hook-state';
|
|
651
974
|
nodeId: string;
|
|
@@ -1181,7 +1504,7 @@ declare function uninstallFiberTreeWalker(): void;
|
|
|
1181
1504
|
*
|
|
1182
1505
|
* Works across React 18 and 19 by probing different internal property names.
|
|
1183
1506
|
*/
|
|
1184
|
-
interface FiberLike {
|
|
1507
|
+
interface FiberLike$1 {
|
|
1185
1508
|
type: {
|
|
1186
1509
|
name?: string;
|
|
1187
1510
|
displayName?: string;
|
|
@@ -1194,7 +1517,7 @@ interface FiberLike {
|
|
|
1194
1517
|
displayName?: string;
|
|
1195
1518
|
};
|
|
1196
1519
|
} | ((...args: unknown[]) => unknown) | string | null;
|
|
1197
|
-
return: FiberLike | null;
|
|
1520
|
+
return: FiberLike$1 | null;
|
|
1198
1521
|
tag: number;
|
|
1199
1522
|
}
|
|
1200
1523
|
/**
|
|
@@ -1204,16 +1527,16 @@ interface FiberLike {
|
|
|
1204
1527
|
* Strategy 2 (React 19): __CLIENT_INTERNALS...owner field (renamed + flattened)
|
|
1205
1528
|
* Both return the fiber currently being rendered (null between renders).
|
|
1206
1529
|
*/
|
|
1207
|
-
declare function getCurrentRenderingFiber(): FiberLike | null;
|
|
1530
|
+
declare function getCurrentRenderingFiber(): FiberLike$1 | null;
|
|
1208
1531
|
/**
|
|
1209
1532
|
* Extract display name from a fiber node.
|
|
1210
1533
|
*/
|
|
1211
|
-
declare function getComponentNameFromFiber(fiber: FiberLike): string | null;
|
|
1534
|
+
declare function getComponentNameFromFiber(fiber: FiberLike$1): string | null;
|
|
1212
1535
|
/**
|
|
1213
1536
|
* Walk up the fiber tree to build an ancestor chain of component names.
|
|
1214
1537
|
* Stops after 10 levels to prevent excessive traversal.
|
|
1215
1538
|
*/
|
|
1216
|
-
declare function buildAncestorChain(fiber: FiberLike): string[];
|
|
1539
|
+
declare function buildAncestorChain(fiber: FiberLike$1): string[];
|
|
1217
1540
|
|
|
1218
1541
|
/**
|
|
1219
1542
|
* Hook Inspector for @flotrace/runtime
|
|
@@ -1714,4 +2037,99 @@ declare function installRscPayloadInterceptor(client: FloTraceWebSocketClient):
|
|
|
1714
2037
|
/** Remove the RSC payload interceptor and restore original fetch */
|
|
1715
2038
|
declare function uninstallRscPayloadInterceptor(): void;
|
|
1716
2039
|
|
|
1717
|
-
|
|
2040
|
+
/**
|
|
2041
|
+
* Fiber debug recorder — buffers fiber + LiveTreeNode observations into
|
|
2042
|
+
* capped in-memory tables, then dumps them to the console *on demand*.
|
|
2043
|
+
*
|
|
2044
|
+
* Why not log eagerly? `getComponentName` runs once per fiber per commit.
|
|
2045
|
+
* A 200-node tree at 60Hz produces ~12k console entries/sec, which OOMs
|
|
2046
|
+
* DevTools. The recorder keeps recording silent until you ask for a dump.
|
|
2047
|
+
*
|
|
2048
|
+
* Usage (from any console — Next.js app, FloTrace renderer, Electron main):
|
|
2049
|
+
* globalThis.__FT_DEBUG = true // start recording
|
|
2050
|
+
* __ft.dump() // print digest (fibers + snapshots)
|
|
2051
|
+
* __ft.fibers() // just the fiber-name table
|
|
2052
|
+
* __ft.snapshots() // just the snapshot timeline
|
|
2053
|
+
* __ft.tail(20) // recent activity
|
|
2054
|
+
* __ft.clear() // reset buffers
|
|
2055
|
+
* __ft.export() // returns a JSON object
|
|
2056
|
+
* __ft.download() // browser-only: save buffers as JSON
|
|
2057
|
+
* __ft.size() // current buffer sizes
|
|
2058
|
+
*
|
|
2059
|
+
* All recorders are no-ops when __FT_DEBUG is falsy, so the wired call
|
|
2060
|
+
* sites cost ~1 boolean check each.
|
|
2061
|
+
*/
|
|
2062
|
+
|
|
2063
|
+
interface FiberRecord {
|
|
2064
|
+
/** Resolved component name (what FloTrace ultimately shows on the node). */
|
|
2065
|
+
name: string;
|
|
2066
|
+
/** Raw `fiber.type.name` — present even for ForwardRef/Memo's inner render. */
|
|
2067
|
+
rawName: string | undefined;
|
|
2068
|
+
/** Raw `fiber.type.displayName` — what the author explicitly set. */
|
|
2069
|
+
rawDisplayName: string | undefined;
|
|
2070
|
+
kind: 'function' | 'class' | 'forwardRef' | 'memo' | 'host' | 'unknown';
|
|
2071
|
+
/** Fiber tag number (0=Function, 1=Class, 11=ForwardRef, 14=Memo, etc.). */
|
|
2072
|
+
fiberTag: number | undefined;
|
|
2073
|
+
looksMinified: boolean;
|
|
2074
|
+
count: number;
|
|
2075
|
+
contexts: Set<string>;
|
|
2076
|
+
firstSeen: number;
|
|
2077
|
+
lastSeen: number;
|
|
2078
|
+
/** First captured _debugSource (if any) — only available in dev builds. */
|
|
2079
|
+
source?: {
|
|
2080
|
+
fileName?: string;
|
|
2081
|
+
lineNumber?: number;
|
|
2082
|
+
};
|
|
2083
|
+
/** First non-null react key observed (useful for diagnosing list rows). */
|
|
2084
|
+
exampleKey?: string;
|
|
2085
|
+
}
|
|
2086
|
+
interface TreeRecord {
|
|
2087
|
+
ts: number;
|
|
2088
|
+
ctx: string;
|
|
2089
|
+
rootName: string;
|
|
2090
|
+
totalNodes: number;
|
|
2091
|
+
maxDepth: number;
|
|
2092
|
+
minifiedLike: number;
|
|
2093
|
+
topNames: string;
|
|
2094
|
+
}
|
|
2095
|
+
declare global {
|
|
2096
|
+
var __FT_DEBUG: boolean | undefined;
|
|
2097
|
+
var __ft: FtConsoleApi | undefined;
|
|
2098
|
+
}
|
|
2099
|
+
declare function setFiberDebug(enabled: boolean): void;
|
|
2100
|
+
type FiberLike = {
|
|
2101
|
+
tag?: number;
|
|
2102
|
+
type?: unknown;
|
|
2103
|
+
key?: string | null;
|
|
2104
|
+
_debugSource?: {
|
|
2105
|
+
fileName?: string;
|
|
2106
|
+
lineNumber?: number;
|
|
2107
|
+
} | null;
|
|
2108
|
+
};
|
|
2109
|
+
declare function describeFiberType(fiber: FiberLike): {
|
|
2110
|
+
kind: FiberRecord['kind'];
|
|
2111
|
+
name: string | undefined;
|
|
2112
|
+
displayName: string | undefined;
|
|
2113
|
+
resolved: string;
|
|
2114
|
+
looksMinified: boolean;
|
|
2115
|
+
};
|
|
2116
|
+
declare function logTreeSnapshot(tree: LiveTreeNode | null, context?: string): void;
|
|
2117
|
+
declare const logTreeSummary: typeof logTreeSnapshot;
|
|
2118
|
+
interface FtConsoleApi {
|
|
2119
|
+
dump(): void;
|
|
2120
|
+
fibers(): void;
|
|
2121
|
+
snapshots(): void;
|
|
2122
|
+
tail(n?: number): void;
|
|
2123
|
+
clear(): void;
|
|
2124
|
+
size(): {
|
|
2125
|
+
fibers: number;
|
|
2126
|
+
snapshots: number;
|
|
2127
|
+
};
|
|
2128
|
+
export(): {
|
|
2129
|
+
fibers: Array<Record<string, unknown>>;
|
|
2130
|
+
snapshots: TreeRecord[];
|
|
2131
|
+
};
|
|
2132
|
+
download(filename?: string): void;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
export { type ActionStateEntry, DEFAULT_CONFIG, type DetailedRenderReason, type DetailedRenderReasonType, type DuplicateKeyEvent, type EffectInfo, FLOTRACE_SOURCE, type Fiber$1 as Fiber, type FiberEffect, type FiberHookState, type FiberTreeWalkerOptions, type FloTraceConfig, FloTraceWebSocketClient, type FlotraceJsxSource, type FrameworkInfo, type HookInfo, type HookType, JSX_RUNTIME_ACTIVE_KEY, type LiveTreeNode, type MutationCorrelation, type NetworkRequestEntry, type PropChange, type ReduxStoreApi, type ResolvedFloTraceConfig, type RscCacheStatus, type RuntimeActionStateMessage, type RuntimeCallSiteMetricsMessage, type RuntimeDuplicateKeyMessage, type RuntimeHydrationEventMessage, type RuntimeNextjsContextMessage, type RuntimeOptimisticDiffMessage, type RuntimeRscPayloadMessage, type RuntimeTreeDiffMessage, type RuntimeValueTraceMessage, type SerializedValue, type SourceConfidence, type TanStackMutationInfo, type TanStackQueryClientApi, type TanStackQueryEvent, type TanStackQueryInfo, type TimelineEvent, type TimelineEventType, type TraceConfidence, type TraceStep, type TrackingOptions, type ValueTrace, type ValueTraceInput, type ZustandStoreApi, buildAncestorChain, clearCallSiteRenders, clearFetchOriginTags, computeCallSiteId, computeCallSiteMetricsPayload, describeFiberType, detectInlineLiterals, detectServerComponent, detectWebFramework, disposeWebSocketClient, findFetchOrigin, getCallSiteRenderRate, getCallSiteRenders, getChangedKeys, getComponentNameFromFiber, getCurrentRenderingFiber, getDetailedRenderReason, getFiberRefMap, getNodeEffects, getNodeHooks, getNodeProps, getReduxSnapshot, getTanstackSnapshot, getTimeline, getWebSocketClient, getZustandSnapshot, hasActiveTags, inspectEffects, inspectHooks, installFiberTreeWalker, installReduxTracker, installRscPayloadInterceptor, installTanStackQueryTracker, installTimelineTracker, installZustandTracker, isJsxRuntimeActive, isReduxStore, isTanStackQueryClient, isUserComponent, logTreeSnapshot, logTreeSummary, markJsxRuntimeActive, maybeEmitNextjsContext, normalizeJsxSourcePath, recordCallSiteRender, recordTimelineEvent, requestFullSnapshot, requestTreeSnapshot, resetNextjsDetection, resolveValueTrace, serializeProps, serializeValue, setDuplicateKeyEmitter, setFiberDebug, tagFetchData, uninstallFiberTreeWalker, uninstallReduxTracker, uninstallRscPayloadInterceptor, uninstallTanStackQueryTracker, uninstallTimelineTracker, uninstallZustandTracker };
|