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
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.downloadBundleFile = void 0;
39
40
  const react_1 = __importStar(require("react"));
40
41
  const react_native_1 = require("react-native");
41
42
  const i18n_1 = require("../../i18n");
@@ -44,6 +45,7 @@ const CopyButton_1 = __importDefault(require("../CopyButton"));
44
45
  const HighlightText_1 = __importDefault(require("../HighlightText"));
45
46
  const AppColors_1 = require("../../styles/AppColors");
46
47
  const AppFonts_1 = require("../../styles/AppFonts");
48
+ const helpers_1 = require("../../helpers");
47
49
  const NetworkIcons_1 = require("../NetworkIcons");
48
50
  const react_native_svg_1 = __importStar(require("react-native-svg"));
49
51
  const bundleAnalyzer_1 = require("../../customHooks/bundleAnalyzer");
@@ -342,7 +344,73 @@ function buildBundleFileTree(files) {
342
344
  }
343
345
  return convertMapToNodes(rootMap);
344
346
  }
345
- const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, }) => {
347
+ const downloadBundleFile = async (file, scriptURL) => {
348
+ let content = '';
349
+ // 1. Attempt to fetch real source code from Metro dev server if available
350
+ try {
351
+ let devServerOrigin = 'http://localhost:8081';
352
+ if (scriptURL && scriptURL.startsWith('http')) {
353
+ const match = scriptURL.match(/^(https?:\/\/[^/]+)/);
354
+ if (match) {
355
+ devServerOrigin = match[1];
356
+ }
357
+ }
358
+ const cleanPath = file.path.replace(/^\/+/, '');
359
+ const url = `${devServerOrigin}/${cleanPath}`;
360
+ const res = await fetch(url);
361
+ if (res.ok) {
362
+ const text = await res.text();
363
+ if (text && text.length > 0) {
364
+ content = text;
365
+ }
366
+ }
367
+ }
368
+ catch { }
369
+ // 2. If dev server direct fetch is unavailable, provide structured module descriptor with full path
370
+ if (!content) {
371
+ content = `// ─────────────────────────────────────────────────────────────
372
+ // File: ${file.name}
373
+ // Path: ${file.path}
374
+ // Category: ${file.category.toUpperCase()}
375
+ // Size: ${file.sizeKb} KB
376
+ // Status: ${file.status === 'optimal' ? 'Active in Dependency Graph' : 'Not consumed / Static'}
377
+ // Meta: ${file.meta}
378
+ // ─────────────────────────────────────────────────────────────
379
+
380
+ export default {
381
+ id: ${JSON.stringify(file.id)},
382
+ name: ${JSON.stringify(file.name)},
383
+ path: ${JSON.stringify(file.path)},
384
+ category: ${JSON.stringify(file.category)},
385
+ sizeKb: ${file.sizeKb},
386
+ meta: ${JSON.stringify(file.meta)},
387
+ status: ${JSON.stringify(file.status || 'optimal')},
388
+ };
389
+ `;
390
+ }
391
+ // 3. Trigger native Share / Download sheet
392
+ try {
393
+ await react_native_1.Share.share(react_native_1.Platform.OS === 'ios'
394
+ ? {
395
+ title: file.name,
396
+ message: content,
397
+ }
398
+ : {
399
+ title: `Download ${file.name}`,
400
+ message: content,
401
+ }, {
402
+ dialogTitle: `Download / Save ${file.name}`,
403
+ });
404
+ }
405
+ catch { }
406
+ // 4. Also copy directly to clipboard for quick paste in VS Code or external editor
407
+ try {
408
+ (0, helpers_1.copyToClipboard)(content, file.name);
409
+ }
410
+ catch { }
411
+ };
412
+ exports.downloadBundleFile = downloadBundleFile;
413
+ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, onDownload, }) => {
346
414
  const isExpanded = search ? true : !!expandedFolders[node.fullPath];
347
415
  const isCollapsed = !isExpanded;
348
416
  const indentLeft = level * 16 + 10;
@@ -380,7 +448,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
380
448
  </TouchableScale_1.default>
381
449
 
382
450
  {!isCollapsed && (<react_native_1.View style={bundleStyles.treeChildrenContainer}>
383
- {node.children.map((child, cIdx) => (<BundleTreeNodeView key={child.id} node={child} level={level + 1} search={search} totalBundleKb={totalBundleKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={cIdx === node.children.length - 1}/>))}
451
+ {node.children.map((child, cIdx) => (<BundleTreeNodeView key={child.id} node={child} level={level + 1} search={search} totalBundleKb={totalBundleKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={cIdx === node.children.length - 1} onDownload={onDownload}/>))}
384
452
  </react_native_1.View>)}
385
453
  </react_native_1.View>);
386
454
  }
@@ -421,7 +489,13 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolder
421
489
  <react_native_1.Text style={bundleStyles.treeFilePctText}>{pctOfTotal}%</react_native_1.Text>
422
490
  </react_native_1.View>
423
491
 
424
- <CopyButton_1.default value={() => file} label={(0, i18n_1.t)('bundle.fileDetailsJson')}/>
492
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
493
+ {onDownload && (<TouchableScale_1.default onPress={() => onDownload(file)} style={bundleStyles.treeActionBtn} hitSlop={8}>
494
+ <NetworkIcons_1.DownloadIcon size={13} color={AppColors_1.AppColors.sky500}/>
495
+ </TouchableScale_1.default>)}
496
+
497
+ <CopyButton_1.default value={() => file} label={(0, i18n_1.t)('bundle.fileDetailsJson')}/>
498
+ </react_native_1.View>
425
499
  </react_native_1.View>
426
500
  </react_native_1.View>);
427
501
  };
@@ -598,12 +672,28 @@ const BundleTab = react_1.default.memo(() => {
598
672
  const filteredTotalKb = (0, react_1.useMemo)(() => {
599
673
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
600
674
  }, [filteredFiles]);
601
- // Tree View State & Node Generation (Collapsed by default)
675
+ // Tree View State & Node Generation (Top folders expanded by default)
602
676
  const [filesViewMode, setFilesViewMode] = (0, react_1.useState)('tree');
603
677
  const [expandedFolders, setExpandedFolders] = (0, react_1.useState)({});
604
678
  const fileTreeNodes = (0, react_1.useMemo)(() => {
605
679
  return buildBundleFileTree(filteredFiles);
606
680
  }, [filteredFiles]);
681
+ (0, react_1.useEffect)(() => {
682
+ if (fileTreeNodes.length > 0 && Object.keys(expandedFolders).length === 0) {
683
+ const initial = {};
684
+ fileTreeNodes.forEach(node => {
685
+ if (node.isFolder) {
686
+ initial[node.fullPath] = true;
687
+ node.children.forEach(child => {
688
+ if (child.isFolder) {
689
+ initial[child.fullPath] = true;
690
+ }
691
+ });
692
+ }
693
+ });
694
+ setExpandedFolders(initial);
695
+ }
696
+ }, [fileTreeNodes]);
607
697
  const toggleFolder = (0, react_1.useCallback)((path) => {
608
698
  setExpandedFolders(prev => ({
609
699
  ...prev,
@@ -1356,7 +1446,7 @@ const BundleTab = react_1.default.memo(() => {
1356
1446
 
1357
1447
  {/* ── MODE 1: DIRECTORY TREE VIEW ── */}
1358
1448
  {filesViewMode === 'tree' ? (<react_native_1.View style={bundleStyles.treeRootCard}>
1359
- {fileTreeNodes.map((node, nIdx) => (<BundleTreeNodeView key={node.id} node={node} level={0} search={search} totalBundleKb={summary.totalKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={nIdx === fileTreeNodes.length - 1}/>))}
1449
+ {fileTreeNodes.map((node, nIdx) => (<BundleTreeNodeView key={node.id} node={node} level={0} search={search} totalBundleKb={summary.totalKb} expandedFolders={expandedFolders} toggleFolder={toggleFolder} isLastChild={nIdx === fileTreeNodes.length - 1} onDownload={f => (0, exports.downloadBundleFile)(f, analysis?.scriptURL)}/>))}
1360
1450
  </react_native_1.View>) : (
1361
1451
  /* ── MODE 2: FLAT LIST VIEW ── */
1362
1452
  filteredFiles.map((file, index) => {
@@ -1392,6 +1482,9 @@ const BundleTab = react_1.default.memo(() => {
1392
1482
  </react_native_1.Text>
1393
1483
  <react_native_1.Text style={bundleStyles.filePercent}>{pctOfTotal}%</react_native_1.Text>
1394
1484
  </react_native_1.View>
1485
+ <TouchableScale_1.default onPress={() => (0, exports.downloadBundleFile)(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1486
+ <NetworkIcons_1.DownloadIcon size={14} color={AppColors_1.AppColors.sky500}/>
1487
+ </TouchableScale_1.default>
1395
1488
  <CopyButton_1.default value={() => file} label={t('bundle.fileDetailsJson')}/>
1396
1489
  </react_native_1.View>
1397
1490
  </react_native_1.View>
@@ -1410,21 +1503,25 @@ const BundleTab = react_1.default.memo(() => {
1410
1503
  <react_native_1.Text style={bundleStyles.fileMetaText}>{file.meta}</react_native_1.Text>
1411
1504
  {file.advice && (<react_native_1.View style={[
1412
1505
  bundleStyles.adviceBadge,
1413
- file.status === 'warning' && bundleStyles.adviceWarning,
1414
- file.status === 'optimal' && bundleStyles.adviceOptimal,
1506
+ file.status === 'warning' &&
1507
+ bundleStyles.adviceWarning,
1508
+ file.status === 'optimal' &&
1509
+ bundleStyles.adviceOptimal,
1415
1510
  isUnused && bundleStyles.adviceUnused,
1416
1511
  ]}>
1417
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1418
1512
  {isUnused ? (<NetworkIcons_1.TrashIcon color={AppColors_1.AppColors.red600} size={12}/>) : file.status === 'warning' ? (<NetworkIcons_1.LightbulbIcon color={AppColors_1.AppColors.amber800Warm} size={12}/>) : (<NetworkIcons_1.SparkleIcon color={AppColors_1.AppColors.emerald700} size={12}/>)}
1419
1513
  <react_native_1.Text style={[
1420
1514
  bundleStyles.adviceText,
1421
- file.status === 'warning' && { color: AppColors_1.AppColors.amber800Warm },
1422
- file.status === 'optimal' && { color: AppColors_1.AppColors.emerald700 },
1515
+ file.status === 'warning' && {
1516
+ color: AppColors_1.AppColors.amber800Warm,
1517
+ },
1518
+ file.status === 'optimal' && {
1519
+ color: AppColors_1.AppColors.emerald700,
1520
+ },
1423
1521
  isUnused && { color: AppColors_1.AppColors.red600 },
1424
1522
  ]}>
1425
1523
  {file.advice}
1426
1524
  </react_native_1.Text>
1427
- </react_native_1.View>
1428
1525
  </react_native_1.View>)}
1429
1526
  </react_native_1.View>
1430
1527
  </react_native_1.View>);
@@ -1655,6 +1752,9 @@ const BundleTab = react_1.default.memo(() => {
1655
1752
  {t('bundle.fileSizeKb', { size: file.sizeKb })}
1656
1753
  </react_native_1.Text>
1657
1754
  </react_native_1.View>
1755
+ <TouchableScale_1.default onPress={() => (0, exports.downloadBundleFile)(file, analysis?.scriptURL)} style={bundleStyles.downloadFileBtn} hitSlop={8}>
1756
+ <NetworkIcons_1.DownloadIcon size={14} color={AppColors_1.AppColors.sky500}/>
1757
+ </TouchableScale_1.default>
1658
1758
  <CopyButton_1.default value={() => file} label={t('bundle.mediaItemJson')}/>
1659
1759
  </react_native_1.View>
1660
1760
  </react_native_1.View>
@@ -1666,16 +1766,18 @@ const BundleTab = react_1.default.memo(() => {
1666
1766
  file.status === 'warning' && bundleStyles.adviceWarning,
1667
1767
  file.status === 'optimal' && bundleStyles.adviceOptimal,
1668
1768
  ]}>
1669
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
1670
- {file.status === 'warning' ? (<NetworkIcons_1.LightbulbIcon color={AppColors_1.AppColors.amber800Warm} size={12}/>) : (<NetworkIcons_1.SparkleIcon color={AppColors_1.AppColors.emerald700} size={12}/>)}
1671
- <react_native_1.Text style={[
1769
+ {file.status === 'warning' ? (<NetworkIcons_1.LightbulbIcon color={AppColors_1.AppColors.amber800Warm} size={12}/>) : (<NetworkIcons_1.SparkleIcon color={AppColors_1.AppColors.emerald700} size={12}/>)}
1770
+ <react_native_1.Text style={[
1672
1771
  bundleStyles.adviceText,
1673
- file.status === 'warning' && { color: AppColors_1.AppColors.amber800Warm },
1674
- file.status === 'optimal' && { color: AppColors_1.AppColors.emerald700 },
1772
+ file.status === 'warning' && {
1773
+ color: AppColors_1.AppColors.amber800Warm,
1774
+ },
1775
+ file.status === 'optimal' && {
1776
+ color: AppColors_1.AppColors.emerald700,
1777
+ },
1675
1778
  ]}>
1676
- {file.advice}
1677
- </react_native_1.Text>
1678
- </react_native_1.View>
1779
+ {file.advice}
1780
+ </react_native_1.Text>
1679
1781
  </react_native_1.View>)}
1680
1782
  </react_native_1.View>
1681
1783
  </react_native_1.View>))}
@@ -2446,6 +2548,24 @@ const bundleStyles = react_native_1.StyleSheet.create({
2446
2548
  fileSizeBox: {
2447
2549
  alignItems: 'flex-end',
2448
2550
  },
2551
+ downloadFileBtn: {
2552
+ padding: 6,
2553
+ borderRadius: 6,
2554
+ backgroundColor: `${AppColors_1.AppColors.sky500}14`,
2555
+ alignItems: 'center',
2556
+ justifyContent: 'center',
2557
+ borderWidth: 1,
2558
+ borderColor: `${AppColors_1.AppColors.sky500}28`,
2559
+ },
2560
+ treeActionBtn: {
2561
+ padding: 4,
2562
+ borderRadius: 4,
2563
+ backgroundColor: `${AppColors_1.AppColors.sky500}14`,
2564
+ alignItems: 'center',
2565
+ justifyContent: 'center',
2566
+ borderWidth: 1,
2567
+ borderColor: `${AppColors_1.AppColors.sky500}28`,
2568
+ },
2449
2569
  fileSizeKb: {
2450
2570
  fontFamily: AppFonts_1.AppFonts.interBold,
2451
2571
  fontSize: 12,
@@ -2617,10 +2737,13 @@ const bundleStyles = react_native_1.StyleSheet.create({
2617
2737
  color: AppColors_1.AppColors.amber800Warm,
2618
2738
  },
2619
2739
  adviceBadge: {
2740
+ flexDirection: 'row',
2741
+ alignItems: 'center',
2620
2742
  paddingHorizontal: 8,
2621
- paddingVertical: 4,
2743
+ paddingVertical: 5,
2622
2744
  borderRadius: 6,
2623
- marginTop: 2,
2745
+ marginTop: 4,
2746
+ gap: 6,
2624
2747
  },
2625
2748
  adviceWarning: {
2626
2749
  backgroundColor: AppColors_1.AppColors.amber100,
@@ -2636,6 +2759,7 @@ const bundleStyles = react_native_1.StyleSheet.create({
2636
2759
  fontFamily: AppFonts_1.AppFonts.interMedium,
2637
2760
  fontSize: 10.5,
2638
2761
  lineHeight: 14,
2762
+ flex: 1,
2639
2763
  },
2640
2764
  impactBadge: {
2641
2765
  paddingHorizontal: 6,
@@ -72,14 +72,6 @@ const CrashTab = react_1.default.memo(() => {
72
72
  const [crashFilters, setCrashFilters] = (0, react_1.useState)(CrashFilterModal_1.DEFAULT_CRASH_FILTERS);
73
73
  const [isFilterModalOpen, setIsFilterModalOpen] = (0, react_1.useState)(false);
74
74
  const [detailSubTab, setDetailSubTab] = (0, react_1.useState)('stack');
75
- // Auto-open the latest crash detail on first visit if crashes exist
76
- const hasAutoOpened = react_1.default.useRef(false);
77
- react_1.default.useEffect(() => {
78
- if (!hasAutoOpened.current && !selectedCrash && crashRecords.length > 0) {
79
- setSelectedCrash(crashRecords[0]);
80
- hasAutoOpened.current = true;
81
- }
82
- }, [crashRecords, selectedCrash, setSelectedCrash]);
83
75
  // Calculate statistics
84
76
  const stats = (0, react_1.useMemo)(() => {
85
77
  const total = crashRecords.length;
@@ -146,9 +146,9 @@ const InspectorHeader = react_1.default.memo(() => {
146
146
  flex: 1,
147
147
  }}>
148
148
  <AppHeaderLogo_1.default size={46} customIcon={appIcon}/>
149
- <react_native_1.View style={{ gap: 2, flex: 1 }}>
150
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
151
- <react_native_1.Text style={[styles_1.default.headerTitle]} numberOfLines={1}>
149
+ <react_native_1.View style={{ gap: 2, flex: 1, minWidth: 0 }}>
150
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'nowrap', minWidth: 0 }}>
151
+ <react_native_1.Text style={[styles_1.default.headerTitle, { flexShrink: 1 }]} numberOfLines={1}>
152
152
  {(0, helpers_1.getAppName)()}
153
153
  </react_native_1.Text>
154
154
  <react_native_1.View style={[
@@ -372,6 +372,20 @@ const InspectorHeader = react_1.default.memo(() => {
372
372
  : '—'}
373
373
  </react_native_1.Text>
374
374
  </react_native_1.View>
375
+ {selected.response != null && (<react_native_1.View style={{
376
+ flexDirection: 'row',
377
+ alignItems: 'center',
378
+ gap: 4,
379
+ paddingHorizontal: 8,
380
+ paddingVertical: 3,
381
+ borderRadius: 20,
382
+ backgroundColor: `${AppColors_1.AppColors.white}29`,
383
+ }}>
384
+ <NetworkIcons_1.SizeIcon color={AppColors_1.AppColors.white} size={11}/>
385
+ <react_native_1.Text style={styles_1.default.headerSubTitle}>
386
+ {(0, helpers_1.getSize)(selected.response)}
387
+ </react_native_1.Text>
388
+ </react_native_1.View>)}
375
389
  </react_native_1.View>
376
390
  </react_native_1.View>) : activeTab === 'analytics' && selectedEvent != null ? (<react_native_1.View style={styles_1.default.headerDetailCenter}>
377
391
  <react_native_1.View style={styles_1.default.headerDetailRow}>
@@ -307,16 +307,18 @@ const LogDetail = react_1.default.memo(() => {
307
307
  : originFrame?.fileName || t('console.consoleLog')}
308
308
  </react_native_1.Text>
309
309
 
310
- {originFrame?.fileName && (<react_native_1.View style={[
310
+ {originFrame?.fileName && (<react_native_1.Pressable onPress={() => (0, helpers_1.openInVSCode)(originFrame.rawFilePath ||
311
+ originFrame.fullPath ||
312
+ originFrame.fileName, originFrame.lineNumber, originFrame.columnNumber)} hitSlop={8} style={[
311
313
  styles_1.default.metaChip,
312
314
  {
313
- backgroundColor: `${AppColors_1.AppColors.brandPurple}12`,
314
- borderColor: `${AppColors_1.AppColors.brandPurple}30`,
315
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}15`,
316
+ borderColor: `${AppColors_1.AppColors.brandPurple}40`,
315
317
  paddingHorizontal: 7,
316
318
  paddingVertical: 2,
317
319
  },
318
320
  ]}>
319
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 3, flexWrap: 'wrap', flexShrink: 1 }}>
321
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flexShrink: 1 }}>
320
322
  <NetworkIcons_1.DocIcon color={AppColors_1.AppColors.brandPurple} size={10}/>
321
323
  <react_native_1.Text style={[
322
324
  styles_1.default.metaChipText,
@@ -326,8 +328,9 @@ const LogDetail = react_1.default.memo(() => {
326
328
  {originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
327
329
  {originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
328
330
  </react_native_1.Text>
331
+ <NetworkIcons_1.ExternalLinkIcon color={AppColors_1.AppColors.brandPurple} size={9}/>
329
332
  </react_native_1.View>
330
- </react_native_1.View>)}
333
+ </react_native_1.Pressable>)}
331
334
  </react_native_1.View>
332
335
 
333
336
  {/* Enhanced metadata metrics row */}
@@ -56,6 +56,7 @@ const PerformanceTab_1 = __importDefault(require("./PerformanceTab"));
56
56
  const CrashTab_1 = __importDefault(require("./CrashTab"));
57
57
  const CrashDetail_1 = __importDefault(require("./CrashDetail"));
58
58
  const SettingsPanel_1 = __importDefault(require("./SettingsPanel"));
59
+ const Toast_1 = __importDefault(require("../Toast"));
59
60
  const styles_1 = __importDefault(require("../../styles"));
60
61
  const AppColors_1 = require("../../styles/AppColors");
61
62
  const NavigationTracker_1 = __importDefault(require("./NavigationTracker"));
@@ -153,6 +154,9 @@ const MainScreen = () => {
153
154
  ]}>
154
155
  <SettingsPanel_1.default />
155
156
  </react_native_1.View>)}
157
+
158
+ {/* Bottom floating toast notification */}
159
+ <Toast_1.default />
156
160
  </react_native_1.View>
157
161
  </react_native_1.View>
158
162
  </ErrorBoundary_1.default>)}
@@ -25,6 +25,26 @@ const NetworkIcons_1 = require("../NetworkIcons");
25
25
  const NetworkDetail = react_1.default.memo(() => {
26
26
  const { t } = (0, i18n_1.useTranslation)();
27
27
  const { selected, detailDisplayUrl, apiDetailActiveTab, setApiDetailActiveTab, detailSearch, setDetailSearch, reqExpanded, setReqExpanded, resExpanded, setResExpanded, showReqDiff, setShowReqDiff, showResDiff, setShowResDiff, prevRequestData, prevResponseData, logRouteMapRef, } = (0, InspectorContext_1.useInspector)();
28
+ const handleOpenUrl = () => {
29
+ react_native_1.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${detailDisplayUrl}`, [
30
+ { text: t('common.cancel') || 'Cancel', style: 'cancel' },
31
+ {
32
+ text: t('common.open') || 'Open',
33
+ onPress: () => {
34
+ react_native_1.Linking.canOpenURL(detailDisplayUrl)
35
+ .then(supported => {
36
+ if (supported) {
37
+ react_native_1.Linking.openURL(detailDisplayUrl);
38
+ }
39
+ else {
40
+ react_native_1.Linking.openURL(detailDisplayUrl).catch(() => { });
41
+ }
42
+ })
43
+ .catch(() => { });
44
+ },
45
+ },
46
+ ]);
47
+ };
28
48
  if (!selected)
29
49
  return null;
30
50
  return (<react_native_1.View style={{ flex: 1 }}>
@@ -102,6 +122,47 @@ const NetworkDetail = react_1.default.memo(() => {
102
122
  </react_native_1.Text>
103
123
  </react_native_1.View>
104
124
 
125
+ {/* Client Chip (AXIOS / FETCH / XHR) */}
126
+ {selected.client && (<react_native_1.View style={[
127
+ styles_1.default.chip,
128
+ {
129
+ backgroundColor: selected.client === 'axios'
130
+ ? `${AppColors_1.AppColors.purple}14`
131
+ : selected.client === 'apollo' || selected.client === 'graphql'
132
+ ? `${AppColors_1.AppColors.pink500}14`
133
+ : selected.client === 'xhr'
134
+ ? `${AppColors_1.AppColors.amber500}14`
135
+ : `${AppColors_1.AppColors.sky500}14`,
136
+ borderColor: selected.client === 'axios'
137
+ ? `${AppColors_1.AppColors.purple}30`
138
+ : selected.client === 'apollo' || selected.client === 'graphql'
139
+ ? `${AppColors_1.AppColors.pink500}30`
140
+ : selected.client === 'xhr'
141
+ ? `${AppColors_1.AppColors.amber500}30`
142
+ : `${AppColors_1.AppColors.sky500}30`,
143
+ paddingHorizontal: 6,
144
+ paddingVertical: 3,
145
+ borderRadius: 6,
146
+ },
147
+ ]}>
148
+ <react_native_1.Text style={[
149
+ styles_1.default.chipText,
150
+ {
151
+ fontFamily: AppFonts_1.AppFonts.interBold,
152
+ fontSize: 9.5,
153
+ color: selected.client === 'axios'
154
+ ? AppColors_1.AppColors.purple
155
+ : selected.client === 'apollo' || selected.client === 'graphql'
156
+ ? AppColors_1.AppColors.pink500
157
+ : selected.client === 'xhr'
158
+ ? AppColors_1.AppColors.amber700
159
+ : AppColors_1.AppColors.sky600,
160
+ },
161
+ ]}>
162
+ {selected.client.toUpperCase()}
163
+ </react_native_1.Text>
164
+ </react_native_1.View>)}
165
+
105
166
  {/* Status Pill */}
106
167
  {selected.status != null && (<react_native_1.View style={[
107
168
  styles_1.default.chip,
@@ -191,7 +252,7 @@ const NetworkDetail = react_1.default.memo(() => {
191
252
  </react_native_1.View>
192
253
 
193
254
  <react_native_1.View style={styles_1.default.detailInfoRight}>
194
- <TouchableScale_1.default style={[styles_1.default.iconSquareBtn, { backgroundColor: `${AppColors_1.AppColors.sky600}15` }]} onPress={() => react_native_1.Linking.openURL(detailDisplayUrl)} hitSlop={12}>
255
+ <TouchableScale_1.default style={[styles_1.default.iconSquareBtn, { backgroundColor: `${AppColors_1.AppColors.sky600}15` }]} onPress={handleOpenUrl} hitSlop={12}>
195
256
  <NetworkIcons_1.ExternalLinkIcon color={AppColors_1.AppColors.sky600} size={14}/>
196
257
  </TouchableScale_1.default>
197
258
  <CopyButton_1.default value={(0, helpers_1.getFetchCommand)(selected)} label="fetch()" iconType="fetch"/>
@@ -209,7 +270,7 @@ const NetworkDetail = react_1.default.memo(() => {
209
270
  padding: 10,
210
271
  marginTop: 8,
211
272
  gap: 6,
212
- }} onPress={() => react_native_1.Linking.openURL(detailDisplayUrl)}>
273
+ }} onPress={handleOpenUrl}>
213
274
  <react_native_1.View style={{
214
275
  flexDirection: 'row',
215
276
  alignItems: 'center',
@@ -392,7 +453,7 @@ const NetworkDetail = react_1.default.memo(() => {
392
453
  if (!reqExpanded && !showReqDiff)
393
454
  setReqExpanded(true);
394
455
  }}/>
395
- {showReqDiff ? (<DiffViewer_1.default oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer_1.default data={selected.request} search={detailSearch} forceOpen/>) : (<react_native_1.View style={styles_1.default.codeBlock}>
456
+ {showReqDiff ? (<DiffViewer_1.default oldData={prevRequestData} newData={selected.request} forceOpen={reqExpanded}/>) : reqExpanded ? (<JsonViewer_1.default data={selected.request} search={detailSearch}/>) : (<react_native_1.View style={styles_1.default.codeBlock}>
396
457
  <react_native_1.Text style={[
397
458
  styles_1.default.codeText,
398
459
  { color: AppColors_1.AppColors.grayTextWeak },
@@ -411,26 +472,6 @@ const NetworkDetail = react_1.default.memo(() => {
411
472
  <NetworkIcons_1.ClearIcon color={AppColors_1.AppColors.grayTextWeak} size={14}/>
412
473
  </react_native_1.Pressable>)}
413
474
  </react_native_1.View>
414
- {selected.response != null && (<react_native_1.View style={{
415
- flexDirection: 'row',
416
- alignItems: 'center',
417
- gap: 4,
418
- paddingHorizontal: 10,
419
- height: 34,
420
- borderRadius: 8,
421
- borderWidth: 1,
422
- borderColor: `${AppColors_1.AppColors.purple}30`,
423
- backgroundColor: `${AppColors_1.AppColors.purple}10`,
424
- }}>
425
- <NetworkIcons_1.SizeIcon color={AppColors_1.AppColors.purple} size={12}/>
426
- <react_native_1.Text style={{
427
- fontFamily: AppFonts_1.AppFonts.interBold,
428
- fontSize: 10.5,
429
- color: AppColors_1.AppColors.purple,
430
- }}>
431
- {(0, helpers_1.getSize)(selected.response)}
432
- </react_native_1.Text>
433
- </react_native_1.View>)}
434
475
  </react_native_1.View>
435
476
 
436
477
  <react_native_1.View style={styles_1.default.sectionContainer}>
@@ -439,7 +480,7 @@ const NetworkDetail = react_1.default.memo(() => {
439
480
  if (!resExpanded && !showResDiff)
440
481
  setResExpanded(true);
441
482
  }}/>
442
- {showResDiff ? (<DiffViewer_1.default oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer_1.default data={selected.response} search={detailSearch} forceOpen wrap/>) : (<react_native_1.View style={styles_1.default.codeBlock}>
483
+ {showResDiff ? (<DiffViewer_1.default oldData={prevResponseData} newData={selected.response} forceOpen={resExpanded}/>) : resExpanded ? (<JsonViewer_1.default data={selected.response} search={detailSearch} wrap/>) : (<react_native_1.View style={styles_1.default.codeBlock}>
443
484
  <react_native_1.Text style={[
444
485
  styles_1.default.codeText,
445
486
  { color: AppColors_1.AppColors.grayTextWeak },
@@ -6,6 +6,11 @@ export interface PerformanceEvent {
6
6
  category: 'render' | 'navigation' | 'memory' | 'io' | 'bridge';
7
7
  fps: number;
8
8
  durationMs: number;
9
+ droppedFrames?: number;
10
+ frameTimeMs?: number;
11
+ frameBudgetPct?: number;
12
+ bottleneckThread?: 'JS Thread' | 'UI Thread' | 'Bridge' | 'Balanced';
13
+ screenName?: string;
9
14
  label: string;
10
15
  detail: string;
11
16
  source?: string;