react-native-inapp-inspector 1.1.27 → 1.1.29

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 (100) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -46
  2. package/dist/commonjs/components/AppHeaderLogo.js +12 -15
  3. package/dist/commonjs/components/ConsoleLogCard.js +31 -7
  4. package/dist/commonjs/components/CopyButton.js +15 -1
  5. package/dist/commonjs/components/DomainHeader.js +56 -28
  6. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  7. package/dist/commonjs/components/ErrorBoundary.js +530 -100
  8. package/dist/commonjs/components/HighlightText.js +7 -1
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +190 -126
  10. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -0
  11. package/dist/commonjs/components/Inspector/BundleTab.js +145 -21
  12. package/dist/commonjs/components/Inspector/CrashTab.js +0 -8
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +17 -3
  14. package/dist/commonjs/components/Inspector/LogDetail.js +8 -5
  15. package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
  16. package/dist/commonjs/components/Inspector/NetworkDetail.js +65 -24
  17. package/dist/commonjs/components/Inspector/PerformanceTab.d.ts +5 -0
  18. package/dist/commonjs/components/Inspector/PerformanceTab.js +588 -155
  19. package/dist/commonjs/components/Inspector/SettingsPanel.js +57 -22
  20. package/dist/commonjs/components/Inspector/TabBar.js +13 -1
  21. package/dist/commonjs/components/JsonViewer.js +25 -3
  22. package/dist/commonjs/components/LogCard.js +173 -40
  23. package/dist/commonjs/components/NetworkIcons.d.ts +112 -102
  24. package/dist/commonjs/components/Toast.d.ts +3 -0
  25. package/dist/commonjs/components/Toast.js +152 -0
  26. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  27. package/dist/commonjs/components/TouchableScale.js +3 -3
  28. package/dist/commonjs/components/TreeNode.js +1 -1
  29. package/dist/commonjs/constants/index.js +6 -6
  30. package/dist/commonjs/constants/version.d.ts +1 -1
  31. package/dist/commonjs/constants/version.js +1 -1
  32. package/dist/commonjs/customHooks/analyticsLogger.d.ts +1 -11
  33. package/dist/commonjs/customHooks/analyticsLogger.js +8 -1
  34. package/dist/commonjs/customHooks/bundleAnalyzer.js +121 -124
  35. package/dist/commonjs/customHooks/networkLogger.d.ts +1 -0
  36. package/dist/commonjs/customHooks/networkLogger.js +16 -0
  37. package/dist/commonjs/customHooks/performanceTracker.d.ts +74 -0
  38. package/dist/commonjs/customHooks/performanceTracker.js +546 -392
  39. package/dist/commonjs/customHooks/reduxLogger.d.ts +1 -0
  40. package/dist/commonjs/customHooks/reduxLogger.js +3 -1
  41. package/dist/commonjs/helpers/index.d.ts +4 -0
  42. package/dist/commonjs/helpers/index.js +92 -69
  43. package/dist/commonjs/helpers/toast.d.ts +4 -0
  44. package/dist/commonjs/helpers/toast.js +20 -0
  45. package/dist/commonjs/i18n/locales/en.json +229 -5
  46. package/dist/commonjs/index.d.ts +1 -1
  47. package/dist/commonjs/index.js +36 -2
  48. package/dist/commonjs/styles/index.d.ts +63 -15
  49. package/dist/commonjs/styles/index.js +79 -32
  50. package/dist/commonjs/types/interfaces.d.ts +3 -0
  51. package/dist/esm/components/AppHeaderLogo.js +12 -15
  52. package/dist/esm/components/ConsoleLogCard.js +33 -9
  53. package/dist/esm/components/CopyButton.js +15 -1
  54. package/dist/esm/components/DomainHeader.js +56 -28
  55. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  56. package/dist/esm/components/ErrorBoundary.js +498 -101
  57. package/dist/esm/components/HighlightText.js +7 -1
  58. package/dist/esm/components/Inspector/AnalyticsTab.js +191 -127
  59. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -0
  60. package/dist/esm/components/Inspector/BundleTab.js +145 -23
  61. package/dist/esm/components/Inspector/CrashTab.js +0 -8
  62. package/dist/esm/components/Inspector/InspectorHeader.js +18 -4
  63. package/dist/esm/components/Inspector/LogDetail.js +10 -7
  64. package/dist/esm/components/Inspector/MainScreen.js +4 -0
  65. package/dist/esm/components/Inspector/NetworkDetail.js +66 -25
  66. package/dist/esm/components/Inspector/PerformanceTab.d.ts +5 -0
  67. package/dist/esm/components/Inspector/PerformanceTab.js +591 -158
  68. package/dist/esm/components/Inspector/SettingsPanel.js +57 -22
  69. package/dist/esm/components/Inspector/TabBar.js +13 -1
  70. package/dist/esm/components/JsonViewer.js +25 -3
  71. package/dist/esm/components/LogCard.js +175 -42
  72. package/dist/esm/components/NetworkIcons.d.ts +112 -102
  73. package/dist/esm/components/Toast.d.ts +3 -0
  74. package/dist/esm/components/Toast.js +117 -0
  75. package/dist/esm/components/TouchableScale.d.ts +1 -0
  76. package/dist/esm/components/TouchableScale.js +3 -3
  77. package/dist/esm/components/TreeNode.js +1 -1
  78. package/dist/esm/constants/index.js +6 -6
  79. package/dist/esm/constants/version.d.ts +1 -1
  80. package/dist/esm/constants/version.js +1 -1
  81. package/dist/esm/customHooks/analyticsLogger.d.ts +1 -11
  82. package/dist/esm/customHooks/analyticsLogger.js +6 -0
  83. package/dist/esm/customHooks/bundleAnalyzer.js +121 -124
  84. package/dist/esm/customHooks/networkLogger.d.ts +1 -0
  85. package/dist/esm/customHooks/networkLogger.js +16 -0
  86. package/dist/esm/customHooks/performanceTracker.d.ts +74 -0
  87. package/dist/esm/customHooks/performanceTracker.js +531 -391
  88. package/dist/esm/customHooks/reduxLogger.d.ts +1 -0
  89. package/dist/esm/customHooks/reduxLogger.js +1 -0
  90. package/dist/esm/helpers/index.d.ts +4 -0
  91. package/dist/esm/helpers/index.js +87 -69
  92. package/dist/esm/helpers/toast.d.ts +4 -0
  93. package/dist/esm/helpers/toast.js +15 -0
  94. package/dist/esm/i18n/locales/en.json +229 -5
  95. package/dist/esm/index.d.ts +1 -1
  96. package/dist/esm/index.js +24 -3
  97. package/dist/esm/styles/index.d.ts +63 -15
  98. package/dist/esm/styles/index.js +79 -32
  99. package/dist/esm/types/interfaces.d.ts +3 -0
  100. package/package.json +4 -1
@@ -13,8 +13,12 @@ import { clearNetworkLogs } from '../../customHooks/networkLogger';
13
13
  import { clearConsoleLogs } from '../../customHooks/consoleLogger';
14
14
  import { clearAnalyticsEvents } from '../../customHooks/analyticsLogger';
15
15
  import { clearCrashRecords, } from '../../customHooks/crashHandler';
16
+ import { isReduxConnected } from '../../customHooks/reduxLogger';
17
+ import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
18
+ import { useTranslation } from '../../i18n';
16
19
  import { SignalIcon, TerminalIcon, AnalyticsIcon, SettingsIcon, SunIcon, MoonIcon, ScreenIcon, MotionIcon, LayersIcon, EyeIcon, CheckIcon, TrashIcon, WhiteBackNavigation, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, ShieldAlertIcon, } from '../NetworkIcons';
17
20
  const SettingsPanel = () => {
21
+ const { t } = useTranslation();
18
22
  const { settingsPage, setSettingsPage, settingsActiveSubTab, setSettingsActiveSubTab, tabVisibility, toggleTabVisibility, defaultTab, setDefaultTab, isDark, setIsDark, modalHeightPercent, setModalHeightPercent, modalAnimationType, setModalAnimationType, showDuplicateLogs, setShowDuplicateLogs, showConsoleLevels, setShowConsoleLevels, resetToDefaults, storage, logs, consoleLogs, analyticsEvents, reduxState, maxNetworkLogs, setMaxNetworkLogs, maxConsoleLogs, setMaxConsoleLogs, reduxAutoRefresh, setReduxAutoRefreshState, reduxExpandDepth, setReduxExpandDepth, switchActiveTab, setSelected, setSelectedEvent, setReduxState, crashRecords, maxCrashLogs, setMaxCrashLogs, } = useInspector();
19
23
  const isPersistent = isPersistentStorageAvailable();
20
24
  const headerTopPadding = Platform.OS === 'ios' && modalHeightPercent >= 95 ? 44 : 0;
@@ -185,9 +189,14 @@ const SettingsPanel = () => {
185
189
  </View>
186
190
 
187
191
  {settingsTabs.map((tab, idx) => {
188
- const isVisible = tab.key === 'apis' || tabVisibility?.[tab.key];
192
+ const isReduxAvail = isReduxConnected();
193
+ const isAnalyticsAvail = isAnalyticsConnected();
194
+ const isUnavailable = (tab.key === 'redux' && !isReduxAvail) ||
195
+ (tab.key === 'analytics' && !isAnalyticsAvail);
196
+ const isLocked = tab.key === 'apis' || isUnavailable;
197
+ const isVisible = tab.key === 'apis' ||
198
+ (Boolean(tabVisibility?.[tab.key]) && !isUnavailable);
189
199
  const isLast = idx === settingsTabs.length - 1;
190
- const isLocked = tab.key === 'apis';
191
200
  return (<View key={tab.key} style={{
192
201
  flexDirection: 'row',
193
202
  alignItems: 'center',
@@ -203,6 +212,7 @@ const SettingsPanel = () => {
203
212
  flexDirection: 'row',
204
213
  alignItems: 'center',
205
214
  gap: 8,
215
+ flexWrap: 'wrap',
206
216
  }}>
207
217
  {/* Small icon tile */}
208
218
  <View style={{
@@ -251,8 +261,28 @@ const SettingsPanel = () => {
251
261
  }}>
252
262
  {tab.label}
253
263
  </Text>
264
+ {/* Unavailable badge */}
265
+ {isUnavailable && (<View style={{
266
+ backgroundColor: `${AppColors.amber500}18`,
267
+ borderRadius: 4,
268
+ paddingHorizontal: 5,
269
+ paddingVertical: 1.5,
270
+ borderWidth: 1,
271
+ borderColor: `${AppColors.amber500}35`,
272
+ }}>
273
+ <Text style={{
274
+ fontFamily: AppFonts.interBold,
275
+ fontSize: 8,
276
+ color: AppColors.amber700,
277
+ letterSpacing: 0.3,
278
+ }}>
279
+ {tab.key === 'redux'
280
+ ? t('settings.notConnectedBadge')
281
+ : t('settings.notDetectedBadge')}
282
+ </Text>
283
+ </View>)}
254
284
  {/* #6 — badge marks the configured default tab */}
255
- {tab.key === defaultTab && (<View style={{
285
+ {tab.key === defaultTab && !isUnavailable && (<View style={{
256
286
  flexDirection: 'row',
257
287
  alignItems: 'center',
258
288
  backgroundColor: `${AppColors.purple}14`,
@@ -281,21 +311,21 @@ const SettingsPanel = () => {
281
311
  </View>)}
282
312
 
283
313
  {/* Settings gear icon next to label */}
284
- <TouchableScale onPress={() => {
285
- animateNextLayout();
286
- setSettingsPage(tab.key);
287
- }} hitSlop={8} style={{
288
- marginLeft: 4,
289
- padding: 4,
290
- borderRadius: 6,
291
- backgroundColor: AppColors.purpleShade50,
292
- borderWidth: 1,
293
- borderColor: `${AppColors.purple}26`,
294
- alignItems: 'center',
295
- justifyContent: 'center',
296
- }}>
297
- <SettingsIcon color={AppColors.purple} size={10}/>
298
- </TouchableScale>
314
+ {!isUnavailable && (<TouchableScale onPress={() => {
315
+ animateNextLayout();
316
+ setSettingsPage(tab.key);
317
+ }} hitSlop={8} style={{
318
+ marginLeft: 4,
319
+ padding: 4,
320
+ borderRadius: 6,
321
+ backgroundColor: AppColors.purpleShade50,
322
+ borderWidth: 1,
323
+ borderColor: `${AppColors.purple}26`,
324
+ alignItems: 'center',
325
+ justifyContent: 'center',
326
+ }}>
327
+ <SettingsIcon color={AppColors.purple} size={10}/>
328
+ </TouchableScale>)}
299
329
  </View>
300
330
 
301
331
  {/* Visibility Switch in VISIBILITY column */}
@@ -345,12 +375,17 @@ const SettingsPanel = () => {
345
375
  </TouchableScale>
346
376
  {isLocked && (<Text style={{
347
377
  fontFamily: AppFonts.interBold,
348
- fontSize: 8,
349
- color: AppColors.grayTextWeak,
350
- letterSpacing: 0.4,
378
+ fontSize: 7.5,
379
+ color: isUnavailable
380
+ ? AppColors.amber700
381
+ : AppColors.grayTextWeak,
382
+ letterSpacing: 0.3,
351
383
  marginTop: 3,
384
+ textAlign: 'right',
352
385
  }}>
353
- REQUIRED
386
+ {tab.key === 'apis'
387
+ ? t('settings.requiredBadge')
388
+ : t('settings.readOnlyBadge')}
354
389
  </Text>)}
355
390
  </View>
356
391
  </View>);
@@ -5,8 +5,12 @@ import TouchableScale from '../TouchableScale';
5
5
  import styles from '../../styles';
6
6
  import { AppColors } from '../../styles/AppColors';
7
7
  import { SignalIcon, TerminalIcon, AnalyticsIcon, PackageIcon, ReduxIcon, PerformanceIcon, CrashIcon, } from '../NetworkIcons';
8
+ import { isReduxConnected } from '../../customHooks/reduxLogger';
9
+ import { isAnalyticsConnected } from '../../customHooks/analyticsLogger';
8
10
  const TabBar = React.memo(() => {
9
11
  const { activeTab, switchActiveTab, tabVisibility, logs, consoleLogs, analyticsEvents, crashRecords, lastReadApisCount, lastReadLogsCount, lastReadCrashesCount, unreadPulseAnim, } = useInspector();
12
+ const isReduxAvail = isReduxConnected();
13
+ const isAnalyticsAvail = isAnalyticsConnected();
10
14
  return (<View style={styles.tabBarContainer}>
11
15
  <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ paddingRight: 16 }}>
12
16
  {[
@@ -53,7 +57,15 @@ const TabBar = React.memo(() => {
53
57
  icon: 'crash',
54
58
  },
55
59
  ]
56
- .filter(tab => tabVisibility?.[tab.key])
60
+ .filter(tab => {
61
+ if (!tabVisibility?.[tab.key])
62
+ return false;
63
+ if (tab.key === 'redux' && !isReduxAvail)
64
+ return false;
65
+ if (tab.key === 'analytics' && !isAnalyticsAvail)
66
+ return false;
67
+ return true;
68
+ })
57
69
  .map(tab => {
58
70
  const isActive = activeTab === tab.key;
59
71
  const iconColor = isActive
@@ -57,7 +57,7 @@ const JsonTableRow = React.memo(({ itemKey, val, search, }) => {
57
57
  // ── JsonViewer Component ─────────────────────────────────────────────────────
58
58
  const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wrap, fullHeight = false, maxHeight, mode: externalMode, onModeChange, hideTabs = false, }) => {
59
59
  const { t } = useTranslation();
60
- const [internalMode, setInternalMode] = useState('pretty');
60
+ const [internalMode, setInternalMode] = useState('raw');
61
61
  const mode = externalMode ?? internalMode;
62
62
  const setMode = (newMode) => {
63
63
  if (onModeChange) {
@@ -78,6 +78,10 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
78
78
  return String(data);
79
79
  }
80
80
  }, [data]);
81
+ const [showFullRaw, setShowFullRaw] = useState(false);
82
+ const RAW_LIMIT = 50000;
83
+ const isTruncated = rawText.length > RAW_LIMIT && !showFullRaw;
84
+ const displayRawText = isTruncated ? rawText.slice(0, RAW_LIMIT) : rawText;
81
85
  useEffect(() => {
82
86
  if (externalMode) {
83
87
  setInternalMode(externalMode);
@@ -148,7 +152,7 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
148
152
  <View style={contentWrapperStyle}>
149
153
  {mode === 'pretty' && (isEmpty ? (<View style={localStyles.emptyTable}>
150
154
  <Text style={localStyles.emptyTableText}>
151
- {t('network.emptyResponse')}
155
+ {t('network.jsonViewer.emptyPayload')}
152
156
  </Text>
153
157
  </View>) : (wrap ? (fullHeight ? (<ScrollView style={{ flex: 1 }} contentContainerStyle={{ flexGrow: 1 }}>
154
158
  <View style={[styles.codeBlock, { width: '100%' }]}>{tree}</View>
@@ -163,12 +167,30 @@ const JsonViewer = React.memo(({ data, search, forceOpen, defaultExpandDepth, wr
163
167
  </ScrollView>))))}
164
168
 
165
169
  {mode === 'raw' && (<ScrollView style={scrollStyle} contentContainerStyle={localStyles.rawContainer} showsVerticalScrollIndicator={true} nestedScrollEnabled={true}>
166
- <HighlightText text={rawText} search={search} detectLinks={true} style={localStyles.rawPlainText} highlightStyle={{
170
+ <HighlightText text={displayRawText} search={search} detectLinks={rawText.length < 20000} style={localStyles.rawPlainText} selectable={true} highlightStyle={{
167
171
  backgroundColor: AppColors.yellowHighlight,
168
172
  color: AppColors.primaryBlack,
169
173
  borderRadius: 3,
170
174
  paddingHorizontal: 2,
171
175
  }}/>
176
+ {isTruncated && (<TouchableOpacity onPress={() => setShowFullRaw(true)} style={{
177
+ marginTop: 10,
178
+ backgroundColor: `${AppColors.brandPurple}15`,
179
+ paddingVertical: 8,
180
+ paddingHorizontal: 12,
181
+ borderRadius: 6,
182
+ alignItems: 'center',
183
+ borderWidth: 1,
184
+ borderColor: `${AppColors.brandPurple}30`,
185
+ }}>
186
+ <Text style={{
187
+ fontFamily: AppFonts.interBold,
188
+ fontSize: 11,
189
+ color: AppColors.brandPurple,
190
+ }}>
191
+ Showing first 50 KB ({Math.round(rawText.length / 1024)} KB total) · Tap to load full
192
+ </Text>
193
+ </TouchableOpacity>)}
172
194
  </ScrollView>)}
173
195
 
174
196
  {mode === 'table' && (fullHeight ? (<ScrollView style={scrollStyle}>
@@ -1,10 +1,11 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
- import { Animated, View, Pressable, Text, StyleSheet } from 'react-native';
2
+ import { Alert, Animated, Linking, View, Pressable, Text, StyleSheet, } from 'react-native';
3
3
  import Svg, { Path } from 'react-native-svg';
4
4
  import { AppColors } from '../styles/AppColors';
5
5
  import { METHOD_COLORS } from '../constants';
6
- import { getStatusColor, getDurationColor, getPath, getBaseUrl, formatDateTime, getSize, } from '../helpers';
6
+ import { getStatusColor, getDurationColor, formatDateTime, getSize, } from '../helpers';
7
7
  import { CalendarIcon, ClockIcon, SizeIcon } from './NetworkIcons';
8
+ import { AppFonts } from '../styles/AppFonts';
8
9
  import styles from '../styles';
9
10
  import HighlightText from './HighlightText';
10
11
  import TouchableScale from './TouchableScale';
@@ -12,6 +13,27 @@ import { useTranslation } from '../i18n';
12
13
  const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, timelineTotalRange, isNew, isSelected, onToggleSelect, searchStr, }) {
13
14
  const { t } = useTranslation();
14
15
  const methodColor = METHOD_COLORS[item.method] ?? METHOD_COLORS.ALL;
16
+ const handleOpenUrl = (e) => {
17
+ e?.stopPropagation?.();
18
+ Alert.alert(t('common.openInBrowser') || 'Open in Browser', `${t('common.openInBrowserPrompt') || 'Are you sure you want to open this URL in your external browser?'}\n\n${item.url}`, [
19
+ { text: t('common.cancel') || 'Cancel', style: 'cancel' },
20
+ {
21
+ text: t('common.open') || 'Open',
22
+ onPress: () => {
23
+ Linking.canOpenURL(item.url)
24
+ .then(supported => {
25
+ if (supported) {
26
+ Linking.openURL(item.url);
27
+ }
28
+ else {
29
+ Linking.openURL(item.url).catch(() => { });
30
+ }
31
+ })
32
+ .catch(() => { });
33
+ },
34
+ },
35
+ ]);
36
+ };
15
37
  const isFailed = item.status === 0 || (item.status != null && item.status >= 400);
16
38
  const isLoading = item.status == null;
17
39
  const statusColor = getStatusColor(item.status);
@@ -37,10 +59,6 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
37
59
  }).start();
38
60
  }
39
61
  }, [isNew]);
40
- const path = getPath(item.url);
41
- const baseUrl = getBaseUrl(item.url) || item.url;
42
- const slug = path && path !== '/' ? path : item.url;
43
- const showSlug = slug !== baseUrl && slug !== item.url;
44
62
  const triggeredAt = formatDateTime(item.startTime);
45
63
  const isJson = item.url.split('?')[0].toLowerCase().endsWith('.json');
46
64
  return (<TouchableScale onPress={onPress} style={[
@@ -51,7 +69,7 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
51
69
  },
52
70
  ]}>
53
71
  <View style={styles.cardBody}>
54
- {/* Row 1: Header (Checkbox, Serial, Method Badge, Base URL / Host with HTTPS, Status Pill) */}
72
+ {/* Row 1: Header (Checkbox, Serial, Method Badge, Client Tag, Status Pill) */}
55
73
  <View style={styles.cardHeaderRow}>
56
74
  <View style={styles.cardHeaderLeft}>
57
75
  <Pressable onPress={() => onToggleSelect(item.id)} hitSlop={12} style={[
@@ -71,7 +89,122 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
71
89
  </Text>
72
90
  </View>
73
91
 
74
- <HighlightText text={baseUrl || item.url} search={searchStr} style={styles.cardHostText} highlightStyle={styles.highlight} numberOfLines={1} ellipsizeMode="tail"/>
92
+ {item.client && (<View style={[
93
+ styles.chip,
94
+ {
95
+ backgroundColor: item.client === 'axios'
96
+ ? `${AppColors.purple}14`
97
+ : item.client === 'apollo' || item.client === 'graphql'
98
+ ? `${AppColors.pink500}14`
99
+ : item.client === 'xhr'
100
+ ? `${AppColors.amber500}14`
101
+ : `${AppColors.sky500}14`,
102
+ borderColor: item.client === 'axios'
103
+ ? `${AppColors.purple}30`
104
+ : item.client === 'apollo' || item.client === 'graphql'
105
+ ? `${AppColors.pink500}30`
106
+ : item.client === 'xhr'
107
+ ? `${AppColors.amber500}30`
108
+ : `${AppColors.sky500}30`,
109
+ paddingHorizontal: 4.5,
110
+ paddingVertical: 1,
111
+ borderRadius: 4,
112
+ },
113
+ ]}>
114
+ <Text style={[
115
+ styles.chipText,
116
+ {
117
+ fontFamily: AppFonts.interBold,
118
+ fontSize: 8.5,
119
+ color: item.client === 'axios'
120
+ ? AppColors.purple
121
+ : item.client === 'apollo' || item.client === 'graphql'
122
+ ? AppColors.pink500
123
+ : item.client === 'xhr'
124
+ ? AppColors.amber700
125
+ : AppColors.sky600,
126
+ },
127
+ ]}>
128
+ {item.client.toUpperCase()}
129
+ </Text>
130
+ </View>)}
131
+
132
+ {/* Protocol / HTTPS Badge */}
133
+ <View style={[
134
+ styles.chip,
135
+ {
136
+ backgroundColor: item.url.toLowerCase().startsWith('https')
137
+ ? '#ECFDF5'
138
+ : '#FFFBEB',
139
+ borderColor: item.url.toLowerCase().startsWith('https')
140
+ ? '#A7F3D0'
141
+ : '#FDE68A',
142
+ paddingHorizontal: 4,
143
+ paddingVertical: 1,
144
+ borderRadius: 4,
145
+ },
146
+ ]}>
147
+ <Text style={[
148
+ styles.chipText,
149
+ {
150
+ fontFamily: AppFonts.interBold,
151
+ fontSize: 8,
152
+ color: item.url.toLowerCase().startsWith('https')
153
+ ? '#059669'
154
+ : '#D97706',
155
+ },
156
+ ]}>
157
+ {item.url.toLowerCase().startsWith('https') ? 'HTTPS' : 'HTTP'}
158
+ </Text>
159
+ </View>
160
+
161
+ {/* GraphQL Indicator */}
162
+ {(item.url.toLowerCase().includes('graphql') ||
163
+ item.client === 'apollo' ||
164
+ item.client === 'graphql') && (<View style={[
165
+ styles.chip,
166
+ {
167
+ backgroundColor: '#FDF2F8',
168
+ borderColor: '#FBCFE8',
169
+ paddingHorizontal: 4,
170
+ paddingVertical: 1,
171
+ borderRadius: 4,
172
+ },
173
+ ]}>
174
+ <Text style={[
175
+ styles.chipText,
176
+ {
177
+ fontFamily: AppFonts.interBold,
178
+ fontSize: 8,
179
+ color: '#DB2777',
180
+ },
181
+ ]}>
182
+ GQL
183
+ </Text>
184
+ </View>)}
185
+
186
+ {/* In-Line Duration / Latency Pill */}
187
+ {item.duration != null && !isFailed && (<View style={[
188
+ styles.chip,
189
+ {
190
+ backgroundColor: `${durationColor}12`,
191
+ borderColor: `${durationColor}2E`,
192
+ paddingHorizontal: 4.5,
193
+ paddingVertical: 1,
194
+ borderRadius: 4,
195
+ },
196
+ ]}>
197
+ <Text style={[
198
+ styles.chipText,
199
+ {
200
+ fontFamily: AppFonts.interBold,
201
+ fontSize: 8.5,
202
+ color: durationColor,
203
+ },
204
+ ]}>
205
+ {item.duration}ms
206
+ </Text>
207
+ </View>)}
75
208
  </View>
76
209
 
77
210
  <View style={[
@@ -108,42 +241,42 @@ const LogCard = React.memo(function LogCard({ item, onPress, timelineMinStart, t
108
241
  </View>
109
242
  </View>
110
243
 
111
- {/* Row 2: Improved Slug Capsule Container */}
112
- {showSlug && (<View style={styles.cardSlugBox}>
113
- <View style={styles.slugLeft}>
114
- <Text style={styles.slugTag}>PATH</Text>
115
- <HighlightText text={slug} search={searchStr} style={styles.slugText} highlightStyle={styles.highlight} numberOfLines={1} ellipsizeMode="tail"/>
116
- </View>
244
+ {/* Row 2: Full URL Capsule Container (Clickable with link prompt) */}
245
+ <View style={styles.cardSlugBox}>
246
+ <Pressable onPress={handleOpenUrl} style={styles.slugLeft} hitSlop={6}>
247
+ <Text style={styles.slugTag}>URL ↗</Text>
248
+ <HighlightText text={item.url} search={searchStr} style={[styles.slugText, { color: AppColors.skyBlue, textDecorationLine: 'underline' }]} highlightStyle={styles.highlight} numberOfLines={2} ellipsizeMode="middle"/>
249
+ </Pressable>
117
250
 
118
- <View style={styles.slugRight}>
119
- {isJson && (<View style={[
120
- styles.chip,
121
- {
122
- backgroundColor: `${AppColors.darkOrange}12`,
123
- borderColor: `${AppColors.darkOrange}28`,
124
- },
125
- ]}>
126
- <Text style={[styles.chipText, { color: AppColors.darkOrange }]}>
127
- .json
128
- </Text>
129
- </View>)}
251
+ <View style={styles.slugRight}>
252
+ {isJson && (<View style={[
253
+ styles.chip,
254
+ {
255
+ backgroundColor: `${AppColors.darkOrange}12`,
256
+ borderColor: `${AppColors.darkOrange}28`,
257
+ },
258
+ ]}>
259
+ <Text style={[styles.chipText, { color: AppColors.darkOrange }]}>
260
+ .json
261
+ </Text>
262
+ </View>)}
130
263
 
131
- {item.duplicateCount != null && item.duplicateCount > 1 && (<View style={[
132
- styles.chip,
133
- {
134
- backgroundColor: `${AppColors.purple}12`,
135
- borderColor: `${AppColors.purple}28`,
136
- },
137
- ]}>
138
- <Text style={[
139
- styles.chipText,
140
- { color: AppColors.purple, fontWeight: '700' },
141
- ]}>
142
- ×{item.duplicateCount}
143
- </Text>
144
- </View>)}
145
- </View>
146
- </View>)}
264
+ {item.duplicateCount != null && item.duplicateCount > 1 && (<View style={[
265
+ styles.chip,
266
+ {
267
+ backgroundColor: `${AppColors.purple}12`,
268
+ borderColor: `${AppColors.purple}28`,
269
+ },
270
+ ]}>
271
+ <Text style={[
272
+ styles.chipText,
273
+ { color: AppColors.purple, fontWeight: '700' },
274
+ ]}>
275
+ ×{item.duplicateCount}
276
+ </Text>
277
+ </View>)}
278
+ </View>
279
+ </View>
147
280
 
148
281
  {/* Row 3: Footer (Timestamp on Left, Response Size & Duration on Right) */}
149
282
  <View style={styles.cardFooterRow}>