react-native-inapp-inspector 1.1.34 → 2.0.0

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 (122) hide show
  1. package/README.md +123 -101
  2. package/android/build.gradle +13 -1
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +904 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
  5. package/dist/commonjs/components/ConsoleLogCard.js +57 -56
  6. package/dist/commonjs/components/CopyButton.js +10 -1
  7. package/dist/commonjs/components/ErrorBoundary.js +3 -0
  8. package/dist/commonjs/components/Inspector/AnalyticsTab.js +7 -3
  9. package/dist/commonjs/components/Inspector/BundleTab.js +20 -20
  10. package/dist/commonjs/components/Inspector/ConsoleTab.js +1 -1
  11. package/dist/commonjs/components/Inspector/CrashDetail.js +16 -12
  12. package/dist/commonjs/components/Inspector/CrashTab.js +97 -90
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +28 -24
  14. package/dist/commonjs/components/Inspector/MainScreen.js +20 -103
  15. package/dist/commonjs/components/Inspector/NetworkTab.js +143 -10
  16. package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
  17. package/dist/commonjs/components/Inspector/ReduxDetail.js +24 -19
  18. package/dist/commonjs/components/Inspector/ReduxTab.js +30 -18
  19. package/dist/commonjs/components/Inspector/SettingsPanel.js +1578 -754
  20. package/dist/commonjs/components/Inspector/TabBar.js +6 -3
  21. package/dist/commonjs/components/LogCard.js +27 -9
  22. package/dist/commonjs/components/MetaAccordion.js +42 -0
  23. package/dist/commonjs/components/NetworkIcons.d.ts +10 -0
  24. package/dist/commonjs/components/NetworkIcons.js +53 -1
  25. package/dist/commonjs/components/Toast.js +3 -3
  26. package/dist/commonjs/components/TouchableScale.d.ts +5 -2
  27. package/dist/commonjs/components/TouchableScale.js +30 -32
  28. package/dist/commonjs/constants/index.d.ts +1 -2
  29. package/dist/commonjs/constants/index.js +4 -0
  30. package/dist/commonjs/constants/version.d.ts +1 -1
  31. package/dist/commonjs/constants/version.js +1 -1
  32. package/dist/commonjs/customHooks/analyticsLogger.d.ts +6 -0
  33. package/dist/commonjs/customHooks/analyticsLogger.js +16 -5
  34. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +2 -0
  35. package/dist/commonjs/customHooks/bundleAnalyzer.js +26 -17
  36. package/dist/commonjs/customHooks/consoleLogger.d.ts +2 -0
  37. package/dist/commonjs/customHooks/consoleLogger.js +10 -1
  38. package/dist/commonjs/customHooks/crashHandler.d.ts +2 -0
  39. package/dist/commonjs/customHooks/crashHandler.js +17 -1
  40. package/dist/commonjs/customHooks/networkLogger.d.ts +2 -0
  41. package/dist/commonjs/customHooks/networkLogger.js +12 -1
  42. package/dist/commonjs/customHooks/performanceTracker.d.ts +3 -1
  43. package/dist/commonjs/customHooks/performanceTracker.js +18 -4
  44. package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -0
  45. package/dist/commonjs/customHooks/reduxLogger.js +10 -1
  46. package/dist/commonjs/helpers/index.js +10 -1
  47. package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
  48. package/dist/commonjs/helpers/settingsStore.js +59 -3
  49. package/dist/commonjs/i18n/locales/en.json +96 -28
  50. package/dist/commonjs/index.d.ts +1 -0
  51. package/dist/commonjs/index.js +307 -73
  52. package/dist/commonjs/native/NativeInspector.d.ts +125 -0
  53. package/dist/commonjs/native/NativeInspector.js +313 -0
  54. package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
  55. package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
  56. package/dist/commonjs/styles/AppColors.d.ts +28 -0
  57. package/dist/commonjs/styles/AppColors.js +30 -0
  58. package/dist/commonjs/styles/index.js +6 -6
  59. package/dist/commonjs/types/enums.d.ts +1 -1
  60. package/dist/commonjs/types/interfaces.d.ts +9 -0
  61. package/dist/esm/components/ConsoleLogCard.js +58 -57
  62. package/dist/esm/components/CopyButton.js +10 -1
  63. package/dist/esm/components/ErrorBoundary.js +3 -0
  64. package/dist/esm/components/Inspector/AnalyticsTab.js +7 -3
  65. package/dist/esm/components/Inspector/BundleTab.js +20 -20
  66. package/dist/esm/components/Inspector/ConsoleTab.js +1 -1
  67. package/dist/esm/components/Inspector/CrashDetail.js +16 -12
  68. package/dist/esm/components/Inspector/CrashTab.js +97 -90
  69. package/dist/esm/components/Inspector/InspectorHeader.js +29 -25
  70. package/dist/esm/components/Inspector/MainScreen.js +21 -71
  71. package/dist/esm/components/Inspector/NetworkTab.js +145 -12
  72. package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
  73. package/dist/esm/components/Inspector/ReduxDetail.js +24 -19
  74. package/dist/esm/components/Inspector/ReduxTab.js +30 -18
  75. package/dist/esm/components/Inspector/SettingsPanel.js +1582 -758
  76. package/dist/esm/components/Inspector/TabBar.js +7 -4
  77. package/dist/esm/components/LogCard.js +27 -9
  78. package/dist/esm/components/MetaAccordion.js +42 -0
  79. package/dist/esm/components/NetworkIcons.d.ts +10 -0
  80. package/dist/esm/components/NetworkIcons.js +42 -0
  81. package/dist/esm/components/Toast.js +3 -3
  82. package/dist/esm/components/TouchableScale.d.ts +5 -2
  83. package/dist/esm/components/TouchableScale.js +31 -33
  84. package/dist/esm/constants/index.d.ts +1 -2
  85. package/dist/esm/constants/index.js +4 -0
  86. package/dist/esm/constants/version.d.ts +1 -1
  87. package/dist/esm/constants/version.js +1 -1
  88. package/dist/esm/customHooks/analyticsLogger.d.ts +6 -0
  89. package/dist/esm/customHooks/analyticsLogger.js +12 -4
  90. package/dist/esm/customHooks/bundleAnalyzer.d.ts +2 -0
  91. package/dist/esm/customHooks/bundleAnalyzer.js +23 -16
  92. package/dist/esm/customHooks/consoleLogger.d.ts +2 -0
  93. package/dist/esm/customHooks/consoleLogger.js +7 -0
  94. package/dist/esm/customHooks/crashHandler.d.ts +2 -0
  95. package/dist/esm/customHooks/crashHandler.js +14 -0
  96. package/dist/esm/customHooks/networkLogger.d.ts +2 -0
  97. package/dist/esm/customHooks/networkLogger.js +9 -0
  98. package/dist/esm/customHooks/performanceTracker.d.ts +3 -1
  99. package/dist/esm/customHooks/performanceTracker.js +15 -3
  100. package/dist/esm/customHooks/reduxLogger.d.ts +2 -0
  101. package/dist/esm/customHooks/reduxLogger.js +7 -0
  102. package/dist/esm/helpers/index.js +10 -1
  103. package/dist/esm/helpers/settingsStore.d.ts +9 -0
  104. package/dist/esm/helpers/settingsStore.js +58 -3
  105. package/dist/esm/i18n/locales/en.json +96 -28
  106. package/dist/esm/index.d.ts +1 -0
  107. package/dist/esm/index.js +279 -59
  108. package/dist/esm/native/NativeInspector.d.ts +125 -0
  109. package/dist/esm/native/NativeInspector.js +289 -0
  110. package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
  111. package/dist/esm/native/NativeNetworkInspector.js +2 -0
  112. package/dist/esm/styles/AppColors.d.ts +28 -0
  113. package/dist/esm/styles/AppColors.js +30 -0
  114. package/dist/esm/styles/index.js +6 -6
  115. package/dist/esm/types/enums.d.ts +1 -1
  116. package/dist/esm/types/interfaces.d.ts +9 -0
  117. package/ios/NetworkInspectorModule.h +6 -0
  118. package/ios/NetworkInspectorModule.m +720 -5
  119. package/package.json +15 -3
  120. package/react-native-inapp-inspector.podspec +7 -2
  121. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
  122. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
@@ -6,7 +6,7 @@ import { AppFonts } from '../styles/AppFonts';
6
6
  import { formatTime, getJsonContent, getJsonPreviewText, parseStackLine, openInVSCode, } from '../helpers';
7
7
  import HighlightText from './HighlightText';
8
8
  import CopyButton from './CopyButton';
9
- import { ChevronIcon, ExternalLinkIcon } from './NetworkIcons';
9
+ import { ChevronIcon, ExternalLinkIcon, FlaskIcon, ZapIcon, GlobeIcon, DiceIcon, AtomIcon, BarChartIcon, KeyIcon, SmartphoneIcon, AlertTriangleIcon, BugIcon, TagIcon, } from './NetworkIcons';
10
10
  import { useInspector } from './Inspector/InspectorContext';
11
11
  const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle, numberOfLines) => {
12
12
  if (!message)
@@ -20,44 +20,45 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
20
20
  const getTagDecorator = (tag) => {
21
21
  const clean = tag.replace(/[\[\]]/g, '').trim().toUpperCase();
22
22
  if (clean === 'AXIOS') {
23
- return { color: AppColors.emerald600, icon: '⚡', label: 'AXIOS' };
23
+ return { color: AppColors.emerald600, Icon: ZapIcon, label: 'AXIOS' };
24
24
  }
25
25
  if (clean === 'API' || clean === 'FETCH' || clean === 'HTTP' || clean === 'NETWORK') {
26
- return { color: AppColors.sky600, icon: '🌐', label: clean };
26
+ return { color: AppColors.sky600, Icon: GlobeIcon, label: clean };
27
27
  }
28
28
  if (clean === 'SAMPLE' || clean === 'BATCH') {
29
- return { color: AppColors.indigo600Alt, icon: '🎲', label: clean };
29
+ return { color: AppColors.indigo600Alt, Icon: DiceIcon, label: clean };
30
30
  }
31
31
  if (clean === 'REDUX' || clean === 'STORE' || clean === 'STATE') {
32
- return { color: AppColors.violet600, icon: '⚛️', label: clean };
32
+ return { color: AppColors.violet600, Icon: AtomIcon, label: clean };
33
33
  }
34
34
  if (clean === 'ANALYTICS' || clean === 'FIREBASE' || clean === 'GA4' || clean === 'GA') {
35
- return { color: AppColors.teal600, icon: '📊', label: clean };
35
+ return { color: AppColors.teal600, Icon: BarChartIcon, label: clean };
36
36
  }
37
37
  if (clean === 'AUTH' || clean === 'TOKEN' || clean === 'SESSION') {
38
- return { color: AppColors.amber600, icon: '🔑', label: clean };
38
+ return { color: AppColors.amber600, Icon: KeyIcon, label: clean };
39
39
  }
40
40
  if (clean === 'APP') {
41
- return { color: AppColors.indigo600Alt, icon: '📱', label: 'APP' };
41
+ return { color: AppColors.indigo600Alt, Icon: SmartphoneIcon, label: 'APP' };
42
42
  }
43
43
  if (clean === 'TEST') {
44
- return { color: AppColors.emerald500, icon: '🧪', label: 'TEST' };
44
+ return { color: AppColors.emerald500, Icon: FlaskIcon, label: 'TEST' };
45
45
  }
46
46
  if (clean === 'WARN' || clean === 'WARNING') {
47
- return { color: AppColors.amber600, icon: '⚠️', label: 'WARN' };
47
+ return { color: AppColors.amber600, Icon: AlertTriangleIcon, label: 'WARN' };
48
48
  }
49
49
  if (clean === 'ERROR' || clean === 'CRASH' || clean === 'BUG') {
50
- return { color: AppColors.red600, icon: '💥', label: 'ERROR' };
50
+ return { color: AppColors.red600, Icon: BugIcon, label: 'ERROR' };
51
51
  }
52
52
  if (clean === 'PERF' || clean === 'RENDER') {
53
- return { color: AppColors.violet500, icon: '⚡', label: clean };
53
+ return { color: AppColors.violet500, Icon: ZapIcon, label: clean };
54
54
  }
55
- return { color: AppColors.slate600, icon: '🏷️', label: clean };
55
+ return { color: AppColors.slate600, Icon: TagIcon, label: clean };
56
56
  };
57
57
  return (<View style={{ flexDirection: 'column', gap: 6 }}>
58
58
  <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 5 }}>
59
59
  {tags.map((tag, i) => {
60
60
  const dec = getTagDecorator(tag);
61
+ const IconComp = dec.Icon;
61
62
  return (<View key={i} style={[
62
63
  styles.prefixTag,
63
64
  {
@@ -65,7 +66,7 @@ const getLogMessageWithBadges = (message, searchStr, textStyle, highlightStyle,
65
66
  borderColor: `${dec.color}35`,
66
67
  },
67
68
  ]}>
68
- <Text style={{ fontSize: 9.5 }}>{dec.icon}</Text>
69
+ <IconComp color={dec.color} size={10}/>
69
70
  <Text style={[styles.prefixTagText, { color: dec.color }]}>
70
71
  {dec.label}
71
72
  </Text>
@@ -93,67 +94,67 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
93
94
  const method = (item.sourceMethod || type).toLowerCase();
94
95
  if (isAnalyticsError || type === 'error' || method === 'error') {
95
96
  return {
96
- border: '#EF4444',
97
- badgeBg: '#FEE2E2',
98
- badgeText: '#DC2626',
97
+ border: AppColors.errorColor,
98
+ badgeBg: AppColors.errorCardBg,
99
+ badgeText: AppColors.rose600,
99
100
  label: 'ERROR',
100
- cardBg: '#FEF2F2',
101
- methodBorder: '#FECACA',
102
- methodBg: '#FEE2E2',
103
- methodText: '#DC2626',
104
- textColor: '#991B1B',
101
+ cardBg: AppColors.errorCardBg,
102
+ methodBorder: AppColors.errorBorder,
103
+ methodBg: AppColors.errorCardBg,
104
+ methodText: AppColors.rose600,
105
+ textColor: AppColors.redErrorText,
105
106
  };
106
107
  }
107
108
  if (type === 'warn' || method === 'warn') {
108
109
  return {
109
- border: '#F59E0B',
110
- badgeBg: '#FEF3C7',
111
- badgeText: '#D97706',
110
+ border: AppColors.amber600,
111
+ badgeBg: AppColors.amberBg,
112
+ badgeText: AppColors.amber700,
112
113
  label: 'WARN',
113
- cardBg: '#FFFBEB',
114
- methodBorder: '#FDE68A',
115
- methodBg: '#FEF3C7',
116
- methodText: '#D97706',
117
- textColor: '#92400E',
114
+ cardBg: AppColors.amberWarmBg,
115
+ methodBorder: AppColors.amberWarmBorder,
116
+ methodBg: AppColors.amberBg,
117
+ methodText: AppColors.amber700,
118
+ textColor: AppColors.amberWarmText,
118
119
  };
119
120
  }
120
121
  if (type === 'debug' || method === 'debug') {
121
122
  return {
122
- border: '#8B5CF6',
123
- badgeBg: '#EDE9FE',
124
- badgeText: '#7C3AED',
123
+ border: AppColors.purple500,
124
+ badgeBg: AppColors.violetSoftBg,
125
+ badgeText: AppColors.violetSoftText,
125
126
  label: 'DEBUG',
126
- cardBg: '#F5F3FF',
127
- methodBorder: '#DDD6FE',
128
- methodBg: '#EDE9FE',
129
- methodText: '#7C3AED',
130
- textColor: '#4C1D95',
127
+ cardBg: AppColors.purpleShade50,
128
+ methodBorder: AppColors.violetSoftBorder,
129
+ methodBg: AppColors.violetSoftBg,
130
+ methodText: AppColors.violetSoftText,
131
+ textColor: AppColors.purpleText,
131
132
  };
132
133
  }
133
134
  if (type === 'info' || method === 'info') {
134
135
  return {
135
- border: '#0EA5E9',
136
- badgeBg: '#E0F2FE',
137
- badgeText: '#0284C7',
136
+ border: AppColors.sky500,
137
+ badgeBg: AppColors.skySoftBg,
138
+ badgeText: AppColors.skySoftText,
138
139
  label: 'INFO',
139
- cardBg: '#F0F9FF',
140
- methodBorder: '#BAE6FD',
141
- methodBg: '#E0F2FE',
142
- methodText: '#0284C7',
143
- textColor: '#075985',
140
+ cardBg: AppColors.blueBg,
141
+ methodBorder: AppColors.skySoftBorder,
142
+ methodBg: AppColors.skySoftBg,
143
+ methodText: AppColors.skySoftText,
144
+ textColor: AppColors.blue800,
144
145
  };
145
146
  }
146
147
  // Default / Standard console.log
147
148
  return {
148
- border: '#6366F1',
149
- badgeBg: '#EEF2FF',
150
- badgeText: '#4F46E5',
149
+ border: AppColors.indigo500,
150
+ badgeBg: AppColors.indigo50,
151
+ badgeText: AppColors.indigo600Alt,
151
152
  label: 'LOG',
152
- cardBg: '#FFFFFF',
153
- methodBorder: '#E0E7FF',
154
- methodBg: '#EEF2FF',
155
- methodText: '#4F46E5',
156
- textColor: '#0F172A',
153
+ cardBg: AppColors.primaryLight,
154
+ methodBorder: AppColors.dividerColor,
155
+ methodBg: AppColors.indigo50,
156
+ methodText: AppColors.indigo600Alt,
157
+ textColor: AppColors.primaryBlack,
157
158
  };
158
159
  };
159
160
  const colors = getLogColors();
@@ -198,11 +199,11 @@ export const ConsoleLogCard = React.memo(function ConsoleLogCard({ item, searchS
198
199
  {jsonContent && (<View style={[
199
200
  styles.metaChip,
200
201
  {
201
- backgroundColor: '#ECFDF5',
202
- borderColor: '#A7F3D0',
202
+ backgroundColor: AppColors.mintGreenBg,
203
+ borderColor: AppColors.mintGreenBorder,
203
204
  },
204
205
  ]}>
205
- <Text style={[styles.metaChipText, { color: '#059669', fontFamily: AppFonts.interBold }]}>
206
+ <Text style={[styles.metaChipText, { color: AppColors.mintGreenText, fontFamily: AppFonts.interBold }]}>
206
207
  {Array.isArray(jsonContent.data)
207
208
  ? `Array[${jsonContent.data.length}]`
208
209
  : `Object{${Object.keys(jsonContent.data).length}}`}
@@ -4,6 +4,7 @@ import { Share } from 'react-native';
4
4
  import TouchableScale from './TouchableScale';
5
5
  // Helpers
6
6
  import { copyToClipboard } from '../helpers';
7
+ import { triggerNativeHaptic } from '../native/NativeInspector';
7
8
  // Assets
8
9
  import { TerminalIcon, FetchIcon, CopyIcon, CheckIcon } from './NetworkIcons';
9
10
  // Stylesheet
@@ -12,6 +13,7 @@ import styles from '../styles';
12
13
  const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
13
14
  const [copied, setCopied] = useState(false);
14
15
  const handlePress = useCallback(() => {
16
+ triggerNativeHaptic('success');
15
17
  const resolvedValue = typeof value === 'function' ? value() : value;
16
18
  copyToClipboard(resolvedValue, label);
17
19
  setCopied(true);
@@ -19,6 +21,7 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
19
21
  }, [value, label]);
20
22
  const handleLongPress = useCallback(() => {
21
23
  try {
24
+ triggerNativeHaptic('medium');
22
25
  const resolvedValue = typeof value === 'function' ? value() : value;
23
26
  const textToShare = typeof resolvedValue === 'string'
24
27
  ? resolvedValue
@@ -39,7 +42,13 @@ const CopyButton = React.memo(({ value, label, iconType = 'copy' }) => {
39
42
  : iconType === 'fetch'
40
43
  ? FetchIcon
41
44
  : CopyIcon;
42
- return (<TouchableScale onPress={handlePress} onLongPress={handleLongPress} hitSlop={12} style={containerStyle}>
45
+ return (<TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={label
46
+ ? copied
47
+ ? `${label} copied`
48
+ : `Copy ${label}`
49
+ : copied
50
+ ? 'Copied to clipboard'
51
+ : 'Copy to clipboard'} accessibilityHint="Double tap to copy, long press to share" onPress={handlePress} onLongPress={handleLongPress} hitSlop={12} style={containerStyle}>
43
52
  {copied ? (<CheckIcon color={AppColors.greenColor} size={14}/>) : (<IconComponent color={AppColors.grayTextWeak} size={14}/>)}
44
53
  </TouchableScale>);
45
54
  });
@@ -7,6 +7,7 @@ import { t } from '../i18n';
7
7
  import { WarningTriangleIcon, CopyIcon, RefreshCcwIcon, ClearIcon, CircleAlertIcon, } from './NetworkIcons';
8
8
  import { handleInterceptedCrash } from '../customHooks/crashHandler';
9
9
  import { CrashType } from '../types/enums';
10
+ import { showNativeFloatingButton, setNativeFloatingButtonBadge, } from '../native/NativeInspector';
10
11
  function parseStackTrace(stack) {
11
12
  if (!stack)
12
13
  return { rootFrame: null, frames: [] };
@@ -109,6 +110,8 @@ ${error.stack || 'No stack trace available'}
109
110
  try {
110
111
  const stack = error?.stack || errorInfo?.componentStack || '';
111
112
  handleInterceptedCrash(error, stack, false, CrashType.Render, errorInfo?.componentStack);
113
+ showNativeFloatingButton();
114
+ setNativeFloatingButtonBadge(true);
112
115
  }
113
116
  catch { }
114
117
  }
@@ -21,6 +21,10 @@ const AnalyticsHeader = React.memo(() => {
21
21
  const userProperties = getCurrentUserProperties();
22
22
  const defaultParams = getDefaultEventParameters();
23
23
  const isTrackingEnabled = getCollectionEnabled();
24
+ // If there are no analytics events in the session, hide the graph, category breakdown, and realtime card
25
+ if (analyticsEvents.length === 0) {
26
+ return null;
27
+ }
24
28
  const hasUserProps = Object.keys(userProperties).length > 0;
25
29
  const hasDefaultParams = Object.keys(defaultParams).length > 0;
26
30
  const totalEvents = filteredAnalyticsEvents.length;
@@ -590,8 +594,8 @@ const AnalyticsHeader = React.memo(() => {
590
594
  </View>
591
595
 
592
596
  {/* ── Crashlytics-Style Multi-Bar Histogram with Trendline Overlay ── */}
593
- <View style={{ height: chartHeight, position: 'relative' }}>
594
- <Svg width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`}>
597
+ <View style={{ height: chartHeight, position: 'relative', overflow: 'hidden', borderRadius: 8 }}>
598
+ <Svg width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`} preserveAspectRatio="none">
595
599
  <Defs>
596
600
  <SvgLinearGradient id="crashlyticsBarGrad" x1="0" y1="0" x2="0" y2="1">
597
601
  <Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.9"/>
@@ -955,7 +959,7 @@ const AnalyticsTab = React.memo(() => {
955
959
  <AnalyticsFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)}/>
956
960
 
957
961
  <View style={{ flex: 1 }} onLayout={() => setIsAnalyticsLayoutReady(true)}>
958
- {selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={20} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={false} ListEmptyComponent={<View style={styles.emptyContainer}>
962
+ {selectedEvent != null ? (<AnalyticsDetail event={selectedEvent}/>) : isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<View style={styles.emptyContainer}>
959
963
  <View style={styles.emptyIconWrap}>
960
964
  <EmptyRadarIcon color={AppColors.purple} size={32}/>
961
965
  </View>
@@ -379,7 +379,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
379
379
  const indentLeft = level * 16 + 10;
380
380
  if (node.isFolder) {
381
381
  return (<View style={bundleStyles.treeFolderBlock}>
382
- <TouchableScale onPress={() => toggleFolder(node.fullPath)} style={[
382
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`Folder ${node.name}, ${node.fileCount} ${node.fileCount === 1 ? 'file' : 'files'}, ${node.sizeKb >= 1024 ? `${(node.sizeKb / 1024).toFixed(2)} MB` : `${node.sizeKb} KB`}`} accessibilityState={{ expanded: isExpanded }} accessibilityHint="Double tap to expand or collapse folder" onPress={() => toggleFolder(node.fullPath)} style={[
383
383
  bundleStyles.treeFolderRow,
384
384
  { paddingLeft: indentLeft },
385
385
  ]}>
@@ -453,7 +453,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
453
453
  </View>
454
454
 
455
455
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
456
- {onDownload && (<TouchableScale onPress={() => onDownload(file)} style={bundleStyles.treeActionBtn} hitSlop={8}>
456
+ {onDownload && (<TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`Download source code for ${file.name}`} accessibilityHint="Fetches module source code from Metro dev server" onPress={() => onDownload(file)} style={bundleStyles.treeActionBtn} hitSlop={8}>
457
457
  <DownloadIcon size={13} color={AppColors.sky500}/>
458
458
  </TouchableScale>)}
459
459
 
@@ -746,7 +746,7 @@ const BundleTab = React.memo(() => {
746
746
  <ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={bundleStyles.subTabsScroll}>
747
747
  {subTabs.map(tab => {
748
748
  const isActive = activeSubTab === tab.key;
749
- return (<TouchableScale key={tab.key} onPress={() => setActiveSubTab(tab.key)} style={[
749
+ return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="tab" accessibilityLabel={`${tab.label} tab`} accessibilityState={{ selected: isActive }} accessibilityHint={`Switches to ${tab.label} tab`} onPress={() => setActiveSubTab(tab.key)} style={[
750
750
  bundleStyles.subTabPill,
751
751
  isActive && bundleStyles.subTabPillActive,
752
752
  ]}>
@@ -760,7 +760,7 @@ const BundleTab = React.memo(() => {
760
760
  </TouchableScale>);
761
761
  })}
762
762
  </ScrollView>
763
- <TouchableScale onPress={refreshAnalysis} style={bundleStyles.reloadButton} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
763
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={isAnalyzing ? "Analyzing Metro bundle" : "Reload bundle analysis"} accessibilityHint="Re-probes Metro dev server and refreshes bundle statistics" onPress={refreshAnalysis} style={bundleStyles.reloadButton} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
764
764
  {isAnalyzing ? (<ActivityIndicator size="small" color={AppColors.brandPurple}/>) : (<RefreshCcwIcon color={AppColors.brandPurple} size={14}/>)}
765
765
  </TouchableScale>
766
766
  </View>
@@ -782,7 +782,7 @@ const BundleTab = React.memo(() => {
782
782
  <Text style={bundleStyles.heroSubtitle}>
783
783
  {t('bundle.heroSubtitle')}
784
784
  </Text>
785
- <Pressable style={{
785
+ <Pressable accessible={true} accessibilityRole="link" accessibilityLabel={`Live Metro bundle URL: ${analysis.scriptURL}`} accessibilityHint="Opens Metro bundle script in browser" style={{
786
786
  backgroundColor: `${AppColors.skyBlue}10`,
787
787
  borderColor: `${AppColors.skyBlue}30`,
788
788
  borderWidth: 1,
@@ -816,7 +816,7 @@ const BundleTab = React.memo(() => {
816
816
  {analysis.scriptURL.length} chars
817
817
  </Text>
818
818
  </View>
819
- <Text selectable={true} style={{
819
+ <Text selectable={true} numberOfLines={1} ellipsizeMode="tail" style={{
820
820
  fontFamily: AppFonts.interMedium,
821
821
  fontSize: 11,
822
822
  color: AppColors.skyBlue,
@@ -992,7 +992,7 @@ const BundleTab = React.memo(() => {
992
992
  <CopyButton value={() => summary} label={t('bundle.categoriesJson')}/>
993
993
  </View>
994
994
 
995
- {summary.categories.map((cat, cIdx) => (<Pressable key={cIdx} style={({ pressed }) => [
995
+ {summary.categories.map((cat, cIdx) => (<Pressable key={cIdx} accessible={true} accessibilityRole="button" accessibilityLabel={`${cat.title}, ${cat.sizeKb >= 1024 ? `${cat.sizeMb} MB` : `${cat.sizeKb} KB`}, ${cat.pct}% of bundle`} accessibilityHint={`Double tap to view ${cat.title} details`} style={({ pressed }) => [
996
996
  bundleStyles.catRowCard,
997
997
  pressed && { opacity: 0.85, transform: [{ scale: 0.99 }] },
998
998
  ]} onPress={() => setActiveSubTab(cat.targetTab)}>
@@ -1250,7 +1250,7 @@ const BundleTab = React.memo(() => {
1250
1250
  { key: 'androidApk', label: 'Universal APK (.apk)', badge: `${analysis?.production.androidApk.totalInstallMb || 364.0} MB` },
1251
1251
  ].map(tab => {
1252
1252
  const isSelected = prodPlatform === tab.key;
1253
- return (<TouchableScale key={tab.key} onPress={() => setProdPlatform(tab.key)} style={[
1253
+ return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="tab" accessibilityLabel={`${tab.label}, ${tab.badge}`} accessibilityState={{ selected: isSelected }} accessibilityHint={`Switches production estimate to ${tab.label}`} onPress={() => setProdPlatform(tab.key)} style={[
1254
1254
  bundleStyles.prodPlatformBtn,
1255
1255
  isSelected && bundleStyles.prodPlatformBtnActive,
1256
1256
  ]}>
@@ -1500,8 +1500,8 @@ const BundleTab = React.memo(() => {
1500
1500
  <View style={bundleStyles.filterContainer}>
1501
1501
  <View style={bundleStyles.searchRow}>
1502
1502
  <SearchIcon color={AppColors.grayTextWeak} size={15}/>
1503
- <TextInput placeholder={t('bundle.searchFilesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1504
- {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1503
+ <TextInput accessible={true} accessibilityRole="search" accessibilityLabel="Search files" accessibilityHint="Type a filename, path, or extension to filter" placeholder={t('bundle.searchFilesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1504
+ {search.length > 0 && (<Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Clear search input" onPress={() => setSearch('')} hitSlop={10}>
1505
1505
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1506
1506
  </Pressable>)}
1507
1507
  <CopyButton value={() => filteredFiles} label={t('bundle.filteredFilesJson')}/>
@@ -1518,7 +1518,7 @@ const BundleTab = React.memo(() => {
1518
1518
  : tab.key === 'CONSUMED'
1519
1519
  ? bundleFiles.filter(f => f.isConsumed !== false).length
1520
1520
  : bundleFiles.filter(f => f.category === tab.key).length;
1521
- return (<TouchableScale key={tab.key} onPress={() => setActiveCategory(tab.key)} style={[
1521
+ return (<TouchableScale key={tab.key} accessible={true} accessibilityRole="button" accessibilityLabel={`Filter by ${t(tab.labelKey)}, ${count} items`} accessibilityState={{ selected: isActive }} accessibilityHint={`Filters files list to ${t(tab.labelKey)}`} onPress={() => setActiveCategory(tab.key)} style={[
1522
1522
  bundleStyles.catPill,
1523
1523
  isActive && bundleStyles.catPillActive,
1524
1524
  tab.key === 'UNUSED' && isActive && { backgroundColor: AppColors.red500, borderColor: AppColors.red600 },
@@ -1542,7 +1542,7 @@ const BundleTab = React.memo(() => {
1542
1542
  {/* View Mode Toggle Header Bar */}
1543
1543
  <View style={bundleStyles.treeHeaderRow}>
1544
1544
  <View style={bundleStyles.viewModeToggleGroup}>
1545
- <TouchableScale onPress={() => setFilesViewMode('tree')} style={[
1545
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Tree folder view" accessibilityState={{ selected: filesViewMode === 'tree' }} onPress={() => setFilesViewMode('tree')} style={[
1546
1546
  bundleStyles.viewModeBtn,
1547
1547
  filesViewMode === 'tree' && bundleStyles.viewModeBtnActive,
1548
1548
  ]}>
@@ -1553,7 +1553,7 @@ const BundleTab = React.memo(() => {
1553
1553
  {t('bundle.treeView')}
1554
1554
  </Text>
1555
1555
  </TouchableScale>
1556
- <TouchableScale onPress={() => setFilesViewMode('list')} style={[
1556
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Flat list view" accessibilityState={{ selected: filesViewMode === 'list' }} onPress={() => setFilesViewMode('list')} style={[
1557
1557
  bundleStyles.viewModeBtn,
1558
1558
  filesViewMode === 'list' && bundleStyles.viewModeBtnActive,
1559
1559
  ]}>
@@ -1567,11 +1567,11 @@ const BundleTab = React.memo(() => {
1567
1567
  </View>
1568
1568
 
1569
1569
  {filesViewMode === 'tree' ? (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
1570
- <TouchableScale onPress={expandAllFolders}>
1570
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Expand all folders" onPress={expandAllFolders}>
1571
1571
  <Text style={bundleStyles.treeActionLink}>{t('bundle.expand')}</Text>
1572
1572
  </TouchableScale>
1573
1573
  <Text style={{ color: AppColors.grayTextWeak, fontSize: 11 }}>•</Text>
1574
- <TouchableScale onPress={collapseAllFolders}>
1574
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel="Collapse all folders" onPress={collapseAllFolders}>
1575
1575
  <Text style={bundleStyles.treeActionLink}>{t('bundle.collapse')}</Text>
1576
1576
  </TouchableScale>
1577
1577
  </View>) : (<Text style={bundleStyles.listHeaderCount}>
@@ -1695,8 +1695,8 @@ const BundleTab = React.memo(() => {
1695
1695
  <View style={bundleStyles.filterContainer}>
1696
1696
  <View style={bundleStyles.searchRow}>
1697
1697
  <SearchIcon color={AppColors.grayTextWeak} size={15}/>
1698
- <TextInput placeholder={t('bundle.searchPackagesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1699
- {search.length > 0 && (<Pressable onPress={() => setSearch('')} hitSlop={10}>
1698
+ <TextInput accessible={true} accessibilityRole="search" accessibilityLabel="Search packages" accessibilityHint="Type a package name or description to filter" placeholder={t('bundle.searchPackagesPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={bundleStyles.searchInput} autoCorrect={false} autoCapitalize="none"/>
1699
+ {search.length > 0 && (<Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Clear search input" onPress={() => setSearch('')} hitSlop={10}>
1700
1700
  <ClearIcon color={AppColors.grayTextWeak} size={14}/>
1701
1701
  </Pressable>)}
1702
1702
  <CopyButton value={() => filteredPackages} label={t('bundle.dependenciesJson')}/>
@@ -1711,7 +1711,7 @@ const BundleTab = React.memo(() => {
1711
1711
  const cleanInstalledVersion = pkg.version ? pkg.version.replace(/^\^/, '') : '';
1712
1712
  const hasUpdate = !!pkg.latestVersion && !!cleanInstalledVersion &&
1713
1713
  pkg.latestVersion !== cleanInstalledVersion;
1714
- return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: AppColors.errorBorder, backgroundColor: '#FFFDFD' }]}>
1714
+ return (<View key={pkg.id} style={[bundleStyles.fileCard, pkg.isDeprecated && { borderColor: AppColors.errorBorder, backgroundColor: AppColors.errorCardBg }]}>
1715
1715
  {/* Header Row: #s.no + Logo + Package Name + Version Pill + Update/Deprecated Badges + Size & Copy */}
1716
1716
  <View style={bundleStyles.fileCardTop}>
1717
1717
  <View style={bundleStyles.sNoBadge}>
@@ -1811,7 +1811,7 @@ const BundleTab = React.memo(() => {
1811
1811
  <Text style={bundleStyles.fileMetaText}>
1812
1812
  {t('bundle.minified', { size: pkg.sizeKb })}
1813
1813
  </Text>
1814
- <TouchableScale onPress={() => {
1814
+ <TouchableScale accessible={true} accessibilityRole="link" accessibilityLabel={`Open ${pkg.name} on NPM`} accessibilityHint="Opens NPM package page in browser" onPress={() => {
1815
1815
  const url = pkg.npmUrl || `https://www.npmjs.com/package/${pkg.name}`;
1816
1816
  Linking.openURL(url).catch(() => { });
1817
1817
  }} style={bundleStyles.npmLinkBtn}>
@@ -1890,7 +1890,7 @@ const BundleTab = React.memo(() => {
1890
1890
  {t('bundle.fileSizeKb', { size: file.sizeKb })}
1891
1891
  </Text>
1892
1892
  </View>
1893
- <TouchableScale onPress={() => downloadBundleFile(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1893
+ <TouchableScale accessible={true} accessibilityRole="button" accessibilityLabel={`Download ${file.name}`} accessibilityHint="Downloads media asset file" onPress={() => downloadBundleFile(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1894
1894
  <DownloadIcon size={14} color={AppColors.sky500}/>
1895
1895
  </TouchableScale>
1896
1896
  <CopyButton value={() => file} label={t('bundle.mediaItemJson')}/>
@@ -469,7 +469,7 @@ const ConsoleTab = React.memo(() => {
469
469
  </ScrollView>
470
470
  </View>
471
471
 
472
- <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={15} maxToRenderPerBatch={15} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
472
+ <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
473
473
  styles.listContent,
474
474
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
475
475
  ]} keyboardShouldPersistTaps="handled"/>
@@ -97,23 +97,27 @@ const CrashDetail = React.memo(() => {
97
97
  localStyles.pillBadge,
98
98
  {
99
99
  backgroundColor: selectedCrash.isFatal
100
- ? '#FEE2E2'
101
- : '#FEF3C7',
102
- borderColor: selectedCrash.isFatal ? '#FCA5A5' : '#FCD34D',
100
+ ? AppColors.errorCardBg
101
+ : AppColors.amberBg,
102
+ borderColor: selectedCrash.isFatal
103
+ ? AppColors.errorBorder
104
+ : AppColors.amberWarmBorder,
103
105
  },
104
106
  ]}>
105
107
  <View style={[
106
108
  localStyles.statusDot,
107
109
  {
108
110
  backgroundColor: selectedCrash.isFatal
109
- ? '#DC2626'
110
- : '#D97706',
111
+ ? AppColors.red600
112
+ : AppColors.amber600,
111
113
  },
112
114
  ]}/>
113
115
  <Text style={[
114
116
  localStyles.pillBadgeText,
115
117
  {
116
- color: selectedCrash.isFatal ? '#991B1B' : '#92400E',
118
+ color: selectedCrash.isFatal
119
+ ? AppColors.redErrorText
120
+ : AppColors.amberWarmText,
117
121
  },
118
122
  ]}>
119
123
  {selectedCrash.isFatal ? t('crash.fatalCrash') : t('crash.handledException')}
@@ -124,11 +128,11 @@ const CrashDetail = React.memo(() => {
124
128
  <View style={[
125
129
  localStyles.pillBadge,
126
130
  {
127
- backgroundColor: '#EDE9FE',
128
- borderColor: '#C4B5FD',
131
+ backgroundColor: AppColors.violetSoftBg,
132
+ borderColor: AppColors.violetSoftBorder,
129
133
  },
130
134
  ]}>
131
- <Text style={[localStyles.pillBadgeText, { color: '#6D28D9' }]}>
135
+ <Text style={[localStyles.pillBadgeText, { color: AppColors.purple700 }]}>
132
136
  {selectedCrash.type.toUpperCase()}
133
137
  </Text>
134
138
  </View>
@@ -138,12 +142,12 @@ const CrashDetail = React.memo(() => {
138
142
  {/* Copy Report Action */}
139
143
  <TouchableScale onPress={handleCopyReport} hitSlop={10} style={[
140
144
  localStyles.actionButton,
141
- copiedSuccess && { backgroundColor: '#ECFDF5', borderColor: '#6EE7B7' },
145
+ copiedSuccess && { backgroundColor: AppColors.mintGreenBg, borderColor: AppColors.mintGreenBorder },
142
146
  ]}>
143
- {copiedSuccess ? (<CircleCheckIcon color="#059669" size={12}/>) : (<CopyIcon color={AppColors.purple} size={12}/>)}
147
+ {copiedSuccess ? (<CircleCheckIcon color={AppColors.mintGreenText} size={12}/>) : (<CopyIcon color={AppColors.purple} size={12}/>)}
144
148
  <Text style={[
145
149
  localStyles.actionButtonText,
146
- copiedSuccess && { color: '#059669' },
150
+ copiedSuccess && { color: AppColors.mintGreenText },
147
151
  ]}>
148
152
  {copiedSuccess ? t('crash.copied') : t('crash.copyReport')}
149
153
  </Text>