react-native-inapp-inspector 1.1.13 → 1.1.15

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 (250) hide show
  1. package/README.md +9 -34
  2. package/dist/commonjs/assets/svgAssets.d.ts +3 -0
  3. package/dist/commonjs/assets/svgAssets.js +17 -0
  4. package/dist/commonjs/components/AnalyticsEventCard.d.ts +1 -14
  5. package/dist/commonjs/components/AnalyticsEventCard.js +120 -50
  6. package/dist/commonjs/components/AnalyticsGraph.d.ts +2 -6
  7. package/dist/commonjs/components/AnalyticsGraph.js +8 -8
  8. package/dist/commonjs/components/AnalyticsTabView.d.ts +15 -0
  9. package/dist/commonjs/components/AnalyticsTabView.js +157 -0
  10. package/dist/commonjs/components/AnimatedEntrance.d.ts +1 -1
  11. package/dist/commonjs/components/AnimatedEntrance.js +2 -2
  12. package/dist/commonjs/components/ApiDetailView.d.ts +22 -0
  13. package/dist/commonjs/components/ApiDetailView.js +304 -0
  14. package/dist/commonjs/components/ApisTabView.d.ts +33 -0
  15. package/dist/commonjs/components/ApisTabView.js +235 -0
  16. package/dist/commonjs/components/CodeSnippet.d.ts +1 -5
  17. package/dist/commonjs/components/CodeSnippet.js +34 -34
  18. package/dist/commonjs/components/ConsoleLogCard.d.ts +1 -8
  19. package/dist/commonjs/components/ConsoleLogCard.js +262 -442
  20. package/dist/commonjs/components/ConsoleLogDetailView.d.ts +9 -0
  21. package/dist/commonjs/components/ConsoleLogDetailView.js +278 -0
  22. package/dist/commonjs/components/ConsoleLogsTabView.d.ts +24 -0
  23. package/dist/commonjs/components/ConsoleLogsTabView.js +354 -0
  24. package/dist/commonjs/components/DiffViewer.js +4 -2
  25. package/dist/commonjs/components/DomainHeader.js +66 -41
  26. package/dist/commonjs/components/EmptyState.d.ts +2 -2
  27. package/dist/commonjs/components/EmptyState.js +2 -2
  28. package/dist/commonjs/components/EndOfListFooter.d.ts +8 -0
  29. package/dist/commonjs/components/EndOfListFooter.js +90 -0
  30. package/dist/commonjs/components/ErrorBoundary.d.ts +5 -14
  31. package/dist/commonjs/components/ErrorBoundary.js +28 -28
  32. package/dist/commonjs/components/FloatingBubble.d.ts +14 -0
  33. package/dist/commonjs/components/FloatingBubble.js +75 -0
  34. package/dist/commonjs/components/HeadersSection.js +5 -3
  35. package/dist/commonjs/components/HighlightText.d.ts +1 -1
  36. package/dist/commonjs/components/HighlightText.js +5 -7
  37. package/dist/commonjs/components/Inspector/AnalyticsTab.d.ts +3 -0
  38. package/dist/commonjs/components/Inspector/AnalyticsTab.js +232 -0
  39. package/dist/commonjs/components/Inspector/ConsoleTab.d.ts +3 -0
  40. package/dist/commonjs/components/Inspector/ConsoleTab.js +410 -0
  41. package/dist/commonjs/components/Inspector/FabLauncher.d.ts +3 -0
  42. package/dist/commonjs/components/Inspector/FabLauncher.js +54 -0
  43. package/dist/commonjs/components/Inspector/InsightsDashboard.d.ts +3 -0
  44. package/dist/commonjs/components/Inspector/InsightsDashboard.js +619 -0
  45. package/dist/commonjs/components/Inspector/InspectorContext.d.ts +5 -0
  46. package/dist/commonjs/components/Inspector/InspectorContext.js +18 -0
  47. package/dist/commonjs/components/Inspector/InspectorHeader.d.ts +3 -0
  48. package/dist/commonjs/components/Inspector/InspectorHeader.js +452 -0
  49. package/dist/commonjs/components/Inspector/LogDetail.d.ts +3 -0
  50. package/dist/commonjs/components/Inspector/LogDetail.js +963 -0
  51. package/dist/commonjs/components/Inspector/MainScreen.d.ts +3 -0
  52. package/dist/commonjs/components/Inspector/MainScreen.js +68 -0
  53. package/dist/commonjs/components/Inspector/NavigationTracker.d.ts +3 -0
  54. package/dist/commonjs/components/Inspector/NavigationTracker.js +12 -0
  55. package/dist/commonjs/components/Inspector/NetworkDetail.d.ts +3 -0
  56. package/dist/commonjs/components/Inspector/NetworkDetail.js +405 -0
  57. package/dist/commonjs/components/Inspector/NetworkTab.d.ts +3 -0
  58. package/dist/commonjs/components/Inspector/NetworkTab.js +281 -0
  59. package/dist/commonjs/components/Inspector/ReduxTab.d.ts +3 -0
  60. package/dist/commonjs/components/Inspector/ReduxTab.js +75 -0
  61. package/dist/commonjs/components/Inspector/SettingsPanel.d.ts +3 -0
  62. package/dist/commonjs/components/Inspector/SettingsPanel.js +1476 -0
  63. package/dist/commonjs/components/Inspector/TabBar.d.ts +3 -0
  64. package/dist/commonjs/components/Inspector/TabBar.js +95 -0
  65. package/dist/commonjs/components/Inspector/WebViewTab.d.ts +3 -0
  66. package/dist/commonjs/components/Inspector/WebViewTab.js +757 -0
  67. package/dist/commonjs/components/InspectorHeader.d.ts +19 -0
  68. package/dist/commonjs/components/InspectorHeader.js +329 -0
  69. package/dist/commonjs/components/InspectorTabBar.d.ts +16 -0
  70. package/dist/commonjs/components/InspectorTabBar.js +90 -0
  71. package/dist/commonjs/components/JsonViewer.d.ts +4 -1
  72. package/dist/commonjs/components/JsonViewer.js +110 -171
  73. package/dist/commonjs/components/LogCard.js +109 -86
  74. package/dist/commonjs/components/MetaAccordion.d.ts +1 -10
  75. package/dist/commonjs/components/MetaAccordion.js +23 -21
  76. package/dist/commonjs/components/NavigationTracker.d.ts +7 -0
  77. package/dist/commonjs/components/NavigationTracker.js +76 -0
  78. package/dist/commonjs/components/NetworkIcons.d.ts +6 -0
  79. package/dist/commonjs/components/NetworkIcons.js +54 -8
  80. package/dist/commonjs/components/ReduxTabView.d.ts +9 -0
  81. package/dist/commonjs/components/ReduxTabView.js +56 -0
  82. package/dist/commonjs/components/ReduxTreeView.js +241 -200
  83. package/dist/commonjs/components/SegmentedTabs.d.ts +15 -0
  84. package/dist/commonjs/components/SegmentedTabs.js +52 -0
  85. package/dist/commonjs/components/SettingsView.d.ts +13 -0
  86. package/dist/commonjs/components/SettingsView.js +328 -0
  87. package/dist/commonjs/components/TouchableScale.d.ts +2 -2
  88. package/dist/commonjs/components/TouchableScale.js +2 -2
  89. package/dist/commonjs/components/TreeNode.js +2 -2
  90. package/dist/commonjs/constants/index.js +1 -12
  91. package/dist/commonjs/constants/version.d.ts +1 -1
  92. package/dist/commonjs/constants/version.js +1 -1
  93. package/dist/commonjs/customHooks/analyticsLogger.d.ts +8 -0
  94. package/dist/commonjs/customHooks/analyticsLogger.js +42 -1
  95. package/dist/commonjs/customHooks/consoleLogger.js +137 -13
  96. package/dist/commonjs/customHooks/networkLogger.d.ts +0 -1
  97. package/dist/commonjs/customHooks/networkLogger.js +0 -1
  98. package/dist/commonjs/customHooks/reduxLogger.d.ts +2 -9
  99. package/dist/commonjs/enums/index.d.ts +39 -0
  100. package/dist/commonjs/enums/index.js +50 -0
  101. package/dist/commonjs/helpers/index.d.ts +27 -1
  102. package/dist/commonjs/helpers/index.js +187 -12
  103. package/dist/commonjs/helpers/settingsStore.d.ts +2 -24
  104. package/dist/commonjs/i18n/en.json +144 -0
  105. package/dist/commonjs/i18n/index.d.ts +2 -0
  106. package/dist/commonjs/i18n/index.js +27 -0
  107. package/dist/commonjs/i18n/locales/en.json +314 -0
  108. package/dist/commonjs/index.d.ts +2 -3
  109. package/dist/commonjs/index.js +207 -4927
  110. package/dist/commonjs/styles/AppColors.d.ts +148 -0
  111. package/dist/commonjs/styles/AppColors.js +176 -0
  112. package/dist/commonjs/styles/common.d.ts +3 -0
  113. package/dist/commonjs/styles/common.js +201 -0
  114. package/dist/commonjs/styles/index.d.ts +219 -435
  115. package/dist/commonjs/styles/index.js +312 -488
  116. package/dist/commonjs/types/enums.d.ts +9 -0
  117. package/dist/commonjs/types/enums.js +4 -0
  118. package/dist/commonjs/types/index.d.ts +4 -111
  119. package/dist/commonjs/types/index.js +16 -0
  120. package/dist/commonjs/types/interfaces.d.ts +337 -0
  121. package/dist/commonjs/types/interfaces.js +2 -0
  122. package/dist/esm/assets/svgAssets.d.ts +3 -0
  123. package/dist/esm/assets/svgAssets.js +14 -0
  124. package/dist/esm/components/AnalyticsEventCard.d.ts +1 -14
  125. package/dist/esm/components/AnalyticsEventCard.js +118 -48
  126. package/dist/esm/components/AnalyticsGraph.d.ts +2 -6
  127. package/dist/esm/components/AnalyticsGraph.js +8 -8
  128. package/dist/esm/components/AnalyticsTabView.d.ts +15 -0
  129. package/dist/esm/components/AnalyticsTabView.js +151 -0
  130. package/dist/esm/components/AnimatedEntrance.d.ts +1 -1
  131. package/dist/esm/components/AnimatedEntrance.js +2 -2
  132. package/dist/esm/components/ApiDetailView.d.ts +22 -0
  133. package/dist/esm/components/ApiDetailView.js +264 -0
  134. package/dist/esm/components/ApisTabView.d.ts +33 -0
  135. package/dist/esm/components/ApisTabView.js +196 -0
  136. package/dist/esm/components/CodeSnippet.d.ts +1 -5
  137. package/dist/esm/components/CodeSnippet.js +34 -34
  138. package/dist/esm/components/ConsoleLogCard.d.ts +1 -8
  139. package/dist/esm/components/ConsoleLogCard.js +262 -409
  140. package/dist/esm/components/ConsoleLogDetailView.d.ts +9 -0
  141. package/dist/esm/components/ConsoleLogDetailView.js +271 -0
  142. package/dist/esm/components/ConsoleLogsTabView.d.ts +24 -0
  143. package/dist/esm/components/ConsoleLogsTabView.js +348 -0
  144. package/dist/esm/components/DiffViewer.js +4 -2
  145. package/dist/esm/components/DomainHeader.js +66 -41
  146. package/dist/esm/components/EmptyState.d.ts +2 -2
  147. package/dist/esm/components/EmptyState.js +2 -2
  148. package/dist/esm/components/EndOfListFooter.d.ts +8 -0
  149. package/dist/esm/components/EndOfListFooter.js +83 -0
  150. package/dist/esm/components/ErrorBoundary.d.ts +5 -14
  151. package/dist/esm/components/ErrorBoundary.js +29 -29
  152. package/dist/esm/components/FloatingBubble.d.ts +14 -0
  153. package/dist/esm/components/FloatingBubble.js +68 -0
  154. package/dist/esm/components/HeadersSection.js +5 -3
  155. package/dist/esm/components/HighlightText.d.ts +1 -1
  156. package/dist/esm/components/HighlightText.js +5 -7
  157. package/dist/esm/components/Inspector/AnalyticsTab.d.ts +3 -0
  158. package/dist/esm/components/Inspector/AnalyticsTab.js +194 -0
  159. package/dist/esm/components/Inspector/ConsoleTab.d.ts +3 -0
  160. package/dist/esm/components/Inspector/ConsoleTab.js +372 -0
  161. package/dist/esm/components/Inspector/FabLauncher.d.ts +3 -0
  162. package/dist/esm/components/Inspector/FabLauncher.js +49 -0
  163. package/dist/esm/components/Inspector/InsightsDashboard.d.ts +3 -0
  164. package/dist/esm/components/Inspector/InsightsDashboard.js +614 -0
  165. package/dist/esm/components/Inspector/InspectorContext.d.ts +5 -0
  166. package/dist/esm/components/Inspector/InspectorContext.js +13 -0
  167. package/dist/esm/components/Inspector/InspectorHeader.d.ts +3 -0
  168. package/dist/esm/components/Inspector/InspectorHeader.js +447 -0
  169. package/dist/esm/components/Inspector/LogDetail.d.ts +3 -0
  170. package/dist/esm/components/Inspector/LogDetail.js +925 -0
  171. package/dist/esm/components/Inspector/MainScreen.d.ts +3 -0
  172. package/dist/esm/components/Inspector/MainScreen.js +63 -0
  173. package/dist/esm/components/Inspector/NavigationTracker.d.ts +3 -0
  174. package/dist/esm/components/Inspector/NavigationTracker.js +10 -0
  175. package/dist/esm/components/Inspector/NetworkDetail.d.ts +3 -0
  176. package/dist/esm/components/Inspector/NetworkDetail.js +367 -0
  177. package/dist/esm/components/Inspector/NetworkTab.d.ts +3 -0
  178. package/dist/esm/components/Inspector/NetworkTab.js +243 -0
  179. package/dist/esm/components/Inspector/ReduxTab.d.ts +3 -0
  180. package/dist/esm/components/Inspector/ReduxTab.js +70 -0
  181. package/dist/esm/components/Inspector/SettingsPanel.d.ts +3 -0
  182. package/dist/esm/components/Inspector/SettingsPanel.js +1438 -0
  183. package/dist/esm/components/Inspector/TabBar.d.ts +3 -0
  184. package/dist/esm/components/Inspector/TabBar.js +90 -0
  185. package/dist/esm/components/Inspector/WebViewTab.d.ts +3 -0
  186. package/dist/esm/components/Inspector/WebViewTab.js +752 -0
  187. package/dist/esm/components/InspectorHeader.d.ts +19 -0
  188. package/dist/esm/components/InspectorHeader.js +323 -0
  189. package/dist/esm/components/InspectorTabBar.d.ts +16 -0
  190. package/dist/esm/components/InspectorTabBar.js +84 -0
  191. package/dist/esm/components/JsonViewer.d.ts +4 -1
  192. package/dist/esm/components/JsonViewer.js +111 -172
  193. package/dist/esm/components/LogCard.js +111 -88
  194. package/dist/esm/components/MetaAccordion.d.ts +1 -10
  195. package/dist/esm/components/MetaAccordion.js +23 -21
  196. package/dist/esm/components/NavigationTracker.d.ts +7 -0
  197. package/dist/esm/components/NavigationTracker.js +39 -0
  198. package/dist/esm/components/NetworkIcons.d.ts +6 -0
  199. package/dist/esm/components/NetworkIcons.js +46 -6
  200. package/dist/esm/components/ReduxTabView.d.ts +9 -0
  201. package/dist/esm/components/ReduxTabView.js +50 -0
  202. package/dist/esm/components/ReduxTreeView.js +243 -202
  203. package/dist/esm/components/SegmentedTabs.d.ts +15 -0
  204. package/dist/esm/components/SegmentedTabs.js +47 -0
  205. package/dist/esm/components/SettingsView.d.ts +13 -0
  206. package/dist/esm/components/SettingsView.js +321 -0
  207. package/dist/esm/components/TouchableScale.d.ts +2 -2
  208. package/dist/esm/components/TouchableScale.js +2 -2
  209. package/dist/esm/components/TreeNode.js +2 -2
  210. package/dist/esm/constants/index.js +1 -12
  211. package/dist/esm/constants/version.d.ts +1 -1
  212. package/dist/esm/constants/version.js +1 -1
  213. package/dist/esm/customHooks/analyticsLogger.d.ts +8 -0
  214. package/dist/esm/customHooks/analyticsLogger.js +37 -0
  215. package/dist/esm/customHooks/consoleLogger.js +137 -13
  216. package/dist/esm/customHooks/networkLogger.d.ts +0 -1
  217. package/dist/esm/customHooks/networkLogger.js +0 -1
  218. package/dist/esm/customHooks/reduxLogger.d.ts +2 -9
  219. package/dist/esm/enums/index.d.ts +39 -0
  220. package/dist/esm/enums/index.js +47 -0
  221. package/dist/esm/helpers/index.d.ts +27 -1
  222. package/dist/esm/helpers/index.js +180 -11
  223. package/dist/esm/helpers/settingsStore.d.ts +2 -24
  224. package/dist/esm/i18n/en.json +144 -0
  225. package/dist/esm/i18n/index.d.ts +2 -0
  226. package/dist/esm/i18n/index.js +22 -0
  227. package/dist/esm/i18n/locales/en.json +314 -0
  228. package/dist/esm/index.d.ts +2 -3
  229. package/dist/esm/index.js +207 -4923
  230. package/dist/esm/styles/AppColors.d.ts +148 -0
  231. package/dist/esm/styles/AppColors.js +176 -0
  232. package/dist/esm/styles/common.d.ts +3 -0
  233. package/dist/esm/styles/common.js +197 -0
  234. package/dist/esm/styles/index.d.ts +219 -435
  235. package/dist/esm/styles/index.js +312 -488
  236. package/dist/esm/types/enums.d.ts +9 -0
  237. package/dist/esm/types/enums.js +3 -0
  238. package/dist/esm/types/index.d.ts +4 -111
  239. package/dist/esm/types/index.js +2 -1
  240. package/dist/esm/types/interfaces.d.ts +337 -0
  241. package/dist/esm/types/interfaces.js +1 -0
  242. package/example/App.tsx +88 -1
  243. package/example/android/app/build.gradle +29 -1
  244. package/example/android/app/src/main/java/com/example/MainApplication.kt +25 -0
  245. package/example/android/build.gradle +31 -4
  246. package/example/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  247. package/example/android/gradle.properties +1 -0
  248. package/example/android/settings.gradle +20 -1
  249. package/example/package-lock.json +1 -1
  250. package/package.json +3 -1
@@ -0,0 +1,925 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Pressable, ScrollView, StyleSheet, Text, TextInput, View, } from 'react-native';
4
+ import { useInspector } from './InspectorContext';
5
+ import HighlightText from '../HighlightText';
6
+ import JsonViewer from '../JsonViewer';
7
+ import CopyButton from '../CopyButton';
8
+ import SegmentedTabs from '../SegmentedTabs';
9
+ import { ClearIcon, SearchIcon, PrettyIcon, RawIcon, TableIcon, LayersIcon, TerminalIcon, RequestIcon, InfoCircleIcon, } from '../NetworkIcons';
10
+ import { AppColors } from '../../styles/AppColors';
11
+ import { AppFonts } from '../../styles/AppFonts';
12
+ import styles from '../../styles';
13
+ import { getJsonContent, getSize, formatDateTime, parseStackLine, } from '../../helpers';
14
+ const getRelativeTime = (timestamp) => {
15
+ const now = Date.now();
16
+ const diff = now - timestamp;
17
+ const seconds = Math.floor(diff / 1000);
18
+ const minutes = Math.floor(seconds / 60);
19
+ const hours = Math.floor(minutes / 60);
20
+ const days = Math.floor(hours / 24);
21
+ if (seconds < 60)
22
+ return `${seconds}s ago`;
23
+ if (minutes < 60)
24
+ return `${minutes}m ago`;
25
+ if (hours < 24)
26
+ return `${hours}h ago`;
27
+ return `${days}d ago`;
28
+ };
29
+ const LogDetail = () => {
30
+ const { t } = useTranslation();
31
+ const { selectedLog } = useInspector();
32
+ const [activeTab, setActiveTab] = useState('output');
33
+ const [detailSearch, setDetailSearch] = useState('');
34
+ const [viewMode, setViewMode] = useState('pretty');
35
+ const jsonContent = useMemo(() => (selectedLog ? getJsonContent(selectedLog.message) : null), [selectedLog]);
36
+ const [stackViewMode, setStackViewMode] = useState('structured');
37
+ const [stackSource, setStackSource] = useState('caller');
38
+ const activeStackString = useMemo(() => {
39
+ if (!selectedLog)
40
+ return '';
41
+ if (stackSource === 'error' && selectedLog.errorStack) {
42
+ return selectedLog.errorStack;
43
+ }
44
+ return selectedLog.stack || selectedLog.caller || '';
45
+ }, [selectedLog, stackSource]);
46
+ const originFrame = useMemo(() => {
47
+ if (!selectedLog)
48
+ return null;
49
+ if (selectedLog.caller && selectedLog.caller !== 'Unknown') {
50
+ return parseStackLine(selectedLog.caller, true);
51
+ }
52
+ if (selectedLog.stack) {
53
+ const firstLine = selectedLog.stack
54
+ .split('\n')
55
+ .find(l => l.trim().length > 0);
56
+ if (firstLine)
57
+ return parseStackLine(firstLine, true);
58
+ }
59
+ return null;
60
+ }, [selectedLog]);
61
+ const parsedStackFrames = useMemo(() => {
62
+ if (!activeStackString)
63
+ return [];
64
+ return activeStackString
65
+ .split('\n')
66
+ .map(l => l.trim())
67
+ .filter(l => l.length > 0)
68
+ .map((line, idx) => parseStackLine(line, idx === 0));
69
+ }, [activeStackString]);
70
+ if (!selectedLog)
71
+ return null;
72
+ const isDark = AppColors.primaryLight !== AppColors.white;
73
+ const getTypeColors = () => {
74
+ if (selectedLog.type === 'error') {
75
+ return {
76
+ border: AppColors.errorColor,
77
+ badgeBg: `${AppColors.errorColor}15`,
78
+ badgeText: AppColors.errorColor,
79
+ label: 'ERROR',
80
+ };
81
+ }
82
+ if (selectedLog.type === 'warn') {
83
+ return {
84
+ border: AppColors.lightOrange,
85
+ badgeBg: `${AppColors.lightOrange}15`,
86
+ badgeText: AppColors.darkOrange || AppColors.lightOrange,
87
+ label: 'WARN',
88
+ };
89
+ }
90
+ return {
91
+ border: AppColors.purple,
92
+ badgeBg: `${AppColors.purple}15`,
93
+ badgeText: AppColors.purple,
94
+ label: 'INFO',
95
+ };
96
+ };
97
+ const typeColors = getTypeColors();
98
+ const hasMultipleArgs = selectedLog.rawArgs != null && selectedLog.rawArgs.length > 1;
99
+ const hasStack = Boolean(selectedLog.stack && selectedLog.stack.trim().length > 0);
100
+ const subTabs = [
101
+ {
102
+ key: 'output',
103
+ label: 'Output',
104
+ icon: (isActive) => (<TerminalIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
105
+ },
106
+ ...(hasMultipleArgs
107
+ ? [
108
+ {
109
+ key: 'arguments',
110
+ label: `Args (${selectedLog.rawArgs?.length})`,
111
+ icon: (isActive) => (<RequestIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
112
+ },
113
+ ]
114
+ : []),
115
+ ...(hasStack
116
+ ? [
117
+ {
118
+ key: 'stack',
119
+ label: 'Stack Trace',
120
+ icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
121
+ },
122
+ ]
123
+ : []),
124
+ {
125
+ key: 'metadata',
126
+ label: 'Metadata',
127
+ icon: (isActive) => (<InfoCircleIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
128
+ },
129
+ ];
130
+ return (<View style={{ flex: 1 }}>
131
+ {/* Enhanced details header bar */}
132
+ <View style={styles.detailInfoBar}>
133
+ <View style={{ flexDirection: 'column', gap: 10 }}>
134
+ {/* Top Row: Type badge, method, content pill & Copy button */}
135
+ <View style={{
136
+ flexDirection: 'row',
137
+ alignItems: 'center',
138
+ justifyContent: 'space-between',
139
+ gap: 8,
140
+ }}>
141
+ <View style={{
142
+ flexDirection: 'row',
143
+ alignItems: 'center',
144
+ gap: 6,
145
+ flex: 1,
146
+ flexWrap: 'wrap',
147
+ }}>
148
+ {/* Type Badge */}
149
+ <View style={[
150
+ styles.typeChip,
151
+ {
152
+ backgroundColor: typeColors.badgeBg,
153
+ borderWidth: 1,
154
+ borderColor: `${typeColors.border}40`,
155
+ paddingHorizontal: 8,
156
+ paddingVertical: 3,
157
+ },
158
+ ]}>
159
+ <View style={[
160
+ styles.typeDot,
161
+ { backgroundColor: typeColors.badgeText },
162
+ ]}/>
163
+ <Text style={[
164
+ styles.typeChipText,
165
+ { color: typeColors.badgeText, fontSize: 10 },
166
+ ]}>
167
+ {typeColors.label}
168
+ </Text>
169
+ </View>
170
+
171
+ {/* Method chip */}
172
+ <View style={[
173
+ styles.metaChip,
174
+ {
175
+ backgroundColor: `${AppColors.brandPurple}12`,
176
+ borderColor: `${AppColors.brandPurple}30`,
177
+ },
178
+ ]}>
179
+ <Text style={[
180
+ styles.metaChipText,
181
+ { color: AppColors.brandPurple, fontSize: 10 },
182
+ ]}>
183
+ console.
184
+ {selectedLog.sourceMethod || selectedLog.type || 'log'}
185
+ </Text>
186
+ </View>
187
+
188
+ {/* JSON content type chip */}
189
+ {jsonContent && (<View style={[
190
+ styles.metaChip,
191
+ {
192
+ backgroundColor: `${AppColors.teal600}12`,
193
+ borderColor: `${AppColors.teal600}2B`,
194
+ },
195
+ ]}>
196
+ <Text style={[
197
+ styles.metaChipText,
198
+ { color: AppColors.teal600, fontSize: 10 },
199
+ ]}>
200
+ {Array.isArray(jsonContent.data)
201
+ ? `Array[${jsonContent.data.length}]`
202
+ : `Object{${Object.keys(jsonContent.data).length}}`}
203
+ </Text>
204
+ </View>)}
205
+
206
+ {/* Duplicate count chip */}
207
+ {selectedLog.duplicateCount != null &&
208
+ selectedLog.duplicateCount > 1 && (<View style={[
209
+ styles.metaChip,
210
+ {
211
+ backgroundColor: `${AppColors.purple}1A`,
212
+ borderColor: `${AppColors.purple}3D`,
213
+ },
214
+ ]}>
215
+ <Text style={[
216
+ styles.metaChipText,
217
+ { color: AppColors.purple, fontWeight: '700' },
218
+ ]}>
219
+ {t('console.duplicates', {
220
+ count: selectedLog.duplicateCount,
221
+ })}
222
+ </Text>
223
+ </View>)}
224
+ </View>
225
+
226
+ {/* Quick Copy button */}
227
+ <CopyButton value={selectedLog.message} label={t('console.logMessage')}/>
228
+ </View>
229
+
230
+ {/* Caller & Trace origin info */}
231
+ <View style={{ gap: 4 }}>
232
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
233
+ {originFrame?.fileExt && originFrame.fileExt !== 'other' && (<View style={{
234
+ backgroundColor: originFrame.fileExt === 'tsx' || originFrame.fileExt === 'ts'
235
+ ? AppColors.brandPurple
236
+ : AppColors.teal600,
237
+ borderRadius: 4,
238
+ paddingHorizontal: 5,
239
+ paddingVertical: 1.5,
240
+ }}>
241
+ <Text style={{
242
+ fontFamily: AppFonts.interBold,
243
+ fontSize: 9.5,
244
+ color: AppColors.white,
245
+ letterSpacing: 0.5,
246
+ }}>
247
+ {originFrame.fileExt.toUpperCase()}
248
+ </Text>
249
+ </View>)}
250
+
251
+ <Text selectable style={{
252
+ fontFamily: AppFonts.interBold,
253
+ fontSize: 13,
254
+ color: AppColors.primaryBlack,
255
+ lineHeight: 18,
256
+ }} numberOfLines={1}>
257
+ {originFrame?.functionName && originFrame.functionName !== '<anonymous>'
258
+ ? `${originFrame.functionName}()`
259
+ : originFrame?.fileName || t('console.consoleLog')}
260
+ </Text>
261
+
262
+ {originFrame?.fileName && (<View style={[
263
+ styles.metaChip,
264
+ {
265
+ backgroundColor: `${AppColors.brandPurple}12`,
266
+ borderColor: `${AppColors.brandPurple}30`,
267
+ paddingHorizontal: 7,
268
+ paddingVertical: 2,
269
+ },
270
+ ]}>
271
+ <Text style={[
272
+ styles.metaChipText,
273
+ { color: AppColors.brandPurple, fontSize: 10, fontFamily: AppFonts.interBold },
274
+ ]}>
275
+ 📄 {originFrame.fileName}
276
+ {originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
277
+ {originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
278
+ </Text>
279
+ </View>)}
280
+ </View>
281
+
282
+ {/* Enhanced metadata metrics row */}
283
+ <View style={{
284
+ flexDirection: 'row',
285
+ alignItems: 'center',
286
+ gap: 8,
287
+ flexWrap: 'wrap',
288
+ }}>
289
+ <Text style={{
290
+ fontFamily: AppFonts.interBold,
291
+ fontSize: 10,
292
+ color: AppColors.grayTextWeak,
293
+ }}>
294
+ #{selectedLog.id + 1}
295
+ </Text>
296
+ <Text style={detailStyles.metaDot}>•</Text>
297
+ <Text style={{
298
+ fontFamily: AppFonts.interRegular,
299
+ fontSize: 10,
300
+ color: AppColors.grayTextWeak,
301
+ }}>
302
+ {formatDateTime(selectedLog.timestamp)} ({getRelativeTime(selectedLog.timestamp)})
303
+ </Text>
304
+ <Text style={detailStyles.metaDot}>•</Text>
305
+ <Text style={{
306
+ fontFamily: AppFonts.interRegular,
307
+ fontSize: 10,
308
+ color: AppColors.grayTextWeak,
309
+ }}>
310
+ {getSize(selectedLog.message)}
311
+ </Text>
312
+ </View>
313
+ </View>
314
+ </View>
315
+ </View>
316
+
317
+ {/* Sub-tabs header */}
318
+ <View style={detailStyles.tabsHeaderWrap}>
319
+ <SegmentedTabs tabs={subTabs} activeKey={activeTab} onChange={key => setActiveTab(key)}/>
320
+ </View>
321
+
322
+ {/* Search row (only for output/arguments/stack tabs) */}
323
+ {activeTab !== 'metadata' && (<View style={{ paddingHorizontal: 12, paddingTop: 6, paddingBottom: 4 }}>
324
+ <View style={[styles.detailSearchRow, { marginBottom: 0 }]}>
325
+ <View style={[
326
+ styles.detailSearchBox,
327
+ {
328
+ backgroundColor: isDark
329
+ ? `${AppColors.brandPurple}10`
330
+ : AppColors.purpleShade50,
331
+ borderWidth: 1,
332
+ borderColor: AppColors.dividerColor,
333
+ },
334
+ ]}>
335
+ <SearchIcon color={AppColors.grayTextWeak} size={15}/>
336
+ <TextInput placeholder="Search in log details..." placeholderTextColor={AppColors.grayTextWeak} value={detailSearch} onChangeText={setDetailSearch} style={styles.detailSearchInput} autoCorrect={false} autoCapitalize="none"/>
337
+ {detailSearch.length > 0 && (<Pressable onPress={() => setDetailSearch('')} hitSlop={10} style={{ padding: 6 }}>
338
+ <ClearIcon color={AppColors.grayTextWeak} size={13}/>
339
+ </Pressable>)}
340
+ </View>
341
+ </View>
342
+ </View>)}
343
+
344
+ {/* Tab Content Container */}
345
+ <ScrollView style={{ flex: 1 }} contentContainerStyle={{
346
+ paddingHorizontal: 12,
347
+ paddingTop: 6,
348
+ paddingBottom: 28,
349
+ flexGrow: 1,
350
+ }} showsVerticalScrollIndicator={true} keyboardShouldPersistTaps="handled">
351
+ {/* Tab 1: Output / Message */}
352
+ {activeTab === 'output' && (<View style={[
353
+ styles.sectionContainer,
354
+ {
355
+ backgroundColor: isDark
356
+ ? `${AppColors.purple}12`
357
+ : AppColors.purpleShade50,
358
+ borderWidth: 1,
359
+ borderColor: AppColors.dividerColor,
360
+ borderRadius: 12,
361
+ overflow: 'hidden',
362
+ flex: 1,
363
+ },
364
+ ]}>
365
+ {/* Section Header with View Modes */}
366
+ <View style={{
367
+ flexDirection: 'column',
368
+ gap: 8,
369
+ paddingHorizontal: 12,
370
+ paddingVertical: 10,
371
+ borderBottomWidth: 1,
372
+ borderBottomColor: AppColors.dividerColor,
373
+ backgroundColor: isDark
374
+ ? AppColors.primaryLight
375
+ : AppColors.grayBackground,
376
+ }}>
377
+ <View style={{
378
+ flexDirection: 'row',
379
+ justifyContent: 'space-between',
380
+ alignItems: 'center',
381
+ }}>
382
+ <Text style={{
383
+ fontFamily: AppFonts.interBold,
384
+ fontSize: 11.5,
385
+ color: AppColors.purple,
386
+ letterSpacing: 0.4,
387
+ textTransform: 'uppercase',
388
+ }}>
389
+ {jsonContent
390
+ ? t('console.jsonTitle')
391
+ : t('console.messageTitle')}
392
+ </Text>
393
+ <CopyButton value={selectedLog.message} label={t('console.logMessage')}/>
394
+ </View>
395
+
396
+ {jsonContent && (<SegmentedTabs tabs={[
397
+ {
398
+ key: 'pretty',
399
+ label: t('network.jsonViewer.pretty'),
400
+ icon: (isActive) => (<PrettyIcon color={isActive
401
+ ? AppColors.white
402
+ : AppColors.grayTextWeak} size={12}/>),
403
+ },
404
+ {
405
+ key: 'raw',
406
+ label: t('network.jsonViewer.raw'),
407
+ icon: (isActive) => (<RawIcon color={isActive
408
+ ? AppColors.white
409
+ : AppColors.grayTextWeak} size={12}/>),
410
+ },
411
+ {
412
+ key: 'table',
413
+ label: t('network.jsonViewer.table'),
414
+ icon: (isActive) => (<TableIcon color={isActive
415
+ ? AppColors.white
416
+ : AppColors.grayTextWeak} size={12}/>),
417
+ },
418
+ ]} activeKey={viewMode} onChange={key => setViewMode(key)}/>)}
419
+ </View>
420
+
421
+ {/* Content Body */}
422
+ <View style={{ padding: 12, flex: 1 }}>
423
+ {jsonContent ? (<>
424
+ {jsonContent.header ? (<View style={{
425
+ backgroundColor: AppColors.primaryLight,
426
+ borderRadius: 8,
427
+ borderWidth: 1,
428
+ borderColor: AppColors.dividerColor,
429
+ padding: 10,
430
+ marginBottom: 8,
431
+ }}>
432
+ <Text selectable style={{
433
+ fontFamily: AppFonts.interRegular,
434
+ fontSize: 12,
435
+ color: AppColors.primaryBlack,
436
+ lineHeight: 17,
437
+ }}>
438
+ {jsonContent.header}
439
+ </Text>
440
+ </View>) : null}
441
+ <JsonViewer data={jsonContent.data} search={detailSearch} wrap forceOpen hideTabs fullHeight mode={viewMode} defaultExpandDepth={jsonContent.data &&
442
+ typeof jsonContent.data === 'object' &&
443
+ Object.keys(jsonContent.data).length > 10
444
+ ? 1
445
+ : 2}/>
446
+ </>) : (<View style={{
447
+ backgroundColor: AppColors.primaryLight,
448
+ borderRadius: 8,
449
+ borderWidth: 1,
450
+ borderColor: AppColors.dividerColor,
451
+ padding: 10,
452
+ }}>
453
+ <HighlightText text={selectedLog.message} search={detailSearch} style={{
454
+ fontFamily: AppFonts.interRegular,
455
+ fontSize: 12,
456
+ color: AppColors.primaryBlack,
457
+ lineHeight: 18,
458
+ }} highlightStyle={{
459
+ backgroundColor: AppColors.yellowHighlight,
460
+ color: AppColors.primaryBlack,
461
+ borderRadius: 3,
462
+ paddingHorizontal: 2,
463
+ }} detectLinks={true}/>
464
+ </View>)}
465
+ </View>
466
+ </View>)}
467
+
468
+ {/* Tab 2: Individual Arguments */}
469
+ {activeTab === 'arguments' && selectedLog.rawArgs && (<View style={{ gap: 10 }}>
470
+ {selectedLog.rawArgs.map((arg, idx) => {
471
+ const argType = arg === null
472
+ ? 'null'
473
+ : Array.isArray(arg)
474
+ ? `Array[${arg.length}]`
475
+ : typeof arg === 'object'
476
+ ? `Object{${Object.keys(arg).length}}`
477
+ : typeof arg;
478
+ const isObj = arg != null &&
479
+ (typeof arg === 'object' || Array.isArray(arg));
480
+ const argStr = isObj
481
+ ? JSON.stringify(arg, null, 2)
482
+ : String(arg);
483
+ return (<View key={idx} style={[
484
+ styles.sectionContainer,
485
+ {
486
+ backgroundColor: AppColors.primaryLight,
487
+ borderRadius: 10,
488
+ borderWidth: 1,
489
+ borderColor: AppColors.dividerColor,
490
+ overflow: 'hidden',
491
+ },
492
+ ]}>
493
+ <View style={{
494
+ flexDirection: 'row',
495
+ alignItems: 'center',
496
+ justifyContent: 'space-between',
497
+ paddingHorizontal: 12,
498
+ paddingVertical: 8,
499
+ backgroundColor: AppColors.grayBackground,
500
+ borderBottomWidth: 1,
501
+ borderBottomColor: AppColors.dividerColor,
502
+ }}>
503
+ <View style={{
504
+ flexDirection: 'row',
505
+ alignItems: 'center',
506
+ gap: 6,
507
+ }}>
508
+ <Text style={{
509
+ fontFamily: AppFonts.interBold,
510
+ fontSize: 11,
511
+ color: AppColors.purple,
512
+ }}>
513
+ Arg #{idx + 1}
514
+ </Text>
515
+ <View style={[
516
+ styles.metaChip,
517
+ {
518
+ backgroundColor: `${AppColors.teal600}12`,
519
+ borderColor: `${AppColors.teal600}2B`,
520
+ paddingHorizontal: 5,
521
+ paddingVertical: 1.5,
522
+ },
523
+ ]}>
524
+ <Text style={[
525
+ styles.metaChipText,
526
+ { color: AppColors.teal600, fontSize: 9 },
527
+ ]}>
528
+ {argType}
529
+ </Text>
530
+ </View>
531
+ </View>
532
+ <CopyButton value={argStr} label={`Arg #${idx + 1}`}/>
533
+ </View>
534
+
535
+ <View style={{ padding: 10 }}>
536
+ {isObj ? (<JsonViewer data={arg} search={detailSearch} wrap forceOpen hideTabs defaultExpandDepth={2}/>) : (<HighlightText text={argStr} search={detailSearch} style={{
537
+ fontFamily: AppFonts.interRegular,
538
+ fontSize: 12,
539
+ color: AppColors.primaryBlack,
540
+ lineHeight: 17,
541
+ }} highlightStyle={{
542
+ backgroundColor: AppColors.yellowHighlight,
543
+ color: AppColors.primaryBlack,
544
+ }}/>)}
545
+ </View>
546
+ </View>);
547
+ })}
548
+ </View>)}
549
+
550
+ {/* Tab 3: Call Stack Trace */}
551
+ {activeTab === 'stack' && (<View style={[
552
+ styles.sectionContainer,
553
+ {
554
+ backgroundColor: isDark
555
+ ? `${AppColors.purple}12`
556
+ : AppColors.purpleShade50,
557
+ borderWidth: 1,
558
+ borderColor: AppColors.dividerColor,
559
+ borderRadius: 12,
560
+ overflow: 'hidden',
561
+ },
562
+ ]}>
563
+ {/* Section Header with Mode Switch */}
564
+ <View style={{
565
+ flexDirection: 'column',
566
+ gap: 8,
567
+ paddingHorizontal: 12,
568
+ paddingVertical: 10,
569
+ borderBottomWidth: 1,
570
+ borderBottomColor: AppColors.dividerColor,
571
+ backgroundColor: isDark
572
+ ? AppColors.primaryLight
573
+ : AppColors.grayBackground,
574
+ }}>
575
+ <View style={{
576
+ flexDirection: 'row',
577
+ alignItems: 'center',
578
+ justifyContent: 'space-between',
579
+ }}>
580
+ <Text style={{
581
+ fontFamily: AppFonts.interBold,
582
+ fontSize: 11.5,
583
+ color: AppColors.purple,
584
+ textTransform: 'uppercase',
585
+ letterSpacing: 0.4,
586
+ }}>
587
+ {stackSource === 'error' ? 'Error Exception Stack' : 'Call Stack'} ({parsedStackFrames.length} frames)
588
+ </Text>
589
+ <CopyButton value={activeStackString} label="Full Stack Trace"/>
590
+ </View>
591
+
592
+ {/* Source Switcher if Error Stack exists */}
593
+ {selectedLog.errorStack && (<SegmentedTabs tabs={[
594
+ {
595
+ key: 'caller',
596
+ label: 'Call Origin Stack',
597
+ },
598
+ {
599
+ key: 'error',
600
+ label: 'Error Thrown Stack',
601
+ },
602
+ ]} activeKey={stackSource} onChange={key => setStackSource(key)}/>)}
603
+
604
+ {/* Structured vs Raw View Mode Tabs */}
605
+ <SegmentedTabs tabs={[
606
+ {
607
+ key: 'structured',
608
+ label: 'Structured Cards',
609
+ icon: (isActive) => (<LayersIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
610
+ },
611
+ {
612
+ key: 'raw',
613
+ label: 'Raw Trace',
614
+ icon: (isActive) => (<RawIcon color={isActive ? AppColors.white : AppColors.grayTextWeak} size={12}/>),
615
+ },
616
+ ]} activeKey={stackViewMode} onChange={key => setStackViewMode(key)}/>
617
+ </View>
618
+
619
+ {/* Stack Content: Structured Cards or Raw Full Trace */}
620
+ {stackViewMode === 'structured' ? (<View style={{ padding: 12, gap: 10 }}>
621
+ {parsedStackFrames.length > 0 ? (parsedStackFrames.map((frame, frameIdx) => {
622
+ const isTopFrame = frameIdx === 0;
623
+ return (<View key={frameIdx} style={{
624
+ backgroundColor: isTopFrame
625
+ ? `${AppColors.brandPurple}0A`
626
+ : AppColors.primaryLight,
627
+ borderRadius: 8,
628
+ borderWidth: 1,
629
+ borderColor: isTopFrame
630
+ ? `${AppColors.brandPurple}30`
631
+ : AppColors.dividerColor,
632
+ padding: 10,
633
+ gap: 5,
634
+ }}>
635
+ {/* Frame Header Row */}
636
+ <View style={{
637
+ flexDirection: 'row',
638
+ alignItems: 'center',
639
+ justifyContent: 'space-between',
640
+ gap: 6,
641
+ }}>
642
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }}>
643
+ <View style={{
644
+ backgroundColor: isTopFrame
645
+ ? AppColors.brandPurple
646
+ : AppColors.grayBorderSecondary,
647
+ borderRadius: 4,
648
+ paddingHorizontal: 5,
649
+ paddingVertical: 1.5,
650
+ }}>
651
+ <Text style={{
652
+ fontFamily: AppFonts.interBold,
653
+ fontSize: 9,
654
+ color: isTopFrame
655
+ ? AppColors.white
656
+ : AppColors.grayTextStrong,
657
+ }}>
658
+ #{frameIdx + 1}
659
+ {isTopFrame ? ' ORIGIN' : ''}
660
+ </Text>
661
+ </View>
662
+
663
+ <HighlightText text={frame.functionName !== '<anonymous>' ? `${frame.functionName}()` : '<anonymous>'} search={detailSearch} style={{
664
+ fontFamily: AppFonts.interBold,
665
+ fontSize: 12,
666
+ color: isTopFrame
667
+ ? AppColors.primaryBlack
668
+ : AppColors.grayTextStrong,
669
+ }} highlightStyle={{
670
+ backgroundColor: AppColors.yellowHighlight,
671
+ color: AppColors.primaryBlack,
672
+ }}/>
673
+ </View>
674
+
675
+ {frame.lineNumber && (<View style={{
676
+ backgroundColor: `${AppColors.teal600}15`,
677
+ borderColor: `${AppColors.teal600}30`,
678
+ borderWidth: 1,
679
+ borderRadius: 4,
680
+ paddingHorizontal: 6,
681
+ paddingVertical: 1.5,
682
+ }}>
683
+ <Text style={{
684
+ fontFamily: AppFonts.interBold,
685
+ fontSize: 9.5,
686
+ color: AppColors.teal600,
687
+ }}>
688
+ L{frame.lineNumber}
689
+ {frame.columnNumber ? `:C${frame.columnNumber}` : ''}
690
+ </Text>
691
+ </View>)}
692
+ </View>
693
+
694
+ {/* File Path Row */}
695
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
696
+ {frame.fileExt && frame.fileExt !== 'other' && (<View style={{
697
+ backgroundColor: frame.fileExt === 'tsx' || frame.fileExt === 'ts'
698
+ ? `${AppColors.brandPurple}18`
699
+ : `${AppColors.teal600}18`,
700
+ borderRadius: 3,
701
+ paddingHorizontal: 4,
702
+ paddingVertical: 1,
703
+ }}>
704
+ <Text style={{
705
+ fontFamily: AppFonts.interBold,
706
+ fontSize: 8.5,
707
+ color: frame.fileExt === 'tsx' || frame.fileExt === 'ts'
708
+ ? AppColors.brandPurple
709
+ : AppColors.teal600,
710
+ }}>
711
+ {frame.fileExt.toUpperCase()}
712
+ </Text>
713
+ </View>)}
714
+ <Text style={{
715
+ fontFamily: AppFonts.interMedium,
716
+ fontSize: 10.5,
717
+ color: isTopFrame
718
+ ? AppColors.brandPurple
719
+ : AppColors.grayTextWeak,
720
+ }}>
721
+ 📄 {frame.fileName}
722
+ </Text>
723
+ </View>
724
+
725
+ {/* Full path */}
726
+ {frame.fullPath && frame.fullPath !== frame.fileName && (<Text selectable style={{
727
+ fontFamily: AppFonts.interRegular,
728
+ fontSize: 9.5,
729
+ color: AppColors.grayTextWeak,
730
+ lineHeight: 14,
731
+ }} numberOfLines={2}>
732
+ {frame.fullPath}
733
+ </Text>)}
734
+ </View>);
735
+ })) : (<Text style={{
736
+ fontFamily: AppFonts.interRegular,
737
+ fontSize: 11.5,
738
+ color: AppColors.grayTextWeak,
739
+ textAlign: 'center',
740
+ paddingVertical: 16,
741
+ }}>
742
+ No call stack trace available for this log.
743
+ </Text>)}
744
+ </View>) : (
745
+ /* Raw Full Trace Monospace Block */
746
+ <View style={{
747
+ backgroundColor: AppColors.primaryLight,
748
+ padding: 12,
749
+ }}>
750
+ {activeStackString.split('\n').filter(l => l.trim().length > 0).map((line, lineIdx) => (<View key={lineIdx} style={{
751
+ flexDirection: 'row',
752
+ alignItems: 'flex-start',
753
+ paddingVertical: 2,
754
+ }}>
755
+ <Text style={{
756
+ width: 28,
757
+ textAlign: 'right',
758
+ paddingRight: 8,
759
+ fontFamily: AppFonts.interRegular,
760
+ fontSize: 11,
761
+ color: AppColors.grayTextWeak,
762
+ lineHeight: 17,
763
+ }}>
764
+ {lineIdx + 1}
765
+ </Text>
766
+ <HighlightText text={line.trim()} search={detailSearch} style={{
767
+ flex: 1,
768
+ fontFamily: AppFonts.interRegular,
769
+ fontSize: 12,
770
+ color: AppColors.primaryBlack,
771
+ lineHeight: 17,
772
+ }} highlightStyle={{
773
+ backgroundColor: AppColors.yellowHighlight,
774
+ color: AppColors.primaryBlack,
775
+ }}/>
776
+ </View>))}
777
+ </View>)}
778
+ </View>)}
779
+
780
+ {/* Tab 4: Structured Metadata */}
781
+ {activeTab === 'metadata' && (<View style={[
782
+ styles.sectionContainer,
783
+ {
784
+ backgroundColor: AppColors.primaryLight,
785
+ borderRadius: 10,
786
+ borderWidth: 1,
787
+ borderColor: AppColors.dividerColor,
788
+ overflow: 'hidden',
789
+ },
790
+ ]}>
791
+ <View style={{
792
+ flexDirection: 'row',
793
+ alignItems: 'center',
794
+ justifyContent: 'space-between',
795
+ paddingHorizontal: 12,
796
+ paddingVertical: 9,
797
+ backgroundColor: AppColors.grayBackground,
798
+ borderBottomWidth: 1,
799
+ borderBottomColor: AppColors.dividerColor,
800
+ }}>
801
+ <Text style={{
802
+ fontFamily: AppFonts.interBold,
803
+ fontSize: 11.5,
804
+ color: AppColors.purple,
805
+ textTransform: 'uppercase',
806
+ letterSpacing: 0.4,
807
+ }}>
808
+ Log Properties
809
+ </Text>
810
+ <CopyButton value={JSON.stringify({
811
+ id: selectedLog.id + 1,
812
+ type: selectedLog.type,
813
+ sourceMethod: selectedLog.sourceMethod || 'log',
814
+ timestamp: selectedLog.timestamp,
815
+ dateTime: formatDateTime(selectedLog.timestamp),
816
+ caller: selectedLog.caller,
817
+ triggerFile: originFrame?.fileName,
818
+ triggerLine: originFrame?.lineNumber,
819
+ triggerFunction: originFrame?.functionName,
820
+ charCount: selectedLog.message.length,
821
+ size: getSize(selectedLog.message),
822
+ duplicates: selectedLog.duplicateCount || 1,
823
+ }, null, 2)} label="Metadata JSON"/>
824
+ </View>
825
+
826
+ <View style={{ paddingHorizontal: 12, paddingVertical: 4 }}>
827
+ {[
828
+ { label: 'Log ID', value: `#${selectedLog.id + 1}` },
829
+ { label: 'Level', value: selectedLog.type.toUpperCase() },
830
+ {
831
+ label: 'Source Method',
832
+ value: `console.${selectedLog.sourceMethod || selectedLog.type || 'log'}`,
833
+ },
834
+ {
835
+ label: 'Triggered At',
836
+ value: `${formatDateTime(selectedLog.timestamp)} (${getRelativeTime(selectedLog.timestamp)})`,
837
+ },
838
+ { label: 'Timestamp (ms)', value: String(selectedLog.timestamp) },
839
+ {
840
+ label: 'Trigger File',
841
+ value: originFrame?.fileName || 'Unknown',
842
+ },
843
+ ...(originFrame?.lineNumber
844
+ ? [
845
+ {
846
+ label: 'Trigger Location',
847
+ value: `Line ${originFrame.lineNumber}, Col ${originFrame.columnNumber || 0}`,
848
+ },
849
+ ]
850
+ : []),
851
+ ...(originFrame?.functionName && originFrame.functionName !== '<anonymous>'
852
+ ? [
853
+ {
854
+ label: 'Trigger Function',
855
+ value: `${originFrame.functionName}()`,
856
+ },
857
+ ]
858
+ : []),
859
+ {
860
+ label: 'Full Location',
861
+ value: selectedLog.caller || 'Unknown',
862
+ },
863
+ {
864
+ label: 'Character Length',
865
+ value: `${selectedLog.message.length} chars`,
866
+ },
867
+ {
868
+ label: 'Approx. Size',
869
+ value: getSize(selectedLog.message),
870
+ },
871
+ {
872
+ label: 'Duplicate Count',
873
+ value: String(selectedLog.duplicateCount || 1),
874
+ },
875
+ ...(hasMultipleArgs
876
+ ? [
877
+ {
878
+ label: 'Arguments Count',
879
+ value: `${selectedLog.rawArgs?.length} arguments`,
880
+ },
881
+ ]
882
+ : []),
883
+ ].map((row, rIdx, arr) => (<View key={rIdx} style={{
884
+ flexDirection: 'row',
885
+ justifyContent: 'space-between',
886
+ alignItems: 'center',
887
+ paddingVertical: 8,
888
+ borderBottomWidth: rIdx < arr.length - 1 ? 1 : 0,
889
+ borderBottomColor: AppColors.grayBorderSecondary,
890
+ }}>
891
+ <Text style={{
892
+ fontFamily: AppFonts.interMedium,
893
+ fontSize: 11.5,
894
+ color: AppColors.grayTextWeak,
895
+ }}>
896
+ {row.label}
897
+ </Text>
898
+ <Text selectable style={{
899
+ fontFamily: AppFonts.interBold,
900
+ fontSize: 11.5,
901
+ color: AppColors.primaryBlack,
902
+ maxWidth: '60%',
903
+ textAlign: 'right',
904
+ }} numberOfLines={2}>
905
+ {row.value}
906
+ </Text>
907
+ </View>))}
908
+ </View>
909
+ </View>)}
910
+ </ScrollView>
911
+ </View>);
912
+ };
913
+ const detailStyles = StyleSheet.create({
914
+ metaDot: {
915
+ fontSize: 10,
916
+ color: AppColors.grayTextWeak,
917
+ opacity: 0.7,
918
+ },
919
+ tabsHeaderWrap: {
920
+ paddingHorizontal: 12,
921
+ paddingTop: 8,
922
+ paddingBottom: 4,
923
+ },
924
+ });
925
+ export default LogDetail;