react-native-inapp-inspector 1.1.24 → 1.1.26

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