react-native-inapp-inspector 1.1.26 → 1.1.28

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 (67) hide show
  1. package/android/build.gradle +1 -0
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +34 -0
  3. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  4. package/dist/commonjs/components/ErrorBoundary.js +530 -100
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +176 -126
  6. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
  7. package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
  8. package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
  9. package/dist/commonjs/components/Inspector/NetworkDetail.js +41 -0
  10. package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
  13. package/dist/commonjs/components/Inspector/TabBar.js +13 -1
  14. package/dist/commonjs/components/LogCard.js +75 -34
  15. package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
  16. package/dist/commonjs/constants/version.d.ts +1 -1
  17. package/dist/commonjs/constants/version.js +1 -1
  18. package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
  19. package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
  20. package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
  21. package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
  22. package/dist/commonjs/customHooks/networkLogger.js +16 -0
  23. package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
  24. package/dist/commonjs/customHooks/performanceTracker.js +546 -392
  25. package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
  26. package/dist/commonjs/customHooks/reduxLogger.js +3 -1
  27. package/dist/commonjs/helpers/index.js +27 -3
  28. package/dist/commonjs/i18n/locales/en.json +225 -4
  29. package/dist/commonjs/index.d.ts +1 -1
  30. package/dist/commonjs/index.js +36 -2
  31. package/dist/commonjs/styles/index.d.ts +3 -1
  32. package/dist/commonjs/styles/index.js +8 -6
  33. package/dist/commonjs/types/interfaces.d.ts +3 -0
  34. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  35. package/dist/esm/components/ErrorBoundary.js +498 -101
  36. package/dist/esm/components/Inspector/AnalyticsTab.js +177 -127
  37. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
  38. package/dist/esm/components/Inspector/BundleTab.js +145 -23
  39. package/dist/esm/components/Inspector/CrashTab.js +0 -8
  40. package/dist/esm/components/Inspector/NetworkDetail.js +41 -0
  41. package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
  42. package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
  43. package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
  44. package/dist/esm/components/Inspector/TabBar.js +13 -1
  45. package/dist/esm/components/LogCard.js +75 -34
  46. package/dist/esm/components/NetworkIcons.d.ts +112 -102
  47. package/dist/esm/constants/version.d.ts +1 -1
  48. package/dist/esm/constants/version.js +1 -1
  49. package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
  50. package/dist/esm/customHooks/analyticsLogger.js +6 -0
  51. package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
  52. package/dist/esm/customHooks/networkLogger.d.ts +1 -0
  53. package/dist/esm/customHooks/networkLogger.js +16 -0
  54. package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
  55. package/dist/esm/customHooks/performanceTracker.js +531 -391
  56. package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
  57. package/dist/esm/customHooks/reduxLogger.js +1 -0
  58. package/dist/esm/helpers/index.js +27 -3
  59. package/dist/esm/i18n/locales/en.json +225 -4
  60. package/dist/esm/index.d.ts +1 -1
  61. package/dist/esm/index.js +24 -3
  62. package/dist/esm/styles/index.d.ts +3 -1
  63. package/dist/esm/styles/index.js +8 -6
  64. package/dist/esm/types/interfaces.d.ts +3 -0
  65. package/ios/NetworkInspectorModule.h +2 -0
  66. package/ios/NetworkInspectorModule.m +10 -0
  67. package/package.json +1 -1
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const react_1 = __importStar(require("react"));
40
40
  const react_native_1 = require("react-native");
41
+ const react_native_svg_1 = __importStar(require("react-native-svg"));
41
42
  const TouchableScale_1 = __importDefault(require("../TouchableScale"));
42
43
  const CopyButton_1 = __importDefault(require("../CopyButton"));
43
44
  const HighlightText_1 = __importDefault(require("../HighlightText"));
@@ -73,8 +74,8 @@ const PerformanceTab = react_1.default.memo(() => {
73
74
  const [fpsHistory, setFpsHistory] = (0, react_1.useState)([
74
75
  60, 59, 60, 60, 58, 60, 59, 60, 60, 60, 57, 60, 59, 60, 60, 58, 60, 60, 59, 60,
75
76
  ]);
76
- // Performance Log Events (Initialized with benchmark & diagnostics events)
77
- const [events, setEvents] = (0, react_1.useState)(performanceTracker_1.INITIAL_EVENTS);
77
+ // Performance Log Events (Dynamic live subscription)
78
+ const [events, setEvents] = (0, react_1.useState)(() => (0, performanceTracker_1.getPerformanceEvents)());
78
79
  const [filterCategory, setFilterCategory] = (0, react_1.useState)('ALL');
79
80
  const [search, setSearch] = (0, react_1.useState)('');
80
81
  (0, react_1.useEffect)(() => {
@@ -249,9 +250,88 @@ const PerformanceTab = react_1.default.memo(() => {
249
250
  headroomPercent,
250
251
  };
251
252
  }, [avgFps, jsLagMs]);
253
+ // ─── Dynamic SVG FPS Area & Trend Curve Data ────────────────────────────
254
+ const [selectedFpsPointIdx, setSelectedFpsPointIdx] = (0, react_1.useState)(null);
255
+ const svgGraphWidth = 320;
256
+ const svgGraphHeight = 64;
257
+ const fpsGraphData = (0, react_1.useMemo)(() => {
258
+ const points = fpsHistory.length > 0 ? fpsHistory : [60, 60];
259
+ const stepX = svgGraphWidth / Math.max(1, points.length - 1);
260
+ const coords = points.map((val, idx) => {
261
+ const clamped = Math.max(0, Math.min(60, val));
262
+ const x = Number((idx * stepX).toFixed(1));
263
+ const y = Number((svgGraphHeight - (clamped / 60) * (svgGraphHeight - 14) - 6).toFixed(1));
264
+ return { x, y, val };
265
+ });
266
+ let linePath = `M ${coords[0].x} ${coords[0].y}`;
267
+ for (let i = 0; i < coords.length - 1; i++) {
268
+ const p0 = coords[i];
269
+ const p1 = coords[i + 1];
270
+ const midX = Number(((p0.x + p1.x) / 2).toFixed(1));
271
+ linePath += ` C ${midX} ${p0.y}, ${midX} ${p1.y}, ${p1.x} ${p1.y}`;
272
+ }
273
+ const last = coords[coords.length - 1];
274
+ const first = coords[0];
275
+ const areaPath = `${linePath} L ${last.x} ${svgGraphHeight} L ${first.x} ${svgGraphHeight} Z`;
276
+ return { linePath, areaPath, coords };
277
+ }, [fpsHistory]);
278
+ const latencyBuckets = (0, react_1.useMemo)(() => {
279
+ let optimal = 0;
280
+ let minorJank = 0;
281
+ let noticeableJank = 0;
282
+ let severeFreeze = 0;
283
+ fpsHistory.forEach(fps => {
284
+ if (fps >= 55)
285
+ optimal++;
286
+ else if (fps >= 30)
287
+ minorJank++;
288
+ else if (fps >= 20)
289
+ noticeableJank++;
290
+ else
291
+ severeFreeze++;
292
+ });
293
+ const total = Math.max(1, fpsHistory.length);
294
+ return [
295
+ {
296
+ key: 'optimal',
297
+ label: t('performance.optimalBucket'),
298
+ count: optimal,
299
+ percent: Number(((optimal / total) * 100).toFixed(0)),
300
+ color: AppColors_1.AppColors.emerald500,
301
+ bgColor: AppColors_1.AppColors.emerald100,
302
+ },
303
+ {
304
+ key: 'minorJank',
305
+ label: t('performance.minorJankBucket'),
306
+ count: minorJank,
307
+ percent: Number(((minorJank / total) * 100).toFixed(0)),
308
+ color: AppColors_1.AppColors.amber500,
309
+ bgColor: AppColors_1.AppColors.amber100,
310
+ },
311
+ {
312
+ key: 'noticeableJank',
313
+ label: t('performance.noticeableJankBucket'),
314
+ count: noticeableJank,
315
+ percent: Number(((noticeableJank / total) * 100).toFixed(0)),
316
+ color: AppColors_1.AppColors.firebaseOrange,
317
+ bgColor: AppColors_1.AppColors.lightOrange,
318
+ },
319
+ {
320
+ key: 'severeFreeze',
321
+ label: t('performance.severeFreezeBucket'),
322
+ count: severeFreeze,
323
+ percent: Number(((severeFreeze / total) * 100).toFixed(0)),
324
+ color: AppColors_1.AppColors.errorColor,
325
+ bgColor: AppColors_1.AppColors.errorCardBg,
326
+ },
327
+ ];
328
+ }, [fpsHistory, t]);
252
329
  const clearEvents = () => {
253
330
  (0, performanceTracker_1.clearPerformanceEvents)();
254
331
  };
332
+ const activeScrubPoint = selectedFpsPointIdx !== null && fpsGraphData.coords[selectedFpsPointIdx]
333
+ ? fpsGraphData.coords[selectedFpsPointIdx]
334
+ : null;
255
335
  return (<react_native_1.View style={perfStyles.container}>
256
336
  {/* Search & Live Control Header */}
257
337
  <react_native_1.View style={perfStyles.headerBar}>
@@ -263,99 +343,81 @@ const PerformanceTab = react_1.default.memo(() => {
263
343
  </TouchableScale_1.default>) : null}
264
344
  </react_native_1.View>
265
345
 
266
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
267
- <TouchableScale_1.default onPress={() => setIsRecording(prev => !prev)} style={[
268
- perfStyles.recordBtn,
269
- isRecording ? perfStyles.recordBtnActive : perfStyles.recordBtnPaused,
346
+ {/* Live FPS Telemetry Recorder Toggle */}
347
+ <TouchableScale_1.default onPress={() => setIsRecording(prev => !prev)} style={[
348
+ perfStyles.recordToggleBtn,
349
+ isRecording
350
+ ? perfStyles.recordToggleActive
351
+ : perfStyles.recordToggleInactive,
270
352
  ]}>
271
- <react_native_1.View style={[
272
- perfStyles.recordDot,
273
- { backgroundColor: isRecording ? AppColors_1.AppColors.emerald500 : AppColors_1.AppColors.amber500 },
353
+ <react_native_1.View style={[
354
+ perfStyles.recordingDot,
355
+ {
356
+ backgroundColor: isRecording
357
+ ? AppColors_1.AppColors.errorColor
358
+ : AppColors_1.AppColors.grayTextWeak,
359
+ },
274
360
  ]}/>
275
- <react_native_1.Text style={[
276
- perfStyles.recordBtnText,
277
- { color: isRecording ? AppColors_1.AppColors.emerald600 : AppColors_1.AppColors.amber700 },
361
+ <react_native_1.Text style={[
362
+ perfStyles.recordToggleText,
363
+ {
364
+ color: isRecording
365
+ ? AppColors_1.AppColors.errorColor
366
+ : AppColors_1.AppColors.grayTextWeak,
367
+ },
278
368
  ]}>
279
- {isRecording ? t('performance.live') : t('redux.paused')}
280
- </react_native_1.Text>
281
- </TouchableScale_1.default>
282
-
283
- <CopyButton_1.default value={() => ({
284
- currentFps,
285
- minFps,
286
- maxFps,
287
- avgFps,
288
- jankyFrameCount,
289
- jsLagMs,
290
- platform: react_native_1.Platform.OS,
291
- engine: runtimeDiagnostics.jsEngineName,
292
- totalEvents: events.length,
293
- events,
294
- })} label={t('common.copyJson')}/>
295
- </react_native_1.View>
369
+ {isRecording
370
+ ? t('performance.recording')
371
+ : t('performance.paused')}
372
+ </react_native_1.Text>
373
+ </TouchableScale_1.default>
296
374
  </react_native_1.View>
297
375
 
298
- {/* ─── Frozen Interactive Filter Tabs Under Search Component ─── */}
299
- <react_native_1.View style={perfStyles.filterRow}>
300
- <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={perfStyles.filterScrollContent}>
376
+ {/* Filter Category Tabs Carousel */}
377
+ <react_native_1.View style={perfStyles.categoryScrollWrapper}>
378
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} style={perfStyles.categoryScrollView} contentContainerStyle={perfStyles.categoryScroll}>
301
379
  {[
302
- { key: 'ALL', label: t('performance.allLogs'), Icon: NetworkIcons_1.BoltIcon, count: events.length },
303
- {
304
- key: 'JANKY',
305
- label: t('performance.jankySlow'),
306
- Icon: NetworkIcons_1.WarningTriangleIcon,
307
- count: events.filter(e => e.severity === 'warning' || e.severity === 'critical').length,
308
- },
309
- {
310
- key: 'NAVIGATION',
311
- label: t('performance.navigation'),
312
- Icon: NetworkIcons_1.MapIcon,
313
- count: events.filter(e => e.category === 'navigation').length,
314
- },
315
- {
316
- key: 'RENDER',
317
- label: t('performance.components'),
318
- Icon: NetworkIcons_1.AtomIcon,
319
- count: events.filter(e => e.category === 'render').length,
320
- },
321
- {
322
- key: 'MEMORY',
323
- label: t('performance.memoryGc'),
324
- Icon: NetworkIcons_1.BrainIcon,
325
- count: events.filter(e => e.category === 'memory').length,
326
- },
327
- {
328
- key: 'IO',
329
- label: t('performance.networkIo'),
330
- Icon: NetworkIcons_1.GlobeIcon,
331
- count: events.filter(e => e.category === 'io' || e.category === 'bridge').length,
332
- },
333
- ].map(tab => {
334
- const isActive = filterCategory === tab.key;
335
- return (<TouchableScale_1.default key={tab.key} onPress={() => setFilterCategory(tab.key)} style={[
336
- perfStyles.filterPill,
337
- isActive && perfStyles.filterPillActive,
380
+ { key: 'ALL', label: t('performance.catAll') },
381
+ { key: 'JANKY', label: t('performance.catJanky') },
382
+ { key: 'NAVIGATION', label: t('performance.catNavigation') },
383
+ { key: 'RENDER', label: t('performance.catRender') },
384
+ { key: 'MEMORY', label: t('performance.catMemory') },
385
+ { key: 'IO', label: t('performance.catIo') },
386
+ ].map(cat => {
387
+ const isSelected = filterCategory === cat.key;
388
+ return (<TouchableScale_1.default key={cat.key} onPress={() => setFilterCategory(cat.key)} style={[
389
+ perfStyles.categoryChip,
390
+ isSelected && perfStyles.categoryChipActive,
338
391
  ]}>
339
- <tab.Icon color={isActive ? AppColors_1.AppColors.white : AppColors_1.AppColors.grayTextStrong} size={12}/>
340
392
  <react_native_1.Text style={[
341
- perfStyles.filterPillText,
342
- isActive && perfStyles.filterPillTextActive,
393
+ perfStyles.categoryChipText,
394
+ isSelected && perfStyles.categoryChipTextActive,
343
395
  ]}>
344
- {tab.label} ({tab.count})
396
+ {cat.label}
345
397
  </react_native_1.Text>
346
398
  </TouchableScale_1.default>);
347
399
  })}
348
400
  </react_native_1.ScrollView>
349
401
  </react_native_1.View>
350
402
 
351
- <react_native_1.ScrollView style={{ flex: 1 }} contentContainerStyle={perfStyles.scrollContent} keyboardShouldPersistTaps="handled">
352
- {/* ── 1. REALTIME FPS METRIC DASHBOARD ── */}
353
- <react_native_1.View style={perfStyles.metricCard}>
354
- <react_native_1.View style={perfStyles.metricCardHeader}>
403
+ {/* Main Content Scroll View */}
404
+ <react_native_1.ScrollView style={perfStyles.scrollBody} contentContainerStyle={perfStyles.scrollContent} showsVerticalScrollIndicator={false}>
405
+ {/* ── 1. REAL-TIME FPS SCORECARD & SMOOTH AREA GRAPH ── */}
406
+ <react_native_1.View style={perfStyles.heroCard}>
407
+ <react_native_1.View style={perfStyles.heroHeaderRow}>
355
408
  <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
356
- <NetworkIcons_1.PerformanceIcon color={AppColors_1.AppColors.purple} size={18}/>
357
- <react_native_1.Text style={perfStyles.metricCardTitle}>{t('performance.liveFps')}</react_native_1.Text>
409
+ <NetworkIcons_1.PerformanceIcon color={AppColors_1.AppColors.purple} size={17}/>
410
+ <react_native_1.View>
411
+ <react_native_1.Text style={perfStyles.heroTitle}>
412
+ {t('performance.fpsAreaChartTitle')}
413
+ </react_native_1.Text>
414
+ <react_native_1.Text style={perfStyles.heroSubTitle}>
415
+ {t('performance.fpsAreaChartSub')}
416
+ </react_native_1.Text>
417
+ </react_native_1.View>
358
418
  </react_native_1.View>
419
+
420
+ {/* Health Grade Score Pill */}
359
421
  <react_native_1.View style={[
360
422
  perfStyles.scorePill,
361
423
  { backgroundColor: performanceScore.bg },
@@ -389,55 +451,169 @@ const PerformanceTab = react_1.default.memo(() => {
389
451
 
390
452
  <react_native_1.View style={perfStyles.fpsStatsGrid}>
391
453
  <react_native_1.View style={perfStyles.statItem}>
392
- <react_native_1.Text style={perfStyles.statLabel}>{t('performance.avgFrameTime')}</react_native_1.Text>
454
+ <react_native_1.Text style={perfStyles.statLabel}>
455
+ {t('performance.avgFrameTime')}
456
+ </react_native_1.Text>
393
457
  <react_native_1.Text style={perfStyles.statValue}>{avgFps}</react_native_1.Text>
394
458
  </react_native_1.View>
395
459
  <react_native_1.View style={perfStyles.statItem}>
396
- <react_native_1.Text style={perfStyles.statLabel}>{t('performance.peakTime')}</react_native_1.Text>
397
- <react_native_1.Text style={[perfStyles.statValue, minFps < 50 && { color: AppColors_1.AppColors.firebaseOrange }]}>
460
+ <react_native_1.Text style={perfStyles.statLabel}>
461
+ {t('performance.peakTime')}
462
+ </react_native_1.Text>
463
+ <react_native_1.Text style={[
464
+ perfStyles.statValue,
465
+ minFps < 50 && { color: AppColors_1.AppColors.firebaseOrange },
466
+ ]}>
398
467
  {minFps}
399
468
  </react_native_1.Text>
400
469
  </react_native_1.View>
401
470
  <react_native_1.View style={perfStyles.statItem}>
402
- <react_native_1.Text style={perfStyles.statLabel}>{t('performance.jsLag')}</react_native_1.Text>
403
- <react_native_1.Text style={[perfStyles.statValue, jsLagMs > 5 && { color: AppColors_1.AppColors.firebaseOrange }]}>
471
+ <react_native_1.Text style={perfStyles.statLabel}>
472
+ {t('performance.jsLag')}
473
+ </react_native_1.Text>
474
+ <react_native_1.Text style={[
475
+ perfStyles.statValue,
476
+ jsLagMs > 5 && { color: AppColors_1.AppColors.firebaseOrange },
477
+ ]}>
404
478
  {jsLagMs}ms
405
479
  </react_native_1.Text>
406
480
  </react_native_1.View>
407
481
  <react_native_1.View style={perfStyles.statItem}>
408
- <react_native_1.Text style={perfStyles.statLabel}>{t('performance.jankRate')}</react_native_1.Text>
482
+ <react_native_1.Text style={perfStyles.statLabel}>
483
+ {t('performance.jankRate')}
484
+ </react_native_1.Text>
409
485
  <react_native_1.Text style={perfStyles.statValue}>
410
486
  {totalFrames > 0
411
- ? `${((jankyFrameCount / Math.max(1, totalFrames / 60)) * 100).toFixed(0)}%`
487
+ ? `${((jankyFrameCount / Math.max(1, totalFrames / 60)) *
488
+ 100).toFixed(0)}%`
412
489
  : '0%'}
413
490
  </react_native_1.Text>
414
491
  </react_native_1.View>
415
492
  </react_native_1.View>
416
493
  </react_native_1.View>
417
494
 
418
- {/* Sparkline Bar Graph */}
419
- <react_native_1.View style={perfStyles.sparklineTrack}>
420
- {fpsHistory.map((fpsVal, idx) => {
421
- const barHeight = Math.max(6, (fpsVal / 60) * 36);
422
- const isLow = fpsVal < 50;
423
- return (<react_native_1.View key={idx} style={perfStyles.sparklineCol}>
424
- <react_native_1.View style={[
425
- perfStyles.sparklineBar,
426
- {
427
- height: barHeight,
428
- backgroundColor: isLow ? AppColors_1.AppColors.errorColor : AppColors_1.AppColors.emerald500,
429
- },
430
- ]}/>
431
- </react_native_1.View>);
495
+ {/* Interactive Scrub Tooltip */}
496
+ {activeScrubPoint && (<react_native_1.View style={perfStyles.graphScrubTooltip}>
497
+ <react_native_1.Text style={perfStyles.graphScrubTooltipText}>
498
+ {activeScrubPoint.val} FPS •{' '}
499
+ {Number((1000 / Math.max(1, activeScrubPoint.val)).toFixed(1))}
500
+ ms
501
+ </react_native_1.Text>
502
+ </react_native_1.View>)}
503
+
504
+ {/* Real-Time SVG Smooth Area & Trend Wave Graph */}
505
+ <react_native_1.View style={perfStyles.svgGraphContainer}>
506
+ <react_native_svg_1.default width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`}>
507
+ <react_native_svg_1.Defs>
508
+ <react_native_svg_1.LinearGradient id="fpsAreaGradient" x1="0" y1="0" x2="0" y2="1">
509
+ <react_native_svg_1.Stop offset="0%" stopColor={avgFps >= 55
510
+ ? AppColors_1.AppColors.emerald500
511
+ : avgFps >= 40
512
+ ? AppColors_1.AppColors.amber500
513
+ : AppColors_1.AppColors.errorColor} stopOpacity="0.32"/>
514
+ <react_native_svg_1.Stop offset="100%" stopColor={avgFps >= 55
515
+ ? AppColors_1.AppColors.emerald500
516
+ : avgFps >= 40
517
+ ? AppColors_1.AppColors.amber500
518
+ : AppColors_1.AppColors.errorColor} stopOpacity="0.02"/>
519
+ </react_native_svg_1.LinearGradient>
520
+ </react_native_svg_1.Defs>
521
+
522
+ {/* 60 FPS Target Baseline */}
523
+ <react_native_svg_1.Line x1="0" y1="7" x2={svgGraphWidth} y2="7" stroke={AppColors_1.AppColors.emerald500} strokeWidth="1" strokeDasharray="4,4" opacity="0.4"/>
524
+
525
+ {/* 30 FPS Warning Baseline */}
526
+ <react_native_svg_1.Line x1="0" y1={svgGraphHeight / 2} x2={svgGraphWidth} y2={svgGraphHeight / 2} stroke={AppColors_1.AppColors.amber500} strokeWidth="0.8" strokeDasharray="3,3" opacity="0.3"/>
527
+
528
+ {/* Shaded Area Under Curve */}
529
+ {fpsGraphData.areaPath ? (<react_native_svg_1.Path d={fpsGraphData.areaPath} fill="url(#fpsAreaGradient)"/>) : null}
530
+
531
+ {/* Bezier Trend Line */}
532
+ {fpsGraphData.linePath ? (<react_native_svg_1.Path d={fpsGraphData.linePath} fill="none" stroke={avgFps >= 55
533
+ ? AppColors_1.AppColors.emerald600
534
+ : avgFps >= 40
535
+ ? AppColors_1.AppColors.amber600
536
+ : AppColors_1.AppColors.errorColor} strokeWidth="2.2"/>) : null}
537
+
538
+ {/* Sample Nodes */}
539
+ {fpsGraphData.coords.map((c, idx) => {
540
+ const isSelected = selectedFpsPointIdx === idx;
541
+ const isDip = c.val < 50;
542
+ return (<react_native_svg_1.G key={idx}>
543
+ {isDip && (<react_native_svg_1.Circle cx={c.x} cy={c.y} r="4" fill={AppColors_1.AppColors.errorColor} opacity="0.25"/>)}
544
+ <react_native_svg_1.Circle cx={c.x} cy={c.y} r={isSelected ? 4 : isDip ? 2.5 : 1.8} fill={isSelected
545
+ ? AppColors_1.AppColors.brandPurple
546
+ : isDip
547
+ ? AppColors_1.AppColors.errorColor
548
+ : AppColors_1.AppColors.emerald600} stroke={AppColors_1.AppColors.white} strokeWidth={isSelected ? 1.5 : 0.8}/>
549
+ </react_native_svg_1.G>);
432
550
  })}
551
+ </react_native_svg_1.default>
552
+
553
+ {/* Invisible Touch Column Targets for Interactive Inspection */}
554
+ <react_native_1.View style={perfStyles.svgTouchOverlay}>
555
+ {fpsGraphData.coords.map((_, idx) => (<react_native_1.TouchableOpacity key={idx} style={perfStyles.svgTouchCol} activeOpacity={0.7} onPress={() => setSelectedFpsPointIdx(selectedFpsPointIdx === idx ? null : idx)}/>))}
556
+ </react_native_1.View>
433
557
  </react_native_1.View>
558
+
434
559
  <react_native_1.View style={perfStyles.sparklineFooter}>
435
- <react_native_1.Text style={perfStyles.sparklineFooterText}>{t('performance.realtimeWindow')}</react_native_1.Text>
436
- <react_native_1.Text style={perfStyles.sparklineFooterTarget}>{t('performance.fpsTarget')}</react_native_1.Text>
560
+ <react_native_1.Text style={perfStyles.sparklineFooterText}>
561
+ {t('performance.realtimeWindow')}
562
+ </react_native_1.Text>
563
+ <react_native_1.Text style={perfStyles.sparklineFooterTarget}>
564
+ {t('performance.target60Fps', { fps: avgFps })}
565
+ </react_native_1.Text>
437
566
  </react_native_1.View>
438
567
  </react_native_1.View>
439
568
 
440
- {/* ── 2. FRAME BUDGET BREAKDOWN CARD ── */}
569
+ {/* ── 2. FRAME LATENCY DISTRIBUTION HISTOGRAM CARD ── */}
570
+ <react_native_1.View style={perfStyles.budgetCard}>
571
+ <react_native_1.View style={perfStyles.budgetHeader}>
572
+ <react_native_1.View>
573
+ <react_native_1.Text style={perfStyles.budgetTitle}>
574
+ {t('performance.frameLatencyDistTitle')}
575
+ </react_native_1.Text>
576
+ <react_native_1.Text style={perfStyles.heroSubTitle}>
577
+ {t('performance.frameLatencyDistSub')}
578
+ </react_native_1.Text>
579
+ </react_native_1.View>
580
+ </react_native_1.View>
581
+
582
+ <react_native_1.View style={perfStyles.histogramContainer}>
583
+ {latencyBuckets.map(b => (<react_native_1.View key={b.key} style={perfStyles.histogramRow}>
584
+ <react_native_1.View style={perfStyles.histogramLabelCol}>
585
+ <react_native_1.View style={[perfStyles.legendDot, { backgroundColor: b.color }]}/>
586
+ <react_native_1.Text style={perfStyles.histogramLabelText}>{b.label}</react_native_1.Text>
587
+ </react_native_1.View>
588
+
589
+ <react_native_1.View style={perfStyles.histogramBarCol}>
590
+ <react_native_1.View style={perfStyles.histogramTrack}>
591
+ <react_native_1.View style={[
592
+ perfStyles.histogramBarFill,
593
+ {
594
+ width: `${Math.max(b.percent > 0 ? 4 : 0, b.percent)}%`,
595
+ backgroundColor: b.color,
596
+ },
597
+ ]}/>
598
+ </react_native_1.View>
599
+ </react_native_1.View>
600
+
601
+ <react_native_1.View style={perfStyles.histogramStatsCol}>
602
+ <react_native_1.Text style={[
603
+ perfStyles.histogramPercentText,
604
+ { color: b.color },
605
+ ]}>
606
+ {b.percent}%
607
+ </react_native_1.Text>
608
+ <react_native_1.Text style={perfStyles.histogramCountText}>
609
+ ({b.count})
610
+ </react_native_1.Text>
611
+ </react_native_1.View>
612
+ </react_native_1.View>))}
613
+ </react_native_1.View>
614
+ </react_native_1.View>
615
+
616
+ {/* ── 3. FRAME BUDGET BREAKDOWN CARD ── */}
441
617
  <react_native_1.View style={perfStyles.budgetCard}>
442
618
  <react_native_1.View style={perfStyles.budgetHeader}>
443
619
  <react_native_1.Text style={perfStyles.budgetTitle}>{t('performance.budgetUsage')}</react_native_1.Text>
@@ -547,20 +723,45 @@ const PerformanceTab = react_1.default.memo(() => {
547
723
  isCritical && { borderColor: AppColors_1.AppColors.errorBorder, backgroundColor: AppColors_1.AppColors.errorCardBg },
548
724
  ]}>
549
725
  <react_native_1.View style={perfStyles.eventTopRow}>
550
- <react_native_1.View style={[
726
+ <react_native_1.View style={perfStyles.fpsBadgeGroup}>
727
+ <react_native_1.View style={[
551
728
  perfStyles.fpsBadge,
552
729
  { backgroundColor: badgeBg, borderColor: badgeBorder },
553
730
  ]}>
554
- <react_native_1.Text style={[perfStyles.fpsBadgeText, { color: badgeColor }]}>
555
- {event.fps} FPS
556
- </react_native_1.Text>
557
- </react_native_1.View>
731
+ <react_native_1.Text style={[perfStyles.fpsBadgeText, { color: badgeColor }]}>
732
+ {event.fps} FPS
733
+ </react_native_1.Text>
734
+ </react_native_1.View>
558
735
 
559
- <react_native_1.View style={{ flex: 1, paddingHorizontal: 6 }}>
560
- <HighlightText_1.default text={event.label} search={search} style={perfStyles.eventLabel} highlightStyle={perfStyles.searchHighlight}/>
561
- <react_native_1.Text style={perfStyles.eventMeta}>
562
- {t('performance.msTotal', { duration: event.durationMs, time: new Date(event.timestamp).toLocaleTimeString() })}
563
- </react_native_1.Text>
736
+ {/* Dropped Frames Badge */}
737
+ <react_native_1.View style={[
738
+ perfStyles.droppedBadge,
739
+ (event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
740
+ ? perfStyles.droppedBadgeWarning
741
+ : perfStyles.droppedBadgeOptimal,
742
+ ]}>
743
+ <react_native_1.Text style={[
744
+ perfStyles.droppedBadgeText,
745
+ (event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
746
+ ? { color: AppColors_1.AppColors.red600 }
747
+ : { color: AppColors_1.AppColors.emerald600 },
748
+ ]}>
749
+ {(event.droppedFrames ?? Math.max(0, 60 - event.fps)) > 0
750
+ ? t('performance.droppedFrames', { count: event.droppedFrames ?? Math.max(0, 60 - event.fps) })
751
+ : t('performance.zeroDropped')}
752
+ </react_native_1.Text>
753
+ </react_native_1.View>
754
+
755
+ {/* Bottleneck Thread Tag */}
756
+ {event.bottleneckThread && (<react_native_1.View style={perfStyles.bottleneckTag}>
757
+ <react_native_1.Text style={perfStyles.bottleneckTagText}>
758
+ {event.bottleneckThread === 'JS Thread'
759
+ ? `⚡ ${t('performance.jsBound')}`
760
+ : event.bottleneckThread === 'UI Thread'
761
+ ? `🎨 ${t('performance.uiBound')}`
762
+ : t('performance.balanced')}
763
+ </react_native_1.Text>
764
+ </react_native_1.View>)}
564
765
  </react_native_1.View>
565
766
 
566
767
  <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
@@ -571,18 +772,85 @@ const PerformanceTab = react_1.default.memo(() => {
571
772
  </react_native_1.View>
572
773
  </react_native_1.View>
573
774
 
775
+ {/* Event Label & Meta */}
776
+ <react_native_1.View style={{ marginTop: 4 }}>
777
+ <HighlightText_1.default text={event.label} search={search} style={perfStyles.eventLabel} highlightStyle={perfStyles.searchHighlight}/>
778
+ <react_native_1.View style={perfStyles.eventMetaRow}>
779
+ <react_native_1.Text style={perfStyles.eventMeta}>
780
+ {t('performance.msTotal', {
781
+ duration: event.durationMs,
782
+ time: new Date(event.timestamp).toLocaleTimeString(),
783
+ })}
784
+ </react_native_1.Text>
785
+ <react_native_1.Text style={perfStyles.eventMetaDot}>•</react_native_1.Text>
786
+ <react_native_1.Text style={[
787
+ perfStyles.frameBudgetHint,
788
+ event.durationMs > 16.67
789
+ ? { color: AppColors_1.AppColors.amber800Warm }
790
+ : { color: AppColors_1.AppColors.emerald600 },
791
+ ]}>
792
+ {t('performance.frameBudgetMs', {
793
+ time: event.durationMs,
794
+ budget: Number(((event.durationMs / 16.67) * 100).toFixed(0)),
795
+ })}
796
+ </react_native_1.Text>
797
+ </react_native_1.View>
798
+ </react_native_1.View>
799
+
800
+ {/* Visual Frame Time Budget Bar */}
801
+ <react_native_1.View style={perfStyles.frameBarTrack}>
802
+ <react_native_1.View style={[
803
+ perfStyles.frameBarFill,
804
+ {
805
+ width: `${Math.min(100, Math.max(10, (event.durationMs / 33.33) * 100))}%`,
806
+ backgroundColor: badgeColor,
807
+ },
808
+ ]}/>
809
+ </react_native_1.View>
810
+
574
811
  <HighlightText_1.default text={event.detail} search={search} style={perfStyles.eventDetail} highlightStyle={perfStyles.searchHighlight}/>
575
812
 
576
813
  {/* ── EXPANDABLE IN-DEPTH METRICS ACCORDION ── */}
577
814
  {isExpanded && (<react_native_1.View style={perfStyles.expandedDetailBox}>
578
- {/* Source file / hook tag */}
579
- {event.source && (<react_native_1.View style={perfStyles.sourceRow}>
580
- <react_native_1.Text style={perfStyles.sourceTag}>{t('common.source')}</react_native_1.Text>
815
+ {/* Screen / Component Context */}
816
+ {(event.screenName || event.source) && (<react_native_1.View style={perfStyles.sourceRow}>
817
+ <react_native_1.Text style={perfStyles.sourceTag}>{t('performance.screenContext')}</react_native_1.Text>
581
818
  <react_native_1.Text style={perfStyles.sourceText} numberOfLines={1}>
582
- {event.source}
819
+ {event.screenName || event.source}
583
820
  </react_native_1.Text>
584
821
  </react_native_1.View>)}
585
822
 
823
+ {/* Frame Metrics Grid */}
824
+ <react_native_1.View style={perfStyles.fpsMetricsGrid}>
825
+ <react_native_1.View style={perfStyles.fpsMetricItem}>
826
+ <react_native_1.Text style={perfStyles.fpsMetricLabel}>{t('performance.target60Fps', { fps: event.fps })}</react_native_1.Text>
827
+ <react_native_1.Text style={[perfStyles.fpsMetricVal, { color: badgeColor }]}>
828
+ {event.fps} FPS
829
+ </react_native_1.Text>
830
+ </react_native_1.View>
831
+ <react_native_1.View style={perfStyles.fpsMetricItem}>
832
+ <react_native_1.Text style={perfStyles.fpsMetricLabel}>{t('performance.frameDuration')}</react_native_1.Text>
833
+ <react_native_1.Text style={perfStyles.fpsMetricVal}>
834
+ {event.durationMs} ms
835
+ </react_native_1.Text>
836
+ </react_native_1.View>
837
+ <react_native_1.View style={perfStyles.fpsMetricItem}>
838
+ <react_native_1.Text style={perfStyles.fpsMetricLabel}>{t('performance.jankRate')}</react_native_1.Text>
839
+ <react_native_1.Text style={[
840
+ perfStyles.fpsMetricVal,
841
+ (event.droppedFrames ?? 0) > 0 ? { color: AppColors_1.AppColors.red600 } : { color: AppColors_1.AppColors.emerald600 },
842
+ ]}>
843
+ {event.droppedFrames ?? Math.max(0, 60 - event.fps)} frames
844
+ </react_native_1.Text>
845
+ </react_native_1.View>
846
+ <react_native_1.View style={perfStyles.fpsMetricItem}>
847
+ <react_native_1.Text style={perfStyles.fpsMetricLabel}>{t('performance.bottleneck')}</react_native_1.Text>
848
+ <react_native_1.Text style={perfStyles.fpsMetricVal}>
849
+ {event.bottleneckThread || t('performance.balanced')}
850
+ </react_native_1.Text>
851
+ </react_native_1.View>
852
+ </react_native_1.View>
853
+
586
854
  {/* Thread Timing Breakdown */}
587
855
  {event.breakdown && (<react_native_1.View style={perfStyles.breakdownGrid}>
588
856
  <react_native_1.View style={perfStyles.breakdownCol}>
@@ -664,7 +932,7 @@ const perfStyles = react_native_1.StyleSheet.create({
664
932
  color: AppColors_1.AppColors.primaryBlack,
665
933
  paddingVertical: 0,
666
934
  },
667
- recordBtn: {
935
+ recordToggleBtn: {
668
936
  flexDirection: 'row',
669
937
  alignItems: 'center',
670
938
  gap: 5,
@@ -673,63 +941,72 @@ const perfStyles = react_native_1.StyleSheet.create({
673
941
  borderRadius: 7,
674
942
  borderWidth: 1,
675
943
  },
676
- recordBtnActive: {
677
- backgroundColor: AppColors_1.AppColors.greenBg,
678
- borderColor: AppColors_1.AppColors.greenBorder,
944
+ recordToggleActive: {
945
+ backgroundColor: `${AppColors_1.AppColors.errorColor}14`,
946
+ borderColor: `${AppColors_1.AppColors.errorColor}30`,
679
947
  },
680
- recordBtnPaused: {
681
- backgroundColor: AppColors_1.AppColors.amberBg,
682
- borderColor: AppColors_1.AppColors.lightOrange,
948
+ recordToggleInactive: {
949
+ backgroundColor: AppColors_1.AppColors.graySurface,
950
+ borderColor: AppColors_1.AppColors.dividerColor,
683
951
  },
684
- recordDot: {
952
+ recordingDot: {
685
953
  width: 6,
686
954
  height: 6,
687
955
  borderRadius: 3,
688
956
  },
689
- recordBtnText: {
957
+ recordToggleText: {
690
958
  fontFamily: AppFonts_1.AppFonts.interBold,
691
959
  fontSize: 11,
692
960
  },
693
- filterRow: {
961
+ categoryScrollWrapper: {
694
962
  backgroundColor: AppColors_1.AppColors.white,
695
963
  borderBottomWidth: 1,
696
964
  borderBottomColor: AppColors_1.AppColors.dividerColor,
697
- paddingVertical: 5,
965
+ height: 42,
966
+ },
967
+ categoryScrollView: {
968
+ flexGrow: 0,
969
+ height: 42,
698
970
  },
699
- filterScrollContent: {
971
+ categoryScroll: {
972
+ flexDirection: 'row',
973
+ alignItems: 'center',
700
974
  gap: 6,
701
975
  paddingHorizontal: 12,
702
- paddingVertical: 2,
976
+ height: 42,
703
977
  },
704
- filterPill: {
978
+ categoryChip: {
705
979
  flexDirection: 'row',
706
980
  alignItems: 'center',
707
- gap: 4,
708
- paddingHorizontal: 10,
709
- paddingVertical: 5,
710
- borderRadius: 16,
981
+ justifyContent: 'center',
982
+ paddingHorizontal: 12,
983
+ height: 28,
984
+ borderRadius: 14,
711
985
  backgroundColor: AppColors_1.AppColors.grayBackground,
712
986
  borderWidth: 1,
713
987
  borderColor: AppColors_1.AppColors.dividerColor,
714
988
  },
715
- filterPillActive: {
989
+ categoryChipActive: {
716
990
  backgroundColor: AppColors_1.AppColors.brandPurple,
717
991
  borderColor: AppColors_1.AppColors.brandPurple,
718
992
  },
719
- filterPillText: {
993
+ categoryChipText: {
720
994
  fontFamily: AppFonts_1.AppFonts.interMedium,
721
995
  fontSize: 11,
722
996
  color: AppColors_1.AppColors.grayTextStrong,
723
997
  },
724
- filterPillTextActive: {
998
+ categoryChipTextActive: {
725
999
  fontFamily: AppFonts_1.AppFonts.interBold,
726
1000
  color: AppColors_1.AppColors.white,
727
1001
  },
1002
+ scrollBody: {
1003
+ flex: 1,
1004
+ },
728
1005
  scrollContent: {
729
1006
  padding: 12,
730
1007
  gap: 10,
731
1008
  },
732
- metricCard: {
1009
+ heroCard: {
733
1010
  backgroundColor: AppColors_1.AppColors.white,
734
1011
  borderRadius: 12,
735
1012
  padding: 14,
@@ -741,17 +1018,23 @@ const perfStyles = react_native_1.StyleSheet.create({
741
1018
  shadowRadius: 3,
742
1019
  elevation: 1,
743
1020
  },
744
- metricCardHeader: {
1021
+ heroHeaderRow: {
745
1022
  flexDirection: 'row',
746
1023
  alignItems: 'center',
747
1024
  justifyContent: 'space-between',
748
1025
  marginBottom: 10,
749
1026
  },
750
- metricCardTitle: {
1027
+ heroTitle: {
751
1028
  fontFamily: AppFonts_1.AppFonts.interBold,
752
- fontSize: 13.5,
1029
+ fontSize: 13,
753
1030
  color: AppColors_1.AppColors.primaryBlack,
754
1031
  },
1032
+ heroSubTitle: {
1033
+ fontFamily: AppFonts_1.AppFonts.interRegular,
1034
+ fontSize: 9.5,
1035
+ color: AppColors_1.AppColors.grayTextWeak,
1036
+ marginTop: 1,
1037
+ },
755
1038
  scorePill: {
756
1039
  paddingHorizontal: 8,
757
1040
  paddingVertical: 3,
@@ -806,26 +1089,85 @@ const perfStyles = react_native_1.StyleSheet.create({
806
1089
  marginTop: 2,
807
1090
  textAlign: 'center',
808
1091
  },
809
- sparklineTrack: {
1092
+ graphScrubTooltip: {
1093
+ alignSelf: 'center',
1094
+ backgroundColor: AppColors_1.AppColors.primaryBlack,
1095
+ paddingHorizontal: 8,
1096
+ paddingVertical: 3,
1097
+ borderRadius: 6,
1098
+ marginBottom: 4,
1099
+ },
1100
+ graphScrubTooltipText: {
1101
+ fontFamily: AppFonts_1.AppFonts.interBold,
1102
+ fontSize: 10,
1103
+ color: AppColors_1.AppColors.white,
1104
+ },
1105
+ svgGraphContainer: {
1106
+ width: '100%',
1107
+ height: 64,
1108
+ marginTop: 6,
1109
+ position: 'relative',
1110
+ },
1111
+ svgTouchOverlay: {
1112
+ position: 'absolute',
1113
+ top: 0,
1114
+ left: 0,
1115
+ right: 0,
1116
+ bottom: 0,
810
1117
  flexDirection: 'row',
811
- alignItems: 'flex-end',
812
- justifyContent: 'space-between',
813
- height: 38,
814
- marginTop: 10,
815
- paddingTop: 4,
816
- borderBottomWidth: 1,
817
- borderBottomColor: AppColors_1.AppColors.dividerColor,
818
1118
  },
819
- sparklineCol: {
1119
+ svgTouchCol: {
820
1120
  flex: 1,
1121
+ height: '100%',
1122
+ },
1123
+ histogramContainer: {
1124
+ gap: 8,
1125
+ marginTop: 4,
1126
+ },
1127
+ histogramRow: {
1128
+ flexDirection: 'row',
821
1129
  alignItems: 'center',
822
- justifyContent: 'flex-end',
1130
+ gap: 8,
1131
+ },
1132
+ histogramLabelCol: {
1133
+ flexDirection: 'row',
1134
+ alignItems: 'center',
1135
+ gap: 6,
1136
+ width: 140,
1137
+ },
1138
+ histogramLabelText: {
1139
+ fontFamily: AppFonts_1.AppFonts.interMedium,
1140
+ fontSize: 10,
1141
+ color: AppColors_1.AppColors.grayTextStrong,
1142
+ },
1143
+ histogramBarCol: {
1144
+ flex: 1,
1145
+ },
1146
+ histogramTrack: {
1147
+ height: 8,
1148
+ backgroundColor: AppColors_1.AppColors.grayBackground,
1149
+ borderRadius: 4,
1150
+ overflow: 'hidden',
1151
+ },
1152
+ histogramBarFill: {
823
1153
  height: '100%',
824
- paddingHorizontal: 1,
1154
+ borderRadius: 4,
825
1155
  },
826
- sparklineBar: {
827
- width: '80%',
828
- borderRadius: 2,
1156
+ histogramStatsCol: {
1157
+ flexDirection: 'row',
1158
+ alignItems: 'center',
1159
+ gap: 4,
1160
+ minWidth: 52,
1161
+ justifyContent: 'flex-end',
1162
+ },
1163
+ histogramPercentText: {
1164
+ fontFamily: AppFonts_1.AppFonts.interBold,
1165
+ fontSize: 10.5,
1166
+ },
1167
+ histogramCountText: {
1168
+ fontFamily: AppFonts_1.AppFonts.interRegular,
1169
+ fontSize: 9.5,
1170
+ color: AppColors_1.AppColors.grayTextWeak,
829
1171
  },
830
1172
  sparklineFooter: {
831
1173
  flexDirection: 'row',
@@ -958,6 +1300,12 @@ const perfStyles = react_native_1.StyleSheet.create({
958
1300
  alignItems: 'center',
959
1301
  justifyContent: 'space-between',
960
1302
  },
1303
+ fpsBadgeGroup: {
1304
+ flexDirection: 'row',
1305
+ alignItems: 'center',
1306
+ gap: 6,
1307
+ flexWrap: 'wrap',
1308
+ },
961
1309
  fpsBadge: {
962
1310
  paddingHorizontal: 6,
963
1311
  paddingVertical: 2,
@@ -968,6 +1316,91 @@ const perfStyles = react_native_1.StyleSheet.create({
968
1316
  fontFamily: AppFonts_1.AppFonts.interBold,
969
1317
  fontSize: 10,
970
1318
  },
1319
+ droppedBadge: {
1320
+ paddingHorizontal: 6,
1321
+ paddingVertical: 2,
1322
+ borderRadius: 5,
1323
+ borderWidth: 1,
1324
+ },
1325
+ droppedBadgeWarning: {
1326
+ backgroundColor: AppColors_1.AppColors.red100,
1327
+ borderColor: AppColors_1.AppColors.errorBorder,
1328
+ },
1329
+ droppedBadgeOptimal: {
1330
+ backgroundColor: AppColors_1.AppColors.green100,
1331
+ borderColor: AppColors_1.AppColors.greenBorder,
1332
+ },
1333
+ droppedBadgeText: {
1334
+ fontFamily: AppFonts_1.AppFonts.interBold,
1335
+ fontSize: 9.5,
1336
+ },
1337
+ bottleneckTag: {
1338
+ backgroundColor: AppColors_1.AppColors.purpleShade50,
1339
+ paddingHorizontal: 5,
1340
+ paddingVertical: 2,
1341
+ borderRadius: 4,
1342
+ borderWidth: 1,
1343
+ borderColor: AppColors_1.AppColors.purpleTint2,
1344
+ },
1345
+ bottleneckTagText: {
1346
+ fontFamily: AppFonts_1.AppFonts.interMedium,
1347
+ fontSize: 9.5,
1348
+ color: AppColors_1.AppColors.purple,
1349
+ },
1350
+ eventMetaRow: {
1351
+ flexDirection: 'row',
1352
+ alignItems: 'center',
1353
+ gap: 6,
1354
+ marginTop: 2,
1355
+ flexWrap: 'wrap',
1356
+ },
1357
+ eventMetaDot: {
1358
+ fontSize: 9,
1359
+ color: AppColors_1.AppColors.grayTextWeak,
1360
+ },
1361
+ frameBudgetHint: {
1362
+ fontFamily: AppFonts_1.AppFonts.interMedium,
1363
+ fontSize: 10,
1364
+ },
1365
+ frameBarTrack: {
1366
+ height: 4,
1367
+ borderRadius: 2,
1368
+ backgroundColor: AppColors_1.AppColors.graySurface,
1369
+ overflow: 'hidden',
1370
+ marginVertical: 4,
1371
+ },
1372
+ frameBarFill: {
1373
+ height: '100%',
1374
+ borderRadius: 2,
1375
+ },
1376
+ fpsMetricsGrid: {
1377
+ flexDirection: 'row',
1378
+ alignItems: 'center',
1379
+ backgroundColor: AppColors_1.AppColors.white,
1380
+ borderRadius: 6,
1381
+ padding: 6,
1382
+ borderWidth: 1,
1383
+ borderColor: AppColors_1.AppColors.dividerColor,
1384
+ justifyContent: 'space-between',
1385
+ gap: 4,
1386
+ },
1387
+ fpsMetricItem: {
1388
+ alignItems: 'center',
1389
+ flex: 1,
1390
+ },
1391
+ fpsMetricLabel: {
1392
+ fontFamily: AppFonts_1.AppFonts.interRegular,
1393
+ fontSize: 8.5,
1394
+ color: AppColors_1.AppColors.grayTextWeak,
1395
+ textAlign: 'center',
1396
+ },
1397
+ fpsMetricVal: {
1398
+ fontFamily: AppFonts_1.AppFonts.interBold,
1399
+ fontSize: 10.5,
1400
+ color: AppColors_1.AppColors.primaryBlack,
1401
+ marginTop: 1,
1402
+ textAlign: 'center',
1403
+ },
971
1404
  eventLabel: {
972
1405
  fontFamily: AppFonts_1.AppFonts.interBold,
973
1406
  fontSize: 12,