@buoy-gg/highlight-updates 5.0.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +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
37
  // State
37
38
  let globalEnabled = false; // User-controlled toggle for visual highlights
38
39
  // When true, tracking + measurement + storage all run as normal, but the
@@ -114,6 +115,10 @@ function getNativeTag(instance) {
114
115
  if (inst.canonical) {
115
116
  if (inst.canonical.__nativeTag != null) return inst.canonical.__nativeTag;
116
117
  if (inst.canonical._nativeTag != null) return inst.canonical._nativeTag;
118
+ // Modern Fabric instance handles: { node, canonical: { nativeTag, ... } }.
119
+ // Without this shape ~99% of traced nodes were dropped on New
120
+ // Architecture — highlights only appeared for tap-shaped legacy nodes.
121
+ if (inst.canonical.nativeTag != null) return inst.canonical.nativeTag;
117
122
  }
118
123
  return null;
119
124
  }
@@ -320,48 +325,12 @@ function isOurOverlayTag(nativeTag) {
320
325
  // 5. Cached property access to avoid repeated traversal
321
326
  // ============================================================================
322
327
 
323
- // Components that MUST always be excluded to prevent infinite loops.
324
- // Includes the Highlight Updates overlay AND the Events tool (which subscribes
325
- // to render callbacks via RenderTracker.onRenderEvent - tracking its renders
326
- // would create an infinite callback loop).
327
- const HIGHLIGHT_OVERLAY_COMPONENTS = new Set([
328
- // Highlight Updates overlay components
329
- "HighlightUpdatesOverlay", "HighlightUpdatesModal", "HighlightFilterView", "RenderDetailView", "RenderListItem", "RenderListItemInner", "RenderHistoryViewer", "RenderHistoryFooter", "RenderCauseBadge", "TwoLevelCauseBadge", "EnhancedCauseDisplay", "IsolatedRenderList", "IsolatedRenderListInner", "StatsDisplay", "StatsDisplayInner", "CurrentStateView", "EventStepperFooter",
330
- // Events tool components - subscribes to onRenderEvent, tracking these causes infinite loops
331
- "EventsModal", "UnifiedEventList", "UnifiedEventItem", "UnifiedEventDetail", "UnifiedEventFilters", "UnifiedEventViewer", "ReactQueryEventDetail", "EventsCopySettingsView"]);
328
+ // Component-name sets/prefixes live in devtoolsComponentNames.ts (shared with
329
+ // CommitProfiler). Native-ID sets stay here only the node walk uses them.
332
330
 
333
331
  // Native IDs that MUST always be excluded (highlight overlay)
334
332
  const HIGHLIGHT_OVERLAY_NATIVE_IDS = new Set(["highlight-updates-overlay", "__rn_buoy__highlight-modal"]);
335
333
 
336
- // Prefixes for always-excluded components (highlight overlay + events tool)
337
- const HIGHLIGHT_OVERLAY_PREFIXES = ["HighlightUpdates", "RenderList", "RenderDetail", "RenderHistory", "RenderCause", "EventStepper",
338
- // Events tool - subscribes to onRenderEvent, must always be excluded
339
- "UnifiedEvent", "EventsModal", "EventsCopy"];
340
-
341
- // Other dev tools components - can be toggled via excludeDevTools setting
342
- const OTHER_DEV_TOOLS_COMPONENTS = new Set([
343
- // React Buoy devtools components
344
- "JsModalComponent", "JsModal", "TypePicker", "PatternInput", "PatternChip", "DetectedItemsSection", "DetectedCategoryBadge", "IdentifierBadge", "CategoryBadge", "AppRenderer", "AppOverlay", "FloatingTools", "DialDevTools", "DevToolsVisibilityProvider", "AppHostProvider", "MinimizedToolsProvider",
345
- // Shared UI components used in modals
346
- "ModalHeader", "TabSelector", "SectionHeader", "DraggableHeader", "WindowControls",
347
- // Shared UI data viewer components (JSON diff viewers, etc.)
348
- "TreeDiffViewer", "DataViewer", "SplitDiffViewer", "VirtualizedDataExplorer", "DiffSummary", "TypeLegend", "IndentGuides", "IndentGuidesOverlay", "CyberpunkInput", "DiffView", "AnswerCard", "DetailsSection", "DetailRow", "QuickActionsSection", "FilterOptionCard",
349
- // Modal header sub-components
350
- "SearchSection", "SearchSectionInner", "HeaderActions", "HeaderActionsInner", "MainListHeader", "FilterViewHeader", "DetailViewHeader",
351
- // Floating menu components (minimized tools, dial, etc.)
352
- "FloatingMenu", "FloatingDevTools", "MinimizedToolsStack", "MinimizedToolsContext", "GlitchToolButton", "ExpandablePopover", "CollapsedPeek", "ExpandedWrapper", "DialIcon", "DialMenu", "DialIconItem", "OnboardingTooltip", "MenuLauncherIcon", "DevToolsSettingsModal", "SettingsModal",
353
- // Image Overlay components
354
- "ImageOverlayModal", "ImageOverlayStandalone", "ToolCard", "ToolIcon", "GripVerticalIcon", "UserStatus", "Divider",
355
- // Shared UI icon components (Chevrons, etc.)
356
- "ChevronDown", "ChevronUp", "ChevronLeft", "ChevronRight", "ChevronDownIcon", "ChevronUpIcon", "ChevronLeftIcon", "ChevronRightIcon",
357
- // React Native LogBox components (shown on reload/errors)
358
- "LogBox", "LogBoxLog", "LogBoxLogNotification", "LogBoxNotificationContainer", "_LogBoxNotificationContainer", "LogBoxInspector", "LogBoxInspectorContainer", "LogBoxInspectorHeader", "LogBoxInspectorBody", "LogBoxInspectorFooter", "LogBoxInspectorMessageHeader", "LogBoxInspectorStackFrame", "LogBoxInspectorSection", "LogBoxButton", "LogBoxMessage"]);
359
-
360
- // Other dev tools prefixes - can be toggled via excludeDevTools setting
361
- const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "TreeDiff", "DataViewer", "DiffView", "DiffSummary", "Virtualized",
362
- // Floating menu prefixes
363
- "Floating", "Minimized", "Dial", "Expandable", "Chevron", "Glitch", "Settings", "Onboarding", "DevTools", "ImageOverlay"];
364
-
365
334
  // Other dev tools native IDs - can be toggled
366
335
  const OTHER_DEV_TOOLS_NATIVE_IDS = new Set(["jsmodal-root", "image-overlay-standalone",
367
336
  // LogBox native IDs
@@ -520,8 +489,15 @@ function isOurOverlayNode(stateNode) {
520
489
  // Get nativeTag for caching
521
490
  const nativeTag = getNativeTag(stateNode) || getNativeTag(node?.canonical?.publicInstance);
522
491
 
523
- // Check if we should exclude all dev tools or just highlight overlay
524
- const excludeDevTools = RenderTracker.getSettings().excludeDevTools;
492
+ // Check if we should exclude all dev tools or just highlight overlay.
493
+ // Bench "profile our own tools" mode (the example app's /tool-bench page)
494
+ // forces this OFF so live highlight boxes flash on our tool UI too —
495
+ // except the perf-monitor HUD (checked separately in the walk below),
496
+ // whose 250ms sample-tick churn would flash constantly. Cache reads and
497
+ // writes are already gated on excludeDevTools, so bench mode bypasses
498
+ // the cache and can't poison it.
499
+ const benchMode = isCapturingBuoyToolRenders();
500
+ const excludeDevTools = !benchMode && RenderTracker.getSettings().excludeDevTools;
525
501
 
526
502
  // Check cache first (O(1)) - but only for the current excludeDevTools setting
527
503
  // When excludeDevTools is false, skip cache to re-evaluate all nodes
@@ -567,6 +543,12 @@ function isOurOverlayNode(stateNode) {
567
543
  }
568
544
  if (result) break;
569
545
 
546
+ // Bench mode: the perf-monitor HUD still never flashes.
547
+ if (benchMode && isPerfMonitorComponentName(name)) {
548
+ result = true;
549
+ break;
550
+ }
551
+
570
552
  // Only check other dev tools if excludeDevTools is enabled
571
553
  if (excludeDevTools) {
572
554
  if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) {
@@ -776,14 +758,33 @@ function handleTraceUpdates(nodes) {
776
758
  // Use batch size from RenderTracker settings (default: 150)
777
759
  // Note: batchSize already retrieved above for perfTimer
778
760
  perfTimer.markMeasurementStart();
779
- const measurePromises = nodesToDraw.slice(0, batchSize).map(({
761
+ // Measure up to batchSize: coalesced commits can exceed one batch and
762
+ // the old hard slice silently never highlighted the same tail components.
763
+ const measurePromises = nodesToDraw.slice(0, batchSize * 4).map(({
780
764
  node: stateNode,
781
765
  color,
782
766
  count
783
767
  }) => new Promise(resolve => {
768
+ // RN silently never fires measure callbacks for detached nodes;
769
+ // without this timeout one dead node hangs the whole batch's
770
+ // Promise.all (no boxes, no tracking, counts still climb).
771
+ let settled = false;
772
+ const settle = value => {
773
+ if (settled) return;
774
+ settled = true;
775
+ clearTimeout(timeoutId);
776
+ resolve(value);
777
+ };
778
+ const timeoutId = setTimeout(() => settle({
779
+ rect: null,
780
+ stateNode,
781
+ color,
782
+ count
783
+ }), 500);
784
784
  const publicInstance = getPublicInstance(stateNode);
785
- if (!publicInstance) {
786
- resolve({
785
+ const nativeTag = getNativeTag(stateNode) || getNativeTag(publicInstance);
786
+ if (nativeTag == null) {
787
+ settle({
787
788
  rect: null,
788
789
  stateNode,
789
790
  color,
@@ -791,44 +792,62 @@ function handleTraceUpdates(nodes) {
791
792
  });
792
793
  return;
793
794
  }
794
- const nativeTag = getNativeTag(stateNode) || getNativeTag(publicInstance);
795
- if (nativeTag == null) {
796
- resolve({
797
- rect: null,
795
+ const onMeasured = (x, y, width, height, pageX, pageY) => {
796
+ if (pageX == null || pageY == null || width == null || height == null) {
797
+ settle({
798
+ rect: null,
799
+ stateNode,
800
+ color,
801
+ count
802
+ });
803
+ return;
804
+ }
805
+ settle({
806
+ rect: {
807
+ id: nativeTag,
808
+ x: pageX,
809
+ y: pageY,
810
+ width,
811
+ height,
812
+ color,
813
+ count
814
+ },
798
815
  stateNode,
799
816
  color,
800
817
  count
801
818
  });
802
- return;
803
- }
804
- try {
805
- publicInstance.measure((x, y, width, height, pageX, pageY) => {
806
- if (pageX == null || pageY == null || width == null || height == null) {
807
- resolve({
819
+ };
820
+ if (!publicInstance) {
821
+ // New Architecture: canonical.publicInstance is often lazily
822
+ // ABSENT on traceUpdates wrappers measure the wrapper's `node`
823
+ // (the Fabric shadow node) directly through FabricUIManager.
824
+ const fabricNode = stateNode.node;
825
+ const fabricUI = globalThis.nativeFabricUIManager;
826
+ if (fabricNode && fabricUI?.measure) {
827
+ try {
828
+ fabricUI.measure(fabricNode, onMeasured);
829
+ } catch (error) {
830
+ settle({
808
831
  rect: null,
809
832
  stateNode,
810
833
  color,
811
834
  count
812
835
  });
813
- return;
814
836
  }
815
- resolve({
816
- rect: {
817
- id: nativeTag,
818
- x: pageX,
819
- y: pageY,
820
- width,
821
- height,
822
- color,
823
- count
824
- },
825
- stateNode,
826
- color,
827
- count
828
- });
837
+ return;
838
+ }
839
+ settle({
840
+ rect: null,
841
+ stateNode,
842
+ color,
843
+ count
829
844
  });
845
+ return;
846
+ }
847
+ try {
848
+ publicInstance.measure(onMeasured);
830
849
  } catch (error) {
831
- resolve({
850
+ settle({
832
851
  rect: null,
833
852
  stateNode,
834
853
  color,
@@ -843,119 +862,126 @@ function handleTraceUpdates(nodes) {
843
862
  // Mark measurement phase complete
844
863
  perfTimer.markMeasurementComplete(validResults.length, results.length - validResults.length);
845
864
 
846
- // Keep a live handle to each measured node so the desktop's "capture this
847
- // component" flow can re-measure (and scroll to) it on demand later.
848
- // Defensive: never let registration break the measurement/tracking batch.
849
- try {
850
- for (const r of validResults) {
851
- if (r.rect) registerTrackedNode(r.rect.id, r.stateNode);
852
- }
853
- } catch (e) {
854
- debugLog("registerTrackedNode batch failed", e);
865
+ // DRAW FIRST. With the render list open, tracking a big burst
866
+ // (store writes + cause detection + list row rebuilds) used to run
867
+ // BEFORE the highlight callback, so boxes painted ~0.7s later
868
+ // (user-timed: 1.3s list-closed vs 2s+ list-open, 2026-07-08).
869
+ // Boxes are the product — paint them, then bookkeep.
870
+ if (rects.length > 0 && highlightCallback && globalEnabled && !highlightsSuppressed) {
871
+ highlightCallback(rects);
855
872
  }
856
873
 
857
- // Track renders - behavior differs based on what's enabled:
858
- // - globalEnabled (visual tool): full tracking with storage and list updates
859
- // - backgroundTrackingEnabled only (Events tool): emit events only, no storage
860
- const settings = RenderTracker.getSettings();
861
- const trackCauses = settings.trackRenderCauses && settings.showRenderCount;
862
- let batchNativeTags = null;
863
- if (trackCauses) {
864
- batchNativeTags = new Set();
865
- for (const {
866
- rect
867
- } of validResults) {
868
- if (rect) {
869
- batchNativeTags.add(rect.id);
874
+ // Tracking/list work runs in a macrotask so React commits the boxes
875
+ // before this heavier pass starts.
876
+ setTimeout(() => {
877
+ // Keep a live handle to each measured node so the desktop's "capture this
878
+ // component" flow can re-measure (and scroll to) it on demand later.
879
+ // Defensive: never let registration break the measurement/tracking batch.
880
+ try {
881
+ for (const r of validResults) {
882
+ if (r.rect) registerTrackedNode(r.rect.id, r.stateNode);
870
883
  }
884
+ } catch (e) {
885
+ debugLog("registerTrackedNode batch failed", e);
871
886
  }
872
- }
873
- if (globalEnabled) {
874
- // Visual tool is on - full tracking with storage and list notifications
875
- RenderTracker.startBatch();
876
- for (const {
877
- rect,
878
- stateNode,
879
- color,
880
- count
881
- } of validResults) {
882
- if (rect) {
883
- const componentInfo = extractComponentInfo(stateNode);
884
- let renderCause;
885
- if (trackCauses && batchNativeTags) {
886
- const node = stateNode;
887
- const fiber = node?.canonical?.internalInstanceHandle;
888
- renderCause = detectRenderCause(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
887
+
888
+ // Track renders - behavior differs based on what's enabled:
889
+ // - globalEnabled (visual tool): full tracking with storage and list updates
890
+ // - backgroundTrackingEnabled only (Events tool): emit events only, no storage
891
+ const settings = RenderTracker.getSettings();
892
+ const trackCauses = settings.trackRenderCauses && settings.showRenderCount;
893
+ let batchNativeTags = null;
894
+ if (trackCauses) {
895
+ batchNativeTags = new Set();
896
+ for (const {
897
+ rect
898
+ } of validResults) {
899
+ if (rect) {
900
+ batchNativeTags.add(rect.id);
889
901
  }
890
- RenderTracker.trackRender({
891
- nativeTag: rect.id,
892
- viewType: componentInfo.viewType,
893
- testID: componentInfo.testID,
894
- nativeID: componentInfo.nativeID,
895
- accessibilityLabel: componentInfo.accessibilityLabel,
896
- componentName: componentInfo.componentName,
897
- measurements: {
898
- x: rect.x,
899
- y: rect.y,
900
- width: rect.width,
901
- height: rect.height
902
- },
903
- color,
904
- count,
905
- renderCause
906
- });
907
902
  }
908
903
  }
909
- RenderTracker.endBatch();
910
- } else {
911
- // Only background tracking (Events tool) - emit events without storage
912
- for (const {
913
- rect,
914
- stateNode,
915
- color,
916
- count
917
- } of validResults) {
918
- if (rect) {
919
- const componentInfo = extractComponentInfo(stateNode);
920
- let renderCause;
921
- if (trackCauses && batchNativeTags) {
922
- const node = stateNode;
923
- const fiber = node?.canonical?.internalInstanceHandle;
924
- renderCause = detectRenderCause(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
904
+ if (globalEnabled) {
905
+ // Visual tool is on - full tracking with storage and list notifications
906
+ RenderTracker.startBatch();
907
+ for (const {
908
+ rect,
909
+ stateNode,
910
+ color,
911
+ count
912
+ } of validResults) {
913
+ if (rect) {
914
+ const componentInfo = extractComponentInfo(stateNode);
915
+ let renderCause;
916
+ if (trackCauses && batchNativeTags) {
917
+ const node = stateNode;
918
+ const fiber = node?.canonical?.internalInstanceHandle;
919
+ renderCause = detectRenderCause(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
920
+ }
921
+ RenderTracker.trackRender({
922
+ nativeTag: rect.id,
923
+ viewType: componentInfo.viewType,
924
+ testID: componentInfo.testID,
925
+ nativeID: componentInfo.nativeID,
926
+ accessibilityLabel: componentInfo.accessibilityLabel,
927
+ componentName: componentInfo.componentName,
928
+ measurements: {
929
+ x: rect.x,
930
+ y: rect.y,
931
+ width: rect.width,
932
+ height: rect.height
933
+ },
934
+ color,
935
+ count,
936
+ renderCause
937
+ });
938
+ }
939
+ }
940
+ RenderTracker.endBatch();
941
+ } else {
942
+ // Only background tracking (Events tool) - emit events without storage
943
+ for (const {
944
+ rect,
945
+ stateNode,
946
+ color,
947
+ count
948
+ } of validResults) {
949
+ if (rect) {
950
+ const componentInfo = extractComponentInfo(stateNode);
951
+ let renderCause;
952
+ if (trackCauses && batchNativeTags) {
953
+ const node = stateNode;
954
+ const fiber = node?.canonical?.internalInstanceHandle;
955
+ renderCause = detectRenderCause(rect.id, fiber, batchNativeTags, settings.debugLogLevel);
956
+ }
957
+ RenderTracker.emitRenderEvent({
958
+ nativeTag: rect.id,
959
+ viewType: componentInfo.viewType,
960
+ testID: componentInfo.testID,
961
+ nativeID: componentInfo.nativeID,
962
+ accessibilityLabel: componentInfo.accessibilityLabel,
963
+ componentName: componentInfo.componentName,
964
+ measurements: {
965
+ x: rect.x,
966
+ y: rect.y,
967
+ width: rect.width,
968
+ height: rect.height
969
+ },
970
+ color,
971
+ count,
972
+ renderCause
973
+ });
925
974
  }
926
- RenderTracker.emitRenderEvent({
927
- nativeTag: rect.id,
928
- viewType: componentInfo.viewType,
929
- testID: componentInfo.testID,
930
- nativeID: componentInfo.nativeID,
931
- accessibilityLabel: componentInfo.accessibilityLabel,
932
- componentName: componentInfo.componentName,
933
- measurements: {
934
- x: rect.x,
935
- y: rect.y,
936
- width: rect.width,
937
- height: rect.height
938
- },
939
- color,
940
- count,
941
- renderCause
942
- });
943
975
  }
944
976
  }
945
- }
946
977
 
947
- // Mark tracking phase complete
948
- perfTimer.markTrackingComplete();
949
-
950
- // Only show visual highlights when the visual tool is enabled (not just
951
- // background tracking) AND not running in silent mode (screenshot tool).
952
- if (rects.length > 0 && highlightCallback && globalEnabled && !highlightsSuppressed) {
953
- highlightCallback(rects);
954
- }
978
+ // Mark tracking phase complete
979
+ perfTimer.markTrackingComplete();
955
980
 
956
- // Mark callback phase complete and finish timing
957
- perfTimer.markCallbackComplete();
958
- perfTimer.finish();
981
+ // Mark callback phase complete and finish timing
982
+ perfTimer.markCallbackComplete();
983
+ perfTimer.finish();
984
+ }, 0);
959
985
  }).catch(error => {
960
986
  console.error("[HighlightUpdates] Error in measurement pipeline:", error);
961
987
  perfTimer.finish(); // Still log timing even on error
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ // ============================================================================
4
+ // Buoy devtools component-name sets, shared by HighlightUpdatesController's
5
+ // per-node exclusion walk and CommitProfiler's subtree pruning.
6
+ //
7
+ // Extracted from HighlightUpdatesController.ts verbatim — keep the split:
8
+ // HIGHLIGHT_* sets are ALWAYS excluded (tracking them causes infinite render
9
+ // loops); OTHER_DEV_TOOLS_* sets are toggleable via the excludeDevTools
10
+ // setting in the highlight flow (CommitProfiler always excludes both).
11
+ // ============================================================================
12
+
13
+ // Components that MUST always be excluded to prevent infinite loops.
14
+ // Includes the Highlight Updates overlay AND the Events tool (which subscribes
15
+ // to render callbacks via RenderTracker.onRenderEvent - tracking its renders
16
+ // would create an infinite callback loop).
17
+ export const HIGHLIGHT_OVERLAY_COMPONENTS = new Set([
18
+ // Highlight Updates overlay components
19
+ "HighlightUpdatesOverlay", "HighlightUpdatesModal", "HighlightFilterView", "RenderDetailView", "RenderListItem", "RenderListItemInner", "RenderHistoryViewer", "RenderHistoryFooter", "RenderCauseBadge", "TwoLevelCauseBadge", "EnhancedCauseDisplay", "IsolatedRenderList", "IsolatedRenderListInner", "StatsDisplay", "StatsDisplayInner", "CurrentStateView", "EventStepperFooter",
20
+ // Events tool components - subscribes to onRenderEvent, tracking these causes infinite loops
21
+ "EventsModal", "UnifiedEventList", "UnifiedEventItem", "UnifiedEventDetail", "UnifiedEventFilters", "UnifiedEventViewer", "ReactQueryEventDetail", "EventsCopySettingsView"]);
22
+
23
+ // Prefixes for always-excluded components (highlight overlay + events tool)
24
+ export const HIGHLIGHT_OVERLAY_PREFIXES = ["HighlightUpdates", "RenderList", "RenderDetail", "RenderHistory", "RenderCause", "EventStepper",
25
+ // Events tool - subscribes to onRenderEvent, must always be excluded
26
+ "UnifiedEvent", "EventsModal", "EventsCopy"];
27
+
28
+ // Other dev tools components - can be toggled via excludeDevTools setting
29
+ export const OTHER_DEV_TOOLS_COMPONENTS = new Set([
30
+ // React Buoy devtools components
31
+ "JsModalComponent", "JsModal", "TypePicker", "PatternInput", "PatternChip", "DetectedItemsSection", "DetectedCategoryBadge", "IdentifierBadge", "CategoryBadge", "AppRenderer", "AppOverlay", "FloatingTools", "DialDevTools", "DevToolsVisibilityProvider", "AppHostProvider", "MinimizedToolsProvider",
32
+ // Shared UI components used in modals
33
+ "ModalHeader", "TabSelector", "SectionHeader", "DraggableHeader", "WindowControls",
34
+ // Shared UI data viewer components (JSON diff viewers, etc.)
35
+ "TreeDiffViewer", "DataViewer", "SplitDiffViewer", "VirtualizedDataExplorer", "DiffSummary", "TypeLegend", "IndentGuides", "IndentGuidesOverlay", "CyberpunkInput", "DiffView", "AnswerCard", "DetailsSection", "DetailRow", "QuickActionsSection", "FilterOptionCard",
36
+ // Modal header sub-components
37
+ "SearchSection", "SearchSectionInner", "HeaderActions", "HeaderActionsInner", "MainListHeader", "FilterViewHeader", "DetailViewHeader",
38
+ // Floating menu components (minimized tools, dial, etc.)
39
+ "FloatingMenu", "FloatingDevTools", "MinimizedToolsStack", "MinimizedToolsContext", "GlitchToolButton", "ExpandablePopover", "CollapsedPeek", "ExpandedWrapper", "DialIcon", "DialMenu", "DialIconItem", "OnboardingTooltip", "MenuLauncherIcon", "DevToolsSettingsModal", "SettingsModal",
40
+ // Image Overlay components
41
+ "ImageOverlayModal", "ImageOverlayStandalone", "ToolCard", "ToolIcon", "GripVerticalIcon", "UserStatus", "Divider",
42
+ // Shared UI icon components (Chevrons, etc.)
43
+ "ChevronDown", "ChevronUp", "ChevronLeft", "ChevronRight", "ChevronDownIcon", "ChevronUpIcon", "ChevronLeftIcon", "ChevronRightIcon",
44
+ // React Native LogBox components (shown on reload/errors)
45
+ "LogBox", "LogBoxLog", "LogBoxLogNotification", "LogBoxNotificationContainer", "_LogBoxNotificationContainer", "LogBoxInspector", "LogBoxInspectorContainer", "LogBoxInspectorHeader", "LogBoxInspectorBody", "LogBoxInspectorFooter", "LogBoxInspectorMessageHeader", "LogBoxInspectorStackFrame", "LogBoxInspectorSection", "LogBoxButton", "LogBoxMessage",
46
+ // Perf-monitor components. The HUD re-renders on every 250ms sample tick
47
+ // during a benchmark recording, so leaving these in would dominate any
48
+ // render-capture result. NOTE: no blanket "Perf" prefix — user benchmark
49
+ // screens are commonly named Perf* (e.g. PerfBenchHeavy in the example app).
50
+ "PerfMonitorOverlay", "PerfMonitorModal", "PerfMonitorRoot", "PerfHudInline", "PerfDiagnostics", "MetricSparkline", "JsFallbackNotice", "AutomationConfigView", "AutomationProgressOverlay", "AutomationResumer", "SaveBenchmarkPrompt", "SavedCasesModal", "BatchBarReport", "BatchReportView",
51
+ // Perf HUD INNER components. The live-highlight exclusion walk climbs at
52
+ // most 30 fiber levels from a flagged host view — the HUD's metric
53
+ // TextInputs sit under deep TextInput/Animated wrappers, so the walk can
54
+ // miss the PerfMonitorOverlay root; near names are what it actually hits.
55
+ "PerfHudInlineInner", "PerfMonitorOverlayInner", "MiniMetric", "MetricRow", "MetricNumber", "MetricSparklineInner", "RecColumn", "PillBody", "FullBody", "TargetLine", "PagesSection", "ProbesSection"]);
56
+
57
+ // Other dev tools prefixes - can be toggled via excludeDevTools setting
58
+ export const OTHER_DEV_TOOLS_PREFIXES = ["JsModal", "TreeDiff", "DataViewer", "DiffView", "DiffSummary", "Virtualized",
59
+ // Floating menu prefixes
60
+ "Floating", "Minimized", "Dial", "Expandable", "Chevron", "Glitch", "Settings", "Onboarding", "DevTools", "ImageOverlay",
61
+ // Perf-monitor prefixes (safe: user code is Perf*, buoy code is
62
+ // PerfMonitor* / PerfHud*)
63
+ "PerfMonitor", "PerfHud"];
64
+
65
+ // ── Bench "profile our own tools" mode ──────────────────────────────────
66
+ // When enabled (by a dedicated benchmark screen, e.g. the example app's
67
+ // /tool-bench page), render capture INCLUDES Buoy's tool UI so we can
68
+ // benchmark our own devtools. Two groups stay excluded regardless:
69
+ // - highlight overlay + events tool (tracking them = infinite render loop)
70
+ // - perf-monitor UI (the HUD re-renders on every 250ms sample tick while
71
+ // recording, so its renders would dominate any capture result)
72
+ // Only consulted by `isDevToolsComponentName` (the CommitProfiler path) —
73
+ // the highlight flow's own excludeDevTools walk reads the raw sets above
74
+ // and is deliberately unaffected.
75
+ let captureBuoyToolRenders = false;
76
+ export function setCaptureBuoyToolRenders(enabled) {
77
+ captureBuoyToolRenders = enabled;
78
+ }
79
+ export function isCapturingBuoyToolRenders() {
80
+ return captureBuoyToolRenders;
81
+ }
82
+
83
+ // Perf-monitor components without the "PerfMonitor" prefix (which is
84
+ // matched separately). Mirrors the perf entries in OTHER_DEV_TOOLS_*.
85
+ const PERF_MONITOR_ALWAYS_EXCLUDED = new Set(["PerfHudInline", "PerfHudInlineInner", "PerfDiagnostics", "MetricSparkline", "MetricSparklineInner", "JsFallbackNotice", "AutomationConfigView", "AutomationProgressOverlay", "AutomationResumer", "SaveBenchmarkPrompt", "SavedCasesModal", "BatchBarReport", "BatchReportView",
86
+ // HUD inner components — churn every 250ms sample tick while recording.
87
+ "MiniMetric", "MetricRow", "MetricNumber", "RecColumn", "PillBody", "FullBody", "TargetLine", "PagesSection", "ProbesSection"]);
88
+
89
+ /**
90
+ * True when a name belongs to the perf-monitor UI. These stay excluded
91
+ * even in bench "profile our own tools" mode — the HUD re-renders on
92
+ * every 250ms sample tick while recording, so both capture results and
93
+ * live highlight boxes would be dominated by it.
94
+ */
95
+ export function isPerfMonitorComponentName(name) {
96
+ return name.startsWith("PerfMonitor") || name.startsWith("PerfHud") || PERF_MONITOR_ALWAYS_EXCLUDED.has(name);
97
+ }
98
+
99
+ /**
100
+ * True when a component display name belongs to Buoy's own devtools UI (or
101
+ * RN's LogBox). Sets are checked first (O(1)), then prefixes.
102
+ */
103
+ export function isDevToolsComponentName(name) {
104
+ // Always excluded — tracking these causes infinite render loops.
105
+ if (HIGHLIGHT_OVERLAY_COMPONENTS.has(name)) return true;
106
+ for (let i = 0; i < HIGHLIGHT_OVERLAY_PREFIXES.length; i++) {
107
+ if (name.startsWith(HIGHLIGHT_OVERLAY_PREFIXES[i])) return true;
108
+ }
109
+ if (captureBuoyToolRenders) {
110
+ // Benchmarking our own tools: capture everything except the
111
+ // perf-monitor UI (its 250ms HUD churn would drown the result).
112
+ return isPerfMonitorComponentName(name);
113
+ }
114
+ if (OTHER_DEV_TOOLS_COMPONENTS.has(name)) return true;
115
+ for (let i = 0; i < OTHER_DEV_TOOLS_PREFIXES.length; i++) {
116
+ if (name.startsWith(OTHER_DEV_TOOLS_PREFIXES[i])) return true;
117
+ }
118
+ return false;
119
+ }
@@ -42,6 +42,56 @@ export { HighlightUpdatesOverlay } from "./highlight-updates/HighlightUpdatesOve
42
42
  // Profiler interceptor for debugging - captures what DevTools detects
43
43
  export { installProfilerInterceptor, uninstallProfilerInterceptor, setComparisonCallback, isInterceptorInstalled, enableProfilerLogging, disableProfilerLogging, isLoggingEnabled } from "./highlight-updates/utils/ProfilerInterceptor";
44
44
 
45
+ // CommitProfiler — per-component render counts/durations over a capture window
46
+ export { CommitProfiler } from "./highlight-updates/utils/CommitProfiler";
47
+
48
+ // Bench "profile our own tools" mode: lets a dedicated benchmark screen
49
+ // include Buoy's tool UI in render capture (normally pruned wholesale).
50
+ // Highlight/events + perf-monitor components stay excluded regardless.
51
+ export { setCaptureBuoyToolRenders, isCapturingBuoyToolRenders } from "./highlight-updates/utils/devtoolsComponentNames";
52
+
53
+ // Register the render-capture provider so perf-monitor benchmarks can capture
54
+ // renders without depending on this package. Runs at module scope: whenever
55
+ // this package is installed, FloatingDevTools require()s it (including on the
56
+ // fresh realm after a reload-between-cases batch step), and Metro evaluates
57
+ // module side effects regardless of the package.json "sideEffects" hint.
58
+ import { setRenderCaptureProvider } from "@buoy-gg/shared-ui";
59
+ import { CommitProfiler as _commitProfilerForRegistry } from "./highlight-updates/utils/CommitProfiler";
60
+ import _controllerForRegistry from "./highlight-updates/utils/HighlightUpdatesController";
61
+ try {
62
+ setRenderCaptureProvider({
63
+ isSupported: _commitProfilerForRegistry.isSupported,
64
+ start: _commitProfilerForRegistry.startCapture,
65
+ stop: _commitProfilerForRegistry.stopCapture,
66
+ // Real-time bounding boxes (the Render Highlighter visual) for the span
67
+ // of a recording. Saves the user's prior overlay state and hands back a
68
+ // restore fn: previously ON stays ON, silent tracking (events tool) gets
69
+ // re-suppressed, otherwise fully disabled again.
70
+ startLiveHighlights: () => {
71
+ try {
72
+ const wasEnabled = _controllerForRegistry.isEnabled();
73
+ const wasSuppressed = _controllerForRegistry.getHighlightsSuppressed();
74
+ _controllerForRegistry.enable(); // idempotent; clears suppression
75
+ return () => {
76
+ try {
77
+ if (!wasEnabled) {
78
+ _controllerForRegistry.disable(); // respects background tracking
79
+ } else if (wasSuppressed) {
80
+ _controllerForRegistry.setHighlightsSuppressed(true);
81
+ }
82
+ } catch {
83
+ // restoring the overlay must never throw into the recorder
84
+ }
85
+ };
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ });
91
+ } catch {
92
+ // shared-ui too old to have the registry — capture is simply unavailable.
93
+ }
94
+
45
95
  // RenderTracker singleton for tracking render history
46
96
  export { RenderTracker } from "./highlight-updates/utils/RenderTracker";
47
97
  // Convenience export for unified events integration
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightUpdatesOverlay.d.ts","sourceRoot":"","sources":["../../../src/highlight-updates/HighlightUpdatesOverlay.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAMN,MAAM,OAAO,CAAC;AA2Bf,UAAU,4BAA4B;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,uBAAuB,CAAC,EACtC,YAAY,GACb,GAAE,4BAAiC,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CA0P/D;AA2DD,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"HighlightUpdatesOverlay.d.ts","sourceRoot":"","sources":["../../../src/highlight-updates/HighlightUpdatesOverlay.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAMN,MAAM,OAAO,CAAC;AA2Bf,UAAU,4BAA4B;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,wBAAgB,uBAAuB,CAAC,EACtC,YAAY,GACb,GAAE,4BAAiC,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAwP/D;AA2DD,eAAe,uBAAuB,CAAC"}