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
@@ -10,9 +10,9 @@ import { AppFonts } from '../../styles/AppFonts';
10
10
  import { METHOD_COLORS } from '../../constants';
11
11
  import { LIB_VERSION } from '../../constants';
12
12
  import { getStatusColor, getAppName, getBundleIdentifier, formatTime, getSize } from '../../helpers';
13
- import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, } from '../NetworkIcons';
13
+ import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ChevronIcon, ClockIcon, AppleIcon, AndroidIcon, PackageBoxIcon, } from '../NetworkIcons';
14
14
  const InspectorHeader = React.memo(() => {
15
- const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, activeTab, environment, } = useInspector();
15
+ const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, setSettingsPage, closeModal, detailTitle, activeTab, environment, selectedCrash, setSelectedCrash, } = useInspector();
16
16
  const envConfig = useMemo(() => {
17
17
  const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
18
18
  const clean = rawEnv.toUpperCase();
@@ -51,7 +51,8 @@ const InspectorHeader = React.memo(() => {
51
51
  const isDetailView = (activeTab === 'apis' && selected != null) ||
52
52
  (activeTab === 'analytics' && selectedEvent != null) ||
53
53
  (activeTab === 'logs' && selectedLog != null) ||
54
- (activeTab === 'redux' && (selectedReduxSlice != null || selectedReduxAction != null));
54
+ (activeTab === 'redux' && (selectedReduxSlice != null || selectedReduxAction != null)) ||
55
+ (activeTab === 'crash' && selectedCrash != null);
55
56
  const isAnySelected = isDetailView;
56
57
  const headerTopPadding = Platform.OS === 'ios' && modalHeightPercent >= 95 ? 44 : 0;
57
58
  return (<LinearGradient colors={[AppColors.purple, AppColors.brandPurple]} style={styles.headerGradient}>
@@ -62,8 +63,9 @@ const InspectorHeader = React.memo(() => {
62
63
  {
63
64
  flexDirection: 'row',
64
65
  alignItems: 'center',
65
- gap: 16,
66
- flex: !isDetailView ? 5 : 1,
66
+ gap: 12,
67
+ flex: !isDetailView ? 1 : 0,
68
+ minWidth: 0,
67
69
  },
68
70
  ]}>
69
71
  <TouchableScale onPress={() => {
@@ -73,6 +75,7 @@ const InspectorHeader = React.memo(() => {
73
75
  setSelectedLog(null);
74
76
  setSelectedReduxSlice(null);
75
77
  setSelectedReduxAction(null);
78
+ setSelectedCrash(null);
76
79
  });
77
80
  }} hitSlop={15} style={[
78
81
  {
@@ -194,10 +197,14 @@ const InspectorHeader = React.memo(() => {
194
197
  borderColor: `${AppColors.white}2E`,
195
198
  }}>
196
199
  <View style={{
197
- paddingHorizontal: 5,
198
- paddingVertical: 2,
200
+ flexDirection: 'row',
201
+ alignItems: 'center',
202
+ gap: 3.5,
203
+ paddingHorizontal: 6,
204
+ paddingVertical: 2.5,
199
205
  backgroundColor: `${AppColors.white}47`,
200
206
  }}>
207
+ {Platform.OS === 'ios' ? (<AppleIcon color={AppColors.white} size={10}/>) : (<AndroidIcon color={AppColors.white} size={10}/>)}
201
208
  <Text style={{
202
209
  fontFamily: AppFonts.interBold,
203
210
  fontSize: 9,
@@ -209,7 +216,7 @@ const InspectorHeader = React.memo(() => {
209
216
  </View>
210
217
  <View style={{
211
218
  paddingHorizontal: 5,
212
- paddingVertical: 2,
219
+ paddingVertical: 2.5,
213
220
  backgroundColor: `${AppColors.white}1F`,
214
221
  }}>
215
222
  <Text style={{
@@ -230,10 +237,14 @@ const InspectorHeader = React.memo(() => {
230
237
  borderColor: `${AppColors.white}2E`,
231
238
  }}>
232
239
  <View style={{
233
- paddingHorizontal: 5,
234
- paddingVertical: 2,
240
+ flexDirection: 'row',
241
+ alignItems: 'center',
242
+ gap: 3.5,
243
+ paddingHorizontal: 6,
244
+ paddingVertical: 2.5,
235
245
  backgroundColor: `${AppColors.white}47`,
236
246
  }}>
247
+ <PackageBoxIcon color={AppColors.white} size={10}/>
237
248
  <Text style={{
238
249
  fontFamily: AppFonts.interBold,
239
250
  fontSize: 9,
@@ -478,18 +489,58 @@ const InspectorHeader = React.memo(() => {
478
489
  {selectedReduxAction.timestamp || 'Dispatched'}
479
490
  </Text>
480
491
  </View>
492
+ </View>) : activeTab === 'crash' && selectedCrash != null ? (<View style={styles.headerDetailCenter}>
493
+ <View style={styles.headerDetailRow}>
494
+ <View style={[
495
+ styles.headerMethodBadge,
496
+ {
497
+ backgroundColor: selectedCrash.isFatal
498
+ ? '#DC2626'
499
+ : '#D97706',
500
+ },
501
+ ]}>
502
+ <Text style={styles.headerMethodText}>
503
+ {selectedCrash.isFatal ? 'FATAL' : selectedCrash.type.toUpperCase()}
504
+ </Text>
505
+ </View>
506
+ <Text style={styles.headerDetailTitle} numberOfLines={1} ellipsizeMode="middle">
507
+ {selectedCrash.name || selectedCrash.message}
508
+ </Text>
509
+ </View>
510
+ <View style={styles.headerDetailSubRow}>
511
+ <View style={[
512
+ styles.headerStatusDot,
513
+ {
514
+ backgroundColor: selectedCrash.isFatal
515
+ ? '#DC2626'
516
+ : '#F59E0B',
517
+ },
518
+ ]}/>
519
+ <Text style={styles.headerSubTitle}>
520
+ {selectedCrash.timeStr || new Date(selectedCrash.timestamp).toLocaleTimeString()}
521
+ </Text>
522
+ {selectedCrash.deviceInfo?.platform && (<>
523
+ <Text style={[styles.headerSubTitle, { opacity: 0.6 }]}>•</Text>
524
+ <Text style={styles.headerSubTitle}>
525
+ {selectedCrash.deviceInfo.platform.toUpperCase()}
526
+ </Text>
527
+ </>)}
528
+ </View>
481
529
  </View>) : null}
482
530
  </View>)}
483
531
 
484
532
  <View style={[
485
533
  styles.headerRight,
486
- !isAnySelected && {
534
+ {
535
+ flexDirection: 'row',
536
+ alignItems: 'center',
537
+ justifyContent: 'flex-end',
487
538
  flexShrink: 0,
488
- minWidth: 116,
539
+ gap: 8,
489
540
  },
490
541
  ]}>
491
542
  {!isAnySelected && (<TouchableScale onPress={() => {
492
- Alert.alert('Clear Everything', 'This clears all tabs — APIs, Logs, Analytics and Redux timeline. Continue?', [
543
+ Alert.alert('Clear Everything', 'This clears all tabs — APIs, Logs, Analytics, Redux timeline and Crash history. Continue?', [
493
544
  { text: 'Cancel', style: 'cancel' },
494
545
  {
495
546
  text: 'Clear All',
@@ -497,13 +548,7 @@ const InspectorHeader = React.memo(() => {
497
548
  style: 'destructive',
498
549
  },
499
550
  ]);
500
- }} hitSlop={15} style={[
501
- styles.closeButtonSquare,
502
- {
503
- marginRight: 8,
504
- backgroundColor: `${AppColors.white}26`,
505
- },
506
- ]}>
551
+ }} hitSlop={15} style={styles.closeButtonSquare}>
507
552
  <Animated.View style={{
508
553
  transform: [
509
554
  {
@@ -524,15 +569,7 @@ const InspectorHeader = React.memo(() => {
524
569
  </Animated.View>
525
570
  </TouchableScale>)}
526
571
 
527
- {selected == null &&
528
- selectedEvent == null &&
529
- selectedLog == null && (<TouchableScale onPress={() => setSettingsPage('main')} hitSlop={15} style={[
530
- styles.closeButtonSquare,
531
- {
532
- marginRight: 8,
533
- backgroundColor: `${AppColors.white}26`,
534
- },
535
- ]}>
572
+ {!isAnySelected && (<TouchableScale onPress={() => setSettingsPage('main')} hitSlop={15} style={styles.closeButtonSquare}>
536
573
  <SettingsIcon color={AppColors.white} size={16}/>
537
574
  </TouchableScale>)}
538
575
 
@@ -35,6 +35,7 @@ const LogDetail = React.memo(() => {
35
35
  const jsonContent = useMemo(() => (selectedLog ? getJsonContent(selectedLog.message) : null), [selectedLog]);
36
36
  const [stackViewMode, setStackViewMode] = useState('structured');
37
37
  const [stackSource, setStackSource] = useState('caller');
38
+ const [stackFilter, setStackFilter] = useState('app');
38
39
  const activeStackString = useMemo(() => {
39
40
  if (!selectedLog)
40
41
  return '';
@@ -43,21 +44,6 @@ const LogDetail = React.memo(() => {
43
44
  }
44
45
  return selectedLog.stack || selectedLog.caller || '';
45
46
  }, [selectedLog, stackSource]);
46
- const originFrame = useMemo(() => {
47
- if (!selectedLog)
48
- return null;
49
- if (selectedLog.caller && selectedLog.caller !== 'Unknown') {
50
- return parseStackLine(selectedLog.caller, true);
51
- }
52
- if (selectedLog.stack) {
53
- const firstLine = selectedLog.stack
54
- .split('\n')
55
- .find(l => l.trim().length > 0);
56
- if (firstLine)
57
- return parseStackLine(firstLine, true);
58
- }
59
- return null;
60
- }, [selectedLog]);
61
47
  const parsedStackFrames = useMemo(() => {
62
48
  if (!activeStackString)
63
49
  return [];
@@ -67,6 +53,29 @@ const LogDetail = React.memo(() => {
67
53
  .filter(l => l.length > 0)
68
54
  .map((line, idx) => parseStackLine(line, idx === 0));
69
55
  }, [activeStackString]);
56
+ const appStackFrames = useMemo(() => {
57
+ return parsedStackFrames.filter(f => f.frameType === 'app' || !f.isRuntimeNoise);
58
+ }, [parsedStackFrames]);
59
+ const displayedFrames = useMemo(() => {
60
+ if (stackFilter === 'app' && appStackFrames.length > 0) {
61
+ return appStackFrames;
62
+ }
63
+ return parsedStackFrames;
64
+ }, [stackFilter, appStackFrames, parsedStackFrames]);
65
+ const originFrame = useMemo(() => {
66
+ if (!selectedLog)
67
+ return null;
68
+ if (appStackFrames.length > 0) {
69
+ return appStackFrames[0];
70
+ }
71
+ if (selectedLog.caller && selectedLog.caller !== 'Unknown') {
72
+ return parseStackLine(selectedLog.caller, true);
73
+ }
74
+ if (parsedStackFrames.length > 0) {
75
+ return parsedStackFrames[0];
76
+ }
77
+ return null;
78
+ }, [selectedLog, appStackFrames, parsedStackFrames]);
70
79
  if (!selectedLog)
71
80
  return null;
72
81
  const isDark = AppColors.primaryLight !== AppColors.white;
@@ -100,14 +109,14 @@ const LogDetail = React.memo(() => {
100
109
  const subTabs = [
101
110
  {
102
111
  key: 'output',
103
- label: 'Output',
112
+ label: t('console.tabOutput'),
104
113
  icon: (isActive) => (<TerminalIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
105
114
  },
106
115
  ...(hasMultipleArgs
107
116
  ? [
108
117
  {
109
118
  key: 'arguments',
110
- label: `Args (${selectedLog.rawArgs?.length})`,
119
+ label: t('console.tabArgs', { count: selectedLog.rawArgs?.length }),
111
120
  icon: (isActive) => (<RequestIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
112
121
  },
113
122
  ]
@@ -116,14 +125,14 @@ const LogDetail = React.memo(() => {
116
125
  ? [
117
126
  {
118
127
  key: 'stack',
119
- label: 'Stack Trace',
128
+ label: t('console.tabStack'),
120
129
  icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
121
130
  },
122
131
  ]
123
132
  : []),
124
133
  {
125
134
  key: 'metadata',
126
- label: 'Metadata',
135
+ label: t('console.tabMetadata'),
127
136
  icon: (isActive) => (<InfoCircleIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
128
137
  },
129
138
  ];
@@ -253,7 +262,8 @@ const LogDetail = React.memo(() => {
253
262
  fontSize: 13,
254
263
  color: AppColors.primaryBlack,
255
264
  lineHeight: 18,
256
- }} numberOfLines={1}>
265
+ flexShrink: 1,
266
+ }}>
257
267
  {originFrame?.functionName && originFrame.functionName !== '<anonymous>'
258
268
  ? `${originFrame.functionName}()`
259
269
  : originFrame?.fileName || t('console.consoleLog')}
@@ -268,11 +278,11 @@ const LogDetail = React.memo(() => {
268
278
  paddingVertical: 2,
269
279
  },
270
280
  ]}>
271
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3 }}>
281
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3, flexWrap: 'wrap', flexShrink: 1 }}>
272
282
  <DocIcon color={AppColors.brandPurple} size={10}/>
273
283
  <Text style={[
274
284
  styles.metaChipText,
275
- { color: AppColors.brandPurple, fontSize: 10, fontFamily: AppFonts.interBold },
285
+ { color: AppColors.brandPurple, fontSize: 10, fontFamily: AppFonts.interBold, flexShrink: 1 },
276
286
  ]}>
277
287
  {originFrame.fileName}
278
288
  {originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
@@ -575,10 +585,12 @@ const LogDetail = React.memo(() => {
575
585
  ? AppColors.primaryLight
576
586
  : AppColors.grayBackground,
577
587
  }}>
588
+ {/* Header Top Row: Title, Mode switcher & Full copy */}
578
589
  <View style={{
579
590
  flexDirection: 'row',
580
591
  alignItems: 'center',
581
592
  justifyContent: 'space-between',
593
+ gap: 8,
582
594
  }}>
583
595
  <Text style={{
584
596
  fontFamily: AppFonts.interBold,
@@ -587,156 +599,257 @@ const LogDetail = React.memo(() => {
587
599
  textTransform: 'uppercase',
588
600
  letterSpacing: 0.4,
589
601
  }}>
590
- {stackSource === 'error' ? 'Error Exception Stack' : 'Call Stack'} ({parsedStackFrames.length} frames)
602
+ {stackSource === 'error'
603
+ ? t('console.errorStack', { count: parsedStackFrames.length })
604
+ : t('console.callStack', { count: parsedStackFrames.length })}
591
605
  </Text>
592
- <CopyButton value={activeStackString} label="Full Stack Trace"/>
606
+
607
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
608
+ <View style={{
609
+ flexDirection: 'row',
610
+ backgroundColor: isDark ? AppColors.primaryLight : AppColors.white,
611
+ borderRadius: 7,
612
+ padding: 2,
613
+ borderWidth: 1,
614
+ borderColor: AppColors.dividerColor,
615
+ }}>
616
+ <Pressable onPress={() => setStackViewMode('structured')} style={{
617
+ flexDirection: 'row',
618
+ alignItems: 'center',
619
+ gap: 3,
620
+ paddingVertical: 3.5,
621
+ paddingHorizontal: 7,
622
+ borderRadius: 5,
623
+ backgroundColor: stackViewMode === 'structured'
624
+ ? AppColors.brandPurple
625
+ : 'transparent',
626
+ }}>
627
+ <LayersIcon color={stackViewMode === 'structured'
628
+ ? AppColors.white
629
+ : AppColors.grayTextWeak} size={11}/>
630
+ <Text style={{
631
+ fontFamily: AppFonts.interBold,
632
+ fontSize: 10,
633
+ color: stackViewMode === 'structured'
634
+ ? AppColors.white
635
+ : AppColors.grayTextStrong,
636
+ }}>
637
+ {t('console.cardsView')}
638
+ </Text>
639
+ </Pressable>
640
+
641
+ <Pressable onPress={() => setStackViewMode('raw')} style={{
642
+ flexDirection: 'row',
643
+ alignItems: 'center',
644
+ gap: 3,
645
+ paddingVertical: 3.5,
646
+ paddingHorizontal: 7,
647
+ borderRadius: 5,
648
+ backgroundColor: stackViewMode === 'raw'
649
+ ? AppColors.brandPurple
650
+ : 'transparent',
651
+ }}>
652
+ <RawIcon color={stackViewMode === 'raw'
653
+ ? AppColors.white
654
+ : AppColors.grayTextWeak} size={11}/>
655
+ <Text style={{
656
+ fontFamily: AppFonts.interBold,
657
+ fontSize: 10,
658
+ color: stackViewMode === 'raw'
659
+ ? AppColors.white
660
+ : AppColors.grayTextStrong,
661
+ }}>
662
+ {t('console.rawTraceView')}
663
+ </Text>
664
+ </Pressable>
665
+ </View>
666
+
667
+ <CopyButton value={activeStackString} label={t('console.fullStackTrace')}/>
668
+ </View>
593
669
  </View>
594
670
 
595
671
  {/* Source Switcher if Error Stack exists */}
596
672
  {selectedLog.errorStack && (<SegmentedTabs tabs={[
597
673
  {
598
674
  key: 'caller',
599
- label: 'Call Origin Stack',
675
+ label: t('console.callOriginStack'),
600
676
  },
601
677
  {
602
678
  key: 'error',
603
- label: 'Error Thrown Stack',
679
+ label: t('console.errorThrownStack'),
604
680
  },
605
681
  ]} activeKey={stackSource} onChange={key => setStackSource(key)}/>)}
606
682
 
607
- {/* Structured vs Raw View Mode Tabs */}
683
+ {/* Scope Filter Tabs (Full Width) */}
608
684
  <SegmentedTabs tabs={[
609
685
  {
610
- key: 'structured',
611
- label: 'Structured Cards',
612
- icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
686
+ key: 'app',
687
+ label: t('console.appCodeScope', { count: appStackFrames.length }),
613
688
  },
614
689
  {
615
- key: 'raw',
616
- label: 'Raw Trace',
617
- icon: (isActive) => (<RawIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
690
+ key: 'all',
691
+ label: t('console.allFramesScope', { count: parsedStackFrames.length }),
618
692
  },
619
- ]} activeKey={stackViewMode} onChange={key => setStackViewMode(key)}/>
693
+ ]} activeKey={stackFilter} onChange={key => setStackFilter(key)}/>
620
694
  </View>
621
695
 
622
696
  {/* Stack Content: Structured Cards or Raw Full Trace */}
623
697
  {stackViewMode === 'structured' ? (<View style={{ padding: 12, gap: 10 }}>
624
- {parsedStackFrames.length > 0 ? (parsedStackFrames.map((frame, frameIdx) => {
698
+ {displayedFrames.length > 0 ? (displayedFrames.map((frame, frameIdx) => {
625
699
  const isTopFrame = frameIdx === 0;
700
+ const frameBadgeBg = frame.frameType === 'app'
701
+ ? `${AppColors.brandPurple}15`
702
+ : frame.frameType === 'dependency'
703
+ ? `${AppColors.amber500}18`
704
+ : frame.frameType === 'native'
705
+ ? `${AppColors.sky500}18`
706
+ : AppColors.grayBorderSecondary;
707
+ const frameBadgeText = frame.frameType === 'app'
708
+ ? AppColors.brandPurple
709
+ : frame.frameType === 'dependency'
710
+ ? AppColors.amber700
711
+ : frame.frameType === 'native'
712
+ ? AppColors.sky500
713
+ : AppColors.grayTextWeak;
626
714
  return (<View key={frameIdx} style={{
627
715
  backgroundColor: isTopFrame
628
716
  ? `${AppColors.brandPurple}0A`
629
717
  : AppColors.primaryLight,
630
- borderRadius: 8,
718
+ borderRadius: 10,
631
719
  borderWidth: 1,
632
720
  borderColor: isTopFrame
633
- ? `${AppColors.brandPurple}30`
721
+ ? `${AppColors.brandPurple}35`
634
722
  : AppColors.dividerColor,
635
- padding: 10,
636
- gap: 5,
723
+ padding: 11,
724
+ gap: 6,
637
725
  }}>
638
- {/* Frame Header Row */}
726
+ {/* Frame Top Row: Origin / Category, File, Line badge, Copy */}
639
727
  <View style={{
640
728
  flexDirection: 'row',
641
729
  alignItems: 'center',
642
730
  justifyContent: 'space-between',
643
731
  gap: 6,
644
732
  }}>
645
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }}>
646
- <View style={{
647
- backgroundColor: isTopFrame
648
- ? AppColors.brandPurple
649
- : AppColors.grayBorderSecondary,
650
- borderRadius: 4,
651
- paddingHorizontal: 5,
652
- paddingVertical: 1.5,
653
- }}>
654
- <Text style={{
655
- fontFamily: AppFonts.interBold,
656
- fontSize: 9,
657
- color: isTopFrame
658
- ? AppColors.white
659
- : AppColors.grayTextStrong,
660
- }}>
661
- #{frameIdx + 1}
662
- {isTopFrame ? ' ORIGIN' : ''}
663
- </Text>
664
- </View>
665
-
666
- <HighlightText text={frame.functionName !== '<anonymous>' ? `${frame.functionName}()` : '<anonymous>'} search={detailSearch} style={{
667
- fontFamily: AppFonts.interBold,
668
- fontSize: 12,
669
- color: isTopFrame
670
- ? AppColors.primaryBlack
671
- : AppColors.grayTextStrong,
672
- }} highlightStyle={{
673
- backgroundColor: AppColors.yellowHighlight,
674
- color: AppColors.primaryBlack,
675
- }}/>
676
- </View>
677
-
678
- {frame.lineNumber && (<View style={{
679
- backgroundColor: `${AppColors.teal600}15`,
680
- borderColor: `${AppColors.teal600}30`,
681
- borderWidth: 1,
733
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, flex: 1, flexWrap: 'wrap' }}>
734
+ {isTopFrame && (<View style={{
735
+ backgroundColor: AppColors.brandPurple,
682
736
  borderRadius: 4,
683
- paddingHorizontal: 6,
737
+ paddingHorizontal: 5,
684
738
  paddingVertical: 1.5,
685
739
  }}>
686
- <Text style={{
740
+ <Text style={{
687
741
  fontFamily: AppFonts.interBold,
688
- fontSize: 9.5,
689
- color: AppColors.teal600,
742
+ fontSize: 8.5,
743
+ color: AppColors.white,
690
744
  }}>
691
- L{frame.lineNumber}
692
- {frame.columnNumber ? `:C${frame.columnNumber}` : ''}
693
- </Text>
694
- </View>)}
695
- </View>
745
+ {t('console.originBadge')}
746
+ </Text>
747
+ </View>)}
696
748
 
697
- {/* File Path Row */}
698
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
699
- {frame.fileExt && frame.fileExt !== 'other' && (<View style={{
749
+ {frame.fileExt && frame.fileExt !== 'other' && (<View style={{
700
750
  backgroundColor: frame.fileExt === 'tsx' || frame.fileExt === 'ts'
701
751
  ? `${AppColors.brandPurple}18`
702
752
  : `${AppColors.teal600}18`,
703
- borderRadius: 3,
704
- paddingHorizontal: 4,
705
- paddingVertical: 1,
753
+ borderRadius: 4,
754
+ paddingHorizontal: 4.5,
755
+ paddingVertical: 1.5,
706
756
  }}>
707
- <Text style={{
757
+ <Text style={{
708
758
  fontFamily: AppFonts.interBold,
709
759
  fontSize: 8.5,
710
760
  color: frame.fileExt === 'tsx' || frame.fileExt === 'ts'
711
761
  ? AppColors.brandPurple
712
762
  : AppColors.teal600,
713
763
  }}>
714
- {frame.fileExt.toUpperCase()}
715
- </Text>
716
- </View>)}
717
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 3 }}>
718
- <DocIcon color={isTopFrame ? AppColors.brandPurple : AppColors.grayTextWeak} size={10}/>
719
- <Text style={{
720
- fontFamily: AppFonts.interMedium,
721
- fontSize: 10.5,
764
+ {frame.fileExt.toUpperCase()}
765
+ </Text>
766
+ </View>)}
767
+
768
+ <Text style={{
769
+ fontFamily: AppFonts.interBold,
770
+ fontSize: 11.5,
722
771
  color: isTopFrame
723
772
  ? AppColors.brandPurple
724
- : AppColors.grayTextWeak,
773
+ : AppColors.primaryBlack,
725
774
  }}>
726
- {frame.fileName}
727
- </Text>
775
+ {frame.fileName}
776
+ </Text>
777
+ </View>
778
+
779
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
780
+ {frame.lineNumber && (<View style={{
781
+ backgroundColor: `${AppColors.teal600}15`,
782
+ borderColor: `${AppColors.teal600}30`,
783
+ borderWidth: 1,
784
+ borderRadius: 4,
785
+ paddingHorizontal: 5,
786
+ paddingVertical: 1.5,
787
+ }}>
788
+ <Text style={{
789
+ fontFamily: AppFonts.interBold,
790
+ fontSize: 9.5,
791
+ color: AppColors.teal600,
792
+ }}>
793
+ L{frame.lineNumber}{frame.columnNumber ? `:C${frame.columnNumber}` : ''}
794
+ </Text>
795
+ </View>)}
796
+ <CopyButton value={frame.copyableLocation} label={frame.fileName}/>
797
+ </View>
728
798
  </View>
799
+
800
+ {/* Middle Row: Function / Symbol Invocation */}
801
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
802
+ <HighlightText text={frame.functionName !== '<anonymous>' ? `${frame.functionName}()` : '<anonymous>'} search={detailSearch} style={{
803
+ fontFamily: AppFonts.interBold,
804
+ fontSize: 12.5,
805
+ color: AppColors.primaryBlack,
806
+ }} highlightStyle={{
807
+ backgroundColor: AppColors.yellowHighlight,
808
+ color: AppColors.primaryBlack,
809
+ }}/>
729
810
  </View>
730
811
 
731
- {/* Full path */}
732
- {frame.fullPath && frame.fullPath !== frame.fileName && (<Text selectable style={{
733
- fontFamily: AppFonts.interRegular,
734
- fontSize: 9.5,
735
- color: AppColors.grayTextWeak,
736
- lineHeight: 14,
737
- }} numberOfLines={2}>
738
- {frame.fullPath}
739
- </Text>)}
812
+ {/* Bottom Row: Clean Relative Path & Execution Scope Tag */}
813
+ <View style={{
814
+ flexDirection: 'row',
815
+ alignItems: 'center',
816
+ justifyContent: 'space-between',
817
+ backgroundColor: isDark ? `${AppColors.primaryBlack}25` : AppColors.grayBackground,
818
+ borderRadius: 6,
819
+ paddingHorizontal: 8,
820
+ paddingVertical: 4.5,
821
+ gap: 6,
822
+ }}>
823
+ <Text selectable style={{
824
+ fontFamily: AppFonts.interRegular,
825
+ fontSize: 9.5,
826
+ color: AppColors.grayTextStrong,
827
+ flex: 1,
828
+ }} numberOfLines={1}>
829
+ 📂 {frame.fullPath}
830
+ </Text>
831
+
832
+ <View style={{
833
+ backgroundColor: frameBadgeBg,
834
+ borderRadius: 4,
835
+ paddingHorizontal: 5,
836
+ paddingVertical: 1.5,
837
+ }}>
838
+ <Text style={{
839
+ fontFamily: AppFonts.interBold,
840
+ fontSize: 8.5,
841
+ color: frameBadgeText,
842
+ }}>
843
+ {frame.frameType === 'app'
844
+ ? t('console.appCodeBadge')
845
+ : frame.frameType === 'dependency'
846
+ ? t('console.dependencyBadge')
847
+ : frame.frameType === 'native'
848
+ ? t('console.nativeBadge')
849
+ : t('console.hermesVmBadge')}
850
+ </Text>
851
+ </View>
852
+ </View>
740
853
  </View>);
741
854
  })) : (<Text style={{
742
855
  fontFamily: AppFonts.interRegular,
@@ -745,7 +858,7 @@ const LogDetail = React.memo(() => {
745
858
  textAlign: 'center',
746
859
  paddingVertical: 16,
747
860
  }}>
748
- No call stack trace available for this log.
861
+ {t('console.noStackAvailable')}
749
862
  </Text>)}
750
863
  </View>) : (
751
864
  /* Raw Full Trace Monospace Block */