@buoy-gg/benchmark 2.1.3 → 2.1.4-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/lib/commonjs/benchmarking/BenchmarkComparator.js +221 -1
  2. package/lib/commonjs/benchmarking/BenchmarkRecorder.js +497 -1
  3. package/lib/commonjs/benchmarking/BenchmarkStorage.js +235 -1
  4. package/lib/commonjs/benchmarking/index.js +83 -1
  5. package/lib/commonjs/benchmarking/types.js +13 -1
  6. package/lib/commonjs/components/BenchmarkCompareView.js +475 -1
  7. package/lib/commonjs/components/BenchmarkDetailView.js +346 -1
  8. package/lib/commonjs/components/BenchmarkModal.js +505 -1
  9. package/lib/commonjs/components/BenchmarkSessionCard.js +193 -1
  10. package/lib/commonjs/index.js +62 -1
  11. package/lib/commonjs/preset.js +86 -1
  12. package/lib/module/benchmarking/BenchmarkComparator.js +216 -1
  13. package/lib/module/benchmarking/BenchmarkRecorder.js +493 -1
  14. package/lib/module/benchmarking/BenchmarkStorage.js +227 -1
  15. package/lib/module/benchmarking/index.js +48 -1
  16. package/lib/module/benchmarking/types.js +13 -1
  17. package/lib/module/components/BenchmarkCompareView.js +469 -1
  18. package/lib/module/components/BenchmarkDetailView.js +340 -1
  19. package/lib/module/components/BenchmarkModal.js +499 -1
  20. package/lib/module/components/BenchmarkSessionCard.js +187 -1
  21. package/lib/module/index.js +39 -1
  22. package/lib/module/preset.js +81 -1
  23. package/lib/typescript/benchmarking/BenchmarkComparator.d.ts.map +1 -0
  24. package/lib/typescript/benchmarking/BenchmarkRecorder.d.ts.map +1 -0
  25. package/lib/typescript/benchmarking/BenchmarkStorage.d.ts.map +1 -0
  26. package/lib/typescript/benchmarking/index.d.ts.map +1 -0
  27. package/lib/typescript/benchmarking/types.d.ts.map +1 -0
  28. package/lib/typescript/components/BenchmarkCompareView.d.ts.map +1 -0
  29. package/lib/typescript/components/BenchmarkDetailView.d.ts.map +1 -0
  30. package/lib/typescript/components/BenchmarkModal.d.ts.map +1 -0
  31. package/lib/typescript/components/BenchmarkSessionCard.d.ts.map +1 -0
  32. package/lib/typescript/index.d.ts.map +1 -0
  33. package/lib/typescript/preset.d.ts.map +1 -0
  34. package/package.json +2 -2
@@ -1 +1,505 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.BenchmarkModal=BenchmarkModal,exports.default=void 0;var _react=_interopRequireWildcard(require("react")),_reactNative=require("react-native"),_sharedUi=require("@buoy-gg/shared-ui"),_benchmarking=require("../benchmarking"),_BenchmarkSessionCard=require("./BenchmarkSessionCard"),_BenchmarkDetailView=require("./BenchmarkDetailView"),_BenchmarkCompareView=require("./BenchmarkCompareView"),_jsxRuntime=require("react/jsx-runtime");function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var a=new WeakMap,r=new WeakMap;return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var s,o,i={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return i;if(s=t?r:a){if(s.has(e))return s.get(e);s.set(e,i)}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((o=(s=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(o.get||o.set)?s(i,t,o):i[t]=e[t]);return i})(e,t)}const storageAdapter=(0,_benchmarking.createAsyncStorageAdapter)(),storage=storageAdapter?new _benchmarking.BenchmarkStorage(storageAdapter):null;function BenchmarkModal({visible:e,onClose:t,onBack:a,onMinimize:r,enableSharedModalDimensions:s=!1}){const[o,i]=(0,_react.useState)(!1),[n,c]=(0,_react.useState)([]),[l,m]=(0,_react.useState)(null),[d,u]=(0,_react.useState)("list"),[_,h]=(0,_react.useState)(!1),[p,x]=(0,_react.useState)(new Set),[g,y]=(0,_react.useState)(null),[j,R]=(0,_react.useState)(0);(0,_react.useRef)(!1),(0,_react.useEffect)(()=>{e&&storage&&(async()=>{const e=await storage.listReports();c(e)})()},[e,j]),(0,_react.useEffect)(()=>{const e=_benchmarking.benchmarkRecorder.subscribe(e=>{"start"===e?i(!0):"stop"===e&&(i(!1),R(e=>e+1))});return i(_benchmarking.benchmarkRecorder.isRecording()),e},[]);const b=(0,_react.useCallback)(async()=>{if(o){const e=_benchmarking.benchmarkRecorder.stopSession();e&&storage&&(await storage.saveReport(e),R(e=>e+1))}else{const e=(new Date).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});_benchmarking.benchmarkRecorder.startSession({name:`Benchmark ${e}`,captureMemory:!0,verbose:!1})}},[o]),S=(0,_react.useCallback)(async e=>{if(_)x(t=>{const a=new Set(t);return a.has(e.id)?a.delete(e.id):a.size<2&&a.add(e.id),a});else{if(!storage)return;const t=await storage.loadReport(e.id);t&&(m(t),u("detail"))}},[_]),C=(0,_react.useCallback)(e=>{h(!0),x(new Set([e.id]))},[]),T=(0,_react.useCallback)(async()=>{if(2!==p.size||!storage)return;const e=Array.from(p),[t,a]=await Promise.all([storage.loadReport(e[0]),storage.loadReport(e[1])]);if(t&&a){const[e,r]=t.createdAt<a.createdAt?[t,a]:[a,t];y({baseline:e,comparison:r}),u("compare"),h(!1),x(new Set)}},[p]),k=(0,_react.useCallback)(async()=>{storage&&(await storage.clearAll(),R(e=>e+1))},[]),f=(0,_react.useCallback)(async()=>{0!==p.size&&storage&&(await Promise.all(Array.from(p).map(e=>storage.deleteReport(e))),x(new Set),h(!1),R(e=>e+1))},[p]),v=(0,_react.useCallback)(()=>{"detail"===d||"compare"===d?(u("list"),m(null),y(null)):_?(h(!1),x(new Set)):a&&a()},[d,_,a]),B=(0,_react.useCallback)(()=>{l&&storage&&_reactNative.Alert.prompt("Rename Benchmark","Enter a new name for this benchmark:",[{text:"Cancel",style:"cancel"},{text:"Save",onPress:async e=>{e&&e.trim()&&(await storage.updateReport(l.id,{name:e.trim()}),m({...l,name:e.trim()}),R(e=>e+1))}}],"plain-text","")},[l]),O=(0,_react.useCallback)(async()=>{l&&storage&&(await storage.deleteReport(l.id),u("list"),m(null),R(e=>e+1))},[l]),w=(0,_react.useCallback)(async()=>{if(!g)return;const{baseline:e,comparison:t}=g,a=_benchmarking.BenchmarkComparator.compare(e,t),r=e=>e<1?`${(1e3*e).toFixed(0)}μs`:e<10?`${e.toFixed(2)}ms`:e<100?`${e.toFixed(1)}ms`:`${e.toFixed(0)}ms`,s=e=>`${e>=0?"+":""}${e.toFixed(1)}%`,o=["BENCHMARK COMPARISON RESULTS","============================","",`Overall: ${a.isImproved?"IMPROVED":"REGRESSED"} ${s(a.overallImprovement)}`,"","COMPARING","---------",`Baseline: ${e.name}`,` Date: ${new Date(e.createdAt).toLocaleString()}`,`Comparison: ${t.name}`,` Date: ${new Date(t.createdAt).toLocaleString()}`,"","TIMING COMPARISON","-----------------",`Measure Time: ${r(e.stats.avgMeasureTime)} → ${r(t.stats.avgMeasureTime)} (${s(a.measureTimeImprovement)})`,`Pipeline Time: ${r(e.stats.avgTotalTime)} → ${r(t.stats.avgTotalTime)} (${s(a.pipelineTimeImprovement)})`,`Filter Time: ${r(e.stats.avgFilterTime)} → ${r(t.stats.avgFilterTime)} (${s(a.filterTimeImprovement)})`,`Track Time: ${r(e.stats.avgTrackTime)} → ${r(t.stats.avgTrackTime)} (${s(a.trackTimeImprovement)})`,`Overlay Render: ${r(e.stats.avgOverlayRenderTime)} → ${r(t.stats.avgOverlayRenderTime)} (${s(a.overlayRenderImprovement)})`,"","PERCENTILES","-----------",`P50 (Median): ${r(e.stats.p50TotalTime)} → ${r(t.stats.p50TotalTime)}`,`P95: ${r(e.stats.p95TotalTime)} → ${r(t.stats.p95TotalTime)}`,`P99: ${r(e.stats.p99TotalTime)} → ${r(t.stats.p99TotalTime)}`,"","BATCH STATISTICS","----------------",`Total Batches: ${e.stats.batchCount} → ${t.stats.batchCount}`,`Nodes Processed: ${e.stats.totalNodesProcessed.toLocaleString()} → ${t.stats.totalNodesProcessed.toLocaleString()}`];null!=e.memoryDelta&&null!=t.memoryDelta&&o.push("","MEMORY","------",`Memory Delta: ${(e.memoryDelta/1024/1024).toFixed(2)}MB → ${(t.memoryDelta/1024/1024).toFixed(2)}MB`),o.push("",`Compared at: ${new Date(a.comparedAt).toLocaleString()}`),await(0,_sharedUi.copyToClipboard)(o.join("\n"))},[g]),$=(0,_react.useCallback)(({item:e})=>(0,_jsxRuntime.jsx)(_BenchmarkSessionCard.BenchmarkSessionCard,{metadata:e,isSelected:p.has(e.id),onPress:()=>S(e),onLongPress:()=>C(e),selectionMode:_}),[_,p,S,C]),U=(0,_react.useCallback)(e=>e.id,[]),M=s?_sharedUi.devToolsStorageKeys.modal.root():_sharedUi.devToolsStorageKeys.benchmark.modal(),N=(0,_react.useMemo)(()=>"detail"===d&&l?l.name:"compare"===d?"Compare":_?0===p.size?"Select 2 to compare":1===p.size?"Select 1 more":`${p.size} Selected`:"Benchmarks",[d,l,_,p]),P="list"!==d||_||a;return e?(0,_jsxRuntime.jsxs)(_sharedUi.JsModal,{visible:e,onClose:t,onBack:P?v:void 0,onMinimize:r,persistenceKey:M,header:{showToggleButton:!0,customContent:(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[P&&(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:v}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:N}),(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions,{children:["list"===d&&!_&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_sharedUi.PowerToggleButton,{isEnabled:o,onToggle:b,accessibilityLabel:"Toggle benchmark recording"}),n.length>=2&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>h(!0),style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.GitBranch,{size:14,color:_sharedUi.macOSColors.semantic.info})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>R(e=>e+1),style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.RefreshCw,{size:14,color:_sharedUi.macOSColors.text.secondary})}),n.length>0&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:k,style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Trash2,{size:14,color:_sharedUi.macOSColors.semantic.error})})]}),"detail"===d&&l&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:B,style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Edit3,{size:14,color:_sharedUi.macOSColors.text.secondary})}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:O,style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Trash2,{size:14,color:_sharedUi.macOSColors.semantic.error})})]}),"compare"===d&&g&&(0,_jsxRuntime.jsx)(_jsxRuntime.Fragment,{children:(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:w,style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Copy,{size:14,color:_sharedUi.macOSColors.text.secondary})})}),_&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[2===p.size&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:T,style:[styles.iconButton,styles.compareButton],children:(0,_jsxRuntime.jsx)(_sharedUi.GitBranch,{size:14,color:_sharedUi.macOSColors.semantic.info})}),p.size>0&&(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:f,style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Trash2,{size:14,color:_sharedUi.macOSColors.semantic.error})})]})]})]})},enablePersistence:!0,initialMode:"bottomSheet",enableGlitchEffects:!0,children:[o&&"list"===d&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.recordingBanner,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.recordingDot}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.recordingText,children:"Recording..."}),(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:b,style:styles.stopButton,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.stopButtonText,children:"Stop"})})]}),"detail"===d&&l?(0,_jsxRuntime.jsx)(_BenchmarkDetailView.BenchmarkDetailView,{report:l}):"compare"===d&&g?(0,_jsxRuntime.jsx)(_BenchmarkCompareView.BenchmarkCompareView,{baseline:g.baseline,comparison:g.comparison}):0===n.length?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.emptyState,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyIcon,children:"📊"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyTitle,children:"No Benchmarks Yet"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptySubtitle,children:"Press the record button to start capturing performance metrics"})]}):(0,_jsxRuntime.jsx)(_reactNative.FlatList,{data:n,renderItem:$,keyExtractor:U,contentContainerStyle:styles.listContent,ItemSeparatorComponent:()=>(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.separator}),scrollEnabled:!1})]}):null}const styles=_reactNative.StyleSheet.create({iconButton:{width:32,height:32,borderRadius:8,backgroundColor:_sharedUi.macOSColors.background.hover,borderWidth:1,borderColor:_sharedUi.macOSColors.border.default,alignItems:"center",justifyContent:"center"},compareButton:{backgroundColor:_sharedUi.macOSColors.semantic.infoBackground,borderColor:_sharedUi.macOSColors.semantic.info},listContent:{paddingVertical:16},separator:{height:8},emptyState:{flex:1,justifyContent:"center",alignItems:"center",padding:32},emptyIcon:{fontSize:48,marginBottom:16},emptyTitle:{fontSize:16,fontWeight:"600",color:_sharedUi.macOSColors.text.primary,fontFamily:"monospace",marginBottom:8},emptySubtitle:{fontSize:13,color:_sharedUi.macOSColors.text.secondary,fontFamily:"monospace",textAlign:"center"},recordingBanner:{flexDirection:"row",alignItems:"center",backgroundColor:_sharedUi.macOSColors.semantic.errorBackground,paddingHorizontal:16,paddingVertical:12,borderBottomWidth:1,borderColor:_sharedUi.macOSColors.semantic.error},recordingDot:{width:8,height:8,borderRadius:4,backgroundColor:_sharedUi.macOSColors.semantic.error,marginRight:8},recordingText:{flex:1,fontSize:13,fontWeight:"600",color:_sharedUi.macOSColors.semantic.error,fontFamily:"monospace"},stopButton:{paddingHorizontal:12,paddingVertical:6,backgroundColor:_sharedUi.macOSColors.semantic.error,borderRadius:4},stopButtonText:{fontSize:12,fontWeight:"600",color:"#fff",fontFamily:"monospace"}});var _default=exports.default=BenchmarkModal;
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.BenchmarkModal = BenchmarkModal;
7
+ exports.default = void 0;
8
+ var _react = _interopRequireWildcard(require("react"));
9
+ var _reactNative = require("react-native");
10
+ var _sharedUi = require("@buoy-gg/shared-ui");
11
+ var _benchmarking = require("../benchmarking");
12
+ var _BenchmarkSessionCard = require("./BenchmarkSessionCard");
13
+ var _BenchmarkDetailView = require("./BenchmarkDetailView");
14
+ var _BenchmarkCompareView = require("./BenchmarkCompareView");
15
+ var _jsxRuntime = require("react/jsx-runtime");
16
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
17
+ /**
18
+ * BenchmarkModal
19
+ *
20
+ * Main modal component for the Benchmark dev tool.
21
+ * Provides recording controls, session list, detail views, and comparison.
22
+ */
23
+
24
+ // Initialize storage
25
+ const storageAdapter = (0, _benchmarking.createAsyncStorageAdapter)();
26
+ const storage = storageAdapter ? new _benchmarking.BenchmarkStorage(storageAdapter) : null;
27
+ function BenchmarkModal({
28
+ visible,
29
+ onClose,
30
+ onBack,
31
+ onMinimize,
32
+ enableSharedModalDimensions = false
33
+ }) {
34
+ // State
35
+ const [isRecording, setIsRecording] = (0, _react.useState)(false);
36
+ const [reports, setReports] = (0, _react.useState)([]);
37
+ const [selectedReport, setSelectedReport] = (0, _react.useState)(null);
38
+ const [viewMode, setViewMode] = (0, _react.useState)("list");
39
+ const [selectionMode, setSelectionMode] = (0, _react.useState)(false);
40
+ const [selectedIds, setSelectedIds] = (0, _react.useState)(new Set());
41
+ const [compareReports, setCompareReports] = (0, _react.useState)(null);
42
+ const [refreshKey, setRefreshKey] = (0, _react.useState)(0);
43
+ const hasLoadedState = (0, _react.useRef)(false);
44
+
45
+ // Load reports on mount
46
+ (0, _react.useEffect)(() => {
47
+ if (!visible || !storage) return;
48
+ const loadReports = async () => {
49
+ const list = await storage.listReports();
50
+ setReports(list);
51
+ };
52
+ loadReports();
53
+ }, [visible, refreshKey]);
54
+
55
+ // Subscribe to recorder state changes
56
+ (0, _react.useEffect)(() => {
57
+ const unsubscribe = _benchmarking.benchmarkRecorder.subscribe(event => {
58
+ if (event === "start") {
59
+ setIsRecording(true);
60
+ } else if (event === "stop") {
61
+ setIsRecording(false);
62
+ // Refresh list after stopping
63
+ setRefreshKey(k => k + 1);
64
+ }
65
+ });
66
+
67
+ // Sync initial state
68
+ setIsRecording(_benchmarking.benchmarkRecorder.isRecording());
69
+ return unsubscribe;
70
+ }, []);
71
+
72
+ // Handle start/stop recording
73
+ const handleToggleRecording = (0, _react.useCallback)(async () => {
74
+ if (isRecording) {
75
+ // Stop and save
76
+ const report = _benchmarking.benchmarkRecorder.stopSession();
77
+ if (report && storage) {
78
+ await storage.saveReport(report);
79
+ setRefreshKey(k => k + 1);
80
+ }
81
+ } else {
82
+ // Start new session
83
+ const timestamp = new Date().toLocaleString(undefined, {
84
+ month: "short",
85
+ day: "numeric",
86
+ hour: "2-digit",
87
+ minute: "2-digit"
88
+ });
89
+ _benchmarking.benchmarkRecorder.startSession({
90
+ name: `Benchmark ${timestamp}`,
91
+ captureMemory: true,
92
+ verbose: false
93
+ });
94
+ }
95
+ }, [isRecording]);
96
+
97
+ // Handle report press
98
+ const handleReportPress = (0, _react.useCallback)(async metadata => {
99
+ if (selectionMode) {
100
+ // Toggle selection
101
+ setSelectedIds(prev => {
102
+ const next = new Set(prev);
103
+ if (next.has(metadata.id)) {
104
+ next.delete(metadata.id);
105
+ } else {
106
+ // Max 2 selections for comparison
107
+ if (next.size < 2) {
108
+ next.add(metadata.id);
109
+ }
110
+ }
111
+ return next;
112
+ });
113
+ } else {
114
+ // Load and show detail
115
+ if (!storage) return;
116
+ const report = await storage.loadReport(metadata.id);
117
+ if (report) {
118
+ setSelectedReport(report);
119
+ setViewMode("detail");
120
+ }
121
+ }
122
+ }, [selectionMode]);
123
+
124
+ // Handle long press to enter selection mode
125
+ const handleReportLongPress = (0, _react.useCallback)(metadata => {
126
+ setSelectionMode(true);
127
+ setSelectedIds(new Set([metadata.id]));
128
+ }, []);
129
+
130
+ // Handle compare button
131
+ const handleCompare = (0, _react.useCallback)(async () => {
132
+ if (selectedIds.size !== 2 || !storage) return;
133
+ const ids = Array.from(selectedIds);
134
+ const [report1, report2] = await Promise.all([storage.loadReport(ids[0]), storage.loadReport(ids[1])]);
135
+ if (report1 && report2) {
136
+ // Older report is baseline, newer is comparison
137
+ const [baseline, comparison] = report1.createdAt < report2.createdAt ? [report1, report2] : [report2, report1];
138
+ setCompareReports({
139
+ baseline,
140
+ comparison
141
+ });
142
+ setViewMode("compare");
143
+ setSelectionMode(false);
144
+ setSelectedIds(new Set());
145
+ }
146
+ }, [selectedIds]);
147
+
148
+ // Handle clear all
149
+ const handleClearAll = (0, _react.useCallback)(async () => {
150
+ if (storage) {
151
+ await storage.clearAll();
152
+ setRefreshKey(k => k + 1);
153
+ }
154
+ }, []);
155
+
156
+ // Handle delete selected
157
+ const handleDeleteSelected = (0, _react.useCallback)(async () => {
158
+ if (selectedIds.size === 0 || !storage) return;
159
+ await Promise.all(Array.from(selectedIds).map(id => storage.deleteReport(id)));
160
+ setSelectedIds(new Set());
161
+ setSelectionMode(false);
162
+ setRefreshKey(k => k + 1);
163
+ }, [selectedIds]);
164
+
165
+ // Handle back navigation
166
+ const handleBack = (0, _react.useCallback)(() => {
167
+ if (viewMode === "detail" || viewMode === "compare") {
168
+ setViewMode("list");
169
+ setSelectedReport(null);
170
+ setCompareReports(null);
171
+ } else if (selectionMode) {
172
+ setSelectionMode(false);
173
+ setSelectedIds(new Set());
174
+ } else if (onBack) {
175
+ onBack();
176
+ }
177
+ }, [viewMode, selectionMode, onBack]);
178
+
179
+ // Handle rename current report
180
+ const handleRename = (0, _react.useCallback)(() => {
181
+ if (!selectedReport || !storage) return;
182
+ _reactNative.Alert.prompt("Rename Benchmark", "Enter a new name for this benchmark:", [{
183
+ text: "Cancel",
184
+ style: "cancel"
185
+ }, {
186
+ text: "Save",
187
+ onPress: async newName => {
188
+ if (newName && newName.trim()) {
189
+ await storage.updateReport(selectedReport.id, {
190
+ name: newName.trim()
191
+ });
192
+ // Update local state
193
+ setSelectedReport({
194
+ ...selectedReport,
195
+ name: newName.trim()
196
+ });
197
+ setRefreshKey(k => k + 1);
198
+ }
199
+ }
200
+ }], "plain-text", "");
201
+ }, [selectedReport]);
202
+
203
+ // Handle delete current report
204
+ const handleDeleteCurrent = (0, _react.useCallback)(async () => {
205
+ if (!selectedReport || !storage) return;
206
+ await storage.deleteReport(selectedReport.id);
207
+ setViewMode("list");
208
+ setSelectedReport(null);
209
+ setRefreshKey(k => k + 1);
210
+ }, [selectedReport]);
211
+
212
+ // Handle copy compare results
213
+ const handleCopyCompare = (0, _react.useCallback)(async () => {
214
+ if (!compareReports) return;
215
+ const {
216
+ baseline,
217
+ comparison
218
+ } = compareReports;
219
+ const result = _benchmarking.BenchmarkComparator.compare(baseline, comparison);
220
+ const formatMs = ms => {
221
+ if (ms < 1) return `${(ms * 1000).toFixed(0)}μs`;
222
+ if (ms < 10) return `${ms.toFixed(2)}ms`;
223
+ if (ms < 100) return `${ms.toFixed(1)}ms`;
224
+ return `${ms.toFixed(0)}ms`;
225
+ };
226
+ const formatPercent = value => {
227
+ const sign = value >= 0 ? "+" : "";
228
+ return `${sign}${value.toFixed(1)}%`;
229
+ };
230
+ const lines = ["BENCHMARK COMPARISON RESULTS", "============================", "", `Overall: ${result.isImproved ? "IMPROVED" : "REGRESSED"} ${formatPercent(result.overallImprovement)}`, "", "COMPARING", "---------", `Baseline: ${baseline.name}`, ` Date: ${new Date(baseline.createdAt).toLocaleString()}`, `Comparison: ${comparison.name}`, ` Date: ${new Date(comparison.createdAt).toLocaleString()}`, "", "TIMING COMPARISON", "-----------------", `Measure Time: ${formatMs(baseline.stats.avgMeasureTime)} → ${formatMs(comparison.stats.avgMeasureTime)} (${formatPercent(result.measureTimeImprovement)})`, `Pipeline Time: ${formatMs(baseline.stats.avgTotalTime)} → ${formatMs(comparison.stats.avgTotalTime)} (${formatPercent(result.pipelineTimeImprovement)})`, `Filter Time: ${formatMs(baseline.stats.avgFilterTime)} → ${formatMs(comparison.stats.avgFilterTime)} (${formatPercent(result.filterTimeImprovement)})`, `Track Time: ${formatMs(baseline.stats.avgTrackTime)} → ${formatMs(comparison.stats.avgTrackTime)} (${formatPercent(result.trackTimeImprovement)})`, `Overlay Render: ${formatMs(baseline.stats.avgOverlayRenderTime)} → ${formatMs(comparison.stats.avgOverlayRenderTime)} (${formatPercent(result.overlayRenderImprovement)})`, "", "PERCENTILES", "-----------", `P50 (Median): ${formatMs(baseline.stats.p50TotalTime)} → ${formatMs(comparison.stats.p50TotalTime)}`, `P95: ${formatMs(baseline.stats.p95TotalTime)} → ${formatMs(comparison.stats.p95TotalTime)}`, `P99: ${formatMs(baseline.stats.p99TotalTime)} → ${formatMs(comparison.stats.p99TotalTime)}`, "", "BATCH STATISTICS", "----------------", `Total Batches: ${baseline.stats.batchCount} → ${comparison.stats.batchCount}`, `Nodes Processed: ${baseline.stats.totalNodesProcessed.toLocaleString()} → ${comparison.stats.totalNodesProcessed.toLocaleString()}`];
231
+ if (baseline.memoryDelta != null && comparison.memoryDelta != null) {
232
+ lines.push("", "MEMORY", "------", `Memory Delta: ${(baseline.memoryDelta / 1024 / 1024).toFixed(2)}MB → ${(comparison.memoryDelta / 1024 / 1024).toFixed(2)}MB`);
233
+ }
234
+ lines.push("", `Compared at: ${new Date(result.comparedAt).toLocaleString()}`);
235
+ await (0, _sharedUi.copyToClipboard)(lines.join("\n"));
236
+ }, [compareReports]);
237
+
238
+ // Render list item
239
+ const renderItem = (0, _react.useCallback)(({
240
+ item
241
+ }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_BenchmarkSessionCard.BenchmarkSessionCard, {
242
+ metadata: item,
243
+ isSelected: selectedIds.has(item.id),
244
+ onPress: () => handleReportPress(item),
245
+ onLongPress: () => handleReportLongPress(item),
246
+ selectionMode: selectionMode
247
+ }), [selectionMode, selectedIds, handleReportPress, handleReportLongPress]);
248
+ const keyExtractor = (0, _react.useCallback)(item => item.id, []);
249
+
250
+ // Render content
251
+ const renderContent = () => {
252
+ if (viewMode === "detail" && selectedReport) {
253
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_BenchmarkDetailView.BenchmarkDetailView, {
254
+ report: selectedReport
255
+ });
256
+ }
257
+ if (viewMode === "compare" && compareReports) {
258
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_BenchmarkCompareView.BenchmarkCompareView, {
259
+ baseline: compareReports.baseline,
260
+ comparison: compareReports.comparison
261
+ });
262
+ }
263
+
264
+ // List view
265
+ if (reports.length === 0) {
266
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
267
+ style: styles.emptyState,
268
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
269
+ style: styles.emptyIcon,
270
+ children: "\uD83D\uDCCA"
271
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
272
+ style: styles.emptyTitle,
273
+ children: "No Benchmarks Yet"
274
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
275
+ style: styles.emptySubtitle,
276
+ children: "Press the record button to start capturing performance metrics"
277
+ })]
278
+ });
279
+ }
280
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
281
+ data: reports,
282
+ renderItem: renderItem,
283
+ keyExtractor: keyExtractor,
284
+ contentContainerStyle: styles.listContent,
285
+ ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
286
+ style: styles.separator
287
+ }),
288
+ scrollEnabled: false
289
+ });
290
+ };
291
+ const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : _sharedUi.devToolsStorageKeys.benchmark.modal();
292
+
293
+ // Header title based on view mode
294
+ const headerTitle = (0, _react.useMemo)(() => {
295
+ if (viewMode === "detail" && selectedReport) {
296
+ return selectedReport.name;
297
+ }
298
+ if (viewMode === "compare") {
299
+ return "Compare";
300
+ }
301
+ if (selectionMode) {
302
+ if (selectedIds.size === 0) {
303
+ return "Select 2 to compare";
304
+ }
305
+ if (selectedIds.size === 1) {
306
+ return "Select 1 more";
307
+ }
308
+ return `${selectedIds.size} Selected`;
309
+ }
310
+ return "Benchmarks";
311
+ }, [viewMode, selectedReport, selectionMode, selectedIds]);
312
+
313
+ // Determine back handler
314
+ const showBackButton = viewMode !== "list" || selectionMode || onBack;
315
+
316
+ // Early return AFTER all hooks to avoid "Rendered fewer hooks than expected" error
317
+ if (!visible) return null;
318
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.JsModal, {
319
+ visible: visible,
320
+ onClose: onClose,
321
+ onBack: showBackButton ? handleBack : undefined,
322
+ onMinimize: onMinimize,
323
+ persistenceKey: persistenceKey,
324
+ header: {
325
+ showToggleButton: true,
326
+ customContent: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
327
+ children: [showBackButton && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
328
+ onBack: handleBack
329
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
330
+ title: headerTitle
331
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
332
+ children: [viewMode === "list" && !selectionMode && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
333
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
334
+ isEnabled: isRecording,
335
+ onToggle: handleToggleRecording,
336
+ accessibilityLabel: "Toggle benchmark recording"
337
+ }), reports.length >= 2 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
338
+ onPress: () => setSelectionMode(true),
339
+ style: styles.iconButton,
340
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.GitBranch, {
341
+ size: 14,
342
+ color: _sharedUi.macOSColors.semantic.info
343
+ })
344
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
345
+ onPress: () => setRefreshKey(k => k + 1),
346
+ style: styles.iconButton,
347
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
348
+ size: 14,
349
+ color: _sharedUi.macOSColors.text.secondary
350
+ })
351
+ }), reports.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
352
+ onPress: handleClearAll,
353
+ style: styles.iconButton,
354
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
355
+ size: 14,
356
+ color: _sharedUi.macOSColors.semantic.error
357
+ })
358
+ })]
359
+ }), viewMode === "detail" && selectedReport && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
360
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
361
+ onPress: handleRename,
362
+ style: styles.iconButton,
363
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Edit3, {
364
+ size: 14,
365
+ color: _sharedUi.macOSColors.text.secondary
366
+ })
367
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
368
+ onPress: handleDeleteCurrent,
369
+ style: styles.iconButton,
370
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
371
+ size: 14,
372
+ color: _sharedUi.macOSColors.semantic.error
373
+ })
374
+ })]
375
+ }), viewMode === "compare" && compareReports && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
376
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
377
+ onPress: handleCopyCompare,
378
+ style: styles.iconButton,
379
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
380
+ size: 14,
381
+ color: _sharedUi.macOSColors.text.secondary
382
+ })
383
+ })
384
+ }), selectionMode && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
385
+ children: [selectedIds.size === 2 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
386
+ onPress: handleCompare,
387
+ style: [styles.iconButton, styles.compareButton],
388
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.GitBranch, {
389
+ size: 14,
390
+ color: _sharedUi.macOSColors.semantic.info
391
+ })
392
+ }), selectedIds.size > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
393
+ onPress: handleDeleteSelected,
394
+ style: styles.iconButton,
395
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
396
+ size: 14,
397
+ color: _sharedUi.macOSColors.semantic.error
398
+ })
399
+ })]
400
+ })]
401
+ })]
402
+ })
403
+ },
404
+ enablePersistence: true,
405
+ initialMode: "bottomSheet",
406
+ enableGlitchEffects: true,
407
+ children: [isRecording && viewMode === "list" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
408
+ style: styles.recordingBanner,
409
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
410
+ style: styles.recordingDot
411
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
412
+ style: styles.recordingText,
413
+ children: "Recording..."
414
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
415
+ onPress: handleToggleRecording,
416
+ style: styles.stopButton,
417
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
418
+ style: styles.stopButtonText,
419
+ children: "Stop"
420
+ })
421
+ })]
422
+ }), renderContent()]
423
+ });
424
+ }
425
+ const styles = _reactNative.StyleSheet.create({
426
+ iconButton: {
427
+ width: 32,
428
+ height: 32,
429
+ borderRadius: 8,
430
+ backgroundColor: _sharedUi.macOSColors.background.hover,
431
+ borderWidth: 1,
432
+ borderColor: _sharedUi.macOSColors.border.default,
433
+ alignItems: "center",
434
+ justifyContent: "center"
435
+ },
436
+ compareButton: {
437
+ backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
438
+ borderColor: _sharedUi.macOSColors.semantic.info
439
+ },
440
+ listContent: {
441
+ paddingVertical: 16
442
+ },
443
+ separator: {
444
+ height: 8
445
+ },
446
+ emptyState: {
447
+ flex: 1,
448
+ justifyContent: "center",
449
+ alignItems: "center",
450
+ padding: 32
451
+ },
452
+ emptyIcon: {
453
+ fontSize: 48,
454
+ marginBottom: 16
455
+ },
456
+ emptyTitle: {
457
+ fontSize: 16,
458
+ fontWeight: "600",
459
+ color: _sharedUi.macOSColors.text.primary,
460
+ fontFamily: "monospace",
461
+ marginBottom: 8
462
+ },
463
+ emptySubtitle: {
464
+ fontSize: 13,
465
+ color: _sharedUi.macOSColors.text.secondary,
466
+ fontFamily: "monospace",
467
+ textAlign: "center"
468
+ },
469
+ recordingBanner: {
470
+ flexDirection: "row",
471
+ alignItems: "center",
472
+ backgroundColor: _sharedUi.macOSColors.semantic.errorBackground,
473
+ paddingHorizontal: 16,
474
+ paddingVertical: 12,
475
+ borderBottomWidth: 1,
476
+ borderColor: _sharedUi.macOSColors.semantic.error
477
+ },
478
+ recordingDot: {
479
+ width: 8,
480
+ height: 8,
481
+ borderRadius: 4,
482
+ backgroundColor: _sharedUi.macOSColors.semantic.error,
483
+ marginRight: 8
484
+ },
485
+ recordingText: {
486
+ flex: 1,
487
+ fontSize: 13,
488
+ fontWeight: "600",
489
+ color: _sharedUi.macOSColors.semantic.error,
490
+ fontFamily: "monospace"
491
+ },
492
+ stopButton: {
493
+ paddingHorizontal: 12,
494
+ paddingVertical: 6,
495
+ backgroundColor: _sharedUi.macOSColors.semantic.error,
496
+ borderRadius: 4
497
+ },
498
+ stopButtonText: {
499
+ fontSize: 12,
500
+ fontWeight: "600",
501
+ color: "#fff",
502
+ fontFamily: "monospace"
503
+ }
504
+ });
505
+ var _default = exports.default = BenchmarkModal;