react-native-inapp-inspector 2.2.8 → 2.3.0

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.kt +22 -7
  2. package/dist/commonjs/components/AppHeaderLogo.js +45 -12
  3. package/dist/commonjs/components/BrandSquareIcon.js +92 -49
  4. package/dist/commonjs/components/EmptyState.d.ts +7 -2
  5. package/dist/commonjs/components/EmptyState.js +11 -5
  6. package/dist/commonjs/components/Inspector/AnalyticsTab.js +29 -18
  7. package/dist/commonjs/components/Inspector/ConsoleTab.js +6 -1
  8. package/dist/commonjs/components/Inspector/CrashDetail.js +43 -9
  9. package/dist/commonjs/components/Inspector/CrashTab.js +34 -3
  10. package/dist/commonjs/components/Inspector/DeviceInfoTab.js +80 -16
  11. package/dist/commonjs/components/Inspector/InspectorHeader.js +16 -3
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +3 -2
  13. package/dist/commonjs/components/Inspector/NetworkTab.js +103 -4
  14. package/dist/commonjs/components/Inspector/ReduxTab.js +19 -3
  15. package/dist/commonjs/components/Inspector/SettingsPanel.js +4 -4
  16. package/dist/commonjs/components/Inspector/StorageTab.js +19 -3
  17. package/dist/commonjs/components/JsonViewer.js +2 -0
  18. package/dist/commonjs/components/MetaAccordion.js +15 -3
  19. package/dist/commonjs/constants/version.d.ts +1 -1
  20. package/dist/commonjs/constants/version.js +1 -1
  21. package/dist/commonjs/customHooks/networkLogger.d.ts +3 -0
  22. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  23. package/dist/commonjs/helpers/index.d.ts +6 -0
  24. package/dist/commonjs/helpers/index.js +196 -6
  25. package/dist/commonjs/helpers/searchQueryParser.js +62 -20
  26. package/dist/commonjs/i18n/index.d.ts +13 -0
  27. package/dist/commonjs/i18n/index.js +35 -2
  28. package/dist/commonjs/index.d.ts +3 -0
  29. package/dist/commonjs/index.js +80 -11
  30. package/dist/commonjs/native/NativeInspector.js +13 -0
  31. package/dist/commonjs/native/NativeNetworkInspector.d.ts +1 -1
  32. package/dist/commonjs/styles/AppColors.d.ts +4 -0
  33. package/dist/commonjs/styles/AppColors.js +11 -1
  34. package/dist/commonjs/styles/AppFonts.d.ts +11 -0
  35. package/dist/commonjs/styles/AppFonts.js +11 -1
  36. package/dist/commonjs/types/interfaces.d.ts +1 -0
  37. package/dist/esm/components/AppHeaderLogo.js +45 -12
  38. package/dist/esm/components/BrandSquareIcon.js +92 -49
  39. package/dist/esm/components/EmptyState.d.ts +7 -2
  40. package/dist/esm/components/EmptyState.js +11 -5
  41. package/dist/esm/components/Inspector/AnalyticsTab.js +30 -19
  42. package/dist/esm/components/Inspector/ConsoleTab.js +6 -1
  43. package/dist/esm/components/Inspector/CrashDetail.js +43 -9
  44. package/dist/esm/components/Inspector/CrashTab.js +34 -3
  45. package/dist/esm/components/Inspector/DeviceInfoTab.js +80 -16
  46. package/dist/esm/components/Inspector/InspectorHeader.js +17 -4
  47. package/dist/esm/components/Inspector/NetworkDetail.js +3 -2
  48. package/dist/esm/components/Inspector/NetworkTab.js +103 -4
  49. package/dist/esm/components/Inspector/ReduxTab.js +19 -3
  50. package/dist/esm/components/Inspector/SettingsPanel.js +4 -4
  51. package/dist/esm/components/Inspector/StorageTab.js +19 -3
  52. package/dist/esm/components/JsonViewer.js +2 -0
  53. package/dist/esm/components/MetaAccordion.js +15 -3
  54. package/dist/esm/constants/version.d.ts +1 -1
  55. package/dist/esm/constants/version.js +1 -1
  56. package/dist/esm/customHooks/networkLogger.d.ts +3 -0
  57. package/dist/esm/customHooks/networkLogger.js +8 -0
  58. package/dist/esm/helpers/index.d.ts +6 -0
  59. package/dist/esm/helpers/index.js +193 -5
  60. package/dist/esm/helpers/searchQueryParser.js +62 -20
  61. package/dist/esm/i18n/index.d.ts +13 -0
  62. package/dist/esm/i18n/index.js +33 -3
  63. package/dist/esm/index.d.ts +3 -0
  64. package/dist/esm/index.js +68 -12
  65. package/dist/esm/native/NativeInspector.js +13 -0
  66. package/dist/esm/native/NativeNetworkInspector.d.ts +1 -1
  67. package/dist/esm/styles/AppColors.d.ts +4 -0
  68. package/dist/esm/styles/AppColors.js +9 -0
  69. package/dist/esm/styles/AppFonts.d.ts +11 -0
  70. package/dist/esm/styles/AppFonts.js +9 -0
  71. package/dist/esm/types/interfaces.d.ts +1 -0
  72. package/ios/NetworkInspectorModule.mm +11 -1
  73. package/package.json +1 -1
  74. package/src/components/AppHeaderLogo.tsx +46 -12
  75. package/src/components/BrandSquareIcon.tsx +127 -78
  76. package/src/components/EmptyState.tsx +27 -5
  77. package/src/components/Inspector/AnalyticsTab.tsx +48 -27
  78. package/src/components/Inspector/ConsoleTab.tsx +10 -0
  79. package/src/components/Inspector/CrashDetail.tsx +65 -9
  80. package/src/components/Inspector/CrashTab.tsx +41 -3
  81. package/src/components/Inspector/DeviceInfoTab.tsx +87 -16
  82. package/src/components/Inspector/InspectorHeader.tsx +24 -4
  83. package/src/components/Inspector/NetworkDetail.tsx +4 -4
  84. package/src/components/Inspector/NetworkTab.tsx +129 -4
  85. package/src/components/Inspector/ReduxTab.tsx +26 -8
  86. package/src/components/Inspector/SettingsPanel.tsx +4 -4
  87. package/src/components/Inspector/StorageTab.tsx +21 -3
  88. package/src/components/JsonViewer.tsx +2 -0
  89. package/src/components/MetaAccordion.tsx +18 -3
  90. package/src/constants/version.ts +1 -1
  91. package/src/customHooks/networkLogger.ts +15 -0
  92. package/src/helpers/index.ts +213 -5
  93. package/src/helpers/searchQueryParser.ts +63 -20
  94. package/src/i18n/index.ts +38 -3
  95. package/src/index.tsx +89 -11
  96. package/src/native/NativeInspector.ts +13 -0
  97. package/src/native/NativeNetworkInspector.ts +1 -1
  98. package/src/styles/AppColors.ts +9 -0
  99. package/src/styles/AppFonts.ts +16 -0
  100. package/src/types/interfaces.ts +1 -0
@@ -4,6 +4,7 @@ import { animateNextLayout, useInspector } from './InspectorContext';
4
4
  import AnalyticsEventCard from '../AnalyticsEventCard';
5
5
  import AnalyticsFilterModal from './AnalyticsFilterModal';
6
6
  import EndOfListFooter from '../EndOfListFooter';
7
+ import EmptyState from '../EmptyState';
7
8
  import styles from '../../styles';
8
9
  import { AppColors } from '../../styles/AppColors';
9
10
  import { AppFonts } from '../../styles/AppFonts';
@@ -12,7 +13,7 @@ import Svg, { Path, Rect, Line, Circle, G, Defs, LinearGradient as SvgLinearGrad
12
13
  import TouchableScale from '../TouchableScale';
13
14
  import { getCurrentUserId, getCurrentUserProperties, getDefaultEventParameters, getCollectionEnabled, } from '../../customHooks/analyticsLogger';
14
15
  import { getEventCategory, } from '../../helpers';
15
- import { LayersIcon, SearchIcon, ClearIcon, TrashIcon, EmptyRadarIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
16
+ import { LayersIcon, SearchIcon, ClearIcon, TrashIcon, HeaderPauseIcon, FilterIcon, CartIcon, GlobeIcon, BoltIcon, SparkleIcon, PinIcon, MoneyIcon, } from '../NetworkIcons';
16
17
  const AnalyticsHeader = React.memo(() => {
17
18
  const { t } = useTranslation();
18
19
  const { analyticsEvents, filteredAnalyticsEvents, analyticsFilters, setAnalyticsFilters, analyticsHeaderExpanded, setAnalyticsHeaderExpanded, } = useInspector();
@@ -850,9 +851,29 @@ const AnalyticsTab = React.memo(() => {
850
851
  <View style={styles.searchContainer}>
851
852
  <SearchIcon color={AppColors.grayTextWeak} size={16}/>
852
853
  <TextInput placeholder="Search events..." placeholderTextColor={AppColors.grayTextWeak} value={analyticsSearch} onChangeText={setAnalyticsSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
853
- {analyticsSearch.length > 0 && (<Pressable onPress={() => setAnalyticsSearch('')} hitSlop={10} style={styles.clearBtn}>
854
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
855
- </Pressable>)}
854
+ {analyticsSearch.length > 0 && (<View style={{
855
+ flexDirection: 'row',
856
+ alignItems: 'center',
857
+ gap: 4,
858
+ }}>
859
+ <View style={{
860
+ backgroundColor: `${AppColors.purple}20`,
861
+ borderRadius: 10,
862
+ paddingHorizontal: 6,
863
+ paddingVertical: 1.5,
864
+ }}>
865
+ <Text style={{
866
+ color: AppColors.purple,
867
+ fontSize: 9.5,
868
+ fontFamily: AppFonts.interBold,
869
+ }}>
870
+ {filteredAnalyticsEvents.length}
871
+ </Text>
872
+ </View>
873
+ <Pressable onPress={() => setAnalyticsSearch('')} hitSlop={10} style={styles.clearBtn}>
874
+ <ClearIcon color={AppColors.grayTextWeak} size={13}/>
875
+ </Pressable>
876
+ </View>)}
856
877
  </View>
857
878
  <View style={styles.toolbarRight}>
858
879
  <TouchableOpacity style={[
@@ -907,21 +928,11 @@ const AnalyticsTab = React.memo(() => {
907
928
  <AnalyticsFilterModal visible={isFilterModalOpen} onClose={() => setIsFilterModalOpen(false)}/>
908
929
 
909
930
  <View style={{ flex: 1 }} onLayout={() => setIsAnalyticsLayoutReady(true)}>
910
- {isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<View style={styles.emptyContainer}>
911
- <View style={styles.emptyIconWrap}>
912
- <EmptyRadarIcon color={AppColors.purple} size={32}/>
913
- </View>
914
- <Text style={styles.emptyTitle}>
915
- {analyticsSearch.length > 0
916
- ? 'No matching events'
917
- : 'No analytics events yet'}
918
- </Text>
919
- <Text style={styles.emptySub}>
920
- {analyticsSearch.length > 0
921
- ? 'Try adjusting your search.'
922
- : 'Call setupAnalyticsLogger(analytics()) at app start.'}
923
- </Text>
924
- </View>} ListFooterComponent={filteredAnalyticsEvents.length > 0 ? (<EndOfListFooter count={filteredAnalyticsEvents.length} label="events"/>) : null} contentContainerStyle={[
931
+ {isAnalyticsLayoutReady ? (<FlatList data={filteredAnalyticsEvents} keyExtractor={keyExtractor} ListHeaderComponent={AnalyticsHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={analyticsSearch.length > 0} searchQuery={analyticsSearch} customTitle={analyticsSearch.length > 0
932
+ ? 'No matching analytics events'
933
+ : 'No analytics events yet'} customSub={analyticsSearch.length > 0
934
+ ? `No events matched "${analyticsSearch}"`
935
+ : 'Call setupAnalyticsLogger(analytics()) at app start.'} onClearSearch={() => setAnalyticsSearch('')}/>} ListFooterComponent={filteredAnalyticsEvents.length > 0 ? (<EndOfListFooter count={filteredAnalyticsEvents.length} label="events"/>) : null} contentContainerStyle={[
925
936
  styles.listContent,
926
937
  filteredAnalyticsEvents.length === 0 && {
927
938
  flexGrow: 1,
@@ -480,7 +480,12 @@ const ConsoleTab = React.memo(() => {
480
480
  </ScrollView>
481
481
  </View>
482
482
 
483
- <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
483
+ <FlatList data={filteredConsoleLogs} keyExtractor={keyExtractor} ListHeaderComponent={listHeader} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState isSearch={logSearch.length > 0 || logFilters.size > 0} searchQuery={logSearch} customTitle={logSearch.length > 0
484
+ ? 'No matching console logs'
485
+ : 'No console logs'} onClearSearch={() => {
486
+ setLogSearch('');
487
+ setLogFilters(new Set());
488
+ }}/>} ListFooterComponent={filteredConsoleLogs.length > 0 ? (<EndOfListFooter count={filteredConsoleLogs.length} label="logs"/>) : null} contentContainerStyle={[
484
489
  styles.listContent,
485
490
  filteredConsoleLogs.length === 0 && { flexGrow: 1 },
486
491
  ]} keyboardShouldPersistTaps="handled"/>
@@ -6,6 +6,7 @@ import TouchableScale from '../TouchableScale';
6
6
  import SegmentedTabs from '../SegmentedTabs';
7
7
  import HighlightText from '../HighlightText';
8
8
  import JsonViewer from '../JsonViewer';
9
+ import CopyButton from '../CopyButton';
9
10
  import { AppColors } from '../../styles/AppColors';
10
11
  import { AppFonts } from '../../styles/AppFonts';
11
12
  import { exportCrashReport, parseCrashStackTrace, } from '../../customHooks/crashHandler';
@@ -328,9 +329,20 @@ const CrashDetail = React.memo(() => {
328
329
  {activeSubTab === 'diagnostics' && (<View style={{ gap: 12 }}>
329
330
  {/* Device & OS Diagnostics Card */}
330
331
  <View style={localStyles.sectionCard}>
331
- <View style={localStyles.sectionCardHeader}>
332
- <GlobeIcon color={AppColors.purple} size={14}/>
333
- <Text style={localStyles.sectionCardTitle}>{t('crash.deviceEnvironment')}</Text>
332
+ <View style={[
333
+ localStyles.sectionCardHeader,
334
+ { justifyContent: 'space-between' },
335
+ ]}>
336
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
337
+ <GlobeIcon color={AppColors.purple} size={14}/>
338
+ <Text style={localStyles.sectionCardTitle}>
339
+ {t('crash.deviceEnvironment')}
340
+ </Text>
341
+ </View>
342
+ <CopyButton value={selectedCrash.deviceInfo || {
343
+ platform: Platform.OS,
344
+ osVersion: String(Platform.Version),
345
+ }} label="Diagnostics"/>
334
346
  </View>
335
347
 
336
348
  <View style={localStyles.diagGrid}>
@@ -380,9 +392,17 @@ const CrashDetail = React.memo(() => {
380
392
 
381
393
  {/* Memory Metrics Card */}
382
394
  {selectedCrash.memoryInfo && (<View style={localStyles.sectionCard}>
383
- <View style={localStyles.sectionCardHeader}>
384
- <LayersIcon color={AppColors.purple} size={14}/>
385
- <Text style={localStyles.sectionCardTitle}>{t('crash.jsHeapMemory')}</Text>
395
+ <View style={[
396
+ localStyles.sectionCardHeader,
397
+ { justifyContent: 'space-between' },
398
+ ]}>
399
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
400
+ <LayersIcon color={AppColors.purple} size={14}/>
401
+ <Text style={localStyles.sectionCardTitle}>
402
+ {t('crash.jsHeapMemory')}
403
+ </Text>
404
+ </View>
405
+ <CopyButton value={selectedCrash.memoryInfo} label="Memory Info"/>
386
406
  </View>
387
407
 
388
408
  <View style={localStyles.diagGrid}>
@@ -405,6 +425,13 @@ const CrashDetail = React.memo(() => {
405
425
 
406
426
  {/* 3. BREADCRUMBS TAB */}
407
427
  {activeSubTab === 'breadcrumbs' && (<View style={{ gap: 10 }}>
428
+ {selectedCrash.breadcrumbs && selectedCrash.breadcrumbs.length > 0 && (<View style={{
429
+ flexDirection: 'row',
430
+ justifyContent: 'flex-end',
431
+ marginBottom: 2,
432
+ }}>
433
+ <CopyButton value={selectedCrash.breadcrumbs} label="All Breadcrumbs"/>
434
+ </View>)}
408
435
  {selectedCrash.breadcrumbs && selectedCrash.breadcrumbs.length > 0 ? (selectedCrash.breadcrumbs.map((b, idx) => (<View key={`crumb_${idx}`} style={localStyles.breadcrumbCard}>
409
436
  <View style={localStyles.breadcrumbHeader}>
410
437
  <View style={localStyles.breadcrumbTag}>
@@ -412,9 +439,16 @@ const CrashDetail = React.memo(() => {
412
439
  {b.type.toUpperCase()}
413
440
  </Text>
414
441
  </View>
415
- <Text style={localStyles.breadcrumbTime}>
416
- {new Date(b.timestamp).toLocaleTimeString()}
417
- </Text>
442
+ <View style={{
443
+ flexDirection: 'row',
444
+ alignItems: 'center',
445
+ gap: 8,
446
+ }}>
447
+ <Text style={localStyles.breadcrumbTime}>
448
+ {new Date(b.timestamp).toLocaleTimeString()}
449
+ </Text>
450
+ <CopyButton value={`${new Date(b.timestamp).toLocaleTimeString()} [${b.type.toUpperCase()}] ${b.message}${b.data ? ' ' + JSON.stringify(b.data) : ''}`} label="Breadcrumb"/>
451
+ </View>
418
452
  </View>
419
453
  <Text style={localStyles.breadcrumbMessage}>{b.message}</Text>
420
454
  {b.data && (<View style={{ marginTop: 6 }}>
@@ -307,9 +307,25 @@ const CrashTab = React.memo(() => {
307
307
  <View style={localStyles.searchInputWrapper}>
308
308
  <SearchIcon color={AppColors.grayTextWeak} size={14}/>
309
309
  <TextInput value={searchQuery} onChangeText={setSearchQuery} placeholder={t('crash.searchPlaceholder')} placeholderTextColor={AppColors.grayTextWeak} style={localStyles.searchInput} autoCapitalize="none" autoCorrect={false}/>
310
- {searchQuery.length > 0 && (<TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
311
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
312
- </TouchableOpacity>)}
310
+ {searchQuery.length > 0 && (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
311
+ <View style={{
312
+ backgroundColor: `${AppColors.purple}20`,
313
+ borderRadius: 10,
314
+ paddingHorizontal: 6,
315
+ paddingVertical: 1.5,
316
+ }}>
317
+ <Text style={{
318
+ color: AppColors.purple,
319
+ fontSize: 9.5,
320
+ fontFamily: AppFonts.interBold,
321
+ }}>
322
+ {filteredList.length}
323
+ </Text>
324
+ </View>
325
+ <TouchableOpacity onPress={() => setSearchQuery('')} hitSlop={8}>
326
+ <ClearIcon color={AppColors.grayTextWeak} size={14}/>
327
+ </TouchableOpacity>
328
+ </View>)}
313
329
  </View>
314
330
 
315
331
  {/* Crash Filters */}
@@ -381,6 +397,21 @@ const CrashTab = React.memo(() => {
381
397
  ? t('crash.emptySearchSubtitle')
382
398
  : t('crash.emptySubtitle')}
383
399
  </Text>
400
+ {searchQuery.length > 0 && (<TouchableScale onPress={() => setSearchQuery('')} style={{
401
+ marginTop: 10,
402
+ paddingHorizontal: 14,
403
+ paddingVertical: 7,
404
+ borderRadius: 8,
405
+ backgroundColor: AppColors.purple,
406
+ }}>
407
+ <Text style={{
408
+ fontFamily: AppFonts.interBold,
409
+ fontSize: 11.5,
410
+ color: AppColors.white,
411
+ }}>
412
+ Clear Search
413
+ </Text>
414
+ </TouchableScale>)}
384
415
  </ScrollView>) : (<FlatList data={filteredList} keyExtractor={item => item.id} renderItem={renderCard} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} contentContainerStyle={localStyles.listContent} showsVerticalScrollIndicator={false} ListFooterComponent={<EndOfListFooter />}/>)}
385
416
 
386
417
  {/* Filter Modal */}
@@ -21,9 +21,9 @@ const InfoRow = React.memo(({ label, value, copyable = true, badge, subtext, isL
21
21
  styles.infoRow,
22
22
  isLast && { borderBottomWidth: 0 },
23
23
  ]}>
24
- <View style={{ flex: 1, paddingRight: 8 }}>
25
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
26
- <Text style={styles.infoLabel}>{label}</Text>
24
+ <View style={{ flex: 1, paddingRight: 8, minWidth: 0 }}>
25
+ <View style={{ flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 6 }}>
26
+ <Text style={styles.infoLabel} numberOfLines={2}>{label}</Text>
27
27
  {badge && (<View style={[
28
28
  styles.infoBadge,
29
29
  { backgroundColor: badge.bg, borderColor: `${badge.color}33` },
@@ -33,15 +33,22 @@ const InfoRow = React.memo(({ label, value, copyable = true, badge, subtext, isL
33
33
  </Text>
34
34
  </View>)}
35
35
  </View>
36
- {subtext && <Text style={styles.infoSubtext}>{subtext}</Text>}
36
+ {subtext && <Text style={styles.infoSubtext} numberOfLines={2}>{subtext}</Text>}
37
37
  </View>
38
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
38
+ <View style={{
39
+ flexDirection: 'row',
40
+ alignItems: 'center',
41
+ gap: 6,
42
+ flexShrink: 0,
43
+ maxWidth: '58%',
44
+ justifyContent: 'flex-end',
45
+ }}>
39
46
  <Text style={[
40
47
  styles.infoValue,
41
48
  typeof value === 'boolean' && {
42
49
  color: value ? AppColors.emerald500 : AppColors.errorColor,
43
50
  },
44
- ]} numberOfLines={2} selectable>
51
+ ]} numberOfLines={3} selectable>
45
52
  {displayValue}
46
53
  </Text>
47
54
  {copyable && value !== null && value !== undefined && (<TouchableScale hitSlop={8} onPress={() => {
@@ -348,6 +355,14 @@ export const DeviceInfoTab = React.memo(() => {
348
355
  return true;
349
356
  return text.toLowerCase().includes(search.toLowerCase().trim());
350
357
  };
358
+ const hasAnyMatch = useMemo(() => {
359
+ if (!search.trim())
360
+ return true;
361
+ const query = search.toLowerCase().trim();
362
+ return (JSON.stringify(fullDeviceData).toLowerCase().includes(query) ||
363
+ ipAddress.toLowerCase().includes(query) ||
364
+ pseudoUDID.toLowerCase().includes(query));
365
+ }, [search, fullDeviceData, ipAddress, pseudoUDID]);
351
366
  return (<View style={styles.container}>
352
367
  {/* ─── Top Sub-Tabs Navigation Bar ─── */}
353
368
  <View style={styles.subTabsWrapper}>
@@ -397,11 +412,11 @@ export const DeviceInfoTab = React.memo(() => {
397
412
  <View style={styles.heroIconWrap}>
398
413
  {Platform.OS === 'ios' ? (<AppleIcon size={20} color={AppColors.white}/>) : (<AndroidIcon size={20} color={AppColors.white}/>)}
399
414
  </View>
400
- <View style={{ flex: 1 }}>
401
- <Text style={styles.heroTitle}>
415
+ <View style={{ flex: 1, minWidth: 0 }}>
416
+ <Text style={styles.heroTitle} numberOfLines={1} ellipsizeMode="tail">
402
417
  {deviceMetrics?.deviceModel || Platform.constants?.Model || (Platform.OS === 'ios' ? 'Apple iPhone' : 'Android Device')}
403
418
  </Text>
404
- <Text style={styles.heroSubtitle}>
419
+ <Text style={styles.heroSubtitle} numberOfLines={1} ellipsizeMode="tail">
405
420
  {Platform.OS === 'ios' ? `iOS ${Platform.Version}` : `Android ${Platform.Version} (API ${deviceMetrics?.apiLevel || Platform.Version})`}
406
421
  </Text>
407
422
  </View>
@@ -414,22 +429,22 @@ export const DeviceInfoTab = React.memo(() => {
414
429
  {/* Quick Metrics Strip */}
415
430
  <View style={styles.heroMetricsStrip}>
416
431
  <View style={styles.heroMetricItem}>
417
- <Text style={styles.heroMetricLabel}>IP ADDRESS</Text>
418
- <Text style={styles.heroMetricValue} numberOfLines={1}>
432
+ <Text style={styles.heroMetricLabel} numberOfLines={1} ellipsizeMode="tail">IP ADDRESS</Text>
433
+ <Text style={styles.heroMetricValue} numberOfLines={1} ellipsizeMode="tail">
419
434
  {ipAddress}
420
435
  </Text>
421
436
  </View>
422
437
  <View style={styles.heroMetricDivider}/>
423
438
  <View style={styles.heroMetricItem}>
424
- <Text style={styles.heroMetricLabel}>RAM (USED/TOTAL)</Text>
425
- <Text style={styles.heroMetricValue}>
439
+ <Text style={styles.heroMetricLabel} numberOfLines={1} ellipsizeMode="tail">RAM (USED/TOTAL)</Text>
440
+ <Text style={styles.heroMetricValue} numberOfLines={1} ellipsizeMode="tail">
426
441
  {usedRamMb}/{totalRamMb} MB
427
442
  </Text>
428
443
  </View>
429
444
  <View style={styles.heroMetricDivider}/>
430
445
  <View style={styles.heroMetricItem}>
431
- <Text style={styles.heroMetricLabel}>UPTIME</Text>
432
- <Text style={styles.heroMetricValue}>{deviceUptime}</Text>
446
+ <Text style={styles.heroMetricLabel} numberOfLines={1} ellipsizeMode="tail">UPTIME</Text>
447
+ <Text style={styles.heroMetricValue} numberOfLines={1} ellipsizeMode="tail">{deviceUptime}</Text>
433
448
  </View>
434
449
  </View>
435
450
  </View>
@@ -542,6 +557,18 @@ export const DeviceInfoTab = React.memo(() => {
542
557
  {isMatch('Sandbox Integrity') && (<InfoRow label="App Sandbox File Isolation" value="Enforced by OS Kernel" isLast/>)}
543
558
  </View>)}
544
559
 
560
+ {/* Empty search results state */}
561
+ {search.trim().length > 0 && !hasAnyMatch && (<View style={styles.emptySearchCard}>
562
+ <SearchIcon size={26} color={AppColors.grayTextWeak}/>
563
+ <Text style={styles.emptySearchTitle}>No matching device specifications</Text>
564
+ <Text style={styles.emptySearchSubtitle}>
565
+ No properties matched &ldquo;{search}&rdquo;
566
+ </Text>
567
+ <TouchableScale onPress={() => setSearch('')} style={styles.clearSearchBtn}>
568
+ <Text style={styles.clearSearchBtnText}>Clear Search</Text>
569
+ </TouchableScale>
570
+ </View>)}
571
+
545
572
  <View style={{ height: 48 }}/>
546
573
  </ScrollView>
547
574
  </View>);
@@ -713,6 +740,8 @@ const styles = StyleSheet.create({
713
740
  heroMetricItem: {
714
741
  flex: 1,
715
742
  alignItems: 'center',
743
+ paddingHorizontal: 2,
744
+ minWidth: 0,
716
745
  },
717
746
  heroMetricLabel: {
718
747
  fontFamily: AppFonts.interBold,
@@ -784,7 +813,7 @@ const styles = StyleSheet.create({
784
813
  fontFamily: AppFonts.interBold,
785
814
  fontSize: 12,
786
815
  color: AppColors.primaryBlack,
787
- maxWidth: 180,
816
+ flexShrink: 1,
788
817
  textAlign: 'right',
789
818
  },
790
819
  rowCopyBtn: {
@@ -792,5 +821,40 @@ const styles = StyleSheet.create({
792
821
  borderRadius: 4,
793
822
  backgroundColor: AppColors.grayBackground,
794
823
  },
824
+ emptySearchCard: {
825
+ backgroundColor: AppColors.primaryLight,
826
+ borderRadius: 14,
827
+ borderWidth: 1,
828
+ borderColor: AppColors.grayBorderSecondary,
829
+ padding: 24,
830
+ alignItems: 'center',
831
+ justifyContent: 'center',
832
+ gap: 8,
833
+ marginTop: 8,
834
+ },
835
+ emptySearchTitle: {
836
+ fontFamily: AppFonts.interBold,
837
+ fontSize: 13.5,
838
+ color: AppColors.primaryBlack,
839
+ textAlign: 'center',
840
+ },
841
+ emptySearchSubtitle: {
842
+ fontFamily: AppFonts.interRegular,
843
+ fontSize: 11.5,
844
+ color: AppColors.grayTextWeak,
845
+ textAlign: 'center',
846
+ },
847
+ clearSearchBtn: {
848
+ marginTop: 6,
849
+ paddingHorizontal: 14,
850
+ paddingVertical: 6.5,
851
+ borderRadius: 8,
852
+ backgroundColor: AppColors.purple,
853
+ },
854
+ clearSearchBtnText: {
855
+ fontFamily: AppFonts.interBold,
856
+ fontSize: 11,
857
+ color: AppColors.white,
858
+ },
795
859
  });
796
860
  export default DeviceInfoTab;
@@ -9,12 +9,27 @@ import { AppColors } from '../../styles/AppColors';
9
9
  import { AppFonts } from '../../styles/AppFonts';
10
10
  import { METHOD_COLORS } from '../../constants';
11
11
  import { LIB_VERSION } from '../../constants';
12
- import { getStatusColor, getAppName, formatTime, getSize } from '../../helpers';
12
+ import { getStatusColor, getAppName, formatTime, getSize, getAppVersionAndBuild, } from '../../helpers';
13
+ import { getNativeDeviceMetrics } from '../../native/NativeInspector';
13
14
  import { UpdateAvailableModal } from './UpdateAvailableModal';
14
15
  import { WhiteBackNavigation, TrashIcon, SettingsIcon, CloseWhite, ClockIcon, SizeIcon, AppleIcon, AndroidIcon, NpmIcon, ResetIcon, BoltIcon, } from '../NetworkIcons';
15
16
  const InspectorHeader = React.memo(() => {
16
17
  const { modalHeightPercent, appIcon, selected, setSelected, selectedEvent, setSelectedEvent, selectedLog, setSelectedLog, selectedReduxSlice, setSelectedReduxSlice, selectedReduxAction, setSelectedReduxAction, reduxState, reduxLastActionMap, showHeaderInfo, setShowHeaderInfo, updateAvailable, latestNpmVersion, clearAnim, activePulseAnim, unreadPulseAnim, runClearAllWithAnimation, settingsPage, setSettingsPage, resetToDefaults, closeModal, detailTitle, activeTab, environment, visible, selectedCrash, setSelectedCrash, } = useInspector();
17
18
  const [showUpdateModal, setShowUpdateModal] = React.useState(false);
19
+ const [appVersionString, setAppVersionString] = React.useState(() => {
20
+ return getAppVersionAndBuild().formatted;
21
+ });
22
+ React.useEffect(() => {
23
+ getNativeDeviceMetrics()
24
+ .then(metrics => {
25
+ if (metrics?.appVersion) {
26
+ const v = metrics.appVersion;
27
+ const b = metrics.appBuild || '1';
28
+ setAppVersionString(`${v} (${b})`);
29
+ }
30
+ })
31
+ .catch(() => { });
32
+ }, []);
18
33
  const envConfig = useMemo(() => {
19
34
  const rawEnv = (environment || (__DEV__ ? 'DEV' : 'PROD')).trim();
20
35
  const clean = rawEnv.toUpperCase();
@@ -265,9 +280,7 @@ const InspectorHeader = React.memo(() => {
265
280
  color: `${AppColors.white}EB`,
266
281
  letterSpacing: 0.1,
267
282
  }} numberOfLines={1}>
268
- {Platform.OS === 'ios'
269
- ? `iOS ${Platform.Version}`
270
- : `Android ${Platform.Version}`}
283
+ {appVersionString}
271
284
  </Text>
272
285
  </View>
273
286
 
@@ -396,8 +396,9 @@ const NetworkDetail = React.memo(() => {
396
396
  selected.responseHeaders?.['Content-Type']} url={selected.url}/>
397
397
 
398
398
  {(() => {
399
- const routeInfo = logRouteMapRef.current.get(selected.id);
400
- if (!routeInfo || routeInfo.path === 'Navigators')
399
+ const routeInfo = logRouteMapRef.current.get(selected.id) ||
400
+ selected?.routeInfo;
401
+ if (!routeInfo || !routeInfo.path || routeInfo.path === 'Navigators')
401
402
  return null;
402
403
  return <SourcePageCard routeInfo={routeInfo}/>;
403
404
  })()}
@@ -469,7 +469,7 @@ const NetworkTab = React.memo(() => {
469
469
  <View style={{
470
470
  backgroundColor: `${AppColors.purple}20`,
471
471
  borderRadius: 10,
472
- paddingHorizontal: 5,
472
+ paddingHorizontal: 6,
473
473
  paddingVertical: 1.5,
474
474
  }}>
475
475
  <Text style={{
@@ -522,14 +522,104 @@ const NetworkTab = React.memo(() => {
522
522
  styles.toolbarBtn,
523
523
  filtersAccordion.isOpen &&
524
524
  styles.toolbarBtnActive,
525
- ]} onPress={filtersAccordion.toggleOpen} hitSlop={10}>
525
+ ]} onPress={filtersAccordion.toggleOpen} hitSlop={6}>
526
526
  <FilterIcon color={filtersAccordion.isOpen
527
527
  ? AppColors.purple
528
- : AppColors.grayTextStrong} size={18}/>
528
+ : AppColors.grayTextStrong} size={16}/>
529
+ {(statusFilters.size > 0 ||
530
+ methodFilters.size > 0) && (<View style={styles.activeFilterDot}/>)}
529
531
  </TouchableScale>
530
532
  </View>
531
533
  </View>
532
534
 
535
+ {/* Quick Search Scope & Filter Tags Row */}
536
+ <View style={{ paddingBottom: 4 }}>
537
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
538
+ paddingHorizontal: 12,
539
+ flexDirection: 'row',
540
+ alignItems: 'center',
541
+ gap: 5,
542
+ paddingVertical: 2,
543
+ }}>
544
+ {/* Search Scopes */}
545
+ {SEARCH_SCOPES.map(s => {
546
+ const isActive = searchScope === s.id;
547
+ return (<TouchableScale key={`scope_${s.id}`} onPress={() => setSearchScope(s.id)} hitSlop={6} style={{
548
+ flexDirection: 'row',
549
+ alignItems: 'center',
550
+ gap: 3.5,
551
+ paddingHorizontal: 7.5,
552
+ paddingVertical: 3.5,
553
+ borderRadius: 6,
554
+ backgroundColor: isActive
555
+ ? AppColors.purple
556
+ : AppColors.grayBackground,
557
+ borderWidth: 1,
558
+ borderColor: isActive
559
+ ? AppColors.purple
560
+ : AppColors.grayBorderSecondary,
561
+ }}>
562
+ {s.icon(isActive ? AppColors.white : AppColors.grayTextWeak)}
563
+ <Text style={{
564
+ fontFamily: AppFonts.interBold,
565
+ fontSize: 10,
566
+ color: isActive ? AppColors.white : AppColors.grayText,
567
+ }}>
568
+ {s.label}
569
+ </Text>
570
+ </TouchableScale>);
571
+ })}
572
+
573
+ <View style={{
574
+ width: 1,
575
+ height: 14,
576
+ backgroundColor: AppColors.dividerColor,
577
+ marginHorizontal: 3,
578
+ }}/>
579
+
580
+ {/* Quick Query Suggestions */}
581
+ {QUICK_API_QUERY_TAGS.map(q => {
582
+ const isActive = search.includes(q.query);
583
+ return (<TouchableScale key={`quick_${q.query}`} onPress={() => {
584
+ setSearch(prev => {
585
+ if (prev.includes(q.query)) {
586
+ return prev
587
+ .replace(q.query, '')
588
+ .replace(/\s+/g, ' ')
589
+ .trim();
590
+ }
591
+ return `${prev} ${q.query}`.trim();
592
+ });
593
+ }} hitSlop={6} style={{
594
+ flexDirection: 'row',
595
+ alignItems: 'center',
596
+ gap: 3.5,
597
+ paddingHorizontal: 7.5,
598
+ paddingVertical: 3.5,
599
+ borderRadius: 6,
600
+ backgroundColor: isActive
601
+ ? `${AppColors.purple}20`
602
+ : AppColors.grayBackground,
603
+ borderWidth: 1,
604
+ borderColor: isActive
605
+ ? AppColors.purple
606
+ : AppColors.grayBorderSecondary,
607
+ }}>
608
+ {q.icon(isActive ? AppColors.purple : AppColors.grayTextWeak)}
609
+ <Text style={{
610
+ fontFamily: AppFonts.interMedium,
611
+ fontSize: 10,
612
+ color: isActive
613
+ ? AppColors.purple
614
+ : AppColors.grayText,
615
+ }}>
616
+ {q.label}
617
+ </Text>
618
+ </TouchableScale>);
619
+ })}
620
+ </ScrollView>
621
+ </View>
622
+
533
623
  {/* Quick Filter Horizontal Chips Bar with Live Counts */}
534
624
  <View style={{ marginBottom: 8 }}>
535
625
  <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{
@@ -815,7 +905,16 @@ const NetworkTab = React.memo(() => {
815
905
  ? `${logs.length} requests`
816
906
  : `${filteredLogs.length} of ${logs.length} filtered requests`}
817
907
  </Text>)}
818
- </View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 || statusFilters.size > 0}/>} ListFooterComponent={groupedData.length > 0 ? (<EndOfListFooter count={filteredLogs.length} label="requests"/>) : null} contentContainerStyle={[
908
+ </View>} ListEmptyComponent={<EmptyState isSearch={search.length > 0 ||
909
+ statusFilters.size > 0 ||
910
+ methodFilters.size > 0} searchQuery={search} customTitle={search.length > 0
911
+ ? 'No matching API requests'
912
+ : 'No network activity'} onClearSearch={() => {
913
+ setSearch('');
914
+ setStatusFilters(new Set());
915
+ setMethodFilters(new Set());
916
+ setSearchScope('all');
917
+ }}/>} ListFooterComponent={groupedData.length > 0 ? (<EndOfListFooter count={filteredLogs.length} label="requests"/>) : null} contentContainerStyle={[
819
918
  styles.listContent,
820
919
  filteredLogs.length === 0 && { flexGrow: 1 },
821
920
  ]} keyboardShouldPersistTaps="handled"/>
@@ -396,9 +396,25 @@ const ReduxTab = React.memo(() => {
396
396
  <View style={styles.searchContainer}>
397
397
  <SearchIcon color={AppColors.grayTextWeak} size={15}/>
398
398
  <TextInput accessible={true} accessibilityRole="search" accessibilityLabel="Search Redux slices or actions" accessibilityHint="Type to filter state slices in real time" placeholder="Search Redux slices or actions..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={styles.searchInput} autoCorrect={false} autoCapitalize="none"/>
399
- {reduxSearch.length > 0 && (<Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Clear search input" onPress={() => setReduxSearch('')} hitSlop={10}>
400
- <ClearIcon color={AppColors.grayTextWeak} size={14}/>
401
- </Pressable>)}
399
+ {reduxSearch.length > 0 && (<View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
400
+ <View style={{
401
+ backgroundColor: `${AppColors.purple}20`,
402
+ borderRadius: 10,
403
+ paddingHorizontal: 6,
404
+ paddingVertical: 1.5,
405
+ }}>
406
+ <Text style={{
407
+ color: AppColors.purple,
408
+ fontSize: 9.5,
409
+ fontFamily: AppFonts.interBold,
410
+ }}>
411
+ {filteredSlices.length}
412
+ </Text>
413
+ </View>
414
+ <Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Clear search input" onPress={() => setReduxSearch('')} hitSlop={10}>
415
+ <ClearIcon color={AppColors.grayTextWeak} size={14}/>
416
+ </Pressable>
417
+ </View>)}
402
418
  </View>
403
419
 
404
420
  {/* Pause / Resume Live Updates Button */}
@@ -108,8 +108,8 @@ const SettingsPanel = () => {
108
108
  bundle: Boolean(tabVisibility?.bundle),
109
109
  performance: Boolean(tabVisibility?.performance),
110
110
  crash: Boolean(tabVisibility?.crash),
111
- device: Boolean(tabVisibility?.device ?? true),
112
- storage: Boolean(tabVisibility?.storage ?? true),
111
+ device: Boolean(tabVisibility?.device),
112
+ storage: Boolean(tabVisibility?.storage),
113
113
  }));
114
114
  // Synchronize staged state with tabVisibility when tabVisibility updates
115
115
  useEffect(() => {
@@ -121,8 +121,8 @@ const SettingsPanel = () => {
121
121
  bundle: Boolean(tabVisibility?.bundle),
122
122
  performance: Boolean(tabVisibility?.performance),
123
123
  crash: Boolean(tabVisibility?.crash),
124
- device: Boolean(tabVisibility?.device ?? true),
125
- storage: Boolean(tabVisibility?.storage ?? true),
124
+ device: Boolean(tabVisibility?.device),
125
+ storage: Boolean(tabVisibility?.storage),
126
126
  });
127
127
  }, [tabVisibility]);
128
128
  const hasUnsavedChanges = useMemo(() => {