react-native-inapp-inspector 2.2.1 → 2.2.3

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 (181) hide show
  1. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  2. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  3. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  4. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  5. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  10. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  11. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  12. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  13. package/android/.gradle/9.2.0/gc.properties +0 -0
  14. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  15. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/build/reports/problems/problems-report.html +659 -0
  18. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +28 -6
  19. package/dist/commonjs/analytics.d.ts +7 -0
  20. package/dist/commonjs/analytics.js +28 -0
  21. package/dist/commonjs/bundle.d.ts +6 -0
  22. package/dist/commonjs/bundle.js +20 -0
  23. package/dist/commonjs/console.d.ts +7 -0
  24. package/dist/commonjs/console.js +20 -0
  25. package/dist/commonjs/constants/version.d.ts +1 -1
  26. package/dist/commonjs/constants/version.js +1 -1
  27. package/dist/commonjs/crash.d.ts +7 -0
  28. package/dist/commonjs/crash.js +27 -0
  29. package/dist/commonjs/customHooks/analyticsLogger.d.ts +3 -0
  30. package/dist/commonjs/customHooks/analyticsLogger.js +23 -2
  31. package/dist/commonjs/customHooks/consoleLogger.d.ts +3 -0
  32. package/dist/commonjs/customHooks/consoleLogger.js +24 -4
  33. package/dist/commonjs/customHooks/crashHandler.d.ts +2 -0
  34. package/dist/commonjs/customHooks/crashHandler.js +15 -1
  35. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  36. package/dist/commonjs/customHooks/networkLogger.js +23 -2
  37. package/dist/commonjs/customHooks/performanceTracker.d.ts +3 -0
  38. package/dist/commonjs/customHooks/performanceTracker.js +34 -3
  39. package/dist/commonjs/customHooks/reduxLogger.d.ts +3 -0
  40. package/dist/commonjs/customHooks/reduxLogger.js +24 -4
  41. package/dist/commonjs/helpers/index.d.ts +1 -0
  42. package/dist/commonjs/helpers/index.js +1 -0
  43. package/dist/commonjs/helpers/memoryManager.d.ts +29 -0
  44. package/dist/commonjs/helpers/memoryManager.js +112 -0
  45. package/dist/commonjs/helpers/telemetry.js +4 -4
  46. package/dist/commonjs/index.d.ts +7 -1
  47. package/dist/commonjs/index.js +36 -2
  48. package/dist/commonjs/network.d.ts +6 -0
  49. package/dist/commonjs/network.js +17 -0
  50. package/dist/commonjs/performance.d.ts +6 -0
  51. package/dist/commonjs/performance.js +33 -0
  52. package/dist/commonjs/redux.d.ts +5 -0
  53. package/dist/commonjs/redux.js +24 -0
  54. package/dist/esm/analytics.d.ts +7 -0
  55. package/dist/esm/analytics.js +6 -0
  56. package/dist/esm/bundle.d.ts +6 -0
  57. package/dist/esm/bundle.js +6 -0
  58. package/dist/esm/console.d.ts +7 -0
  59. package/dist/esm/console.js +6 -0
  60. package/dist/esm/constants/version.d.ts +1 -1
  61. package/dist/esm/constants/version.js +1 -1
  62. package/dist/esm/crash.d.ts +7 -0
  63. package/dist/esm/crash.js +6 -0
  64. package/dist/esm/customHooks/analyticsLogger.d.ts +3 -0
  65. package/dist/esm/customHooks/analyticsLogger.js +19 -1
  66. package/dist/esm/customHooks/consoleLogger.d.ts +3 -0
  67. package/dist/esm/customHooks/consoleLogger.js +20 -3
  68. package/dist/esm/customHooks/crashHandler.d.ts +2 -0
  69. package/dist/esm/customHooks/crashHandler.js +12 -0
  70. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  71. package/dist/esm/customHooks/networkLogger.js +19 -1
  72. package/dist/esm/customHooks/performanceTracker.d.ts +3 -0
  73. package/dist/esm/customHooks/performanceTracker.js +30 -2
  74. package/dist/esm/customHooks/reduxLogger.d.ts +3 -0
  75. package/dist/esm/customHooks/reduxLogger.js +20 -3
  76. package/dist/esm/helpers/index.d.ts +1 -0
  77. package/dist/esm/helpers/index.js +1 -0
  78. package/dist/esm/helpers/memoryManager.d.ts +29 -0
  79. package/dist/esm/helpers/memoryManager.js +104 -0
  80. package/dist/esm/helpers/telemetry.js +4 -4
  81. package/dist/esm/index.d.ts +7 -1
  82. package/dist/esm/index.js +17 -3
  83. package/dist/esm/network.d.ts +6 -0
  84. package/dist/esm/network.js +5 -0
  85. package/dist/esm/performance.d.ts +6 -0
  86. package/dist/esm/performance.js +6 -0
  87. package/dist/esm/redux.d.ts +5 -0
  88. package/dist/esm/redux.js +5 -0
  89. package/package.json +53 -2
  90. package/src/analytics.ts +35 -0
  91. package/src/bundle.ts +25 -0
  92. package/src/components/AnalyticsDetail.tsx +865 -0
  93. package/src/components/AnalyticsEventCard.tsx +441 -0
  94. package/src/components/AnalyticsGraph.tsx +583 -0
  95. package/src/components/AnimatedEntrance.tsx +64 -0
  96. package/src/components/AppHeaderLogo.tsx +109 -0
  97. package/src/components/BrandCircleIcon.tsx +144 -0
  98. package/src/components/BrandSquareIcon.tsx +144 -0
  99. package/src/components/CodeSnippet.tsx +725 -0
  100. package/src/components/ConsoleLogCard.tsx +628 -0
  101. package/src/components/CopyButton.tsx +87 -0
  102. package/src/components/DiffViewer.tsx +82 -0
  103. package/src/components/DomainHeader.tsx +237 -0
  104. package/src/components/EmptyState.tsx +73 -0
  105. package/src/components/EndOfListFooter.tsx +100 -0
  106. package/src/components/ErrorBoundary.tsx +688 -0
  107. package/src/components/HeadersSection.tsx +192 -0
  108. package/src/components/HighlightText.tsx +100 -0
  109. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  110. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  111. package/src/components/Inspector/BundleTab.tsx +4731 -0
  112. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  113. package/src/components/Inspector/CrashDetail.tsx +941 -0
  114. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  115. package/src/components/Inspector/CrashTab.tsx +871 -0
  116. package/src/components/Inspector/FabLauncher.tsx +80 -0
  117. package/src/components/Inspector/InspectorContext.tsx +28 -0
  118. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  119. package/src/components/Inspector/LogDetail.tsx +1427 -0
  120. package/src/components/Inspector/MainScreen.tsx +258 -0
  121. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  122. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  123. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  124. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  125. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  126. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  127. package/src/components/Inspector/ReduxTab.tsx +954 -0
  128. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  129. package/src/components/Inspector/TabBar.tsx +187 -0
  130. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  131. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  132. package/src/components/JsonViewer.tsx +486 -0
  133. package/src/components/LogCard.tsx +491 -0
  134. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  135. package/src/components/MetaAccordion.tsx +340 -0
  136. package/src/components/MiniBarChart.tsx +42 -0
  137. package/src/components/MiniLineChart.tsx +33 -0
  138. package/src/components/NetworkIcons.tsx +2118 -0
  139. package/src/components/SectionHeader.tsx +113 -0
  140. package/src/components/SegmentedTabs.tsx +83 -0
  141. package/src/components/Slider.tsx +300 -0
  142. package/src/components/SourcePageCard.tsx +148 -0
  143. package/src/components/Toast.tsx +131 -0
  144. package/src/components/TouchableScale.tsx +91 -0
  145. package/src/components/TreeNode.tsx +186 -0
  146. package/src/console.ts +24 -0
  147. package/src/constants/index.ts +38 -0
  148. package/src/constants/version.ts +3 -0
  149. package/src/crash.ts +32 -0
  150. package/src/customHooks/analyticsLogger.ts +336 -0
  151. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  152. package/src/customHooks/consoleLogger.ts +497 -0
  153. package/src/customHooks/crashHandler.ts +944 -0
  154. package/src/customHooks/logFilters.ts +32 -0
  155. package/src/customHooks/networkLogger.ts +419 -0
  156. package/src/customHooks/performanceTracker.ts +1014 -0
  157. package/src/customHooks/reduxLogger.ts +406 -0
  158. package/src/customHooks/useAccordion.tsx +60 -0
  159. package/src/decorators/index.ts +184 -0
  160. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  161. package/src/helpers/index.ts +860 -0
  162. package/src/helpers/memoryManager.ts +138 -0
  163. package/src/helpers/searchQueryParser.ts +283 -0
  164. package/src/helpers/settingsStore.ts +171 -0
  165. package/src/helpers/telemetry.ts +505 -0
  166. package/src/helpers/toast.ts +17 -0
  167. package/src/i18n/index.ts +69 -0
  168. package/src/i18n/locales/en.json +1052 -0
  169. package/src/index.tsx +2336 -0
  170. package/src/native/NativeInspector.ts +397 -0
  171. package/src/native/NativeNetworkInspector.ts +24 -0
  172. package/src/network.ts +22 -0
  173. package/src/performance.ts +38 -0
  174. package/src/redux.ts +23 -0
  175. package/src/styles/AppColors.ts +408 -0
  176. package/src/styles/AppFonts.ts +8 -0
  177. package/src/styles/common.ts +209 -0
  178. package/src/styles/index.ts +1570 -0
  179. package/src/types/enums.ts +194 -0
  180. package/src/types/index.ts +34 -0
  181. package/src/types/interfaces.ts +515 -0
@@ -0,0 +1,1427 @@
1
+ import React, {useMemo, useState} from 'react';
2
+ import {useTranslation} from '../../i18n';
3
+ import {
4
+ Pressable,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Text,
8
+ TextInput,
9
+ View,
10
+ } from 'react-native';
11
+ import {useInspector} from './InspectorContext';
12
+ import HighlightText from '../HighlightText';
13
+ import JsonViewer from '../JsonViewer';
14
+ import CopyButton from '../CopyButton';
15
+ import SegmentedTabs from '../SegmentedTabs';
16
+ import {
17
+ ClearIcon,
18
+ SearchIcon,
19
+ PrettyIcon,
20
+ RawIcon,
21
+ TableIcon,
22
+ HeadersIcon,
23
+ LayersIcon,
24
+ TerminalIcon,
25
+ RequestIcon,
26
+ InfoCircleIcon,
27
+ DocIcon,
28
+ ExternalLinkIcon,
29
+ } from '../NetworkIcons';
30
+ import {AppColors} from '../../styles/AppColors';
31
+ import {AppFonts} from '../../styles/AppFonts';
32
+ import styles from '../../styles';
33
+ import {
34
+ getJsonContent,
35
+ getSize,
36
+ formatDateTime,
37
+ parseStackLine,
38
+ ParsedStackFrame,
39
+ openInVSCode,
40
+ } from '../../helpers';
41
+
42
+ type DetailSubTab = 'output' | 'arguments' | 'stack' | 'metadata';
43
+
44
+ const getRelativeTime = (timestamp: number): string => {
45
+ const now = Date.now();
46
+ const diff = now - timestamp;
47
+ const seconds = Math.floor(diff / 1000);
48
+ const minutes = Math.floor(seconds / 60);
49
+ const hours = Math.floor(minutes / 60);
50
+ const days = Math.floor(hours / 24);
51
+
52
+ if (seconds < 60) return `${seconds}s ago`;
53
+ if (minutes < 60) return `${minutes}m ago`;
54
+ if (hours < 24) return `${hours}h ago`;
55
+ return `${days}d ago`;
56
+ };
57
+
58
+ const LogDetail = React.memo(() => {
59
+ const {t} = useTranslation();
60
+ const {selectedLog} = useInspector();
61
+ const [activeTab, setActiveTab] = useState<DetailSubTab>('output');
62
+ const [detailSearch, setDetailSearch] = useState('');
63
+ const [viewMode, setViewMode] = useState<'pretty' | 'raw' | 'table'>('pretty');
64
+
65
+ const jsonContent = useMemo(
66
+ () => (selectedLog ? getJsonContent(selectedLog.message) : null),
67
+ [selectedLog],
68
+ );
69
+
70
+ const [stackViewMode, setStackViewMode] = useState<'structured' | 'raw'>('structured');
71
+ const [stackSource, setStackSource] = useState<'caller' | 'error'>('caller');
72
+ const [stackFilter, setStackFilter] = useState<'app' | 'all'>('app');
73
+
74
+ const activeStackString = useMemo(() => {
75
+ if (!selectedLog) return '';
76
+ if (stackSource === 'error' && selectedLog.errorStack) {
77
+ return selectedLog.errorStack;
78
+ }
79
+ return selectedLog.stack || selectedLog.caller || '';
80
+ }, [selectedLog, stackSource]);
81
+
82
+ const parsedStackFrames: ParsedStackFrame[] = useMemo(() => {
83
+ if (!activeStackString) return [];
84
+ return activeStackString
85
+ .split('\n')
86
+ .map(l => l.trim())
87
+ .filter(l => l.length > 0)
88
+ .map((line, idx) => parseStackLine(line, idx === 0));
89
+ }, [activeStackString]);
90
+
91
+ const appStackFrames = useMemo(() => {
92
+ return parsedStackFrames.filter(f => f.frameType === 'app' || !f.isRuntimeNoise);
93
+ }, [parsedStackFrames]);
94
+
95
+ const displayedFrames = useMemo(() => {
96
+ if (stackFilter === 'app' && appStackFrames.length > 0) {
97
+ return appStackFrames;
98
+ }
99
+ return parsedStackFrames;
100
+ }, [stackFilter, appStackFrames, parsedStackFrames]);
101
+
102
+ const originFrame: ParsedStackFrame | null = useMemo(() => {
103
+ if (!selectedLog) return null;
104
+ if (appStackFrames.length > 0) {
105
+ return appStackFrames[0];
106
+ }
107
+ if (selectedLog.caller && selectedLog.caller !== 'Unknown') {
108
+ return parseStackLine(selectedLog.caller, true);
109
+ }
110
+ if (parsedStackFrames.length > 0) {
111
+ return parsedStackFrames[0];
112
+ }
113
+ return null;
114
+ }, [selectedLog, appStackFrames, parsedStackFrames]);
115
+
116
+ if (!selectedLog) return null;
117
+
118
+ const isDark = AppColors.primaryLight !== AppColors.white;
119
+ const getTypeColors = () => {
120
+ const type = (selectedLog.type || 'log').toLowerCase();
121
+ const method = ((selectedLog as any).sourceMethod || type).toLowerCase();
122
+
123
+ if (type === 'error' || method === 'error') {
124
+ return {
125
+ border: AppColors.red500,
126
+ badgeBg: AppColors.red100,
127
+ badgeText: AppColors.redErrorText,
128
+ label: 'ERROR',
129
+ methodBorder: AppColors.errorBorder,
130
+ methodBg: AppColors.red100,
131
+ methodText: AppColors.redErrorText,
132
+ };
133
+ }
134
+ if (type === 'warn' || method === 'warn') {
135
+ return {
136
+ border: AppColors.amber500,
137
+ badgeBg: AppColors.amber100,
138
+ badgeText: AppColors.amber800Warm,
139
+ label: 'WARN',
140
+ methodBorder: AppColors.amber200,
141
+ methodBg: AppColors.amber100,
142
+ methodText: AppColors.amber800Warm,
143
+ };
144
+ }
145
+ if (type === 'debug' || method === 'debug') {
146
+ return {
147
+ border: AppColors.violet500,
148
+ badgeBg: AppColors.purple100,
149
+ badgeText: AppColors.violet600,
150
+ label: 'DEBUG',
151
+ methodBorder: AppColors.purple200,
152
+ methodBg: AppColors.purple100,
153
+ methodText: AppColors.violet600,
154
+ };
155
+ }
156
+ if (type === 'info' || method === 'info') {
157
+ return {
158
+ border: AppColors.sky500,
159
+ badgeBg: AppColors.sky100,
160
+ badgeText: AppColors.sky600,
161
+ label: 'INFO',
162
+ methodBorder: AppColors.sky400,
163
+ methodBg: AppColors.sky100,
164
+ methodText: AppColors.sky600,
165
+ };
166
+ }
167
+ return {
168
+ border: AppColors.indigo500,
169
+ badgeBg: AppColors.indigo50,
170
+ badgeText: AppColors.indigo600Alt,
171
+ label: 'LOG',
172
+ methodBorder: AppColors.indigo400,
173
+ methodBg: AppColors.indigo50,
174
+ methodText: AppColors.indigo600Alt,
175
+ };
176
+ };
177
+
178
+ const typeColors = getTypeColors();
179
+ const hasMultipleArgs =
180
+ selectedLog.rawArgs != null && selectedLog.rawArgs.length > 1;
181
+ const hasStack = Boolean(
182
+ selectedLog.stack && selectedLog.stack.trim().length > 0,
183
+ );
184
+
185
+ const subTabs = [
186
+ {
187
+ key: 'output',
188
+ label: t('console.tabOutput'),
189
+ icon: (isActive: boolean) => (
190
+ <TerminalIcon
191
+ color={isActive ? AppColors.white : AppColors.grayTextWeak}
192
+ size={12}
193
+ />
194
+ ),
195
+ },
196
+ ...(hasMultipleArgs
197
+ ? [
198
+ {
199
+ key: 'arguments',
200
+ label: t('console.tabArgs', {count: selectedLog.rawArgs?.length}),
201
+ icon: (isActive: boolean) => (
202
+ <RequestIcon
203
+ color={isActive ? AppColors.white : AppColors.grayTextWeak}
204
+ size={12}
205
+ />
206
+ ),
207
+ },
208
+ ]
209
+ : []),
210
+ ...(hasStack
211
+ ? [
212
+ {
213
+ key: 'stack',
214
+ label: t('console.tabStack'),
215
+ icon: (isActive: boolean) => (
216
+ <LayersIcon
217
+ color={isActive ? AppColors.white : AppColors.grayTextWeak}
218
+ size={12}
219
+ />
220
+ ),
221
+ },
222
+ ]
223
+ : []),
224
+ {
225
+ key: 'metadata',
226
+ label: t('console.tabMetadata'),
227
+ icon: (isActive: boolean) => (
228
+ <InfoCircleIcon
229
+ color={isActive ? AppColors.white : AppColors.grayTextWeak}
230
+ size={12}
231
+ />
232
+ ),
233
+ },
234
+ ];
235
+
236
+ return (
237
+ <View style={{flex: 1}}>
238
+ {/* Enhanced details header bar */}
239
+ <View style={styles.detailInfoBar}>
240
+ <View style={{flexDirection: 'column', gap: 10}}>
241
+ {/* Top Row: Type badge, method, content pill & Copy button */}
242
+ <View
243
+ style={{
244
+ flexDirection: 'row',
245
+ alignItems: 'center',
246
+ justifyContent: 'space-between',
247
+ gap: 8,
248
+ }}>
249
+ <View
250
+ style={{
251
+ flexDirection: 'row',
252
+ alignItems: 'center',
253
+ gap: 6,
254
+ flex: 1,
255
+ flexWrap: 'wrap',
256
+ }}>
257
+ {/* Type Badge */}
258
+ <View
259
+ style={[
260
+ styles.typeChip,
261
+ {
262
+ backgroundColor: typeColors.badgeBg,
263
+ borderWidth: 1,
264
+ borderColor: `${typeColors.border}40`,
265
+ paddingHorizontal: 8,
266
+ paddingVertical: 3,
267
+ },
268
+ ]}>
269
+ <View
270
+ style={[
271
+ styles.typeDot,
272
+ {backgroundColor: typeColors.badgeText},
273
+ ]}
274
+ />
275
+ <Text
276
+ style={[
277
+ styles.typeChipText,
278
+ {color: typeColors.badgeText, fontSize: 10},
279
+ ]}>
280
+ {typeColors.label}
281
+ </Text>
282
+ </View>
283
+
284
+ {/* Method chip */}
285
+ <View
286
+ style={[
287
+ styles.metaChip,
288
+ {
289
+ backgroundColor: `${AppColors.brandPurple}12`,
290
+ borderColor: `${AppColors.brandPurple}30`,
291
+ },
292
+ ]}>
293
+ <Text
294
+ style={[
295
+ styles.metaChipText,
296
+ {color: AppColors.brandPurple, fontSize: 10},
297
+ ]}>
298
+ console.
299
+ {selectedLog.sourceMethod || selectedLog.type || 'log'}
300
+ </Text>
301
+ </View>
302
+
303
+ {/* JSON content type chip */}
304
+ {jsonContent && (
305
+ <View
306
+ style={[
307
+ styles.metaChip,
308
+ {
309
+ backgroundColor: `${AppColors.teal600}12`,
310
+ borderColor: `${AppColors.teal600}2B`,
311
+ },
312
+ ]}>
313
+ <Text
314
+ style={[
315
+ styles.metaChipText,
316
+ {color: AppColors.teal600, fontSize: 10},
317
+ ]}>
318
+ {Array.isArray(jsonContent.data)
319
+ ? `Array[${jsonContent.data.length}]`
320
+ : `Object{${Object.keys(jsonContent.data).length}}`}
321
+ </Text>
322
+ </View>
323
+ )}
324
+
325
+ {/* Duplicate count chip */}
326
+ {selectedLog.duplicateCount != null &&
327
+ selectedLog.duplicateCount > 1 && (
328
+ <View
329
+ style={[
330
+ styles.metaChip,
331
+ {
332
+ backgroundColor: `${AppColors.purple}1A`,
333
+ borderColor: `${AppColors.purple}3D`,
334
+ },
335
+ ]}>
336
+ <Text
337
+ style={[
338
+ styles.metaChipText,
339
+ {color: AppColors.purple, fontWeight: '700'},
340
+ ]}>
341
+ {t('console.duplicates', {
342
+ count: selectedLog.duplicateCount,
343
+ })}
344
+ </Text>
345
+ </View>
346
+ )}
347
+ </View>
348
+
349
+ {/* Quick Copy button */}
350
+ <CopyButton
351
+ value={selectedLog.message}
352
+ label={t('console.logMessage')}
353
+ />
354
+ </View>
355
+
356
+ {/* Caller & Trace origin info */}
357
+ <View style={{gap: 4}}>
358
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap'}}>
359
+ {originFrame?.fileExt && originFrame.fileExt !== 'other' && (
360
+ <View
361
+ style={{
362
+ backgroundColor:
363
+ originFrame.fileExt === 'tsx' || originFrame.fileExt === 'ts'
364
+ ? AppColors.brandPurple
365
+ : AppColors.teal600,
366
+ borderRadius: 4,
367
+ paddingHorizontal: 5,
368
+ paddingVertical: 1.5,
369
+ }}>
370
+ <Text
371
+ style={{
372
+ fontFamily: AppFonts.interBold,
373
+ fontSize: 9.5,
374
+ color: AppColors.white,
375
+ letterSpacing: 0.5,
376
+ }}>
377
+ {originFrame.fileExt.toUpperCase()}
378
+ </Text>
379
+ </View>
380
+ )}
381
+
382
+ <Text
383
+ selectable
384
+ style={{
385
+ fontFamily: AppFonts.interBold,
386
+ fontSize: 13,
387
+ color: AppColors.primaryBlack,
388
+ lineHeight: 18,
389
+ flexShrink: 1,
390
+ }}>
391
+ {originFrame?.functionName && originFrame.functionName !== '<anonymous>'
392
+ ? `${originFrame.functionName}()`
393
+ : originFrame?.fileName || t('console.consoleLog')}
394
+ </Text>
395
+
396
+ {originFrame?.fileName && (
397
+ <Pressable
398
+ onPress={() =>
399
+ openInVSCode(
400
+ originFrame.rawFilePath ||
401
+ originFrame.fullPath ||
402
+ originFrame.fileName,
403
+ originFrame.lineNumber,
404
+ originFrame.columnNumber,
405
+ )
406
+ }
407
+ hitSlop={8}
408
+ style={[
409
+ styles.metaChip,
410
+ {
411
+ backgroundColor: `${AppColors.brandPurple}15`,
412
+ borderColor: `${AppColors.brandPurple}40`,
413
+ paddingHorizontal: 7,
414
+ paddingVertical: 2,
415
+ },
416
+ ]}>
417
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 4, flexShrink: 1}}>
418
+ <DocIcon color={AppColors.brandPurple} size={10} />
419
+ <Text
420
+ style={[
421
+ styles.metaChipText,
422
+ {color: AppColors.brandPurple, fontSize: 10, fontFamily: AppFonts.interBold, flexShrink: 1},
423
+ ]}>
424
+ {originFrame.fileName}
425
+ {originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
426
+ {originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
427
+ </Text>
428
+ <ExternalLinkIcon color={AppColors.brandPurple} size={9} />
429
+ </View>
430
+ </Pressable>
431
+ )}
432
+ </View>
433
+
434
+ {/* Enhanced metadata metrics row */}
435
+ <View
436
+ style={{
437
+ flexDirection: 'row',
438
+ alignItems: 'center',
439
+ gap: 8,
440
+ flexWrap: 'wrap',
441
+ }}>
442
+ <Text
443
+ style={{
444
+ fontFamily: AppFonts.interBold,
445
+ fontSize: 10,
446
+ color: AppColors.grayTextWeak,
447
+ }}>
448
+ #{selectedLog.id + 1}
449
+ </Text>
450
+ <Text style={detailStyles.metaDot}>•</Text>
451
+ <Text
452
+ style={{
453
+ fontFamily: AppFonts.interRegular,
454
+ fontSize: 10,
455
+ color: AppColors.grayTextWeak,
456
+ }}>
457
+ {formatDateTime(selectedLog.timestamp)} ({getRelativeTime(selectedLog.timestamp)})
458
+ </Text>
459
+ <Text style={detailStyles.metaDot}>•</Text>
460
+ <Text
461
+ style={{
462
+ fontFamily: AppFonts.interRegular,
463
+ fontSize: 10,
464
+ color: AppColors.grayTextWeak,
465
+ }}>
466
+ {getSize(selectedLog.message)}
467
+ </Text>
468
+ </View>
469
+ </View>
470
+ </View>
471
+ </View>
472
+
473
+ {/* Sub-tabs header */}
474
+ <View style={detailStyles.tabsHeaderWrap}>
475
+ <SegmentedTabs
476
+ tabs={subTabs}
477
+ activeKey={activeTab}
478
+ onChange={key => setActiveTab(key as DetailSubTab)}
479
+ />
480
+ </View>
481
+
482
+ {/* Search row (only for output/arguments/stack tabs) */}
483
+ {activeTab !== 'metadata' && (
484
+ <View style={{paddingHorizontal: 12, paddingTop: 6, paddingBottom: 4}}>
485
+ <View style={[styles.detailSearchRow, {marginBottom: 0}]}>
486
+ <View
487
+ style={[
488
+ styles.detailSearchBox,
489
+ {
490
+ backgroundColor: isDark
491
+ ? `${AppColors.brandPurple}10`
492
+ : AppColors.purpleShade50,
493
+ borderWidth: 1,
494
+ borderColor: AppColors.dividerColor,
495
+ },
496
+ ]}>
497
+ <SearchIcon color={AppColors.grayTextWeak} size={15} />
498
+ <TextInput
499
+ placeholder="Search in log details..."
500
+ placeholderTextColor={AppColors.grayTextWeak}
501
+ value={detailSearch}
502
+ onChangeText={setDetailSearch}
503
+ style={styles.detailSearchInput}
504
+ autoCorrect={false}
505
+ autoCapitalize="none"
506
+ />
507
+ {detailSearch.length > 0 && (
508
+ <Pressable
509
+ onPress={() => setDetailSearch('')}
510
+ hitSlop={10}
511
+ style={{padding: 6}}>
512
+ <ClearIcon color={AppColors.grayTextWeak} size={13} />
513
+ </Pressable>
514
+ )}
515
+ </View>
516
+ </View>
517
+ </View>
518
+ )}
519
+
520
+ {/* Tab Content Container */}
521
+ <ScrollView
522
+ style={{flex: 1}}
523
+ contentContainerStyle={{
524
+ paddingHorizontal: 12,
525
+ paddingTop: 6,
526
+ paddingBottom: 28,
527
+ flexGrow: 1,
528
+ }}
529
+ showsVerticalScrollIndicator={true}
530
+ keyboardShouldPersistTaps="handled">
531
+ {/* Tab 1: Output / Message */}
532
+ {activeTab === 'output' && (
533
+ <View
534
+ style={[
535
+ styles.sectionContainer,
536
+ {
537
+ backgroundColor: isDark
538
+ ? `${AppColors.purple}12`
539
+ : AppColors.purpleShade50,
540
+ borderWidth: 1,
541
+ borderColor: AppColors.dividerColor,
542
+ borderRadius: 12,
543
+ overflow: 'hidden',
544
+ flex: 1,
545
+ },
546
+ ]}>
547
+ {/* Section Header with View Modes */}
548
+ <View
549
+ style={{
550
+ flexDirection: 'column',
551
+ gap: 8,
552
+ paddingHorizontal: 12,
553
+ paddingVertical: 10,
554
+ borderBottomWidth: 1,
555
+ borderBottomColor: AppColors.dividerColor,
556
+ backgroundColor: isDark
557
+ ? AppColors.primaryLight
558
+ : AppColors.grayBackground,
559
+ }}>
560
+ <View
561
+ style={{
562
+ flexDirection: 'row',
563
+ justifyContent: 'space-between',
564
+ alignItems: 'center',
565
+ }}>
566
+ <Text
567
+ style={{
568
+ fontFamily: AppFonts.interBold,
569
+ fontSize: 11.5,
570
+ color: AppColors.purple,
571
+ letterSpacing: 0.4,
572
+ textTransform: 'uppercase',
573
+ }}>
574
+ {jsonContent
575
+ ? t('console.jsonTitle')
576
+ : t('console.messageTitle')}
577
+ </Text>
578
+ <CopyButton
579
+ value={selectedLog.message}
580
+ label={t('console.logMessage')}
581
+ />
582
+ </View>
583
+
584
+ {jsonContent && (
585
+ <SegmentedTabs
586
+ tabs={[
587
+ {
588
+ key: 'pretty',
589
+ label: t('network.jsonViewer.pretty'),
590
+ icon: (isActive: boolean) => (
591
+ <PrettyIcon
592
+ color={
593
+ isActive
594
+ ? AppColors.white
595
+ : AppColors.grayTextWeak
596
+ }
597
+ size={12}
598
+ />
599
+ ),
600
+ },
601
+ {
602
+ key: 'raw',
603
+ label: t('network.jsonViewer.raw'),
604
+ icon: (isActive: boolean) => (
605
+ <RawIcon
606
+ color={
607
+ isActive
608
+ ? AppColors.white
609
+ : AppColors.grayTextWeak
610
+ }
611
+ size={12}
612
+ />
613
+ ),
614
+ },
615
+ {
616
+ key: 'table',
617
+ label: t('network.jsonViewer.table'),
618
+ icon: (isActive: boolean) => (
619
+ <TableIcon
620
+ color={
621
+ isActive
622
+ ? AppColors.white
623
+ : AppColors.grayTextWeak
624
+ }
625
+ size={12}
626
+ />
627
+ ),
628
+ },
629
+ ]}
630
+ activeKey={viewMode}
631
+ onChange={key =>
632
+ setViewMode(key as 'pretty' | 'raw' | 'table')
633
+ }
634
+ />
635
+ )}
636
+ </View>
637
+
638
+ {/* Content Body */}
639
+ <View style={{padding: 12, flex: 1}}>
640
+ {jsonContent ? (
641
+ <>
642
+ {jsonContent.header ? (
643
+ <View
644
+ style={{
645
+ backgroundColor: AppColors.primaryLight,
646
+ borderRadius: 8,
647
+ borderWidth: 1,
648
+ borderColor: AppColors.dividerColor,
649
+ padding: 10,
650
+ marginBottom: 8,
651
+ }}>
652
+ <Text
653
+ selectable
654
+ style={{
655
+ fontFamily: AppFonts.interRegular,
656
+ fontSize: 12,
657
+ color: AppColors.primaryBlack,
658
+ lineHeight: 17,
659
+ }}>
660
+ {jsonContent.header}
661
+ </Text>
662
+ </View>
663
+ ) : null}
664
+ <JsonViewer
665
+ data={jsonContent.data}
666
+ search={detailSearch}
667
+ wrap
668
+ forceOpen
669
+ hideTabs
670
+ fullHeight
671
+ mode={viewMode}
672
+ defaultExpandDepth={
673
+ jsonContent.data &&
674
+ typeof jsonContent.data === 'object' &&
675
+ Object.keys(jsonContent.data).length > 10
676
+ ? 1
677
+ : 2
678
+ }
679
+ />
680
+ </>
681
+ ) : (
682
+ <View
683
+ style={{
684
+ backgroundColor: AppColors.primaryLight,
685
+ borderRadius: 8,
686
+ borderWidth: 1,
687
+ borderColor: AppColors.dividerColor,
688
+ padding: 10,
689
+ }}>
690
+ <HighlightText
691
+ text={selectedLog.message}
692
+ search={detailSearch}
693
+ style={{
694
+ fontFamily: AppFonts.interRegular,
695
+ fontSize: 12,
696
+ color: AppColors.primaryBlack,
697
+ lineHeight: 18,
698
+ }}
699
+ highlightStyle={{
700
+ backgroundColor: AppColors.yellowHighlight,
701
+ color: AppColors.primaryBlack,
702
+ borderRadius: 3,
703
+ paddingHorizontal: 2,
704
+ }}
705
+ detectLinks={true}
706
+ />
707
+ </View>
708
+ )}
709
+ </View>
710
+ </View>
711
+ )}
712
+
713
+ {/* Tab 2: Individual Arguments */}
714
+ {activeTab === 'arguments' && selectedLog.rawArgs && (
715
+ <View style={{gap: 10}}>
716
+ {selectedLog.rawArgs.map((arg, idx) => {
717
+ const argType =
718
+ arg === null
719
+ ? 'null'
720
+ : Array.isArray(arg)
721
+ ? `Array[${arg.length}]`
722
+ : typeof arg === 'object'
723
+ ? `Object{${Object.keys(arg).length}}`
724
+ : typeof arg;
725
+ const isObj =
726
+ arg != null &&
727
+ (typeof arg === 'object' || Array.isArray(arg));
728
+ const argStr = isObj
729
+ ? JSON.stringify(arg, null, 2)
730
+ : String(arg);
731
+
732
+ return (
733
+ <View
734
+ key={idx}
735
+ style={[
736
+ styles.sectionContainer,
737
+ {
738
+ backgroundColor: AppColors.primaryLight,
739
+ borderRadius: 10,
740
+ borderWidth: 1,
741
+ borderColor: AppColors.dividerColor,
742
+ overflow: 'hidden',
743
+ },
744
+ ]}>
745
+ <View
746
+ style={{
747
+ flexDirection: 'row',
748
+ alignItems: 'center',
749
+ justifyContent: 'space-between',
750
+ paddingHorizontal: 12,
751
+ paddingVertical: 8,
752
+ backgroundColor: AppColors.grayBackground,
753
+ borderBottomWidth: 1,
754
+ borderBottomColor: AppColors.dividerColor,
755
+ }}>
756
+ <View
757
+ style={{
758
+ flexDirection: 'row',
759
+ alignItems: 'center',
760
+ gap: 6,
761
+ }}>
762
+ <Text
763
+ style={{
764
+ fontFamily: AppFonts.interBold,
765
+ fontSize: 11,
766
+ color: AppColors.purple,
767
+ }}>
768
+ Arg #{idx + 1}
769
+ </Text>
770
+ <View
771
+ style={[
772
+ styles.metaChip,
773
+ {
774
+ backgroundColor: `${AppColors.teal600}12`,
775
+ borderColor: `${AppColors.teal600}2B`,
776
+ paddingHorizontal: 5,
777
+ paddingVertical: 1.5,
778
+ },
779
+ ]}>
780
+ <Text
781
+ style={[
782
+ styles.metaChipText,
783
+ {color: AppColors.teal600, fontSize: 9},
784
+ ]}>
785
+ {argType}
786
+ </Text>
787
+ </View>
788
+ </View>
789
+ <CopyButton value={argStr} label={`Arg #${idx + 1}`} />
790
+ </View>
791
+
792
+ <View style={{padding: 10}}>
793
+ {isObj ? (
794
+ <JsonViewer
795
+ data={arg}
796
+ search={detailSearch}
797
+ wrap
798
+ forceOpen
799
+ hideTabs
800
+ defaultExpandDepth={2}
801
+ />
802
+ ) : (
803
+ <HighlightText
804
+ text={argStr}
805
+ search={detailSearch}
806
+ style={{
807
+ fontFamily: AppFonts.interRegular,
808
+ fontSize: 12,
809
+ color: AppColors.primaryBlack,
810
+ lineHeight: 17,
811
+ }}
812
+ highlightStyle={{
813
+ backgroundColor: AppColors.yellowHighlight,
814
+ color: AppColors.primaryBlack,
815
+ }}
816
+ />
817
+ )}
818
+ </View>
819
+ </View>
820
+ );
821
+ })}
822
+ </View>
823
+ )}
824
+
825
+ {/* Tab 3: Call Stack Trace */}
826
+ {activeTab === 'stack' && (
827
+ <View
828
+ style={[
829
+ styles.sectionContainer,
830
+ {
831
+ backgroundColor: isDark
832
+ ? `${AppColors.purple}12`
833
+ : AppColors.purpleShade50,
834
+ borderWidth: 1,
835
+ borderColor: AppColors.dividerColor,
836
+ borderRadius: 12,
837
+ overflow: 'hidden',
838
+ },
839
+ ]}>
840
+ {/* Section Header with Mode Switch */}
841
+ <View
842
+ style={{
843
+ flexDirection: 'column',
844
+ gap: 8,
845
+ paddingHorizontal: 12,
846
+ paddingVertical: 10,
847
+ borderBottomWidth: 1,
848
+ borderBottomColor: AppColors.dividerColor,
849
+ backgroundColor: isDark
850
+ ? AppColors.primaryLight
851
+ : AppColors.grayBackground,
852
+ }}>
853
+ {/* Header Top Row: Title, Mode switcher & Full copy */}
854
+ <View
855
+ style={{
856
+ flexDirection: 'row',
857
+ alignItems: 'center',
858
+ justifyContent: 'space-between',
859
+ gap: 8,
860
+ }}>
861
+ <Text
862
+ style={{
863
+ fontFamily: AppFonts.interBold,
864
+ fontSize: 11.5,
865
+ color: AppColors.purple,
866
+ textTransform: 'uppercase',
867
+ letterSpacing: 0.4,
868
+ }}>
869
+ {stackSource === 'error'
870
+ ? t('console.errorStack', {count: parsedStackFrames.length})
871
+ : t('console.callStack', {count: parsedStackFrames.length})}
872
+ </Text>
873
+
874
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 5}}>
875
+ <View
876
+ style={{
877
+ flexDirection: 'row',
878
+ backgroundColor: isDark ? AppColors.primaryLight : AppColors.white,
879
+ borderRadius: 7,
880
+ padding: 2,
881
+ borderWidth: 1,
882
+ borderColor: AppColors.dividerColor,
883
+ }}>
884
+ <Pressable
885
+ onPress={() => setStackViewMode('structured')}
886
+ style={{
887
+ flexDirection: 'row',
888
+ alignItems: 'center',
889
+ gap: 3,
890
+ paddingVertical: 3.5,
891
+ paddingHorizontal: 7,
892
+ borderRadius: 5,
893
+ backgroundColor:
894
+ stackViewMode === 'structured'
895
+ ? AppColors.brandPurple
896
+ : 'transparent',
897
+ }}>
898
+ <LayersIcon
899
+ color={
900
+ stackViewMode === 'structured'
901
+ ? AppColors.white
902
+ : AppColors.grayTextWeak
903
+ }
904
+ size={11}
905
+ />
906
+ <Text
907
+ style={{
908
+ fontFamily: AppFonts.interBold,
909
+ fontSize: 10,
910
+ color:
911
+ stackViewMode === 'structured'
912
+ ? AppColors.white
913
+ : AppColors.grayTextStrong,
914
+ }}>
915
+ {t('console.cardsView')}
916
+ </Text>
917
+ </Pressable>
918
+
919
+ <Pressable
920
+ onPress={() => setStackViewMode('raw')}
921
+ style={{
922
+ flexDirection: 'row',
923
+ alignItems: 'center',
924
+ gap: 3,
925
+ paddingVertical: 3.5,
926
+ paddingHorizontal: 7,
927
+ borderRadius: 5,
928
+ backgroundColor:
929
+ stackViewMode === 'raw'
930
+ ? AppColors.brandPurple
931
+ : 'transparent',
932
+ }}>
933
+ <RawIcon
934
+ color={
935
+ stackViewMode === 'raw'
936
+ ? AppColors.white
937
+ : AppColors.grayTextWeak
938
+ }
939
+ size={11}
940
+ />
941
+ <Text
942
+ style={{
943
+ fontFamily: AppFonts.interBold,
944
+ fontSize: 10,
945
+ color:
946
+ stackViewMode === 'raw'
947
+ ? AppColors.white
948
+ : AppColors.grayTextStrong,
949
+ }}>
950
+ {t('console.rawTraceView')}
951
+ </Text>
952
+ </Pressable>
953
+ </View>
954
+
955
+ <CopyButton
956
+ value={activeStackString}
957
+ label={t('console.fullStackTrace')}
958
+ />
959
+ </View>
960
+ </View>
961
+
962
+ {/* Source Switcher if Error Stack exists */}
963
+ {selectedLog.errorStack && (
964
+ <SegmentedTabs
965
+ tabs={[
966
+ {
967
+ key: 'caller',
968
+ label: t('console.callOriginStack'),
969
+ },
970
+ {
971
+ key: 'error',
972
+ label: t('console.errorThrownStack'),
973
+ },
974
+ ]}
975
+ activeKey={stackSource}
976
+ onChange={key => setStackSource(key as 'caller' | 'error')}
977
+ />
978
+ )}
979
+
980
+ {/* Scope Filter Tabs (Full Width) */}
981
+ <SegmentedTabs
982
+ tabs={[
983
+ {
984
+ key: 'app',
985
+ label: t('console.appCodeScope', {count: appStackFrames.length}),
986
+ },
987
+ {
988
+ key: 'all',
989
+ label: t('console.allFramesScope', {count: parsedStackFrames.length}),
990
+ },
991
+ ]}
992
+ activeKey={stackFilter}
993
+ onChange={key => setStackFilter(key as 'app' | 'all')}
994
+ />
995
+ </View>
996
+
997
+ {/* Stack Content: Structured Cards or Raw Full Trace */}
998
+ {stackViewMode === 'structured' ? (
999
+ <View style={{padding: 12, gap: 10}}>
1000
+ {displayedFrames.length > 0 ? (
1001
+ displayedFrames.map((frame, frameIdx) => {
1002
+ const isTopFrame = frameIdx === 0;
1003
+
1004
+ const frameBadgeBg =
1005
+ frame.frameType === 'app'
1006
+ ? `${AppColors.brandPurple}15`
1007
+ : frame.frameType === 'dependency'
1008
+ ? `${AppColors.amber500}18`
1009
+ : frame.frameType === 'native'
1010
+ ? `${AppColors.sky500}18`
1011
+ : AppColors.grayBorderSecondary;
1012
+
1013
+ const frameBadgeText =
1014
+ frame.frameType === 'app'
1015
+ ? AppColors.brandPurple
1016
+ : frame.frameType === 'dependency'
1017
+ ? AppColors.amber700
1018
+ : frame.frameType === 'native'
1019
+ ? AppColors.sky500
1020
+ : AppColors.grayTextWeak;
1021
+
1022
+ return (
1023
+ <View
1024
+ key={frameIdx}
1025
+ style={{
1026
+ backgroundColor: isTopFrame
1027
+ ? `${AppColors.brandPurple}0A`
1028
+ : AppColors.primaryLight,
1029
+ borderRadius: 10,
1030
+ borderWidth: 1,
1031
+ borderColor: isTopFrame
1032
+ ? `${AppColors.brandPurple}35`
1033
+ : AppColors.dividerColor,
1034
+ padding: 11,
1035
+ gap: 6,
1036
+ }}>
1037
+ {/* Frame Top Row: Origin / Category, File, Line badge, Copy */}
1038
+ <View
1039
+ style={{
1040
+ flexDirection: 'row',
1041
+ alignItems: 'center',
1042
+ justifyContent: 'space-between',
1043
+ gap: 6,
1044
+ }}>
1045
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 5, flex: 1, flexWrap: 'wrap'}}>
1046
+ {isTopFrame && (
1047
+ <View
1048
+ style={{
1049
+ backgroundColor: AppColors.brandPurple,
1050
+ borderRadius: 4,
1051
+ paddingHorizontal: 5,
1052
+ paddingVertical: 1.5,
1053
+ }}>
1054
+ <Text
1055
+ style={{
1056
+ fontFamily: AppFonts.interBold,
1057
+ fontSize: 8.5,
1058
+ color: AppColors.white,
1059
+ }}>
1060
+ {t('console.originBadge')}
1061
+ </Text>
1062
+ </View>
1063
+ )}
1064
+
1065
+ {frame.fileExt && frame.fileExt !== 'other' && (
1066
+ <View
1067
+ style={{
1068
+ backgroundColor:
1069
+ frame.fileExt === 'tsx' || frame.fileExt === 'ts'
1070
+ ? `${AppColors.brandPurple}18`
1071
+ : `${AppColors.teal600}18`,
1072
+ borderRadius: 4,
1073
+ paddingHorizontal: 4.5,
1074
+ paddingVertical: 1.5,
1075
+ }}>
1076
+ <Text
1077
+ style={{
1078
+ fontFamily: AppFonts.interBold,
1079
+ fontSize: 8.5,
1080
+ color:
1081
+ frame.fileExt === 'tsx' || frame.fileExt === 'ts'
1082
+ ? AppColors.brandPurple
1083
+ : AppColors.teal600,
1084
+ }}>
1085
+ {frame.fileExt.toUpperCase()}
1086
+ </Text>
1087
+ </View>
1088
+ )}
1089
+
1090
+ <Text
1091
+ style={{
1092
+ fontFamily: AppFonts.interBold,
1093
+ fontSize: 11.5,
1094
+ color: isTopFrame
1095
+ ? AppColors.brandPurple
1096
+ : AppColors.primaryBlack,
1097
+ }}>
1098
+ {frame.fileName}
1099
+ </Text>
1100
+ </View>
1101
+
1102
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 4}}>
1103
+ {frame.lineNumber && (
1104
+ <View
1105
+ style={{
1106
+ backgroundColor: `${AppColors.teal600}15`,
1107
+ borderColor: `${AppColors.teal600}30`,
1108
+ borderWidth: 1,
1109
+ borderRadius: 4,
1110
+ paddingHorizontal: 5,
1111
+ paddingVertical: 1.5,
1112
+ }}>
1113
+ <Text
1114
+ style={{
1115
+ fontFamily: AppFonts.interBold,
1116
+ fontSize: 9.5,
1117
+ color: AppColors.teal600,
1118
+ }}>
1119
+ L{frame.lineNumber}{frame.columnNumber ? `:C${frame.columnNumber}` : ''}
1120
+ </Text>
1121
+ </View>
1122
+ )}
1123
+ <CopyButton
1124
+ value={frame.copyableLocation}
1125
+ label={frame.fileName}
1126
+ />
1127
+ </View>
1128
+ </View>
1129
+
1130
+ {/* Middle Row: Function / Symbol Invocation */}
1131
+ <View style={{flexDirection: 'row', alignItems: 'center', gap: 5}}>
1132
+ <HighlightText
1133
+ text={frame.functionName !== '<anonymous>' ? `${frame.functionName}()` : '<anonymous>'}
1134
+ search={detailSearch}
1135
+ style={{
1136
+ fontFamily: AppFonts.interBold,
1137
+ fontSize: 12.5,
1138
+ color: AppColors.primaryBlack,
1139
+ }}
1140
+ highlightStyle={{
1141
+ backgroundColor: AppColors.yellowHighlight,
1142
+ color: AppColors.primaryBlack,
1143
+ }}
1144
+ />
1145
+ </View>
1146
+
1147
+ {/* Bottom Row: Clean Relative Path & Execution Scope Tag */}
1148
+ <View
1149
+ style={{
1150
+ flexDirection: 'row',
1151
+ alignItems: 'center',
1152
+ justifyContent: 'space-between',
1153
+ backgroundColor: isDark ? `${AppColors.primaryBlack}25` : AppColors.grayBackground,
1154
+ borderRadius: 6,
1155
+ paddingHorizontal: 8,
1156
+ paddingVertical: 4.5,
1157
+ gap: 6,
1158
+ }}>
1159
+ <Text
1160
+ selectable
1161
+ style={{
1162
+ fontFamily: AppFonts.interRegular,
1163
+ fontSize: 9.5,
1164
+ color: AppColors.grayTextStrong,
1165
+ flex: 1,
1166
+ }}
1167
+ numberOfLines={1}>
1168
+ 📂 {frame.fullPath}
1169
+ </Text>
1170
+
1171
+ <View
1172
+ style={{
1173
+ backgroundColor: frameBadgeBg,
1174
+ borderRadius: 4,
1175
+ paddingHorizontal: 5,
1176
+ paddingVertical: 1.5,
1177
+ }}>
1178
+ <Text
1179
+ style={{
1180
+ fontFamily: AppFonts.interBold,
1181
+ fontSize: 8.5,
1182
+ color: frameBadgeText,
1183
+ }}>
1184
+ {frame.frameType === 'app'
1185
+ ? t('console.appCodeBadge')
1186
+ : frame.frameType === 'dependency'
1187
+ ? t('console.dependencyBadge')
1188
+ : frame.frameType === 'native'
1189
+ ? t('console.nativeBadge')
1190
+ : t('console.hermesVmBadge')}
1191
+ </Text>
1192
+ </View>
1193
+ </View>
1194
+ </View>
1195
+ );
1196
+ })
1197
+ ) : (
1198
+ <Text
1199
+ style={{
1200
+ fontFamily: AppFonts.interRegular,
1201
+ fontSize: 11.5,
1202
+ color: AppColors.grayTextWeak,
1203
+ textAlign: 'center',
1204
+ paddingVertical: 16,
1205
+ }}>
1206
+ {t('console.noStackAvailable')}
1207
+ </Text>
1208
+ )}
1209
+ </View>
1210
+ ) : (
1211
+ /* Raw Full Trace Monospace Block */
1212
+ <View
1213
+ style={{
1214
+ backgroundColor: AppColors.primaryLight,
1215
+ padding: 12,
1216
+ }}>
1217
+ {activeStackString.split('\n').filter(l => l.trim().length > 0).map((line, lineIdx) => (
1218
+ <View
1219
+ key={lineIdx}
1220
+ style={{
1221
+ flexDirection: 'row',
1222
+ alignItems: 'flex-start',
1223
+ paddingVertical: 2,
1224
+ }}>
1225
+ <Text
1226
+ style={{
1227
+ width: 28,
1228
+ textAlign: 'right',
1229
+ paddingRight: 8,
1230
+ fontFamily: AppFonts.interRegular,
1231
+ fontSize: 11,
1232
+ color: AppColors.grayTextWeak,
1233
+ lineHeight: 17,
1234
+ }}>
1235
+ {lineIdx + 1}
1236
+ </Text>
1237
+ <HighlightText
1238
+ text={line.trim()}
1239
+ search={detailSearch}
1240
+ style={{
1241
+ flex: 1,
1242
+ fontFamily: AppFonts.interRegular,
1243
+ fontSize: 12,
1244
+ color: AppColors.primaryBlack,
1245
+ lineHeight: 17,
1246
+ }}
1247
+ highlightStyle={{
1248
+ backgroundColor: AppColors.yellowHighlight,
1249
+ color: AppColors.primaryBlack,
1250
+ }}
1251
+ />
1252
+ </View>
1253
+ ))}
1254
+ </View>
1255
+ )}
1256
+ </View>
1257
+ )}
1258
+
1259
+ {/* Tab 4: Structured Metadata */}
1260
+ {activeTab === 'metadata' && (
1261
+ <View
1262
+ style={[
1263
+ styles.sectionContainer,
1264
+ {
1265
+ backgroundColor: AppColors.primaryLight,
1266
+ borderRadius: 10,
1267
+ borderWidth: 1,
1268
+ borderColor: AppColors.dividerColor,
1269
+ overflow: 'hidden',
1270
+ },
1271
+ ]}>
1272
+ <View
1273
+ style={{
1274
+ flexDirection: 'row',
1275
+ alignItems: 'center',
1276
+ justifyContent: 'space-between',
1277
+ paddingHorizontal: 12,
1278
+ paddingVertical: 9,
1279
+ backgroundColor: AppColors.grayBackground,
1280
+ borderBottomWidth: 1,
1281
+ borderBottomColor: AppColors.dividerColor,
1282
+ }}>
1283
+ <Text
1284
+ style={{
1285
+ fontFamily: AppFonts.interBold,
1286
+ fontSize: 11.5,
1287
+ color: AppColors.purple,
1288
+ textTransform: 'uppercase',
1289
+ letterSpacing: 0.4,
1290
+ }}>
1291
+ Log Properties
1292
+ </Text>
1293
+ <CopyButton
1294
+ value={JSON.stringify(
1295
+ {
1296
+ id: selectedLog.id + 1,
1297
+ type: selectedLog.type,
1298
+ sourceMethod: selectedLog.sourceMethod || 'log',
1299
+ timestamp: selectedLog.timestamp,
1300
+ dateTime: formatDateTime(selectedLog.timestamp),
1301
+ caller: selectedLog.caller,
1302
+ triggerFile: originFrame?.fileName,
1303
+ triggerLine: originFrame?.lineNumber,
1304
+ triggerFunction: originFrame?.functionName,
1305
+ charCount: selectedLog.message.length,
1306
+ size: getSize(selectedLog.message),
1307
+ duplicates: selectedLog.duplicateCount || 1,
1308
+ },
1309
+ null,
1310
+ 2,
1311
+ )}
1312
+ label="Metadata JSON"
1313
+ />
1314
+ </View>
1315
+
1316
+ <View style={{paddingHorizontal: 12, paddingVertical: 4}}>
1317
+ {[
1318
+ {label: 'Log ID', value: `#${selectedLog.id + 1}`},
1319
+ {label: 'Level', value: selectedLog.type.toUpperCase()},
1320
+ {
1321
+ label: 'Source Method',
1322
+ value: `console.${selectedLog.sourceMethod || selectedLog.type || 'log'}`,
1323
+ },
1324
+ {
1325
+ label: 'Triggered At',
1326
+ value: `${formatDateTime(selectedLog.timestamp)} (${getRelativeTime(selectedLog.timestamp)})`,
1327
+ },
1328
+ {label: 'Timestamp (ms)', value: String(selectedLog.timestamp)},
1329
+ {
1330
+ label: 'Trigger File',
1331
+ value: originFrame?.fileName || 'Unknown',
1332
+ },
1333
+ ...(originFrame?.lineNumber
1334
+ ? [
1335
+ {
1336
+ label: 'Trigger Location',
1337
+ value: `Line ${originFrame.lineNumber}, Col ${originFrame.columnNumber || 0}`,
1338
+ },
1339
+ ]
1340
+ : []),
1341
+ ...(originFrame?.functionName && originFrame.functionName !== '<anonymous>'
1342
+ ? [
1343
+ {
1344
+ label: 'Trigger Function',
1345
+ value: `${originFrame.functionName}()`,
1346
+ },
1347
+ ]
1348
+ : []),
1349
+ {
1350
+ label: 'Full Location',
1351
+ value: selectedLog.caller || 'Unknown',
1352
+ },
1353
+ {
1354
+ label: 'Character Length',
1355
+ value: `${selectedLog.message.length} chars`,
1356
+ },
1357
+ {
1358
+ label: 'Approx. Size',
1359
+ value: getSize(selectedLog.message),
1360
+ },
1361
+ {
1362
+ label: 'Duplicate Count',
1363
+ value: String(selectedLog.duplicateCount || 1),
1364
+ },
1365
+ ...(hasMultipleArgs
1366
+ ? [
1367
+ {
1368
+ label: 'Arguments Count',
1369
+ value: `${selectedLog.rawArgs?.length} arguments`,
1370
+ },
1371
+ ]
1372
+ : []),
1373
+ ].map((row, rIdx, arr) => (
1374
+ <View
1375
+ key={rIdx}
1376
+ style={{
1377
+ flexDirection: 'row',
1378
+ justifyContent: 'space-between',
1379
+ alignItems: 'center',
1380
+ paddingVertical: 8,
1381
+ borderBottomWidth: rIdx < arr.length - 1 ? 1 : 0,
1382
+ borderBottomColor: AppColors.grayBorderSecondary,
1383
+ }}>
1384
+ <Text
1385
+ style={{
1386
+ fontFamily: AppFonts.interMedium,
1387
+ fontSize: 11.5,
1388
+ color: AppColors.grayTextWeak,
1389
+ }}>
1390
+ {row.label}
1391
+ </Text>
1392
+ <Text
1393
+ selectable
1394
+ style={{
1395
+ fontFamily: AppFonts.interBold,
1396
+ fontSize: 11.5,
1397
+ color: AppColors.primaryBlack,
1398
+ maxWidth: '60%',
1399
+ textAlign: 'right',
1400
+ }}
1401
+ numberOfLines={2}>
1402
+ {row.value}
1403
+ </Text>
1404
+ </View>
1405
+ ))}
1406
+ </View>
1407
+ </View>
1408
+ )}
1409
+ </ScrollView>
1410
+ </View>
1411
+ );
1412
+ });
1413
+
1414
+ const detailStyles = StyleSheet.create({
1415
+ metaDot: {
1416
+ fontSize: 10,
1417
+ color: AppColors.grayTextWeak,
1418
+ opacity: 0.7,
1419
+ },
1420
+ tabsHeaderWrap: {
1421
+ paddingHorizontal: 12,
1422
+ paddingTop: 8,
1423
+ paddingBottom: 4,
1424
+ },
1425
+ });
1426
+
1427
+ export default LogDetail;