@buoy-gg/highlight-updates 7.0.38 → 7.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +281 -1
  2. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1474 -1
  3. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +641 -1
  4. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +273 -1
  5. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +188 -1
  6. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +293 -1
  7. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +508 -1
  8. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +846 -1
  9. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +915 -1
  10. package/lib/commonjs/highlight-updates/components/RenderListItem.js +235 -1
  11. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +469 -1
  12. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +73 -1
  13. package/lib/commonjs/highlight-updates/types/copySettings.js +107 -1
  14. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +446 -1
  15. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1920 -1
  16. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +359 -1
  17. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +371 -1
  18. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1840 -1
  19. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +952 -1
  20. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +264 -1
  21. package/lib/commonjs/highlight-updates/utils/copySettingsStorage.js +32 -1
  22. package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +188 -1
  23. package/lib/commonjs/highlight-updates/utils/namedComposite.js +41 -1
  24. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +103 -1
  25. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.js +50 -1
  26. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +58 -1
  27. package/lib/commonjs/highlight-updates/utils/rendersExportFormatter.js +485 -1
  28. package/lib/commonjs/highlight-updates/utils/screenElements.js +1248 -1
  29. package/lib/commonjs/highlight-updates/utils/tapMatch.js +83 -1
  30. package/lib/commonjs/highlight-updates/utils/testIDs.js +68 -1
  31. package/lib/commonjs/highlight-updates/utils/touchCapture.js +277 -1
  32. package/lib/commonjs/index.js +429 -1
  33. package/lib/commonjs/preset.js +278 -1
  34. package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +235 -1
  35. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +274 -1
  36. package/lib/module/highlight-updates/components/HighlightFilterView.js +1468 -1
  37. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +634 -1
  38. package/lib/module/highlight-updates/components/IdentifierBadge.js +265 -1
  39. package/lib/module/highlight-updates/components/IsolatedRenderList.js +183 -1
  40. package/lib/module/highlight-updates/components/ModalHeaderContent.js +288 -1
  41. package/lib/module/highlight-updates/components/RenderCauseBadge.js +499 -1
  42. package/lib/module/highlight-updates/components/RenderDetailView.js +840 -1
  43. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +908 -1
  44. package/lib/module/highlight-updates/components/RenderListItem.js +230 -1
  45. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +465 -1
  46. package/lib/module/highlight-updates/components/StatsDisplay.js +70 -1
  47. package/lib/module/highlight-updates/types/copySettings.js +102 -1
  48. package/lib/module/highlight-updates/utils/CommitProfiler.js +442 -1
  49. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1916 -1
  50. package/lib/module/highlight-updates/utils/PerformanceLogger.js +353 -1
  51. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +358 -1
  52. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1830 -1
  53. package/lib/module/highlight-updates/utils/RenderTracker.js +949 -1
  54. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +255 -1
  55. package/lib/module/highlight-updates/utils/copySettingsStorage.js +26 -1
  56. package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +178 -1
  57. package/lib/module/highlight-updates/utils/namedComposite.js +36 -1
  58. package/lib/module/highlight-updates/utils/ownStateChanged.js +98 -1
  59. package/lib/module/highlight-updates/utils/rawEventEmitter.js +46 -1
  60. package/lib/module/highlight-updates/utils/renderExportFormatter.js +54 -1
  61. package/lib/module/highlight-updates/utils/rendersExportFormatter.js +478 -1
  62. package/lib/module/highlight-updates/utils/screenElements.js +1242 -1
  63. package/lib/module/highlight-updates/utils/tapMatch.js +76 -1
  64. package/lib/module/highlight-updates/utils/testIDs.js +60 -1
  65. package/lib/module/highlight-updates/utils/touchCapture.js +270 -1
  66. package/lib/module/index.js +130 -1
  67. package/lib/module/preset.js +272 -1
  68. package/lib/module/sync/highlightUpdatesSyncAdapter.js +230 -1
  69. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -0
  70. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -0
  71. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -0
  72. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -0
  73. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -0
  74. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -0
  75. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -0
  76. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -0
  77. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -0
  78. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -0
  79. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +1 -0
  80. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -0
  81. package/lib/typescript/highlight-updates/types/copySettings.d.ts.map +1 -0
  82. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -0
  83. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -0
  84. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +1 -0
  85. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +1 -0
  86. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -0
  87. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -0
  88. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +1 -0
  89. package/lib/typescript/highlight-updates/utils/copySettingsStorage.d.ts.map +1 -0
  90. package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +1 -0
  91. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +1 -0
  92. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +1 -0
  93. package/lib/typescript/highlight-updates/utils/rawEventEmitter.d.ts.map +1 -0
  94. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +1 -0
  95. package/lib/typescript/highlight-updates/utils/rendersExportFormatter.d.ts.map +1 -0
  96. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +1 -0
  97. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +1 -0
  98. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +1 -0
  99. package/lib/typescript/highlight-updates/utils/touchCapture.d.ts.map +1 -0
  100. package/lib/typescript/index.d.ts.map +1 -0
  101. package/lib/typescript/preset.d.ts.map +1 -0
  102. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +1 -0
  103. package/package.json +7 -7
@@ -1 +1,840 @@
1
- "use strict";import React,{useMemo,useState,useCallback}from"react";import{View,Text,TouchableOpacity,ScrollView,StyleSheet}from"react-native";import{CopyButton,formatRelativeTime,PlusIcon,MinusIcon,EventStepperFooter,Lock,night,useLimit,monoFont}from"@buoy-gg/shared-ui";import{TreeDiffViewer}from"@buoy-gg/shared-ui/dataViewer";import{CAUSE_CONFIG,COMPONENT_CAUSE_CONFIG}from"./RenderCauseBadge";import{generateSingleComponentExport}from"../utils/rendersExportFormatter";import{jsx as _jsx,jsxs as _jsxs}from"react/jsx-runtime";export function RenderDetailView({render:e,disableInternalFooter:t=!1,selectedEventIndex:i,onEventIndexChange:o,onAddFilter:l,isPro:r=!1}){const[s,n]=useState(0),a=i??s,c=o??n,d=useMemo(()=>e.renderHistory&&0!==e.renderHistory.length?[...e.renderHistory].sort((e,t)=>e.timestamp-t.timestamp):[],[e.renderHistory]),h=d.length,u=d[a],g=useLimit("highlightUpdates.detailEvents"),y=r?1/0:g,x=useMemo(()=>Math.max(0,h-y),[y,h]),b=u?.cause||e.lastRenderCause,p=useCallback(()=>{a>0&&c(a-1)},[a,c]),m=useCallback(()=>{const e=Math.min(y-1,h-1);a<e&&c(a+1)},[a,h,c,y]),f=useMemo(()=>generateSingleComponentExport(e),[e]),C=e.componentName||e.displayName,w=e.viewType,j=e.lastRenderTime-e.firstRenderTime,I=j>0?(e.renderCount/(j/1e3)).toFixed(1):e.renderCount.toString(),T=!t&&h>1;return _jsxs(View,{style:styles.container,children:[_jsxs(View,{style:styles.header,children:[_jsxs(View,{style:styles.headerLeft,children:[_jsx(Text,{testID:"buoy-highlight-detail-name",style:styles.componentName,numberOfLines:1,children:C}),_jsx(View,{style:styles.nativeTypeBadge,children:_jsx(Text,{testID:"buoy-highlight-detail-type",style:styles.nativeTypeText,children:w})})]}),_jsx(CopyButton,{value:f,size:14,testID:"buoy-highlight-detail-copy"})]}),_jsxs(ScrollView,{testID:"buoy-highlight-detail-scroll",style:styles.scrollView,contentContainerStyle:[styles.scrollContent,T&&{paddingBottom:100}],showsVerticalScrollIndicator:!1,children:[b&&_jsx(AnswerCard,{cause:b,renderNumber:u?.renderNumber}),_jsx(DetailsSection,{render:e,rendersPerSec:I}),x>0&&_jsxs(View,{testID:"buoy-highlight-detail-locked",style:styles.lockedBanner,children:[_jsx(Lock,{size:14,color:night.color.warning}),_jsxs(Text,{testID:"buoy-highlight-detail-locked-text",style:styles.lockedBannerText,children:[x," ",1===x?"event":"events"," hidden"]}),_jsx(Text,{style:styles.lockedBannerSubtext,children:"Upgrade to Pro"})]}),l&&_jsx(QuickActionsSection,{render:e,onAddFilter:l})]}),T&&_jsx(EventStepperFooter,{currentIndex:a,totalItems:h,onPrevious:p,onNext:m,itemLabel:"Render",subtitle:u?.timestamp?formatRelativeTime(new Date(u.timestamp)):void 0,applySafeAreaInset:!1,absolute:!0})]})}function AnswerCard({cause:e,renderNumber:t}){const i=e.componentCause||e.type,o=e.componentCause?COMPONENT_CAUSE_CONFIG[e.componentCause]:CAUSE_CONFIG[e.type],l=e.hookChanges&&e.hookChanges.length>0,r=e.changedKeys&&e.changedKeys.length>0;return _jsxs(View,{testID:"buoy-highlight-detail-cause",style:styles.answerCard,children:[_jsxs(View,{style:styles.causeBadgeRow,children:[_jsx(Text,{testID:"buoy-highlight-detail-cause-label",style:styles.causeLabel,children:"mount"===e.type?"First render":"parent"===i?"Triggered by":"Changed:"}),_jsx(View,{style:[styles.causeBadgeLarge,{backgroundColor:o.color+"20"}],children:_jsx(Text,{testID:"buoy-highlight-detail-cause-badge",style:[styles.causeBadgeLargeText,{color:o.color}],children:o.label.toUpperCase()})})]}),l&&_jsx(View,{style:styles.hookChangesContainer,children:e.hookChanges.map((e,t)=>{const i=`${e.type}[${e.index}]`,o={[i]:e.previousValue},l={[i]:e.currentValue};return _jsx(View,{style:styles.hookDiffContainer,children:_jsx(TreeDiffViewer,{oldValue:o,newValue:l,theme:"dark",showUnchanged:!1})},t)})}),!l&&r&&_jsx(View,{style:styles.propsChangesContainer,children:e.changedKeys.filter(e=>!e.includes("(ref only)")).slice(0,5).map((e,t)=>_jsx(View,{style:styles.propChangeChip,children:_jsx(Text,{testID:`buoy-highlight-detail-changed-${t}`,style:styles.propChangeText,children:e})},t))})]})}function DetailsSection({render:e,rendersPerSec:t}){const i=e.testID||e.nativeID||e.accessibilityLabel,o=e.measurements;return _jsxs(View,{style:styles.detailsSection,children:[i&&_jsxs(View,{testID:"buoy-highlight-detail-identifiers",style:styles.detailsCard,children:[_jsx(Text,{style:styles.detailsCardTitle,children:"Identifiers"}),e.testID&&_jsx(DetailRow,{label:"testID",value:e.testID}),e.nativeID&&_jsx(DetailRow,{label:"nativeID",value:e.nativeID}),e.accessibilityLabel&&_jsx(DetailRow,{label:"a11y",value:e.accessibilityLabel})]}),_jsxs(View,{testID:"buoy-highlight-detail-stats",style:styles.statsRow,children:[_jsxs(View,{testID:"buoy-highlight-detail-stat-renders",style:styles.statItem,children:[_jsx(Text,{testID:"buoy-highlight-detail-stat-renders-value",style:[styles.statValue,{color:e.color}],children:e.renderCount}),_jsx(Text,{style:styles.statLabel,children:"renders"})]}),_jsx(View,{style:styles.statDivider}),_jsxs(View,{testID:"buoy-highlight-detail-stat-rate",style:styles.statItem,children:[_jsx(Text,{testID:"buoy-highlight-detail-stat-rate-value",style:styles.statValue,children:t}),_jsx(Text,{style:styles.statLabel,children:"/sec"})]}),_jsx(View,{style:styles.statDivider}),_jsxs(View,{testID:"buoy-highlight-detail-stat-tag",style:styles.statItem,children:[_jsx(Text,{testID:"buoy-highlight-detail-stat-tag-value",style:styles.statValue,children:e.nativeTag}),_jsx(Text,{style:styles.statLabel,children:"tag"})]})]}),o&&_jsxs(View,{testID:"buoy-highlight-detail-measure",style:styles.measurementsRow,children:[_jsxs(View,{style:styles.measurementItem,children:[_jsx(Text,{style:styles.measurementLabel,children:"x"}),_jsx(Text,{testID:"buoy-highlight-detail-measure-x",style:styles.measurementValue,children:Math.round(e.measurements.x)})]}),_jsxs(View,{style:styles.measurementItem,children:[_jsx(Text,{style:styles.measurementLabel,children:"y"}),_jsx(Text,{testID:"buoy-highlight-detail-measure-y",style:styles.measurementValue,children:Math.round(e.measurements.y)})]}),_jsxs(View,{style:styles.measurementItem,children:[_jsx(Text,{style:styles.measurementLabel,children:"w"}),_jsx(Text,{testID:"buoy-highlight-detail-measure-w",style:styles.measurementValue,children:Math.round(e.measurements.width)})]}),_jsxs(View,{style:styles.measurementItem,children:[_jsx(Text,{style:styles.measurementLabel,children:"h"}),_jsx(Text,{testID:"buoy-highlight-detail-measure-h",style:styles.measurementValue,children:Math.round(e.measurements.height)})]})]})]})}function DetailRow({label:e,value:t}){const i=`buoy-highlight-detail-id-${e.toLowerCase()}`;return _jsxs(View,{testID:i,style:styles.detailRow,children:[_jsx(Text,{style:styles.detailLabel,children:e}),_jsx(Text,{testID:`${i}-value`,style:styles.detailValue,numberOfLines:1,children:t})]})}function QuickActionsSection({render:e,onAddFilter:t}){const[i,o]=useState(null),l=useMemo(()=>{const t=[];return e.nativeID&&t.push({type:"nativeID",value:e.nativeID,label:"nativeID"}),e.testID&&t.push({type:"testID",value:e.testID,label:"testID"}),e.accessibilityLabel&&t.push({type:"accessibilityLabel",value:e.accessibilityLabel,label:"a11y"}),e.componentName&&t.push({type:"component",value:e.componentName,label:"component"}),t.push({type:"viewType",value:e.viewType,label:"viewType"}),t},[e]),r=null!==i?l[i]:null,s=useCallback(e=>{o(t=>t===e?null:e)},[]),n=useCallback(()=>{r&&(t({type:r.type,value:r.value},"include"),o(null))},[r,t]),a=useCallback(()=>{r&&(t({type:r.type,value:r.value},"exclude"),o(null))},[r,t]);return _jsxs(View,{testID:"buoy-highlight-quick",style:styles.quickActionsSection,children:[_jsx(Text,{style:styles.quickActionsTitle,children:"Quick Filters"}),_jsx(View,{style:styles.filterOptionsList,children:l.map((e,t)=>_jsx(FilterOptionCard,{testID:`buoy-highlight-quick-${e.type.toLowerCase()}`,label:e.label,value:e.value,isSelected:i===t,onSelect:()=>s(t)},e.type))}),_jsxs(View,{style:styles.filterActionButtons,children:[_jsxs(TouchableOpacity,{testID:"buoy-highlight-quick-include",accessibilityRole:"button",accessibilityLabel:"Only show this",style:[styles.filterActionButton,styles.filterActionButtonInclude,!r&&styles.filterActionButtonDisabled],onPress:n,disabled:!r,activeOpacity:.7,children:[_jsx(PlusIcon,{size:14,color:r?night.color.accent:night.color.textTertiary}),_jsx(Text,{style:[styles.filterActionButtonText,{color:r?night.color.accent:night.color.textTertiary}],children:"Only Show This"})]}),_jsxs(TouchableOpacity,{testID:"buoy-highlight-quick-exclude",accessibilityRole:"button",accessibilityLabel:"Hide this",style:[styles.filterActionButton,styles.filterActionButtonExclude,!r&&styles.filterActionButtonDisabled],onPress:a,disabled:!r,activeOpacity:.7,children:[_jsx(MinusIcon,{size:14,color:r?night.color.danger:night.color.textTertiary}),_jsx(Text,{style:[styles.filterActionButtonText,{color:r?night.color.danger:night.color.textTertiary}],children:"Hide This"})]})]})]})}function FilterOptionCard({testID:e,label:t,value:i,isSelected:o,onSelect:l}){return _jsxs(TouchableOpacity,{testID:e,accessibilityRole:"button",accessibilityLabel:`${t}, ${i}`,style:[styles.filterOptionCard,o&&styles.filterOptionCardSelected],onPress:l,activeOpacity:.7,children:[_jsx(Text,{style:[styles.filterOptionLabel,o&&styles.filterOptionLabelSelected],children:t}),_jsx(Text,{testID:e?`${e}-value`:void 0,style:[styles.filterOptionValue,o&&styles.filterOptionValueSelected],numberOfLines:1,children:i})]})}const styles=StyleSheet.create({container:{flex:1,backgroundColor:"transparent"},header:{flexDirection:"row",alignItems:"center",justifyContent:"center",gap:12,padding:16,paddingBottom:0},scrollView:{flex:1},scrollContent:{padding:16,paddingTop:12,paddingBottom:80,gap:12},headerLeft:{flex:1,flexDirection:"row",alignItems:"center",justifyContent:"center",gap:10},componentName:{fontSize:18,fontWeight:"700",color:night.color.text,flexShrink:1},nativeTypeBadge:{backgroundColor:night.color.bg,paddingVertical:3,paddingHorizontal:8,borderRadius:4},nativeTypeText:{fontSize:11,fontWeight:"600",color:night.color.textTertiary,fontFamily:monoFont},answerCard:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:12,gap:6},causeBadgeRow:{flexDirection:"row",alignItems:"center",gap:8},causeBadgeLarge:{paddingVertical:4,paddingHorizontal:10,borderRadius:4},causeBadgeLargeText:{fontSize:11,fontWeight:"700",letterSpacing:.5},causeLabel:{fontSize:12,color:night.color.textSecondary,fontWeight:"500"},hookChangesContainer:{gap:6},hookDiffContainer:{backgroundColor:night.color.bg,borderRadius:6,overflow:"hidden"},propsChangesContainer:{flexDirection:"row",flexWrap:"wrap",gap:4},propChangeChip:{backgroundColor:night.color.bg,paddingVertical:2,paddingHorizontal:6,borderRadius:4},propChangeText:{fontSize:11,color:night.color.text,fontFamily:monoFont},detailsSection:{gap:10},detailsCard:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:12,gap:6},detailsCardTitle:{fontSize:10,fontWeight:"600",color:night.color.textTertiary,letterSpacing:.5,textTransform:"uppercase",marginBottom:4},detailRow:{flexDirection:"row",alignItems:"center",gap:8},detailLabel:{fontSize:11,fontWeight:"600",color:night.color.textSecondary,minWidth:55},detailValue:{fontSize:12,color:night.color.text,fontFamily:monoFont,flex:1},statsRow:{flexDirection:"row",alignItems:"center",justifyContent:"center",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,paddingVertical:10,paddingHorizontal:16},statItem:{flex:1,alignItems:"center"},statValue:{fontSize:16,fontWeight:"700",color:night.color.text,fontFamily:monoFont},statLabel:{fontSize:10,color:night.color.textTertiary,marginTop:2},statDivider:{width:1,height:24,backgroundColor:night.color.border,marginHorizontal:12},measurementsRow:{flexDirection:"row",alignItems:"center",justifyContent:"space-around",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,paddingVertical:8,paddingHorizontal:12},measurementItem:{flexDirection:"row",alignItems:"baseline",gap:4},measurementLabel:{fontSize:10,fontWeight:"600",color:night.color.textTertiary,textTransform:"uppercase"},measurementValue:{fontSize:12,fontWeight:"600",color:night.color.text,fontFamily:monoFont},quickActionsSection:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:12,gap:10},quickActionsTitle:{fontSize:10,fontWeight:"600",color:night.color.textTertiary,letterSpacing:.5,textTransform:"uppercase",marginBottom:2},filterOptionsList:{gap:6},filterOptionCard:{flexDirection:"row",alignItems:"center",backgroundColor:night.color.surfaceElevated,borderRadius:6,paddingVertical:10,paddingHorizontal:12,borderWidth:1,borderColor:"transparent",gap:10},filterOptionCardSelected:{borderColor:night.color.accentBorderStrong,backgroundColor:night.color.accentSoft},filterOptionLabel:{fontSize:10,fontWeight:"600",color:night.color.textTertiary,minWidth:70},filterOptionLabelSelected:{color:night.color.accent},filterOptionValue:{fontSize:12,color:night.color.text,fontFamily:monoFont,flex:1},filterOptionValueSelected:{color:night.color.text},filterActionButtons:{flexDirection:"row",gap:8,marginTop:12},filterActionButton:{flexDirection:"row",alignItems:"center",justifyContent:"center",gap:4,paddingVertical:8,paddingHorizontal:10,borderRadius:6,borderWidth:1},filterActionButtonInclude:{backgroundColor:night.color.accentSoft,borderColor:night.color.accentBorderStrong},filterActionButtonExclude:{backgroundColor:night.color.dangerSoft,borderColor:night.color.danger+"40"},filterActionButtonDisabled:{backgroundColor:night.color.surfaceElevated,borderColor:night.color.border,opacity:.5},filterActionButtonText:{fontSize:11,fontWeight:"600"},lockedBanner:{flexDirection:"row",alignItems:"center",justifyContent:"center",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.warning+"40",paddingVertical:12,paddingHorizontal:16,gap:8},lockedBannerText:{color:night.color.warning,fontSize:13,fontWeight:"600"},lockedBannerSubtext:{color:night.color.textTertiary,fontSize:12}});export default RenderDetailView;
1
+ "use strict";
2
+
3
+ /**
4
+ * RenderDetailView
5
+ *
6
+ * Minimal, glanceable view for render details.
7
+ * Design principle: Dev should understand WHY in 3 seconds.
8
+ *
9
+ * Layout:
10
+ * - Header: Component name + native type
11
+ * - Answer Card: Cause badges + hook change (THE answer)
12
+ * - History Row: Compact inline navigation
13
+ */
14
+
15
+ import React, { useMemo, useState, useCallback } from "react";
16
+ import { View, Text, TouchableOpacity, ScrollView, StyleSheet } from "react-native";
17
+ import { CopyButton, formatRelativeTime, PlusIcon, MinusIcon, EventStepperFooter, Lock, night, useLimit, monoFont } from "@buoy-gg/shared-ui";
18
+ import { TreeDiffViewer } from "@buoy-gg/shared-ui/dataViewer";
19
+ import { CAUSE_CONFIG, COMPONENT_CAUSE_CONFIG } from "./RenderCauseBadge";
20
+ import { generateSingleComponentExport } from "../utils/rendersExportFormatter";
21
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22
+ // Free tier limit for render history events
23
+ export function RenderDetailView({
24
+ render,
25
+ disableInternalFooter = false,
26
+ selectedEventIndex: externalIndex,
27
+ onEventIndexChange: externalOnChange,
28
+ onAddFilter,
29
+ isPro = false
30
+ }) {
31
+ // Internal state for event index when not controlled externally
32
+ const [internalIndex, setInternalIndex] = useState(0);
33
+
34
+ // Use external or internal state
35
+ const selectedEventIndex = externalIndex ?? internalIndex;
36
+ const onEventIndexChange = externalOnChange ?? setInternalIndex;
37
+
38
+ // Get events sorted by timestamp (oldest first)
39
+ const events = useMemo(() => {
40
+ if (!render.renderHistory || render.renderHistory.length === 0) {
41
+ return [];
42
+ }
43
+ return [...render.renderHistory].sort((a, b) => a.timestamp - b.timestamp);
44
+ }, [render.renderHistory]);
45
+ const totalEvents = events.length;
46
+ const currentEvent = events[selectedEventIndex];
47
+
48
+ // `isPro` stays a prop so callers can force the unlimited view; the tier cap
49
+ // applies otherwise. useLimit() already returns Infinity for Pro, so the two
50
+ // agree — this only preserves the prop contract for cross-package callers.
51
+ const tierLimit = useLimit("highlightUpdates.detailEvents");
52
+ const eventLimit = isPro ? Infinity : tierLimit;
53
+ const hiddenEventCount = useMemo(() => Math.max(0, totalEvents - eventLimit), [eventLimit, totalEvents]);
54
+
55
+ // Use current event's cause if available, otherwise fall back to lastRenderCause
56
+ const displayCause = currentEvent?.cause || render.lastRenderCause;
57
+
58
+ // Navigation handlers
59
+ const goToPrevious = useCallback(() => {
60
+ if (selectedEventIndex > 0) {
61
+ onEventIndexChange(selectedEventIndex - 1);
62
+ }
63
+ }, [selectedEventIndex, onEventIndexChange]);
64
+ const goToNext = useCallback(() => {
65
+ // Infinity - 1 is Infinity, so an unlimited tier just gets totalEvents - 1.
66
+ const maxIndex = Math.min(eventLimit - 1, totalEvents - 1);
67
+ if (selectedEventIndex < maxIndex) {
68
+ onEventIndexChange(selectedEventIndex + 1);
69
+ }
70
+ }, [selectedEventIndex, totalEvents, onEventIndexChange, eventLimit]);
71
+
72
+ // Memoize copy data
73
+ const copyData = useMemo(() => generateSingleComponentExport(render), [render]);
74
+
75
+ // Get component name (prefer componentName, fall back to displayName)
76
+ const componentName = render.componentName || render.displayName;
77
+ const nativeType = render.viewType;
78
+
79
+ // Calculate render stats
80
+ const renderDuration = render.lastRenderTime - render.firstRenderTime;
81
+ const rendersPerSec = renderDuration > 0 ? (render.renderCount / (renderDuration / 1000)).toFixed(1) : render.renderCount.toString();
82
+
83
+ // Determine if we should show footer (either history or filters)
84
+ const showHistoryFooter = !disableInternalFooter && totalEvents > 1;
85
+ return /*#__PURE__*/_jsxs(View, {
86
+ style: styles.container,
87
+ children: [/*#__PURE__*/_jsxs(View, {
88
+ style: styles.header,
89
+ children: [/*#__PURE__*/_jsxs(View, {
90
+ style: styles.headerLeft,
91
+ children: [/*#__PURE__*/_jsx(Text, {
92
+ testID: "buoy-highlight-detail-name",
93
+ style: styles.componentName,
94
+ numberOfLines: 1,
95
+ children: componentName
96
+ }), /*#__PURE__*/_jsx(View, {
97
+ style: styles.nativeTypeBadge,
98
+ children: /*#__PURE__*/_jsx(Text, {
99
+ testID: "buoy-highlight-detail-type",
100
+ style: styles.nativeTypeText,
101
+ children: nativeType
102
+ })
103
+ })]
104
+ }), /*#__PURE__*/_jsx(CopyButton, {
105
+ value: copyData,
106
+ size: 14,
107
+ testID: "buoy-highlight-detail-copy"
108
+ })]
109
+ }), /*#__PURE__*/_jsxs(ScrollView, {
110
+ testID: "buoy-highlight-detail-scroll",
111
+ style: styles.scrollView,
112
+ contentContainerStyle: [styles.scrollContent, showHistoryFooter && {
113
+ paddingBottom: 100
114
+ }],
115
+ showsVerticalScrollIndicator: false,
116
+ children: [displayCause && /*#__PURE__*/_jsx(AnswerCard, {
117
+ cause: displayCause,
118
+ renderNumber: currentEvent?.renderNumber
119
+ }), /*#__PURE__*/_jsx(DetailsSection, {
120
+ render: render,
121
+ rendersPerSec: rendersPerSec
122
+ }), hiddenEventCount > 0 && /*#__PURE__*/_jsxs(View, {
123
+ testID: "buoy-highlight-detail-locked",
124
+ style: styles.lockedBanner,
125
+ children: [/*#__PURE__*/_jsx(Lock, {
126
+ size: 14,
127
+ color: night.color.warning
128
+ }), /*#__PURE__*/_jsxs(Text, {
129
+ testID: "buoy-highlight-detail-locked-text",
130
+ style: styles.lockedBannerText,
131
+ children: [hiddenEventCount, " ", hiddenEventCount === 1 ? 'event' : 'events', " hidden"]
132
+ }), /*#__PURE__*/_jsx(Text, {
133
+ style: styles.lockedBannerSubtext,
134
+ children: "Upgrade to Pro"
135
+ })]
136
+ }), onAddFilter && /*#__PURE__*/_jsx(QuickActionsSection, {
137
+ render: render,
138
+ onAddFilter: onAddFilter
139
+ })]
140
+ }), showHistoryFooter && /*#__PURE__*/_jsx(EventStepperFooter, {
141
+ currentIndex: selectedEventIndex,
142
+ totalItems: totalEvents,
143
+ onPrevious: goToPrevious,
144
+ onNext: goToNext,
145
+ itemLabel: "Render",
146
+ subtitle: currentEvent?.timestamp ? formatRelativeTime(new Date(currentEvent.timestamp)) : undefined,
147
+ applySafeAreaInset: false,
148
+ absolute: true
149
+ })]
150
+ });
151
+ }
152
+
153
+ /**
154
+ * AnswerCard - The hero section showing WHY the component rendered
155
+ * Design: Single badge + what changed below it
156
+ */
157
+ function AnswerCard({
158
+ cause,
159
+ renderNumber
160
+ }) {
161
+ // Use component cause if available, otherwise native cause
162
+ const displayCauseType = cause.componentCause || cause.type;
163
+ const config = cause.componentCause ? COMPONENT_CAUSE_CONFIG[cause.componentCause] : CAUSE_CONFIG[cause.type];
164
+ const hasHookChanges = cause.hookChanges && cause.hookChanges.length > 0;
165
+ const hasChangedKeys = cause.changedKeys && cause.changedKeys.length > 0;
166
+
167
+ // Get contextual label based on cause type
168
+ const getLabel = () => {
169
+ if (cause.type === "mount") return "First render";
170
+ if (displayCauseType === "parent") return "Triggered by";
171
+ return "Changed:";
172
+ };
173
+ return /*#__PURE__*/_jsxs(View, {
174
+ testID: "buoy-highlight-detail-cause",
175
+ style: styles.answerCard,
176
+ children: [/*#__PURE__*/_jsxs(View, {
177
+ style: styles.causeBadgeRow,
178
+ children: [/*#__PURE__*/_jsx(Text, {
179
+ testID: "buoy-highlight-detail-cause-label",
180
+ style: styles.causeLabel,
181
+ children: getLabel()
182
+ }), /*#__PURE__*/_jsx(View, {
183
+ style: [styles.causeBadgeLarge, {
184
+ backgroundColor: config.color + "20"
185
+ }],
186
+ children: /*#__PURE__*/_jsx(Text, {
187
+ testID: "buoy-highlight-detail-cause-badge",
188
+ style: [styles.causeBadgeLargeText, {
189
+ color: config.color
190
+ }],
191
+ children: config.label.toUpperCase()
192
+ })
193
+ })]
194
+ }), hasHookChanges && /*#__PURE__*/_jsx(View, {
195
+ style: styles.hookChangesContainer,
196
+ children: cause.hookChanges.map((hook, index) => {
197
+ const hookKey = `${hook.type}[${hook.index}]`;
198
+ const oldValue = {
199
+ [hookKey]: hook.previousValue
200
+ };
201
+ const newValue = {
202
+ [hookKey]: hook.currentValue
203
+ };
204
+ return /*#__PURE__*/_jsx(View, {
205
+ style: styles.hookDiffContainer,
206
+ children: /*#__PURE__*/_jsx(TreeDiffViewer, {
207
+ oldValue: oldValue,
208
+ newValue: newValue,
209
+ theme: "dark",
210
+ showUnchanged: false
211
+ })
212
+ }, index);
213
+ })
214
+ }), !hasHookChanges && hasChangedKeys && /*#__PURE__*/_jsx(View, {
215
+ style: styles.propsChangesContainer,
216
+ children: cause.changedKeys.filter(k => !k.includes("(ref only)")).slice(0, 5).map((key, index) => /*#__PURE__*/_jsx(View, {
217
+ style: styles.propChangeChip,
218
+ children: /*#__PURE__*/_jsx(Text, {
219
+ testID: `buoy-highlight-detail-changed-${index}`,
220
+ style: styles.propChangeText,
221
+ children: key
222
+ })
223
+ }, index))
224
+ })]
225
+ });
226
+ }
227
+
228
+ /**
229
+ * DetailsSection - Component identifiers, measurements, and stats
230
+ * Helps devs find the component in their codebase
231
+ */
232
+ function DetailsSection({
233
+ render,
234
+ rendersPerSec
235
+ }) {
236
+ const hasIdentifiers = render.testID || render.nativeID || render.accessibilityLabel;
237
+ const hasMeasurements = render.measurements;
238
+ return /*#__PURE__*/_jsxs(View, {
239
+ style: styles.detailsSection,
240
+ children: [hasIdentifiers && /*#__PURE__*/_jsxs(View, {
241
+ testID: "buoy-highlight-detail-identifiers",
242
+ style: styles.detailsCard,
243
+ children: [/*#__PURE__*/_jsx(Text, {
244
+ style: styles.detailsCardTitle,
245
+ children: "Identifiers"
246
+ }), render.testID && /*#__PURE__*/_jsx(DetailRow, {
247
+ label: "testID",
248
+ value: render.testID
249
+ }), render.nativeID && /*#__PURE__*/_jsx(DetailRow, {
250
+ label: "nativeID",
251
+ value: render.nativeID
252
+ }), render.accessibilityLabel && /*#__PURE__*/_jsx(DetailRow, {
253
+ label: "a11y",
254
+ value: render.accessibilityLabel
255
+ })]
256
+ }), /*#__PURE__*/_jsxs(View, {
257
+ testID: "buoy-highlight-detail-stats",
258
+ style: styles.statsRow,
259
+ children: [/*#__PURE__*/_jsxs(View, {
260
+ testID: "buoy-highlight-detail-stat-renders",
261
+ style: styles.statItem,
262
+ children: [/*#__PURE__*/_jsx(Text, {
263
+ testID: "buoy-highlight-detail-stat-renders-value",
264
+ style: [styles.statValue, {
265
+ color: render.color
266
+ }],
267
+ children: render.renderCount
268
+ }), /*#__PURE__*/_jsx(Text, {
269
+ style: styles.statLabel,
270
+ children: "renders"
271
+ })]
272
+ }), /*#__PURE__*/_jsx(View, {
273
+ style: styles.statDivider
274
+ }), /*#__PURE__*/_jsxs(View, {
275
+ testID: "buoy-highlight-detail-stat-rate",
276
+ style: styles.statItem,
277
+ children: [/*#__PURE__*/_jsx(Text, {
278
+ testID: "buoy-highlight-detail-stat-rate-value",
279
+ style: styles.statValue,
280
+ children: rendersPerSec
281
+ }), /*#__PURE__*/_jsx(Text, {
282
+ style: styles.statLabel,
283
+ children: "/sec"
284
+ })]
285
+ }), /*#__PURE__*/_jsx(View, {
286
+ style: styles.statDivider
287
+ }), /*#__PURE__*/_jsxs(View, {
288
+ testID: "buoy-highlight-detail-stat-tag",
289
+ style: styles.statItem,
290
+ children: [/*#__PURE__*/_jsx(Text, {
291
+ testID: "buoy-highlight-detail-stat-tag-value",
292
+ style: styles.statValue,
293
+ children: render.nativeTag
294
+ }), /*#__PURE__*/_jsx(Text, {
295
+ style: styles.statLabel,
296
+ children: "tag"
297
+ })]
298
+ })]
299
+ }), hasMeasurements && /*#__PURE__*/_jsxs(View, {
300
+ testID: "buoy-highlight-detail-measure",
301
+ style: styles.measurementsRow,
302
+ children: [/*#__PURE__*/_jsxs(View, {
303
+ style: styles.measurementItem,
304
+ children: [/*#__PURE__*/_jsx(Text, {
305
+ style: styles.measurementLabel,
306
+ children: "x"
307
+ }), /*#__PURE__*/_jsx(Text, {
308
+ testID: "buoy-highlight-detail-measure-x",
309
+ style: styles.measurementValue,
310
+ children: Math.round(render.measurements.x)
311
+ })]
312
+ }), /*#__PURE__*/_jsxs(View, {
313
+ style: styles.measurementItem,
314
+ children: [/*#__PURE__*/_jsx(Text, {
315
+ style: styles.measurementLabel,
316
+ children: "y"
317
+ }), /*#__PURE__*/_jsx(Text, {
318
+ testID: "buoy-highlight-detail-measure-y",
319
+ style: styles.measurementValue,
320
+ children: Math.round(render.measurements.y)
321
+ })]
322
+ }), /*#__PURE__*/_jsxs(View, {
323
+ style: styles.measurementItem,
324
+ children: [/*#__PURE__*/_jsx(Text, {
325
+ style: styles.measurementLabel,
326
+ children: "w"
327
+ }), /*#__PURE__*/_jsx(Text, {
328
+ testID: "buoy-highlight-detail-measure-w",
329
+ style: styles.measurementValue,
330
+ children: Math.round(render.measurements.width)
331
+ })]
332
+ }), /*#__PURE__*/_jsxs(View, {
333
+ style: styles.measurementItem,
334
+ children: [/*#__PURE__*/_jsx(Text, {
335
+ style: styles.measurementLabel,
336
+ children: "h"
337
+ }), /*#__PURE__*/_jsx(Text, {
338
+ testID: "buoy-highlight-detail-measure-h",
339
+ style: styles.measurementValue,
340
+ children: Math.round(render.measurements.height)
341
+ })]
342
+ })]
343
+ })]
344
+ });
345
+ }
346
+
347
+ /**
348
+ * DetailRow - Single row for identifier display
349
+ */
350
+ function DetailRow({
351
+ label,
352
+ value
353
+ }) {
354
+ const handle = `buoy-highlight-detail-id-${label.toLowerCase()}`;
355
+ return /*#__PURE__*/_jsxs(View, {
356
+ testID: handle,
357
+ style: styles.detailRow,
358
+ children: [/*#__PURE__*/_jsx(Text, {
359
+ style: styles.detailLabel,
360
+ children: label
361
+ }), /*#__PURE__*/_jsx(Text, {
362
+ testID: `${handle}-value`,
363
+ style: styles.detailValue,
364
+ numberOfLines: 1,
365
+ children: value
366
+ })]
367
+ });
368
+ }
369
+
370
+ /**
371
+ * QuickActionsSection - Quick filter actions for the component
372
+ * Users select a filter option, then use action buttons to include/exclude
373
+ */
374
+ function QuickActionsSection({
375
+ render,
376
+ onAddFilter
377
+ }) {
378
+ const [selectedIndex, setSelectedIndex] = useState(null);
379
+
380
+ // Build list of available filter options (most specific to most general)
381
+ const filterOptions = useMemo(() => {
382
+ const options = [];
383
+
384
+ // Most specific first
385
+ if (render.nativeID) {
386
+ options.push({
387
+ type: "nativeID",
388
+ value: render.nativeID,
389
+ label: "nativeID"
390
+ });
391
+ }
392
+ if (render.testID) {
393
+ options.push({
394
+ type: "testID",
395
+ value: render.testID,
396
+ label: "testID"
397
+ });
398
+ }
399
+ if (render.accessibilityLabel) {
400
+ options.push({
401
+ type: "accessibilityLabel",
402
+ value: render.accessibilityLabel,
403
+ label: "a11y"
404
+ });
405
+ }
406
+ if (render.componentName) {
407
+ options.push({
408
+ type: "component",
409
+ value: render.componentName,
410
+ label: "component"
411
+ });
412
+ }
413
+ // Most general last (always available)
414
+ options.push({
415
+ type: "viewType",
416
+ value: render.viewType,
417
+ label: "viewType"
418
+ });
419
+ return options;
420
+ }, [render]);
421
+ const selectedOption = selectedIndex !== null ? filterOptions[selectedIndex] : null;
422
+ const handleSelectOption = useCallback(index => {
423
+ setSelectedIndex(prev => prev === index ? null : index);
424
+ }, []);
425
+ const handleInclude = useCallback(() => {
426
+ if (selectedOption) {
427
+ onAddFilter({
428
+ type: selectedOption.type,
429
+ value: selectedOption.value
430
+ }, "include");
431
+ setSelectedIndex(null);
432
+ }
433
+ }, [selectedOption, onAddFilter]);
434
+ const handleExclude = useCallback(() => {
435
+ if (selectedOption) {
436
+ onAddFilter({
437
+ type: selectedOption.type,
438
+ value: selectedOption.value
439
+ }, "exclude");
440
+ setSelectedIndex(null);
441
+ }
442
+ }, [selectedOption, onAddFilter]);
443
+ return /*#__PURE__*/_jsxs(View, {
444
+ testID: "buoy-highlight-quick",
445
+ style: styles.quickActionsSection,
446
+ children: [/*#__PURE__*/_jsx(Text, {
447
+ style: styles.quickActionsTitle,
448
+ children: "Quick Filters"
449
+ }), /*#__PURE__*/_jsx(View, {
450
+ style: styles.filterOptionsList,
451
+ children: filterOptions.map((option, index) => /*#__PURE__*/_jsx(FilterOptionCard, {
452
+ testID: `buoy-highlight-quick-${option.type.toLowerCase()}`,
453
+ label: option.label,
454
+ value: option.value,
455
+ isSelected: selectedIndex === index,
456
+ onSelect: () => handleSelectOption(index)
457
+ }, option.type))
458
+ }), /*#__PURE__*/_jsxs(View, {
459
+ style: styles.filterActionButtons,
460
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
461
+ testID: "buoy-highlight-quick-include",
462
+ accessibilityRole: "button",
463
+ accessibilityLabel: "Only show this",
464
+ style: [styles.filterActionButton, styles.filterActionButtonInclude, !selectedOption && styles.filterActionButtonDisabled],
465
+ onPress: handleInclude,
466
+ disabled: !selectedOption,
467
+ activeOpacity: 0.7,
468
+ children: [/*#__PURE__*/_jsx(PlusIcon, {
469
+ size: 14,
470
+ color: selectedOption ? night.color.accent : night.color.textTertiary
471
+ }), /*#__PURE__*/_jsx(Text, {
472
+ style: [styles.filterActionButtonText, {
473
+ color: selectedOption ? night.color.accent : night.color.textTertiary
474
+ }],
475
+ children: "Only Show This"
476
+ })]
477
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
478
+ testID: "buoy-highlight-quick-exclude",
479
+ accessibilityRole: "button",
480
+ accessibilityLabel: "Hide this",
481
+ style: [styles.filterActionButton, styles.filterActionButtonExclude, !selectedOption && styles.filterActionButtonDisabled],
482
+ onPress: handleExclude,
483
+ disabled: !selectedOption,
484
+ activeOpacity: 0.7,
485
+ children: [/*#__PURE__*/_jsx(MinusIcon, {
486
+ size: 14,
487
+ color: selectedOption ? night.color.danger : night.color.textTertiary
488
+ }), /*#__PURE__*/_jsx(Text, {
489
+ style: [styles.filterActionButtonText, {
490
+ color: selectedOption ? night.color.danger : night.color.textTertiary
491
+ }],
492
+ children: "Hide This"
493
+ })]
494
+ })]
495
+ })]
496
+ });
497
+ }
498
+
499
+ /**
500
+ * FilterOptionCard - A selectable filter option card
501
+ */
502
+ function FilterOptionCard({
503
+ testID,
504
+ label,
505
+ value,
506
+ isSelected,
507
+ onSelect
508
+ }) {
509
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
510
+ testID: testID,
511
+ accessibilityRole: "button",
512
+ accessibilityLabel: `${label}, ${value}`,
513
+ style: [styles.filterOptionCard, isSelected && styles.filterOptionCardSelected],
514
+ onPress: onSelect,
515
+ activeOpacity: 0.7,
516
+ children: [/*#__PURE__*/_jsx(Text, {
517
+ style: [styles.filterOptionLabel, isSelected && styles.filterOptionLabelSelected],
518
+ children: label
519
+ }), /*#__PURE__*/_jsx(Text, {
520
+ testID: testID ? `${testID}-value` : undefined,
521
+ style: [styles.filterOptionValue, isSelected && styles.filterOptionValueSelected],
522
+ numberOfLines: 1,
523
+ children: value
524
+ })]
525
+ });
526
+ }
527
+ const styles = StyleSheet.create({
528
+ container: {
529
+ flex: 1,
530
+ // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
531
+ backgroundColor: "transparent"
532
+ },
533
+ // Header
534
+ header: {
535
+ flexDirection: "row",
536
+ alignItems: "center",
537
+ justifyContent: "center",
538
+ gap: 12,
539
+ padding: 16,
540
+ paddingBottom: 0
541
+ },
542
+ // ScrollView
543
+ scrollView: {
544
+ flex: 1
545
+ },
546
+ scrollContent: {
547
+ padding: 16,
548
+ paddingTop: 12,
549
+ paddingBottom: 80,
550
+ gap: 12
551
+ },
552
+ headerLeft: {
553
+ flex: 1,
554
+ flexDirection: "row",
555
+ alignItems: "center",
556
+ justifyContent: "center",
557
+ gap: 10
558
+ },
559
+ componentName: {
560
+ fontSize: 18,
561
+ fontWeight: "700",
562
+ color: night.color.text,
563
+ flexShrink: 1
564
+ },
565
+ nativeTypeBadge: {
566
+ // Inset display chip — the night track color.
567
+ backgroundColor: night.color.bg,
568
+ paddingVertical: 3,
569
+ paddingHorizontal: 8,
570
+ borderRadius: 4
571
+ },
572
+ nativeTypeText: {
573
+ fontSize: 11,
574
+ fontWeight: "600",
575
+ color: night.color.textTertiary,
576
+ fontFamily: monoFont
577
+ },
578
+ // Answer Card
579
+ answerCard: {
580
+ backgroundColor: night.color.surface,
581
+ borderRadius: 8,
582
+ borderWidth: 1,
583
+ borderColor: night.color.border,
584
+ padding: 12,
585
+ gap: 6
586
+ },
587
+ causeBadgeRow: {
588
+ flexDirection: "row",
589
+ alignItems: "center",
590
+ gap: 8
591
+ },
592
+ causeBadgeLarge: {
593
+ paddingVertical: 4,
594
+ paddingHorizontal: 10,
595
+ borderRadius: 4
596
+ },
597
+ causeBadgeLargeText: {
598
+ fontSize: 11,
599
+ fontWeight: "700",
600
+ letterSpacing: 0.5
601
+ },
602
+ causeLabel: {
603
+ fontSize: 12,
604
+ color: night.color.textSecondary,
605
+ fontWeight: "500"
606
+ },
607
+ hookChangesContainer: {
608
+ gap: 6
609
+ },
610
+ hookDiffContainer: {
611
+ // Inset code box — the night track color.
612
+ backgroundColor: night.color.bg,
613
+ borderRadius: 6,
614
+ overflow: "hidden"
615
+ },
616
+ propsChangesContainer: {
617
+ flexDirection: "row",
618
+ flexWrap: "wrap",
619
+ gap: 4
620
+ },
621
+ propChangeChip: {
622
+ // Inset display chip — the night track color.
623
+ backgroundColor: night.color.bg,
624
+ paddingVertical: 2,
625
+ paddingHorizontal: 6,
626
+ borderRadius: 4
627
+ },
628
+ propChangeText: {
629
+ fontSize: 11,
630
+ color: night.color.text,
631
+ fontFamily: monoFont
632
+ },
633
+ // Details Section
634
+ detailsSection: {
635
+ gap: 10
636
+ },
637
+ detailsCard: {
638
+ backgroundColor: night.color.surface,
639
+ borderRadius: 8,
640
+ borderWidth: 1,
641
+ borderColor: night.color.border,
642
+ padding: 12,
643
+ gap: 6
644
+ },
645
+ detailsCardTitle: {
646
+ fontSize: 10,
647
+ fontWeight: "600",
648
+ color: night.color.textTertiary,
649
+ letterSpacing: 0.5,
650
+ textTransform: "uppercase",
651
+ marginBottom: 4
652
+ },
653
+ detailRow: {
654
+ flexDirection: "row",
655
+ alignItems: "center",
656
+ gap: 8
657
+ },
658
+ detailLabel: {
659
+ fontSize: 11,
660
+ fontWeight: "600",
661
+ color: night.color.textSecondary,
662
+ minWidth: 55
663
+ },
664
+ detailValue: {
665
+ fontSize: 12,
666
+ color: night.color.text,
667
+ fontFamily: monoFont,
668
+ flex: 1
669
+ },
670
+ // Stats Row
671
+ statsRow: {
672
+ flexDirection: "row",
673
+ alignItems: "center",
674
+ justifyContent: "center",
675
+ backgroundColor: night.color.surface,
676
+ borderRadius: 8,
677
+ borderWidth: 1,
678
+ borderColor: night.color.border,
679
+ paddingVertical: 10,
680
+ paddingHorizontal: 16
681
+ },
682
+ statItem: {
683
+ flex: 1,
684
+ alignItems: "center"
685
+ },
686
+ statValue: {
687
+ fontSize: 16,
688
+ fontWeight: "700",
689
+ color: night.color.text,
690
+ fontFamily: monoFont
691
+ },
692
+ statLabel: {
693
+ fontSize: 10,
694
+ color: night.color.textTertiary,
695
+ marginTop: 2
696
+ },
697
+ statDivider: {
698
+ width: 1,
699
+ height: 24,
700
+ backgroundColor: night.color.border,
701
+ marginHorizontal: 12
702
+ },
703
+ // Measurements Row
704
+ measurementsRow: {
705
+ flexDirection: "row",
706
+ alignItems: "center",
707
+ justifyContent: "space-around",
708
+ backgroundColor: night.color.surface,
709
+ borderRadius: 8,
710
+ borderWidth: 1,
711
+ borderColor: night.color.border,
712
+ paddingVertical: 8,
713
+ paddingHorizontal: 12
714
+ },
715
+ measurementItem: {
716
+ flexDirection: "row",
717
+ alignItems: "baseline",
718
+ gap: 4
719
+ },
720
+ measurementLabel: {
721
+ fontSize: 10,
722
+ fontWeight: "600",
723
+ color: night.color.textTertiary,
724
+ textTransform: "uppercase"
725
+ },
726
+ measurementValue: {
727
+ fontSize: 12,
728
+ fontWeight: "600",
729
+ color: night.color.text,
730
+ fontFamily: monoFont
731
+ },
732
+ // Quick Actions Section
733
+ quickActionsSection: {
734
+ backgroundColor: night.color.surface,
735
+ borderRadius: 8,
736
+ borderWidth: 1,
737
+ borderColor: night.color.border,
738
+ padding: 12,
739
+ gap: 10
740
+ },
741
+ quickActionsTitle: {
742
+ fontSize: 10,
743
+ fontWeight: "600",
744
+ color: night.color.textTertiary,
745
+ letterSpacing: 0.5,
746
+ textTransform: "uppercase",
747
+ marginBottom: 2
748
+ },
749
+ filterOptionsList: {
750
+ gap: 6
751
+ },
752
+ filterOptionCard: {
753
+ flexDirection: "row",
754
+ alignItems: "center",
755
+ backgroundColor: night.color.surfaceElevated,
756
+ borderRadius: 6,
757
+ paddingVertical: 10,
758
+ paddingHorizontal: 12,
759
+ borderWidth: 1,
760
+ borderColor: "transparent",
761
+ gap: 10
762
+ },
763
+ filterOptionCardSelected: {
764
+ borderColor: night.color.accentBorderStrong,
765
+ backgroundColor: night.color.accentSoft
766
+ },
767
+ filterOptionLabel: {
768
+ fontSize: 10,
769
+ fontWeight: "600",
770
+ color: night.color.textTertiary,
771
+ minWidth: 70
772
+ },
773
+ filterOptionLabelSelected: {
774
+ color: night.color.accent
775
+ },
776
+ filterOptionValue: {
777
+ fontSize: 12,
778
+ color: night.color.text,
779
+ fontFamily: monoFont,
780
+ flex: 1
781
+ },
782
+ filterOptionValueSelected: {
783
+ color: night.color.text
784
+ },
785
+ filterActionButtons: {
786
+ flexDirection: "row",
787
+ gap: 8,
788
+ marginTop: 12
789
+ },
790
+ filterActionButton: {
791
+ flexDirection: "row",
792
+ alignItems: "center",
793
+ justifyContent: "center",
794
+ gap: 4,
795
+ paddingVertical: 8,
796
+ paddingHorizontal: 10,
797
+ borderRadius: 6,
798
+ borderWidth: 1
799
+ },
800
+ filterActionButtonInclude: {
801
+ backgroundColor: night.color.accentSoft,
802
+ borderColor: night.color.accentBorderStrong
803
+ },
804
+ filterActionButtonExclude: {
805
+ backgroundColor: night.color.dangerSoft,
806
+ borderColor: night.color.danger + "40"
807
+ },
808
+ filterActionButtonDisabled: {
809
+ backgroundColor: night.color.surfaceElevated,
810
+ borderColor: night.color.border,
811
+ opacity: 0.5
812
+ },
813
+ filterActionButtonText: {
814
+ fontSize: 11,
815
+ fontWeight: "600"
816
+ },
817
+ // Locked Banner
818
+ lockedBanner: {
819
+ flexDirection: "row",
820
+ alignItems: "center",
821
+ justifyContent: "center",
822
+ backgroundColor: night.color.surface,
823
+ borderRadius: 8,
824
+ borderWidth: 1,
825
+ borderColor: night.color.warning + "40",
826
+ paddingVertical: 12,
827
+ paddingHorizontal: 16,
828
+ gap: 8
829
+ },
830
+ lockedBannerText: {
831
+ color: night.color.warning,
832
+ fontSize: 13,
833
+ fontWeight: "600"
834
+ },
835
+ lockedBannerSubtext: {
836
+ color: night.color.textTertiary,
837
+ fontSize: 12
838
+ }
839
+ });
840
+ export default RenderDetailView;