react-native-inapp-inspector 1.1.35 → 2.0.1

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 (104) hide show
  1. package/README.md +123 -101
  2. package/android/build.gradle +13 -1
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +890 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.kt +17 -0
  5. package/dist/commonjs/components/ConsoleLogCard.js +5 -5
  6. package/dist/commonjs/components/CopyButton.js +3 -0
  7. package/dist/commonjs/components/EndOfListFooter.js +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +3 -0
  9. package/dist/commonjs/components/Inspector/AnalyticsTab.js +25 -73
  10. package/dist/commonjs/components/Inspector/BundleTab.js +671 -189
  11. package/dist/commonjs/components/Inspector/ConsoleTab.js +110 -99
  12. package/dist/commonjs/components/Inspector/CrashTab.js +99 -91
  13. package/dist/commonjs/components/Inspector/InspectorHeader.js +107 -20
  14. package/dist/commonjs/components/Inspector/MainScreen.js +111 -68
  15. package/dist/commonjs/components/Inspector/NetworkTab.js +151 -30
  16. package/dist/commonjs/components/Inspector/PerformanceTab.js +96 -3
  17. package/dist/commonjs/components/Inspector/ReduxTab.js +18 -10
  18. package/dist/commonjs/components/Inspector/SettingsPanel.js +1218 -1238
  19. package/dist/commonjs/components/Inspector/TabBar.js +4 -2
  20. package/dist/commonjs/components/JsonViewer.js +9 -4
  21. package/dist/commonjs/components/LogCard.js +31 -1
  22. package/dist/commonjs/components/LogSyntaxHighlighter.d.ts +16 -0
  23. package/dist/commonjs/components/LogSyntaxHighlighter.js +130 -0
  24. package/dist/commonjs/components/NetworkIcons.d.ts +3 -0
  25. package/dist/commonjs/components/NetworkIcons.js +15 -1
  26. package/dist/commonjs/components/Slider.d.ts +13 -0
  27. package/dist/commonjs/components/Slider.js +261 -0
  28. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  29. package/dist/commonjs/components/TouchableScale.js +30 -32
  30. package/dist/commonjs/constants/index.d.ts +1 -2
  31. package/dist/commonjs/constants/index.js +4 -0
  32. package/dist/commonjs/constants/version.d.ts +1 -1
  33. package/dist/commonjs/constants/version.js +1 -1
  34. package/dist/commonjs/customHooks/analyticsLogger.d.ts +4 -0
  35. package/dist/commonjs/customHooks/analyticsLogger.js +8 -6
  36. package/dist/commonjs/customHooks/crashHandler.js +8 -1
  37. package/dist/commonjs/helpers/index.d.ts +1 -0
  38. package/dist/commonjs/helpers/index.js +25 -1
  39. package/dist/commonjs/helpers/searchQueryParser.d.ts +6 -0
  40. package/dist/commonjs/helpers/searchQueryParser.js +236 -0
  41. package/dist/commonjs/helpers/settingsStore.d.ts +9 -0
  42. package/dist/commonjs/helpers/settingsStore.js +59 -3
  43. package/dist/commonjs/index.d.ts +1 -0
  44. package/dist/commonjs/index.js +220 -43
  45. package/dist/commonjs/native/NativeInspector.d.ts +125 -0
  46. package/dist/commonjs/native/NativeInspector.js +313 -0
  47. package/dist/commonjs/native/NativeNetworkInspector.d.ts +22 -0
  48. package/dist/commonjs/native/NativeNetworkInspector.js +4 -0
  49. package/dist/commonjs/styles/index.js +41 -41
  50. package/dist/commonjs/types/enums.d.ts +1 -1
  51. package/dist/commonjs/types/interfaces.d.ts +9 -0
  52. package/dist/esm/components/ConsoleLogCard.js +5 -5
  53. package/dist/esm/components/CopyButton.js +3 -0
  54. package/dist/esm/components/EndOfListFooter.js +2 -2
  55. package/dist/esm/components/ErrorBoundary.js +3 -0
  56. package/dist/esm/components/Inspector/AnalyticsTab.js +27 -75
  57. package/dist/esm/components/Inspector/BundleTab.js +671 -189
  58. package/dist/esm/components/Inspector/ConsoleTab.js +111 -100
  59. package/dist/esm/components/Inspector/CrashTab.js +99 -91
  60. package/dist/esm/components/Inspector/InspectorHeader.js +108 -21
  61. package/dist/esm/components/Inspector/MainScreen.js +113 -70
  62. package/dist/esm/components/Inspector/NetworkTab.js +152 -31
  63. package/dist/esm/components/Inspector/PerformanceTab.js +96 -3
  64. package/dist/esm/components/Inspector/ReduxTab.js +18 -10
  65. package/dist/esm/components/Inspector/SettingsPanel.js +1222 -1242
  66. package/dist/esm/components/Inspector/TabBar.js +4 -2
  67. package/dist/esm/components/JsonViewer.js +9 -4
  68. package/dist/esm/components/LogCard.js +32 -2
  69. package/dist/esm/components/LogSyntaxHighlighter.d.ts +16 -0
  70. package/dist/esm/components/LogSyntaxHighlighter.js +123 -0
  71. package/dist/esm/components/NetworkIcons.d.ts +3 -0
  72. package/dist/esm/components/NetworkIcons.js +11 -0
  73. package/dist/esm/components/Slider.d.ts +13 -0
  74. package/dist/esm/components/Slider.js +222 -0
  75. package/dist/esm/components/TouchableScale.d.ts +1 -0
  76. package/dist/esm/components/TouchableScale.js +31 -33
  77. package/dist/esm/constants/index.d.ts +1 -2
  78. package/dist/esm/constants/index.js +4 -0
  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 +4 -0
  82. package/dist/esm/customHooks/analyticsLogger.js +6 -5
  83. package/dist/esm/customHooks/crashHandler.js +8 -1
  84. package/dist/esm/helpers/index.d.ts +1 -0
  85. package/dist/esm/helpers/index.js +11 -1
  86. package/dist/esm/helpers/searchQueryParser.d.ts +6 -0
  87. package/dist/esm/helpers/searchQueryParser.js +233 -0
  88. package/dist/esm/helpers/settingsStore.d.ts +9 -0
  89. package/dist/esm/helpers/settingsStore.js +58 -3
  90. package/dist/esm/index.d.ts +1 -0
  91. package/dist/esm/index.js +207 -44
  92. package/dist/esm/native/NativeInspector.d.ts +125 -0
  93. package/dist/esm/native/NativeInspector.js +289 -0
  94. package/dist/esm/native/NativeNetworkInspector.d.ts +22 -0
  95. package/dist/esm/native/NativeNetworkInspector.js +2 -0
  96. package/dist/esm/styles/index.js +41 -41
  97. package/dist/esm/types/enums.d.ts +1 -1
  98. package/dist/esm/types/interfaces.d.ts +9 -0
  99. package/ios/NetworkInspectorModule.h +6 -0
  100. package/ios/NetworkInspectorModule.m +760 -5
  101. package/package.json +13 -2
  102. package/react-native-inapp-inspector.podspec +7 -2
  103. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +0 -97
  104. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +0 -28
@@ -55,39 +55,70 @@ const NetworkIcons_1 = require("../NetworkIcons");
55
55
  const STATUS_META = {
56
56
  ALL: { color: AppColors_1.AppColors.grayText, Icon: NetworkIcons_1.LayersIcon },
57
57
  '2xx': { color: AppColors_1.AppColors.greenColor, Icon: NetworkIcons_1.CircleCheckIcon },
58
+ '200': { color: AppColors_1.AppColors.greenColor, Icon: NetworkIcons_1.CircleCheckIcon },
58
59
  '3xx': { color: AppColors_1.AppColors.warningIconGold, Icon: NetworkIcons_1.CircleAlertIcon },
60
+ '300': { color: AppColors_1.AppColors.warningIconGold, Icon: NetworkIcons_1.CircleAlertIcon },
59
61
  '4xx': { color: AppColors_1.AppColors.darkOrange, Icon: NetworkIcons_1.CircleAlertIcon },
62
+ '400': { color: AppColors_1.AppColors.darkOrange, Icon: NetworkIcons_1.CircleAlertIcon },
63
+ '404': { color: AppColors_1.AppColors.darkOrange, Icon: NetworkIcons_1.CircleAlertIcon },
60
64
  '5xx': { color: AppColors_1.AppColors.errorColor, Icon: NetworkIcons_1.CircleXIcon },
65
+ '500': { color: AppColors_1.AppColors.errorColor, Icon: NetworkIcons_1.CircleXIcon },
61
66
  Failed: { color: AppColors_1.AppColors.errorColor, Icon: NetworkIcons_1.CircleXIcon },
62
67
  };
68
+ const getStatusMeta = (filter) => {
69
+ if (STATUS_META[filter])
70
+ return STATUS_META[filter];
71
+ const num = parseInt(filter, 10);
72
+ if (!isNaN(num)) {
73
+ if (num >= 200 && num < 300)
74
+ return { color: AppColors_1.AppColors.greenColor, Icon: NetworkIcons_1.CircleCheckIcon };
75
+ if (num >= 300 && num < 400)
76
+ return { color: AppColors_1.AppColors.warningIconGold, Icon: NetworkIcons_1.CircleAlertIcon };
77
+ if (num >= 400 && num < 500)
78
+ return { color: AppColors_1.AppColors.darkOrange, Icon: NetworkIcons_1.CircleAlertIcon };
79
+ if (num >= 500)
80
+ return { color: AppColors_1.AppColors.errorColor, Icon: NetworkIcons_1.CircleXIcon };
81
+ }
82
+ if (filter.startsWith('2'))
83
+ return { color: AppColors_1.AppColors.greenColor, Icon: NetworkIcons_1.CircleCheckIcon };
84
+ if (filter.startsWith('3'))
85
+ return { color: AppColors_1.AppColors.warningIconGold, Icon: NetworkIcons_1.CircleAlertIcon };
86
+ if (filter.startsWith('4'))
87
+ return { color: AppColors_1.AppColors.darkOrange, Icon: NetworkIcons_1.CircleAlertIcon };
88
+ if (filter.startsWith('5'))
89
+ return { color: AppColors_1.AppColors.errorColor, Icon: NetworkIcons_1.CircleXIcon };
90
+ return { color: AppColors_1.AppColors.grayText, Icon: NetworkIcons_1.LayersIcon };
91
+ };
63
92
  const FilterChip = react_1.default.memo(({ label, color, Icon, badge, active, onPress, }) => {
64
93
  const iconColor = active ? color : AppColors_1.AppColors.grayTextWeak;
65
94
  return (<TouchableScale_1.default style={styles_1.default.statusFilterWrap} onPress={onPress} hitSlop={10}>
66
95
  <react_native_1.View style={[
67
96
  styles_1.default.statusFilterChip,
97
+ {
98
+ paddingHorizontal: 9,
99
+ paddingVertical: 5,
100
+ borderRadius: 7,
101
+ borderWidth: 1,
102
+ borderColor: active ? color : `${AppColors_1.AppColors.grayBorderSecondary}`,
103
+ backgroundColor: active ? `${color}14` : AppColors_1.AppColors.grayBackground,
104
+ },
68
105
  active && {
69
- borderColor: color,
70
- backgroundColor: `${color}12`,
106
+ shadowColor: color,
107
+ shadowOffset: { width: 0, height: 1 },
108
+ shadowOpacity: 0.2,
109
+ shadowRadius: 2,
110
+ elevation: 1.5,
71
111
  },
72
112
  ]}>
73
- <react_native_1.View style={[
74
- styles_1.default.filterCheckbox,
75
- active && [
76
- styles_1.default.filterCheckboxActive,
77
- { backgroundColor: color },
78
- ],
79
- ]}>
80
- {active && (<NetworkIcons_1.CheckIcon color={AppColors_1.AppColors.white} size={10}/>)}
81
- </react_native_1.View>
82
113
  {badge != null ? (<react_native_1.View style={[
83
114
  styles_1.default.methodBadgeMini,
84
115
  { backgroundColor: badge },
85
116
  ]}/>) : (Icon && (<Icon color={iconColor} size={13}/>))}
86
117
  <react_native_1.Text numberOfLines={1} style={[
87
118
  styles_1.default.statusFilterText,
88
- active && {
89
- color,
90
- fontFamily: AppFonts_1.AppFonts.interBold,
119
+ {
120
+ color: active ? color : AppColors_1.AppColors.grayText,
121
+ fontFamily: active ? AppFonts_1.AppFonts.interBold : AppFonts_1.AppFonts.interMedium,
91
122
  },
92
123
  ]}>
93
124
  {label}
@@ -100,6 +131,19 @@ const NetworkTab = react_1.default.memo(() => {
100
131
  const { t } = (0, i18n_1.useTranslation)();
101
132
  const filtersAccordion = (0, useAccordion_1.default)(false, 300, 260);
102
133
  const apisListRef = (0, react_1.useRef)(null);
134
+ const [isSearchFocused, setIsSearchFocused] = react_1.default.useState(false);
135
+ const QUICK_API_QUERY_TAGS = (0, react_1.useMemo)(() => [
136
+ { label: '⚠️ Failed', query: 'is:error' },
137
+ { label: '🐢 Slow >1s', query: 'slow:>1s' },
138
+ { label: 'POST', query: 'm:POST' },
139
+ { label: 'GET', query: 'm:GET' },
140
+ { label: 'GraphQL', query: 'is:graphql' },
141
+ { label: 'Axios', query: 'client:axios' },
142
+ { label: '200 OK', query: 'status:200' },
143
+ { label: '404', query: 'status:404' },
144
+ { label: '500', query: 'status:500' },
145
+ { label: '🔒 HTTPS', query: 'is:https' },
146
+ ], []);
103
147
  const networkMetrics = (0, react_1.useMemo)(() => {
104
148
  if (!logs || logs.length === 0)
105
149
  return null;
@@ -172,7 +216,7 @@ const NetworkTab = react_1.default.memo(() => {
172
216
  setSelected,
173
217
  ]);
174
218
  return (<react_native_1.View style={{ flex: 1 }}>
175
- <react_native_1.FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={10} maxToRenderPerBatch={10} windowSize={5} removeClippedSubviews={true} ListHeaderComponent={<react_native_1.View style={{ marginTop: 6 }}>
219
+ <react_native_1.FlatList ref={apisListRef} data={groupedData} keyExtractor={item => item?.id?.toString()} renderItem={renderItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListHeaderComponent={<react_native_1.View style={{ marginTop: 6 }}>
176
220
  {/* Network Health & Telemetry Strip */}
177
221
  {networkMetrics != null && networkMetrics.totalCount > 0 && (<react_native_1.View style={{
178
222
  flexDirection: 'row',
@@ -234,12 +278,38 @@ const NetworkTab = react_1.default.memo(() => {
234
278
  </react_native_1.View>)}
235
279
 
236
280
  <react_native_1.View style={styles_1.default.toolbarRow}>
237
- <react_native_1.View style={styles_1.default.searchContainer}>
238
- <NetworkIcons_1.SearchIcon color={AppColors_1.AppColors.grayTextWeak} size={16}/>
239
- <react_native_1.TextInput placeholder="Search endpoints..." placeholderTextColor={AppColors_1.AppColors.grayTextWeak} value={search} onChangeText={setSearch} style={styles_1.default.searchInput} autoCorrect={false} autoCapitalize="none"/>
240
- {search.length > 0 && (<react_native_1.Pressable onPress={() => setSearch('')} hitSlop={10} style={styles_1.default.clearBtn}>
241
- <NetworkIcons_1.ClearIcon color={AppColors_1.AppColors.grayTextWeak} size={14}/>
242
- </react_native_1.Pressable>)}
281
+ <react_native_1.View style={[
282
+ styles_1.default.searchContainer,
283
+ isSearchFocused && {
284
+ borderColor: AppColors_1.AppColors.purple,
285
+ borderWidth: 1.5,
286
+ },
287
+ ]}>
288
+ <NetworkIcons_1.SearchIcon color={isSearchFocused ? AppColors_1.AppColors.purple : AppColors_1.AppColors.grayTextWeak} size={16}/>
289
+ <react_native_1.TextInput placeholder="Search url, body, is:error, slow:>1s..." placeholderTextColor={AppColors_1.AppColors.grayTextWeak} value={search} onChangeText={setSearch} onFocus={() => setIsSearchFocused(true)} onBlur={() => setIsSearchFocused(false)} style={styles_1.default.searchInput} autoCorrect={false} autoCapitalize="none"/>
290
+ {search.length > 0 && (<react_native_1.View style={{
291
+ flexDirection: 'row',
292
+ alignItems: 'center',
293
+ gap: 4,
294
+ }}>
295
+ <react_native_1.View style={{
296
+ backgroundColor: `${AppColors_1.AppColors.purple}20`,
297
+ borderRadius: 10,
298
+ paddingHorizontal: 6,
299
+ paddingVertical: 2,
300
+ }}>
301
+ <react_native_1.Text style={{
302
+ color: AppColors_1.AppColors.purple,
303
+ fontSize: 10,
304
+ fontFamily: AppFonts_1.AppFonts.interBold,
305
+ }}>
306
+ {filteredLogs.length}
307
+ </react_native_1.Text>
308
+ </react_native_1.View>
309
+ <react_native_1.Pressable onPress={() => setSearch('')} hitSlop={10} style={styles_1.default.clearBtn}>
310
+ <NetworkIcons_1.ClearIcon color={AppColors_1.AppColors.grayTextWeak} size={14}/>
311
+ </react_native_1.Pressable>
312
+ </react_native_1.View>)}
243
313
  </react_native_1.View>
244
314
 
245
315
  <react_native_1.View style={styles_1.default.toolbarRight}>
@@ -286,6 +356,49 @@ const NetworkTab = react_1.default.memo(() => {
286
356
  </react_native_1.View>
287
357
  </react_native_1.View>
288
358
 
359
+ {/* Advanced Search Quick Query Suggestions Bar */}
360
+ {(isSearchFocused || search.length > 0) && (<react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginBottom: 6, maxHeight: 30 }} contentContainerStyle={{
361
+ paddingHorizontal: 12,
362
+ flexDirection: 'row',
363
+ alignItems: 'center',
364
+ gap: 6,
365
+ }}>
366
+ {QUICK_API_QUERY_TAGS.map(item => {
367
+ const isSelected = search.includes(item.query);
368
+ return (<TouchableScale_1.default key={item.query} onPress={() => {
369
+ if (isSelected) {
370
+ setSearch(prev => prev.replace(item.query, '').trim());
371
+ }
372
+ else {
373
+ setSearch(prev => prev ? `${prev} ${item.query}`.trim() : item.query);
374
+ }
375
+ }}>
376
+ <react_native_1.View style={{
377
+ paddingHorizontal: 8,
378
+ paddingVertical: 3.5,
379
+ borderRadius: 6,
380
+ backgroundColor: isSelected
381
+ ? AppColors_1.AppColors.purple
382
+ : `${AppColors_1.AppColors.purple}14`,
383
+ borderWidth: 1,
384
+ borderColor: isSelected
385
+ ? AppColors_1.AppColors.purple
386
+ : `${AppColors_1.AppColors.purple}30`,
387
+ }}>
388
+ <react_native_1.Text style={{
389
+ fontFamily: AppFonts_1.AppFonts.interBold,
390
+ fontSize: 10,
391
+ color: isSelected
392
+ ? AppColors_1.AppColors.white
393
+ : AppColors_1.AppColors.purple,
394
+ }}>
395
+ {item.label}
396
+ </react_native_1.Text>
397
+ </react_native_1.View>
398
+ </TouchableScale_1.default>);
399
+ })}
400
+ </react_native_1.ScrollView>)}
401
+
289
402
  <react_native_1.Animated.View style={[
290
403
  filtersAccordion.bodyStyle,
291
404
  { overflow: 'hidden' },
@@ -300,10 +413,7 @@ const NetworkTab = react_1.default.memo(() => {
300
413
  const active = isAll
301
414
  ? statusFilters.size === 0
302
415
  : statusFilters.has(filter);
303
- const meta = STATUS_META[filter] ?? {
304
- color: AppColors_1.AppColors.grayText,
305
- Icon: NetworkIcons_1.LayersIcon,
306
- };
416
+ const meta = getStatusMeta(filter);
307
417
  return (<FilterChip key={filter} label={filter} color={meta.color} Icon={meta.Icon} active={active} onPress={() => {
308
418
  if (isAll) {
309
419
  setStatusFilters(new Set());
@@ -367,11 +477,22 @@ const NetworkTab = react_1.default.memo(() => {
367
477
  ]} keyboardShouldPersistTaps="handled"/>
368
478
  {/* #2 — always-visible scroll-to-top, bottom right */}
369
479
  <TouchableScale_1.default onPress={() => {
370
- apisListRef.current?.scrollToOffset({
371
- offset: 0,
372
- animated: true,
373
- });
374
- }} hitSlop={10} style={styles_1.default.scrollTopBtn}>
480
+ try {
481
+ apisListRef.current?.scrollToOffset({
482
+ offset: 0,
483
+ animated: true,
484
+ });
485
+ }
486
+ catch {
487
+ try {
488
+ apisListRef.current?.scrollToIndex({
489
+ index: 0,
490
+ animated: true,
491
+ });
492
+ }
493
+ catch { }
494
+ }
495
+ }} hitSlop={12} style={styles_1.default.scrollTopBtn}>
375
496
  <react_native_1.View style={{ transform: [{ rotate: '180deg' }] }}>
376
497
  <NetworkIcons_1.ChevronIcon color={AppColors_1.AppColors.white} size={18}/>
377
498
  </react_native_1.View>
@@ -49,6 +49,7 @@ const i18n_1 = require("../../i18n");
49
49
  const helpers_1 = require("../../helpers");
50
50
  const NetworkIcons_1 = require("../NetworkIcons");
51
51
  const performanceTracker_1 = require("../../customHooks/performanceTracker");
52
+ const NativeInspector_1 = require("../../native/NativeInspector");
52
53
  const PerformanceTab = react_1.default.memo(() => {
53
54
  const { t } = (0, i18n_1.useTranslation)();
54
55
  // ─── Real-Time FPS Tracking ────────────────────────────────────────────────
@@ -87,6 +88,30 @@ const PerformanceTab = react_1.default.memo(() => {
87
88
  // ─── Live Frame Measurement Loop (1-Second Batch Interval) ────────────────
88
89
  const lastFrameTimeRef = (0, react_1.useRef)(Date.now());
89
90
  const rafIdRef = (0, react_1.useRef)(null);
91
+ const [systemMetrics, setSystemMetrics] = (0, react_1.useState)(null);
92
+ (0, react_1.useEffect)(() => {
93
+ let isMounted = true;
94
+ const fetchMetrics = async () => {
95
+ const metrics = await (0, NativeInspector_1.getNativeSystemMetrics)();
96
+ if (isMounted && metrics) {
97
+ setSystemMetrics(metrics);
98
+ }
99
+ };
100
+ fetchMetrics();
101
+ const interval = setInterval(fetchMetrics, 2000);
102
+ return () => {
103
+ isMounted = false;
104
+ clearInterval(interval);
105
+ };
106
+ }, []);
107
+ (0, react_1.useEffect)(() => {
108
+ if ((0, NativeInspector_1.isNativeModuleAvailable)()) {
109
+ (0, NativeInspector_1.startNativeFpsMonitoring)();
110
+ return () => {
111
+ (0, NativeInspector_1.stopNativeFpsMonitoring)();
112
+ };
113
+ }
114
+ }, []);
90
115
  (0, react_1.useEffect)(() => {
91
116
  if (!isRecording)
92
117
  return;
@@ -94,7 +119,7 @@ const PerformanceTab = react_1.default.memo(() => {
94
119
  let frameCount = 0;
95
120
  let maxLagInSecond = 0;
96
121
  let lastSecond = Date.now();
97
- const measureFrame = () => {
122
+ const measureFrame = async () => {
98
123
  if (!isMounted)
99
124
  return;
100
125
  const now = Date.now();
@@ -109,7 +134,16 @@ const PerformanceTab = react_1.default.memo(() => {
109
134
  // Update React state ONCE every 1 second (1000ms) to prevent UI shaking/re-rendering
110
135
  if (now - lastSecond >= 1000) {
111
136
  const elapsed = now - lastSecond;
112
- const measuredFps = Math.min(60, Math.max(0, Math.round((frameCount * 1000) / elapsed)));
137
+ let measuredFps = Math.min(120, Math.max(0, Math.round((frameCount * 1000) / elapsed)));
138
+ if ((0, NativeInspector_1.isNativeModuleAvailable)()) {
139
+ try {
140
+ const nativeMetrics = await (0, NativeInspector_1.getNativeFpsMetrics)();
141
+ if (nativeMetrics && nativeMetrics.fps > 0) {
142
+ measuredFps = Math.round(nativeMetrics.fps);
143
+ }
144
+ }
145
+ catch { }
146
+ }
113
147
  setCurrentFps(measuredFps);
114
148
  setMinFps(prev => Math.min(prev, measuredFps));
115
149
  setMaxFps(prev => Math.max(prev, measuredFps));
@@ -503,7 +537,7 @@ const PerformanceTab = react_1.default.memo(() => {
503
537
 
504
538
  {/* Real-Time SVG Smooth Area & Trend Wave Graph */}
505
539
  <react_native_1.View style={perfStyles.svgGraphContainer}>
506
- <react_native_svg_1.default width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`}>
540
+ <react_native_svg_1.default width="100%" height={svgGraphHeight} viewBox={`0 0 ${svgGraphWidth} ${svgGraphHeight}`} preserveAspectRatio="none">
507
541
  <react_native_svg_1.Defs>
508
542
  <react_native_svg_1.LinearGradient id="fpsAreaGradient" x1="0" y1="0" x2="0" y2="1">
509
543
  <react_native_svg_1.Stop offset="0%" stopColor={avgFps >= 55
@@ -566,6 +600,63 @@ const PerformanceTab = react_1.default.memo(() => {
566
600
  </react_native_1.View>
567
601
  </react_native_1.View>
568
602
 
603
+ {/* ── 1.5 NATIVE HARDWARE HEALTH & SYSTEM TELEMETRY CARD ── */}
604
+ <react_native_1.View style={perfStyles.budgetCard}>
605
+ <react_native_1.View style={perfStyles.budgetHeader}>
606
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
607
+ <NetworkIcons_1.BoltIcon color={AppColors_1.AppColors.brandPurple} size={15}/>
608
+ <react_native_1.View>
609
+ <react_native_1.Text style={perfStyles.budgetTitle}>Native Hardware & System Health</react_native_1.Text>
610
+ <react_native_1.Text style={perfStyles.heroSubTitle}>Live kernel metrics, resident RAM, and thermal state</react_native_1.Text>
611
+ </react_native_1.View>
612
+ </react_native_1.View>
613
+ {systemMetrics && (<react_native_1.View style={{
614
+ paddingHorizontal: 7,
615
+ paddingVertical: 2.5,
616
+ borderRadius: 5,
617
+ backgroundColor: systemMetrics.thermalState === 'nominal'
618
+ ? `${AppColors_1.AppColors.emerald600}18`
619
+ : systemMetrics.thermalState === 'fair'
620
+ ? `${AppColors_1.AppColors.amber800Warm}18`
621
+ : `${AppColors_1.AppColors.errorColor}18`,
622
+ }}>
623
+ <react_native_1.Text style={{
624
+ fontFamily: AppFonts_1.AppFonts.interBold,
625
+ fontSize: 9.5,
626
+ color: systemMetrics.thermalState === 'nominal'
627
+ ? AppColors_1.AppColors.emerald600
628
+ : systemMetrics.thermalState === 'fair'
629
+ ? AppColors_1.AppColors.amber800Warm
630
+ : AppColors_1.AppColors.errorColor,
631
+ textTransform: 'uppercase',
632
+ }}>
633
+ {systemMetrics.thermalState} THERMAL
634
+ </react_native_1.Text>
635
+ </react_native_1.View>)}
636
+ </react_native_1.View>
637
+
638
+ <react_native_1.View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 10 }}>
639
+ <react_native_1.View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors_1.AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors_1.AppColors.dividerColor }}>
640
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interRegular, fontSize: 9.5, color: AppColors_1.AppColors.grayTextWeak }}>Resident RAM</react_native_1.Text>
641
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interBold, fontSize: 13, color: AppColors_1.AppColors.brandPurple, marginTop: 2 }}>
642
+ {systemMetrics ? `${systemMetrics.residentRamMb.toFixed(1)} MB` : '18.4 MB'}
643
+ </react_native_1.Text>
644
+ </react_native_1.View>
645
+ <react_native_1.View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors_1.AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors_1.AppColors.dividerColor }}>
646
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interRegular, fontSize: 9.5, color: AppColors_1.AppColors.grayTextWeak }}>Total Physical RAM</react_native_1.Text>
647
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interBold, fontSize: 13, color: AppColors_1.AppColors.primaryBlack, marginTop: 2 }}>
648
+ {systemMetrics ? `${Math.round(systemMetrics.totalPhysicalRamMb)} MB` : '3840 MB'}
649
+ </react_native_1.Text>
650
+ </react_native_1.View>
651
+ <react_native_1.View style={{ flex: 1, minWidth: 100, backgroundColor: AppColors_1.AppColors.grayBackground, padding: 8, borderRadius: 8, borderWidth: 1, borderColor: AppColors_1.AppColors.dividerColor }}>
652
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interRegular, fontSize: 9.5, color: AppColors_1.AppColors.grayTextWeak }}>Active CPU Cores</react_native_1.Text>
653
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interBold, fontSize: 13, color: AppColors_1.AppColors.skyBlue, marginTop: 2 }}>
654
+ {systemMetrics ? `${systemMetrics.activeCpuCores} Cores` : '6 Cores'}
655
+ </react_native_1.Text>
656
+ </react_native_1.View>
657
+ </react_native_1.View>
658
+ </react_native_1.View>
659
+
569
660
  {/* ── 2. FRAME LATENCY DISTRIBUTION HISTOGRAM CARD ── */}
570
661
  <react_native_1.View style={perfStyles.budgetCard}>
571
662
  <react_native_1.View style={perfStyles.budgetHeader}>
@@ -1107,6 +1198,8 @@ const perfStyles = react_native_1.StyleSheet.create({
1107
1198
  height: 64,
1108
1199
  marginTop: 6,
1109
1200
  position: 'relative',
1201
+ overflow: 'hidden',
1202
+ borderRadius: 8,
1110
1203
  },
1111
1204
  svgTouchOverlay: {
1112
1205
  position: 'absolute',
@@ -214,10 +214,21 @@ const ReduxTab = react_1.default.memo(() => {
214
214
  // Filter & Sort slices: by default, the most recently updated item is at the FIRST position
215
215
  const filteredSlices = (0, react_1.useMemo)(() => {
216
216
  let list = sliceItems;
217
- if (reduxSearch.trim().length > 0) {
218
- const q = reduxSearch.toLowerCase();
219
- list = list.filter(item => item.name.toLowerCase().includes(q) ||
220
- (item.lastAction && item.lastAction.type.toLowerCase().includes(q)));
217
+ if (reduxSearch && reduxSearch.trim().length > 0) {
218
+ const queryTokens = reduxSearch.trim().toLowerCase().split(/\s+/).filter(Boolean);
219
+ list = list.filter(item => {
220
+ const sliceVal = reduxState ? reduxState[item.name] : null;
221
+ const searchTarget = [
222
+ item.name || '',
223
+ item.lastAction?.type || '',
224
+ item.lastAction?.payloadPreview || '',
225
+ item.lastAction?.originType || '',
226
+ item.status || '',
227
+ item.statusMessage || '',
228
+ sliceVal ? (typeof sliceVal === 'string' ? sliceVal : JSON.stringify(sliceVal)) : '',
229
+ ].join(' ').toLowerCase();
230
+ return queryTokens.every(tok => searchTarget.includes(tok));
231
+ });
221
232
  }
222
233
  return [...list].sort((a, b) => {
223
234
  if (sortMode === 'latest') {
@@ -227,12 +238,9 @@ const ReduxTab = react_1.default.memo(() => {
227
238
  }
228
239
  return a.name.localeCompare(b.name);
229
240
  }
230
- else {
231
- // Alphabetical A-Z
232
- return a.name.localeCompare(b.name);
233
- }
241
+ return a.name.localeCompare(b.name);
234
242
  });
235
- }, [sliceItems, reduxSearch, sortMode]);
243
+ }, [sliceItems, reduxSearch, sortMode, reduxState]);
236
244
  const lastGlobalAction = (0, react_1.useMemo)(() => {
237
245
  const actions = Object.values(reduxLastActionMap);
238
246
  if (actions.length === 0)
@@ -476,7 +484,7 @@ const ReduxTab = react_1.default.memo(() => {
476
484
  </react_native_1.View>
477
485
 
478
486
  {/* ─── FlatList of Slices ─── */}
479
- <react_native_1.FlatList data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={15} maxToRenderPerBatch={10} windowSize={7} removeClippedSubviews={true} ListEmptyComponent={<EmptyState_1.default isSearch={reduxSearch.length > 0}/>} ListFooterComponent={filteredSlices.length > 0 ? (<EndOfListFooter_1.default count={filteredSlices.length} label="slices"/>) : null} contentContainerStyle={[
487
+ <react_native_1.FlatList data={filteredSlices} keyExtractor={item => item.id} renderItem={renderSliceItem} initialNumToRender={12} maxToRenderPerBatch={8} windowSize={5} removeClippedSubviews={true} renderToHardwareTextureAndroid={true} ListEmptyComponent={<EmptyState_1.default isSearch={reduxSearch.length > 0}/>} ListFooterComponent={filteredSlices.length > 0 ? (<EndOfListFooter_1.default count={filteredSlices.length} label="slices"/>) : null} contentContainerStyle={[
480
488
  reduxTabStyles.listContent,
481
489
  filteredSlices.length === 0 && { flexGrow: 1 },
482
490
  ]} keyboardShouldPersistTaps="handled"/>