@buoy-gg/highlight-updates 7.0.27 → 7.0.29

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 (72) hide show
  1. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +102 -0
  2. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +5 -1
  3. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +7 -1
  4. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +6 -0
  5. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +26 -3
  6. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +7 -1
  7. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +40 -1
  8. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +29 -6
  9. package/lib/commonjs/highlight-updates/components/RenderListItem.js +15 -0
  10. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +11 -0
  11. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +3 -0
  12. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +6 -1
  13. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +65 -3
  14. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +15 -3
  15. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +6 -1
  16. package/lib/commonjs/highlight-updates/utils/namedComposite.js +41 -0
  17. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +103 -0
  18. package/lib/commonjs/highlight-updates/utils/screenElements.js +50 -25
  19. package/lib/commonjs/highlight-updates/utils/tapMatch.js +58 -0
  20. package/lib/commonjs/highlight-updates/utils/testIDs.js +68 -0
  21. package/lib/commonjs/index.js +34 -0
  22. package/lib/module/highlight-updates/components/HighlightFilterView.js +102 -0
  23. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +5 -1
  24. package/lib/module/highlight-updates/components/IdentifierBadge.js +7 -1
  25. package/lib/module/highlight-updates/components/IsolatedRenderList.js +6 -0
  26. package/lib/module/highlight-updates/components/ModalHeaderContent.js +26 -3
  27. package/lib/module/highlight-updates/components/RenderCauseBadge.js +7 -1
  28. package/lib/module/highlight-updates/components/RenderDetailView.js +40 -1
  29. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +29 -6
  30. package/lib/module/highlight-updates/components/RenderListItem.js +15 -0
  31. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +11 -0
  32. package/lib/module/highlight-updates/components/StatsDisplay.js +3 -0
  33. package/lib/module/highlight-updates/utils/CommitProfiler.js +5 -1
  34. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +65 -3
  35. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +15 -3
  36. package/lib/module/highlight-updates/utils/RenderTracker.js +6 -1
  37. package/lib/module/highlight-updates/utils/namedComposite.js +36 -0
  38. package/lib/module/highlight-updates/utils/ownStateChanged.js +98 -0
  39. package/lib/module/highlight-updates/utils/screenElements.js +50 -25
  40. package/lib/module/highlight-updates/utils/tapMatch.js +52 -0
  41. package/lib/module/highlight-updates/utils/testIDs.js +60 -0
  42. package/lib/module/index.js +5 -1
  43. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -1
  44. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -1
  45. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts +7 -1
  46. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -1
  47. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -1
  48. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -1
  49. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts +8 -1
  50. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -1
  51. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -1
  52. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -1
  53. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -1
  54. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +1 -1
  55. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -1
  56. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -1
  57. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -1
  58. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -1
  59. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts +6 -0
  60. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -1
  61. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts +17 -0
  62. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +1 -0
  63. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts +66 -0
  64. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +1 -0
  65. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +1 -1
  66. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts +42 -0
  67. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +1 -0
  68. package/lib/typescript/highlight-updates/utils/testIDs.d.ts +44 -0
  69. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +1 -0
  70. package/lib/typescript/index.d.ts +1 -0
  71. package/lib/typescript/index.d.ts.map +1 -1
  72. package/package.json +7 -7
@@ -89,21 +89,25 @@ export function RenderDetailView({
89
89
  children: [/*#__PURE__*/_jsxs(View, {
90
90
  style: styles.headerLeft,
91
91
  children: [/*#__PURE__*/_jsx(Text, {
92
+ testID: "buoy-highlight-detail-name",
92
93
  style: styles.componentName,
93
94
  numberOfLines: 1,
94
95
  children: componentName
95
96
  }), /*#__PURE__*/_jsx(View, {
96
97
  style: styles.nativeTypeBadge,
97
98
  children: /*#__PURE__*/_jsx(Text, {
99
+ testID: "buoy-highlight-detail-type",
98
100
  style: styles.nativeTypeText,
99
101
  children: nativeType
100
102
  })
101
103
  })]
102
104
  }), /*#__PURE__*/_jsx(CopyButton, {
103
105
  value: copyData,
104
- size: 14
106
+ size: 14,
107
+ testID: "buoy-highlight-detail-copy"
105
108
  })]
106
109
  }), /*#__PURE__*/_jsxs(ScrollView, {
110
+ testID: "buoy-highlight-detail-scroll",
107
111
  style: styles.scrollView,
108
112
  contentContainerStyle: [styles.scrollContent, showHistoryFooter && {
109
113
  paddingBottom: 100
@@ -116,11 +120,13 @@ export function RenderDetailView({
116
120
  render: render,
117
121
  rendersPerSec: rendersPerSec
118
122
  }), hiddenEventCount > 0 && /*#__PURE__*/_jsxs(View, {
123
+ testID: "buoy-highlight-detail-locked",
119
124
  style: styles.lockedBanner,
120
125
  children: [/*#__PURE__*/_jsx(Lock, {
121
126
  size: 14,
122
127
  color: night.color.warning
123
128
  }), /*#__PURE__*/_jsxs(Text, {
129
+ testID: "buoy-highlight-detail-locked-text",
124
130
  style: styles.lockedBannerText,
125
131
  children: [hiddenEventCount, " ", hiddenEventCount === 1 ? 'event' : 'events', " hidden"]
126
132
  }), /*#__PURE__*/_jsx(Text, {
@@ -165,10 +171,12 @@ function AnswerCard({
165
171
  return "Changed:";
166
172
  };
167
173
  return /*#__PURE__*/_jsxs(View, {
174
+ testID: "buoy-highlight-detail-cause",
168
175
  style: styles.answerCard,
169
176
  children: [/*#__PURE__*/_jsxs(View, {
170
177
  style: styles.causeBadgeRow,
171
178
  children: [/*#__PURE__*/_jsx(Text, {
179
+ testID: "buoy-highlight-detail-cause-label",
172
180
  style: styles.causeLabel,
173
181
  children: getLabel()
174
182
  }), /*#__PURE__*/_jsx(View, {
@@ -176,6 +184,7 @@ function AnswerCard({
176
184
  backgroundColor: config.color + "20"
177
185
  }],
178
186
  children: /*#__PURE__*/_jsx(Text, {
187
+ testID: "buoy-highlight-detail-cause-badge",
179
188
  style: [styles.causeBadgeLargeText, {
180
189
  color: config.color
181
190
  }],
@@ -207,6 +216,7 @@ function AnswerCard({
207
216
  children: cause.changedKeys.filter(k => !k.includes("(ref only)")).slice(0, 5).map((key, index) => /*#__PURE__*/_jsx(View, {
208
217
  style: styles.propChangeChip,
209
218
  children: /*#__PURE__*/_jsx(Text, {
219
+ testID: `buoy-highlight-detail-changed-${index}`,
210
220
  style: styles.propChangeText,
211
221
  children: key
212
222
  })
@@ -228,6 +238,7 @@ function DetailsSection({
228
238
  return /*#__PURE__*/_jsxs(View, {
229
239
  style: styles.detailsSection,
230
240
  children: [hasIdentifiers && /*#__PURE__*/_jsxs(View, {
241
+ testID: "buoy-highlight-detail-identifiers",
231
242
  style: styles.detailsCard,
232
243
  children: [/*#__PURE__*/_jsx(Text, {
233
244
  style: styles.detailsCardTitle,
@@ -243,10 +254,13 @@ function DetailsSection({
243
254
  value: render.accessibilityLabel
244
255
  })]
245
256
  }), /*#__PURE__*/_jsxs(View, {
257
+ testID: "buoy-highlight-detail-stats",
246
258
  style: styles.statsRow,
247
259
  children: [/*#__PURE__*/_jsxs(View, {
260
+ testID: "buoy-highlight-detail-stat-renders",
248
261
  style: styles.statItem,
249
262
  children: [/*#__PURE__*/_jsx(Text, {
263
+ testID: "buoy-highlight-detail-stat-renders-value",
250
264
  style: [styles.statValue, {
251
265
  color: render.color
252
266
  }],
@@ -258,8 +272,10 @@ function DetailsSection({
258
272
  }), /*#__PURE__*/_jsx(View, {
259
273
  style: styles.statDivider
260
274
  }), /*#__PURE__*/_jsxs(View, {
275
+ testID: "buoy-highlight-detail-stat-rate",
261
276
  style: styles.statItem,
262
277
  children: [/*#__PURE__*/_jsx(Text, {
278
+ testID: "buoy-highlight-detail-stat-rate-value",
263
279
  style: styles.statValue,
264
280
  children: rendersPerSec
265
281
  }), /*#__PURE__*/_jsx(Text, {
@@ -269,8 +285,10 @@ function DetailsSection({
269
285
  }), /*#__PURE__*/_jsx(View, {
270
286
  style: styles.statDivider
271
287
  }), /*#__PURE__*/_jsxs(View, {
288
+ testID: "buoy-highlight-detail-stat-tag",
272
289
  style: styles.statItem,
273
290
  children: [/*#__PURE__*/_jsx(Text, {
291
+ testID: "buoy-highlight-detail-stat-tag-value",
274
292
  style: styles.statValue,
275
293
  children: render.nativeTag
276
294
  }), /*#__PURE__*/_jsx(Text, {
@@ -279,6 +297,7 @@ function DetailsSection({
279
297
  })]
280
298
  })]
281
299
  }), hasMeasurements && /*#__PURE__*/_jsxs(View, {
300
+ testID: "buoy-highlight-detail-measure",
282
301
  style: styles.measurementsRow,
283
302
  children: [/*#__PURE__*/_jsxs(View, {
284
303
  style: styles.measurementItem,
@@ -286,6 +305,7 @@ function DetailsSection({
286
305
  style: styles.measurementLabel,
287
306
  children: "x"
288
307
  }), /*#__PURE__*/_jsx(Text, {
308
+ testID: "buoy-highlight-detail-measure-x",
289
309
  style: styles.measurementValue,
290
310
  children: Math.round(render.measurements.x)
291
311
  })]
@@ -295,6 +315,7 @@ function DetailsSection({
295
315
  style: styles.measurementLabel,
296
316
  children: "y"
297
317
  }), /*#__PURE__*/_jsx(Text, {
318
+ testID: "buoy-highlight-detail-measure-y",
298
319
  style: styles.measurementValue,
299
320
  children: Math.round(render.measurements.y)
300
321
  })]
@@ -304,6 +325,7 @@ function DetailsSection({
304
325
  style: styles.measurementLabel,
305
326
  children: "w"
306
327
  }), /*#__PURE__*/_jsx(Text, {
328
+ testID: "buoy-highlight-detail-measure-w",
307
329
  style: styles.measurementValue,
308
330
  children: Math.round(render.measurements.width)
309
331
  })]
@@ -313,6 +335,7 @@ function DetailsSection({
313
335
  style: styles.measurementLabel,
314
336
  children: "h"
315
337
  }), /*#__PURE__*/_jsx(Text, {
338
+ testID: "buoy-highlight-detail-measure-h",
316
339
  style: styles.measurementValue,
317
340
  children: Math.round(render.measurements.height)
318
341
  })]
@@ -328,12 +351,15 @@ function DetailRow({
328
351
  label,
329
352
  value
330
353
  }) {
354
+ const handle = `buoy-highlight-detail-id-${label.toLowerCase()}`;
331
355
  return /*#__PURE__*/_jsxs(View, {
356
+ testID: handle,
332
357
  style: styles.detailRow,
333
358
  children: [/*#__PURE__*/_jsx(Text, {
334
359
  style: styles.detailLabel,
335
360
  children: label
336
361
  }), /*#__PURE__*/_jsx(Text, {
362
+ testID: `${handle}-value`,
337
363
  style: styles.detailValue,
338
364
  numberOfLines: 1,
339
365
  children: value
@@ -415,6 +441,7 @@ function QuickActionsSection({
415
441
  }
416
442
  }, [selectedOption, onAddFilter]);
417
443
  return /*#__PURE__*/_jsxs(View, {
444
+ testID: "buoy-highlight-quick",
418
445
  style: styles.quickActionsSection,
419
446
  children: [/*#__PURE__*/_jsx(Text, {
420
447
  style: styles.quickActionsTitle,
@@ -422,6 +449,7 @@ function QuickActionsSection({
422
449
  }), /*#__PURE__*/_jsx(View, {
423
450
  style: styles.filterOptionsList,
424
451
  children: filterOptions.map((option, index) => /*#__PURE__*/_jsx(FilterOptionCard, {
452
+ testID: `buoy-highlight-quick-${option.type.toLowerCase()}`,
425
453
  label: option.label,
426
454
  value: option.value,
427
455
  isSelected: selectedIndex === index,
@@ -430,6 +458,9 @@ function QuickActionsSection({
430
458
  }), /*#__PURE__*/_jsxs(View, {
431
459
  style: styles.filterActionButtons,
432
460
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
461
+ testID: "buoy-highlight-quick-include",
462
+ accessibilityRole: "button",
463
+ accessibilityLabel: "Only show this",
433
464
  style: [styles.filterActionButton, styles.filterActionButtonInclude, !selectedOption && styles.filterActionButtonDisabled],
434
465
  onPress: handleInclude,
435
466
  disabled: !selectedOption,
@@ -444,6 +475,9 @@ function QuickActionsSection({
444
475
  children: "Only Show This"
445
476
  })]
446
477
  }), /*#__PURE__*/_jsxs(TouchableOpacity, {
478
+ testID: "buoy-highlight-quick-exclude",
479
+ accessibilityRole: "button",
480
+ accessibilityLabel: "Hide this",
447
481
  style: [styles.filterActionButton, styles.filterActionButtonExclude, !selectedOption && styles.filterActionButtonDisabled],
448
482
  onPress: handleExclude,
449
483
  disabled: !selectedOption,
@@ -466,12 +500,16 @@ function QuickActionsSection({
466
500
  * FilterOptionCard - A selectable filter option card
467
501
  */
468
502
  function FilterOptionCard({
503
+ testID,
469
504
  label,
470
505
  value,
471
506
  isSelected,
472
507
  onSelect
473
508
  }) {
474
509
  return /*#__PURE__*/_jsxs(TouchableOpacity, {
510
+ testID: testID,
511
+ accessibilityRole: "button",
512
+ accessibilityLabel: `${label}, ${value}`,
475
513
  style: [styles.filterOptionCard, isSelected && styles.filterOptionCardSelected],
476
514
  onPress: onSelect,
477
515
  activeOpacity: 0.7,
@@ -479,6 +517,7 @@ function FilterOptionCard({
479
517
  style: [styles.filterOptionLabel, isSelected && styles.filterOptionLabelSelected],
480
518
  children: label
481
519
  }), /*#__PURE__*/_jsx(Text, {
520
+ testID: testID ? `${testID}-value` : undefined,
482
521
  style: [styles.filterOptionValue, isSelected && styles.filterOptionValueSelected],
483
522
  numberOfLines: 1,
484
523
  children: value
@@ -15,15 +15,22 @@ import { TreeDiffViewer } from "@buoy-gg/shared-ui/dataViewer";
15
15
  import { EnhancedCauseDisplay, CAUSE_CONFIG } from "./RenderCauseBadge";
16
16
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
17
17
  /**
18
- * Format timestamp with milliseconds
18
+ * `HH:MM:SS.mmm` — the same shape Redux, Zustand and Jotai print for the same
19
+ * job, and for the same reason: the milliseconds are the point.
20
+ *
21
+ * Built by hand rather than from `toLocaleTimeString`, which appends the day
22
+ * period on a 12-hour locale — so concatenating the milliseconds to the end of
23
+ * it produced "8:21:44 AM.351", with the fraction attached to the wrong part
24
+ * of the time. It also made the string depend on the hour of day, which is a
25
+ * fine way to have a test that only passes after 10am.
19
26
  */
20
27
  function formatTimeWithMs(timestamp) {
21
28
  const date = new Date(timestamp);
22
- return date.toLocaleTimeString([], {
23
- hour: "2-digit",
24
- minute: "2-digit",
25
- second: "2-digit"
26
- }) + `.${date.getMilliseconds().toString().padStart(3, "0")}`;
29
+ const h = date.getHours().toString().padStart(2, "0");
30
+ const m = date.getMinutes().toString().padStart(2, "0");
31
+ const s = date.getSeconds().toString().padStart(2, "0");
32
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
33
+ return `${h}:${m}:${s}.${ms}`;
27
34
  }
28
35
 
29
36
  /**
@@ -83,14 +90,17 @@ export function RenderHistoryViewer({
83
90
  // If no history, show empty state
84
91
  if (totalEvents === 0) {
85
92
  return /*#__PURE__*/_jsxs(View, {
93
+ testID: "buoy-highlight-history-empty",
86
94
  style: styles.emptyContainer,
87
95
  children: [/*#__PURE__*/_jsx(Clock, {
88
96
  size: 32,
89
97
  color: night.color.textTertiary
90
98
  }), /*#__PURE__*/_jsx(Text, {
99
+ testID: "buoy-highlight-history-empty-title",
91
100
  style: styles.emptyTitle,
92
101
  children: "No Render History"
93
102
  }), /*#__PURE__*/_jsx(Text, {
103
+ testID: "buoy-highlight-history-empty-text",
94
104
  style: styles.emptyText,
95
105
  children: "Enable render history tracking in settings to see render events here."
96
106
  })]
@@ -102,11 +112,13 @@ export function RenderHistoryViewer({
102
112
  paddingBottom: !disableInternalFooter && totalEvents > 1 ? 80 : 0
103
113
  }],
104
114
  children: [lockedEventCount > 0 && /*#__PURE__*/_jsxs(View, {
115
+ testID: "buoy-highlight-history-locked",
105
116
  style: styles.lockedBanner,
106
117
  children: [/*#__PURE__*/_jsx(Lock, {
107
118
  size: 14,
108
119
  color: night.color.warning
109
120
  }), /*#__PURE__*/_jsxs(Text, {
121
+ testID: "buoy-highlight-history-locked-text",
110
122
  style: styles.lockedBannerText,
111
123
  children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'render' : 'renders', " locked"]
112
124
  }), /*#__PURE__*/_jsx(Text, {
@@ -116,6 +128,9 @@ export function RenderHistoryViewer({
116
128
  }), /*#__PURE__*/_jsxs(View, {
117
129
  style: styles.viewToggleContainer,
118
130
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
131
+ testID: "buoy-highlight-history-mode-current",
132
+ accessibilityRole: "button",
133
+ accessibilityLabel: "Current state",
119
134
  style: [styles.viewToggleCard, activeView === "current" && styles.viewToggleCardActive],
120
135
  onPress: () => setActiveView("current"),
121
136
  activeOpacity: 0.8,
@@ -135,6 +150,8 @@ export function RenderHistoryViewer({
135
150
  children: ["View render #", currentEvent?.renderNumber ?? 0, " details"]
136
151
  })]
137
152
  }), /*#__PURE__*/_jsxs(View, {
153
+ testID: "buoy-highlight-history-mode-diff",
154
+ accessibilityLabel: "Diff view, coming soon",
138
155
  style: [styles.viewToggleCard, styles.viewToggleCardDisabled],
139
156
  children: [/*#__PURE__*/_jsxs(View, {
140
157
  style: styles.viewToggleContent,
@@ -159,6 +176,7 @@ export function RenderHistoryViewer({
159
176
  })]
160
177
  })]
161
178
  }), /*#__PURE__*/_jsx(ScrollView, {
179
+ testID: "buoy-highlight-history-scroll",
162
180
  style: styles.contentScroll,
163
181
  contentContainerStyle: styles.contentContainer,
164
182
  showsVerticalScrollIndicator: false,
@@ -191,6 +209,7 @@ function CurrentStateView({
191
209
  }) {
192
210
  if (!event) {
193
211
  return /*#__PURE__*/_jsx(View, {
212
+ testID: "buoy-highlight-history-no-event",
194
213
  style: styles.noEventContainer,
195
214
  children: /*#__PURE__*/_jsx(Text, {
196
215
  style: styles.noEventText,
@@ -206,9 +225,11 @@ function CurrentStateView({
206
225
  children: [/*#__PURE__*/_jsxs(View, {
207
226
  style: styles.eventHeaderLeft,
208
227
  children: [/*#__PURE__*/_jsxs(Text, {
228
+ testID: "buoy-highlight-history-event-title",
209
229
  style: styles.eventTitle,
210
230
  children: ["Render #", event.renderNumber]
211
231
  }), /*#__PURE__*/_jsx(Text, {
232
+ testID: "buoy-highlight-history-event-time",
212
233
  style: styles.eventTime,
213
234
  children: formatTimeWithMs(event.timestamp)
214
235
  })]
@@ -221,6 +242,7 @@ function CurrentStateView({
221
242
  backgroundColor: render.color
222
243
  }]
223
244
  }), /*#__PURE__*/_jsx(Text, {
245
+ testID: "buoy-highlight-history-event-cause",
224
246
  style: [styles.eventBadgeText, {
225
247
  color: render.color
226
248
  }],
@@ -261,6 +283,7 @@ function CurrentStateView({
261
283
  showsHorizontalScrollIndicator: false,
262
284
  style: styles.propsScrollView,
263
285
  children: /*#__PURE__*/_jsx(Text, {
286
+ testID: "buoy-highlight-history-props",
264
287
  style: styles.propsJson,
265
288
  children: JSON.stringify(event.capturedProps, null, 2)
266
289
  })
@@ -12,6 +12,7 @@ import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
12
12
  import { ChevronRight, night, useRelativeTime } from "@buoy-gg/shared-ui";
13
13
  import { IdentifierBadge } from "./IdentifierBadge";
14
14
  import { RenderCauseBadge } from "./RenderCauseBadge";
15
+ import { renderRowTestID } from "../utils/testIDs";
15
16
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
17
  function RenderListItemInner({
17
18
  render,
@@ -54,7 +55,15 @@ function RenderListItemInner({
54
55
 
55
56
  // Format time since last render (live-updating)
56
57
  const timeSinceRender = useRelativeTime(render.lastRenderTime);
58
+
59
+ // The row is addressed by the identifier it SHOWS, never by nativeTag — see
60
+ // utils/testIDs.ts for why a tag can't be a handle. The label spells the row
61
+ // out for assistive tech, which otherwise gets a coloured bar and a count.
62
+ const rowTestID = renderRowTestID(identifier.value);
57
63
  return /*#__PURE__*/_jsxs(TouchableOpacity, {
64
+ testID: rowTestID,
65
+ accessibilityRole: "button",
66
+ accessibilityLabel: `${render.displayName}, ${identifier.type} ${identifier.value}, ${render.renderCount} renders`,
58
67
  style: styles.container,
59
68
  onPress: () => onPress(render),
60
69
  activeOpacity: 0.7,
@@ -69,6 +78,7 @@ function RenderListItemInner({
69
78
  children: [/*#__PURE__*/_jsxs(View, {
70
79
  style: styles.viewTypeContainer,
71
80
  children: [/*#__PURE__*/_jsx(Text, {
81
+ testID: `${rowTestID}-name`,
72
82
  style: styles.viewType,
73
83
  numberOfLines: 1,
74
84
  children: render.displayName
@@ -78,6 +88,7 @@ function RenderListItemInner({
78
88
  compact: true,
79
89
  shortLabel: true
80
90
  }) : render.displayName !== render.viewType ? /*#__PURE__*/_jsx(Text, {
91
+ testID: `${rowTestID}-type`,
81
92
  style: styles.nativeType,
82
93
  numberOfLines: 1,
83
94
  children: render.viewType
@@ -87,6 +98,7 @@ function RenderListItemInner({
87
98
  backgroundColor: render.color + "30"
88
99
  }],
89
100
  children: /*#__PURE__*/_jsxs(Text, {
101
+ testID: `${rowTestID}-count`,
90
102
  style: [styles.renderCount, {
91
103
  color: render.color
92
104
  }],
@@ -96,6 +108,7 @@ function RenderListItemInner({
96
108
  }), render.lastRenderCause && /*#__PURE__*/_jsx(View, {
97
109
  style: styles.causeRow,
98
110
  children: /*#__PURE__*/_jsx(RenderCauseBadge, {
111
+ testID: `${rowTestID}-cause`,
99
112
  cause: render.lastRenderCause,
100
113
  compact: true,
101
114
  showKeys: true,
@@ -106,12 +119,14 @@ function RenderListItemInner({
106
119
  children: [/*#__PURE__*/_jsx(View, {
107
120
  style: styles.identifierContainer,
108
121
  children: /*#__PURE__*/_jsx(IdentifierBadge, {
122
+ testID: `${rowTestID}-identifier`,
109
123
  type: identifier.type,
110
124
  value: identifier.value,
111
125
  compact: true,
112
126
  shortLabel: true
113
127
  })
114
128
  }), /*#__PURE__*/_jsx(Text, {
129
+ testID: `${rowTestID}-time`,
115
130
  style: styles.timing,
116
131
  children: timeSinceRender
117
132
  })]
@@ -196,6 +196,9 @@ export function RendersCopySettingsView({
196
196
  return /*#__PURE__*/_jsxs(View, {
197
197
  style: styles.collapsedPreview,
198
198
  children: [/*#__PURE__*/_jsx(TouchableOpacity, {
199
+ testID: "buoy-highlight-copy-preview-show",
200
+ accessibilityRole: "button",
201
+ accessibilityLabel: "Show preview",
199
202
  style: styles.expandButton,
200
203
  onPress: () => setIsPreviewExpanded(true),
201
204
  children: /*#__PURE__*/_jsxs(View, {
@@ -209,6 +212,7 @@ export function RendersCopySettingsView({
209
212
  style: styles.expandButtonTitle,
210
213
  children: "Show Preview"
211
214
  }), /*#__PURE__*/_jsxs(Text, {
215
+ testID: "buoy-highlight-copy-preview-summary",
212
216
  style: styles.expandButtonSubtitle,
213
217
  children: [totalComponents, " component", totalComponents !== 1 ? "s" : "", " \xB7 ", totalRenders, " renders \xB7 ~", formatBytes(estimatedSize), !hasLiveData ? " (mock data)" : ""]
214
218
  })]
@@ -218,6 +222,7 @@ export function RendersCopySettingsView({
218
222
  })]
219
223
  })
220
224
  }), sizeWarningLevel !== "none" && /*#__PURE__*/_jsxs(View, {
225
+ testID: "buoy-highlight-copy-size-warning",
221
226
  style: [styles.sizeWarning, sizeWarningLevel === "danger" && styles.sizeWarningDanger],
222
227
  children: [/*#__PURE__*/_jsx(AlertTriangle, {
223
228
  size: 12,
@@ -234,6 +239,9 @@ export function RendersCopySettingsView({
234
239
  const exportText = generateCopyText();
235
240
  return /*#__PURE__*/_jsxs(View, {
236
241
  children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
242
+ testID: "buoy-highlight-copy-preview-hide",
243
+ accessibilityRole: "button",
244
+ accessibilityLabel: "Hide preview",
237
245
  style: styles.collapseButton,
238
246
  onPress: () => setIsPreviewExpanded(false),
239
247
  children: [/*#__PURE__*/_jsx(Text, {
@@ -244,9 +252,11 @@ export function RendersCopySettingsView({
244
252
  color: night.color.textSecondary
245
253
  })]
246
254
  }), /*#__PURE__*/_jsx(ScrollView, {
255
+ testID: "buoy-highlight-copy-preview-scroll",
247
256
  style: styles.previewScroll,
248
257
  nestedScrollEnabled: true,
249
258
  children: /*#__PURE__*/_jsx(Text, {
259
+ testID: "buoy-highlight-copy-preview-text",
250
260
  style: styles.previewText,
251
261
  selectable: true,
252
262
  children: exportText
@@ -261,6 +271,7 @@ export function RendersCopySettingsView({
261
271
  const statusLabel = hasLiveData ? `${totalComponents} components` : "Mock data (no renders captured)";
262
272
  return /*#__PURE__*/_jsxs(_Fragment, {
263
273
  children: [/*#__PURE__*/_jsx(Text, {
274
+ testID: "buoy-highlight-copy-status",
264
275
  style: styles.statusLabel,
265
276
  children: statusLabel
266
277
  }), /*#__PURE__*/_jsx(ToolbarCopyButton, {
@@ -33,11 +33,14 @@ function StatsDisplayInner() {
33
33
  }, []);
34
34
  return /*#__PURE__*/_jsxs(View, {
35
35
  nativeID: "__rn_buoy__stats-row",
36
+ testID: "buoy-highlight-stats",
37
+ accessibilityLabel: `${stats.totalComponents} components tracked`,
36
38
  style: styles.headerChip,
37
39
  children: [/*#__PURE__*/_jsx(Activity, {
38
40
  size: 12,
39
41
  color: night.color.accent
40
42
  }), /*#__PURE__*/_jsx(Text, {
43
+ testID: "buoy-highlight-stats-value",
41
44
  style: [styles.headerChipValue, {
42
45
  color: night.color.accent
43
46
  }],
@@ -19,6 +19,10 @@
19
19
  */
20
20
 
21
21
  import { isDevToolsComponentName, isRenderLoopUnsafeName } from "./devtoolsComponentNames";
22
+ // One implementation, shared with the overlay's RenderCauseDetector — the two
23
+ // classifiers are separate on purpose, but this question is not. See the file
24
+ // for why the obvious identity check is wrong.
25
+ import { ownStateChanged } from "./ownStateChanged";
22
26
 
23
27
  // React work tags for components that render user code.
24
28
  const FunctionComponent = 0;
@@ -206,7 +210,7 @@ function detectCause(fiber, wantKeys) {
206
210
  }
207
211
 
208
212
  // No prop change of substance. Own state/hooks next.
209
- if (prev.memoizedState !== fiber.memoizedState) return {
213
+ if (ownStateChanged(prev, fiber)) return {
210
214
  cause: "hooks"
211
215
  };
212
216
  return {
@@ -30,6 +30,8 @@
30
30
  import { installProfilerInterceptor, uninstallProfilerInterceptor, setComparisonCallback } from "./ProfilerInterceptor";
31
31
  import { findNodeHandle, UIManager, Dimensions, PixelRatio } from "react-native";
32
32
  import { RenderTracker } from "./RenderTracker";
33
+ import { describeScreen } from "./screenElements";
34
+ import { scoreTapMatch } from "./tapMatch";
33
35
  import { PerformanceLogger, markEventReceived } from "./PerformanceLogger";
34
36
  import { detectRenderCause, clearRenderCauseState } from "./RenderCauseDetector";
35
37
  import { notifySubscriberCountChange } from "@buoy-gg/shared-ui";
@@ -1165,10 +1167,60 @@ async function locateComponent(params) {
1165
1167
  const margin = typeof params.margin === "number" ? params.margin : 12;
1166
1168
  const renders = RenderTracker.getRenders();
1167
1169
  if (renders.length === 0) {
1170
+ const described = await describeScreen();
1171
+ const q = (params.query ?? "").trim().toLowerCase();
1172
+ let best;
1173
+ for (const el of described.elements) {
1174
+ if (params.nativeTag != null && el.nativeTag === params.nativeTag) {
1175
+ best = {
1176
+ el,
1177
+ score: 1000
1178
+ };
1179
+ break;
1180
+ }
1181
+ const score = scoreTapMatch({
1182
+ nativeTag: el.nativeTag,
1183
+ testID: el.testID,
1184
+ label: el.label,
1185
+ text: el.text,
1186
+ name: el.name,
1187
+ tap: el.tap
1188
+ }, {
1189
+ query: q || undefined,
1190
+ nativeTag: params.nativeTag
1191
+ });
1192
+ if (score <= 0) continue;
1193
+ if (!best || score > best.score) best = {
1194
+ el,
1195
+ score
1196
+ };
1197
+ }
1198
+ if (!best?.el.frame) {
1199
+ return {
1200
+ matched: false,
1201
+ reason: "no-match",
1202
+ candidates: []
1203
+ };
1204
+ }
1205
+ const win = described.screen;
1206
+ const f = best.el.frame;
1168
1207
  return {
1169
- matched: false,
1170
- reason: "no-renders",
1171
- candidates: []
1208
+ matched: true,
1209
+ live: true,
1210
+ rect: {
1211
+ x: f.x * win.width,
1212
+ y: f.y * win.height,
1213
+ width: f.width * win.width,
1214
+ height: f.height * win.height
1215
+ },
1216
+ scale: PixelRatio.get(),
1217
+ screen: {
1218
+ width: win.width,
1219
+ height: win.height
1220
+ },
1221
+ nativeTag: best.el.nativeTag ?? undefined,
1222
+ componentName: best.el.name,
1223
+ testID: best.el.testID
1172
1224
  };
1173
1225
  }
1174
1226
 
@@ -1570,6 +1622,16 @@ function disable() {
1570
1622
  devToolsNodeCache.clear(); // Clear detection cache
1571
1623
  clearRenderCauseState(); // Clear render cause state to prevent memory leaks
1572
1624
 
1625
+ // Freeze is a view state on a LIVE overlay, and the overlay is going away:
1626
+ // the boxes it was holding open were just cleared with the tags above. Left
1627
+ // latched it outlived the thing it froze — switch tracking off and back on
1628
+ // and the button still read "Unfreeze frame" on a tool with nothing frozen,
1629
+ // while new highlights (correctly) refused to fade.
1630
+ if (isFrozen) {
1631
+ isFrozen = false;
1632
+ notifyFreezeListeners();
1633
+ }
1634
+
1573
1635
  // Only unsubscribe if background tracking doesn't need it
1574
1636
  if (!backgroundTrackingEnabled) {
1575
1637
  unsubscribeFromTraceUpdates();
@@ -26,6 +26,11 @@
26
26
 
27
27
  "use strict";
28
28
 
29
+ // One implementation of "did this component's own state actually change?",
30
+ // shared with CommitProfiler. The two classifiers are separate on purpose;
31
+ // this question is not, and both of them got it wrong the same way.
32
+ import { hookStateChanged } from "./ownStateChanged";
33
+
29
34
  // ============================================================================
30
35
  // TYPE DEFINITIONS
31
36
  // Based on React Native source: packages/react-native/Libraries/Renderer/
@@ -650,9 +655,16 @@ function detectComponentCause(componentFiber) {
650
655
 
651
656
  // Check if state changed (for hooks, memoizedState is a linked list)
652
657
  // This means useState/useReducer triggered a re-render
653
- const stateChanged = prevMemoizedState !== currentFiber.memoizedState;
654
- if (stateChanged) {
655
- // Phase 3: Compare hook states to get actual value changes
658
+ // NOT `prevMemoizedState !== currentFiber.memoizedState`, which is what this
659
+ // used to be — see utils/ownStateChanged.ts for why that is wrong and what
660
+ // it cost. Shared with `CommitProfiler`, which reached the same wrong line
661
+ // independently; the two classifiers stay separate, this one question does
662
+ // not.
663
+ if (hookStateChanged(prevMemoizedState, currentFiber.memoizedState, {
664
+ isClassComponent: currentFiber.tag === 1
665
+ })) {
666
+ // Phase 3: the actual before/after values, for the UI. Only needed once
667
+ // the cause is settled, and only the overlay displays them.
656
668
  const hookChanges = compareHookStates(prevExtractedHooks, currentHooks);
657
669
  return {
658
670
  cause: "state",
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { getComponentDisplayName } from "./ViewTypeMapper";
12
12
  import { PerformanceLogger } from "./PerformanceLogger";
13
+ import { isNamedComposite } from "./namedComposite";
13
14
 
14
15
  /**
15
16
  * Debug logging levels for render cause detection.
@@ -69,7 +70,8 @@ class RenderTrackerSingleton {
69
70
  // Debug settings
70
71
  debugLogLevel: "off",
71
72
  // Dev tools visibility - exclude by default
72
- excludeDevTools: true
73
+ excludeDevTools: true,
74
+ showHostViews: false
73
75
  };
74
76
 
75
77
  // Batch mode: defer notifyListeners until endBatch is called
@@ -418,6 +420,9 @@ class RenderTrackerSingleton {
418
420
  */
419
421
  getFilteredRenders(searchText = "") {
420
422
  let renders = this.getRenders();
423
+ if (!this.settings.showHostViews) {
424
+ renders = renders.filter(isNamedComposite);
425
+ }
421
426
 
422
427
  // Apply search filter
423
428
  if (searchText.trim()) {