@buoy-gg/highlight-updates 7.0.41 → 7.0.42

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 (108) hide show
  1. package/README.md +11 -71
  2. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +1 -281
  3. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1 -1474
  4. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +1 -641
  5. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +1 -273
  6. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +1 -188
  7. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +1 -293
  8. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +1 -508
  9. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +1 -846
  10. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +1 -915
  11. package/lib/commonjs/highlight-updates/components/RenderListItem.js +1 -235
  12. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +1 -469
  13. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +1 -73
  14. package/lib/commonjs/highlight-updates/types/copySettings.js +1 -107
  15. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +1 -455
  16. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1 -1929
  17. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +26 -359
  18. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +1 -371
  19. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +5 -1840
  20. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +1 -952
  21. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +1 -264
  22. package/lib/commonjs/highlight-updates/utils/copySettingsStorage.js +1 -32
  23. package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +1 -188
  24. package/lib/commonjs/highlight-updates/utils/namedComposite.js +1 -41
  25. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +1 -103
  26. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.js +1 -50
  27. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.web.js +1 -12
  28. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +1 -58
  29. package/lib/commonjs/highlight-updates/utils/rendersExportFormatter.js +1 -485
  30. package/lib/commonjs/highlight-updates/utils/screenElements.js +1 -1269
  31. package/lib/commonjs/highlight-updates/utils/tapMatch.js +1 -83
  32. package/lib/commonjs/highlight-updates/utils/testIDs.js +1 -68
  33. package/lib/commonjs/highlight-updates/utils/touchCapture.js +1 -277
  34. package/lib/commonjs/index.js +1 -429
  35. package/lib/commonjs/preset.js +1 -278
  36. package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +1 -235
  37. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +1 -274
  38. package/lib/module/highlight-updates/components/HighlightFilterView.js +1 -1468
  39. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +1 -634
  40. package/lib/module/highlight-updates/components/IdentifierBadge.js +1 -265
  41. package/lib/module/highlight-updates/components/IsolatedRenderList.js +1 -183
  42. package/lib/module/highlight-updates/components/ModalHeaderContent.js +1 -288
  43. package/lib/module/highlight-updates/components/RenderCauseBadge.js +1 -499
  44. package/lib/module/highlight-updates/components/RenderDetailView.js +1 -840
  45. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +1 -908
  46. package/lib/module/highlight-updates/components/RenderListItem.js +1 -230
  47. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +1 -465
  48. package/lib/module/highlight-updates/components/StatsDisplay.js +1 -70
  49. package/lib/module/highlight-updates/types/copySettings.js +1 -102
  50. package/lib/module/highlight-updates/utils/CommitProfiler.js +1 -451
  51. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1 -1925
  52. package/lib/module/highlight-updates/utils/PerformanceLogger.js +26 -353
  53. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +1 -358
  54. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +5 -1830
  55. package/lib/module/highlight-updates/utils/RenderTracker.js +1 -949
  56. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +1 -255
  57. package/lib/module/highlight-updates/utils/copySettingsStorage.js +1 -26
  58. package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +1 -178
  59. package/lib/module/highlight-updates/utils/namedComposite.js +1 -36
  60. package/lib/module/highlight-updates/utils/ownStateChanged.js +1 -98
  61. package/lib/module/highlight-updates/utils/rawEventEmitter.js +1 -46
  62. package/lib/module/highlight-updates/utils/rawEventEmitter.web.js +1 -3
  63. package/lib/module/highlight-updates/utils/renderExportFormatter.js +1 -54
  64. package/lib/module/highlight-updates/utils/rendersExportFormatter.js +1 -478
  65. package/lib/module/highlight-updates/utils/screenElements.js +1 -1263
  66. package/lib/module/highlight-updates/utils/tapMatch.js +1 -76
  67. package/lib/module/highlight-updates/utils/testIDs.js +1 -60
  68. package/lib/module/highlight-updates/utils/touchCapture.js +1 -270
  69. package/lib/module/index.js +1 -130
  70. package/lib/module/preset.js +1 -272
  71. package/lib/module/sync/highlightUpdatesSyncAdapter.js +1 -230
  72. package/lib/web/index.mjs +28 -29
  73. package/package.json +7 -7
  74. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +0 -1
  75. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +0 -1
  76. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +0 -1
  77. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +0 -1
  78. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +0 -1
  79. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +0 -1
  80. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +0 -1
  81. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +0 -1
  82. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +0 -1
  83. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +0 -1
  84. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +0 -1
  85. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +0 -1
  86. package/lib/typescript/highlight-updates/types/copySettings.d.ts.map +0 -1
  87. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +0 -1
  88. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +0 -1
  89. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +0 -1
  90. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +0 -1
  91. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +0 -1
  92. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +0 -1
  93. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +0 -1
  94. package/lib/typescript/highlight-updates/utils/copySettingsStorage.d.ts.map +0 -1
  95. package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +0 -1
  96. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +0 -1
  97. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +0 -1
  98. package/lib/typescript/highlight-updates/utils/rawEventEmitter.d.ts.map +0 -1
  99. package/lib/typescript/highlight-updates/utils/rawEventEmitter.web.d.ts.map +0 -1
  100. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +0 -1
  101. package/lib/typescript/highlight-updates/utils/rendersExportFormatter.d.ts.map +0 -1
  102. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +0 -1
  103. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +0 -1
  104. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +0 -1
  105. package/lib/typescript/highlight-updates/utils/touchCapture.d.ts.map +0 -1
  106. package/lib/typescript/index.d.ts.map +0 -1
  107. package/lib/typescript/preset.d.ts.map +0 -1
  108. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +0 -1
@@ -1,46 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * The one guarded require for React Native's RawEventEmitter.
5
- *
6
- * `RawEventEmitter` mirrors every event React Native dispatches BEFORE React's
7
- * plugins or components see it, which makes it the only way to observe what a
8
- * HUMAN is touching without instrumenting the app: Buoy mounts as a sibling of
9
- * the app tree, not a parent, so there is no root View to put a capture
10
- * responder on.
11
- *
12
- * Two things this file is careful about, both learned the hard way in
13
- * focus-inspector's copy:
14
- *
15
- * 1. It is a DEEP import that RN's export map warns about and could move in any
16
- * release. The failure has to degrade to "capture unavailable" rather than
17
- * taking the host app's bundle down — and Metro only treats a require as
18
- * optional when it sits LEXICALLY inside a try statement, so wrapping it in
19
- * a helper or an arrow makes Metro hard-fail the bundle instead.
20
- * 2. Keeping it in its own module gives the unit suite a seam: Node's `require`
21
- * ignores Vitest's module mocks, so the emitter can only be faked by mocking
22
- * a module reached with a real `import`.
23
- */
24
-
25
- export function resolveRawEventEmitter() {
26
- let mod;
27
- try {
28
- mod = require("react-native/Libraries/Core/RawEventEmitter");
29
- } catch (e) {
30
- return {
31
- emitter: null,
32
- reason: e?.message ?? "RawEventEmitter is unavailable"
33
- };
34
- }
35
- const candidate = mod?.default ?? mod;
36
- if (typeof candidate?.addListener !== "function") {
37
- return {
38
- emitter: null,
39
- reason: "RawEventEmitter has no addListener"
40
- };
41
- }
42
- return {
43
- emitter: candidate,
44
- reason: null
45
- };
46
- }
1
+ "use strict";export function resolveRawEventEmitter(){let e;try{e=require("react-native/Libraries/Core/RawEventEmitter")}catch(r){return{emitter:null,reason:r?.message??"RawEventEmitter is unavailable"}}const t=e?.default??e;if(typeof t?.addListener!=="function"){return{emitter:null,reason:"RawEventEmitter has no addListener"}}return{emitter:t,reason:null}}
@@ -1,3 +1 @@
1
- "use strict";
2
-
3
- export { resolveDOMEventEmitter as resolveRawEventEmitter } from '@buoy-gg/shared-ui/utils';
1
+ "use strict";export{resolveDOMEventEmitter as resolveRawEventEmitter}from"@buoy-gg/shared-ui/utils";
@@ -1,54 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Render Export Formatter
5
- *
6
- * Simple JSON export for render tracking data.
7
- */
8
-
9
- /**
10
- * Generate JSON export of all tracked renders
11
- */
12
- export function generateRendersJson(renders) {
13
- const exportData = {
14
- exportedAt: new Date().toISOString(),
15
- totalComponents: renders.length,
16
- totalRenders: renders.reduce((sum, r) => sum + r.renderCount, 0),
17
- components: renders.map(render => ({
18
- // Identity
19
- componentName: render.componentName || render.displayName,
20
- viewType: render.viewType,
21
- nativeTag: render.nativeTag,
22
- // Identifiers
23
- testID: render.testID,
24
- nativeID: render.nativeID,
25
- accessibilityLabel: render.accessibilityLabel,
26
- // Render stats
27
- renderCount: render.renderCount,
28
- firstRenderTime: render.firstRenderTime,
29
- lastRenderTime: render.lastRenderTime,
30
- // Last render cause
31
- lastRenderCause: render.lastRenderCause ? {
32
- type: render.lastRenderCause.type,
33
- componentCause: render.lastRenderCause.componentCause,
34
- changedKeys: render.lastRenderCause.changedKeys,
35
- hookChanges: render.lastRenderCause.hookChanges?.map(h => ({
36
- type: h.type,
37
- index: h.index,
38
- description: h.description
39
- }))
40
- } : null,
41
- // Render history (if available)
42
- renderHistory: render.renderHistory?.map(event => ({
43
- renderNumber: event.renderNumber,
44
- timestamp: event.timestamp,
45
- cause: {
46
- type: event.cause.type,
47
- componentCause: event.cause.componentCause,
48
- changedKeys: event.cause.changedKeys
49
- }
50
- }))
51
- }))
52
- };
53
- return JSON.stringify(exportData, null, 2);
54
- }
1
+ "use strict";export function generateRendersJson(a){const s={exportedAt:new Date().toISOString(),totalComponents:a.length,totalRenders:a.reduce((e,t)=>e+t.renderCount,0),components:a.map(e=>({componentName:e.componentName||e.displayName,viewType:e.viewType,nativeTag:e.nativeTag,testID:e.testID,nativeID:e.nativeID,accessibilityLabel:e.accessibilityLabel,renderCount:e.renderCount,firstRenderTime:e.firstRenderTime,lastRenderTime:e.lastRenderTime,lastRenderCause:e.lastRenderCause?{type:e.lastRenderCause.type,componentCause:e.lastRenderCause.componentCause,changedKeys:e.lastRenderCause.changedKeys,hookChanges:e.lastRenderCause.hookChanges?.map(t=>({type:t.type,index:t.index,description:t.description}))}:null,renderHistory:e.renderHistory?.map(t=>({renderNumber:t.renderNumber,timestamp:t.timestamp,cause:{type:t.cause.type,componentCause:t.cause.componentCause,changedKeys:t.cause.changedKeys}}))}))};return JSON.stringify(s,null,2)}
@@ -1,478 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * Renders Export Formatter
5
- *
6
- * Pipeline: TrackedRender[] -> filter -> group -> aggregate -> sort -> format.
7
- * Goal: compact, debug-focused output. Reports facts, not opinions.
8
- */
9
-
10
- // =============================================================================
11
- // Internal types
12
- // =============================================================================
13
-
14
- // =============================================================================
15
- // Pipeline
16
- // =============================================================================
17
-
18
- function buildReport(renders, settings) {
19
- let working = renders.filter(r => r.renderCount >= settings.minRenders);
20
- if (settings.filterCauses.length > 0) {
21
- const allowed = new Set(settings.filterCauses);
22
- working = working.filter(r => allowed.has(r.lastRenderCause?.type ?? "unknown"));
23
- }
24
-
25
- // Window — computed from the original render list, not after topN
26
- const allTimes = renders.flatMap(r => [r.firstRenderTime, r.lastRenderTime]).filter(t => Number.isFinite(t));
27
- const windowMs = allTimes.length > 0 ? Math.max(...allTimes) - Math.min(...allTimes) : 0;
28
- const windowSec = Math.max(0.001, windowMs / 1000);
29
- const groups = settings.groupByName ? groupByComponentName(working) : working.map(r => [r]);
30
- const processed = groups.map(group => processGroup(group, settings, windowSec));
31
- sortRows(processed, settings.sortBy);
32
- const truncated = settings.topN === -1 ? 0 : Math.max(0, processed.length - settings.topN);
33
- const rows = settings.topN === -1 ? processed : processed.slice(0, settings.topN);
34
- const totalRenders = renders.reduce((sum, r) => sum + r.renderCount, 0);
35
- const causeBreakdown = aggregateCauseBreakdown(renders);
36
-
37
- // Data-gap detection from the source renders (not after filtering)
38
- let rendersWithoutCause = 0;
39
- let historyPresent = false;
40
- for (const r of renders) {
41
- if (!r.lastRenderCause) rendersWithoutCause += r.renderCount;
42
- if (r.renderHistory && r.renderHistory.length > 0) historyPresent = true;
43
- }
44
- return {
45
- totalUniqueComponents: working.length,
46
- totalRenders,
47
- windowMs,
48
- causeBreakdown,
49
- rows,
50
- truncated,
51
- dataGaps: {
52
- historyOff: !historyPresent,
53
- // We can't directly observe whether the capture settings are on,
54
- // but we can detect their effects: capturedProps/capturedState in events.
55
- propsCaptureOff: !renders.some(r => r.renderHistory?.some(e => e.capturedProps !== undefined)),
56
- stateCaptureOff: !renders.some(r => r.renderHistory?.some(e => e.capturedState !== undefined)),
57
- rendersWithoutCause
58
- }
59
- };
60
- }
61
- function groupByComponentName(renders) {
62
- const map = new Map();
63
- for (const r of renders) {
64
- const key = r.componentName || r.displayName || r.viewType;
65
- const existing = map.get(key);
66
- if (existing) {
67
- existing.push(r);
68
- } else {
69
- map.set(key, [r]);
70
- }
71
- }
72
- return Array.from(map.values());
73
- }
74
- function processGroup(group, settings, windowSec) {
75
- const first = group[0];
76
- const name = first.componentName || first.displayName || first.viewType;
77
- let renders = 0;
78
- const causeMix = {};
79
- const hookCounter = new Map();
80
- const changedKeyCounter = new Map();
81
- const parentNameCounter = new Map();
82
- let unknownPropsCount = 0;
83
- let parentWithOwnChange = 0;
84
- let parentNoChange = 0;
85
- for (const r of group) {
86
- renders += r.renderCount;
87
- const cause = r.lastRenderCause;
88
- if (!cause) continue;
89
- const causeType = cause.type;
90
- causeMix[causeType] = (causeMix[causeType] ?? 0) + r.renderCount;
91
-
92
- // Track parent-cascade subtypes
93
- const hadHookChange = (cause.hookChanges?.length ?? 0) > 0;
94
- const hadKeyChange = (cause.changedKeys?.length ?? 0) > 0;
95
- if (causeType === "parent") {
96
- const ownChange = cause.componentCause === "state" || cause.componentCause === "props" || hadHookChange || hadKeyChange;
97
- if (ownChange) parentWithOwnChange += r.renderCount;else parentNoChange += r.renderCount;
98
- }
99
-
100
- // Track props with no key info (cause detector saw a change but couldn't identify which)
101
- if (causeType === "props" && !hadKeyChange) {
102
- unknownPropsCount += r.renderCount;
103
- }
104
-
105
- // Parent name observation
106
- if (cause.parentComponentName) {
107
- parentNameCounter.set(cause.parentComponentName, (parentNameCounter.get(cause.parentComponentName) ?? 0) + 1);
108
- }
109
-
110
- // Aggregate hook causes
111
- if (settings.aggregateCauses && cause.hookChanges) {
112
- for (const hc of cause.hookChanges) {
113
- const key = `${hc.type}[${hc.index}]`;
114
- const existing = hookCounter.get(key);
115
- if (existing) {
116
- existing.count += 1;
117
- } else {
118
- hookCounter.set(key, {
119
- count: 1,
120
- sample: formatHookSample(hc)
121
- });
122
- }
123
- }
124
- }
125
-
126
- // Aggregate changed keys (skip ref-only marks)
127
- if (cause.changedKeys) {
128
- for (const key of cause.changedKeys) {
129
- if (key.includes("(ref only)")) continue;
130
- changedKeyCounter.set(key, (changedKeyCounter.get(key) ?? 0) + 1);
131
- }
132
- }
133
- }
134
- const hookCauses = Array.from(hookCounter.entries()).sort((a, b) => b[1].count - a[1].count).slice(0, 3).map(([hook, info]) => ({
135
- hook,
136
- count: info.count,
137
- sample: info.sample
138
- }));
139
- const changedKeys = Array.from(changedKeyCounter.entries()).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k]) => k);
140
- let dominantCause = "unknown";
141
- let max = -1;
142
- for (const [cause, count] of Object.entries(causeMix)) {
143
- if ((count ?? 0) > max) {
144
- max = count ?? 0;
145
- dominantCause = cause;
146
- }
147
- }
148
-
149
- // Pick the most-observed parent name
150
- let parentName;
151
- let parentMax = 0;
152
- for (const [n, c] of parentNameCounter) {
153
- if (c > parentMax) {
154
- parentMax = c;
155
- parentName = n;
156
- }
157
- }
158
- const instanceCounts = group.map(r => r.renderCount).sort((a, b) => b - a);
159
- const rate = renders / windowSec;
160
- return {
161
- name,
162
- viewType: first.viewType,
163
- instances: group.length,
164
- renders,
165
- causeMix,
166
- dominantCause,
167
- hookCauses,
168
- changedKeys,
169
- unknownPropsCount,
170
- parentWithOwnChange,
171
- parentNoChange,
172
- parentName,
173
- instanceCounts,
174
- rate,
175
- source: group
176
- };
177
- }
178
- function aggregateCauseBreakdown(renders) {
179
- const out = {};
180
- for (const r of renders) {
181
- const t = r.lastRenderCause?.type ?? "unknown";
182
- out[t] = (out[t] ?? 0) + r.renderCount;
183
- }
184
- return out;
185
- }
186
- function sortRows(rows, sortBy) {
187
- switch (sortBy) {
188
- case "renderCount":
189
- rows.sort((a, b) => b.renders - a.renders);
190
- break;
191
- case "rate":
192
- rows.sort((a, b) => b.rate - a.rate);
193
- break;
194
- case "name":
195
- rows.sort((a, b) => a.name.localeCompare(b.name));
196
- break;
197
- }
198
- }
199
-
200
- // =============================================================================
201
- // Helpers
202
- // =============================================================================
203
-
204
- const VALUE_MAX_LEN = 32;
205
- function truncateValue(v) {
206
- if (v === null) return "null";
207
- if (v === undefined) return "undefined";
208
- if (typeof v === "function") return "[Fn]";
209
- if (typeof v === "object") {
210
- if (Array.isArray(v)) return `[Array(${v.length})]`;
211
- return "[Obj]";
212
- }
213
- const s = String(v);
214
- return s.length > VALUE_MAX_LEN ? s.slice(0, VALUE_MAX_LEN - 1) + "…" : s;
215
- }
216
- function formatHookSample(hc) {
217
- if (hc.previousValue === undefined && hc.currentValue === undefined) {
218
- return hc.description;
219
- }
220
- return `${truncateValue(hc.previousValue)} → ${truncateValue(hc.currentValue)}`;
221
- }
222
- function formatWindow(ms) {
223
- if (ms < 1000) return `${ms}ms`;
224
- return `${(ms / 1000).toFixed(1)}s`;
225
- }
226
- function formatRate(rate) {
227
- if (rate >= 100) return `${rate.toFixed(0)}/s`;
228
- if (rate >= 10) return `${rate.toFixed(1)}/s`;
229
- return `${rate.toFixed(2)}/s`;
230
- }
231
- function causeBreakdownLine(breakdown) {
232
- const entries = Object.entries(breakdown).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0));
233
- if (entries.length === 0) return "(no causes recorded)";
234
- return entries.map(([k, v]) => `${k} ${v}`).join(" · ");
235
- }
236
- function describeCause(row) {
237
- const parts = [];
238
-
239
- // Per-row cause mix when at least 2 causes contribute (≥10% each)
240
- const mixEntries = Object.entries(row.causeMix).filter(([, v]) => (v ?? 0) > 0).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0));
241
- const showMix = mixEntries.length >= 2 && (mixEntries[1]?.[1] ?? 0) >= row.renders * 0.1;
242
- if (row.dominantCause === "hooks" && row.hookCauses.length > 0) {
243
- const top = row.hookCauses[0];
244
- if (top.sample) {
245
- parts.push(`hooks ×${top.count} ${top.hook}: ${top.sample}`);
246
- } else {
247
- parts.push(`hooks ×${top.count} ${top.hook}`);
248
- }
249
- } else if (row.dominantCause === "props") {
250
- if (row.changedKeys.length > 0) {
251
- const keys = row.changedKeys.slice(0, 3).join(", ");
252
- parts.push(`props (${keys})`);
253
- } else {
254
- parts.push(`props (keys not captured)`);
255
- }
256
- if (row.unknownPropsCount > 0 && row.changedKeys.length > 0) {
257
- parts.push(`[${row.unknownPropsCount} unidentified]`);
258
- }
259
- } else if (row.dominantCause === "parent") {
260
- let label = "parent cascade";
261
- if (row.parentNoChange > 0 && row.parentWithOwnChange === 0) {
262
- label = "parent cascade, no own change";
263
- }
264
- if (row.parentName) label += ` ← ${row.parentName}`;
265
- parts.push(label);
266
- } else if (row.dominantCause === "mount") {
267
- parts.push("mount");
268
- } else if (row.dominantCause === "state") {
269
- parts.push("state");
270
- } else if (row.dominantCause === "context") {
271
- parts.push("context");
272
- } else {
273
- parts.push("unknown");
274
- }
275
- if (showMix) {
276
- const mixStr = mixEntries.map(([k, v]) => `${k}:${v}`).join(" + ");
277
- parts.push(`(${mixStr})`);
278
- }
279
- return parts.join(" ");
280
- }
281
- function describeInstances(row) {
282
- if (row.instances <= 1) return row.name;
283
- const counts = row.instanceCounts;
284
- const max = counts[0] ?? 0;
285
- const min = counts[counts.length - 1] ?? 0;
286
- const evenly = max - min <= 1;
287
- if (evenly) {
288
- return `${row.name} ×${row.instances} (${max} each)`;
289
- }
290
- // Uneven — show top instance vs others
291
- const others = counts.slice(1);
292
- const othersSum = others.reduce((s, n) => s + n, 0);
293
- const othersAvg = others.length > 0 ? othersSum / others.length : 0;
294
- return `${row.name} ×${row.instances} (top ${max}, others ~${Math.round(othersAvg)})`;
295
- }
296
-
297
- // =============================================================================
298
- // Format generators
299
- // =============================================================================
300
-
301
- const LLM_PREAMBLE = "React Native render report — review the data and identify why components are re-rendering.";
302
- function generateSummary(report, _settings) {
303
- const lines = [];
304
- lines.push(LLM_PREAMBLE);
305
- lines.push("");
306
- lines.push(`Window: ${formatWindow(report.windowMs)} · ${report.totalUniqueComponents} components · ${report.totalRenders} renders`);
307
- lines.push(`Cause mix: ${causeBreakdownLine(report.causeBreakdown)}`);
308
- lines.push("");
309
- lines.push("Top offenders (count · rate · component · cause):");
310
- lines.push("");
311
- const nameStrings = report.rows.map(describeInstances);
312
- const nameWidth = Math.min(36, Math.max(8, ...nameStrings.map(n => n.length)));
313
- const countWidth = Math.max(3, ...report.rows.map(r => `×${r.renders}`.length));
314
- const rateWidth = Math.max(5, ...report.rows.map(r => formatRate(r.rate).length));
315
- for (let i = 0; i < report.rows.length; i++) {
316
- const row = report.rows[i];
317
- const name = (nameStrings[i] ?? row.name).padEnd(nameWidth);
318
- const count = `×${row.renders}`.padStart(countWidth);
319
- const rate = formatRate(row.rate).padStart(rateWidth);
320
- const reason = describeCause(row);
321
- lines.push(` ${count} ${rate} ${name} ${reason}`);
322
- }
323
- if (report.truncated > 0) {
324
- lines.push("");
325
- lines.push(`... ${report.truncated} more (set topN=-1 to expand)`);
326
- }
327
-
328
- // Data-availability footer
329
- const footer = buildDataFooter(report);
330
- if (footer.length > 0) {
331
- lines.push("");
332
- lines.push("Data availability:");
333
- for (const line of footer) lines.push(` · ${line}`);
334
- }
335
- return lines.join("\n");
336
- }
337
- function buildDataFooter(report) {
338
- const out = [];
339
- if (report.dataGaps.rendersWithoutCause > 0) {
340
- out.push(`${report.dataGaps.rendersWithoutCause} renders had no cause info (enable trackRenderCauses for more detail)`);
341
- }
342
- if (report.dataGaps.propsCaptureOff) {
343
- out.push("prop snapshots not captured (enable capturePropsOnRender to see prop diffs for 'props (keys not captured)' rows)");
344
- }
345
- if (report.dataGaps.stateCaptureOff) {
346
- out.push("state snapshots not captured (enable captureStateOnRender)");
347
- }
348
- if (report.dataGaps.historyOff) {
349
- out.push("render history off (enable enableRenderHistory for per-render timeline)");
350
- }
351
- return out;
352
- }
353
- function generateMarkdownTable(report, _settings) {
354
- const lines = [];
355
- lines.push(`# React Render Report`);
356
- lines.push("");
357
- lines.push(`**Window:** ${formatWindow(report.windowMs)} · **Components:** ${report.totalUniqueComponents} · **Renders:** ${report.totalRenders}`);
358
- lines.push(`**Cause mix:** ${causeBreakdownLine(report.causeBreakdown)}`);
359
- lines.push("");
360
- lines.push("| Component | Renders | Rate | Cause |");
361
- lines.push("|---|---:|---:|---|");
362
- for (const row of report.rows) {
363
- const name = describeInstances(row);
364
- const reason = describeCause(row).replace(/\|/g, "\\|");
365
- lines.push(`| ${name} | ${row.renders} | ${formatRate(row.rate)} | ${reason} |`);
366
- }
367
- if (report.truncated > 0) {
368
- lines.push("");
369
- lines.push(`_... ${report.truncated} more rows_`);
370
- }
371
- const footer = buildDataFooter(report);
372
- if (footer.length > 0) {
373
- lines.push("");
374
- lines.push("**Data availability:**");
375
- for (const line of footer) lines.push(`- ${line}`);
376
- }
377
- return lines.join("\n");
378
- }
379
- function generateJson(_renders, report, settings) {
380
- const exportData = {
381
- exportedAt: new Date().toISOString(),
382
- summary: {
383
- windowMs: report.windowMs,
384
- totalComponents: report.totalUniqueComponents,
385
- totalRenders: report.totalRenders,
386
- causeBreakdown: report.causeBreakdown,
387
- truncated: report.truncated,
388
- dataGaps: report.dataGaps
389
- },
390
- components: report.rows.map(row => ({
391
- name: row.name,
392
- viewType: row.viewType,
393
- instances: row.instances,
394
- instanceCounts: row.instances > 1 ? row.instanceCounts : undefined,
395
- renders: row.renders,
396
- ratePerSec: Number(row.rate.toFixed(3)),
397
- dominantCause: row.dominantCause,
398
- causeMix: row.causeMix,
399
- hookCauses: row.hookCauses,
400
- changedKeys: row.changedKeys,
401
- unknownPropsCount: row.unknownPropsCount || undefined,
402
- parentName: row.parentName,
403
- parentWithOwnChange: row.parentWithOwnChange || undefined,
404
- parentNoChange: row.parentNoChange || undefined,
405
- ...(settings.includeHistory ? {
406
- history: extractHistory(row.source)
407
- } : {})
408
- }))
409
- };
410
- return settings.includeHistory ? JSON.stringify(exportData, null, 2) : JSON.stringify(exportData);
411
- }
412
- function extractHistory(source) {
413
- return source.map(r => ({
414
- name: r.componentName || r.displayName,
415
- nativeTag: r.nativeTag,
416
- renderCount: r.renderCount,
417
- history: r.renderHistory?.map(e => ({
418
- n: e.renderNumber,
419
- t: e.timestamp,
420
- cause: {
421
- type: e.cause.type,
422
- componentCause: e.cause.componentCause,
423
- changedKeys: e.cause.changedKeys,
424
- parentComponentName: e.cause.parentComponentName,
425
- hookChanges: e.cause.hookChanges?.map(h => ({
426
- type: h.type,
427
- index: h.index,
428
- desc: h.description
429
- }))
430
- }
431
- }))
432
- }));
433
- }
434
-
435
- // =============================================================================
436
- // Public API
437
- // =============================================================================
438
-
439
- export function generateExport(renders, settings) {
440
- const report = buildReport(renders, settings);
441
- switch (settings.format) {
442
- case "summary":
443
- return generateSummary(report, settings);
444
- case "markdown-table":
445
- return generateMarkdownTable(report, settings);
446
- case "json":
447
- return generateJson(renders, report, settings);
448
- default:
449
- return generateSummary(report, settings);
450
- }
451
- }
452
- export function estimateExportSize(renders, settings) {
453
- return generateExport(renders, settings).length;
454
- }
455
- export function getExportSummary(renders) {
456
- return {
457
- totalComponents: renders.length,
458
- totalRenders: renders.reduce((sum, r) => sum + r.renderCount, 0)
459
- };
460
- }
461
-
462
- /**
463
- * Export a single component's detail (used by RenderDetailView copy button).
464
- * Always uses the summary format since detail copies are inherently small.
465
- */
466
- export function generateSingleComponentExport(render) {
467
- const settings = {
468
- format: "summary",
469
- topN: -1,
470
- minRenders: 1,
471
- filterCauses: [],
472
- groupByName: false,
473
- aggregateCauses: true,
474
- sortBy: "renderCount",
475
- includeHistory: false
476
- };
477
- return generateExport([render], settings);
478
- }
1
+ "use strict";function P(e,n){let t=e.filter(r=>r.renderCount>=n.minRenders);if(n.filterCauses.length>0){const r=new Set(n.filterCauses);t=t.filter(h=>r.has(h.lastRenderCause?.type??"unknown"))}const a=e.flatMap(r=>[r.firstRenderTime,r.lastRenderTime]).filter(r=>Number.isFinite(r));const s=a.length>0?Math.max(...a)-Math.min(...a):0;const c=Math.max(.001,s/1e3);const p=n.groupByName?G(t):t.map(r=>[r]);const d=p.map(r=>F(r,n,c));L(d,n.sortBy);const i=n.topN===-1?0:Math.max(0,d.length-n.topN);const f=n.topN===-1?d:d.slice(0,n.topN);const C=e.reduce((r,h)=>r+h.renderCount,0);const y=_(e);let l=0;let g=false;for(const r of e){if(!r.lastRenderCause)l+=r.renderCount;if(r.renderHistory&&r.renderHistory.length>0)g=true}return{totalUniqueComponents:t.length,totalRenders:C,windowMs:s,causeBreakdown:y,rows:f,truncated:i,dataGaps:{historyOff:!g,propsCaptureOff:!e.some(r=>r.renderHistory?.some(h=>h.capturedProps!==void 0)),stateCaptureOff:!e.some(r=>r.renderHistory?.some(h=>h.capturedState!==void 0)),rendersWithoutCause:l}}}function G(e){const n=new Map;for(const t of e){const a=t.componentName||t.displayName||t.viewType;const s=n.get(a);if(s){s.push(t)}else{n.set(a,[t])}}return Array.from(n.values())}function F(e,n,t){const a=e[0];const s=a.componentName||a.displayName||a.viewType;let c=0;const p={};const d=new Map;const i=new Map;const f=new Map;let C=0;let y=0;let l=0;for(const u of e){c+=u.renderCount;const o=u.lastRenderCause;if(!o)continue;const $=o.type;p[$]=(p[$]??0)+u.renderCount;const E=(o.hookChanges?.length??0)>0;const M=(o.changedKeys?.length??0)>0;if($==="parent"){const m=o.componentCause==="state"||o.componentCause==="props"||E||M;if(m)y+=u.renderCount;else l+=u.renderCount}if($==="props"&&!M){C+=u.renderCount}if(o.parentComponentName){f.set(o.parentComponentName,(f.get(o.parentComponentName)??0)+1)}if(n.aggregateCauses&&o.hookChanges){for(const m of o.hookChanges){const R=`${m.type}[${m.index}]`;const b=d.get(R);if(b){b.count+=1}else{d.set(R,{count:1,sample:V(m)})}}}if(o.changedKeys){for(const m of o.changedKeys){if(m.includes("(ref only)"))continue;i.set(m,(i.get(m)??0)+1)}}}const g=Array.from(d.entries()).sort((u,o)=>o[1].count-u[1].count).slice(0,3).map(([u,o])=>({hook:u,count:o.count,sample:o.sample}));const r=Array.from(i.entries()).sort((u,o)=>o[1]-u[1]).slice(0,5).map(([u])=>u);let h="unknown";let x=-1;for(const[u,o]of Object.entries(p)){if((o??0)>x){x=o??0;h=u}}let N;let w=0;for(const[u,o]of f){if(o>w){w=o;N=u}}const v=e.map(u=>u.renderCount).sort((u,o)=>o-u);const A=c/t;return{name:s,viewType:a.viewType,instances:e.length,renders:c,causeMix:p,dominantCause:h,hookCauses:g,changedKeys:r,unknownPropsCount:C,parentWithOwnChange:y,parentNoChange:l,parentName:N,instanceCounts:v,rate:A,source:e}}function _(e){const n={};for(const t of e){const a=t.lastRenderCause?.type??"unknown";n[a]=(n[a]??0)+t.renderCount}return n}function L(e,n){switch(n){case"renderCount":e.sort((t,a)=>a.renders-t.renders);break;case"rate":e.sort((t,a)=>a.rate-t.rate);break;case"name":e.sort((t,a)=>t.name.localeCompare(a.name));break}}const S=32;function O(e){if(e===null)return"null";if(e===void 0)return"undefined";if(typeof e==="function")return"[Fn]";if(typeof e==="object"){if(Array.isArray(e))return`[Array(${e.length})]`;return"[Obj]"}const n=String(e);return n.length>S?n.slice(0,S-1)+"\u2026":n}function V(e){if(e.previousValue===void 0&&e.currentValue===void 0){return e.description}return`${O(e.previousValue)} \u2192 ${O(e.currentValue)}`}function T(e){if(e<1e3)return`${e}ms`;return`${(e/1e3).toFixed(1)}s`}function k(e){if(e>=100)return`${e.toFixed(0)}/s`;if(e>=10)return`${e.toFixed(1)}/s`;return`${e.toFixed(2)}/s`}function B(e){const n=Object.entries(e).sort((t,a)=>(a[1]??0)-(t[1]??0));if(n.length===0)return"(no causes recorded)";return n.map(([t,a])=>`${t} ${a}`).join(" \xB7 ")}function K(e){const n=[];const t=Object.entries(e.causeMix).filter(([,s])=>(s??0)>0).sort((s,c)=>(c[1]??0)-(s[1]??0));const a=t.length>=2&&(t[1]?.[1]??0)>=e.renders*.1;if(e.dominantCause==="hooks"&&e.hookCauses.length>0){const s=e.hookCauses[0];if(s.sample){n.push(`hooks \xD7${s.count} ${s.hook}: ${s.sample}`)}else{n.push(`hooks \xD7${s.count} ${s.hook}`)}}else if(e.dominantCause==="props"){if(e.changedKeys.length>0){const s=e.changedKeys.slice(0,3).join(", ");n.push(`props (${s})`)}else{n.push(`props (keys not captured)`)}if(e.unknownPropsCount>0&&e.changedKeys.length>0){n.push(`[${e.unknownPropsCount} unidentified]`)}}else if(e.dominantCause==="parent"){let s="parent cascade";if(e.parentNoChange>0&&e.parentWithOwnChange===0){s="parent cascade, no own change"}if(e.parentName)s+=` \u2190 ${e.parentName}`;n.push(s)}else if(e.dominantCause==="mount"){n.push("mount")}else if(e.dominantCause==="state"){n.push("state")}else if(e.dominantCause==="context"){n.push("context")}else{n.push("unknown")}if(a){const s=t.map(([c,p])=>`${c}:${p}`).join(" + ");n.push(`(${s})`)}return n.join(" ")}function W(e){if(e.instances<=1)return e.name;const n=e.instanceCounts;const t=n[0]??0;const a=n[n.length-1]??0;const s=t-a<=1;if(s){return`${e.name} \xD7${e.instances} (${t} each)`}const c=n.slice(1);const p=c.reduce((i,f)=>i+f,0);const d=c.length>0?p/c.length:0;return`${e.name} \xD7${e.instances} (top ${t}, others ~${Math.round(d)})`}const D="React Native render report \u2014 review the data and identify why components are re-rendering.";function j(e,n){const t=[];t.push(D);t.push("");t.push(`Window: ${T(e.windowMs)} \xB7 ${e.totalUniqueComponents} components \xB7 ${e.totalRenders} renders`);t.push(`Cause mix: ${B(e.causeBreakdown)}`);t.push("");t.push("Top offenders (count \xB7 rate \xB7 component \xB7 cause):");t.push("");const a=e.rows.map(W);const s=Math.min(36,Math.max(8,...a.map(i=>i.length)));const c=Math.max(3,...e.rows.map(i=>`\xD7${i.renders}`.length));const p=Math.max(5,...e.rows.map(i=>k(i.rate).length));for(let i=0;i<e.rows.length;i++){const f=e.rows[i];const C=(a[i]??f.name).padEnd(s);const y=`\xD7${f.renders}`.padStart(c);const l=k(f.rate).padStart(p);const g=K(f);t.push(` ${y} ${l} ${C} ${g}`)}if(e.truncated>0){t.push("");t.push(`... ${e.truncated} more (set topN=-1 to expand)`)}const d=H(e);if(d.length>0){t.push("");t.push("Data availability:");for(const i of d)t.push(` \xB7 ${i}`)}return t.join("\n")}function H(e){const n=[];if(e.dataGaps.rendersWithoutCause>0){n.push(`${e.dataGaps.rendersWithoutCause} renders had no cause info (enable trackRenderCauses for more detail)`)}if(e.dataGaps.propsCaptureOff){n.push("prop snapshots not captured (enable capturePropsOnRender to see prop diffs for 'props (keys not captured)' rows)")}if(e.dataGaps.stateCaptureOff){n.push("state snapshots not captured (enable captureStateOnRender)")}if(e.dataGaps.historyOff){n.push("render history off (enable enableRenderHistory for per-render timeline)")}return n}function U(e,n){const t=[];t.push(`# React Render Report`);t.push("");t.push(`**Window:** ${T(e.windowMs)} \xB7 **Components:** ${e.totalUniqueComponents} \xB7 **Renders:** ${e.totalRenders}`);t.push(`**Cause mix:** ${B(e.causeBreakdown)}`);t.push("");t.push("| Component | Renders | Rate | Cause |");t.push("|---|---:|---:|---|");for(const s of e.rows){const c=W(s);const p=K(s).replace(/\|/g,"\\|");t.push(`| ${c} | ${s.renders} | ${k(s.rate)} | ${p} |`)}if(e.truncated>0){t.push("");t.push(`_... ${e.truncated} more rows_`)}const a=H(e);if(a.length>0){t.push("");t.push("**Data availability:**");for(const s of a)t.push(`- ${s}`)}return t.join("\n")}function q(e,n,t){const a={exportedAt:new Date().toISOString(),summary:{windowMs:n.windowMs,totalComponents:n.totalUniqueComponents,totalRenders:n.totalRenders,causeBreakdown:n.causeBreakdown,truncated:n.truncated,dataGaps:n.dataGaps},components:n.rows.map(s=>({name:s.name,viewType:s.viewType,instances:s.instances,instanceCounts:s.instances>1?s.instanceCounts:void 0,renders:s.renders,ratePerSec:Number(s.rate.toFixed(3)),dominantCause:s.dominantCause,causeMix:s.causeMix,hookCauses:s.hookCauses,changedKeys:s.changedKeys,unknownPropsCount:s.unknownPropsCount||void 0,parentName:s.parentName,parentWithOwnChange:s.parentWithOwnChange||void 0,parentNoChange:s.parentNoChange||void 0,...t.includeHistory?{history:J(s.source)}:{}}))};return t.includeHistory?JSON.stringify(a,null,2):JSON.stringify(a)}function J(e){return e.map(n=>({name:n.componentName||n.displayName,nativeTag:n.nativeTag,renderCount:n.renderCount,history:n.renderHistory?.map(t=>({n:t.renderNumber,t:t.timestamp,cause:{type:t.cause.type,componentCause:t.cause.componentCause,changedKeys:t.cause.changedKeys,parentComponentName:t.cause.parentComponentName,hookChanges:t.cause.hookChanges?.map(a=>({type:a.type,index:a.index,desc:a.description}))}}))}))}export function generateExport(e,n){const t=P(e,n);switch(n.format){case"summary":return j(t,n);case"markdown-table":return U(t,n);case"json":return q(e,t,n);default:return j(t,n)}}export function estimateExportSize(e,n){return generateExport(e,n).length}export function getExportSummary(e){return{totalComponents:e.length,totalRenders:e.reduce((n,t)=>n+t.renderCount,0)}}export function generateSingleComponentExport(e){const n={format:"summary",topN:-1,minRenders:1,filterCauses:[],groupByName:false,aggregateCauses:true,sortBy:"renderCount",includeHistory:false};return generateExport([e],n)}