react-native-inapp-inspector 2.2.2 → 2.2.4

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 (159) hide show
  1. package/dist/commonjs/components/AnalyticsDetail.js +5 -1
  2. package/dist/commonjs/components/AnalyticsEventCard.js +2 -1
  3. package/dist/commonjs/components/DiffViewer.js +1 -1
  4. package/dist/commonjs/components/Inspector/BundleTab.js +22 -0
  5. package/dist/commonjs/components/Inspector/DeviceInfoTab.d.ts +3 -0
  6. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +835 -0
  7. package/dist/commonjs/components/Inspector/InspectorHeader.js +12 -14
  8. package/dist/commonjs/components/Inspector/LogDetail.js +6 -3
  9. package/dist/commonjs/components/Inspector/MainScreen.js +7 -3
  10. package/dist/commonjs/components/Inspector/NetworkTab.js +92 -15
  11. package/dist/commonjs/components/Inspector/PerformanceTab.js +38 -12
  12. package/dist/commonjs/components/Inspector/SettingsPanel.js +440 -229
  13. package/dist/commonjs/components/Inspector/StorageTab.d.ts +3 -0
  14. package/dist/commonjs/components/Inspector/StorageTab.js +892 -0
  15. package/dist/commonjs/components/Inspector/TabBar.js +14 -0
  16. package/dist/commonjs/components/Inspector/TelemetryConsentModal.js +11 -2
  17. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +4 -1
  18. package/dist/commonjs/components/NetworkIcons.d.ts +6 -0
  19. package/dist/commonjs/components/NetworkIcons.js +30 -1
  20. package/dist/commonjs/constants/version.d.ts +1 -1
  21. package/dist/commonjs/constants/version.js +1 -1
  22. package/dist/commonjs/customHooks/bundleAnalyzer.js +44 -22
  23. package/dist/commonjs/customHooks/storageInspector.d.ts +67 -0
  24. package/dist/commonjs/customHooks/storageInspector.js +486 -0
  25. package/dist/commonjs/i18n/locales/en.json +8 -2
  26. package/dist/commonjs/index.d.ts +1 -0
  27. package/dist/commonjs/index.js +16 -1
  28. package/dist/commonjs/storage.d.ts +5 -0
  29. package/dist/commonjs/storage.js +18 -0
  30. package/dist/commonjs/types/enums.d.ts +5 -0
  31. package/dist/commonjs/types/enums.js +5 -0
  32. package/dist/esm/components/AnalyticsDetail.js +6 -2
  33. package/dist/esm/components/AnalyticsEventCard.js +2 -1
  34. package/dist/esm/components/DiffViewer.js +1 -1
  35. package/dist/esm/components/Inspector/BundleTab.js +22 -0
  36. package/dist/esm/components/Inspector/DeviceInfoTab.d.ts +3 -0
  37. package/dist/esm/components/Inspector/DeviceInfoTab.js +796 -0
  38. package/dist/esm/components/Inspector/InspectorHeader.js +13 -15
  39. package/dist/esm/components/Inspector/LogDetail.js +7 -4
  40. package/dist/esm/components/Inspector/MainScreen.js +7 -3
  41. package/dist/esm/components/Inspector/NetworkTab.js +93 -16
  42. package/dist/esm/components/Inspector/PerformanceTab.js +39 -13
  43. package/dist/esm/components/Inspector/SettingsPanel.js +441 -230
  44. package/dist/esm/components/Inspector/StorageTab.d.ts +3 -0
  45. package/dist/esm/components/Inspector/StorageTab.js +853 -0
  46. package/dist/esm/components/Inspector/TabBar.js +15 -1
  47. package/dist/esm/components/Inspector/TelemetryConsentModal.js +11 -2
  48. package/dist/esm/components/Inspector/UpdateAvailableModal.js +4 -1
  49. package/dist/esm/components/NetworkIcons.d.ts +6 -0
  50. package/dist/esm/components/NetworkIcons.js +23 -0
  51. package/dist/esm/constants/version.d.ts +1 -1
  52. package/dist/esm/constants/version.js +1 -1
  53. package/dist/esm/customHooks/bundleAnalyzer.js +44 -22
  54. package/dist/esm/customHooks/storageInspector.d.ts +67 -0
  55. package/dist/esm/customHooks/storageInspector.js +469 -0
  56. package/dist/esm/i18n/locales/en.json +8 -2
  57. package/dist/esm/index.d.ts +1 -0
  58. package/dist/esm/index.js +5 -0
  59. package/dist/esm/storage.d.ts +5 -0
  60. package/dist/esm/storage.js +5 -0
  61. package/dist/esm/types/enums.d.ts +5 -0
  62. package/dist/esm/types/enums.js +5 -0
  63. package/package.json +9 -1
  64. package/src/analytics.ts +35 -0
  65. package/src/bundle.ts +25 -0
  66. package/src/components/AnalyticsDetail.tsx +870 -0
  67. package/src/components/AnalyticsEventCard.tsx +442 -0
  68. package/src/components/AnalyticsGraph.tsx +583 -0
  69. package/src/components/AnimatedEntrance.tsx +64 -0
  70. package/src/components/AppHeaderLogo.tsx +109 -0
  71. package/src/components/BrandCircleIcon.tsx +144 -0
  72. package/src/components/BrandSquareIcon.tsx +144 -0
  73. package/src/components/CodeSnippet.tsx +725 -0
  74. package/src/components/ConsoleLogCard.tsx +628 -0
  75. package/src/components/CopyButton.tsx +87 -0
  76. package/src/components/DiffViewer.tsx +82 -0
  77. package/src/components/DomainHeader.tsx +237 -0
  78. package/src/components/EmptyState.tsx +73 -0
  79. package/src/components/EndOfListFooter.tsx +100 -0
  80. package/src/components/ErrorBoundary.tsx +688 -0
  81. package/src/components/HeadersSection.tsx +192 -0
  82. package/src/components/HighlightText.tsx +100 -0
  83. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  84. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  85. package/src/components/Inspector/BundleTab.tsx +4760 -0
  86. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  87. package/src/components/Inspector/CrashDetail.tsx +941 -0
  88. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  89. package/src/components/Inspector/CrashTab.tsx +871 -0
  90. package/src/components/Inspector/DeviceInfoTab.tsx +1204 -0
  91. package/src/components/Inspector/FabLauncher.tsx +80 -0
  92. package/src/components/Inspector/InspectorContext.tsx +28 -0
  93. package/src/components/Inspector/InspectorHeader.tsx +972 -0
  94. package/src/components/Inspector/LogDetail.tsx +1431 -0
  95. package/src/components/Inspector/MainScreen.tsx +262 -0
  96. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  97. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  98. package/src/components/Inspector/NetworkTab.tsx +1183 -0
  99. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  100. package/src/components/Inspector/PerformanceTab.tsx +1928 -0
  101. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  102. package/src/components/Inspector/ReduxTab.tsx +954 -0
  103. package/src/components/Inspector/SettingsPanel.tsx +3447 -0
  104. package/src/components/Inspector/StorageTab.tsx +1048 -0
  105. package/src/components/Inspector/TabBar.tsx +207 -0
  106. package/src/components/Inspector/TelemetryConsentModal.tsx +416 -0
  107. package/src/components/Inspector/UpdateAvailableModal.tsx +557 -0
  108. package/src/components/JsonViewer.tsx +486 -0
  109. package/src/components/LogCard.tsx +491 -0
  110. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  111. package/src/components/MetaAccordion.tsx +340 -0
  112. package/src/components/MiniBarChart.tsx +42 -0
  113. package/src/components/MiniLineChart.tsx +33 -0
  114. package/src/components/NetworkIcons.tsx +2221 -0
  115. package/src/components/SectionHeader.tsx +113 -0
  116. package/src/components/SegmentedTabs.tsx +83 -0
  117. package/src/components/Slider.tsx +300 -0
  118. package/src/components/SourcePageCard.tsx +148 -0
  119. package/src/components/Toast.tsx +131 -0
  120. package/src/components/TouchableScale.tsx +91 -0
  121. package/src/components/TreeNode.tsx +186 -0
  122. package/src/console.ts +24 -0
  123. package/src/constants/index.ts +38 -0
  124. package/src/constants/version.ts +3 -0
  125. package/src/crash.ts +32 -0
  126. package/src/customHooks/analyticsLogger.ts +336 -0
  127. package/src/customHooks/bundleAnalyzer.ts +1256 -0
  128. package/src/customHooks/consoleLogger.ts +497 -0
  129. package/src/customHooks/crashHandler.ts +944 -0
  130. package/src/customHooks/logFilters.ts +32 -0
  131. package/src/customHooks/networkLogger.ts +419 -0
  132. package/src/customHooks/performanceTracker.ts +1014 -0
  133. package/src/customHooks/reduxLogger.ts +406 -0
  134. package/src/customHooks/storageInspector.ts +509 -0
  135. package/src/customHooks/useAccordion.tsx +60 -0
  136. package/src/decorators/index.ts +184 -0
  137. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  138. package/src/helpers/index.ts +860 -0
  139. package/src/helpers/memoryManager.ts +138 -0
  140. package/src/helpers/searchQueryParser.ts +283 -0
  141. package/src/helpers/settingsStore.ts +171 -0
  142. package/src/helpers/telemetry.ts +505 -0
  143. package/src/helpers/toast.ts +17 -0
  144. package/src/i18n/index.ts +69 -0
  145. package/src/i18n/locales/en.json +1058 -0
  146. package/src/index.tsx +2356 -0
  147. package/src/native/NativeInspector.ts +397 -0
  148. package/src/native/NativeNetworkInspector.ts +24 -0
  149. package/src/network.ts +22 -0
  150. package/src/performance.ts +38 -0
  151. package/src/redux.ts +23 -0
  152. package/src/storage.ts +18 -0
  153. package/src/styles/AppColors.ts +408 -0
  154. package/src/styles/AppFonts.ts +8 -0
  155. package/src/styles/common.ts +209 -0
  156. package/src/styles/index.ts +1570 -0
  157. package/src/types/enums.ts +199 -0
  158. package/src/types/index.ts +34 -0
  159. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,941 @@
1
+ import React, {useMemo, useState} from 'react';
2
+ import {
3
+ Platform,
4
+ ScrollView,
5
+ StyleSheet,
6
+ Text,
7
+ TextInput,
8
+ View,
9
+ } from 'react-native';
10
+ import {useTranslation} from '../../i18n';
11
+ import {useInspector} from './InspectorContext';
12
+ import TouchableScale from '../TouchableScale';
13
+ import SegmentedTabs from '../SegmentedTabs';
14
+ import HighlightText from '../HighlightText';
15
+ import JsonViewer from '../JsonViewer';
16
+ import {AppColors} from '../../styles/AppColors';
17
+ import {AppFonts} from '../../styles/AppFonts';
18
+ import {
19
+ ParsedStackFrame,
20
+ } from '../../types';
21
+ import {
22
+ CrashDetailSubTab,
23
+ } from '../../types/enums';
24
+ import {
25
+ exportCrashReport,
26
+ parseCrashStackTrace,
27
+ } from '../../customHooks/crashHandler';
28
+ import {copyToClipboard} from '../../helpers';
29
+ import {
30
+ SearchIcon,
31
+ ClearIcon,
32
+ CopyIcon,
33
+ TerminalIcon,
34
+ LayersIcon,
35
+ ClockIcon,
36
+ CircleCheckIcon,
37
+ GlobeIcon,
38
+ AppleIcon,
39
+ AndroidIcon,
40
+ StackTraceIcon,
41
+ DiagnosticsIcon,
42
+ TrailIcon,
43
+ RawJsonIcon,
44
+ AppFramesIcon,
45
+ AllFramesIcon,
46
+ } from '../NetworkIcons';
47
+
48
+
49
+
50
+ const CrashDetail: React.FC = React.memo(() => {
51
+ const {t} = useTranslation();
52
+ const {selectedCrash} = useInspector();
53
+
54
+ const [activeSubTab, setActiveSubTab] = useState<CrashDetailSubTab>('stack');
55
+ const [searchQuery, setSearchQuery] = useState('');
56
+ const [stackFilter, setStackFilter] = useState<'app' | 'all'>('app');
57
+ const [copiedSuccess, setCopiedSuccess] = useState(false);
58
+ const [copiedFrameIndex, setCopiedFrameIndex] = useState<number | null>(null);
59
+
60
+ if (!selectedCrash) return null;
61
+
62
+ const stackFrames: ParsedStackFrame[] = useMemo(() => {
63
+ if (selectedCrash.parsedStack && selectedCrash.parsedStack.length > 0) {
64
+ return selectedCrash.parsedStack;
65
+ }
66
+ if (selectedCrash.stack) {
67
+ return parseCrashStackTrace(selectedCrash.stack);
68
+ }
69
+ return [];
70
+ }, [selectedCrash]);
71
+
72
+ // Strip keys holding explicit undefined values (e.g. componentStack) so the
73
+ // Raw JSON tab never renders a literal "undefined".
74
+ const rawCrashData = useMemo(() => {
75
+ try {
76
+ return JSON.parse(JSON.stringify(selectedCrash));
77
+ } catch {
78
+ return selectedCrash;
79
+ }
80
+ }, [selectedCrash]);
81
+
82
+ const appStackFrames = useMemo(() => {
83
+ return stackFrames.filter(f => f.isAppCode);
84
+ }, [stackFrames]);
85
+
86
+ const displayedFrames = useMemo(() => {
87
+ if (stackFilter === 'app' && appStackFrames.length > 0) {
88
+ return appStackFrames;
89
+ }
90
+ return stackFrames;
91
+ }, [stackFilter, appStackFrames, stackFrames]);
92
+
93
+ const originFrame = useMemo(() => {
94
+ return appStackFrames[0] || stackFrames[0] || null;
95
+ }, [appStackFrames, stackFrames]);
96
+
97
+ const handleCopyReport = () => {
98
+ const report = exportCrashReport(selectedCrash);
99
+ copyToClipboard(report, 'Crash Report');
100
+ setCopiedSuccess(true);
101
+ setTimeout(() => setCopiedSuccess(false), 2000);
102
+ };
103
+
104
+ const handleCopyFrame = (frame: ParsedStackFrame, index: number) => {
105
+ const text = frame.raw || `${frame.method}@${frame.file}:${frame.lineNumber}:${frame.column}`;
106
+ copyToClipboard(text, 'Stack Frame');
107
+ setCopiedFrameIndex(index);
108
+ setTimeout(() => setCopiedFrameIndex(null), 1500);
109
+ };
110
+
111
+ const tabsConfig = [
112
+ {
113
+ key: 'stack',
114
+ label: t('crash.tabStack', {count: stackFrames.length}),
115
+ icon: (isActive: boolean) => (
116
+ <StackTraceIcon
117
+ size={12}
118
+ color={isActive ? AppColors.white : AppColors.grayText}
119
+ />
120
+ ),
121
+ },
122
+ {
123
+ key: 'diagnostics',
124
+ label: t('crash.tabDiagnostics'),
125
+ icon: (isActive: boolean) => (
126
+ <DiagnosticsIcon
127
+ size={12}
128
+ color={isActive ? AppColors.white : AppColors.grayText}
129
+ />
130
+ ),
131
+ },
132
+ {
133
+ key: 'breadcrumbs',
134
+ label: t('crash.tabTrail', {count: selectedCrash.breadcrumbs?.length || 0}),
135
+ icon: (isActive: boolean) => (
136
+ <TrailIcon
137
+ size={12}
138
+ color={isActive ? AppColors.white : AppColors.grayText}
139
+ />
140
+ ),
141
+ },
142
+ {
143
+ key: 'raw',
144
+ label: t('crash.tabRawJson'),
145
+ icon: (isActive: boolean) => (
146
+ <RawJsonIcon
147
+ size={12}
148
+ color={isActive ? AppColors.white : AppColors.grayText}
149
+ />
150
+ ),
151
+ },
152
+ ];
153
+
154
+ return (
155
+ <View style={{flex: 1, backgroundColor: AppColors.grayBackground}}>
156
+ {/* ─── Top Info Bar (Similar to NetworkDetail / LogDetail) ─── */}
157
+ <View style={{paddingHorizontal: 8, paddingTop: 6}}>
158
+ <View style={localStyles.infoBar}>
159
+ {/* Top Status Badges Row */}
160
+ <View style={localStyles.badgeRow}>
161
+ {/* Fatal / Non-fatal Pill */}
162
+ <View
163
+ style={[
164
+ localStyles.pillBadge,
165
+ {
166
+ backgroundColor: selectedCrash.isFatal
167
+ ? AppColors.errorCardBg
168
+ : AppColors.amberBg,
169
+ borderColor: selectedCrash.isFatal
170
+ ? AppColors.errorBorder
171
+ : AppColors.amberWarmBorder,
172
+ },
173
+ ]}>
174
+ <View
175
+ style={[
176
+ localStyles.statusDot,
177
+ {
178
+ backgroundColor: selectedCrash.isFatal
179
+ ? AppColors.red600
180
+ : AppColors.amber600,
181
+ },
182
+ ]}
183
+ />
184
+ <Text
185
+ style={[
186
+ localStyles.pillBadgeText,
187
+ {
188
+ color: selectedCrash.isFatal
189
+ ? AppColors.redErrorText
190
+ : AppColors.amberWarmText,
191
+ },
192
+ ]}>
193
+ {selectedCrash.isFatal ? t('crash.fatalCrash') : t('crash.handledException')}
194
+ </Text>
195
+ </View>
196
+
197
+ {/* Type Pill */}
198
+ <View
199
+ style={[
200
+ localStyles.pillBadge,
201
+ {
202
+ backgroundColor: AppColors.violetSoftBg,
203
+ borderColor: AppColors.violetSoftBorder,
204
+ },
205
+ ]}>
206
+ <Text style={[localStyles.pillBadgeText, {color: AppColors.purple700}]}>
207
+ {selectedCrash.type.toUpperCase()}
208
+ </Text>
209
+ </View>
210
+
211
+ <View style={{flex: 1}} />
212
+
213
+ {/* Copy Report Action */}
214
+ <TouchableScale
215
+ onPress={handleCopyReport}
216
+ hitSlop={10}
217
+ style={[
218
+ localStyles.actionButton,
219
+ copiedSuccess && {backgroundColor: AppColors.mintGreenBg, borderColor: AppColors.mintGreenBorder},
220
+ ]}>
221
+ {copiedSuccess ? (
222
+ <CircleCheckIcon color={AppColors.mintGreenText} size={12} />
223
+ ) : (
224
+ <CopyIcon color={AppColors.purple} size={12} />
225
+ )}
226
+ <Text
227
+ style={[
228
+ localStyles.actionButtonText,
229
+ copiedSuccess && {color: AppColors.mintGreenText},
230
+ ]}>
231
+ {copiedSuccess ? t('crash.copied') : t('crash.copyReport')}
232
+ </Text>
233
+ </TouchableScale>
234
+ </View>
235
+
236
+ {/* Crash Title / Message */}
237
+ <Text style={localStyles.titleText}>
238
+ {selectedCrash.message}
239
+ </Text>
240
+
241
+ {/* Metadata Row */}
242
+ <ScrollView
243
+ horizontal
244
+ showsHorizontalScrollIndicator={false}
245
+ contentContainerStyle={localStyles.metaRow}>
246
+ {originFrame && (
247
+ <View style={localStyles.metaChip}>
248
+ <TerminalIcon color={AppColors.grayText} size={11} />
249
+ <Text style={localStyles.metaChipText} numberOfLines={2}>
250
+ {originFrame.file}:{originFrame.lineNumber}
251
+ </Text>
252
+ </View>
253
+ )}
254
+
255
+ <View style={localStyles.metaChip}>
256
+ <ClockIcon color={AppColors.grayText} size={11} />
257
+ <Text style={localStyles.metaChipText}>
258
+ {selectedCrash.timeStr || new Date(selectedCrash.timestamp).toLocaleTimeString()}
259
+ </Text>
260
+ </View>
261
+
262
+ {selectedCrash.deviceInfo?.platform && (
263
+ <View style={localStyles.metaChip}>
264
+ {selectedCrash.deviceInfo.platform === 'ios' ? (
265
+ <AppleIcon color={AppColors.grayText} size={11} />
266
+ ) : (
267
+ <AndroidIcon color={AppColors.grayText} size={11} />
268
+ )}
269
+ <Text style={localStyles.metaChipText}>
270
+ {selectedCrash.deviceInfo.platform.toUpperCase()} {selectedCrash.deviceInfo.osVersion}
271
+ </Text>
272
+ </View>
273
+ )}
274
+ </ScrollView>
275
+ </View>
276
+ </View>
277
+
278
+ {/* ─── Segmented Sub Tabs ─── */}
279
+ <View style={{paddingHorizontal: 8, paddingTop: 6, paddingBottom: 4}}>
280
+ <SegmentedTabs
281
+ tabs={tabsConfig}
282
+ activeKey={activeSubTab}
283
+ onChange={tab => setActiveSubTab(tab as CrashDetailSubTab)}
284
+ />
285
+ </View>
286
+
287
+ {/* ─── Search Bar (for Stack & Raw tabs) ─── */}
288
+ {(activeSubTab === 'stack' || activeSubTab === 'raw') && (
289
+ <View style={{paddingHorizontal: 12, paddingBottom: 6}}>
290
+ <View style={localStyles.searchContainer}>
291
+ <SearchIcon color={AppColors.grayText} size={14} />
292
+ <TextInput
293
+ style={localStyles.searchInput}
294
+ placeholder={t('crash.searchDetailPlaceholder')}
295
+ placeholderTextColor={AppColors.grayTextWeak}
296
+ value={searchQuery}
297
+ onChangeText={setSearchQuery}
298
+ autoCapitalize="none"
299
+ autoCorrect={false}
300
+ />
301
+ {searchQuery.length > 0 && (
302
+ <TouchableScale onPress={() => setSearchQuery('')} hitSlop={10}>
303
+ <ClearIcon color={AppColors.grayText} size={13} />
304
+ </TouchableScale>
305
+ )}
306
+ </View>
307
+ </View>
308
+ )}
309
+
310
+ {/* ─── Tab Content ScrollView ─── */}
311
+ <ScrollView
312
+ style={{flex: 1}}
313
+ contentContainerStyle={{padding: 12, paddingBottom: 40, gap: 12}}>
314
+ {/* 1. STACK TRACE TAB */}
315
+ {activeSubTab === 'stack' && (
316
+ <View style={{gap: 10}}>
317
+ {/* Filter Chips (App Code vs All Frames) */}
318
+ <View style={localStyles.filterChipsRow}>
319
+ <TouchableScale
320
+ onPress={() => setStackFilter('app')}
321
+ style={[
322
+ localStyles.filterChip,
323
+ stackFilter === 'app' && localStyles.filterChipActive,
324
+ ]}>
325
+ <AppFramesIcon
326
+ size={12}
327
+ color={
328
+ stackFilter === 'app'
329
+ ? AppColors.white
330
+ : AppColors.purple
331
+ }
332
+ />
333
+ <Text
334
+ style={[
335
+ localStyles.filterChipText,
336
+ stackFilter === 'app' && localStyles.filterChipTextActive,
337
+ ]}>
338
+ {t('crash.appFrames', {count: appStackFrames.length})}
339
+ </Text>
340
+ </TouchableScale>
341
+
342
+ <TouchableScale
343
+ onPress={() => setStackFilter('all')}
344
+ style={[
345
+ localStyles.filterChip,
346
+ stackFilter === 'all' && localStyles.filterChipActive,
347
+ ]}>
348
+ <AllFramesIcon
349
+ size={12}
350
+ color={
351
+ stackFilter === 'all'
352
+ ? AppColors.white
353
+ : AppColors.purple
354
+ }
355
+ />
356
+ <Text
357
+ style={[
358
+ localStyles.filterChipText,
359
+ stackFilter === 'all' && localStyles.filterChipTextActive,
360
+ ]}>
361
+ {t('crash.allFrames', {count: stackFrames.length})}
362
+ </Text>
363
+ </TouchableScale>
364
+ </View>
365
+
366
+ {/* Frames List */}
367
+ {displayedFrames.length > 0 ? (
368
+ displayedFrames
369
+ .filter(frame => {
370
+ if (!searchQuery.trim()) return true;
371
+ const q = searchQuery.toLowerCase();
372
+ return (
373
+ frame.method.toLowerCase().includes(q) ||
374
+ frame.file.toLowerCase().includes(q)
375
+ );
376
+ })
377
+ .map((frame, index) => {
378
+ const isOrigin = index === 0 && frame.isAppCode;
379
+ return (
380
+ <View
381
+ key={`frame_${index}`}
382
+ style={[
383
+ localStyles.frameCard,
384
+ isOrigin && localStyles.originFrameCard,
385
+ ]}>
386
+ <View style={localStyles.frameCardHeader}>
387
+ <View style={localStyles.frameIndexBadge}>
388
+ <Text style={localStyles.frameIndexBadgeText}>
389
+ #{index + 1}
390
+ </Text>
391
+ </View>
392
+ <View
393
+ style={[
394
+ localStyles.frameTypeBadge,
395
+ frame.isAppCode
396
+ ? {backgroundColor: '#ECFDF5', borderColor: '#A7F3D0'}
397
+ : {backgroundColor: '#F1F5F9', borderColor: '#E2E8F0'},
398
+ ]}>
399
+ <Text
400
+ style={[
401
+ localStyles.frameTypeBadgeText,
402
+ frame.isAppCode
403
+ ? {color: '#059669'}
404
+ : {color: '#64748B'},
405
+ ]}>
406
+ {frame.isAppCode ? t('crash.frameApp') : t('crash.frameLib')}
407
+ </Text>
408
+ </View>
409
+
410
+ <Text style={localStyles.frameMethodText} numberOfLines={1}>
411
+ <HighlightText
412
+ text={frame.method || t('crash.anonymous')}
413
+ highlight={searchQuery}
414
+ />
415
+ </Text>
416
+
417
+ <TouchableScale
418
+ onPress={() => handleCopyFrame(frame, index)}
419
+ hitSlop={8}
420
+ style={localStyles.frameCopyBtn}>
421
+ {copiedFrameIndex === index ? (
422
+ <CircleCheckIcon color={AppColors.greenColor} size={12} />
423
+ ) : (
424
+ <CopyIcon color={AppColors.grayText} size={12} />
425
+ )}
426
+ <Text style={localStyles.frameCopyBtnText}>
427
+ {copiedFrameIndex === index
428
+ ? t('crash.frameCopied')
429
+ : t('crash.frameCopy')}
430
+ </Text>
431
+ </TouchableScale>
432
+ </View>
433
+
434
+ <View style={localStyles.frameLocationRow}>
435
+ <Text style={localStyles.frameFileText} numberOfLines={1}>
436
+ <HighlightText
437
+ text={frame.file}
438
+ highlight={searchQuery}
439
+ />
440
+ </Text>
441
+ <Text style={localStyles.frameLineText}>
442
+ :{frame.lineNumber}
443
+ {frame.column ? `:${frame.column}` : ''}
444
+ </Text>
445
+ </View>
446
+
447
+ {frame.raw && (
448
+ <Text
449
+ style={localStyles.frameRawText}
450
+ numberOfLines={2}>
451
+ {frame.raw}
452
+ </Text>
453
+ )}
454
+ </View>
455
+ );
456
+ })
457
+ ) : (
458
+ <View style={localStyles.emptyContainer}>
459
+ <Text style={localStyles.emptyText}>
460
+ {selectedCrash.stack
461
+ ? selectedCrash.stack
462
+ : t('crash.noStackTrace')}
463
+ </Text>
464
+ </View>
465
+ )}
466
+
467
+ {selectedCrash.componentStack && (
468
+ <View style={localStyles.sectionCard}>
469
+ <View style={localStyles.sectionCardHeader}>
470
+ <LayersIcon color={AppColors.purple} size={14} />
471
+ <Text style={localStyles.sectionCardTitle}>
472
+ {t('crash.componentHierarchy')}
473
+ </Text>
474
+ </View>
475
+ <Text style={localStyles.componentStackText}>
476
+ {selectedCrash.componentStack}
477
+ </Text>
478
+ </View>
479
+ )}
480
+ </View>
481
+ )}
482
+
483
+ {/* 2. DIAGNOSTICS TAB */}
484
+ {activeSubTab === 'diagnostics' && (
485
+ <View style={{gap: 12}}>
486
+ {/* Device & OS Diagnostics Card */}
487
+ <View style={localStyles.sectionCard}>
488
+ <View style={localStyles.sectionCardHeader}>
489
+ <GlobeIcon color={AppColors.purple} size={14} />
490
+ <Text style={localStyles.sectionCardTitle}>{t('crash.deviceEnvironment')}</Text>
491
+ </View>
492
+
493
+ <View style={localStyles.diagGrid}>
494
+ <View style={localStyles.diagItem}>
495
+ <Text style={localStyles.diagLabel}>{t('crash.platform')}</Text>
496
+ <Text style={localStyles.diagValue}>
497
+ {selectedCrash.deviceInfo?.platform?.toUpperCase() || Platform.OS.toUpperCase()}
498
+ </Text>
499
+ </View>
500
+
501
+ <View style={localStyles.diagItem}>
502
+ <Text style={localStyles.diagLabel}>{t('crash.osVersion')}</Text>
503
+ <Text style={localStyles.diagValue}>
504
+ {selectedCrash.deviceInfo?.osVersion || String(Platform.Version)}
505
+ </Text>
506
+ </View>
507
+
508
+ <View style={localStyles.diagItem}>
509
+ <Text style={localStyles.diagLabel}>{t('crash.reactNative')}</Text>
510
+ <Text style={localStyles.diagValue}>
511
+ {selectedCrash.deviceInfo?.rnVersion || '0.74+'}
512
+ </Text>
513
+ </View>
514
+
515
+ <View style={localStyles.diagItem}>
516
+ <Text style={localStyles.diagLabel}>{t('crash.jsEngine')}</Text>
517
+ <Text style={localStyles.diagValue}>
518
+ {selectedCrash.deviceInfo?.isHermes ? t('crash.hermesEngine') : t('crash.jsc')}
519
+ </Text>
520
+ </View>
521
+
522
+ <View style={localStyles.diagItem}>
523
+ <Text style={localStyles.diagLabel}>{t('crash.architecture')}</Text>
524
+ <Text style={localStyles.diagValue}>
525
+ {selectedCrash.deviceInfo?.isFabric ? t('crash.fabricNewArch') : t('crash.paperLegacy')}
526
+ </Text>
527
+ </View>
528
+
529
+ <View style={localStyles.diagItem}>
530
+ <Text style={localStyles.diagLabel}>{t('crash.appState')}</Text>
531
+ <Text style={localStyles.diagValue}>
532
+ {selectedCrash.deviceInfo?.appState || 'active'}
533
+ </Text>
534
+ </View>
535
+ </View>
536
+ </View>
537
+
538
+ {/* Memory Metrics Card */}
539
+ {selectedCrash.memoryInfo && (
540
+ <View style={localStyles.sectionCard}>
541
+ <View style={localStyles.sectionCardHeader}>
542
+ <LayersIcon color={AppColors.purple} size={14} />
543
+ <Text style={localStyles.sectionCardTitle}>{t('crash.jsHeapMemory')}</Text>
544
+ </View>
545
+
546
+ <View style={localStyles.diagGrid}>
547
+ <View style={localStyles.diagItem}>
548
+ <Text style={localStyles.diagLabel}>{t('crash.usedHeap')}</Text>
549
+ <Text style={localStyles.diagValue}>
550
+ {selectedCrash.memoryInfo.usedJSHeapSize} MB
551
+ </Text>
552
+ </View>
553
+
554
+ <View style={localStyles.diagItem}>
555
+ <Text style={localStyles.diagLabel}>{t('crash.totalHeap')}</Text>
556
+ <Text style={localStyles.diagValue}>
557
+ {selectedCrash.memoryInfo.totalJSHeapSize} MB
558
+ </Text>
559
+ </View>
560
+ </View>
561
+ </View>
562
+ )}
563
+ </View>
564
+ )}
565
+
566
+ {/* 3. BREADCRUMBS TAB */}
567
+ {activeSubTab === 'breadcrumbs' && (
568
+ <View style={{gap: 10}}>
569
+ {selectedCrash.breadcrumbs && selectedCrash.breadcrumbs.length > 0 ? (
570
+ selectedCrash.breadcrumbs.map((b, idx) => (
571
+ <View key={`crumb_${idx}`} style={localStyles.breadcrumbCard}>
572
+ <View style={localStyles.breadcrumbHeader}>
573
+ <View style={localStyles.breadcrumbTag}>
574
+ <Text style={localStyles.breadcrumbTagText}>
575
+ {b.type.toUpperCase()}
576
+ </Text>
577
+ </View>
578
+ <Text style={localStyles.breadcrumbTime}>
579
+ {new Date(b.timestamp).toLocaleTimeString()}
580
+ </Text>
581
+ </View>
582
+ <Text style={localStyles.breadcrumbMessage}>{b.message}</Text>
583
+ {b.data && (
584
+ <View style={{marginTop: 6}}>
585
+ <JsonViewer data={b.data} search="" wrap />
586
+ </View>
587
+ )}
588
+ </View>
589
+ ))
590
+ ) : (
591
+ <View style={localStyles.emptyContainer}>
592
+ <Text style={localStyles.emptyText}>
593
+ {t('crash.noBreadcrumbs')}
594
+ </Text>
595
+ </View>
596
+ )}
597
+ </View>
598
+ )}
599
+
600
+ {/* 4. RAW JSON TAB */}
601
+ {activeSubTab === 'raw' && (
602
+ <View style={localStyles.rawContainer}>
603
+ <JsonViewer
604
+ data={rawCrashData}
605
+ search={searchQuery}
606
+ wrap
607
+ />
608
+ </View>
609
+ )}
610
+ </ScrollView>
611
+ </View>
612
+ );
613
+ });
614
+
615
+ export default CrashDetail;
616
+
617
+ const localStyles = StyleSheet.create({
618
+ infoBar: {
619
+ backgroundColor: AppColors.primaryLight,
620
+ borderRadius: 12,
621
+ padding: 12,
622
+ borderWidth: 1,
623
+ borderColor: AppColors.grayBorderSecondary,
624
+ shadowColor: AppColors.black,
625
+ shadowOffset: {width: 0, height: 1},
626
+ shadowOpacity: 0.05,
627
+ shadowRadius: 3,
628
+ elevation: 2,
629
+ gap: 8,
630
+ },
631
+ badgeRow: {
632
+ flexDirection: 'row',
633
+ alignItems: 'center',
634
+ gap: 8,
635
+ },
636
+ pillBadge: {
637
+ flexDirection: 'row',
638
+ alignItems: 'center',
639
+ paddingHorizontal: 8,
640
+ paddingVertical: 3,
641
+ borderRadius: 12,
642
+ borderWidth: 1,
643
+ gap: 5,
644
+ },
645
+ statusDot: {
646
+ width: 6,
647
+ height: 6,
648
+ borderRadius: 3,
649
+ },
650
+ pillBadgeText: {
651
+ fontFamily: AppFonts.interBold,
652
+ fontSize: 9.5,
653
+ letterSpacing: 0.4,
654
+ },
655
+ actionButton: {
656
+ flexDirection: 'row',
657
+ alignItems: 'center',
658
+ gap: 4,
659
+ backgroundColor: `${AppColors.purple}14`,
660
+ borderColor: `${AppColors.purple}33`,
661
+ borderWidth: 1,
662
+ paddingHorizontal: 8,
663
+ paddingVertical: 4,
664
+ borderRadius: 8,
665
+ },
666
+ actionButtonText: {
667
+ fontFamily: AppFonts.interBold,
668
+ fontSize: 10.5,
669
+ color: AppColors.purple,
670
+ },
671
+ titleText: {
672
+ fontFamily: AppFonts.interBold,
673
+ fontSize: 14,
674
+ color: AppColors.primaryBlack,
675
+ lineHeight: 19,
676
+ },
677
+ metaRow: {
678
+ flexDirection: 'row',
679
+ alignItems: 'center',
680
+ gap: 6,
681
+ paddingVertical: 2,
682
+ },
683
+ metaChip: {
684
+ flexDirection: 'row',
685
+ alignItems: 'center',
686
+ gap: 4,
687
+ backgroundColor: AppColors.grayBackground,
688
+ paddingHorizontal: 7,
689
+ paddingVertical: 3,
690
+ borderRadius: 6,
691
+ borderWidth: 1,
692
+ borderColor: AppColors.dividerColor,
693
+ },
694
+ metaChipText: {
695
+ fontFamily: AppFonts.interMedium,
696
+ fontSize: 10,
697
+ color: AppColors.grayText,
698
+ },
699
+ searchContainer: {
700
+ flexDirection: 'row',
701
+ alignItems: 'center',
702
+ backgroundColor: AppColors.primaryLight,
703
+ borderWidth: 1,
704
+ borderColor: AppColors.grayBorderSecondary,
705
+ borderRadius: 8,
706
+ paddingHorizontal: 10,
707
+ height: 36,
708
+ gap: 8,
709
+ },
710
+ searchInput: {
711
+ flex: 1,
712
+ fontFamily: AppFonts.interRegular,
713
+ fontSize: 12,
714
+ color: AppColors.primaryBlack,
715
+ paddingVertical: 0,
716
+ },
717
+ filterChipsRow: {
718
+ flexDirection: 'row',
719
+ alignItems: 'center',
720
+ gap: 8,
721
+ },
722
+ filterChip: {
723
+ flexDirection: 'row',
724
+ alignItems: 'center',
725
+ gap: 5,
726
+ paddingHorizontal: 10,
727
+ paddingVertical: 5,
728
+ borderRadius: 8,
729
+ backgroundColor: AppColors.primaryLight,
730
+ borderWidth: 1,
731
+ borderColor: AppColors.grayBorderSecondary,
732
+ },
733
+ filterChipActive: {
734
+ backgroundColor: AppColors.purple,
735
+ borderColor: AppColors.purple,
736
+ },
737
+ filterChipText: {
738
+ fontFamily: AppFonts.interMedium,
739
+ fontSize: 11,
740
+ color: AppColors.grayText,
741
+ },
742
+ filterChipTextActive: {
743
+ fontFamily: AppFonts.interBold,
744
+ color: AppColors.white,
745
+ },
746
+ frameCard: {
747
+ backgroundColor: AppColors.primaryLight,
748
+ borderRadius: 8,
749
+ padding: 10,
750
+ borderWidth: 1,
751
+ borderColor: AppColors.grayBorderSecondary,
752
+ gap: 4,
753
+ },
754
+ originFrameCard: {
755
+ borderColor: '#6EE7B7',
756
+ backgroundColor: '#F0FDF4',
757
+ },
758
+ frameCardHeader: {
759
+ flexDirection: 'row',
760
+ alignItems: 'center',
761
+ gap: 8,
762
+ },
763
+ frameIndexBadge: {
764
+ minWidth: 24,
765
+ height: 18,
766
+ paddingHorizontal: 4,
767
+ borderRadius: 4,
768
+ backgroundColor: `${AppColors.purple}14`,
769
+ alignItems: 'center',
770
+ justifyContent: 'center',
771
+ },
772
+ frameIndexBadgeText: {
773
+ fontFamily: AppFonts.interBold,
774
+ fontSize: 9,
775
+ color: AppColors.purple,
776
+ },
777
+ frameTypeBadge: {
778
+ paddingHorizontal: 5,
779
+ paddingVertical: 2,
780
+ borderRadius: 4,
781
+ borderWidth: 1,
782
+ },
783
+ frameTypeBadgeText: {
784
+ fontFamily: AppFonts.interBold,
785
+ fontSize: 8.5,
786
+ },
787
+ frameMethodText: {
788
+ fontFamily: AppFonts.interBold,
789
+ fontSize: 12,
790
+ color: AppColors.primaryBlack,
791
+ flex: 1,
792
+ },
793
+ frameLocationRow: {
794
+ flexDirection: 'row',
795
+ alignItems: 'center',
796
+ },
797
+ frameFileText: {
798
+ fontFamily: AppFonts.interRegular,
799
+ fontSize: 11,
800
+ color: AppColors.grayText,
801
+ flex: 1,
802
+ },
803
+ frameLineText: {
804
+ fontFamily: AppFonts.interBold,
805
+ fontSize: 11,
806
+ color: AppColors.purple,
807
+ },
808
+ frameCopyBtn: {
809
+ flexDirection: 'row',
810
+ alignItems: 'center',
811
+ gap: 3,
812
+ paddingHorizontal: 6,
813
+ paddingVertical: 3,
814
+ borderRadius: 5,
815
+ backgroundColor: AppColors.grayBackground,
816
+ borderWidth: 1,
817
+ borderColor: AppColors.dividerColor,
818
+ },
819
+ frameCopyBtnText: {
820
+ fontFamily: AppFonts.interBold,
821
+ fontSize: 9,
822
+ color: AppColors.grayText,
823
+ },
824
+ frameRawText: {
825
+ fontFamily: AppFonts.interRegular,
826
+ fontSize: 9.5,
827
+ color: AppColors.grayTextWeak,
828
+ backgroundColor: `${AppColors.primaryBlack}06`,
829
+ borderRadius: 5,
830
+ paddingHorizontal: 7,
831
+ paddingVertical: 4,
832
+ overflow: 'hidden',
833
+ },
834
+ componentStackText: {
835
+ fontFamily: AppFonts.interRegular,
836
+ fontSize: 11,
837
+ lineHeight: 16,
838
+ color: AppColors.grayText,
839
+ backgroundColor: `${AppColors.primaryBlack}06`,
840
+ borderRadius: 6,
841
+ padding: 10,
842
+ },
843
+ sectionCard: {
844
+ backgroundColor: AppColors.primaryLight,
845
+ borderRadius: 10,
846
+ padding: 12,
847
+ borderWidth: 1,
848
+ borderColor: AppColors.grayBorderSecondary,
849
+ gap: 10,
850
+ },
851
+ sectionCardHeader: {
852
+ flexDirection: 'row',
853
+ alignItems: 'center',
854
+ gap: 6,
855
+ paddingBottom: 6,
856
+ borderBottomWidth: 1,
857
+ borderBottomColor: AppColors.dividerColor,
858
+ },
859
+ sectionCardTitle: {
860
+ fontFamily: AppFonts.interBold,
861
+ fontSize: 12.5,
862
+ color: AppColors.primaryBlack,
863
+ },
864
+ diagGrid: {
865
+ flexDirection: 'row',
866
+ flexWrap: 'wrap',
867
+ rowGap: 8,
868
+ columnGap: 12,
869
+ },
870
+ diagItem: {
871
+ width: '47%',
872
+ gap: 2,
873
+ },
874
+ diagLabel: {
875
+ fontFamily: AppFonts.interRegular,
876
+ fontSize: 10,
877
+ color: AppColors.grayTextWeak,
878
+ },
879
+ diagValue: {
880
+ fontFamily: AppFonts.interBold,
881
+ fontSize: 11.5,
882
+ color: AppColors.primaryBlack,
883
+ },
884
+ breadcrumbCard: {
885
+ backgroundColor: AppColors.primaryLight,
886
+ borderRadius: 8,
887
+ padding: 10,
888
+ borderWidth: 1,
889
+ borderColor: AppColors.grayBorderSecondary,
890
+ gap: 4,
891
+ },
892
+ breadcrumbHeader: {
893
+ flexDirection: 'row',
894
+ alignItems: 'center',
895
+ justifyContent: 'space-between',
896
+ },
897
+ breadcrumbTag: {
898
+ backgroundColor: `${AppColors.purple}14`,
899
+ paddingHorizontal: 6,
900
+ paddingVertical: 2,
901
+ borderRadius: 4,
902
+ },
903
+ breadcrumbTagText: {
904
+ fontFamily: AppFonts.interBold,
905
+ fontSize: 9,
906
+ color: AppColors.purple,
907
+ },
908
+ breadcrumbTime: {
909
+ fontFamily: AppFonts.interRegular,
910
+ fontSize: 10,
911
+ color: AppColors.grayTextWeak,
912
+ },
913
+ breadcrumbMessage: {
914
+ fontFamily: AppFonts.interRegular,
915
+ fontSize: 11.5,
916
+ color: AppColors.primaryBlack,
917
+ marginTop: 2,
918
+ },
919
+ rawContainer: {
920
+ backgroundColor: AppColors.primaryLight,
921
+ borderRadius: 10,
922
+ padding: 10,
923
+ borderWidth: 1,
924
+ borderColor: AppColors.grayBorderSecondary,
925
+ },
926
+ emptyContainer: {
927
+ padding: 16,
928
+ alignItems: 'center',
929
+ justifyContent: 'center',
930
+ backgroundColor: AppColors.primaryLight,
931
+ borderRadius: 8,
932
+ borderWidth: 1,
933
+ borderColor: AppColors.grayBorderSecondary,
934
+ },
935
+ emptyText: {
936
+ fontFamily: AppFonts.interRegular,
937
+ fontSize: 11,
938
+ color: AppColors.grayText,
939
+ textAlign: 'center',
940
+ },
941
+ });