react-native-inapp-inspector 2.2.2 → 2.2.4

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