@buoy-gg/highlight-updates 7.0.21 → 7.0.22

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.
@@ -45,9 +45,11 @@ const TOP_COMPONENTS_CAP = 20;
45
45
  let hookInstalled = false;
46
46
  let active = false;
47
47
  let sawDurations = false;
48
+ let captureDetail = false;
48
49
  let totalCommits = 0;
49
50
  let totalRenders = 0;
50
51
  let totalRenderMs = 0;
52
+ let totalCauses = {};
51
53
  let buckets = new Map();
52
54
  function getHook() {
53
55
  if (typeof global === "undefined") return null;
@@ -119,13 +121,129 @@ function isFrameworkWrapperName(name) {
119
121
  }
120
122
  return false;
121
123
  }
122
- function recordRender(fiber) {
124
+
125
+ /** Bound on prop keys compared per render, so a fat props object can't stall a commit. */
126
+ const MAX_PROPS_COMPARED = 40;
127
+ /** Bound on distinct changed-prop key names remembered per component (detail tier). */
128
+ const MAX_CHANGED_KEYS = 5;
129
+
130
+ /**
131
+ * Are two values for the same prop key equivalent despite differing identity?
132
+ *
133
+ * Functions always count as equivalent: a fresh arrow per render is THE
134
+ * unstable-prop bug, and there's no way to compare behaviour anyway. Plain
135
+ * objects and arrays compare one level deep, which catches `style={{...}}`
136
+ * and `data={[...]}`. Anything else falls through to a real value change.
137
+ */
138
+ function isEquivalentValue(a, b) {
139
+ if (Object.is(a, b)) return true;
140
+ const ta = typeof a;
141
+ if (ta !== typeof b) return false;
142
+ if (ta === "function") return true;
143
+ if (a === null || b === null || ta !== "object") return false;
144
+ const aIsArray = Array.isArray(a);
145
+ if (aIsArray !== Array.isArray(b)) return false;
146
+ if (aIsArray) {
147
+ if (a.length !== b.length) return false;
148
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
149
+ return true;
150
+ }
151
+ const keysA = Object.keys(a);
152
+ const keysB = Object.keys(b);
153
+ if (keysA.length !== keysB.length) return false;
154
+ for (let i = 0; i < keysA.length; i++) {
155
+ const k = keysA[i];
156
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
157
+ if (!Object.is(a[k], b[k])) return false;
158
+ }
159
+ return true;
160
+ }
161
+ /**
162
+ * Why did this fiber render?
163
+ *
164
+ * Simpler than the overlay's RenderCauseDetector, and deliberately so: that
165
+ * one carries WeakMap fallbacks and fiber-swap detection because traceUpdates
166
+ * hands it fibers at an ambiguous moment. Here we're inside onCommitFiberRoot
167
+ * walking the committed tree, so `fiber` is current and `fiber.alternate` is
168
+ * previous by definition. Porting the swap logic would be cargo-cult.
169
+ */
170
+ function detectCause(fiber, wantKeys) {
171
+ const prev = fiber.alternate;
172
+ if (!prev) return {
173
+ cause: "mount"
174
+ };
175
+ const nextProps = fiber.memoizedProps;
176
+ const prevProps = prev.memoizedProps;
177
+ if (nextProps !== prevProps && nextProps && prevProps) {
178
+ let sawChange = false;
179
+ let sawValueChange = false;
180
+ let changedKeys;
181
+ const keys = Object.keys(nextProps);
182
+ const limit = keys.length < MAX_PROPS_COMPARED ? keys.length : MAX_PROPS_COMPARED;
183
+ for (let i = 0; i < limit; i++) {
184
+ const k = keys[i];
185
+ const a = prevProps[k];
186
+ const b = nextProps[k];
187
+ if (Object.is(a, b)) continue;
188
+ sawChange = true;
189
+ if (wantKeys) {
190
+ if (!changedKeys) changedKeys = [];
191
+ if (changedKeys.length < MAX_CHANGED_KEYS) changedKeys.push(k);
192
+ }
193
+ if (!isEquivalentValue(a, b)) {
194
+ sawValueChange = true;
195
+ if (!wantKeys) break; // cause is settled; only keys need the full pass
196
+ }
197
+ }
198
+ // A removed key counts as a real change; cheap to detect via key count.
199
+ if (!sawValueChange && Object.keys(prevProps).length !== keys.length) {
200
+ sawChange = true;
201
+ sawValueChange = true;
202
+ }
203
+ if (sawChange) {
204
+ return {
205
+ cause: sawValueChange ? "props" : "propsUnstable",
206
+ changedKeys
207
+ };
208
+ }
209
+ }
210
+
211
+ // No prop change of substance. Own state/hooks next.
212
+ if (prev.memoizedState !== fiber.memoizedState) return {
213
+ cause: "hooks"
214
+ };
215
+ return {
216
+ cause: "parent"
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Nearest ancestor that ALSO rendered this commit and is worth naming — i.e.
222
+ * skipping framework wrappers, which are never where a fix goes. Answers "who
223
+ * dragged this along", which is the actionable half of a cascade.
224
+ */
225
+ function nearestRenderedAncestorName(fiber) {
226
+ for (let p = fiber.return; p != null; p = p.return) {
227
+ if (!isCompositeTag(p.tag)) continue;
228
+ if ((getFlags(p) & PerformedWork) === 0) continue;
229
+ const name = getComponentName(p);
230
+ if (isFrameworkWrapperName(name)) continue;
231
+ return name;
232
+ }
233
+ return undefined;
234
+ }
235
+ function recordRender(fiber, parentName) {
123
236
  const name = getComponentName(fiber);
124
237
  const selfMs = computeSelfMs(fiber);
125
238
  if (selfMs !== null) sawDurations = true;
126
239
  const ms = selfMs ?? 0;
127
240
  totalRenders += 1;
128
241
  totalRenderMs += ms;
242
+ const {
243
+ cause,
244
+ changedKeys
245
+ } = detectCause(fiber, captureDetail);
246
+ totalCauses[cause] = (totalCauses[cause] ?? 0) + 1;
129
247
 
130
248
  // Framework wrappers count toward totals but never surface as a row.
131
249
  if (isFrameworkWrapperName(name)) return;
@@ -136,13 +254,27 @@ function recordRender(fiber) {
136
254
  bucket = {
137
255
  renders: 0,
138
256
  totalSelfMs: 0,
139
- maxSelfMs: 0
257
+ maxSelfMs: 0,
258
+ causes: {}
140
259
  };
141
260
  buckets.set(name, bucket);
142
261
  }
143
262
  bucket.renders += 1;
144
263
  bucket.totalSelfMs += ms;
145
264
  if (ms > bucket.maxSelfMs) bucket.maxSelfMs = ms;
265
+ bucket.causes[cause] = (bucket.causes[cause] ?? 0) + 1;
266
+ if (captureDetail) {
267
+ if (changedKeys) {
268
+ if (!bucket.changedProps) bucket.changedProps = new Set();
269
+ for (let i = 0; i < changedKeys.length; i++) {
270
+ if (bucket.changedProps.size >= MAX_CHANGED_KEYS) break;
271
+ bucket.changedProps.add(changedKeys[i]);
272
+ }
273
+ }
274
+ // First parent wins — a component rendered under several parents is rare,
275
+ // and the first is the one that showed up in the heaviest commit.
276
+ if (parentName && !bucket.parentName) bucket.parentName = parentName;
277
+ }
146
278
  }
147
279
 
148
280
  /**
@@ -161,13 +293,21 @@ function handleCommit(root) {
161
293
  let skipSubtree = false;
162
294
  const composite = isCompositeTag(fiber.tag);
163
295
  if (composite) {
164
- // Prune Buoy's own devtools UI wholesale.
165
- if ((0, _devtoolsComponentNames.isDevToolsComponentName)(getComponentName(fiber))) {
296
+ const compositeName = getComponentName(fiber);
297
+ // Only Buoy's render-tracking UI gets its SUBTREE pruned — walking it
298
+ // feeds its own renders back in and loops. Everything else that's merely
299
+ // devtools noise is skipped as a NODE and we keep descending: app
300
+ // components live underneath Buoy chrome, and pruning a subtree on a name
301
+ // match is what made a stray "Virtualized" prefix hide every FlatList in
302
+ // every app.
303
+ if ((0, _devtoolsComponentNames.isRenderLoopUnsafeName)(compositeName)) {
166
304
  skipSubtree = true;
305
+ } else if ((0, _devtoolsComponentNames.isDevToolsComponentName)(compositeName)) {
306
+ // skip this node, keep walking its children
167
307
  } else if ((getFlags(fiber) & PerformedWork) !== 0) {
168
308
  // PerformedWork is the only trustworthy "rendered this commit"
169
309
  // signal — actualDuration is stale on bailed-out fibers.
170
- recordRender(fiber);
310
+ recordRender(fiber, captureDetail ? nearestRenderedAncestorName(fiber) : undefined);
171
311
  }
172
312
  }
173
313
  if (!skipSubtree) {
@@ -230,14 +370,24 @@ function isCapturing() {
230
370
  return active;
231
371
  }
232
372
 
233
- /** Begin a capture window, discarding any previous unclaimed data. */
234
- function startCapture() {
373
+ /**
374
+ * Begin a capture window, discarding any previous unclaimed data.
375
+ *
376
+ * `detail` turns on the allocating tier — changed prop key names and parent
377
+ * component names. Causes themselves are always classified: it costs a shallow
378
+ * prop compare, against a `computeSelfMs` that already walks every direct
379
+ * child on every render, and the wasted-render total is the number most worth
380
+ * seeing without having to know to ask for it.
381
+ */
382
+ function startCapture(opts) {
235
383
  if (!ensureHookInstalled()) return;
236
384
  buckets = new Map();
237
385
  sawDurations = false;
386
+ captureDetail = opts?.detail === true;
238
387
  totalCommits = 0;
239
388
  totalRenders = 0;
240
389
  totalRenderMs = 0;
390
+ totalCauses = {};
241
391
  active = true;
242
392
  }
243
393
 
@@ -253,19 +403,31 @@ function stopCapture() {
253
403
  renders: b.renders,
254
404
  totalMs: round2(b.totalSelfMs),
255
405
  maxMs: round2(b.maxSelfMs),
256
- avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0
406
+ avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0,
407
+ causes: b.causes,
408
+ ...(b.changedProps && b.changedProps.size > 0 ? {
409
+ changedProps: Array.from(b.changedProps)
410
+ } : null),
411
+ ...(b.parentName ? {
412
+ parentName: b.parentName
413
+ } : null)
257
414
  }));
258
415
  const summary = {
259
416
  totalCommits,
260
417
  totalRenders,
261
418
  capturedDurations: sawDurations,
262
419
  totalRenderMs: round2(totalRenderMs),
420
+ // Parent cascades and identity-only prop churn are both removable without
421
+ // changing behaviour — together they're the recoverable share.
422
+ wastedRenders: (totalCauses.parent ?? 0) + (totalCauses.propsUnstable ?? 0),
423
+ causes: totalCauses,
263
424
  topComponents
264
425
  };
265
426
  buckets = new Map();
266
427
  totalCommits = 0;
267
428
  totalRenders = 0;
268
429
  totalRenderMs = 0;
430
+ totalCauses = {};
269
431
  return summary;
270
432
  }
271
433
  function round2(n) {
@@ -555,7 +555,12 @@ function isOurOverlayNode(stateNode) {
555
555
 
556
556
  // Only check other dev tools if excludeDevTools is enabled
557
557
  if (excludeDevTools) {
558
- if (_devtoolsComponentNames.OTHER_DEV_TOOLS_COMPONENTS.has(name)) {
558
+ // Two sets, same meaning: COMPONENTS is the original list, EXACT
559
+ // holds the names that used to be matched by over-broad prefixes
560
+ // (the "Virtualized" that hid every FlatList, the "Dial" that hid
561
+ // every Dialog). Both paths must consult both, or Buoy's own chrome
562
+ // starts flashing highlight boxes in one of them and not the other.
563
+ if (_devtoolsComponentNames.OTHER_DEV_TOOLS_COMPONENTS.has(name) || _devtoolsComponentNames.OTHER_DEV_TOOLS_EXACT.has(name)) {
559
564
  result = true;
560
565
  break;
561
566
  }
@@ -3,11 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_COMPONENTS = exports.HIGHLIGHT_OVERLAY_PREFIXES = exports.HIGHLIGHT_OVERLAY_COMPONENTS = void 0;
6
+ exports.OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_EXACT = exports.OTHER_DEV_TOOLS_COMPONENTS = exports.HIGHLIGHT_OVERLAY_PREFIXES = exports.HIGHLIGHT_OVERLAY_COMPONENTS = void 0;
7
7
  exports.isCapturingBuoyToolRenders = isCapturingBuoyToolRenders;
8
8
  exports.isDevToolsComponentName = isDevToolsComponentName;
9
9
  exports.isJsTopComponentName = isJsTopComponentName;
10
10
  exports.isPerfMonitorComponentName = isPerfMonitorComponentName;
11
+ exports.isRenderLoopUnsafeName = isRenderLoopUnsafeName;
11
12
  exports.setCaptureBuoyToolRenders = setCaptureBuoyToolRenders;
12
13
  // ============================================================================
13
14
  // Buoy devtools component-name sets, shared by HighlightUpdatesController's
@@ -66,16 +67,37 @@ const OTHER_DEV_TOOLS_COMPONENTS = exports.OTHER_DEV_TOOLS_COMPONENTS = new Set(
66
67
  // separately). The modal/table re-render on every 250ms snapshot tick.
67
68
  "TaskTable", "TaskRow", "BusySparkline"]);
68
69
 
69
- // Other dev tools prefixes - can be toggled via excludeDevTools setting
70
- const OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "TreeDiff", "DataViewer", "DiffView", "DiffSummary", "Virtualized",
71
- // Floating menu prefixes
72
- "Floating", "Minimized", "Dial", "Expandable", "Chevron", "Glitch", "Settings", "Onboarding", "DevTools", "ImageOverlay",
70
+ /**
71
+ * Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
72
+ *
73
+ * EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
74
+ * match hides the component from render reports. Generic English words do not
75
+ * belong in this list, because app components share them.
76
+ *
77
+ * This list used to contain `Virtualized`, meant for Buoy's own
78
+ * VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
79
+ * so the prefix matched — and because CommitProfiler pruned the whole subtree
80
+ * on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
81
+ * app. Lists are the number-one source of render problems in React Native, and
82
+ * the render tools were blind to all of them. `Dial` was the same trap:
83
+ * "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
84
+ *
85
+ * Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
86
+ * as exact matches instead.
87
+ */
88
+ const OTHER_DEV_TOOLS_PREFIXES = exports.OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "ImageOverlay",
73
89
  // Perf-monitor prefixes (safe: user code is Perf*, buoy code is
74
90
  // PerfMonitor* / PerfHud*)
75
91
  "PerfMonitor", "PerfHud",
76
92
  // JS TOP (js-top package)
77
93
  "JsTop"];
78
94
 
95
+ /**
96
+ * Buoy component names that were previously matched by over-broad prefixes.
97
+ * Exact matches only — see the warning above.
98
+ */
99
+ const OTHER_DEV_TOOLS_EXACT = exports.OTHER_DEV_TOOLS_EXACT = new Set(["VirtualizedDataExplorer", "TreeDiffView", "TreeDiffRow", "DataViewer", "DiffView", "DiffSummary", "FloatingDevTools", "FloatingMenu", "FloatingBubble", "MinimizedDock", "DialMenu", "DialItem", "ExpandableSection", "ChevronIcon", "GlitchText", "SettingsModal", "SettingsRow", "OnboardingFlow", "DevToolsMenu"]);
100
+
79
101
  // ── Bench "profile our own tools" mode ──────────────────────────────────
80
102
  // When enabled (by a dedicated benchmark screen, e.g. the example app's
81
103
  // /tool-bench page), render capture INCLUDES Buoy's tool UI so we can
@@ -140,8 +162,27 @@ function isDevToolsComponentName(name) {
140
162
  return isPerfMonitorComponentName(name) || isJsTopComponentName(name);
141
163
  }
142
164
  if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) return true;
165
+ if (OTHER_DEV_TOOLS_EXACT.has(name)) return true;
143
166
  for (let i = 0; i < OTHER_DEV_TOOLS_PREFIXES.length; i++) {
144
167
  if (name.startsWith(OTHER_DEV_TOOLS_PREFIXES[i])) return true;
145
168
  }
146
169
  return false;
170
+ }
171
+
172
+ /**
173
+ * Is this name one whose SUBTREE must be skipped, not merely the node?
174
+ *
175
+ * Only the always-excluded group qualifies: walking Buoy's own render-tracking
176
+ * UI feeds its own renders back in and loops. Everything else in
177
+ * `isDevToolsComponentName` is noise reduction — hide the row, but keep
178
+ * walking, because app components live underneath Buoy chrome (a tool modal
179
+ * renders the host app behind it) and pruning on a NAME is how one bad prefix
180
+ * cost us every FlatList in every app.
181
+ */
182
+ function isRenderLoopUnsafeName(name) {
183
+ if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) return true;
184
+ for (let i = 0; i < HIGHLIGHT_OVERLAY_PREFIXES.length; i++) {
185
+ if (name.startsWith(HIGHLIGHT_OVERLAY_PREFIXES[i])) return true;
186
+ }
187
+ return false;
147
188
  }
@@ -7,6 +7,7 @@ exports.highlightUpdatesSyncAdapter = void 0;
7
7
  var _HighlightUpdatesController = _interopRequireDefault(require("../highlight-updates/utils/HighlightUpdatesController"));
8
8
  var _RenderTracker = require("../highlight-updates/utils/RenderTracker");
9
9
  var _screenElements = require("../highlight-updates/utils/screenElements");
10
+ var _CommitProfiler = require("../highlight-updates/utils/CommitProfiler");
10
11
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
12
  function ensureInitialized() {
12
13
  if (!_HighlightUpdatesController.default.isInitialized()) {
@@ -65,6 +66,41 @@ function jsonSafeRender(render) {
65
66
  }
66
67
  }
67
68
 
69
+ /**
70
+ * Measurement window for automated callers (MCP `measure_renders`).
71
+ *
72
+ * Backed by CommitProfiler, NOT the RenderTracker that feeds the overlay, and
73
+ * the difference is the whole point. RenderTracker tracks HOST VIEWS: ask it
74
+ * why `DealRow` re-rendered and it answers about the native `View` inside it —
75
+ * "props changed: accessibilityState, accessibilityValue" — which is true and
76
+ * useless. CommitProfiler walks COMPOSITE fibers, so it answers about the
77
+ * component you named: which of ITS props changed, whether they changed by
78
+ * value or only by identity, and which parent dragged it along.
79
+ *
80
+ * It also touches none of the user's overlay state — no enabling, no clearing
81
+ * counters someone was collecting, nothing drawn on screen. A measurement is
82
+ * invisible.
83
+ */
84
+ function beginMeasurement() {
85
+ if (!_CommitProfiler.CommitProfiler.isSupported()) {
86
+ return {
87
+ ok: false,
88
+ reason: "render capture needs a dev build with the React DevTools hook available"
89
+ };
90
+ }
91
+ _CommitProfiler.CommitProfiler.startCapture({
92
+ detail: true
93
+ });
94
+ return {
95
+ ok: true
96
+ };
97
+ }
98
+ function endMeasurement() {
99
+ return {
100
+ summary: _CommitProfiler.CommitProfiler.stopCapture()
101
+ };
102
+ }
103
+
68
104
  /**
69
105
  * Sync adapter for the highlight-updates tool, consumed by
70
106
  * @buoy-gg/external-sync's `useExternalSync` (structurally matches its
@@ -81,7 +117,9 @@ function jsonSafeRender(render) {
81
117
  * stay on the device. Dashboards fetch them via `getRenderDetail`.
82
118
  */
83
119
  const highlightUpdatesSyncAdapter = exports.highlightUpdatesSyncAdapter = {
84
- version: 2,
120
+ // v3: beginMeasurement / endMeasurement — a CommitProfiler capture window
121
+ // for automated callers, with per-component causes. Invisible to the user.
122
+ version: 3,
85
123
  getSnapshot: () => ({
86
124
  enabled: _HighlightUpdatesController.default.isEnabled(),
87
125
  frozen: _HighlightUpdatesController.default.getFrozen(),
@@ -103,6 +141,10 @@ const highlightUpdatesSyncAdapter = exports.highlightUpdatesSyncAdapter = {
103
141
  };
104
142
  },
105
143
  actions: {
144
+ // Capture window for automated callers (MCP `measure_renders`). Pairs:
145
+ // begin -> drive the UI -> end. `end` always restores.
146
+ beginMeasurement: () => beginMeasurement(),
147
+ endMeasurement: () => endMeasurement(),
106
148
  /**
107
149
  * On-demand detail: one component's real renderHistory + lastRenderCause
108
150
  * (including hookChanges). Wire-form snapshots omit those.
@@ -18,7 +18,7 @@
18
18
  * registry (see the registration in this package's index.tsx).
19
19
  */
20
20
 
21
- import { isDevToolsComponentName } from "./devtoolsComponentNames";
21
+ import { isDevToolsComponentName, isRenderLoopUnsafeName } from "./devtoolsComponentNames";
22
22
 
23
23
  // React work tags for components that render user code.
24
24
  const FunctionComponent = 0;
@@ -42,9 +42,11 @@ const TOP_COMPONENTS_CAP = 20;
42
42
  let hookInstalled = false;
43
43
  let active = false;
44
44
  let sawDurations = false;
45
+ let captureDetail = false;
45
46
  let totalCommits = 0;
46
47
  let totalRenders = 0;
47
48
  let totalRenderMs = 0;
49
+ let totalCauses = {};
48
50
  let buckets = new Map();
49
51
  function getHook() {
50
52
  if (typeof global === "undefined") return null;
@@ -116,13 +118,129 @@ function isFrameworkWrapperName(name) {
116
118
  }
117
119
  return false;
118
120
  }
119
- function recordRender(fiber) {
121
+
122
+ /** Bound on prop keys compared per render, so a fat props object can't stall a commit. */
123
+ const MAX_PROPS_COMPARED = 40;
124
+ /** Bound on distinct changed-prop key names remembered per component (detail tier). */
125
+ const MAX_CHANGED_KEYS = 5;
126
+
127
+ /**
128
+ * Are two values for the same prop key equivalent despite differing identity?
129
+ *
130
+ * Functions always count as equivalent: a fresh arrow per render is THE
131
+ * unstable-prop bug, and there's no way to compare behaviour anyway. Plain
132
+ * objects and arrays compare one level deep, which catches `style={{...}}`
133
+ * and `data={[...]}`. Anything else falls through to a real value change.
134
+ */
135
+ function isEquivalentValue(a, b) {
136
+ if (Object.is(a, b)) return true;
137
+ const ta = typeof a;
138
+ if (ta !== typeof b) return false;
139
+ if (ta === "function") return true;
140
+ if (a === null || b === null || ta !== "object") return false;
141
+ const aIsArray = Array.isArray(a);
142
+ if (aIsArray !== Array.isArray(b)) return false;
143
+ if (aIsArray) {
144
+ if (a.length !== b.length) return false;
145
+ for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
146
+ return true;
147
+ }
148
+ const keysA = Object.keys(a);
149
+ const keysB = Object.keys(b);
150
+ if (keysA.length !== keysB.length) return false;
151
+ for (let i = 0; i < keysA.length; i++) {
152
+ const k = keysA[i];
153
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
154
+ if (!Object.is(a[k], b[k])) return false;
155
+ }
156
+ return true;
157
+ }
158
+ /**
159
+ * Why did this fiber render?
160
+ *
161
+ * Simpler than the overlay's RenderCauseDetector, and deliberately so: that
162
+ * one carries WeakMap fallbacks and fiber-swap detection because traceUpdates
163
+ * hands it fibers at an ambiguous moment. Here we're inside onCommitFiberRoot
164
+ * walking the committed tree, so `fiber` is current and `fiber.alternate` is
165
+ * previous by definition. Porting the swap logic would be cargo-cult.
166
+ */
167
+ function detectCause(fiber, wantKeys) {
168
+ const prev = fiber.alternate;
169
+ if (!prev) return {
170
+ cause: "mount"
171
+ };
172
+ const nextProps = fiber.memoizedProps;
173
+ const prevProps = prev.memoizedProps;
174
+ if (nextProps !== prevProps && nextProps && prevProps) {
175
+ let sawChange = false;
176
+ let sawValueChange = false;
177
+ let changedKeys;
178
+ const keys = Object.keys(nextProps);
179
+ const limit = keys.length < MAX_PROPS_COMPARED ? keys.length : MAX_PROPS_COMPARED;
180
+ for (let i = 0; i < limit; i++) {
181
+ const k = keys[i];
182
+ const a = prevProps[k];
183
+ const b = nextProps[k];
184
+ if (Object.is(a, b)) continue;
185
+ sawChange = true;
186
+ if (wantKeys) {
187
+ if (!changedKeys) changedKeys = [];
188
+ if (changedKeys.length < MAX_CHANGED_KEYS) changedKeys.push(k);
189
+ }
190
+ if (!isEquivalentValue(a, b)) {
191
+ sawValueChange = true;
192
+ if (!wantKeys) break; // cause is settled; only keys need the full pass
193
+ }
194
+ }
195
+ // A removed key counts as a real change; cheap to detect via key count.
196
+ if (!sawValueChange && Object.keys(prevProps).length !== keys.length) {
197
+ sawChange = true;
198
+ sawValueChange = true;
199
+ }
200
+ if (sawChange) {
201
+ return {
202
+ cause: sawValueChange ? "props" : "propsUnstable",
203
+ changedKeys
204
+ };
205
+ }
206
+ }
207
+
208
+ // No prop change of substance. Own state/hooks next.
209
+ if (prev.memoizedState !== fiber.memoizedState) return {
210
+ cause: "hooks"
211
+ };
212
+ return {
213
+ cause: "parent"
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Nearest ancestor that ALSO rendered this commit and is worth naming — i.e.
219
+ * skipping framework wrappers, which are never where a fix goes. Answers "who
220
+ * dragged this along", which is the actionable half of a cascade.
221
+ */
222
+ function nearestRenderedAncestorName(fiber) {
223
+ for (let p = fiber.return; p != null; p = p.return) {
224
+ if (!isCompositeTag(p.tag)) continue;
225
+ if ((getFlags(p) & PerformedWork) === 0) continue;
226
+ const name = getComponentName(p);
227
+ if (isFrameworkWrapperName(name)) continue;
228
+ return name;
229
+ }
230
+ return undefined;
231
+ }
232
+ function recordRender(fiber, parentName) {
120
233
  const name = getComponentName(fiber);
121
234
  const selfMs = computeSelfMs(fiber);
122
235
  if (selfMs !== null) sawDurations = true;
123
236
  const ms = selfMs ?? 0;
124
237
  totalRenders += 1;
125
238
  totalRenderMs += ms;
239
+ const {
240
+ cause,
241
+ changedKeys
242
+ } = detectCause(fiber, captureDetail);
243
+ totalCauses[cause] = (totalCauses[cause] ?? 0) + 1;
126
244
 
127
245
  // Framework wrappers count toward totals but never surface as a row.
128
246
  if (isFrameworkWrapperName(name)) return;
@@ -133,13 +251,27 @@ function recordRender(fiber) {
133
251
  bucket = {
134
252
  renders: 0,
135
253
  totalSelfMs: 0,
136
- maxSelfMs: 0
254
+ maxSelfMs: 0,
255
+ causes: {}
137
256
  };
138
257
  buckets.set(name, bucket);
139
258
  }
140
259
  bucket.renders += 1;
141
260
  bucket.totalSelfMs += ms;
142
261
  if (ms > bucket.maxSelfMs) bucket.maxSelfMs = ms;
262
+ bucket.causes[cause] = (bucket.causes[cause] ?? 0) + 1;
263
+ if (captureDetail) {
264
+ if (changedKeys) {
265
+ if (!bucket.changedProps) bucket.changedProps = new Set();
266
+ for (let i = 0; i < changedKeys.length; i++) {
267
+ if (bucket.changedProps.size >= MAX_CHANGED_KEYS) break;
268
+ bucket.changedProps.add(changedKeys[i]);
269
+ }
270
+ }
271
+ // First parent wins — a component rendered under several parents is rare,
272
+ // and the first is the one that showed up in the heaviest commit.
273
+ if (parentName && !bucket.parentName) bucket.parentName = parentName;
274
+ }
143
275
  }
144
276
 
145
277
  /**
@@ -158,13 +290,21 @@ function handleCommit(root) {
158
290
  let skipSubtree = false;
159
291
  const composite = isCompositeTag(fiber.tag);
160
292
  if (composite) {
161
- // Prune Buoy's own devtools UI wholesale.
162
- if (isDevToolsComponentName(getComponentName(fiber))) {
293
+ const compositeName = getComponentName(fiber);
294
+ // Only Buoy's render-tracking UI gets its SUBTREE pruned — walking it
295
+ // feeds its own renders back in and loops. Everything else that's merely
296
+ // devtools noise is skipped as a NODE and we keep descending: app
297
+ // components live underneath Buoy chrome, and pruning a subtree on a name
298
+ // match is what made a stray "Virtualized" prefix hide every FlatList in
299
+ // every app.
300
+ if (isRenderLoopUnsafeName(compositeName)) {
163
301
  skipSubtree = true;
302
+ } else if (isDevToolsComponentName(compositeName)) {
303
+ // skip this node, keep walking its children
164
304
  } else if ((getFlags(fiber) & PerformedWork) !== 0) {
165
305
  // PerformedWork is the only trustworthy "rendered this commit"
166
306
  // signal — actualDuration is stale on bailed-out fibers.
167
- recordRender(fiber);
307
+ recordRender(fiber, captureDetail ? nearestRenderedAncestorName(fiber) : undefined);
168
308
  }
169
309
  }
170
310
  if (!skipSubtree) {
@@ -227,14 +367,24 @@ function isCapturing() {
227
367
  return active;
228
368
  }
229
369
 
230
- /** Begin a capture window, discarding any previous unclaimed data. */
231
- function startCapture() {
370
+ /**
371
+ * Begin a capture window, discarding any previous unclaimed data.
372
+ *
373
+ * `detail` turns on the allocating tier — changed prop key names and parent
374
+ * component names. Causes themselves are always classified: it costs a shallow
375
+ * prop compare, against a `computeSelfMs` that already walks every direct
376
+ * child on every render, and the wasted-render total is the number most worth
377
+ * seeing without having to know to ask for it.
378
+ */
379
+ function startCapture(opts) {
232
380
  if (!ensureHookInstalled()) return;
233
381
  buckets = new Map();
234
382
  sawDurations = false;
383
+ captureDetail = opts?.detail === true;
235
384
  totalCommits = 0;
236
385
  totalRenders = 0;
237
386
  totalRenderMs = 0;
387
+ totalCauses = {};
238
388
  active = true;
239
389
  }
240
390
 
@@ -250,19 +400,31 @@ function stopCapture() {
250
400
  renders: b.renders,
251
401
  totalMs: round2(b.totalSelfMs),
252
402
  maxMs: round2(b.maxSelfMs),
253
- avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0
403
+ avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0,
404
+ causes: b.causes,
405
+ ...(b.changedProps && b.changedProps.size > 0 ? {
406
+ changedProps: Array.from(b.changedProps)
407
+ } : null),
408
+ ...(b.parentName ? {
409
+ parentName: b.parentName
410
+ } : null)
254
411
  }));
255
412
  const summary = {
256
413
  totalCommits,
257
414
  totalRenders,
258
415
  capturedDurations: sawDurations,
259
416
  totalRenderMs: round2(totalRenderMs),
417
+ // Parent cascades and identity-only prop churn are both removable without
418
+ // changing behaviour — together they're the recoverable share.
419
+ wastedRenders: (totalCauses.parent ?? 0) + (totalCauses.propsUnstable ?? 0),
420
+ causes: totalCauses,
260
421
  topComponents
261
422
  };
262
423
  buckets = new Map();
263
424
  totalCommits = 0;
264
425
  totalRenders = 0;
265
426
  totalRenderMs = 0;
427
+ totalCauses = {};
266
428
  return summary;
267
429
  }
268
430
  function round2(n) {
@@ -33,7 +33,7 @@ import { RenderTracker } from "./RenderTracker";
33
33
  import { PerformanceLogger, markEventReceived } from "./PerformanceLogger";
34
34
  import { detectRenderCause, clearRenderCauseState } from "./RenderCauseDetector";
35
35
  import { notifySubscriberCountChange } from "@buoy-gg/shared-ui";
36
- import { HIGHLIGHT_OVERLAY_COMPONENTS, HIGHLIGHT_OVERLAY_PREFIXES, OTHER_DEV_TOOLS_COMPONENTS, OTHER_DEV_TOOLS_PREFIXES, isCapturingBuoyToolRenders, isPerfMonitorComponentName } from "./devtoolsComponentNames";
36
+ import { HIGHLIGHT_OVERLAY_COMPONENTS, HIGHLIGHT_OVERLAY_PREFIXES, OTHER_DEV_TOOLS_COMPONENTS, OTHER_DEV_TOOLS_EXACT, OTHER_DEV_TOOLS_PREFIXES, isCapturingBuoyToolRenders, isPerfMonitorComponentName } from "./devtoolsComponentNames";
37
37
  // State
38
38
  let globalEnabled = false; // User-controlled toggle for visual highlights
39
39
  // When true, tracking + measurement + storage all run as normal, but the
@@ -551,7 +551,12 @@ function isOurOverlayNode(stateNode) {
551
551
 
552
552
  // Only check other dev tools if excludeDevTools is enabled
553
553
  if (excludeDevTools) {
554
- if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) {
554
+ // Two sets, same meaning: COMPONENTS is the original list, EXACT
555
+ // holds the names that used to be matched by over-broad prefixes
556
+ // (the "Virtualized" that hid every FlatList, the "Dial" that hid
557
+ // every Dialog). Both paths must consult both, or Buoy's own chrome
558
+ // starts flashing highlight boxes in one of them and not the other.
559
+ if (OTHER_DEV_TOOLS_COMPONENTS.has(name) || OTHER_DEV_TOOLS_EXACT.has(name)) {
555
560
  result = true;
556
561
  break;
557
562
  }
@@ -57,16 +57,37 @@ export const OTHER_DEV_TOOLS_COMPONENTS = new Set([
57
57
  // separately). The modal/table re-render on every 250ms snapshot tick.
58
58
  "TaskTable", "TaskRow", "BusySparkline"]);
59
59
 
60
- // Other dev tools prefixes - can be toggled via excludeDevTools setting
61
- export const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "TreeDiff", "DataViewer", "DiffView", "DiffSummary", "Virtualized",
62
- // Floating menu prefixes
63
- "Floating", "Minimized", "Dial", "Expandable", "Chevron", "Glitch", "Settings", "Onboarding", "DevTools", "ImageOverlay",
60
+ /**
61
+ * Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
62
+ *
63
+ * EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
64
+ * match hides the component from render reports. Generic English words do not
65
+ * belong in this list, because app components share them.
66
+ *
67
+ * This list used to contain `Virtualized`, meant for Buoy's own
68
+ * VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
69
+ * so the prefix matched — and because CommitProfiler pruned the whole subtree
70
+ * on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
71
+ * app. Lists are the number-one source of render problems in React Native, and
72
+ * the render tools were blind to all of them. `Dial` was the same trap:
73
+ * "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
74
+ *
75
+ * Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
76
+ * as exact matches instead.
77
+ */
78
+ export const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "ImageOverlay",
64
79
  // Perf-monitor prefixes (safe: user code is Perf*, buoy code is
65
80
  // PerfMonitor* / PerfHud*)
66
81
  "PerfMonitor", "PerfHud",
67
82
  // JS TOP (js-top package)
68
83
  "JsTop"];
69
84
 
85
+ /**
86
+ * Buoy component names that were previously matched by over-broad prefixes.
87
+ * Exact matches only — see the warning above.
88
+ */
89
+ export const OTHER_DEV_TOOLS_EXACT = new Set(["VirtualizedDataExplorer", "TreeDiffView", "TreeDiffRow", "DataViewer", "DiffView", "DiffSummary", "FloatingDevTools", "FloatingMenu", "FloatingBubble", "MinimizedDock", "DialMenu", "DialItem", "ExpandableSection", "ChevronIcon", "GlitchText", "SettingsModal", "SettingsRow", "OnboardingFlow", "DevToolsMenu"]);
90
+
70
91
  // ── Bench "profile our own tools" mode ──────────────────────────────────
71
92
  // When enabled (by a dedicated benchmark screen, e.g. the example app's
72
93
  // /tool-bench page), render capture INCLUDES Buoy's tool UI so we can
@@ -131,8 +152,27 @@ export function isDevToolsComponentName(name) {
131
152
  return isPerfMonitorComponentName(name) || isJsTopComponentName(name);
132
153
  }
133
154
  if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) return true;
155
+ if (OTHER_DEV_TOOLS_EXACT.has(name)) return true;
134
156
  for (let i = 0; i < OTHER_DEV_TOOLS_PREFIXES.length; i++) {
135
157
  if (name.startsWith(OTHER_DEV_TOOLS_PREFIXES[i])) return true;
136
158
  }
137
159
  return false;
160
+ }
161
+
162
+ /**
163
+ * Is this name one whose SUBTREE must be skipped, not merely the node?
164
+ *
165
+ * Only the always-excluded group qualifies: walking Buoy's own render-tracking
166
+ * UI feeds its own renders back in and loops. Everything else in
167
+ * `isDevToolsComponentName` is noise reduction — hide the row, but keep
168
+ * walking, because app components live underneath Buoy chrome (a tool modal
169
+ * renders the host app behind it) and pruning on a NAME is how one bad prefix
170
+ * cost us every FlatList in every app.
171
+ */
172
+ export function isRenderLoopUnsafeName(name) {
173
+ if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) return true;
174
+ for (let i = 0; i < HIGHLIGHT_OVERLAY_PREFIXES.length; i++) {
175
+ if (name.startsWith(HIGHLIGHT_OVERLAY_PREFIXES[i])) return true;
176
+ }
177
+ return false;
138
178
  }
@@ -3,6 +3,7 @@
3
3
  import HighlightUpdatesController from "../highlight-updates/utils/HighlightUpdatesController";
4
4
  import { RenderTracker } from "../highlight-updates/utils/RenderTracker";
5
5
  import { describeScreen, tapElement } from "../highlight-updates/utils/screenElements";
6
+ import { CommitProfiler } from "../highlight-updates/utils/CommitProfiler";
6
7
  function ensureInitialized() {
7
8
  if (!HighlightUpdatesController.isInitialized()) {
8
9
  HighlightUpdatesController.initialize();
@@ -60,6 +61,41 @@ function jsonSafeRender(render) {
60
61
  }
61
62
  }
62
63
 
64
+ /**
65
+ * Measurement window for automated callers (MCP `measure_renders`).
66
+ *
67
+ * Backed by CommitProfiler, NOT the RenderTracker that feeds the overlay, and
68
+ * the difference is the whole point. RenderTracker tracks HOST VIEWS: ask it
69
+ * why `DealRow` re-rendered and it answers about the native `View` inside it —
70
+ * "props changed: accessibilityState, accessibilityValue" — which is true and
71
+ * useless. CommitProfiler walks COMPOSITE fibers, so it answers about the
72
+ * component you named: which of ITS props changed, whether they changed by
73
+ * value or only by identity, and which parent dragged it along.
74
+ *
75
+ * It also touches none of the user's overlay state — no enabling, no clearing
76
+ * counters someone was collecting, nothing drawn on screen. A measurement is
77
+ * invisible.
78
+ */
79
+ function beginMeasurement() {
80
+ if (!CommitProfiler.isSupported()) {
81
+ return {
82
+ ok: false,
83
+ reason: "render capture needs a dev build with the React DevTools hook available"
84
+ };
85
+ }
86
+ CommitProfiler.startCapture({
87
+ detail: true
88
+ });
89
+ return {
90
+ ok: true
91
+ };
92
+ }
93
+ function endMeasurement() {
94
+ return {
95
+ summary: CommitProfiler.stopCapture()
96
+ };
97
+ }
98
+
63
99
  /**
64
100
  * Sync adapter for the highlight-updates tool, consumed by
65
101
  * @buoy-gg/external-sync's `useExternalSync` (structurally matches its
@@ -76,7 +112,9 @@ function jsonSafeRender(render) {
76
112
  * stay on the device. Dashboards fetch them via `getRenderDetail`.
77
113
  */
78
114
  export const highlightUpdatesSyncAdapter = {
79
- version: 2,
115
+ // v3: beginMeasurement / endMeasurement — a CommitProfiler capture window
116
+ // for automated callers, with per-component causes. Invisible to the user.
117
+ version: 3,
80
118
  getSnapshot: () => ({
81
119
  enabled: HighlightUpdatesController.isEnabled(),
82
120
  frozen: HighlightUpdatesController.getFrozen(),
@@ -98,6 +136,10 @@ export const highlightUpdatesSyncAdapter = {
98
136
  };
99
137
  },
100
138
  actions: {
139
+ // Capture window for automated callers (MCP `measure_renders`). Pairs:
140
+ // begin -> drive the UI -> end. `end` always restores.
141
+ beginMeasurement: () => beginMeasurement(),
142
+ endMeasurement: () => endMeasurement(),
101
143
  /**
102
144
  * On-demand detail: one component's real renderHistory + lastRenderCause
103
145
  * (including hookChanges). Wire-form snapshots omit those.
@@ -18,8 +18,18 @@
18
18
  import type { RenderCaptureSummary } from "@buoy-gg/shared-ui";
19
19
  declare function isSupported(): boolean;
20
20
  declare function isCapturing(): boolean;
21
- /** Begin a capture window, discarding any previous unclaimed data. */
22
- declare function startCapture(): void;
21
+ /**
22
+ * Begin a capture window, discarding any previous unclaimed data.
23
+ *
24
+ * `detail` turns on the allocating tier — changed prop key names and parent
25
+ * component names. Causes themselves are always classified: it costs a shallow
26
+ * prop compare, against a `computeSelfMs` that already walks every direct
27
+ * child on every render, and the wasted-render total is the number most worth
28
+ * seeing without having to know to ask for it.
29
+ */
30
+ declare function startCapture(opts?: {
31
+ detail?: boolean;
32
+ }): void;
23
33
  /** End the window and return the aggregate (null if never started). */
24
34
  declare function stopCapture(): RenderCaptureSummary | null;
25
35
  export declare const CommitProfiler: {
@@ -1 +1 @@
1
- {"version":3,"file":"CommitProfiler.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/CommitProfiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,oBAAoB,CAAC;AA6Q5B,iBAAS,WAAW,IAAI,OAAO,CAI9B;AAED,iBAAS,WAAW,IAAI,OAAO,CAE9B;AAED,sEAAsE;AACtE,iBAAS,YAAY,IAAI,IAAI,CAQ5B;AAED,uEAAuE;AACvE,iBAAS,WAAW,IAAI,oBAAoB,GAAG,IAAI,CAkClD;AAMD,eAAO,MAAM,cAAc;;;;;CAK1B,CAAC"}
1
+ {"version":3,"file":"CommitProfiler.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/CommitProfiler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,oBAAoB,CAAC;AA4a5B,iBAAS,WAAW,IAAI,OAAO,CAI9B;AAED,iBAAS,WAAW,IAAI,OAAO,CAE9B;AAED;;;;;;;;GAQG;AACH,iBAAS,YAAY,CAAC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAUvD;AAED,uEAAuE;AACvE,iBAAS,WAAW,IAAI,oBAAoB,GAAG,IAAI,CA4ClD;AAMD,eAAO,MAAM,cAAc;;;;;CAK1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightUpdatesController.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/HighlightUpdatesController.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"HighlightUpdatesController.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/HighlightUpdatesController.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,30 @@
1
1
  export declare const HIGHLIGHT_OVERLAY_COMPONENTS: Set<string>;
2
2
  export declare const HIGHLIGHT_OVERLAY_PREFIXES: string[];
3
3
  export declare const OTHER_DEV_TOOLS_COMPONENTS: Set<string>;
4
+ /**
5
+ * Other dev-tools prefixes — noise reduction, toggleable via excludeDevTools.
6
+ *
7
+ * EVERY ENTRY HERE MUST BE BUOY-NAMESPACED. These are prefix matches, and a
8
+ * match hides the component from render reports. Generic English words do not
9
+ * belong in this list, because app components share them.
10
+ *
11
+ * This list used to contain `Virtualized`, meant for Buoy's own
12
+ * VirtualizedDataExplorer. React Native's FlatList renders a `VirtualizedList`,
13
+ * so the prefix matched — and because CommitProfiler pruned the whole subtree
14
+ * on a match, Buoy could not see inside ANY FlatList or SectionList in ANY
15
+ * app. Lists are the number-one source of render problems in React Native, and
16
+ * the render tools were blind to all of them. `Dial` was the same trap:
17
+ * "Dialog".startsWith("Dial") is true, so every Dialog vanished too.
18
+ *
19
+ * Names that are generic on their own now live in OTHER_DEV_TOOLS_COMPONENTS
20
+ * as exact matches instead.
21
+ */
4
22
  export declare const OTHER_DEV_TOOLS_PREFIXES: string[];
23
+ /**
24
+ * Buoy component names that were previously matched by over-broad prefixes.
25
+ * Exact matches only — see the warning above.
26
+ */
27
+ export declare const OTHER_DEV_TOOLS_EXACT: Set<string>;
5
28
  export declare function setCaptureBuoyToolRenders(enabled: boolean): void;
6
29
  export declare function isCapturingBuoyToolRenders(): boolean;
7
30
  /**
@@ -22,4 +45,15 @@ export declare function isJsTopComponentName(name: string): boolean;
22
45
  * RN's LogBox). Sets are checked first (O(1)), then prefixes.
23
46
  */
24
47
  export declare function isDevToolsComponentName(name: string): boolean;
48
+ /**
49
+ * Is this name one whose SUBTREE must be skipped, not merely the node?
50
+ *
51
+ * Only the always-excluded group qualifies: walking Buoy's own render-tracking
52
+ * UI feeds its own renders back in and loops. Everything else in
53
+ * `isDevToolsComponentName` is noise reduction — hide the row, but keep
54
+ * walking, because app components live underneath Buoy chrome (a tool modal
55
+ * renders the host app behind it) and pruning on a NAME is how one bad prefix
56
+ * cost us every FlatList in every app.
57
+ */
58
+ export declare function isRenderLoopUnsafeName(name: string): boolean;
25
59
  //# sourceMappingURL=devtoolsComponentNames.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"devtoolsComponentNames.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/devtoolsComponentNames.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,4BAA4B,aA4BvC,CAAC;AAGH,eAAO,MAAM,0BAA0B,UAWtC,CAAC;AAGF,eAAO,MAAM,0BAA0B,aAwIrC,CAAC;AAGH,eAAO,MAAM,wBAAwB,UAwBpC,CAAC;AAcF,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AA8BD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhE;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAiB7D"}
1
+ {"version":3,"file":"devtoolsComponentNames.d.ts","sourceRoot":"","sources":["../../../../src/highlight-updates/utils/devtoolsComponentNames.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,4BAA4B,aA4BvC,CAAC;AAGH,eAAO,MAAM,0BAA0B,UAWtC,CAAC;AAGF,eAAO,MAAM,0BAA0B,aAwIrC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,wBAAwB,UASpC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,aAoBhC,CAAC;AAcH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEhE;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AA8BD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMhE;AAUD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkB7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAM5D"}
@@ -1,4 +1,5 @@
1
1
  import type { TrackedRender } from "../highlight-updates/utils/RenderTracker";
2
+ import type { RenderCaptureSummary } from "@buoy-gg/shared-ui";
2
3
  /**
3
4
  * Sync adapter for the highlight-updates tool, consumed by
4
5
  * @buoy-gg/external-sync's `useExternalSync` (structurally matches its
@@ -28,6 +29,13 @@ export declare const highlightUpdatesSyncAdapter: {
28
29
  };
29
30
  subscribe: (onChange: () => void) => () => void;
30
31
  actions: {
32
+ beginMeasurement: () => {
33
+ ok: boolean;
34
+ reason?: string;
35
+ };
36
+ endMeasurement: () => {
37
+ summary: RenderCaptureSummary | null;
38
+ };
31
39
  /**
32
40
  * On-demand detail: one component's real renderHistory + lastRenderCause
33
41
  * (including hookChanges). Wire-form snapshots omit those.
@@ -1 +1 @@
1
- {"version":3,"file":"highlightUpdatesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/highlightUpdatesSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,0CAA0C,CAAC;AAkElD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;0BAUhB,MAAM,IAAI;;QAc9B;;;WAGG;mCACwB,OAAO;;;;;;;;;;;;8BAcZ,OAAO;;;gCAYL,OAAO;mCASJ,OAAO;qCAQL,OAAO;;8BAad,OAAO;;CAEhC,CAAC"}
1
+ {"version":3,"file":"highlightUpdatesSyncAdapter.d.ts","sourceRoot":"","sources":["../../../src/sync/highlightUpdatesSyncAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,aAAa,EACd,MAAM,0CAA0C,CAAC;AAOlD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AA4F/D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,2BAA2B;;;;;;;;;;;;0BAYhB,MAAM,IAAI;;;gBA3CC,OAAO;qBAAW,MAAM;;;qBAYrB,oBAAoB,GAAG,IAAI;;QAiD7D;;;WAGG;mCACwB,OAAO;;;;;;;;;;;;8BAcZ,OAAO;;;gCAYL,OAAO;mCASJ,OAAO;qCAQL,OAAO;;8BAad,OAAO;;CAEhC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/highlight-updates",
3
- "version": "7.0.21",
3
+ "version": "7.0.22",
4
4
  "description": "Render highlighter for React Native — see every render with its cause: mount, state, props, or parent. Part of Buoy devtools.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -26,14 +26,14 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/shared-ui": "^7.0.21",
30
- "@buoy-gg/floating-tools-core": "^7.0.21"
29
+ "@buoy-gg/shared-ui": "^7.0.22",
30
+ "@buoy-gg/floating-tools-core": "^7.0.22"
31
31
  },
32
32
  "peerDependencies": {
33
- "@buoy-gg/license": "7.0.21",
33
+ "@buoy-gg/license": "7.0.22",
34
34
  "react": "*",
35
35
  "react-native": "*",
36
- "@buoy-gg/core": "^7.0.21"
36
+ "@buoy-gg/core": "^7.0.22"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "@buoy-gg/core": {
@@ -44,8 +44,8 @@
44
44
  "@types/react": "^19.1.0",
45
45
  "@types/react-native": "^0.73.0",
46
46
  "typescript": "~5.8.3",
47
- "@buoy-gg/core": "^7.0.21",
48
- "@buoy-gg/license": "^7.0.21"
47
+ "@buoy-gg/core": "^7.0.22",
48
+ "@buoy-gg/license": "^7.0.22"
49
49
  },
50
50
  "react-native-builder-bob": {
51
51
  "source": "src",