@atlassian/chippy 0.0.0-stage → 0.0.1-beta.2

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.
Files changed (133) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +276 -2
  3. package/bin/chippy.mjs +92 -0
  4. package/bin/pack.mjs +204 -0
  5. package/bin/skill-dir.mjs +15 -0
  6. package/lib/avalanche.js +2601 -0
  7. package/lib/avalanche.js.map +7 -0
  8. package/lib/bootstrap.d.ts +1 -0
  9. package/lib/bootstrap.js +70 -0
  10. package/lib/bootstrap.js.map +7 -0
  11. package/lib/browser.js +15 -0
  12. package/lib/browser.js.map +7 -0
  13. package/lib/extension/avalanche.es.js +5535 -0
  14. package/lib/extension/avalanche.es.js.map +1 -0
  15. package/lib/extension/chippy-page-bridge.js +1953 -0
  16. package/lib/extension/inspect-react.js +216 -0
  17. package/lib/extension/layout-lab.js +1453 -0
  18. package/lib/extension/panel-dom.js +90 -0
  19. package/lib/extension/resolve-probes.js +342 -0
  20. package/lib/mcp-skill/LICENSE +201 -0
  21. package/lib/mcp-skill/README.md +148 -0
  22. package/lib/mcp-skill/SKILL.md +335 -0
  23. package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
  24. package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
  25. package/lib/mcp-skill/extension/armed-reload-startup.js +83 -0
  26. package/lib/mcp-skill/extension/avalanche.es.js +5535 -0
  27. package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
  28. package/lib/mcp-skill/extension/avalanche.iife.js +5536 -0
  29. package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
  30. package/lib/mcp-skill/extension/background.js +325 -0
  31. package/lib/mcp-skill/extension/chippy-page-bridge.js +1953 -0
  32. package/lib/mcp-skill/extension/content-script.js +190 -0
  33. package/lib/mcp-skill/extension/devtools-hook-shim.js +85 -0
  34. package/lib/mcp-skill/extension/early-bootstrap.js +49 -0
  35. package/lib/mcp-skill/extension/icon-128.png +0 -0
  36. package/lib/mcp-skill/extension/icon-16.png +0 -0
  37. package/lib/mcp-skill/extension/icon-32.png +0 -0
  38. package/lib/mcp-skill/extension/icon-48.png +0 -0
  39. package/lib/mcp-skill/extension/inspect-react.js +216 -0
  40. package/lib/mcp-skill/extension/layout-lab.js +1453 -0
  41. package/lib/mcp-skill/extension/logo.svg +14 -0
  42. package/lib/mcp-skill/extension/manifest.json +69 -0
  43. package/lib/mcp-skill/extension/panel-dom.js +90 -0
  44. package/lib/mcp-skill/extension/panel-settings.js +173 -0
  45. package/lib/mcp-skill/extension/popup.html +41 -0
  46. package/lib/mcp-skill/extension/popup.js +80 -0
  47. package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
  48. package/lib/mcp-skill/extension/resolve-probes.js +342 -0
  49. package/lib/mcp-skill/extension/startup-handoff.js +30 -0
  50. package/lib/mcp-skill/package.json +1 -0
  51. package/lib/mcp-skill/references/README.md +56 -0
  52. package/lib/mcp-skill/references/active-stimulus.md +85 -0
  53. package/lib/mcp-skill/references/avalanche-detector.md +731 -0
  54. package/lib/mcp-skill/references/bdd-router.md +310 -0
  55. package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
  56. package/lib/mcp-skill/references/command-output-formats.md +1085 -0
  57. package/lib/mcp-skill/references/command-surface.md +183 -0
  58. package/lib/mcp-skill/references/cynefin.md +99 -0
  59. package/lib/mcp-skill/references/debug-hook.md +161 -0
  60. package/lib/mcp-skill/references/fixing-performance.md +490 -0
  61. package/lib/mcp-skill/references/hook-metrics.md +199 -0
  62. package/lib/mcp-skill/references/hooks-inspection.md +61 -0
  63. package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
  64. package/lib/mcp-skill/references/mcp.md +122 -0
  65. package/lib/mcp-skill/references/mode-a-injection.md +102 -0
  66. package/lib/mcp-skill/references/operating-model.md +128 -0
  67. package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
  68. package/lib/mcp-skill/references/playwright-probes.md +205 -0
  69. package/lib/mcp-skill/references/procedures.md +545 -0
  70. package/lib/mcp-skill/references/recipes.txt +114 -0
  71. package/lib/mcp-skill/references/stability-render.md +428 -0
  72. package/lib/mcp-skill/references/test-mode-playbook.md +194 -0
  73. package/lib/mcp-skill/references/troubleshooting.md +59 -0
  74. package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
  75. package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
  76. package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
  77. package/lib/mcp-skill/scripts/chippy-helper-server.js +1768 -0
  78. package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
  79. package/lib/mcp-skill/scripts/chippy.sh +1252 -0
  80. package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
  81. package/lib/mcp-skill/scripts/helper-access.js +158 -0
  82. package/lib/mcp-skill/scripts/helper-proxy.js +87 -0
  83. package/lib/mcp-skill/scripts/page-socket-trust.js +81 -0
  84. package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
  85. package/lib/mcp-skill/scripts/search-recipes.js +248 -0
  86. package/lib/mcp-skill/snippets/README.md +89 -0
  87. package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
  88. package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
  89. package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
  90. package/lib/mcp-skill/snippets/find-prop.js +83 -0
  91. package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
  92. package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
  93. package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
  94. package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
  95. package/lib/package.json +1 -0
  96. package/lib/plugin.js +119 -0
  97. package/lib/plugin.js.map +7 -0
  98. package/lib/storybook/avalanche-panel-model.js +79 -0
  99. package/lib/storybook/brand.jsx +6 -0
  100. package/lib/storybook/connection-fallback.jsx +47 -0
  101. package/lib/storybook/constants.js +15 -0
  102. package/lib/storybook/context-card.jsx +171 -0
  103. package/lib/storybook/manager-owner.js +42 -0
  104. package/lib/storybook/manager.jsx +425 -0
  105. package/lib/storybook/preset.js +7 -0
  106. package/lib/storybook/preview.js +298 -0
  107. package/lib/storybook/probe-annotation-editor.jsx +52 -0
  108. package/lib/storybook/probe-intent.js +23 -0
  109. package/lib/storybook/setup.js +104 -0
  110. package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
  111. package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
  112. package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
  113. package/lib/types/avalanche/detector.d.ts +183 -0
  114. package/lib/types/avalanche/interaction-capture.d.ts +25 -0
  115. package/lib/types/avalanche/internals.d.ts +189 -0
  116. package/lib/types/avalanche/report-serializer.d.ts +283 -0
  117. package/lib/types/avalanche/reporter.d.ts +610 -0
  118. package/lib/types/chippy-bridge/fiber-source.d.ts +48 -0
  119. package/lib/types/chippy-bridge/react-renderer.d.ts +74 -0
  120. package/lib/types/chippy-bridge/recorded-source.d.ts +44 -0
  121. package/lib/types/chippy-bridge/source-map.d.ts +30 -0
  122. package/lib/types/debug-render/core.d.ts +78 -0
  123. package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
  124. package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
  125. package/lib/types/layout-lab/marker-context.d.ts +94 -0
  126. package/lib/types/package/avalanche.d.ts +6 -0
  127. package/lib/types/package/browser.d.ts +10 -0
  128. package/lib/types/package/plugin.d.ts +15 -0
  129. package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
  130. package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
  131. package/lib/types/plugins/index.d.ts +49 -0
  132. package/lib/types/plugins/jira/context-provider.d.ts +36 -0
  133. package/package.json +106 -4
@@ -0,0 +1,2601 @@
1
+ // src/chippy-bridge/source-map.ts
2
+ var DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ var VALUE_OF = new Int8Array(128).fill(-1);
4
+ for (let index = 0; index < DIGITS.length; index += 1) {
5
+ VALUE_OF[DIGITS.charCodeAt(index)] = index;
6
+ }
7
+
8
+ // src/chippy-bridge/fiber-source.ts
9
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?$/;
10
+ var AT_FRAME = /^\s*(?:(.*?)@)(.+?):(\d+):(\d+)$/;
11
+ var MAX_CALL_SITE_FRAMES = 4;
12
+ var resolvedFiberSources = /* @__PURE__ */ new WeakMap();
13
+ function parseStackFrames(stack) {
14
+ const frames = [];
15
+ for (const raw of stack.split("\n")) {
16
+ const line = raw.trimEnd();
17
+ if (line === "" || line.includes("(eval at ")) continue;
18
+ const match = V8_FRAME.exec(line) ?? AT_FRAME.exec(line);
19
+ if (!match) continue;
20
+ const [, name, url = "", lineDigits = "", columnDigits = ""] = match;
21
+ if (url === "" || url.startsWith("data:")) continue;
22
+ const at = Number(lineDigits);
23
+ const column = Number(columnDigits);
24
+ if (!Number.isFinite(at) || !Number.isFinite(column) || at < 1) continue;
25
+ frames.push({
26
+ url,
27
+ line: at,
28
+ column,
29
+ ...name !== void 0 && name !== "" ? { function: name } : {}
30
+ });
31
+ }
32
+ return frames;
33
+ }
34
+ function isVendorPath(path) {
35
+ return path.includes("node_modules") || path.includes("/.vite/deps/") || path.includes("/.yarn/");
36
+ }
37
+ function rawLocation(frame) {
38
+ return { fileName: frame.url, lineNumber: frame.line, columnNumber: frame.column };
39
+ }
40
+ function locationFromOriginal(original) {
41
+ return { fileName: original.source, lineNumber: original.line, columnNumber: original.column };
42
+ }
43
+ function writerLocationOf(frames, originalFor) {
44
+ for (const frame of frames) {
45
+ if (isVendorPath(frame.url)) continue;
46
+ const original = originalFor(frame);
47
+ if (original === null) return rawLocation(frame);
48
+ if (isVendorPath(original.source)) continue;
49
+ return locationFromOriginal(original);
50
+ }
51
+ return null;
52
+ }
53
+ function debugSourceOf(value) {
54
+ if (!value || typeof value !== "object") return null;
55
+ const source = value;
56
+ if (typeof source.fileName !== "string" || typeof source.lineNumber !== "number") return null;
57
+ return {
58
+ fileName: source.fileName,
59
+ lineNumber: source.lineNumber,
60
+ columnNumber: typeof source.columnNumber === "number" ? source.columnNumber : null
61
+ };
62
+ }
63
+ function recordedSource(fiber) {
64
+ const direct = debugSourceOf(fiber._debugSource);
65
+ if (direct) return direct;
66
+ const type = fiber.type;
67
+ return type && (typeof type === "object" || typeof type === "function") ? debugSourceOf(type._debugSource) : null;
68
+ }
69
+ function getFiberStackFrames(fiber) {
70
+ if (!fiber || !fiber._debugStack || typeof fiber._debugStack !== "object") return [];
71
+ const stack = fiber._debugStack.stack;
72
+ if (typeof stack !== "string" || stack === "") return [];
73
+ const candidates = [];
74
+ for (const frame of parseStackFrames(stack)) {
75
+ if (isVendorPath(frame.url)) continue;
76
+ candidates.push(frame);
77
+ if (candidates.length >= MAX_CALL_SITE_FRAMES) break;
78
+ }
79
+ return candidates;
80
+ }
81
+ function getFiberSourceCandidate(fiber) {
82
+ if (!fiber) return null;
83
+ const recorded = recordedSource(fiber);
84
+ if (recorded) return recorded;
85
+ return writerLocationOf(getFiberStackFrames(fiber), () => null);
86
+ }
87
+ function getFiberSource(fiber) {
88
+ if (!fiber) return null;
89
+ const cached = resolvedFiberSources.get(fiber);
90
+ if (cached !== void 0) return cached;
91
+ return getFiberSourceCandidate(fiber);
92
+ }
93
+
94
+ // src/avalanche/internals.tsx
95
+ var PerformedWork = 1;
96
+ var Placement = 2;
97
+ var Hydrating = 4096;
98
+ var COMPONENT_TAGS = /* @__PURE__ */ new Set([0, 1, 2, 11, 14, 15]);
99
+ var CONTEXT_PROVIDER_TAG = 10;
100
+ var SuspenseTag = 13;
101
+ var ChildDeletion = 16;
102
+ var Update = 4;
103
+ function isComponentFiber(tag) {
104
+ return COMPONENT_TAGS.has(tag);
105
+ }
106
+ function isContextProviderFiber(tag) {
107
+ return tag === CONTEXT_PROVIDER_TAG;
108
+ }
109
+ function isSuspenseFiber(tag) {
110
+ return tag === SuspenseTag;
111
+ }
112
+ function didSuspenseStateChange(fiber) {
113
+ const currSuspended = fiber.memoizedState !== null;
114
+ if (!fiber.alternate) {
115
+ if (currSuspended) {
116
+ return { entered: true, left: false };
117
+ }
118
+ return null;
119
+ }
120
+ const prevSuspended = fiber.alternate.memoizedState !== null;
121
+ if (prevSuspended === currSuspended) return null;
122
+ return { entered: currSuspended, left: prevSuspended };
123
+ }
124
+ function didContextValueChange(fiber) {
125
+ if (fiber.tag !== CONTEXT_PROVIDER_TAG || !fiber.alternate) return false;
126
+ const alternateFiber = fiber.alternate;
127
+ const currentFiber = fiber;
128
+ const prevProps = alternateFiber.memoizedProps;
129
+ const currentProps = currentFiber.memoizedProps;
130
+ return prevProps && currentProps && !Object.is(prevProps.value, currentProps.value);
131
+ }
132
+ function isReactInternalComponentType(type) {
133
+ return typeof type === "symbol";
134
+ }
135
+ function getComponentName(fiber) {
136
+ const type = fiber.type;
137
+ const elementType = fiber.elementType;
138
+ const named = type?.displayName || type?.name || elementType?.displayName || elementType?.name;
139
+ if (named) return named;
140
+ const source = getFiberSourceCandidate(fiber);
141
+ return source && `<${source.fileName}:${source.lineNumber}>` || "Anonymous";
142
+ }
143
+ function isStateHook(hook) {
144
+ const queue = hook.queue;
145
+ if (!queue) return false;
146
+ return typeof queue.dispatch === "function" || "value" in queue && "getSnapshot" in queue;
147
+ }
148
+ function isExternalStoreHook(hook) {
149
+ const queue = hook.queue;
150
+ return queue && typeof queue.dispatch !== "function" && "value" in queue && "getSnapshot" in queue;
151
+ }
152
+ function detectHookStateChanges(prevHookChain, currentHookChain) {
153
+ if (prevHookChain === currentHookChain) {
154
+ return {
155
+ hasChange: false,
156
+ actionable: false,
157
+ changedIndices: [],
158
+ changedHooks: [],
159
+ hasExternalStoreChange: false
160
+ };
161
+ }
162
+ let prevHook = prevHookChain;
163
+ let currentHook = currentHookChain;
164
+ let memoizedStateIndex = 0;
165
+ const changedIndices = [];
166
+ const changedHooks = [];
167
+ let hasExternalStoreChange = false;
168
+ while (prevHook && currentHook) {
169
+ if (isStateHook(currentHook)) {
170
+ const prevValueRaw = prevHook.memoizedState === void 0 ? prevHook.queue?.lastRenderedState : prevHook.memoizedState;
171
+ const curValueRaw = currentHook.memoizedState === void 0 ? currentHook.queue?.lastRenderedState : currentHook.memoizedState;
172
+ if (!Object.is(prevValueRaw, curValueRaw)) {
173
+ const isExternal = isExternalStoreHook(currentHook);
174
+ if (isExternal) hasExternalStoreChange = true;
175
+ changedIndices.push(memoizedStateIndex);
176
+ changedHooks.push({
177
+ prevValue: prevValueRaw,
178
+ currentValue: curValueRaw,
179
+ ...isExternal && { isExternalStore: true }
180
+ });
181
+ }
182
+ }
183
+ prevHook = prevHook.next;
184
+ currentHook = currentHook.next;
185
+ memoizedStateIndex++;
186
+ }
187
+ return {
188
+ hasChange: changedIndices.length > 0,
189
+ actionable: changedIndices.length > 0,
190
+ changedIndices,
191
+ changedHooks,
192
+ hasExternalStoreChange
193
+ };
194
+ }
195
+ function detectHookThrashing(prevHookChain, currentHookChain, mode = "non-primitive", valueProvenance = null) {
196
+ if (prevHookChain === currentHookChain) {
197
+ return { effects: [], callbacks: [], memos: [] };
198
+ }
199
+ const thrashedEffects = [];
200
+ const thrashedCallbacks = [];
201
+ const thrashedMemos = [];
202
+ let prevHook = prevHookChain;
203
+ let currentHook = currentHookChain;
204
+ let hookIndex = 0;
205
+ while (prevHook && currentHook) {
206
+ if (!isStateHook(currentHook)) {
207
+ const prevValue = prevHook.memoizedState;
208
+ const currentValue = currentHook.memoizedState;
209
+ let prevDeps = null;
210
+ let currentDeps = null;
211
+ let hookType = null;
212
+ if (prevValue && currentValue && typeof prevValue === "object" && typeof currentValue === "object" && "deps" in prevValue && "deps" in currentValue && "tag" in currentValue) {
213
+ prevDeps = prevValue.deps;
214
+ currentDeps = currentValue.deps;
215
+ hookType = "effect";
216
+ } else if (Array.isArray(prevValue) && Array.isArray(currentValue) && prevValue.length === 2 && currentValue.length === 2 && Array.isArray(prevValue[1]) && Array.isArray(currentValue[1])) {
217
+ prevDeps = prevValue[1];
218
+ currentDeps = currentValue[1];
219
+ hookType = typeof currentValue[0] === "function" ? "callback" : "memo";
220
+ }
221
+ if (hookType && Array.isArray(prevDeps) && Array.isArray(currentDeps)) {
222
+ const changedDepIndices = [];
223
+ const changedDepOrigins = [];
224
+ for (let j = 0; j < currentDeps.length; j++) {
225
+ if (!Object.is(prevDeps[j], currentDeps[j])) {
226
+ changedDepIndices.push(j);
227
+ let origin = null;
228
+ const dep = currentDeps[j];
229
+ if (valueProvenance && dep != null && (typeof dep === "object" || typeof dep === "function")) {
230
+ origin = valueProvenance.get(dep) || null;
231
+ }
232
+ changedDepOrigins.push(origin);
233
+ }
234
+ }
235
+ let shouldReport = changedDepIndices.length > 0;
236
+ if (shouldReport && mode === "non-primitive") {
237
+ shouldReport = changedDepIndices.some((idx) => {
238
+ const dep = currentDeps[idx];
239
+ const t = typeof dep;
240
+ return !(dep == null || t === "string" || t === "number" || t === "boolean" || t === "symbol" || t === "bigint");
241
+ });
242
+ }
243
+ if (shouldReport) {
244
+ const thrashInfo = {
245
+ hookIndex,
246
+ hookType,
247
+ deps: currentDeps,
248
+ changedDepIndices,
249
+ changedDepOrigins
250
+ };
251
+ if (hookType === "effect") {
252
+ const effectValue = currentValue;
253
+ thrashInfo.effectTag = effectValue.tag;
254
+ if (effectValue.create) {
255
+ thrashInfo.hookBody = effectValue.create;
256
+ }
257
+ thrashedEffects.push(thrashInfo);
258
+ } else if (hookType === "callback") {
259
+ const callbackFn = currentValue[0];
260
+ thrashInfo.hookBody = callbackFn;
261
+ thrashedCallbacks.push(thrashInfo);
262
+ } else {
263
+ const memoValue = currentValue[0];
264
+ thrashInfo.memoValue = memoValue;
265
+ thrashInfo.hookBody = memoValue;
266
+ thrashedMemos.push(thrashInfo);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ prevHook = prevHook.next;
272
+ currentHook = currentHook.next;
273
+ hookIndex++;
274
+ }
275
+ return {
276
+ effects: thrashedEffects,
277
+ callbacks: thrashedCallbacks,
278
+ memos: thrashedMemos
279
+ };
280
+ }
281
+ function didFiberUpdate(fiber) {
282
+ return (fiber.flags & PerformedWork) !== 0;
283
+ }
284
+ function didFiberSelfUpdate(fiber) {
285
+ if (!didFiberUpdate(fiber)) return null;
286
+ const alt = fiber.alternate;
287
+ if (!alt) return null;
288
+ const prevState = alt.memoizedState;
289
+ const currentState = fiber.memoizedState;
290
+ const result = detectHookStateChanges(prevState, currentState);
291
+ return result.hasChange ? result : null;
292
+ }
293
+ function isDevelopment() {
294
+ return true;
295
+ }
296
+
297
+ // src/avalanche/commit-stats-indices.ts
298
+ var CS_RESTARTS = 2;
299
+ var CS_UNMOUNTED_COMPONENTS = 8;
300
+ var CS_UNMOUNTED_SUBTREES = 9;
301
+
302
+ // src/avalanche/report-serializer.tsx
303
+ function serializeReport(report) {
304
+ const sources = report.sources.map((source) => ({
305
+ sourceId: source.sourceId,
306
+ depth: source.depth,
307
+ causedUpdates: source.causedUpdates,
308
+ collateralUpdates: source.collateralUpdates,
309
+ maxCollateralUpdates: source.maxCollateralUpdates,
310
+ maxPenetrationDepth: source.maxPenetrationDepth,
311
+ ...source.restartWaste !== void 0 && { restartWaste: source.restartWaste },
312
+ ...source.maxRestartWaste !== void 0 && { maxRestartWaste: source.maxRestartWaste }
313
+ }));
314
+ const sourcesByCommit = report.sourcesByCommit ? Object.fromEntries(
315
+ Object.entries(report.sourcesByCommit).map(([commitId, commitSources]) => [
316
+ commitId,
317
+ commitSources.map((src) => ({
318
+ sourceId: src.sourceId,
319
+ depth: src.depth,
320
+ collateral: src.collateral,
321
+ rank: src.rank,
322
+ sourceIndex: src.sourceIndex,
323
+ ...(src.trashers || src.hooks) && { trashers: src.trashers || src.hooks },
324
+ ...src.isRepeat && { isRepeat: true }
325
+ }))
326
+ ])
327
+ ) : void 0;
328
+ const suspenseByCommit = {};
329
+ if (report.suspenseByCommit) {
330
+ for (const [k, evts] of Object.entries(report.suspenseByCommit)) {
331
+ suspenseByCommit[Number(k)] = evts.map((e) => ({
332
+ depth: e.depth,
333
+ entered: e.entered,
334
+ left: e.left
335
+ }));
336
+ }
337
+ }
338
+ return {
339
+ meta: report.meta,
340
+ sources,
341
+ commitStats: report.commitStats,
342
+ suspenseByCommit,
343
+ ...sourcesByCommit && { sourcesByCommit },
344
+ ...report.interactionsByCommit && { interactionsByCommit: report.interactionsByCommit },
345
+ ...report.renderedByCommit && { renderedByCommit: report.renderedByCommit },
346
+ ...(report.metricsByCommit || report.atlByCommit) && {
347
+ metricsByCommit: report.metricsByCommit || report.atlByCommit,
348
+ atlByCommit: report.metricsByCommit || report.atlByCommit
349
+ },
350
+ ...report.timing && { timing: report.timing }
351
+ };
352
+ }
353
+
354
+ // src/avalanche/commit-metrics-adapter.ts
355
+ var active = null;
356
+ var REQUIRED_METHODS = ["isAvailable", "snapshot", "measure", "attach", "isAttached", "reattach", "detach"];
357
+ function registerCommitMetricsAdapter(adapter) {
358
+ if (!adapter || typeof adapter.id !== "string" || !adapter.id) {
359
+ throw new TypeError("registerCommitMetricsAdapter expects an adapter with a non-empty id");
360
+ }
361
+ for (const method of REQUIRED_METHODS) {
362
+ if (typeof adapter[method] !== "function") {
363
+ throw new TypeError(`commit metrics adapter ${adapter.id} is missing ${method}()`);
364
+ }
365
+ }
366
+ active = adapter;
367
+ return () => {
368
+ if (active === adapter) active = null;
369
+ };
370
+ }
371
+ function getCommitMetricsAdapter() {
372
+ return active;
373
+ }
374
+ function adapterIdFromMode(mode) {
375
+ return typeof mode === "string" && mode.startsWith("adapter:") ? mode.slice("adapter:".length) : null;
376
+ }
377
+
378
+ // src/avalanche/reporter.tsx
379
+ function isTrueSource(s) {
380
+ const commits = Object.values(s.updateHistory);
381
+ if (commits.length === 0) return false;
382
+ for (let i = 0; i < commits.length; i++) {
383
+ const entry = commits[i];
384
+ if (!entry || entry === "repeat") {
385
+ } else if (typeof entry === "object" && entry !== null) {
386
+ if (Object.values(entry).some((hook) => hook !== "repeat")) {
387
+ return true;
388
+ }
389
+ }
390
+ }
391
+ return false;
392
+ }
393
+ function buildReport(state, thrashMode, topN, sortBy, options = {}, detectorConfig) {
394
+ const { minCollateral = 1 } = options;
395
+ const toHumanReadableId = (internalId, type, depth) => {
396
+ const componentName = getComponentName({ type });
397
+ const match = internalId.match(/#(\d+)@/);
398
+ const instanceNum = match ? match[1] : "1";
399
+ return `${componentName}#${instanceNum}@${depth}`;
400
+ };
401
+ const idMapping = /* @__PURE__ */ new Map();
402
+ const allSources = Array.from(state.sources.entries()).map(([internalId, s]) => {
403
+ const humanId = toHumanReadableId(internalId, s.type, s.depth);
404
+ idMapping.set(internalId, humanId);
405
+ return {
406
+ sourceId: humanId,
407
+ ...s,
408
+ maxPenetrationDepth: Math.max(0, s.maxPenetrationDepth - s.depth),
409
+ // Convert to relative depth (never negative!)
410
+ // Initialize restart waste tracking
411
+ restartWaste: 0,
412
+ maxRestartWaste: 0,
413
+ restartWasteByCommit: {}
414
+ };
415
+ }).filter(isTrueSource);
416
+ Object.entries(state.commitStats).forEach(([commitIdStr, stats]) => {
417
+ const commitId = Number(commitIdStr);
418
+ const restarts = stats[CS_RESTARTS];
419
+ if (restarts === 0) return;
420
+ const sourcesInCommit = allSources.filter(
421
+ (source) => source.updateHistory[commitId] !== void 0
422
+ );
423
+ if (sourcesInCommit.length === 0) {
424
+ return;
425
+ }
426
+ const restartsPerSource = restarts / sourcesInCommit.length;
427
+ for (const sourceItem of sourcesInCommit) {
428
+ sourceItem.restartWaste += restartsPerSource;
429
+ sourceItem.restartWasteByCommit[commitId] = restartsPerSource;
430
+ if (restartsPerSource > sourceItem.maxRestartWaste) {
431
+ sourceItem.maxRestartWaste = restartsPerSource;
432
+ }
433
+ }
434
+ });
435
+ if (thrashMode === "full") {
436
+ for (const sourceItem of allSources) {
437
+ const amplifiedByThisSource = [];
438
+ state.amplifierSources.forEach((amplifierData, amplifierInternalId) => {
439
+ const amplifierHumanId = idMapping.get(amplifierInternalId) || toHumanReadableId(amplifierInternalId, amplifierData.type, amplifierData.depth);
440
+ const causedByHumanId = amplifierData.causedBySourceId ? idMapping.get(amplifierData.causedBySourceId) : null;
441
+ if (causedByHumanId === sourceItem.sourceId || amplifierHumanId === sourceItem.sourceId) {
442
+ amplifiedByThisSource.push({
443
+ sourceId: amplifierHumanId,
444
+ type: amplifierData.type,
445
+ depth: amplifierData.depth,
446
+ effectCount: amplifierData.effectCount,
447
+ callbackCount: amplifierData.callbackCount,
448
+ memoCount: amplifierData.memoCount,
449
+ hooksTotal: amplifierData.hooksTotal,
450
+ commitIds: amplifierData.commitIds,
451
+ byCommit: amplifierData.byCommit,
452
+ lastAmplification: amplifierData.lastAmplification
453
+ });
454
+ }
455
+ });
456
+ if (amplifiedByThisSource.length > 0) {
457
+ sourceItem.amplified = amplifiedByThisSource;
458
+ }
459
+ }
460
+ }
461
+ const sources = [...allSources].sort((a, b) => {
462
+ const aTotalWaste = a.collateralUpdates + (a.restartWaste || 0);
463
+ const bTotalWaste = b.collateralUpdates + (b.restartWaste || 0);
464
+ return bTotalWaste - aTotalWaste || b.maxCollateralUpdates - a.maxCollateralUpdates || (b.maxRestartWaste || 0) - (a.maxRestartWaste || 0);
465
+ }).slice(0, topN);
466
+ const coverage = state.commits > 0 ? Math.round(state.commitsWithMemoizedUpdaters / state.commits * 100) + "%" : "0%";
467
+ const timingEntries = Object.values(state.commitTimings);
468
+ const totalDetectorOverhead = timingEntries.reduce((sum, t) => sum + t.detectorOverhead, 0);
469
+ const totalTimeSpan = timingEntries.reduce((sum, t) => sum + t.timeSinceLastCommit, 0);
470
+ const avgDetectorOverhead = timingEntries.length > 0 ? totalDetectorOverhead / timingEntries.length : 0;
471
+ const avgTimeBetweenCommits = timingEntries.length > 0 ? totalTimeSpan / timingEntries.length : 0;
472
+ const topNLookup = /* @__PURE__ */ new Map();
473
+ for (let i = 0; i < sources.length; i++) {
474
+ topNLookup.set(sources[i].sourceId, { rank: i + 1, index: i });
475
+ }
476
+ const sourcesByCommit = {};
477
+ allSources.forEach((source) => {
478
+ Object.entries(source.updateHistory).forEach(([commitIdStr, data]) => {
479
+ const commitId = Number(commitIdStr);
480
+ if (typeof data === "string" && data !== "repeat") {
481
+ return;
482
+ }
483
+ const collateral = source.collateralByCommit?.[commitId] ?? 0;
484
+ if (collateral < minCollateral) {
485
+ return;
486
+ }
487
+ if (!sourcesByCommit[commitId]) {
488
+ sourcesByCommit[commitId] = [];
489
+ }
490
+ const isRepeat = data === "repeat";
491
+ let hookChanges;
492
+ if (typeof data === "object" && data !== null) {
493
+ hookChanges = Object.entries(data).map(([index, hookData]) => ({
494
+ index: Number(index),
495
+ prevValue: (
496
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
497
+ hookData !== "repeat" ? hookData.prevValue : void 0
498
+ ),
499
+ currentValue: hookData !== "repeat" ? (
500
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
501
+ hookData.currentValue
502
+ ) : void 0
503
+ }));
504
+ }
505
+ const lookup = topNLookup.get(source.sourceId);
506
+ const rank = lookup?.rank ?? null;
507
+ const sourceIndex = lookup?.index ?? null;
508
+ const entry = {
509
+ sourceId: source.sourceId,
510
+ type: source.type,
511
+ depth: source.depth,
512
+ collateral,
513
+ rank,
514
+ sourceIndex
515
+ };
516
+ if (hookChanges && hookChanges.length > 0) {
517
+ entry.hooks = hookChanges;
518
+ }
519
+ if (isRepeat) {
520
+ entry.isRepeat = true;
521
+ }
522
+ sourcesByCommit[commitId].push(entry);
523
+ });
524
+ });
525
+ const hasMetricsData = Object.keys(state.metricsByCommit).length > 0;
526
+ const integrationMode = detectorConfig?.integrationMode ?? "none";
527
+ const metricsAdapter = adapterIdFromMode(integrationMode);
528
+ const metricsAvailable = metricsAdapter !== null && hasMetricsData;
529
+ const meta = {
530
+ version: 6,
531
+ // v6: CommitStats[8,9] = unmountedComponents/unmountedSubtrees; suspenseByCommit
532
+ commits: state.commits,
533
+ config: {
534
+ detectionLevel: detectorConfig?.detectionLevel ?? "full",
535
+ effectThrashMode: detectorConfig?.effectThrashMode ?? "non-primitive",
536
+ captureFibers: detectorConfig?.captureFibers ?? false,
537
+ historySize: detectorConfig?.historySize ?? 1
538
+ },
539
+ memoizedUpdatersSupport: {
540
+ available: state.memoizedUpdatersAvailable,
541
+ commitsWithData: state.commitsWithMemoizedUpdaters,
542
+ coverage
543
+ },
544
+ integrationMode,
545
+ metricsAdapter,
546
+ // Per-field availability — fiber-walk fields are always real measurements
547
+ // because recordTree() runs in every mode. The only adapter-exclusive
548
+ // datapoint is the render-counter delta (restarts, metricsByCommit).
549
+ dataAvailability: {
550
+ collateral: true,
551
+ sources: true,
552
+ restarts: metricsAvailable,
553
+ performedWork: true,
554
+ effects: true,
555
+ memos: true,
556
+ callbacks: true,
557
+ contextUpdates: true,
558
+ metricsByCommit: metricsAvailable,
559
+ atlByCommit: metricsAvailable
560
+ },
561
+ timing: {
562
+ totalDetectorOverhead,
563
+ avgDetectorOverhead,
564
+ totalTimeSpan,
565
+ avgTimeBetweenCommits
566
+ }
567
+ };
568
+ const base = {
569
+ meta,
570
+ sources,
571
+ sourcesByCommit,
572
+ commitStats: state.commitStats,
573
+ commitTimings: state.commitTimings,
574
+ suspenseByCommit: state.suspenseByCommit,
575
+ unmountsByCommit: state.unmountsByCommit,
576
+ interactionsByCommit: state.interactionsByCommit,
577
+ renderedByCommit: state.renderedByCommit,
578
+ ...hasMetricsData && { metricsByCommit: state.metricsByCommit, atlByCommit: state.metricsByCommit },
579
+ toSerializable() {
580
+ return serializeReport(this);
581
+ }
582
+ };
583
+ if (thrashMode === "full") {
584
+ const getSortValue = (amplifier) => {
585
+ if (sortBy === "effect") return amplifier.effectCount;
586
+ if (sortBy === "callback") return amplifier.callbackCount;
587
+ if (sortBy === "memo") return amplifier.memoCount;
588
+ if (sortBy === "total") return amplifier.hooksTotal;
589
+ return amplifier.effectCount;
590
+ };
591
+ const getLinkedSortValue = (as) => {
592
+ if (sortBy === "effect") return as.effectCount;
593
+ if (sortBy === "callback") return as.callbackCount;
594
+ if (sortBy === "memo") return as.memoCount;
595
+ if (sortBy === "total") return as.hooksTotal;
596
+ return as.effectCount;
597
+ };
598
+ const amplifierSources = Array.from(state.amplifierSources.entries()).map(([internalId, lt]) => {
599
+ const humanId = idMapping.get(internalId) || toHumanReadableId(internalId, lt.type, lt.depth);
600
+ const sourceData = state.sources.get(internalId);
601
+ return {
602
+ sourceId: humanId,
603
+ type: lt.type,
604
+ depth: lt.depth,
605
+ collateralUpdates: sourceData?.collateralUpdates ?? 0,
606
+ effectCount: lt.effectCount,
607
+ callbackCount: lt.callbackCount,
608
+ memoCount: lt.memoCount,
609
+ hooksTotal: lt.hooksTotal,
610
+ commitIds: lt.commitIds,
611
+ byCommit: lt.byCommit,
612
+ causedBySourceId: lt.causedBySourceId ? idMapping.get(lt.causedBySourceId) || lt.causedBySourceId : null,
613
+ lastAmplification: lt.lastAmplification
614
+ };
615
+ }).sort((a, b) => getLinkedSortValue(b) - getLinkedSortValue(a) || b.hooksTotal - a.hooksTotal).slice(0, topN);
616
+ return {
617
+ ...base,
618
+ amplifiers: Array.from(state.amplifiers.entries()).map(([type, s]) => ({ type, ...s })).sort(
619
+ (a, b) => getSortValue(b) - getSortValue(a) || b.hooksTotal - a.hooksTotal || b.maxHooksPerCommit - a.maxHooksPerCommit
620
+ ).slice(0, topN),
621
+ amplifierSources
622
+ };
623
+ }
624
+ if (thrashMode === "counts" && state.thrashCounts) {
625
+ meta.amplifiersMetrics = {
626
+ totalHookThrashes: state.thrashCounts.totalHooks,
627
+ affectedComponentCount: state.thrashCounts.affectedComponents.size
628
+ };
629
+ }
630
+ return base;
631
+ }
632
+
633
+ // src/chippy-bridge/react-renderer.ts
634
+ function getReactRenderers(scope = globalThis) {
635
+ const g = scope;
636
+ const hook = g.__REACT_DEVTOOLS_GLOBAL_HOOK__;
637
+ if (!hook || !hook.renderers || typeof hook.renderers.entries !== "function") {
638
+ return [];
639
+ }
640
+ const out = [];
641
+ for (const [, renderer] of hook.renderers.entries()) {
642
+ if (renderer) out.push(renderer);
643
+ }
644
+ return out;
645
+ }
646
+ function dispatcherSlotOf(injected) {
647
+ if (!injected || typeof injected !== "object") return null;
648
+ const owner = injected;
649
+ if (typeof owner.H !== "undefined") return { owner, key: "H" };
650
+ if ("current" in owner) return { owner, key: "current" };
651
+ return null;
652
+ }
653
+ function getDispatcherSlot(scope = globalThis) {
654
+ for (const renderer of getReactRenderers(scope)) {
655
+ const slot = dispatcherSlotOf(renderer.currentDispatcherRef);
656
+ if (slot) return slot;
657
+ }
658
+ return null;
659
+ }
660
+ var modernRefs = /* @__PURE__ */ new WeakMap();
661
+ function dispatcherRefOf(slot) {
662
+ if (slot.key === "current") return slot.owner;
663
+ let ref = modernRefs.get(slot.owner);
664
+ if (!ref) {
665
+ const { owner } = slot;
666
+ ref = {
667
+ get current() {
668
+ return owner.H;
669
+ },
670
+ set current(value) {
671
+ owner.H = value;
672
+ }
673
+ };
674
+ modernRefs.set(owner, ref);
675
+ }
676
+ return ref;
677
+ }
678
+ function getDispatcherRef(scope = globalThis) {
679
+ const slot = getDispatcherSlot(scope);
680
+ return slot ? dispatcherRefOf(slot) : null;
681
+ }
682
+
683
+ // src/debug-render/core.ts
684
+ function parseStackFrames2(stack) {
685
+ return stack.split("\n").slice(1).map((line) => {
686
+ const m = line.match(/^\s*at\s+([^\s(]+)\s*\(([^)]+)\)/) || line.match(/^\s*at\s+()(.+)$/);
687
+ if (!m) return null;
688
+ const name = (m[1] || "").trim();
689
+ const locRaw = (m[2] || "").trim();
690
+ const tail = locRaw.match(/:(\d+):(\d+)\)?$/);
691
+ const loc = tail ? `${tail[1]}:${tail[2]}` : locRaw;
692
+ return name ? { name, loc } : null;
693
+ }).filter((f) => !!f);
694
+ }
695
+ var HOOK_METHODS = [
696
+ "useCallback",
697
+ "useContext",
698
+ "useDebugValue",
699
+ "useDeferredValue",
700
+ "useEffect",
701
+ "useImperativeHandle",
702
+ "useInsertionEffect",
703
+ "useLayoutEffect",
704
+ "useMemo",
705
+ "useReducer",
706
+ "useRef",
707
+ "useState",
708
+ "useSyncExternalStore",
709
+ "useTransition",
710
+ "useId",
711
+ "useCacheRefresh",
712
+ "useMemoCache"
713
+ ];
714
+ var HOOK_PRIMITIVE = {
715
+ useCallback: "Callback",
716
+ useContext: "Context",
717
+ useDebugValue: "DebugValue",
718
+ useDeferredValue: "DeferredValue",
719
+ useEffect: "Effect",
720
+ useImperativeHandle: "ImperativeHandle",
721
+ useInsertionEffect: "InsertionEffect",
722
+ useLayoutEffect: "LayoutEffect",
723
+ useMemo: "Memo",
724
+ useReducer: "Reducer",
725
+ useRef: "Ref",
726
+ useState: "State",
727
+ useSyncExternalStore: "SyncExternalStore",
728
+ useTransition: "Transition",
729
+ useId: "Id",
730
+ useCacheRefresh: "CacheRefresh",
731
+ useMemoCache: "MemoCache"
732
+ };
733
+ function isReactInternalFrameName(name) {
734
+ return name === "atlDispatcherHook" || name === "record" || name.startsWith("Object.") || name === "renderWithHooks" || name === "updateFunctionComponent" || name === "mountIndeterminateComponent" || name === "beginWork" || /^beginWork\$/.test(name) || name === "performUnitOfWork" || name === "workLoopSync" || name === "renderRootSync" || name === "performConcurrentWorkOnRoot" || name === "workLoop" || name === "flushWork";
735
+ }
736
+ function isHookFrameName(name) {
737
+ return /^use[A-Z0-9_]/.test(name.slice(name.lastIndexOf(".") + 1));
738
+ }
739
+ function deriveComponentName(frames) {
740
+ for (const { name } of frames) {
741
+ if (!name) continue;
742
+ if (isReactInternalFrameName(name)) continue;
743
+ if (isHookFrameName(name)) continue;
744
+ return name;
745
+ }
746
+ return "Unknown";
747
+ }
748
+ function createDebugRender(options = {}) {
749
+ const maxEventsPerComponent = options.maxEventsPerComponent ?? 600;
750
+ const eventsByComponent = /* @__PURE__ */ new Map();
751
+ const patchedDispatchers = /* @__PURE__ */ new WeakMap();
752
+ let installed = false;
753
+ let originalDescriptor = void 0;
754
+ let dispatcherSlot = null;
755
+ let lastSeenDispatcher = null;
756
+ function record(primitive, args) {
757
+ const stack = new Error().stack || "";
758
+ const frames = parseStackFrames2(stack);
759
+ const componentName = deriveComponentName(frames);
760
+ const event = {
761
+ primitive,
762
+ stack,
763
+ frames,
764
+ componentName,
765
+ args
766
+ };
767
+ const list = eventsByComponent.get(componentName) || [];
768
+ list.push(event);
769
+ if (list.length > maxEventsPerComponent) {
770
+ list.splice(0, list.length - maxEventsPerComponent);
771
+ }
772
+ eventsByComponent.set(componentName, list);
773
+ }
774
+ function patchDispatcher(dispatcher) {
775
+ if (!dispatcher || typeof dispatcher !== "object") return;
776
+ const obj = dispatcher;
777
+ if (patchedDispatchers.has(obj)) return;
778
+ const restoreMap = /* @__PURE__ */ new Map();
779
+ for (const methodName of HOOK_METHODS) {
780
+ const original = obj[methodName];
781
+ if (typeof original !== "function") continue;
782
+ restoreMap.set(methodName, original);
783
+ const primitive = HOOK_PRIMITIVE[methodName] || methodName;
784
+ obj[methodName] = function atlDispatcherHook(...args) {
785
+ try {
786
+ record(primitive, args);
787
+ } catch (_) {
788
+ }
789
+ return original.apply(this, args);
790
+ };
791
+ }
792
+ patchedDispatchers.set(obj, restoreMap);
793
+ }
794
+ function unpatchDispatcher(dispatcher) {
795
+ if (!dispatcher || typeof dispatcher !== "object") return;
796
+ const obj = dispatcher;
797
+ const restoreMap = patchedDispatchers.get(obj);
798
+ if (!restoreMap) return;
799
+ for (const [methodName, fn] of restoreMap.entries()) {
800
+ obj[methodName] = fn;
801
+ }
802
+ patchedDispatchers.delete(obj);
803
+ }
804
+ function install() {
805
+ if (installed) return { ok: true };
806
+ const slot = getDispatcherSlot();
807
+ if (!slot) return { ok: false, reason: "ReactCurrentDispatcher not found" };
808
+ dispatcherSlot = slot;
809
+ const { owner, key } = slot;
810
+ patchDispatcher(owner[key]);
811
+ lastSeenDispatcher = owner[key];
812
+ originalDescriptor = Object.getOwnPropertyDescriptor(owner, key) || null;
813
+ let stored = owner[key];
814
+ try {
815
+ Object.defineProperty(owner, key, {
816
+ configurable: true,
817
+ get() {
818
+ return stored;
819
+ },
820
+ set(next) {
821
+ stored = next;
822
+ if (next !== lastSeenDispatcher) {
823
+ lastSeenDispatcher = next;
824
+ patchDispatcher(next);
825
+ }
826
+ }
827
+ });
828
+ } catch (e) {
829
+ dispatcherSlot = null;
830
+ return { ok: false, reason: "cannot define property on ReactCurrentDispatcher" };
831
+ }
832
+ installed = true;
833
+ return { ok: true };
834
+ }
835
+ function uninstall() {
836
+ if (!installed) return;
837
+ if (dispatcherSlot) {
838
+ const { owner, key } = dispatcherSlot;
839
+ try {
840
+ if (originalDescriptor) {
841
+ Object.defineProperty(owner, key, originalDescriptor);
842
+ } else {
843
+ Object.defineProperty(owner, key, {
844
+ configurable: true,
845
+ writable: true,
846
+ enumerable: true,
847
+ value: lastSeenDispatcher
848
+ });
849
+ }
850
+ } catch (_) {
851
+ }
852
+ }
853
+ unpatchDispatcher(lastSeenDispatcher);
854
+ installed = false;
855
+ dispatcherSlot = null;
856
+ originalDescriptor = void 0;
857
+ lastSeenDispatcher = null;
858
+ }
859
+ function reset() {
860
+ eventsByComponent.clear();
861
+ }
862
+ function rawEvents(componentName) {
863
+ return (eventsByComponent.get(componentName) || []).slice();
864
+ }
865
+ function allComponents() {
866
+ return Array.from(eventsByComponent.keys());
867
+ }
868
+ function status() {
869
+ const g = globalThis;
870
+ const hasDevtoolsHook = !!g.__REACT_DEVTOOLS_GLOBAL_HOOK__;
871
+ const rendererCount = getReactRenderers().length;
872
+ const hasDispatcherRef = !!(dispatcherSlot || getDispatcherSlot());
873
+ let totalCapturedEvents = 0;
874
+ for (const list of eventsByComponent.values()) totalCapturedEvents += list.length;
875
+ let reason;
876
+ let hint;
877
+ if (!hasDevtoolsHook) {
878
+ reason = "no __REACT_DEVTOOLS_GLOBAL_HOOK__ on window";
879
+ hint = "devtools-hook-shim must run before react-dom evaluates (extension document_start, or Playwright addInitScript)";
880
+ } else if (rendererCount === 0) {
881
+ reason = "__REACT_DEVTOOLS_GLOBAL_HOOK__ present but renderers Map is empty";
882
+ hint = "react-dom evaluated before the hook shim was installed; reload the page after installing the shim, or use addInitScript before navigation";
883
+ } else if (!hasDispatcherRef) {
884
+ reason = "renderer registered but no currentDispatcherRef";
885
+ hint = "unsupported react-dom version, or hook was replaced after registration";
886
+ } else if (!installed) {
887
+ reason = "debug-render not installed";
888
+ hint = "detector did not call debugRender.install() \u2014 check avalanche install() lifecycle";
889
+ } else if (totalCapturedEvents === 0) {
890
+ reason = "capture engine installed but no hook events recorded yet";
891
+ hint = "trigger at least one render of the component you want to inspect after install";
892
+ }
893
+ const ok = installed && hasDispatcherRef && totalCapturedEvents > 0;
894
+ return {
895
+ ok,
896
+ installed,
897
+ hasDevtoolsHook,
898
+ rendererCount,
899
+ hasDispatcherRef,
900
+ totalCapturedComponents: eventsByComponent.size,
901
+ totalCapturedEvents,
902
+ reason,
903
+ hint
904
+ };
905
+ }
906
+ return {
907
+ install,
908
+ uninstall,
909
+ reset,
910
+ isInstalled: () => installed,
911
+ dispatcherRef: () => dispatcherSlot ? dispatcherRefOf(dispatcherSlot) : getDispatcherRef(),
912
+ rawEvents,
913
+ allComponents,
914
+ status,
915
+ recordEventForTest(event) {
916
+ const list = eventsByComponent.get(event.componentName) || [];
917
+ list.push(event);
918
+ eventsByComponent.set(event.componentName, list);
919
+ }
920
+ };
921
+ }
922
+
923
+ // src/avalanche/commit-surface-ownership.ts
924
+ function createCommitSurfaceOwnership() {
925
+ let adapterObserved = false;
926
+ let devtoolsCaptures = /* @__PURE__ */ new WeakMap();
927
+ return {
928
+ get adapterObserved() {
929
+ return adapterObserved;
930
+ },
931
+ recordDevtools(root, capture) {
932
+ devtoolsCaptures.set(root, { current: root.current, capture });
933
+ },
934
+ observeAdapter(root) {
935
+ const previous = devtoolsCaptures.get(root);
936
+ adapterObserved = true;
937
+ devtoolsCaptures.delete(root);
938
+ return previous?.current === root.current ? previous.capture : null;
939
+ },
940
+ reset() {
941
+ adapterObserved = false;
942
+ devtoolsCaptures = /* @__PURE__ */ new WeakMap();
943
+ }
944
+ };
945
+ }
946
+
947
+ // src/layout-lab/fiber-walk.ts
948
+ function getDisplayName(type) {
949
+ if (!type) return null;
950
+ if (typeof type === "string") return type;
951
+ const obj = type;
952
+ return obj.displayName || obj.name || null;
953
+ }
954
+ function isUserComponent(fiber) {
955
+ if (!fiber) return false;
956
+ const name = getDisplayName(fiber.type);
957
+ if (!name || typeof fiber.type === "string") return false;
958
+ if (["hookified", "unboundStoryFn", "Anonymous"].includes(name)) return false;
959
+ return true;
960
+ }
961
+ function getHookSummary(fiber) {
962
+ const MAX_HOOKS = 500;
963
+ const seen = /* @__PURE__ */ new Set();
964
+ let hookNode = fiber?.memoizedState || null;
965
+ let hookCount = 0;
966
+ let hookCountTruncated = false;
967
+ while (hookNode) {
968
+ if (seen.has(hookNode)) break;
969
+ if (hookCount >= MAX_HOOKS) {
970
+ hookCountTruncated = true;
971
+ break;
972
+ }
973
+ seen.add(hookNode);
974
+ hookCount += 1;
975
+ hookNode = hookNode.next || null;
976
+ }
977
+ return { hookCount, hookCountTruncated, hookCountMax: MAX_HOOKS };
978
+ }
979
+ function getOwnerChain(fiber) {
980
+ const chain = [];
981
+ let current = fiber?._debugOwner || null;
982
+ while (current) {
983
+ if (isUserComponent(current)) {
984
+ chain.push({
985
+ name: getDisplayName(current.type),
986
+ key: current.key || null,
987
+ props: current.memoizedProps ? Object.keys(current.memoizedProps) : [],
988
+ ...getHookSummary(current),
989
+ source: describeFiberSource(current)
990
+ });
991
+ }
992
+ current = current._debugOwner || null;
993
+ }
994
+ return chain;
995
+ }
996
+ function getReturnComponentChain(fiber) {
997
+ const chain = [];
998
+ let current = fiber?.return || null;
999
+ while (current) {
1000
+ if (isUserComponent(current)) {
1001
+ chain.push({
1002
+ name: getDisplayName(current.type),
1003
+ key: current.key || null,
1004
+ props: current.memoizedProps ? Object.keys(current.memoizedProps) : [],
1005
+ ...getHookSummary(current),
1006
+ source: describeFiberSource(current)
1007
+ });
1008
+ }
1009
+ current = current.return || null;
1010
+ }
1011
+ return chain;
1012
+ }
1013
+ function describeSource(source) {
1014
+ if (!source) return null;
1015
+ return {
1016
+ fileName: source.fileName || null,
1017
+ lineNumber: source.lineNumber || null,
1018
+ columnNumber: source.columnNumber || null
1019
+ };
1020
+ }
1021
+ function describeFiberSource(fiber) {
1022
+ return describeSource(getFiberSource(fiber));
1023
+ }
1024
+ function describeFiberTarget(fiber, domNode) {
1025
+ if (!fiber && !domNode) return null;
1026
+ return {
1027
+ domTag: domNode?.tagName?.toLowerCase() || null,
1028
+ domText: domNode?.textContent?.trim().slice(0, 60) || "",
1029
+ fiberType: getDisplayName(fiber?.type) || getDisplayName(fiber?.elementType) || null,
1030
+ source: describeFiberSource(fiber)
1031
+ };
1032
+ }
1033
+ function describeComponent(fiber) {
1034
+ if (!fiber) return null;
1035
+ return {
1036
+ name: getDisplayName(fiber.type),
1037
+ key: fiber.key || null,
1038
+ props: fiber.memoizedProps || {},
1039
+ ...getHookSummary(fiber),
1040
+ source: describeFiberSource(fiber),
1041
+ stateNode: fiber.stateNode instanceof Element ? fiber.stateNode.tagName.toLowerCase() : null,
1042
+ owners: getOwnerChain(fiber),
1043
+ parents: getReturnComponentChain(fiber)
1044
+ };
1045
+ }
1046
+
1047
+ // src/avalanche/interaction-capture.ts
1048
+ var EXPLICIT_INTERACTION_EVENTS = ["click", "keydown", "keyup", "input", "change", "submit"];
1049
+ var NOISY_INTERACTION_EVENTS = ["wheel", "scroll", "pointermove", "mousemove", "touchmove"];
1050
+ var ALL_INTERACTION_EVENTS = [
1051
+ ...EXPLICIT_INTERACTION_EVENTS,
1052
+ "pointerdown",
1053
+ "pointerup",
1054
+ "dblclick",
1055
+ "focusin",
1056
+ "focusout",
1057
+ ...NOISY_INTERACTION_EVENTS
1058
+ ];
1059
+ var MAX_INTERACTION_ACTIONS = 200;
1060
+ function createInteractionCapture(options = {}) {
1061
+ const {
1062
+ interactionCapture = "explicit",
1063
+ interactionEvents = [],
1064
+ interactionWindowMs = 1e3
1065
+ } = options;
1066
+ const state = {
1067
+ actions: [],
1068
+ byCommit: {}
1069
+ };
1070
+ let nextActionId = 1;
1071
+ let installed = false;
1072
+ function eventNames() {
1073
+ const base = interactionCapture === "off" ? [] : interactionCapture === "explicit" ? EXPLICIT_INTERACTION_EVENTS : interactionCapture === "noisy" ? EXPLICIT_INTERACTION_EVENTS.concat(NOISY_INTERACTION_EVENTS) : ALL_INTERACTION_EVENTS;
1074
+ return Array.from(new Set(base.concat(interactionEvents).map((name) => String(name).trim()).filter(Boolean)));
1075
+ }
1076
+ function findReactFiberKey(node) {
1077
+ return Object.getOwnPropertyNames(node).find((key) => key.startsWith("__reactFiber$")) || null;
1078
+ }
1079
+ function nearestFiberFromNode(node) {
1080
+ let current = node instanceof Element ? node : null;
1081
+ while (current) {
1082
+ const key = findReactFiberKey(current);
1083
+ if (key) return current[key] || null;
1084
+ current = current.parentElement;
1085
+ }
1086
+ return null;
1087
+ }
1088
+ function nearestComponentFiber(node) {
1089
+ let fiber = nearestFiberFromNode(node);
1090
+ while (fiber) {
1091
+ if (isComponentFiber(fiber.tag)) return fiber;
1092
+ fiber = fiber.return || null;
1093
+ }
1094
+ return null;
1095
+ }
1096
+ function nearestTestId(node) {
1097
+ let current = node instanceof Element ? node : null;
1098
+ while (current) {
1099
+ const value = current.getAttribute("data-testid");
1100
+ if (value) return value;
1101
+ current = current.parentElement;
1102
+ }
1103
+ return null;
1104
+ }
1105
+ function describeActionLocation(target) {
1106
+ const domTarget = target instanceof Element ? target : null;
1107
+ const fiber = nearestComponentFiber(target);
1108
+ const describedTarget = describeFiberTarget(fiber, domTarget);
1109
+ const describedComponent = describeComponent(fiber);
1110
+ return {
1111
+ dom: {
1112
+ tag: describedTarget?.domTag || null,
1113
+ text: describedTarget?.domText || "",
1114
+ nearestTestId: nearestTestId(target)
1115
+ },
1116
+ targetFiber: describedComponent ? {
1117
+ name: describedComponent.name,
1118
+ key: describedComponent.key,
1119
+ source: describedComponent.source,
1120
+ ownerStackKind: describedComponent.owners.length > 0 ? "owners" : "parents",
1121
+ ownerStack: describedComponent.owners.length > 0 ? describedComponent.owners : describedComponent.parents
1122
+ } : null
1123
+ };
1124
+ }
1125
+ function capture(event) {
1126
+ const pathTarget = typeof event.composedPath === "function" ? event.composedPath()[0] : null;
1127
+ const target = pathTarget instanceof Element ? pathTarget : event.target;
1128
+ state.actions.push({
1129
+ id: nextActionId++,
1130
+ action: event.type,
1131
+ timestamp: performance.now(),
1132
+ location: describeActionLocation(target)
1133
+ });
1134
+ if (state.actions.length > MAX_INTERACTION_ACTIONS) {
1135
+ state.actions.splice(0, state.actions.length - MAX_INTERACTION_ACTIONS);
1136
+ }
1137
+ }
1138
+ return {
1139
+ state,
1140
+ install() {
1141
+ if (installed) return;
1142
+ const names = eventNames();
1143
+ if (names.length === 0 || typeof document === "undefined") return;
1144
+ for (const name of names) document.addEventListener(name, capture, true);
1145
+ installed = true;
1146
+ },
1147
+ uninstall() {
1148
+ if (!installed || typeof document === "undefined") return;
1149
+ for (const name of eventNames()) document.removeEventListener(name, capture, true);
1150
+ installed = false;
1151
+ },
1152
+ reset() {
1153
+ state.actions.length = 0;
1154
+ for (const key of Object.keys(state.byCommit)) {
1155
+ delete state.byCommit[Number(key)];
1156
+ }
1157
+ nextActionId = 1;
1158
+ },
1159
+ linkRecent(commitId, commitTime) {
1160
+ const windowMs = Math.max(0, Number(interactionWindowMs) || 0);
1161
+ if (windowMs === 0 || state.actions.length === 0) return;
1162
+ const linked = state.actions.filter((action) => action.timestamp <= commitTime && commitTime - action.timestamp <= windowMs).map((action) => ({
1163
+ id: action.id,
1164
+ lastCommitId: Math.max(0, commitId - 1),
1165
+ deltaTime: Math.round((commitTime - action.timestamp) * 100) / 100,
1166
+ action: action.action,
1167
+ location: action.location
1168
+ }));
1169
+ if (linked.length > 0) state.byCommit[commitId] = linked;
1170
+ while (state.actions.length > 0 && commitTime - state.actions[0].timestamp > windowMs) {
1171
+ state.actions.shift();
1172
+ }
1173
+ }
1174
+ };
1175
+ }
1176
+
1177
+ // src/avalanche/detector.tsx
1178
+ function summarizeFiberSource(fiber) {
1179
+ const source = getFiberSourceCandidate(fiber);
1180
+ return source ? { fileName: source.fileName, lineNumber: source.lineNumber } : null;
1181
+ }
1182
+ function createAvalancheDetector(options = {}) {
1183
+ const {
1184
+ ignore = [],
1185
+ effectThrashMode = "non-primitive",
1186
+ captureFibers = isDevelopment(),
1187
+ detectionMode = isDevelopment() ? "full" : "amplifiers",
1188
+ historySize = isDevelopment() ? 10 : 1
1189
+ } = options;
1190
+ const mode = detectionMode;
1191
+ let metricsAdapter = null;
1192
+ const resolveMetricsAdapter = () => options.metricsAdapter === void 0 ? getCommitMetricsAdapter() : options.metricsAdapter;
1193
+ const ignoreSet = new Set(ignore);
1194
+ let detectionLevel;
1195
+ let thrashMode;
1196
+ if (mode === true) {
1197
+ detectionLevel = "full";
1198
+ thrashMode = "full";
1199
+ } else if (mode === false) {
1200
+ detectionLevel = "stats";
1201
+ thrashMode = false;
1202
+ } else if (mode === "stats") {
1203
+ detectionLevel = "stats";
1204
+ thrashMode = false;
1205
+ } else if (mode === "sources") {
1206
+ detectionLevel = "sources";
1207
+ thrashMode = false;
1208
+ } else if (mode === "amplifiers") {
1209
+ detectionLevel = "amplifiers";
1210
+ thrashMode = "full";
1211
+ } else if (mode === "amplifierMetrics") {
1212
+ detectionLevel = "amplifierMetrics";
1213
+ thrashMode = "counts";
1214
+ } else if (mode === "full") {
1215
+ detectionLevel = "full";
1216
+ thrashMode = "full";
1217
+ }
1218
+ const enableSourceTracking = detectionLevel !== "stats";
1219
+ const enableAmplifiers = thrashMode !== false;
1220
+ const valueProvenance = /* @__PURE__ */ new WeakMap();
1221
+ class StableSourceIdManager {
1222
+ idMap = /* @__PURE__ */ new WeakMap();
1223
+ // WeakMap only accepts objects/functions, not symbols. Store only valid keys.
1224
+ typeToId = /* @__PURE__ */ new WeakMap();
1225
+ instanceCounters = /* @__PURE__ */ new Map();
1226
+ // "typeId@depth" -> instance count
1227
+ nextTypeId = 1;
1228
+ getSourceId(fiber, type, depth) {
1229
+ let sourceId = this.idMap.get(fiber);
1230
+ if (sourceId) return sourceId;
1231
+ if (fiber.alternate) {
1232
+ sourceId = this.idMap.get(fiber.alternate);
1233
+ if (sourceId) {
1234
+ this.idMap.set(fiber, sourceId);
1235
+ return sourceId;
1236
+ }
1237
+ }
1238
+ const canBeWeakMapKey = typeof type === "object" && type !== null || typeof type === "function";
1239
+ const weakKey = canBeWeakMapKey ? type : null;
1240
+ let typeId = weakKey ? this.typeToId.get(weakKey) : void 0;
1241
+ if (typeId === void 0) {
1242
+ typeId = this.nextTypeId++;
1243
+ if (weakKey) {
1244
+ this.typeToId.set(weakKey, typeId);
1245
+ }
1246
+ }
1247
+ const baseKey = `${typeId}@${depth}`;
1248
+ const instanceNum = this.instanceCounters.get(baseKey) || 1;
1249
+ this.instanceCounters.set(baseKey, instanceNum + 1);
1250
+ sourceId = `T${typeId}#${instanceNum}@${depth}`;
1251
+ this.idMap.set(fiber, sourceId);
1252
+ if (fiber.alternate) {
1253
+ this.idMap.set(fiber.alternate, sourceId);
1254
+ }
1255
+ return sourceId;
1256
+ }
1257
+ clear() {
1258
+ this.instanceCounters.clear();
1259
+ this.typeToId = /* @__PURE__ */ new WeakMap();
1260
+ this.nextTypeId = 1;
1261
+ }
1262
+ }
1263
+ const sourceIdManager = new StableSourceIdManager();
1264
+ const debugRender = createDebugRender();
1265
+ const interactionCapture = createInteractionCapture(options);
1266
+ function getComponentDisplayName(type) {
1267
+ if (!type) return "Unknown";
1268
+ return type.displayName || type.name || String(type);
1269
+ }
1270
+ function isDebugInternalFrame(frame, componentName) {
1271
+ const name = frame.name || "";
1272
+ return !!(metricsAdapter ?? resolveMetricsAdapter())?.frameFilters?.includes(name) || name === "record" || name === componentName || name.includes("react-dom") || name.includes("renderWithHooks") || name.includes("mount") || name.includes("update");
1273
+ }
1274
+ function parentFramesForEvent(event, componentName) {
1275
+ return event.frames.filter((frame) => !isDebugInternalFrame(frame, componentName));
1276
+ }
1277
+ function captureMetricsSnapshot() {
1278
+ return metricsAdapter?.snapshot() ?? null;
1279
+ }
1280
+ const lastSeenHookState = /* @__PURE__ */ new WeakMap();
1281
+ function captureHookStateSnapshot(fiber) {
1282
+ const snapshot = [];
1283
+ let hook = fiber.memoizedState;
1284
+ while (hook) {
1285
+ snapshot.push(hook.memoizedState);
1286
+ hook = hook.next;
1287
+ }
1288
+ return snapshot;
1289
+ }
1290
+ function detectChangesFromLastSeen(fiber, currentSnapshot) {
1291
+ const lastSeen = lastSeenHookState.get(fiber) || fiber.alternate && lastSeenHookState.get(fiber.alternate);
1292
+ const changedIndices = [];
1293
+ const changedHooks = [];
1294
+ if (!lastSeen) {
1295
+ return { changedIndices, changedHooks };
1296
+ }
1297
+ const maxLen = Math.max(lastSeen.length, currentSnapshot.length);
1298
+ for (let i = 0; i < maxLen; i++) {
1299
+ const prev = lastSeen[i];
1300
+ const curr = currentSnapshot[i];
1301
+ if (!Object.is(prev, curr)) {
1302
+ changedIndices.push(i);
1303
+ changedHooks.push({ prevValue: prev, currentValue: curr });
1304
+ }
1305
+ }
1306
+ return { changedIndices, changedHooks };
1307
+ }
1308
+ const state = {
1309
+ commits: 0,
1310
+ sources: /* @__PURE__ */ new Map(),
1311
+ amplifiers: /* @__PURE__ */ new Map(),
1312
+ amplifierSources: /* @__PURE__ */ new Map(),
1313
+ thrashCounts: thrashMode === "counts" ? { totalHooks: 0, affectedComponents: /* @__PURE__ */ new Set() } : null,
1314
+ memoizedUpdatersAvailable: false,
1315
+ commitsWithMemoizedUpdaters: 0,
1316
+ // Per-commit stats: [collateral, effects, memos, callbacks]
1317
+ commitStats: {},
1318
+ // Per-commit adapter counter deltas
1319
+ metricsByCommit: {},
1320
+ lastMetricsSnapshot: null,
1321
+ // Per-commit timing data
1322
+ commitTimings: {},
1323
+ lastCommitEndTime: performance.now(),
1324
+ // Per-commit Suspense boundary transitions
1325
+ suspenseByCommit: {},
1326
+ unmountsByCommit: {},
1327
+ interactionActions: interactionCapture.state.actions,
1328
+ interactionsByCommit: interactionCapture.state.byCommit,
1329
+ renderedByCommit: {}
1330
+ };
1331
+ function _pushToHistory(history, item, limit) {
1332
+ history.push(item);
1333
+ if (history.length > limit) {
1334
+ history.splice(0, history.length - limit);
1335
+ }
1336
+ }
1337
+ function trimHistoryToSize(updHistory, limit) {
1338
+ const keys = Object.keys(updHistory).map(Number).sort((a, b) => a - b);
1339
+ const keysToKeep = keys.slice(keys.length - limit);
1340
+ const trimmed = {};
1341
+ keysToKeep.forEach((key) => {
1342
+ trimmed[key] = updHistory[key];
1343
+ });
1344
+ return trimmed;
1345
+ }
1346
+ function findLastConcreteHook(history, hookIndex, beforeCommitId) {
1347
+ const commitIds = Object.keys(history).map(Number).filter((id) => id < beforeCommitId).sort((a, b) => b - a);
1348
+ for (const commitId of commitIds) {
1349
+ const entry = history[commitId];
1350
+ if (entry && entry !== "repeat" && typeof entry !== "string") {
1351
+ const prev = entry[hookIndex];
1352
+ if (prev && prev !== "repeat" && typeof prev === "object") return prev;
1353
+ }
1354
+ }
1355
+ return null;
1356
+ }
1357
+ function summarizePropValue(value) {
1358
+ const isArray = Array.isArray(value);
1359
+ const type = value === null ? "null" : isArray ? "array" : typeof value;
1360
+ const isFunction = typeof value === "function";
1361
+ const isNull = value === null;
1362
+ const isPrimitive = value == null || type === "string" || type === "number" || type === "boolean" || type === "undefined" || type === "symbol" || type === "bigint";
1363
+ const primitiveDisplay = isPrimitive ? String(value) : void 0;
1364
+ const summary = {
1365
+ type,
1366
+ isArray,
1367
+ isFunction,
1368
+ isNull,
1369
+ preview: primitiveDisplay || (isFunction ? "function" : isArray ? `array(${value.length})` : "object")
1370
+ };
1371
+ if (primitiveDisplay !== void 0) {
1372
+ summary.display = primitiveDisplay;
1373
+ }
1374
+ if (typeof value === "string" || isArray) {
1375
+ summary.length = value.length;
1376
+ }
1377
+ if (isArray) {
1378
+ summary.sample = value.slice(0, 3).map((item) => summarizePropValue(item).preview);
1379
+ } else if (value && typeof value === "object") {
1380
+ const keys = Object.keys(value).slice(0, 5);
1381
+ summary.keysTop = keys;
1382
+ summary.length = Object.keys(value).length;
1383
+ if (keys.length > 0) {
1384
+ summary.preview = `object{${keys.join(",")}}`;
1385
+ }
1386
+ } else if (isFunction) {
1387
+ const fn = value;
1388
+ summary.preview = fn.name ? `function ${fn.name}` : "function";
1389
+ }
1390
+ return summary;
1391
+ }
1392
+ function diffProps(prevProps, currentProps) {
1393
+ const changes = [];
1394
+ const prev = prevProps && typeof prevProps === "object" ? prevProps : {};
1395
+ const current = currentProps && typeof currentProps === "object" ? currentProps : {};
1396
+ const keys = /* @__PURE__ */ new Set([...Object.keys(prev), ...Object.keys(current)]);
1397
+ keys.forEach((name) => {
1398
+ const prevHas = Object.prototype.hasOwnProperty.call(prev, name);
1399
+ const currentHas = Object.prototype.hasOwnProperty.call(current, name);
1400
+ const prevValue = prev[name];
1401
+ const currentValue = current[name];
1402
+ if (!prevHas && currentHas) {
1403
+ changes.push({ name, kind: "added", prev: summarizePropValue(void 0), current: summarizePropValue(currentValue) });
1404
+ } else if (prevHas && !currentHas) {
1405
+ changes.push({ name, kind: "removed", prev: summarizePropValue(prevValue), current: summarizePropValue(void 0) });
1406
+ } else if (Object.is(prevValue, currentValue)) {
1407
+ } else if (prevValue !== null && currentValue !== null && (typeof prevValue === "object" || typeof prevValue === "function" || typeof currentValue === "object" || typeof currentValue === "function")) {
1408
+ changes.push({ name, kind: "ref-changed", prev: summarizePropValue(prevValue), current: summarizePropValue(currentValue) });
1409
+ } else {
1410
+ changes.push({ name, kind: "scalar-changed", prev: summarizePropValue(prevValue), current: summarizePropValue(currentValue) });
1411
+ }
1412
+ });
1413
+ return changes;
1414
+ }
1415
+ function pushUpdateHistory(updHistory, commitId, changedHookIndices, changedHooks) {
1416
+ const entry = {};
1417
+ for (let i = 0; i < changedHookIndices.length; i++) {
1418
+ const hookIndex = changedHookIndices[i];
1419
+ const hook = changedHooks[i];
1420
+ if (hook === "repeat") {
1421
+ entry[hookIndex] = "repeat";
1422
+ } else {
1423
+ const prevConcrete = findLastConcreteHook(updHistory, hookIndex, commitId);
1424
+ entry[hookIndex] = prevConcrete && typeof prevConcrete === "object" && Object.is(hook.prevValue, prevConcrete.prevValue) && Object.is(hook.currentValue, prevConcrete.currentValue) ? "repeat" : hook;
1425
+ }
1426
+ }
1427
+ const newEntry = Object.values(entry).every((v) => v === "repeat") ? "repeat" : entry;
1428
+ return { ...updHistory, [commitId]: newEntry };
1429
+ }
1430
+ function recordTree(rootFiber, memoizedUpdaters = null) {
1431
+ if (memoizedUpdaters && memoizedUpdaters.size > 0) {
1432
+ state.memoizedUpdatersAvailable = true;
1433
+ state.commitsWithMemoizedUpdaters++;
1434
+ }
1435
+ const perCommit = {
1436
+ selfUpdated: /* @__PURE__ */ new Map(),
1437
+ thrashing: /* @__PURE__ */ new Map(),
1438
+ /** Linked thrashing: keyed by sourceId, tracks per-component thrashing with linkage */
1439
+ linkedThrashing: /* @__PURE__ */ new Map(),
1440
+ rendered: [],
1441
+ selfFibers: /* @__PURE__ */ new WeakMap(),
1442
+ sourceFibers: /* @__PURE__ */ new Set(),
1443
+ // Track source fibers to exclude from collateral count
1444
+ blastBySourceId: /* @__PURE__ */ new Map(),
1445
+ depthBySourceId: /* @__PURE__ */ new Map(),
1446
+ // Simple counts for commitStats
1447
+ totalCollateral: 0,
1448
+ committedComponentCount: 0,
1449
+ // Components with PerformedWork that committed
1450
+ effectCount: 0,
1451
+ memoCount: 0,
1452
+ callbackCount: 0,
1453
+ contextUpdates: 0,
1454
+ // Unmount tracking (populated by deletions-array walk in visit())
1455
+ unmountedComponents: 0,
1456
+ unmountedSubtrees: 0,
1457
+ // Bounded identity samples for branch starts (topmost component fibers in
1458
+ // each deleted subtree). Cap to keep payload small.
1459
+ unmountSamples: null,
1460
+ // Suspense boundary transition events collected during this commit's visit pass
1461
+ suspenseEvents: null
1462
+ };
1463
+ function visit(fiber, depth = 0, currentOwnerSourceId = null) {
1464
+ if (!fiber) return;
1465
+ const hasWorkInSubtree = (
1466
+ // eslint-disable-next-line no-bitwise -- React fiber flags are bitmasks, bitwise AND is required
1467
+ (fiber.flags & PerformedWork) !== 0 || // eslint-disable-next-line no-bitwise
1468
+ (fiber.subtreeFlags & PerformedWork) !== 0 || // fiber.flags & ChildDeletion = this fiber has children being deleted this commit
1469
+ // (React sets ChildDeletion on fiber.flags, NOT subtreeFlags, at commit time)
1470
+ // eslint-disable-next-line no-bitwise
1471
+ (fiber.flags & ChildDeletion) !== 0 || // subtreeFlags propagation — some descendant has deletions
1472
+ // eslint-disable-next-line no-bitwise
1473
+ (fiber.subtreeFlags & ChildDeletion) !== 0 || // Suspense transitioning: React sets Update (4) on the Suspense fiber's flags at commit.
1474
+ // eslint-disable-next-line no-bitwise
1475
+ isSuspenseFiber(fiber.tag) && (fiber.flags & Update) !== 0 || // subtreeFlags & Update: a descendant (e.g. Suspense) received an Update this commit.
1476
+ // React propagates subtreeFlags upward — HostRoot and all intermediate host fibers
1477
+ // will have Update bit set if any descendant has it. This ensures we don't skip
1478
+ // the path to a Suspense boundary when no PerformedWork exists higher in the tree.
1479
+ // eslint-disable-next-line no-bitwise
1480
+ (fiber.subtreeFlags & Update) !== 0
1481
+ );
1482
+ if (!hasWorkInSubtree) {
1483
+ if (fiber.sibling) visit(fiber.sibling, depth, currentOwnerSourceId);
1484
+ return;
1485
+ }
1486
+ if (isContextProviderFiber(fiber.tag) && didContextValueChange(fiber)) {
1487
+ perCommit.contextUpdates++;
1488
+ }
1489
+ if ((fiber.flags & ChildDeletion) !== 0 && fiber.deletions && fiber.deletions.length > 0) {
1490
+ for (let di = 0; di < fiber.deletions.length; di++) {
1491
+ let collectDeletions = function(df, parentIsComponent, branchStartDepth, currentBranch) {
1492
+ const dfIsComponent = isComponentFiber(df.tag);
1493
+ let nextBranch = currentBranch;
1494
+ if (dfIsComponent) {
1495
+ perCommit.unmountedComponents++;
1496
+ if (!parentIsComponent) {
1497
+ perCommit.unmountedSubtrees++;
1498
+ if (perCommit.unmountSamples == null) perCommit.unmountSamples = [];
1499
+ if (perCommit.unmountSamples.length < 30) {
1500
+ const fiberKey = df.key;
1501
+ const sample = {
1502
+ name: getComponentName(df),
1503
+ key: fiberKey == null ? null : String(fiberKey),
1504
+ depth: branchStartDepth,
1505
+ count: 1,
1506
+ source: summarizeFiberSource(df),
1507
+ host: summarizeHostSubtree(df)
1508
+ };
1509
+ perCommit.unmountSamples.push(sample);
1510
+ nextBranch = { count: 1, sample };
1511
+ } else {
1512
+ nextBranch = null;
1513
+ }
1514
+ } else if (currentBranch && currentBranch.sample) {
1515
+ currentBranch.sample.count++;
1516
+ }
1517
+ lastSeenHookState.delete(df);
1518
+ if (df.alternate) lastSeenHookState.delete(df.alternate);
1519
+ }
1520
+ if (df.child) collectDeletions(df.child, dfIsComponent, branchStartDepth + 1, nextBranch);
1521
+ if (df.sibling) collectDeletions(df.sibling, parentIsComponent, branchStartDepth, currentBranch);
1522
+ };
1523
+ const deleted = fiber.deletions[di];
1524
+ if (perCommit.unmountSamples == null) perCommit.unmountSamples = [];
1525
+ if (perCommit.unmountSamples.length < 30) {
1526
+ let labelFiber = deleted;
1527
+ if (!isComponentFiber(labelFiber.tag)) {
1528
+ const stack = [labelFiber];
1529
+ labelFiber = null;
1530
+ while (stack.length) {
1531
+ const f = stack.pop();
1532
+ if (isComponentFiber(f.tag)) {
1533
+ labelFiber = f;
1534
+ break;
1535
+ }
1536
+ if (f.child) stack.push(f.child);
1537
+ if (f.sibling) stack.push(f.sibling);
1538
+ }
1539
+ }
1540
+ if (labelFiber) {
1541
+ const counts = countUnmountedComponentSubtree(deleted);
1542
+ const fiberKey = labelFiber.key;
1543
+ perCommit.unmountSamples.push({
1544
+ name: getComponentName(labelFiber),
1545
+ key: fiberKey == null ? null : String(fiberKey),
1546
+ depth,
1547
+ count: counts.components,
1548
+ source: summarizeFiberSource(labelFiber),
1549
+ host: summarizeHostSubtree(deleted)
1550
+ });
1551
+ }
1552
+ }
1553
+ collectDeletions(deleted, false, depth, null);
1554
+ }
1555
+ }
1556
+ const isComponent = isComponentFiber(fiber.tag);
1557
+ let ownerSourceId = currentOwnerSourceId;
1558
+ if (isSuspenseFiber(fiber.tag)) {
1559
+ const suspenseChange = didSuspenseStateChange(fiber);
1560
+ if (suspenseChange) {
1561
+ if (!perCommit.suspenseEvents) {
1562
+ perCommit.suspenseEvents = [];
1563
+ }
1564
+ const event = {
1565
+ depth,
1566
+ entered: suspenseChange.entered,
1567
+ left: suspenseChange.left,
1568
+ ...captureFibers && { fiber }
1569
+ };
1570
+ perCommit.suspenseEvents.push(event);
1571
+ if (enableSourceTracking) {
1572
+ const suspenseType = fiber.type;
1573
+ const sourceId = sourceIdManager.getSourceId(fiber, suspenseType, depth);
1574
+ const suspenseHook = {
1575
+ prevValue: suspenseChange.left ? "suspended" : null,
1576
+ currentValue: suspenseChange.entered ? "suspended" : null,
1577
+ info: suspenseChange.entered ? "suspense:entered-fallback" : "suspense:left-fallback"
1578
+ };
1579
+ perCommit.selfUpdated.set(sourceId, {
1580
+ type: suspenseType,
1581
+ depth,
1582
+ count: 1,
1583
+ changedHookIndices: [0],
1584
+ changedHooks: [suspenseHook],
1585
+ fiber: captureFibers ? fiber : void 0,
1586
+ isUpdater: true
1587
+ });
1588
+ ownerSourceId = sourceId;
1589
+ }
1590
+ }
1591
+ }
1592
+ if (isComponent) {
1593
+ const type = fiber.type;
1594
+ if (didFiberUpdate(fiber)) {
1595
+ perCommit.committedComponentCount++;
1596
+ }
1597
+ if (!didFiberUpdate(fiber)) {
1598
+ if (fiber.child) visit(fiber.child, depth + 1, ownerSourceId);
1599
+ if (fiber.sibling) visit(fiber.sibling, depth, currentOwnerSourceId);
1600
+ return;
1601
+ }
1602
+ if (!ignoreSet.has(type) && !isReactInternalComponentType(type)) {
1603
+ const componentSourceId = sourceIdManager.getSourceId(fiber, type, depth);
1604
+ const parentFiber = fiber.return && isComponentFiber(fiber.return.tag) ? fiber.return : null;
1605
+ const parentSourceId = parentFiber ? sourceIdManager.getSourceId(parentFiber, parentFiber.type, depth - 1) : null;
1606
+ const existingRenderEvidenceIndex = perCommit.rendered.findIndex(
1607
+ (entry) => entry.sourceId === componentSourceId
1608
+ );
1609
+ const initialRenderEvidence = {
1610
+ sourceId: componentSourceId,
1611
+ name: getComponentName(fiber),
1612
+ depth,
1613
+ parentSourceId,
1614
+ ownerSourceId,
1615
+ isSelfSource: false,
1616
+ propChanges: fiber.alternate ? diffProps(fiber.alternate.memoizedProps, fiber.memoizedProps) : [],
1617
+ hasPropChanges: false,
1618
+ trasherCounts: { effects: 0, callbacks: 0, memos: 0 }
1619
+ };
1620
+ initialRenderEvidence.hasPropChanges = initialRenderEvidence.propChanges.length > 0;
1621
+ if (existingRenderEvidenceIndex >= 0) {
1622
+ perCommit.rendered[existingRenderEvidenceIndex] = initialRenderEvidence;
1623
+ } else {
1624
+ perCommit.rendered.push(initialRenderEvidence);
1625
+ }
1626
+ if (enableAmplifiers) {
1627
+ let hook = fiber.memoizedState;
1628
+ let hookIndex = 0;
1629
+ while (hook) {
1630
+ const ms = hook.memoizedState;
1631
+ if (Array.isArray(ms) && ms.length === 2 && Array.isArray(ms[1])) {
1632
+ const result = ms[0];
1633
+ if (result != null && (typeof result === "object" || typeof result === "function")) {
1634
+ valueProvenance.set(result, {
1635
+ type,
1636
+ hookIndex,
1637
+ depth,
1638
+ hookType: typeof result === "function" ? "useCallback" : "useMemo"
1639
+ });
1640
+ }
1641
+ }
1642
+ hook = hook.next;
1643
+ hookIndex++;
1644
+ }
1645
+ }
1646
+ let currentHookSnapshot = null;
1647
+ if (enableSourceTracking) {
1648
+ currentHookSnapshot = captureHookStateSnapshot(fiber);
1649
+ }
1650
+ let isRealSource = false;
1651
+ let changedIndices = [];
1652
+ let changedHooks = [];
1653
+ if (enableSourceTracking) {
1654
+ const inMemoizedUpdaters = memoizedUpdaters ? memoizedUpdaters.has(fiber) || fiber.alternate && memoizedUpdaters.has(fiber.alternate) : null;
1655
+ if (memoizedUpdaters) {
1656
+ if (inMemoizedUpdaters) {
1657
+ isRealSource = true;
1658
+ const selfUpdateResult = didFiberSelfUpdate(fiber);
1659
+ if (selfUpdateResult && selfUpdateResult.actionable) {
1660
+ changedIndices = selfUpdateResult.changedIndices;
1661
+ changedHooks = selfUpdateResult.changedHooks;
1662
+ }
1663
+ }
1664
+ } else {
1665
+ const changes = detectChangesFromLastSeen(fiber, currentHookSnapshot);
1666
+ if (changes.changedIndices.length > 0) {
1667
+ isRealSource = true;
1668
+ changedIndices = changes.changedIndices;
1669
+ changedHooks = changes.changedHooks;
1670
+ }
1671
+ }
1672
+ lastSeenHookState.set(fiber, currentHookSnapshot);
1673
+ if (fiber.alternate) {
1674
+ lastSeenHookState.set(fiber.alternate, currentHookSnapshot);
1675
+ }
1676
+ }
1677
+ if (isRealSource && changedIndices.length > 0) {
1678
+ const sourceId = componentSourceId;
1679
+ perCommit.sourceFibers.add(fiber);
1680
+ if (fiber.alternate) {
1681
+ perCommit.sourceFibers.add(fiber.alternate);
1682
+ }
1683
+ const existing = perCommit.selfUpdated.get(sourceId);
1684
+ const updateData = {
1685
+ type,
1686
+ depth,
1687
+ count: (existing?.count || 0) + 1,
1688
+ changedHookIndices: changedIndices,
1689
+ changedHooks,
1690
+ fiber: captureFibers ? fiber : void 0,
1691
+ isUpdater: true
1692
+ };
1693
+ perCommit.selfUpdated.set(sourceId, updateData);
1694
+ perCommit.selfFibers.set(fiber, depth);
1695
+ ownerSourceId = sourceId;
1696
+ }
1697
+ const isSourceItself = perCommit.sourceFibers.has(fiber);
1698
+ const isNonPreventable = (fiber.flags & (Placement | Hydrating)) !== 0;
1699
+ if (isComponent && fiber.alternate && didFiberUpdate(fiber) && ownerSourceId && !isSourceItself && !isNonPreventable) {
1700
+ perCommit.blastBySourceId.set(
1701
+ ownerSourceId,
1702
+ (perCommit.blastBySourceId.get(ownerSourceId) || 0) + 1
1703
+ );
1704
+ const currentMax = perCommit.depthBySourceId.get(ownerSourceId) || 0;
1705
+ if (depth > currentMax) {
1706
+ perCommit.depthBySourceId.set(ownerSourceId, depth);
1707
+ }
1708
+ perCommit.totalCollateral++;
1709
+ }
1710
+ const renderedEntry = perCommit.rendered.find((entry) => entry.sourceId === componentSourceId);
1711
+ if (renderedEntry) {
1712
+ renderedEntry.ownerSourceId = ownerSourceId;
1713
+ renderedEntry.isSelfSource = isRealSource && changedIndices.length > 0;
1714
+ }
1715
+ if (enableAmplifiers && didFiberUpdate(fiber) && fiber.alternate) {
1716
+ const thrashResult = detectHookThrashing(
1717
+ fiber.alternate.memoizedState,
1718
+ fiber.memoizedState,
1719
+ effectThrashMode,
1720
+ valueProvenance
1721
+ );
1722
+ const totalThrashing = thrashResult.effects.length + thrashResult.callbacks.length + thrashResult.memos.length;
1723
+ if (totalThrashing > 0) {
1724
+ if (thrashMode === "counts") {
1725
+ if (state.thrashCounts) {
1726
+ state.thrashCounts.affectedComponents.add(type);
1727
+ state.thrashCounts.totalHooks += totalThrashing;
1728
+ }
1729
+ } else if (thrashMode === "full") {
1730
+ const allThrashedHooks = [
1731
+ ...thrashResult.effects,
1732
+ ...thrashResult.callbacks,
1733
+ ...thrashResult.memos
1734
+ ];
1735
+ const existing = perCommit.thrashing.get(type);
1736
+ const thrashData = {
1737
+ instanceCount: (existing?.instanceCount || 0) + 1,
1738
+ hookCount: (existing?.hookCount || 0) + totalThrashing,
1739
+ lastAmplification: {
1740
+ depth,
1741
+ thrashedHooks: allThrashedHooks,
1742
+ fiber: captureFibers ? fiber : void 0
1743
+ }
1744
+ };
1745
+ perCommit.thrashing.set(type, thrashData);
1746
+ const amplifierSourceId = sourceIdManager.getSourceId(fiber, type, depth);
1747
+ const existingLinked = perCommit.linkedThrashing.get(amplifierSourceId);
1748
+ const effects = (existingLinked?.effects || 0) + thrashResult.effects.length;
1749
+ const callbacks = (existingLinked?.callbacks || 0) + thrashResult.callbacks.length;
1750
+ const memos = (existingLinked?.memos || 0) + thrashResult.memos.length;
1751
+ perCommit.linkedThrashing.set(amplifierSourceId, {
1752
+ type,
1753
+ depth,
1754
+ effects,
1755
+ callbacks,
1756
+ memos,
1757
+ // If this fiber is a source, causedBySourceId is null (self-caused)
1758
+ // If this fiber is collateral, ownerSourceId is who caused it
1759
+ causedBySourceId: isRealSource ? null : ownerSourceId,
1760
+ lastAmplification: {
1761
+ depth,
1762
+ thrashedHooks: allThrashedHooks,
1763
+ fiber: captureFibers ? fiber : void 0
1764
+ }
1765
+ });
1766
+ const renderedEntry2 = perCommit.rendered.find((entry) => entry.sourceId === amplifierSourceId);
1767
+ if (renderedEntry2) {
1768
+ renderedEntry2.trasherCounts = { effects, callbacks, memos };
1769
+ }
1770
+ perCommit.effectCount += thrashResult.effects.length;
1771
+ perCommit.memoCount += thrashResult.memos.length;
1772
+ perCommit.callbackCount += thrashResult.callbacks.length;
1773
+ }
1774
+ }
1775
+ }
1776
+ }
1777
+ }
1778
+ if (fiber.child) visit(fiber.child, depth + 1, ownerSourceId);
1779
+ if (fiber.sibling) visit(fiber.sibling, depth, currentOwnerSourceId);
1780
+ }
1781
+ visit(rootFiber);
1782
+ state.commits++;
1783
+ for (const [sourceId, data] of perCommit.selfUpdated.entries()) {
1784
+ const { type, depth, count, changedHookIndices, fiber } = data;
1785
+ let s = state.sources.get(sourceId);
1786
+ if (!s) {
1787
+ s = {
1788
+ type,
1789
+ depth,
1790
+ renderCount: 0,
1791
+ causedUpdates: 0,
1792
+ collateralUpdates: 0,
1793
+ maxCollateralUpdates: 0,
1794
+ maxPenetrationDepth: 0,
1795
+ updateHistory: {},
1796
+ collateralByCommit: {},
1797
+ // Per-commit collateral for this source
1798
+ debugHookIndices: changedHookIndices.slice()
1799
+ };
1800
+ state.sources.set(sourceId, s);
1801
+ }
1802
+ s.renderCount++;
1803
+ s.debugHookIndices = changedHookIndices.slice();
1804
+ const historyWithUpdate = pushUpdateHistory(
1805
+ s.updateHistory,
1806
+ state.commits,
1807
+ changedHookIndices,
1808
+ data.changedHooks
1809
+ );
1810
+ s.updateHistory = trimHistoryToSize(historyWithUpdate, historySize);
1811
+ const thisCommitEntry = s.updateHistory[state.commits];
1812
+ const isRepeat = thisCommitEntry === "repeat";
1813
+ if (!isRepeat && data.isUpdater) {
1814
+ s.causedUpdates += count;
1815
+ const blast = perCommit.blastBySourceId.get(sourceId) || 0;
1816
+ s.collateralUpdates += blast;
1817
+ if (blast > s.maxCollateralUpdates) s.maxCollateralUpdates = blast;
1818
+ s.collateralByCommit[state.commits] = blast;
1819
+ const penetrationDepth = perCommit.depthBySourceId.get(sourceId) || 0;
1820
+ if (penetrationDepth > s.maxPenetrationDepth) {
1821
+ s.maxPenetrationDepth = penetrationDepth;
1822
+ }
1823
+ }
1824
+ if (captureFibers && fiber) {
1825
+ s.fiber = fiber;
1826
+ }
1827
+ }
1828
+ if (enableAmplifiers && thrashMode === "full") {
1829
+ for (const [type, data] of perCommit.thrashing.entries()) {
1830
+ let s = state.amplifiers.get(type);
1831
+ if (!s) {
1832
+ s = {
1833
+ renderCount: 0,
1834
+ instancesTotal: 0,
1835
+ maxInstancesPerCommit: 0,
1836
+ hooksTotal: 0,
1837
+ maxHooksPerCommit: 0,
1838
+ effectCount: 0,
1839
+ callbackCount: 0,
1840
+ memoCount: 0,
1841
+ lastAmplification: { depth: 0, thrashedHooks: [] }
1842
+ };
1843
+ state.amplifiers.set(type, s);
1844
+ }
1845
+ s.renderCount++;
1846
+ s.instancesTotal += data.instanceCount;
1847
+ s.hooksTotal += data.hookCount;
1848
+ if (data.instanceCount > s.maxInstancesPerCommit)
1849
+ s.maxInstancesPerCommit = data.instanceCount;
1850
+ if (data.hookCount > s.maxHooksPerCommit) s.maxHooksPerCommit = data.hookCount;
1851
+ data.lastAmplification.thrashedHooks.forEach((hook) => {
1852
+ if (hook.hookType === "effect") s.effectCount++;
1853
+ else if (hook.hookType === "callback") s.callbackCount++;
1854
+ else if (hook.hookType === "memo") s.memoCount++;
1855
+ });
1856
+ s.lastAmplification = data.lastAmplification;
1857
+ }
1858
+ for (const [sourceId, data] of perCommit.linkedThrashing.entries()) {
1859
+ let as = state.amplifierSources.get(sourceId);
1860
+ if (!as) {
1861
+ as = {
1862
+ type: data.type,
1863
+ depth: data.depth,
1864
+ effectCount: 0,
1865
+ callbackCount: 0,
1866
+ memoCount: 0,
1867
+ hooksTotal: 0,
1868
+ commitIds: [],
1869
+ byCommit: {},
1870
+ causedBySourceId: data.causedBySourceId,
1871
+ lastAmplification: { depth: 0, thrashedHooks: [] }
1872
+ };
1873
+ state.amplifierSources.set(sourceId, as);
1874
+ }
1875
+ as.effectCount += data.effects;
1876
+ as.callbackCount += data.callbacks;
1877
+ as.memoCount += data.memos;
1878
+ as.hooksTotal += data.effects + data.callbacks + data.memos;
1879
+ if (!as.commitIds.includes(state.commits)) {
1880
+ as.commitIds.push(state.commits);
1881
+ }
1882
+ as.byCommit[state.commits] = {
1883
+ effects: data.effects,
1884
+ callbacks: data.callbacks,
1885
+ memos: data.memos
1886
+ };
1887
+ if (data.causedBySourceId !== null) {
1888
+ as.causedBySourceId = data.causedBySourceId;
1889
+ }
1890
+ as.lastAmplification = data.lastAmplification;
1891
+ }
1892
+ }
1893
+ if (perCommit.suspenseEvents && perCommit.suspenseEvents.length > 0) {
1894
+ state.suspenseByCommit[state.commits] = perCommit.suspenseEvents;
1895
+ }
1896
+ if (perCommit.rendered.length > 0) {
1897
+ state.renderedByCommit[state.commits] = perCommit.rendered;
1898
+ }
1899
+ state.commitStats[state.commits] = [
1900
+ perCommit.totalCollateral,
1901
+ perCommit.selfUpdated.size,
1902
+ // Number of sources in this commit
1903
+ 0,
1904
+ // restarts - will be updated after the adapter's delta calculation
1905
+ perCommit.committedComponentCount,
1906
+ // performedWork - components with PerformedWork flag
1907
+ perCommit.effectCount,
1908
+ perCommit.memoCount,
1909
+ perCommit.callbackCount,
1910
+ perCommit.contextUpdates,
1911
+ perCommit.unmountedComponents,
1912
+ perCommit.unmountedSubtrees
1913
+ ];
1914
+ if (perCommit.unmountSamples && perCommit.unmountSamples.length > 0) {
1915
+ state.unmountsByCommit[state.commits] = perCommit.unmountSamples;
1916
+ }
1917
+ return perCommit.committedComponentCount;
1918
+ }
1919
+ function resetState() {
1920
+ state.commits = 0;
1921
+ state.sources.clear();
1922
+ state.amplifiers.clear();
1923
+ state.amplifierSources.clear();
1924
+ if (state.thrashCounts) {
1925
+ state.thrashCounts.totalHooks = 0;
1926
+ state.thrashCounts.affectedComponents.clear();
1927
+ }
1928
+ state.memoizedUpdatersAvailable = false;
1929
+ state.commitsWithMemoizedUpdaters = 0;
1930
+ for (const key of Object.keys(state.commitStats)) {
1931
+ delete state.commitStats[Number(key)];
1932
+ }
1933
+ for (const key of Object.keys(state.metricsByCommit)) {
1934
+ delete state.metricsByCommit[Number(key)];
1935
+ }
1936
+ for (const key of Object.keys(state.commitTimings)) {
1937
+ delete state.commitTimings[Number(key)];
1938
+ }
1939
+ for (const key of Object.keys(state.suspenseByCommit)) {
1940
+ delete state.suspenseByCommit[Number(key)];
1941
+ }
1942
+ for (const key of Object.keys(state.unmountsByCommit)) {
1943
+ delete state.unmountsByCommit[Number(key)];
1944
+ }
1945
+ for (const key of Object.keys(state.renderedByCommit)) {
1946
+ delete state.renderedByCommit[Number(key)];
1947
+ }
1948
+ interactionCapture.reset();
1949
+ state.lastMetricsSnapshot = captureMetricsSnapshot();
1950
+ state.lastCommitEndTime = performance.now();
1951
+ sourceIdManager.clear();
1952
+ }
1953
+ const commitSurface = createCommitSurfaceOwnership();
1954
+ let devtoolsHookPrev;
1955
+ let devtoolsUnmountPrev;
1956
+ let devtoolsHookCreatedByUs = false;
1957
+ let pendingDevtoolsUnmounts = { components: 0, subtrees: 0 };
1958
+ let pendingDevtoolsUnmountSamples = [];
1959
+ let integrationMode = "none";
1960
+ function countUnmountedComponentSubtree(fiber, parentIsComponent = false) {
1961
+ if (!fiber) return { components: 0, subtrees: 0 };
1962
+ const isComponent = isComponentFiber(fiber.tag);
1963
+ let components = isComponent ? 1 : 0;
1964
+ let subtrees = isComponent && !parentIsComponent ? 1 : 0;
1965
+ if (fiber.child) {
1966
+ const child = countUnmountedComponentSubtree(fiber.child, isComponent);
1967
+ components += child.components;
1968
+ subtrees += child.subtrees;
1969
+ }
1970
+ if (fiber.sibling) {
1971
+ const sibling = countUnmountedComponentSubtree(fiber.sibling, parentIsComponent);
1972
+ components += sibling.components;
1973
+ subtrees += sibling.subtrees;
1974
+ }
1975
+ return { components, subtrees };
1976
+ }
1977
+ function summarizeHostSubtree(fiber) {
1978
+ const tags = /* @__PURE__ */ new Map();
1979
+ const testIds = /* @__PURE__ */ new Map();
1980
+ const roles = /* @__PURE__ */ new Map();
1981
+ const ariaLabels = [];
1982
+ const textHints = [];
1983
+ let elements = 0;
1984
+ let text = 0;
1985
+ const add = (map, key) => {
1986
+ if (typeof key !== "string" || !key) return;
1987
+ map.set(key, (map.get(key) || 0) + 1);
1988
+ };
1989
+ const addHint = (arr, value, max = 6) => {
1990
+ if (typeof value !== "string") return;
1991
+ const trimmed = value.replace(/\s+/g, " ").trim();
1992
+ if (!trimmed || arr.includes(trimmed)) return;
1993
+ arr.push(trimmed.length > 80 ? `${trimmed.slice(0, 77)}\u2026` : trimmed);
1994
+ if (arr.length > max) arr.length = max;
1995
+ };
1996
+ const top = (map) => Array.from(map.entries()).sort((a, b) => b[1] - a[1]).slice(0, 6);
1997
+ const walk = (f) => {
1998
+ if (!f) return;
1999
+ if (f.tag === 5) {
2000
+ elements++;
2001
+ const props = f.memoizedProps || {};
2002
+ add(tags, typeof f.type === "string" ? f.type : "host");
2003
+ add(testIds, props["data-testid"]);
2004
+ add(roles, props.role);
2005
+ addHint(ariaLabels, props["aria-label"]);
2006
+ addHint(ariaLabels, props.title);
2007
+ addHint(textHints, typeof props.children === "string" ? props.children : null);
2008
+ } else if (f.tag === 6) {
2009
+ text++;
2010
+ addHint(textHints, f.memoizedProps);
2011
+ }
2012
+ walk(f.child);
2013
+ walk(f.sibling);
2014
+ };
2015
+ walk(fiber);
2016
+ return { elements, text, tagsTop: top(tags), testIdsTop: top(testIds), rolesTop: top(roles), ariaLabels, textHints };
2017
+ }
2018
+ function recordMetricsEvidence(commitId, committedComponents, before, after) {
2019
+ if (!metricsAdapter) return;
2020
+ const { delta, restarts } = metricsAdapter.measure(before, after, committedComponents, commitId);
2021
+ state.metricsByCommit[commitId] = delta;
2022
+ if (state.commitStats[commitId]) {
2023
+ state.commitStats[commitId][CS_RESTARTS] = restarts;
2024
+ }
2025
+ }
2026
+ function handleCommit(root, metricsDeltaAvailable) {
2027
+ debugRender.install();
2028
+ const commitHookStartTime = performance.now();
2029
+ const timeSinceLastCommit = commitHookStartTime - state.lastCommitEndTime;
2030
+ const metricsBefore = state.lastMetricsSnapshot || captureMetricsSnapshot();
2031
+ const memoizedUpdaters = root.memoizedUpdaters;
2032
+ const committedComponents = recordTree(root.current, memoizedUpdaters || null);
2033
+ const currentStats = state.commitStats[state.commits];
2034
+ if (currentStats && (pendingDevtoolsUnmounts.components > 0 || pendingDevtoolsUnmounts.subtrees > 0)) {
2035
+ const recordTreeSawDeletions = (currentStats[CS_UNMOUNTED_COMPONENTS] || 0) > 0;
2036
+ if (!recordTreeSawDeletions) {
2037
+ currentStats[CS_UNMOUNTED_COMPONENTS] = pendingDevtoolsUnmounts.components;
2038
+ currentStats[CS_UNMOUNTED_SUBTREES] = pendingDevtoolsUnmounts.subtrees;
2039
+ }
2040
+ pendingDevtoolsUnmounts = { components: 0, subtrees: 0 };
2041
+ }
2042
+ if (pendingDevtoolsUnmountSamples.length > 0) {
2043
+ const existing = state.unmountsByCommit[state.commits];
2044
+ if (existing && existing.length > 0) {
2045
+ } else {
2046
+ state.unmountsByCommit[state.commits] = pendingDevtoolsUnmountSamples;
2047
+ }
2048
+ pendingDevtoolsUnmountSamples = [];
2049
+ }
2050
+ const metricsAfter = captureMetricsSnapshot();
2051
+ if (metricsDeltaAvailable && metricsBefore && metricsAfter) {
2052
+ recordMetricsEvidence(state.commits, committedComponents, metricsBefore, metricsAfter);
2053
+ }
2054
+ state.lastMetricsSnapshot = metricsAfter;
2055
+ const commitHookEndTime = performance.now();
2056
+ interactionCapture.linkRecent(state.commits, commitHookEndTime);
2057
+ state.commitTimings[state.commits] = {
2058
+ timeSinceLastCommit,
2059
+ detectorOverhead: commitHookEndTime - commitHookStartTime
2060
+ };
2061
+ state.lastCommitEndTime = commitHookEndTime;
2062
+ return committedComponents;
2063
+ }
2064
+ return {
2065
+ /**
2066
+ * Start profiling React commits.
2067
+ *
2068
+ * Integration model is OBSERVED at runtime, not declared at install time.
2069
+ *
2070
+ * 'devtools' — DevTools `onCommitFiberRoot` is the canonical entry
2071
+ * point. We define the hook if absent, augment (chain)
2072
+ * if present. On every commit we capture the fiber tree.
2073
+ *
2074
+ * 'adapter:<id>' — Promoted from 'devtools' the first time the commit
2075
+ * metrics adapter's host callback actually fires for a
2076
+ * real commit. From then on the adapter is the
2077
+ * authoritative processor and the DevTools surface is
2078
+ * chained-observer-only. The adapter is preferred when
2079
+ * alive because it carries render-counter deltas
2080
+ * (restart math, metricsByCommit).
2081
+ *
2082
+ * 'none' — Only when no DevTools hook surface can be created and
2083
+ * no adapter surface is reachable. Practically never
2084
+ * hit, since the content-script ships a
2085
+ * devtools-hook-shim before react-dom.
2086
+ *
2087
+ * Double-invocation safety:
2088
+ * Once an adapter callback has fired, DevTools remains chained for
2089
+ * existing tooling but does not capture commits; the adapter alone calls
2090
+ * `handleCommit`. Without a live adapter, DevTools is the capture
2091
+ * surface. This avoids duplicate capture without relying on per-root
2092
+ * timing heuristics.
2093
+ *
2094
+ * Side effects:
2095
+ * - Defines `__REACT_DEVTOOLS_GLOBAL_HOOK__` if it does not already exist
2096
+ * (minimal contract: `onCommitFiberRoot` only). React reads the hook at
2097
+ * module-eval time; the content-script shim handles ordering.
2098
+ * - Asks the adapter (when one is registered) to route its host commit
2099
+ * callback here, *if and when* the host calls it.
2100
+ *
2101
+ * Safe to call multiple times (idempotent: chains skipped when our wrappers
2102
+ * are already in place).
2103
+ */
2104
+ install() {
2105
+ interactionCapture.install();
2106
+ const devtoolsGlobal = globalThis;
2107
+ metricsAdapter = resolveMetricsAdapter();
2108
+ state.lastMetricsSnapshot = captureMetricsSnapshot();
2109
+ let hook = devtoolsGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__;
2110
+ if (!hook) {
2111
+ hook = {};
2112
+ devtoolsGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
2113
+ devtoolsHookCreatedByUs = true;
2114
+ } else {
2115
+ devtoolsHookCreatedByUs = false;
2116
+ }
2117
+ devtoolsHookPrev = typeof hook.onCommitFiberRoot === "function" ? hook.onCommitFiberRoot : void 0;
2118
+ devtoolsUnmountPrev = typeof hook.onCommitFiberUnmount === "function" ? hook.onCommitFiberUnmount : void 0;
2119
+ const devtoolsHandler = (...args) => {
2120
+ if (devtoolsHookPrev) {
2121
+ try {
2122
+ devtoolsHookPrev(...args);
2123
+ } catch (_) {
2124
+ }
2125
+ }
2126
+ const root = args[1];
2127
+ if (!root) return;
2128
+ if (commitSurface.adapterObserved && metricsAdapter) {
2129
+ if (!metricsAdapter.isAttached()) {
2130
+ metricsAdapter.reattach();
2131
+ const metricsBefore2 = state.lastMetricsSnapshot || captureMetricsSnapshot();
2132
+ const committedComponents2 = handleCommit(
2133
+ root,
2134
+ /* metricsDeltaAvailable */
2135
+ false
2136
+ );
2137
+ commitSurface.recordDevtools(root, { commitId: state.commits, committedComponents: committedComponents2, metricsBefore: metricsBefore2 });
2138
+ return;
2139
+ }
2140
+ return;
2141
+ }
2142
+ const metricsBefore = state.lastMetricsSnapshot || captureMetricsSnapshot();
2143
+ const committedComponents = handleCommit(
2144
+ root,
2145
+ /* metricsDeltaAvailable */
2146
+ false
2147
+ );
2148
+ commitSurface.recordDevtools(root, { commitId: state.commits, committedComponents, metricsBefore });
2149
+ };
2150
+ hook.onCommitFiberRoot = devtoolsHandler;
2151
+ const devtoolsUnmountHandler = (...args) => {
2152
+ if (devtoolsUnmountPrev) {
2153
+ try {
2154
+ devtoolsUnmountPrev(...args);
2155
+ } catch (_) {
2156
+ }
2157
+ }
2158
+ const fiber = args[1];
2159
+ if (!fiber) return;
2160
+ const counts = countUnmountedComponentSubtree(fiber);
2161
+ pendingDevtoolsUnmounts.components += counts.components;
2162
+ pendingDevtoolsUnmounts.subtrees += counts.subtrees;
2163
+ if (counts.components > 0 && pendingDevtoolsUnmountSamples.length < 30) {
2164
+ let labelFiber = fiber;
2165
+ if (!isComponentFiber(labelFiber.tag)) {
2166
+ const stack = [labelFiber];
2167
+ labelFiber = null;
2168
+ while (stack.length) {
2169
+ const f = stack.pop();
2170
+ if (isComponentFiber(f.tag)) {
2171
+ labelFiber = f;
2172
+ break;
2173
+ }
2174
+ if (f.child) stack.push(f.child);
2175
+ if (f.sibling) stack.push(f.sibling);
2176
+ }
2177
+ }
2178
+ if (labelFiber) {
2179
+ const fiberKey = labelFiber.key;
2180
+ pendingDevtoolsUnmountSamples.push({
2181
+ name: getComponentName(labelFiber),
2182
+ key: fiberKey == null ? null : String(fiberKey),
2183
+ depth: null,
2184
+ count: counts.components,
2185
+ source: summarizeFiberSource(labelFiber),
2186
+ host: summarizeHostSubtree(fiber)
2187
+ });
2188
+ }
2189
+ }
2190
+ lastSeenHookState.delete(fiber);
2191
+ if (fiber.alternate) lastSeenHookState.delete(fiber.alternate);
2192
+ };
2193
+ hook.onCommitFiberUnmount = devtoolsUnmountHandler;
2194
+ integrationMode = "devtools";
2195
+ commitSurface.reset();
2196
+ const adapter = metricsAdapter;
2197
+ if (adapter) {
2198
+ const adapterMode = `adapter:${adapter.id}`;
2199
+ adapter.attach((root) => {
2200
+ const previousCapture = commitSurface.observeAdapter(root);
2201
+ if (integrationMode !== adapterMode) {
2202
+ integrationMode = adapterMode;
2203
+ }
2204
+ if (previousCapture) {
2205
+ const metricsAfter = captureMetricsSnapshot();
2206
+ if (previousCapture.metricsBefore && metricsAfter) {
2207
+ recordMetricsEvidence(previousCapture.commitId, previousCapture.committedComponents, previousCapture.metricsBefore, metricsAfter);
2208
+ }
2209
+ state.lastMetricsSnapshot = metricsAfter;
2210
+ } else {
2211
+ handleCommit(
2212
+ root,
2213
+ /* metricsDeltaAvailable */
2214
+ true
2215
+ );
2216
+ }
2217
+ });
2218
+ }
2219
+ },
2220
+ /**
2221
+ * Stop profiling React commits. Does not clear accumulated state.
2222
+ * Call reset() to clear state.
2223
+ *
2224
+ * Restores the DevTools hook to its prior state:
2225
+ * - hook was created by us → delete the hook entirely
2226
+ * - hook had a prior onCommitFiberRoot → restore it
2227
+ * - hook had no handler → delete the property
2228
+ *
2229
+ * Asks the adapter to remove its host callback only if it is still ours
2230
+ * (don't stomp a concurrent installer).
2231
+ */
2232
+ uninstall() {
2233
+ metricsAdapter?.detach();
2234
+ const devtoolsGlobal = globalThis;
2235
+ const hook = devtoolsGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__;
2236
+ if (hook) {
2237
+ if (devtoolsHookCreatedByUs) {
2238
+ delete devtoolsGlobal.__REACT_DEVTOOLS_GLOBAL_HOOK__;
2239
+ } else {
2240
+ if (devtoolsHookPrev) {
2241
+ hook.onCommitFiberRoot = devtoolsHookPrev;
2242
+ } else {
2243
+ delete hook.onCommitFiberRoot;
2244
+ }
2245
+ if (devtoolsUnmountPrev) {
2246
+ hook.onCommitFiberUnmount = devtoolsUnmountPrev;
2247
+ } else {
2248
+ delete hook.onCommitFiberUnmount;
2249
+ }
2250
+ }
2251
+ }
2252
+ debugRender.uninstall();
2253
+ interactionCapture.uninstall();
2254
+ commitSurface.reset();
2255
+ pendingDevtoolsUnmounts = { components: 0, subtrees: 0 };
2256
+ pendingDevtoolsUnmountSamples = [];
2257
+ devtoolsHookPrev = void 0;
2258
+ devtoolsUnmountPrev = void 0;
2259
+ devtoolsHookCreatedByUs = false;
2260
+ integrationMode = "none";
2261
+ },
2262
+ /**
2263
+ * Clear all accumulated profiling state. Useful for starting fresh measurements.
2264
+ * Does not uninstall the profiler - call uninstall() separately if needed.
2265
+ */
2266
+ reset() {
2267
+ resetState();
2268
+ debugRender.reset();
2269
+ },
2270
+ /**
2271
+ * Generate a report of top update sources and thrashing components.
2272
+ * @param topN - Number of top sources to include (default: 10)
2273
+ * @param sortBy - Sort thrashers by 'effect', 'callback', 'memo', or 'total' (default: 'effect')
2274
+ * @param options - Report filtering options (e.g., minCollateral to hide low-impact sources)
2275
+ * @returns Report containing sources, blast radius, and optional thrashing data
2276
+ */
2277
+ report(topN = 10, sortBy = "effect", reportOptions) {
2278
+ return buildReport(state, thrashMode, topN, sortBy, reportOptions, {
2279
+ effectThrashMode,
2280
+ captureFibers,
2281
+ historySize,
2282
+ detectionLevel,
2283
+ integrationMode
2284
+ });
2285
+ },
2286
+ /**
2287
+ * Raw passive hook events captured for a component (debug surface).
2288
+ */
2289
+ _debugRawHookEvents(componentName) {
2290
+ const components = debugRender.allComponents();
2291
+ const events = componentName === "*" ? [] : debugRender.rawEvents(componentName);
2292
+ return {
2293
+ ok: true,
2294
+ componentName,
2295
+ status: debugRender.status(),
2296
+ components,
2297
+ events
2298
+ };
2299
+ },
2300
+ _debugInjectHookEventsForTest(componentName, events) {
2301
+ if (typeof debugRender.recordEventForTest !== "function") return;
2302
+ for (const event of events) {
2303
+ debugRender.recordEventForTest({ ...event, componentName });
2304
+ }
2305
+ },
2306
+ _debugSetSourceForTest(sourceId, source) {
2307
+ state.sources.set(sourceId, source);
2308
+ },
2309
+ debugHook(sourceId, hookIndex, windowSize = 3) {
2310
+ let source = state.sources.get(sourceId);
2311
+ let resolvedSourceId = sourceId;
2312
+ if (!source) {
2313
+ for (const [internalId, candidate] of state.sources.entries()) {
2314
+ const candidateHumanId = `${getComponentDisplayName(candidate.type)}#${String(internalId).match(/#(\d+)@/)?.[1] || "1"}@${candidate.depth}`;
2315
+ if (candidateHumanId === sourceId) {
2316
+ source = candidate;
2317
+ resolvedSourceId = internalId;
2318
+ break;
2319
+ }
2320
+ }
2321
+ }
2322
+ if (!source) return { ok: false, error: "sourceId not found", sourceId };
2323
+ const componentName = getComponentDisplayName(source.type);
2324
+ let events = debugRender.rawEvents(componentName);
2325
+ let resolvedComponentName = componentName;
2326
+ if (events.length === 0) {
2327
+ const candidates = debugRender.allComponents();
2328
+ const matching = candidates.find((name) => name === componentName || name.includes(componentName) || componentName.includes(name));
2329
+ if (matching) {
2330
+ resolvedComponentName = matching;
2331
+ events = debugRender.rawEvents(matching);
2332
+ }
2333
+ }
2334
+ const sourceHookIndices = Array.isArray(source.debugHookIndices) ? source.debugHookIndices : [];
2335
+ const targetIndex = Math.floor(hookIndex);
2336
+ const eventIndex = sourceHookIndices[targetIndex] ?? targetIndex;
2337
+ const target = events[eventIndex];
2338
+ const radius = Math.max(0, Math.floor(windowSize));
2339
+ if (!target) {
2340
+ return {
2341
+ ok: false,
2342
+ error: "hook event not found",
2343
+ sourceId: resolvedSourceId,
2344
+ requestedSourceId: sourceId,
2345
+ componentName,
2346
+ resolvedComponentName,
2347
+ hookIndex: targetIndex,
2348
+ eventIndex,
2349
+ sourceHookIndices,
2350
+ eventCount: events.length,
2351
+ status: debugRender.status(),
2352
+ caveat: "hook ancestry comes from passive stack capture during real renders; component-name stack parsing can be ambiguous in bundled/minified code"
2353
+ };
2354
+ }
2355
+ const neighborhood = events.map((event, index) => ({ event, index })).filter(({ index }) => Math.abs(index - eventIndex) <= radius).map(({ event, index }) => ({
2356
+ hookIndex: sourceHookIndices.indexOf(index) >= 0 ? sourceHookIndices.indexOf(index) : index,
2357
+ eventIndex: index,
2358
+ primitive: event.primitive,
2359
+ parents: parentFramesForEvent(event, componentName)
2360
+ }));
2361
+ return {
2362
+ ok: true,
2363
+ sourceId: resolvedSourceId,
2364
+ requestedSourceId: sourceId,
2365
+ componentName,
2366
+ resolvedComponentName,
2367
+ hookIndex: targetIndex,
2368
+ eventIndex,
2369
+ sourceHookIndices,
2370
+ primitive: target.primitive,
2371
+ parents: parentFramesForEvent(target, componentName),
2372
+ neighborhood,
2373
+ eventCount: events.length,
2374
+ status: debugRender.status(),
2375
+ caveat: "hook ancestry comes from passive stack capture during real renders; source-local hook indices are mapped onto the captured component event stream"
2376
+ };
2377
+ },
2378
+ /**
2379
+ * Returns which commit-hook integration model is currently observed.
2380
+ * 'devtools' — DevTools hook is the active processor (no proven adapter callbacks).
2381
+ * 'adapter:<id>' — The adapter's host surface has fired; it owns the math now.
2382
+ * 'none' — Detector not installed.
2383
+ */
2384
+ getIntegrationMode() {
2385
+ return integrationMode;
2386
+ },
2387
+ /** Id of the commit metrics adapter resolved on install, or null. */
2388
+ getMetricsAdapterId() {
2389
+ return metricsAdapter?.id ?? null;
2390
+ }
2391
+ };
2392
+ }
2393
+ var DETECTION_LEVEL = {
2394
+ /** Level 1: Only commitStats (performedWork for restarts) - minimal overhead */
2395
+ LEVEL1_STATS: "stats",
2396
+ /** Level 2: Stats + source tracking (which components caused updates) */
2397
+ LEVEL2_SOURCES: "sources",
2398
+ /** Level 3a: Sources + aggregate amplifier counts only (meta.amplifiersMetrics) */
2399
+ LEVEL3_AMPLIFIER_METRICS: "amplifierMetrics",
2400
+ /** Level 3b: Sources + full amplifier arrays (report.amplifiers, report.amplifierSources) */
2401
+ LEVEL3_AMPLIFIERS: "amplifiers"
2402
+ };
2403
+
2404
+ // src/plugins/atl/metrics-slot.ts
2405
+ var DEFAULT_METRICS_SLOT = "__ATL_REACT_METRICS";
2406
+ var METRICS_COUNTER_KEYS = [
2407
+ "mountWorkCount",
2408
+ "updateWorkCount",
2409
+ "commitWorkCount",
2410
+ "interruptedWorkCount",
2411
+ "priorityInterruptedWorkCount",
2412
+ "externalStoreRestartCount",
2413
+ "renderRootSyncCount",
2414
+ "renderRootConcurrentCount",
2415
+ "componentWorkCount",
2416
+ "componentMountWorkCount",
2417
+ "componentUpdateWorkCount",
2418
+ "componentRenderWithHooksCount",
2419
+ "finishClassComponentCount",
2420
+ "domWorkCount",
2421
+ "totalTickCount",
2422
+ "totalCommitCount",
2423
+ "contextUpdateCount",
2424
+ "legacyContextUpdateCount"
2425
+ ];
2426
+ var ACCESSOR_KEYS = [...METRICS_COUNTER_KEYS, "totalRestarts", "totalPerformedWork"];
2427
+ function createZeroMetrics() {
2428
+ const metrics = {};
2429
+ for (const key of METRICS_COUNTER_KEYS) metrics[key] = 0;
2430
+ metrics.setStateCount = 0;
2431
+ return metrics;
2432
+ }
2433
+ function isAllZero(value) {
2434
+ if (!value || typeof value !== "object") return true;
2435
+ const record = value;
2436
+ for (const key of ACCESSOR_KEYS) if ((record[key] || 0) !== 0) return false;
2437
+ return true;
2438
+ }
2439
+ function isSlotHostLocked(target, slotName = DEFAULT_METRICS_SLOT) {
2440
+ const existing = Object.getOwnPropertyDescriptor(target, slotName);
2441
+ return !!existing && (existing.configurable === false || typeof existing.get === "function");
2442
+ }
2443
+ function protectMetricsSlot(target, metrics, slotName = DEFAULT_METRICS_SLOT) {
2444
+ try {
2445
+ const existing = Object.getOwnPropertyDescriptor(target, slotName);
2446
+ if (existing && existing.configurable === false) return;
2447
+ Object.defineProperty(target, slotName, { configurable: true, enumerable: true, writable: true, value: metrics });
2448
+ } catch {
2449
+ try {
2450
+ target[slotName] = metrics;
2451
+ } catch {
2452
+ }
2453
+ }
2454
+ }
2455
+ function seedMetricsSlot(target, slotName = DEFAULT_METRICS_SLOT) {
2456
+ const metrics = createZeroMetrics();
2457
+ try {
2458
+ target[slotName] = metrics;
2459
+ } catch {
2460
+ }
2461
+ protectMetricsSlot(target, metrics, slotName);
2462
+ return metrics;
2463
+ }
2464
+ function installMetricsAccessor(target, slotName = DEFAULT_METRICS_SLOT) {
2465
+ try {
2466
+ const existing = Object.getOwnPropertyDescriptor(target, slotName);
2467
+ if (existing && existing.configurable === false) return false;
2468
+ let live = existing && typeof existing.get === "function" ? target[slotName] : existing?.value;
2469
+ if (!live || typeof live !== "object") {
2470
+ live = { onCommitFiberRoot: null };
2471
+ for (const key of ACCESSOR_KEYS) live[key] = 0;
2472
+ }
2473
+ Object.defineProperty(target, slotName, {
2474
+ configurable: true,
2475
+ enumerable: true,
2476
+ get() {
2477
+ return live;
2478
+ },
2479
+ set(next) {
2480
+ if (next === void 0 || next === null || isAllZero(next)) return;
2481
+ live = next;
2482
+ }
2483
+ });
2484
+ return true;
2485
+ } catch {
2486
+ return false;
2487
+ }
2488
+ }
2489
+
2490
+ // src/plugins/atl/commit-metrics-adapter.ts
2491
+ var ATL_DISPATCHER_FRAME = "atlDispatcherHook";
2492
+ var num = (snapshot, key) => snapshot[key] ?? 0;
2493
+ var diff = (before, after, key) => num(after, key) - num(before, key);
2494
+ function calculateATLDelta(before, after) {
2495
+ return {
2496
+ // Basic work counters
2497
+ mountWorkCount: diff(before, after, "mountWorkCount"),
2498
+ updateWorkCount: diff(before, after, "updateWorkCount"),
2499
+ commitWorkCount: diff(before, after, "commitWorkCount"),
2500
+ performedWorkCount: diff(before, after, "totalPerformedWork"),
2501
+ // Interruption/restart counters
2502
+ interruptedWorkCount: diff(before, after, "interruptedWorkCount"),
2503
+ externalStoreRestartCount: diff(before, after, "externalStoreRestartCount"),
2504
+ priorityInterruptedWorkCount: diff(before, after, "priorityInterruptedWorkCount"),
2505
+ renderRootSyncCount: diff(before, after, "renderRootSyncCount"),
2506
+ renderRootConcurrentCount: diff(before, after, "renderRootConcurrentCount"),
2507
+ // Scheduler
2508
+ ticksCount: diff(before, after, "totalTickCount"),
2509
+ // Component work counters
2510
+ componentWorkCount: diff(before, after, "componentWorkCount"),
2511
+ componentMountWorkCount: diff(before, after, "componentMountWorkCount"),
2512
+ componentUpdateWorkCount: diff(before, after, "componentUpdateWorkCount"),
2513
+ componentRenderWithHooksCount: diff(before, after, "componentRenderWithHooksCount"),
2514
+ finishClassComponentCount: diff(before, after, "finishClassComponentCount"),
2515
+ // DOM work
2516
+ domWorkCount: diff(before, after, "domWorkCount"),
2517
+ // Context updates
2518
+ contextUpdateCount: diff(before, after, "contextUpdateCount"),
2519
+ legacyContextUpdateCount: diff(before, after, "legacyContextUpdateCount"),
2520
+ // Hook-metric delta (react-dom patch; optional). Omit when neither snapshot
2521
+ // carries a numeric setStateCount so we never emit a bogus 0 on unpatched hosts.
2522
+ // TODO: Require finite values on both sides or model a counter reset
2523
+ // explicitly. Treating a missing/wiped side as zero can emit a negative delta,
2524
+ // and typeof NaN === 'number' lets a poisoned counter enter the report.
2525
+ ...typeof after.setStateCount === "number" || typeof before.setStateCount === "number" ? { setStateCount: diff(before, after, "setStateCount") } : {}
2526
+ };
2527
+ }
2528
+ function createATLCommitMetricsAdapter(options = {}) {
2529
+ const slotName = options.slotName ?? DEFAULT_METRICS_SLOT;
2530
+ const target = () => options.target ?? globalThis;
2531
+ const slot = () => {
2532
+ const value = target()[slotName];
2533
+ return value && typeof value === "object" ? value : null;
2534
+ };
2535
+ let ourHandler;
2536
+ return {
2537
+ id: "atl",
2538
+ frameFilters: [ATL_DISPATCHER_FRAME],
2539
+ prePage() {
2540
+ installMetricsAccessor(target(), slotName);
2541
+ },
2542
+ prepare() {
2543
+ try {
2544
+ if (!isSlotHostLocked(target(), slotName)) seedMetricsSlot(target(), slotName);
2545
+ } catch {
2546
+ }
2547
+ },
2548
+ isAvailable: () => !!slot(),
2549
+ snapshot() {
2550
+ const live = slot();
2551
+ return live ? { ...live } : null;
2552
+ },
2553
+ measure(before, after, committedComponents, commitId) {
2554
+ const delta = calculateATLDelta(before, after);
2555
+ delta.performedWorkCount = committedComponents;
2556
+ const rendered = (delta.componentRenderWithHooksCount ?? 0) + (delta.finishClassComponentCount ?? 0);
2557
+ const restarts = Math.max(0, rendered - committedComponents);
2558
+ if (committedComponents > rendered && rendered > 0) {
2559
+ console.warn(
2560
+ `[Avalanche] Commit ${commitId}: committedComponents (${committedComponents}) > atlTotal (${rendered}). This indicates a counting bug. Using 0 restarts.`
2561
+ );
2562
+ }
2563
+ const live = slot();
2564
+ if (live) {
2565
+ live.totalRestarts = (live.totalRestarts ?? 0) + restarts;
2566
+ live.totalPerformedWork = (live.totalPerformedWork ?? 0) + committedComponents;
2567
+ }
2568
+ return { delta, restarts };
2569
+ },
2570
+ attach(onCommit) {
2571
+ const live = slot();
2572
+ if (!live) return false;
2573
+ const handler = (_rendererID, root) => onCommit(root);
2574
+ live.onCommitFiberRoot = handler;
2575
+ ourHandler = handler;
2576
+ return true;
2577
+ },
2578
+ isAttached() {
2579
+ const live = slot();
2580
+ return !!live && !!ourHandler && live.onCommitFiberRoot === ourHandler;
2581
+ },
2582
+ reattach() {
2583
+ const fresh = seedMetricsSlot(target(), slotName);
2584
+ if (ourHandler) fresh.onCommitFiberRoot = ourHandler;
2585
+ },
2586
+ detach() {
2587
+ const live = slot();
2588
+ if (live && ourHandler && live.onCommitFiberRoot === ourHandler) live.onCommitFiberRoot = void 0;
2589
+ ourHandler = void 0;
2590
+ }
2591
+ };
2592
+ }
2593
+ var atlCommitMetricsAdapter = createATLCommitMetricsAdapter();
2594
+ export {
2595
+ DETECTION_LEVEL,
2596
+ atlCommitMetricsAdapter,
2597
+ createATLCommitMetricsAdapter,
2598
+ createAvalancheDetector,
2599
+ registerCommitMetricsAdapter
2600
+ };
2601
+ //# sourceMappingURL=avalanche.js.map