react-native-inapp-inspector 2.2.2 → 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 (97) hide show
  1. package/dist/commonjs/constants/version.d.ts +1 -1
  2. package/dist/commonjs/constants/version.js +1 -1
  3. package/dist/esm/constants/version.d.ts +1 -1
  4. package/dist/esm/constants/version.js +1 -1
  5. package/package.json +2 -1
  6. package/src/analytics.ts +35 -0
  7. package/src/bundle.ts +25 -0
  8. package/src/components/AnalyticsDetail.tsx +865 -0
  9. package/src/components/AnalyticsEventCard.tsx +441 -0
  10. package/src/components/AnalyticsGraph.tsx +583 -0
  11. package/src/components/AnimatedEntrance.tsx +64 -0
  12. package/src/components/AppHeaderLogo.tsx +109 -0
  13. package/src/components/BrandCircleIcon.tsx +144 -0
  14. package/src/components/BrandSquareIcon.tsx +144 -0
  15. package/src/components/CodeSnippet.tsx +725 -0
  16. package/src/components/ConsoleLogCard.tsx +628 -0
  17. package/src/components/CopyButton.tsx +87 -0
  18. package/src/components/DiffViewer.tsx +82 -0
  19. package/src/components/DomainHeader.tsx +237 -0
  20. package/src/components/EmptyState.tsx +73 -0
  21. package/src/components/EndOfListFooter.tsx +100 -0
  22. package/src/components/ErrorBoundary.tsx +688 -0
  23. package/src/components/HeadersSection.tsx +192 -0
  24. package/src/components/HighlightText.tsx +100 -0
  25. package/src/components/Inspector/AnalyticsFilterModal.tsx +1250 -0
  26. package/src/components/Inspector/AnalyticsTab.tsx +1336 -0
  27. package/src/components/Inspector/BundleTab.tsx +4731 -0
  28. package/src/components/Inspector/ConsoleTab.tsx +702 -0
  29. package/src/components/Inspector/CrashDetail.tsx +941 -0
  30. package/src/components/Inspector/CrashFilterModal.tsx +721 -0
  31. package/src/components/Inspector/CrashTab.tsx +871 -0
  32. package/src/components/Inspector/FabLauncher.tsx +80 -0
  33. package/src/components/Inspector/InspectorContext.tsx +28 -0
  34. package/src/components/Inspector/InspectorHeader.tsx +973 -0
  35. package/src/components/Inspector/LogDetail.tsx +1427 -0
  36. package/src/components/Inspector/MainScreen.tsx +258 -0
  37. package/src/components/Inspector/NavigationTracker.tsx +13 -0
  38. package/src/components/Inspector/NetworkDetail.tsx +794 -0
  39. package/src/components/Inspector/NetworkTab.tsx +1095 -0
  40. package/src/components/Inspector/NpmUpdateToast.tsx +392 -0
  41. package/src/components/Inspector/PerformanceTab.tsx +1894 -0
  42. package/src/components/Inspector/ReduxDetail.tsx +1651 -0
  43. package/src/components/Inspector/ReduxTab.tsx +954 -0
  44. package/src/components/Inspector/SettingsPanel.tsx +3181 -0
  45. package/src/components/Inspector/TabBar.tsx +187 -0
  46. package/src/components/Inspector/TelemetryConsentModal.tsx +392 -0
  47. package/src/components/Inspector/UpdateAvailableModal.tsx +545 -0
  48. package/src/components/JsonViewer.tsx +486 -0
  49. package/src/components/LogCard.tsx +491 -0
  50. package/src/components/LogSyntaxHighlighter.tsx +178 -0
  51. package/src/components/MetaAccordion.tsx +340 -0
  52. package/src/components/MiniBarChart.tsx +42 -0
  53. package/src/components/MiniLineChart.tsx +33 -0
  54. package/src/components/NetworkIcons.tsx +2118 -0
  55. package/src/components/SectionHeader.tsx +113 -0
  56. package/src/components/SegmentedTabs.tsx +83 -0
  57. package/src/components/Slider.tsx +300 -0
  58. package/src/components/SourcePageCard.tsx +148 -0
  59. package/src/components/Toast.tsx +131 -0
  60. package/src/components/TouchableScale.tsx +91 -0
  61. package/src/components/TreeNode.tsx +186 -0
  62. package/src/console.ts +24 -0
  63. package/src/constants/index.ts +38 -0
  64. package/src/constants/version.ts +3 -0
  65. package/src/crash.ts +32 -0
  66. package/src/customHooks/analyticsLogger.ts +336 -0
  67. package/src/customHooks/bundleAnalyzer.ts +1231 -0
  68. package/src/customHooks/consoleLogger.ts +497 -0
  69. package/src/customHooks/crashHandler.ts +944 -0
  70. package/src/customHooks/logFilters.ts +32 -0
  71. package/src/customHooks/networkLogger.ts +419 -0
  72. package/src/customHooks/performanceTracker.ts +1014 -0
  73. package/src/customHooks/reduxLogger.ts +406 -0
  74. package/src/customHooks/useAccordion.tsx +60 -0
  75. package/src/decorators/index.ts +184 -0
  76. package/src/helpers/gaAnalyticsRegistry.ts +204 -0
  77. package/src/helpers/index.ts +860 -0
  78. package/src/helpers/memoryManager.ts +138 -0
  79. package/src/helpers/searchQueryParser.ts +283 -0
  80. package/src/helpers/settingsStore.ts +171 -0
  81. package/src/helpers/telemetry.ts +505 -0
  82. package/src/helpers/toast.ts +17 -0
  83. package/src/i18n/index.ts +69 -0
  84. package/src/i18n/locales/en.json +1052 -0
  85. package/src/index.tsx +2336 -0
  86. package/src/native/NativeInspector.ts +397 -0
  87. package/src/native/NativeNetworkInspector.ts +24 -0
  88. package/src/network.ts +22 -0
  89. package/src/performance.ts +38 -0
  90. package/src/redux.ts +23 -0
  91. package/src/styles/AppColors.ts +408 -0
  92. package/src/styles/AppFonts.ts +8 -0
  93. package/src/styles/common.ts +209 -0
  94. package/src/styles/index.ts +1570 -0
  95. package/src/types/enums.ts +194 -0
  96. package/src/types/index.ts +34 -0
  97. 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;