@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.
- package/README.md +29 -178
- package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +11 -15
- package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +279 -0
- package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +200 -174
- package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +127 -0
- package/lib/commonjs/index.js +67 -0
- package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +12 -16
- package/lib/module/highlight-updates/utils/CommitProfiler.js +276 -0
- package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +196 -170
- package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +119 -0
- package/lib/module/index.js +50 -0
- package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts +32 -0
- package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -0
- package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -1
- package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts +19 -0
- package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +8 -8
|
@@ -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
|
-
//
|
|
324
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
761
|
+
// Measure up to 4× 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
|
-
|
|
786
|
-
|
|
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
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
847
|
-
//
|
|
848
|
-
//
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
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
|
-
//
|
|
858
|
-
//
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
874
|
-
//
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
948
|
-
|
|
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
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
+
}
|
package/lib/module/index.js
CHANGED
|
@@ -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,
|
|
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"}
|