@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,908 @@
1
- "use strict";import React,{useState,useCallback,useMemo}from"react";import{View,Text,TouchableOpacity,ScrollView,StyleSheet}from"react-native";import{Clock,GitBranch,Database,Lock,formatRelativeTime,SectionHeader,EventStepperFooter,buoyColors,night,useLimit,ToolEmptyState,monoFont}from"@buoy-gg/shared-ui";import{TreeDiffViewer}from"@buoy-gg/shared-ui/dataViewer";import{RenderTracker}from"../utils/RenderTracker";import{EnhancedCauseDisplay,CAUSE_CONFIG}from"./RenderCauseBadge";import{jsx as _jsx,jsxs as _jsxs,Fragment as _Fragment}from"react/jsx-runtime";function formatTimeWithMs(e){const t=new Date(e);return`${t.getHours().toString().padStart(2,"0")}:${t.getMinutes().toString().padStart(2,"0")}:${t.getSeconds().toString().padStart(2,"0")}.${t.getMilliseconds().toString().padStart(3,"0")}`}export function RenderHistoryViewer({render:e,disableInternalFooter:t=!1,selectedEventIndex:o,onEventIndexChange:r,isPro:i=!1}){const[n,s]=useState(0),l=o??n,a=r??s,[c,d]=useState("current"),g=useMemo(()=>e.renderHistory&&0!==e.renderHistory.length?[...e.renderHistory].sort((e,t)=>e.timestamp-t.timestamp):[],[e.renderHistory]),y=useLimit("highlightUpdates.historyEvents"),h=i?1/0:y,f=useMemo(()=>g.slice(-h),[g,h]),x=useMemo(()=>Math.max(0,g.length-h),[g.length,h]),p=f.length,u=f[l],m=l>0?f[l-1]:null,T=useCallback(()=>{l>0&&a(l-1)},[l,a]),b=useCallback(()=>{l<p-1&&a(l+1)},[l,p,a]);if(0===p){const e=RenderTracker.getSettings().enableRenderHistory;return _jsx(ToolEmptyState,{testID:"buoy-highlight-history-empty",otto:e?"stare":"paused",title:"No Render History",message:e?"History is on and armed — this component has not re-rendered since. Interact with the app to record one.":"Enable render history tracking in settings to see render events here."})}return _jsxs(_Fragment,{children:[_jsxs(View,{style:[styles.container,{paddingBottom:!t&&p>1?80:0}],children:[x>0&&_jsxs(View,{testID:"buoy-highlight-history-locked",style:styles.lockedBanner,children:[_jsx(Lock,{size:14,color:night.color.warning}),_jsxs(Text,{testID:"buoy-highlight-history-locked-text",style:styles.lockedBannerText,children:[x," older ",1===x?"render":"renders"," locked"]}),_jsx(Text,{style:styles.lockedBannerSubtext,children:"Upgrade to Pro"})]}),_jsxs(View,{style:styles.viewToggleContainer,children:[_jsxs(TouchableOpacity,{testID:"buoy-highlight-history-mode-current",accessibilityRole:"button",accessibilityLabel:"Current state",style:[styles.viewToggleCard,"current"===c&&styles.viewToggleCardActive],onPress:()=>d("current"),activeOpacity:.8,children:[_jsxs(View,{style:styles.viewToggleContent,children:[_jsx(Database,{size:16,color:"current"===c?night.color.accent:night.color.textSecondary}),_jsx(Text,{style:[styles.viewToggleLabel,"current"===c&&styles.viewToggleLabelActive],children:"CURRENT STATE"})]}),_jsxs(Text,{style:[styles.viewToggleDescription,"current"===c&&{color:night.color.text}],children:["View render #",u?.renderNumber??0," details"]})]}),_jsxs(View,{testID:"buoy-highlight-history-mode-diff",accessibilityLabel:"Diff view, coming soon",style:[styles.viewToggleCard,styles.viewToggleCardDisabled],children:[_jsxs(View,{style:styles.viewToggleContent,children:[_jsx(GitBranch,{size:16,color:night.color.textTertiary}),_jsx(Text,{style:[styles.viewToggleLabel,styles.viewToggleLabelDisabled],children:"DIFF VIEW"}),_jsx(View,{style:styles.todoBadge,children:_jsx(Text,{style:styles.todoBadgeText,children:"TODO"})})]}),_jsx(Text,{style:[styles.viewToggleDescription,{color:night.color.textTertiary}],children:"Compare renders side by side (coming soon)"})]})]}),_jsx(ScrollView,{testID:"buoy-highlight-history-scroll",style:styles.contentScroll,contentContainerStyle:styles.contentContainer,showsVerticalScrollIndicator:!1,children:"current"===c?_jsx(CurrentStateView,{event:u,render:e}):_jsx(DiffView,{previousEvent:m,currentEvent:u,render:e})})]}),!t&&_jsx(EventStepperFooter,{currentIndex:l,totalItems:p,onPrevious:T,onNext:b,itemLabel:"Render",subtitle:formatRelativeTime(new Date(u?.timestamp??Date.now()))})]})}function CurrentStateView({event:e,render:t}){if(!e)return _jsx(View,{testID:"buoy-highlight-history-no-event",style:styles.noEventContainer,children:_jsx(Text,{style:styles.noEventText,children:"No event selected"})});const o=CAUSE_CONFIG[e.cause.type];return _jsxs(View,{style:styles.currentStateContainer,children:[_jsxs(View,{style:styles.eventHeader,children:[_jsxs(View,{style:styles.eventHeaderLeft,children:[_jsxs(Text,{testID:"buoy-highlight-history-event-title",style:styles.eventTitle,children:["Render #",e.renderNumber]}),_jsx(Text,{testID:"buoy-highlight-history-event-time",style:styles.eventTime,children:formatTimeWithMs(e.timestamp)})]}),_jsxs(View,{style:[styles.eventBadge,{backgroundColor:t.color+"30"}],children:[_jsx(View,{style:[styles.eventBadgeDot,{backgroundColor:t.color}]}),_jsx(Text,{testID:"buoy-highlight-history-event-cause",style:[styles.eventBadgeText,{color:t.color}],children:e.cause.type.toUpperCase()})]})]}),_jsxs(View,{style:styles.section,children:[_jsxs(SectionHeader,{children:[_jsx(SectionHeader.Icon,{icon:Clock,color:o.color,size:12}),_jsx(SectionHeader.Title,{children:"WHY DID THIS RENDER?"})]}),_jsx(View,{style:styles.sectionContent,children:_jsx(EnhancedCauseDisplay,{cause:e.cause,nativeType:t.viewType})})]}),e.capturedProps&&Object.keys(e.capturedProps).length>0&&_jsxs(View,{style:styles.section,children:[_jsxs(SectionHeader,{children:[_jsx(SectionHeader.Icon,{icon:Database,color:night.color.accent,size:12}),_jsx(SectionHeader.Title,{children:"CAPTURED PROPS"})]}),_jsx(View,{style:styles.sectionContent,children:_jsx(ScrollView,{horizontal:!0,showsHorizontalScrollIndicator:!1,style:styles.propsScrollView,children:_jsx(Text,{testID:"buoy-highlight-history-props",style:styles.propsJson,children:JSON.stringify(e.capturedProps,null,2)})})})]})]})}function DiffView({previousEvent:e,currentEvent:t,render:o}){const[r,i]=useState("props");if(!e||!t)return _jsx(View,{style:styles.noEventContainer,children:_jsx(Text,{style:styles.noEventText,children:"Select an event with a previous event to compare"})});const n=CAUSE_CONFIG[e.cause.type],s=CAUSE_CONFIG[t.cause.type],l=e.capturedProps&&t.capturedProps,a=e.capturedState&&t.capturedState;return _jsxs(View,{style:styles.diffContainer,children:[_jsxs(View,{style:styles.compareBar,children:[_jsxs(View,{style:styles.compareSide,children:[_jsxs(View,{style:styles.compareLabelRow,children:[_jsx(Text,{style:[styles.compareLabel,{color:buoyColors.warning}],children:"PREV"}),_jsx(View,{style:[styles.compareActionBadge,{backgroundColor:`${n.color}20`}],children:_jsx(Text,{style:[styles.compareActionText,{color:n.color}],children:e.cause.type.toUpperCase()})})]}),_jsxs(View,{style:styles.compareMeta,children:[_jsxs(Text,{style:styles.compareIndex,children:["#",e.renderNumber]}),_jsx(Text,{style:styles.compareTime,children:formatTimeWithMs(e.timestamp)})]})]}),_jsx(View,{style:styles.compareDivider,children:_jsx(Text,{style:styles.compareArrow,children:"→"})}),_jsxs(View,{style:styles.compareSide,children:[_jsxs(View,{style:styles.compareLabelRow,children:[_jsx(Text,{style:[styles.compareLabel,{color:buoyColors.success}],children:"CUR"}),_jsx(View,{style:[styles.compareActionBadge,{backgroundColor:`${s.color}20`}],children:_jsx(Text,{style:[styles.compareActionText,{color:s.color}],children:t.cause.type.toUpperCase()})})]}),_jsxs(View,{style:styles.compareMeta,children:[_jsxs(Text,{style:styles.compareIndex,children:["#",t.renderNumber]}),_jsx(Text,{style:styles.compareTime,children:formatTimeWithMs(t.timestamp)})]})]})]}),_jsxs(View,{style:styles.diffModeTabs,children:[_jsx(TouchableOpacity,{style:[styles.diffModeTab,"cause"===r&&styles.diffModeTabActive],onPress:()=>i("cause"),activeOpacity:.7,children:_jsx(Text,{style:[styles.diffModeTabText,"cause"===r&&styles.diffModeTabTextActive],children:"CAUSE"})}),_jsx(TouchableOpacity,{style:[styles.diffModeTab,"props"===r&&styles.diffModeTabActive,!l&&styles.diffModeTabDisabled],onPress:()=>l&&i("props"),activeOpacity:l?.7:1,children:_jsx(Text,{style:[styles.diffModeTabText,"props"===r&&styles.diffModeTabTextActive,!l&&styles.diffModeTabTextDisabled],children:"PROPS"})}),_jsx(TouchableOpacity,{style:[styles.diffModeTab,"state"===r&&styles.diffModeTabActive,!a&&styles.diffModeTabDisabled],onPress:()=>a&&i("state"),activeOpacity:a?.7:1,children:_jsx(Text,{style:[styles.diffModeTabText,"state"===r&&styles.diffModeTabTextActive,!a&&styles.diffModeTabTextDisabled],children:"STATE"})})]}),"cause"===r&&_jsx(View,{style:styles.diffSummary,children:_jsx(EnhancedCauseDisplay,{cause:t.cause,nativeType:o.viewType})}),"props"===r&&_jsx(View,{style:styles.treeDiffContainer,children:l?_jsx(TreeDiffViewer,{oldValue:e.capturedProps,newValue:t.capturedProps,theme:"dark",showUnchanged:!0}):_jsxs(View,{style:styles.noDiffData,children:[_jsx(Text,{style:styles.noDiffDataTitle,children:"Props Not Captured"}),_jsx(Text,{style:styles.noDiffDataText,children:'Enable "Capture Props on Render" in settings to see props diff.'})]})}),"state"===r&&_jsx(View,{style:styles.treeDiffContainer,children:a?_jsx(TreeDiffViewer,{oldValue:e.capturedState,newValue:t.capturedState,theme:"dark",showUnchanged:!0}):_jsxs(View,{style:styles.noDiffData,children:[_jsx(Text,{style:styles.noDiffDataTitle,children:"State Not Captured"}),_jsx(Text,{style:styles.noDiffDataText,children:'Enable "Capture State on Render" in settings to see state diff.'})]})})]})}export function RenderHistoryFooter({render:e,selectedEventIndex:t=0,onEventIndexChange:o=()=>{},isPro:r=!1}){const i=useMemo(()=>e.renderHistory&&0!==e.renderHistory.length?[...e.renderHistory].sort((e,t)=>e.timestamp-t.timestamp):[],[e.renderHistory]),n=useLimit("highlightUpdates.historyEvents"),s=r?1/0:n,l=useMemo(()=>i.slice(-s),[i,s]),a=l.length,c=l[t],d=useCallback(()=>{o(Math.max(0,t-1))},[t,o]),g=useCallback(()=>{o(Math.min(a-1,t+1))},[t,a,o]);return a<=1?null:_jsx(EventStepperFooter,{currentIndex:t,totalItems:a,onPrevious:d,onNext:g,itemLabel:"Render",subtitle:c?formatRelativeTime(new Date(c.timestamp)):void 0})}const styles=StyleSheet.create({container:{flex:1,backgroundColor:"transparent"},emptyContainer:{flex:1,alignItems:"center",justifyContent:"center",padding:32,gap:12},emptyTitle:{fontSize:16,fontWeight:"600",color:night.color.text},emptyText:{fontSize:13,color:night.color.textSecondary,textAlign:"center",lineHeight:18},viewToggleContainer:{flexDirection:"row",padding:12,gap:8},viewToggleCard:{flex:1,backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:10,gap:4},viewToggleCardActive:{borderColor:night.color.accentBorderStrong,backgroundColor:night.color.accentSoft},viewToggleCardDisabled:{opacity:.5},todoBadge:{backgroundColor:night.color.fillSecondary,paddingHorizontal:6,paddingVertical:2,borderRadius:4,marginLeft:4},todoBadgeText:{fontSize:9,fontWeight:"700",color:night.color.textTertiary,letterSpacing:.3},viewToggleContent:{flexDirection:"row",alignItems:"center",gap:6},viewToggleLabel:{fontSize:10,fontWeight:"700",color:night.color.textSecondary,letterSpacing:.5},viewToggleLabelActive:{color:night.color.accent},viewToggleLabelDisabled:{color:night.color.textTertiary},viewToggleDescription:{fontSize:11,color:night.color.textTertiary,marginTop:2},contentScroll:{flex:1},contentContainer:{padding:12,paddingTop:0},noEventContainer:{padding:24,alignItems:"center"},noEventText:{fontSize:13,color:night.color.textTertiary},currentStateContainer:{gap:12},eventHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:12},eventHeaderLeft:{gap:2},eventTitle:{fontSize:15,fontWeight:"700",color:night.color.text},eventTime:{fontSize:11,color:night.color.textTertiary,fontFamily:monoFont},eventBadge:{flexDirection:"row",alignItems:"center",paddingHorizontal:10,paddingVertical:4,borderRadius:12,gap:6},eventBadgeDot:{width:6,height:6,borderRadius:3},eventBadgeText:{fontSize:10,fontWeight:"700",fontFamily:monoFont},section:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,overflow:"hidden"},sectionContent:{padding:12,paddingTop:8},causeContainer:{marginBottom:12},changedList:{marginBottom:12,paddingLeft:4},changedListTitle:{fontSize:11,fontWeight:"600",color:night.color.textSecondary,marginBottom:6},changedItem:{fontSize:12,color:night.color.text,fontFamily:monoFont,marginBottom:3,paddingLeft:8},propsScrollView:{maxHeight:150},propsJson:{fontSize:11,fontFamily:monoFont,color:night.color.text,backgroundColor:night.color.bg,padding:10,borderRadius:6},diffContainer:{gap:12},compareBar:{flexDirection:"row",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:10},compareSide:{flex:1,gap:4},compareLabelRow:{flexDirection:"row",alignItems:"center",gap:8},compareLabel:{fontSize:10,fontWeight:"700",letterSpacing:.5},compareActionBadge:{paddingHorizontal:6,paddingVertical:2,borderRadius:4},compareActionText:{fontSize:9,fontWeight:"600"},compareMeta:{flexDirection:"row",alignItems:"center",gap:6},compareIndex:{fontSize:12,fontWeight:"600",color:night.color.text},compareTime:{fontSize:10,color:night.color.textTertiary,fontFamily:monoFont},compareDivider:{width:30,alignItems:"center",justifyContent:"center"},compareArrow:{fontSize:16,color:night.color.textTertiary},diffSummary:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:12},diffSummaryTitle:{fontSize:11,fontWeight:"600",color:night.color.textSecondary,marginBottom:8},diffChanges:{gap:4},diffChangeItem:{flexDirection:"row",alignItems:"center",gap:6},diffChangeIcon:{fontSize:12,fontWeight:"700",color:night.color.warning,fontFamily:monoFont},diffChangeKey:{fontSize:12,color:night.color.text,fontFamily:monoFont},diffNoChanges:{fontSize:12,color:night.color.textTertiary,fontStyle:"italic"},diffPlaceholder:{fontSize:12,color:night.color.textTertiary,fontStyle:"italic",textAlign:"center",padding:16},diffModeTabs:{flexDirection:"row",backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,padding:4,gap:4},diffModeTab:{flex:1,paddingVertical:8,paddingHorizontal:12,borderRadius:6,alignItems:"center"},diffModeTabActive:{backgroundColor:night.color.accentSoft},diffModeTabDisabled:{opacity:.4},diffModeTabText:{fontSize:10,fontWeight:"600",color:night.color.textSecondary,letterSpacing:.5},diffModeTabTextActive:{color:night.color.accent},diffModeTabTextDisabled:{color:night.color.textTertiary},causeDiffContainer:{gap:8,marginBottom:12},causeDiffRow:{flexDirection:"row",alignItems:"center",gap:8},causeDiffLabel:{fontSize:11,fontWeight:"600",color:night.color.textSecondary,minWidth:80},causeDiffValue:{fontSize:12,color:night.color.text,fontFamily:monoFont,fontWeight:"500"},diffChangesTitle:{fontSize:11,fontWeight:"600",color:night.color.textSecondary,marginBottom:6},treeDiffContainer:{backgroundColor:night.color.surface,borderRadius:8,borderWidth:1,borderColor:night.color.border,overflow:"hidden",minHeight:150},noDiffData:{padding:24,alignItems:"center",gap:8},noDiffDataTitle:{fontSize:13,fontWeight:"600",color:night.color.text},noDiffDataText:{fontSize:12,color:night.color.textTertiary,textAlign:"center",lineHeight:18},lockedBanner:{flexDirection:"row",alignItems:"center",justifyContent:"center",paddingVertical:10,paddingHorizontal:16,marginHorizontal:12,marginTop:8,backgroundColor:night.color.warningSoft,borderRadius:8,borderWidth:1,borderColor:night.color.warning+"30",gap:8},lockedBannerText:{color:night.color.warning,fontSize:13,fontWeight:"500"},lockedBannerSubtext:{color:night.color.textTertiary,fontSize:12}});export default RenderHistoryViewer;
1
+ "use strict";
2
+
3
+ /**
4
+ * RenderHistoryViewer
5
+ *
6
+ * Displays render history for a component with event stepping.
7
+ * Allows navigating through render events chronologically.
8
+ * Shows current state view or diff view between renders.
9
+ */
10
+
11
+ import React, { useState, useCallback, useMemo } from "react";
12
+ import { View, Text, TouchableOpacity, ScrollView, StyleSheet } from "react-native";
13
+ import { Clock, GitBranch, Database, Lock, formatRelativeTime, SectionHeader, EventStepperFooter, buoyColors, night, useLimit, ToolEmptyState, monoFont } from "@buoy-gg/shared-ui";
14
+ import { TreeDiffViewer } from "@buoy-gg/shared-ui/dataViewer";
15
+ import { RenderTracker } from "../utils/RenderTracker";
16
+ import { EnhancedCauseDisplay, CAUSE_CONFIG } from "./RenderCauseBadge";
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ /**
19
+ * `HH:MM:SS.mmm` — the same shape Redux, Zustand and Jotai print for the same
20
+ * job, and for the same reason: the milliseconds are the point.
21
+ *
22
+ * Built by hand rather than from `toLocaleTimeString`, which appends the day
23
+ * period on a 12-hour locale — so concatenating the milliseconds to the end of
24
+ * it produced "8:21:44 AM.351", with the fraction attached to the wrong part
25
+ * of the time. It also made the string depend on the hour of day, which is a
26
+ * fine way to have a test that only passes after 10am.
27
+ */
28
+ function formatTimeWithMs(timestamp) {
29
+ const date = new Date(timestamp);
30
+ const h = date.getHours().toString().padStart(2, "0");
31
+ const m = date.getMinutes().toString().padStart(2, "0");
32
+ const s = date.getSeconds().toString().padStart(2, "0");
33
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
34
+ return `${h}:${m}:${s}.${ms}`;
35
+ }
36
+
37
+ /**
38
+ * Main RenderHistoryViewer component
39
+ */
40
+ export function RenderHistoryViewer({
41
+ render,
42
+ disableInternalFooter = false,
43
+ selectedEventIndex: externalIndex,
44
+ onEventIndexChange: externalOnChange,
45
+ isPro = false
46
+ }) {
47
+ // Internal state for event index when not controlled externally
48
+ const [internalIndex, setInternalIndex] = useState(0);
49
+
50
+ // Use external or internal state
51
+ const selectedEventIndex = externalIndex ?? internalIndex;
52
+ const onEventIndexChange = externalOnChange ?? setInternalIndex;
53
+
54
+ // View mode: "current" shows selected event, "diff" shows comparison
55
+ const [activeView, setActiveView] = useState("current");
56
+
57
+ // Get all events sorted by timestamp (oldest first)
58
+ const allEvents = useMemo(() => {
59
+ if (!render.renderHistory || render.renderHistory.length === 0) {
60
+ return [];
61
+ }
62
+ return [...render.renderHistory].sort((a, b) => a.timestamp - b.timestamp);
63
+ }, [render.renderHistory]);
64
+
65
+ // `isPro` stays a prop so callers can force the unlimited view; the tier cap
66
+ // applies otherwise. useLimit() already returns Infinity for Pro, so the two
67
+ // agree — this only preserves the prop contract for cross-package callers.
68
+ const tierLimit = useLimit("highlightUpdates.historyEvents");
69
+ const eventLimit = isPro ? Infinity : tierLimit;
70
+
71
+ // Show the most recent N events (negative index clamps to 0, so an
72
+ // unlimited tier returns the whole list).
73
+ const events = useMemo(() => allEvents.slice(-eventLimit), [allEvents, eventLimit]);
74
+ const lockedEventCount = useMemo(() => Math.max(0, allEvents.length - eventLimit), [allEvents.length, eventLimit]);
75
+ const totalEvents = events.length;
76
+ const currentEvent = events[selectedEventIndex];
77
+ const previousEvent = selectedEventIndex > 0 ? events[selectedEventIndex - 1] : null;
78
+
79
+ // Navigation handlers
80
+ const goToPrevious = useCallback(() => {
81
+ if (selectedEventIndex > 0) {
82
+ onEventIndexChange(selectedEventIndex - 1);
83
+ }
84
+ }, [selectedEventIndex, onEventIndexChange]);
85
+ const goToNext = useCallback(() => {
86
+ if (selectedEventIndex < totalEvents - 1) {
87
+ onEventIndexChange(selectedEventIndex + 1);
88
+ }
89
+ }, [selectedEventIndex, totalEvents, onEventIndexChange]);
90
+
91
+ // If no history, show empty state
92
+ if (totalEvents === 0) {
93
+ // The copy used to tell EVERYONE to go and enable the setting, including
94
+ // the people who already had. Asking the tracker which it is costs one
95
+ // synchronous read on a branch that renders nothing else.
96
+ const historyEnabled = RenderTracker.getSettings().enableRenderHistory;
97
+ return /*#__PURE__*/_jsx(ToolEmptyState, {
98
+ testID: "buoy-highlight-history-empty"
99
+ // paused = the recorder is switched off; stare = armed and watching,
100
+ // which is the truth once the setting is on and this component simply
101
+ // has not re-rendered yet.
102
+ ,
103
+ otto: historyEnabled ? "stare" : "paused",
104
+ title: "No Render History",
105
+ message: historyEnabled ? "History is on and armed — this component has not re-rendered since. Interact with the app to record one." : "Enable render history tracking in settings to see render events here."
106
+ });
107
+ }
108
+ return /*#__PURE__*/_jsxs(_Fragment, {
109
+ children: [/*#__PURE__*/_jsxs(View, {
110
+ style: [styles.container, {
111
+ paddingBottom: !disableInternalFooter && totalEvents > 1 ? 80 : 0
112
+ }],
113
+ children: [lockedEventCount > 0 && /*#__PURE__*/_jsxs(View, {
114
+ testID: "buoy-highlight-history-locked",
115
+ style: styles.lockedBanner,
116
+ children: [/*#__PURE__*/_jsx(Lock, {
117
+ size: 14,
118
+ color: night.color.warning
119
+ }), /*#__PURE__*/_jsxs(Text, {
120
+ testID: "buoy-highlight-history-locked-text",
121
+ style: styles.lockedBannerText,
122
+ children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'render' : 'renders', " locked"]
123
+ }), /*#__PURE__*/_jsx(Text, {
124
+ style: styles.lockedBannerSubtext,
125
+ children: "Upgrade to Pro"
126
+ })]
127
+ }), /*#__PURE__*/_jsxs(View, {
128
+ style: styles.viewToggleContainer,
129
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
130
+ testID: "buoy-highlight-history-mode-current",
131
+ accessibilityRole: "button",
132
+ accessibilityLabel: "Current state",
133
+ style: [styles.viewToggleCard, activeView === "current" && styles.viewToggleCardActive],
134
+ onPress: () => setActiveView("current"),
135
+ activeOpacity: 0.8,
136
+ children: [/*#__PURE__*/_jsxs(View, {
137
+ style: styles.viewToggleContent,
138
+ children: [/*#__PURE__*/_jsx(Database, {
139
+ size: 16,
140
+ color: activeView === "current" ? night.color.accent : night.color.textSecondary
141
+ }), /*#__PURE__*/_jsx(Text, {
142
+ style: [styles.viewToggleLabel, activeView === "current" && styles.viewToggleLabelActive],
143
+ children: "CURRENT STATE"
144
+ })]
145
+ }), /*#__PURE__*/_jsxs(Text, {
146
+ style: [styles.viewToggleDescription, activeView === "current" && {
147
+ color: night.color.text
148
+ }],
149
+ children: ["View render #", currentEvent?.renderNumber ?? 0, " details"]
150
+ })]
151
+ }), /*#__PURE__*/_jsxs(View, {
152
+ testID: "buoy-highlight-history-mode-diff",
153
+ accessibilityLabel: "Diff view, coming soon",
154
+ style: [styles.viewToggleCard, styles.viewToggleCardDisabled],
155
+ children: [/*#__PURE__*/_jsxs(View, {
156
+ style: styles.viewToggleContent,
157
+ children: [/*#__PURE__*/_jsx(GitBranch, {
158
+ size: 16,
159
+ color: night.color.textTertiary
160
+ }), /*#__PURE__*/_jsx(Text, {
161
+ style: [styles.viewToggleLabel, styles.viewToggleLabelDisabled],
162
+ children: "DIFF VIEW"
163
+ }), /*#__PURE__*/_jsx(View, {
164
+ style: styles.todoBadge,
165
+ children: /*#__PURE__*/_jsx(Text, {
166
+ style: styles.todoBadgeText,
167
+ children: "TODO"
168
+ })
169
+ })]
170
+ }), /*#__PURE__*/_jsx(Text, {
171
+ style: [styles.viewToggleDescription, {
172
+ color: night.color.textTertiary
173
+ }],
174
+ children: "Compare renders side by side (coming soon)"
175
+ })]
176
+ })]
177
+ }), /*#__PURE__*/_jsx(ScrollView, {
178
+ testID: "buoy-highlight-history-scroll",
179
+ style: styles.contentScroll,
180
+ contentContainerStyle: styles.contentContainer,
181
+ showsVerticalScrollIndicator: false,
182
+ children: activeView === "current" ? /*#__PURE__*/_jsx(CurrentStateView, {
183
+ event: currentEvent,
184
+ render: render
185
+ }) : /*#__PURE__*/_jsx(DiffView, {
186
+ previousEvent: previousEvent,
187
+ currentEvent: currentEvent,
188
+ render: render
189
+ })
190
+ })]
191
+ }), !disableInternalFooter && /*#__PURE__*/_jsx(EventStepperFooter, {
192
+ currentIndex: selectedEventIndex,
193
+ totalItems: totalEvents,
194
+ onPrevious: goToPrevious,
195
+ onNext: goToNext,
196
+ itemLabel: "Render",
197
+ subtitle: formatRelativeTime(new Date(currentEvent?.timestamp ?? Date.now()))
198
+ })]
199
+ });
200
+ }
201
+
202
+ /**
203
+ * Current State View - shows details of the selected render event
204
+ */
205
+ function CurrentStateView({
206
+ event,
207
+ render
208
+ }) {
209
+ if (!event) {
210
+ return /*#__PURE__*/_jsx(View, {
211
+ testID: "buoy-highlight-history-no-event",
212
+ style: styles.noEventContainer,
213
+ children: /*#__PURE__*/_jsx(Text, {
214
+ style: styles.noEventText,
215
+ children: "No event selected"
216
+ })
217
+ });
218
+ }
219
+ const causeConfig = CAUSE_CONFIG[event.cause.type];
220
+ return /*#__PURE__*/_jsxs(View, {
221
+ style: styles.currentStateContainer,
222
+ children: [/*#__PURE__*/_jsxs(View, {
223
+ style: styles.eventHeader,
224
+ children: [/*#__PURE__*/_jsxs(View, {
225
+ style: styles.eventHeaderLeft,
226
+ children: [/*#__PURE__*/_jsxs(Text, {
227
+ testID: "buoy-highlight-history-event-title",
228
+ style: styles.eventTitle,
229
+ children: ["Render #", event.renderNumber]
230
+ }), /*#__PURE__*/_jsx(Text, {
231
+ testID: "buoy-highlight-history-event-time",
232
+ style: styles.eventTime,
233
+ children: formatTimeWithMs(event.timestamp)
234
+ })]
235
+ }), /*#__PURE__*/_jsxs(View, {
236
+ style: [styles.eventBadge, {
237
+ backgroundColor: render.color + "30"
238
+ }],
239
+ children: [/*#__PURE__*/_jsx(View, {
240
+ style: [styles.eventBadgeDot, {
241
+ backgroundColor: render.color
242
+ }]
243
+ }), /*#__PURE__*/_jsx(Text, {
244
+ testID: "buoy-highlight-history-event-cause",
245
+ style: [styles.eventBadgeText, {
246
+ color: render.color
247
+ }],
248
+ children: event.cause.type.toUpperCase()
249
+ })]
250
+ })]
251
+ }), /*#__PURE__*/_jsxs(View, {
252
+ style: styles.section,
253
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
254
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
255
+ icon: Clock,
256
+ color: causeConfig.color,
257
+ size: 12
258
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
259
+ children: "WHY DID THIS RENDER?"
260
+ })]
261
+ }), /*#__PURE__*/_jsx(View, {
262
+ style: styles.sectionContent,
263
+ children: /*#__PURE__*/_jsx(EnhancedCauseDisplay, {
264
+ cause: event.cause,
265
+ nativeType: render.viewType
266
+ })
267
+ })]
268
+ }), event.capturedProps && Object.keys(event.capturedProps).length > 0 && /*#__PURE__*/_jsxs(View, {
269
+ style: styles.section,
270
+ children: [/*#__PURE__*/_jsxs(SectionHeader, {
271
+ children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
272
+ icon: Database,
273
+ color: night.color.accent,
274
+ size: 12
275
+ }), /*#__PURE__*/_jsx(SectionHeader.Title, {
276
+ children: "CAPTURED PROPS"
277
+ })]
278
+ }), /*#__PURE__*/_jsx(View, {
279
+ style: styles.sectionContent,
280
+ children: /*#__PURE__*/_jsx(ScrollView, {
281
+ horizontal: true,
282
+ showsHorizontalScrollIndicator: false,
283
+ style: styles.propsScrollView,
284
+ children: /*#__PURE__*/_jsx(Text, {
285
+ testID: "buoy-highlight-history-props",
286
+ style: styles.propsJson,
287
+ children: JSON.stringify(event.capturedProps, null, 2)
288
+ })
289
+ })
290
+ })]
291
+ })]
292
+ });
293
+ }
294
+
295
+ /**
296
+ * Diff mode types
297
+ */
298
+
299
+ /**
300
+ * Diff View - shows comparison between two render events
301
+ */
302
+ function DiffView({
303
+ previousEvent,
304
+ currentEvent,
305
+ render
306
+ }) {
307
+ // Diff mode tab state
308
+ const [diffMode, setDiffMode] = useState("props");
309
+ if (!previousEvent || !currentEvent) {
310
+ return /*#__PURE__*/_jsx(View, {
311
+ style: styles.noEventContainer,
312
+ children: /*#__PURE__*/_jsx(Text, {
313
+ style: styles.noEventText,
314
+ children: "Select an event with a previous event to compare"
315
+ })
316
+ });
317
+ }
318
+ const prevCauseConfig = CAUSE_CONFIG[previousEvent.cause.type];
319
+ const currCauseConfig = CAUSE_CONFIG[currentEvent.cause.type];
320
+
321
+ // Check what data is available for diff
322
+ const hasPropsData = previousEvent.capturedProps && currentEvent.capturedProps;
323
+ const hasStateData = previousEvent.capturedState && currentEvent.capturedState;
324
+ return /*#__PURE__*/_jsxs(View, {
325
+ style: styles.diffContainer,
326
+ children: [/*#__PURE__*/_jsxs(View, {
327
+ style: styles.compareBar,
328
+ children: [/*#__PURE__*/_jsxs(View, {
329
+ style: styles.compareSide,
330
+ children: [/*#__PURE__*/_jsxs(View, {
331
+ style: styles.compareLabelRow,
332
+ children: [/*#__PURE__*/_jsx(Text, {
333
+ style: [styles.compareLabel, {
334
+ color: buoyColors.warning
335
+ }],
336
+ children: "PREV"
337
+ }), /*#__PURE__*/_jsx(View, {
338
+ style: [styles.compareActionBadge, {
339
+ backgroundColor: `${prevCauseConfig.color}20`
340
+ }],
341
+ children: /*#__PURE__*/_jsx(Text, {
342
+ style: [styles.compareActionText, {
343
+ color: prevCauseConfig.color
344
+ }],
345
+ children: previousEvent.cause.type.toUpperCase()
346
+ })
347
+ })]
348
+ }), /*#__PURE__*/_jsxs(View, {
349
+ style: styles.compareMeta,
350
+ children: [/*#__PURE__*/_jsxs(Text, {
351
+ style: styles.compareIndex,
352
+ children: ["#", previousEvent.renderNumber]
353
+ }), /*#__PURE__*/_jsx(Text, {
354
+ style: styles.compareTime,
355
+ children: formatTimeWithMs(previousEvent.timestamp)
356
+ })]
357
+ })]
358
+ }), /*#__PURE__*/_jsx(View, {
359
+ style: styles.compareDivider,
360
+ children: /*#__PURE__*/_jsx(Text, {
361
+ style: styles.compareArrow,
362
+ children: "\u2192"
363
+ })
364
+ }), /*#__PURE__*/_jsxs(View, {
365
+ style: styles.compareSide,
366
+ children: [/*#__PURE__*/_jsxs(View, {
367
+ style: styles.compareLabelRow,
368
+ children: [/*#__PURE__*/_jsx(Text, {
369
+ style: [styles.compareLabel, {
370
+ color: buoyColors.success
371
+ }],
372
+ children: "CUR"
373
+ }), /*#__PURE__*/_jsx(View, {
374
+ style: [styles.compareActionBadge, {
375
+ backgroundColor: `${currCauseConfig.color}20`
376
+ }],
377
+ children: /*#__PURE__*/_jsx(Text, {
378
+ style: [styles.compareActionText, {
379
+ color: currCauseConfig.color
380
+ }],
381
+ children: currentEvent.cause.type.toUpperCase()
382
+ })
383
+ })]
384
+ }), /*#__PURE__*/_jsxs(View, {
385
+ style: styles.compareMeta,
386
+ children: [/*#__PURE__*/_jsxs(Text, {
387
+ style: styles.compareIndex,
388
+ children: ["#", currentEvent.renderNumber]
389
+ }), /*#__PURE__*/_jsx(Text, {
390
+ style: styles.compareTime,
391
+ children: formatTimeWithMs(currentEvent.timestamp)
392
+ })]
393
+ })]
394
+ })]
395
+ }), /*#__PURE__*/_jsxs(View, {
396
+ style: styles.diffModeTabs,
397
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
398
+ style: [styles.diffModeTab, diffMode === "cause" && styles.diffModeTabActive],
399
+ onPress: () => setDiffMode("cause"),
400
+ activeOpacity: 0.7,
401
+ children: /*#__PURE__*/_jsx(Text, {
402
+ style: [styles.diffModeTabText, diffMode === "cause" && styles.diffModeTabTextActive],
403
+ children: "CAUSE"
404
+ })
405
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
406
+ style: [styles.diffModeTab, diffMode === "props" && styles.diffModeTabActive, !hasPropsData && styles.diffModeTabDisabled],
407
+ onPress: () => hasPropsData && setDiffMode("props"),
408
+ activeOpacity: hasPropsData ? 0.7 : 1,
409
+ children: /*#__PURE__*/_jsx(Text, {
410
+ style: [styles.diffModeTabText, diffMode === "props" && styles.diffModeTabTextActive, !hasPropsData && styles.diffModeTabTextDisabled],
411
+ children: "PROPS"
412
+ })
413
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
414
+ style: [styles.diffModeTab, diffMode === "state" && styles.diffModeTabActive, !hasStateData && styles.diffModeTabDisabled],
415
+ onPress: () => hasStateData && setDiffMode("state"),
416
+ activeOpacity: hasStateData ? 0.7 : 1,
417
+ children: /*#__PURE__*/_jsx(Text, {
418
+ style: [styles.diffModeTabText, diffMode === "state" && styles.diffModeTabTextActive, !hasStateData && styles.diffModeTabTextDisabled],
419
+ children: "STATE"
420
+ })
421
+ })]
422
+ }), diffMode === "cause" && /*#__PURE__*/_jsx(View, {
423
+ style: styles.diffSummary,
424
+ children: /*#__PURE__*/_jsx(EnhancedCauseDisplay, {
425
+ cause: currentEvent.cause,
426
+ nativeType: render.viewType
427
+ })
428
+ }), diffMode === "props" && /*#__PURE__*/_jsx(View, {
429
+ style: styles.treeDiffContainer,
430
+ children: hasPropsData ? /*#__PURE__*/_jsx(TreeDiffViewer, {
431
+ oldValue: previousEvent.capturedProps,
432
+ newValue: currentEvent.capturedProps,
433
+ theme: "dark",
434
+ showUnchanged: true
435
+ }) : /*#__PURE__*/_jsxs(View, {
436
+ style: styles.noDiffData,
437
+ children: [/*#__PURE__*/_jsx(Text, {
438
+ style: styles.noDiffDataTitle,
439
+ children: "Props Not Captured"
440
+ }), /*#__PURE__*/_jsx(Text, {
441
+ style: styles.noDiffDataText,
442
+ children: "Enable \"Capture Props on Render\" in settings to see props diff."
443
+ })]
444
+ })
445
+ }), diffMode === "state" && /*#__PURE__*/_jsx(View, {
446
+ style: styles.treeDiffContainer,
447
+ children: hasStateData ? /*#__PURE__*/_jsx(TreeDiffViewer, {
448
+ oldValue: previousEvent.capturedState,
449
+ newValue: currentEvent.capturedState,
450
+ theme: "dark",
451
+ showUnchanged: true
452
+ }) : /*#__PURE__*/_jsxs(View, {
453
+ style: styles.noDiffData,
454
+ children: [/*#__PURE__*/_jsx(Text, {
455
+ style: styles.noDiffDataTitle,
456
+ children: "State Not Captured"
457
+ }), /*#__PURE__*/_jsx(Text, {
458
+ style: styles.noDiffDataText,
459
+ children: "Enable \"Capture State on Render\" in settings to see state diff."
460
+ })]
461
+ })
462
+ })]
463
+ });
464
+ }
465
+
466
+ /**
467
+ * External footer component for modal integration
468
+ * Uses renderHistory for events (only available when history tracking is enabled)
469
+ */
470
+ export function RenderHistoryFooter({
471
+ render,
472
+ selectedEventIndex = 0,
473
+ onEventIndexChange = () => {},
474
+ isPro = false
475
+ }) {
476
+ // Get all events from render history (sorted by timestamp)
477
+ const allEvents = useMemo(() => {
478
+ if (!render.renderHistory || render.renderHistory.length === 0) {
479
+ return [];
480
+ }
481
+ return [...render.renderHistory].sort((a, b) => a.timestamp - b.timestamp);
482
+ }, [render.renderHistory]);
483
+
484
+ // Same cap as RenderHistoryViewer above; `isPro` remains a caller override.
485
+ const tierLimit = useLimit("highlightUpdates.historyEvents");
486
+ const eventLimit = isPro ? Infinity : tierLimit;
487
+
488
+ // Most recent N (negative index clamps to 0, so unlimited returns all).
489
+ const events = useMemo(() => allEvents.slice(-eventLimit), [allEvents, eventLimit]);
490
+ const totalEvents = events.length;
491
+ const currentEvent = events[selectedEventIndex];
492
+ const goToPrevious = useCallback(() => {
493
+ onEventIndexChange(Math.max(0, selectedEventIndex - 1));
494
+ }, [selectedEventIndex, onEventIndexChange]);
495
+ const goToNext = useCallback(() => {
496
+ onEventIndexChange(Math.min(totalEvents - 1, selectedEventIndex + 1));
497
+ }, [selectedEventIndex, totalEvents, onEventIndexChange]);
498
+
499
+ // Don't render if no history events (history tracking might be disabled)
500
+ if (totalEvents <= 1) {
501
+ return null;
502
+ }
503
+ return /*#__PURE__*/_jsx(EventStepperFooter, {
504
+ currentIndex: selectedEventIndex,
505
+ totalItems: totalEvents,
506
+ onPrevious: goToPrevious,
507
+ onNext: goToNext,
508
+ itemLabel: "Render",
509
+ subtitle: currentEvent ? formatRelativeTime(new Date(currentEvent.timestamp)) : undefined
510
+ });
511
+ }
512
+ const styles = StyleSheet.create({
513
+ container: {
514
+ flex: 1,
515
+ // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
516
+ backgroundColor: "transparent"
517
+ },
518
+ emptyContainer: {
519
+ flex: 1,
520
+ alignItems: "center",
521
+ justifyContent: "center",
522
+ padding: 32,
523
+ gap: 12
524
+ },
525
+ emptyTitle: {
526
+ fontSize: 16,
527
+ fontWeight: "600",
528
+ color: night.color.text
529
+ },
530
+ emptyText: {
531
+ fontSize: 13,
532
+ color: night.color.textSecondary,
533
+ textAlign: "center",
534
+ lineHeight: 18
535
+ },
536
+ viewToggleContainer: {
537
+ flexDirection: "row",
538
+ padding: 12,
539
+ gap: 8
540
+ },
541
+ viewToggleCard: {
542
+ flex: 1,
543
+ backgroundColor: night.color.surface,
544
+ borderRadius: 8,
545
+ borderWidth: 1,
546
+ borderColor: night.color.border,
547
+ padding: 10,
548
+ gap: 4
549
+ },
550
+ viewToggleCardActive: {
551
+ borderColor: night.color.accentBorderStrong,
552
+ backgroundColor: night.color.accentSoft
553
+ },
554
+ viewToggleCardDisabled: {
555
+ opacity: 0.5
556
+ },
557
+ todoBadge: {
558
+ backgroundColor: night.color.fillSecondary,
559
+ paddingHorizontal: 6,
560
+ paddingVertical: 2,
561
+ borderRadius: 4,
562
+ marginLeft: 4
563
+ },
564
+ todoBadgeText: {
565
+ fontSize: 9,
566
+ fontWeight: "700",
567
+ color: night.color.textTertiary,
568
+ letterSpacing: 0.3
569
+ },
570
+ viewToggleContent: {
571
+ flexDirection: "row",
572
+ alignItems: "center",
573
+ gap: 6
574
+ },
575
+ viewToggleLabel: {
576
+ fontSize: 10,
577
+ fontWeight: "700",
578
+ color: night.color.textSecondary,
579
+ letterSpacing: 0.5
580
+ },
581
+ viewToggleLabelActive: {
582
+ color: night.color.accent
583
+ },
584
+ viewToggleLabelDisabled: {
585
+ color: night.color.textTertiary
586
+ },
587
+ viewToggleDescription: {
588
+ fontSize: 11,
589
+ color: night.color.textTertiary,
590
+ marginTop: 2
591
+ },
592
+ contentScroll: {
593
+ flex: 1
594
+ },
595
+ contentContainer: {
596
+ padding: 12,
597
+ paddingTop: 0
598
+ },
599
+ noEventContainer: {
600
+ padding: 24,
601
+ alignItems: "center"
602
+ },
603
+ noEventText: {
604
+ fontSize: 13,
605
+ color: night.color.textTertiary
606
+ },
607
+ currentStateContainer: {
608
+ gap: 12
609
+ },
610
+ eventHeader: {
611
+ flexDirection: "row",
612
+ alignItems: "center",
613
+ justifyContent: "space-between",
614
+ backgroundColor: night.color.surface,
615
+ borderRadius: 8,
616
+ borderWidth: 1,
617
+ borderColor: night.color.border,
618
+ padding: 12
619
+ },
620
+ eventHeaderLeft: {
621
+ gap: 2
622
+ },
623
+ eventTitle: {
624
+ fontSize: 15,
625
+ fontWeight: "700",
626
+ color: night.color.text
627
+ },
628
+ eventTime: {
629
+ fontSize: 11,
630
+ color: night.color.textTertiary,
631
+ fontFamily: monoFont
632
+ },
633
+ eventBadge: {
634
+ flexDirection: "row",
635
+ alignItems: "center",
636
+ paddingHorizontal: 10,
637
+ paddingVertical: 4,
638
+ borderRadius: 12,
639
+ gap: 6
640
+ },
641
+ eventBadgeDot: {
642
+ width: 6,
643
+ height: 6,
644
+ borderRadius: 3
645
+ },
646
+ eventBadgeText: {
647
+ fontSize: 10,
648
+ fontWeight: "700",
649
+ fontFamily: monoFont
650
+ },
651
+ section: {
652
+ backgroundColor: night.color.surface,
653
+ borderRadius: 8,
654
+ borderWidth: 1,
655
+ borderColor: night.color.border,
656
+ overflow: "hidden"
657
+ },
658
+ sectionContent: {
659
+ padding: 12,
660
+ paddingTop: 8
661
+ },
662
+ causeContainer: {
663
+ marginBottom: 12
664
+ },
665
+ changedList: {
666
+ marginBottom: 12,
667
+ paddingLeft: 4
668
+ },
669
+ changedListTitle: {
670
+ fontSize: 11,
671
+ fontWeight: "600",
672
+ color: night.color.textSecondary,
673
+ marginBottom: 6
674
+ },
675
+ changedItem: {
676
+ fontSize: 12,
677
+ color: night.color.text,
678
+ fontFamily: monoFont,
679
+ marginBottom: 3,
680
+ paddingLeft: 8
681
+ },
682
+ propsScrollView: {
683
+ maxHeight: 150
684
+ },
685
+ propsJson: {
686
+ fontSize: 11,
687
+ fontFamily: monoFont,
688
+ color: night.color.text,
689
+ // Inset code box — the night track color.
690
+ backgroundColor: night.color.bg,
691
+ padding: 10,
692
+ borderRadius: 6
693
+ },
694
+ diffContainer: {
695
+ gap: 12
696
+ },
697
+ compareBar: {
698
+ flexDirection: "row",
699
+ backgroundColor: night.color.surface,
700
+ borderRadius: 8,
701
+ borderWidth: 1,
702
+ borderColor: night.color.border,
703
+ padding: 10
704
+ },
705
+ compareSide: {
706
+ flex: 1,
707
+ gap: 4
708
+ },
709
+ compareLabelRow: {
710
+ flexDirection: "row",
711
+ alignItems: "center",
712
+ gap: 8
713
+ },
714
+ compareLabel: {
715
+ fontSize: 10,
716
+ fontWeight: "700",
717
+ letterSpacing: 0.5
718
+ },
719
+ compareActionBadge: {
720
+ paddingHorizontal: 6,
721
+ paddingVertical: 2,
722
+ borderRadius: 4
723
+ },
724
+ compareActionText: {
725
+ fontSize: 9,
726
+ fontWeight: "600"
727
+ },
728
+ compareMeta: {
729
+ flexDirection: "row",
730
+ alignItems: "center",
731
+ gap: 6
732
+ },
733
+ compareIndex: {
734
+ fontSize: 12,
735
+ fontWeight: "600",
736
+ color: night.color.text
737
+ },
738
+ compareTime: {
739
+ fontSize: 10,
740
+ color: night.color.textTertiary,
741
+ fontFamily: monoFont
742
+ },
743
+ compareDivider: {
744
+ width: 30,
745
+ alignItems: "center",
746
+ justifyContent: "center"
747
+ },
748
+ compareArrow: {
749
+ fontSize: 16,
750
+ color: night.color.textTertiary
751
+ },
752
+ diffSummary: {
753
+ backgroundColor: night.color.surface,
754
+ borderRadius: 8,
755
+ borderWidth: 1,
756
+ borderColor: night.color.border,
757
+ padding: 12
758
+ },
759
+ diffSummaryTitle: {
760
+ fontSize: 11,
761
+ fontWeight: "600",
762
+ color: night.color.textSecondary,
763
+ marginBottom: 8
764
+ },
765
+ diffChanges: {
766
+ gap: 4
767
+ },
768
+ diffChangeItem: {
769
+ flexDirection: "row",
770
+ alignItems: "center",
771
+ gap: 6
772
+ },
773
+ diffChangeIcon: {
774
+ fontSize: 12,
775
+ fontWeight: "700",
776
+ color: night.color.warning,
777
+ fontFamily: monoFont
778
+ },
779
+ diffChangeKey: {
780
+ fontSize: 12,
781
+ color: night.color.text,
782
+ fontFamily: monoFont
783
+ },
784
+ diffNoChanges: {
785
+ fontSize: 12,
786
+ color: night.color.textTertiary,
787
+ fontStyle: "italic"
788
+ },
789
+ diffPlaceholder: {
790
+ fontSize: 12,
791
+ color: night.color.textTertiary,
792
+ fontStyle: "italic",
793
+ textAlign: "center",
794
+ padding: 16
795
+ },
796
+ // Diff mode tabs
797
+ diffModeTabs: {
798
+ flexDirection: "row",
799
+ backgroundColor: night.color.surface,
800
+ borderRadius: 8,
801
+ borderWidth: 1,
802
+ borderColor: night.color.border,
803
+ padding: 4,
804
+ gap: 4
805
+ },
806
+ diffModeTab: {
807
+ flex: 1,
808
+ paddingVertical: 8,
809
+ paddingHorizontal: 12,
810
+ borderRadius: 6,
811
+ alignItems: "center"
812
+ },
813
+ diffModeTabActive: {
814
+ backgroundColor: night.color.accentSoft
815
+ },
816
+ diffModeTabDisabled: {
817
+ opacity: 0.4
818
+ },
819
+ diffModeTabText: {
820
+ fontSize: 10,
821
+ fontWeight: "600",
822
+ color: night.color.textSecondary,
823
+ letterSpacing: 0.5
824
+ },
825
+ diffModeTabTextActive: {
826
+ color: night.color.accent
827
+ },
828
+ diffModeTabTextDisabled: {
829
+ color: night.color.textTertiary
830
+ },
831
+ // Cause diff styles
832
+ causeDiffContainer: {
833
+ gap: 8,
834
+ marginBottom: 12
835
+ },
836
+ causeDiffRow: {
837
+ flexDirection: "row",
838
+ alignItems: "center",
839
+ gap: 8
840
+ },
841
+ causeDiffLabel: {
842
+ fontSize: 11,
843
+ fontWeight: "600",
844
+ color: night.color.textSecondary,
845
+ minWidth: 80
846
+ },
847
+ causeDiffValue: {
848
+ fontSize: 12,
849
+ color: night.color.text,
850
+ fontFamily: monoFont,
851
+ fontWeight: "500"
852
+ },
853
+ diffChangesTitle: {
854
+ fontSize: 11,
855
+ fontWeight: "600",
856
+ color: night.color.textSecondary,
857
+ marginBottom: 6
858
+ },
859
+ // Tree diff container
860
+ treeDiffContainer: {
861
+ backgroundColor: night.color.surface,
862
+ borderRadius: 8,
863
+ borderWidth: 1,
864
+ borderColor: night.color.border,
865
+ overflow: "hidden",
866
+ minHeight: 150
867
+ },
868
+ noDiffData: {
869
+ padding: 24,
870
+ alignItems: "center",
871
+ gap: 8
872
+ },
873
+ noDiffDataTitle: {
874
+ fontSize: 13,
875
+ fontWeight: "600",
876
+ color: night.color.text
877
+ },
878
+ noDiffDataText: {
879
+ fontSize: 12,
880
+ color: night.color.textTertiary,
881
+ textAlign: "center",
882
+ lineHeight: 18
883
+ },
884
+ lockedBanner: {
885
+ flexDirection: "row",
886
+ alignItems: "center",
887
+ justifyContent: "center",
888
+ paddingVertical: 10,
889
+ paddingHorizontal: 16,
890
+ marginHorizontal: 12,
891
+ marginTop: 8,
892
+ backgroundColor: night.color.warningSoft,
893
+ borderRadius: 8,
894
+ borderWidth: 1,
895
+ borderColor: night.color.warning + "30",
896
+ gap: 8
897
+ },
898
+ lockedBannerText: {
899
+ color: night.color.warning,
900
+ fontSize: 13,
901
+ fontWeight: "500"
902
+ },
903
+ lockedBannerSubtext: {
904
+ color: night.color.textTertiary,
905
+ fontSize: 12
906
+ }
907
+ });
908
+ export default RenderHistoryViewer;