@buoy-gg/highlight-updates 2.1.10 → 2.1.11

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