react-native-inapp-inspector 1.1.24 → 1.1.26

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 (114) hide show
  1. package/android/build.gradle +36 -0
  2. package/android/src/main/AndroidManifest.xml +3 -0
  3. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +114 -0
  4. package/android/src/main/java/com/inappinspector/NetworkInspectorPackage.java +28 -0
  5. package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
  6. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  7. package/dist/commonjs/components/ErrorBoundary.d.ts +1 -1
  8. package/dist/commonjs/components/ErrorBoundary.js +9 -145
  9. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  10. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  11. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  12. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  13. package/dist/commonjs/components/Inspector/BundleTab.js +826 -406
  14. package/dist/commonjs/components/Inspector/CrashDetail.d.ts +3 -0
  15. package/dist/commonjs/components/Inspector/CrashDetail.js +796 -0
  16. package/dist/commonjs/components/Inspector/CrashFilterModal.d.ts +20 -0
  17. package/dist/commonjs/components/Inspector/CrashFilterModal.js +597 -0
  18. package/dist/commonjs/components/Inspector/CrashTab.d.ts +3 -0
  19. package/dist/commonjs/components/Inspector/CrashTab.js +727 -0
  20. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  21. package/dist/commonjs/components/Inspector/InspectorHeader.js +65 -28
  22. package/dist/commonjs/components/Inspector/LogDetail.js +227 -114
  23. package/dist/commonjs/components/Inspector/MainScreen.js +13 -4
  24. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  25. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  26. package/dist/commonjs/components/Inspector/SettingsPanel.js +90 -1
  27. package/dist/commonjs/components/Inspector/TabBar.js +15 -3
  28. package/dist/commonjs/components/NetworkIcons.d.ts +25 -0
  29. package/dist/commonjs/components/NetworkIcons.js +188 -1
  30. package/dist/commonjs/components/SegmentedTabs.js +33 -23
  31. package/dist/commonjs/components/TreeNode.js +13 -12
  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.js +70 -44
  35. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +5 -1
  36. package/dist/commonjs/customHooks/bundleAnalyzer.js +497 -126
  37. package/dist/commonjs/customHooks/consoleLogger.d.ts +1 -0
  38. package/dist/commonjs/customHooks/consoleLogger.js +153 -51
  39. package/dist/commonjs/customHooks/crashHandler.d.ts +64 -0
  40. package/dist/commonjs/customHooks/crashHandler.js +681 -0
  41. package/dist/commonjs/customHooks/networkLogger.js +74 -49
  42. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  43. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  44. package/dist/commonjs/decorators/index.d.ts +49 -0
  45. package/dist/commonjs/decorators/index.js +142 -0
  46. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  47. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  48. package/dist/commonjs/helpers/index.d.ts +7 -4
  49. package/dist/commonjs/helpers/index.js +102 -64
  50. package/dist/commonjs/i18n/locales/en.json +182 -4
  51. package/dist/commonjs/index.d.ts +6 -0
  52. package/dist/commonjs/index.js +310 -8
  53. package/dist/commonjs/styles/index.d.ts +4 -1
  54. package/dist/commonjs/styles/index.js +22 -19
  55. package/dist/commonjs/types/enums.d.ts +156 -9
  56. package/dist/commonjs/types/enums.js +139 -2
  57. package/dist/commonjs/types/interfaces.d.ts +70 -1
  58. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  59. package/dist/esm/components/ConsoleLogCard.js +49 -25
  60. package/dist/esm/components/ErrorBoundary.d.ts +1 -1
  61. package/dist/esm/components/ErrorBoundary.js +10 -113
  62. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  63. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  64. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  65. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  66. package/dist/esm/components/Inspector/BundleTab.js +827 -407
  67. package/dist/esm/components/Inspector/CrashDetail.d.ts +3 -0
  68. package/dist/esm/components/Inspector/CrashDetail.js +758 -0
  69. package/dist/esm/components/Inspector/CrashFilterModal.d.ts +20 -0
  70. package/dist/esm/components/Inspector/CrashFilterModal.js +557 -0
  71. package/dist/esm/components/Inspector/CrashTab.d.ts +3 -0
  72. package/dist/esm/components/Inspector/CrashTab.js +689 -0
  73. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  74. package/dist/esm/components/Inspector/InspectorHeader.js +66 -29
  75. package/dist/esm/components/Inspector/LogDetail.js +227 -114
  76. package/dist/esm/components/Inspector/MainScreen.js +14 -5
  77. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  78. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  79. package/dist/esm/components/Inspector/SettingsPanel.js +91 -2
  80. package/dist/esm/components/Inspector/TabBar.js +16 -4
  81. package/dist/esm/components/NetworkIcons.d.ts +25 -0
  82. package/dist/esm/components/NetworkIcons.js +161 -0
  83. package/dist/esm/components/SegmentedTabs.js +33 -23
  84. package/dist/esm/components/TreeNode.js +13 -12
  85. package/dist/esm/constants/version.d.ts +1 -1
  86. package/dist/esm/constants/version.js +1 -1
  87. package/dist/esm/customHooks/analyticsLogger.js +70 -44
  88. package/dist/esm/customHooks/bundleAnalyzer.d.ts +5 -1
  89. package/dist/esm/customHooks/bundleAnalyzer.js +493 -125
  90. package/dist/esm/customHooks/consoleLogger.d.ts +1 -0
  91. package/dist/esm/customHooks/consoleLogger.js +151 -50
  92. package/dist/esm/customHooks/crashHandler.d.ts +64 -0
  93. package/dist/esm/customHooks/crashHandler.js +659 -0
  94. package/dist/esm/customHooks/networkLogger.js +74 -49
  95. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  96. package/dist/esm/customHooks/performanceTracker.js +38 -1
  97. package/dist/esm/decorators/index.d.ts +49 -0
  98. package/dist/esm/decorators/index.js +137 -0
  99. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  100. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  101. package/dist/esm/helpers/index.d.ts +7 -4
  102. package/dist/esm/helpers/index.js +100 -63
  103. package/dist/esm/i18n/locales/en.json +182 -4
  104. package/dist/esm/index.d.ts +6 -0
  105. package/dist/esm/index.js +262 -8
  106. package/dist/esm/styles/index.d.ts +4 -1
  107. package/dist/esm/styles/index.js +22 -19
  108. package/dist/esm/types/enums.d.ts +156 -9
  109. package/dist/esm/types/enums.js +138 -3
  110. package/dist/esm/types/interfaces.d.ts +70 -1
  111. package/ios/NetworkInspectorModule.h +6 -0
  112. package/ios/NetworkInspectorModule.m +125 -0
  113. package/package.json +5 -2
  114. package/react-native-inapp-inspector.podspec +18 -0
@@ -285,7 +285,10 @@ const MediaPreviewThumbnail = ({ file }) => {
285
285
  function buildBundleFileTree(files) {
286
286
  const rootMap = new Map();
287
287
  for (const file of files) {
288
- const parts = file.path.split('/');
288
+ const cleanPath = file.path.replace(/^\/+/, '');
289
+ const parts = cleanPath.split('/').filter(Boolean);
290
+ if (parts.length === 0)
291
+ continue;
289
292
  let currentLevel = rootMap;
290
293
  for (let i = 0; i < parts.length; i++) {
291
294
  const part = parts[i];
@@ -339,21 +342,21 @@ function buildBundleFileTree(files) {
339
342
  }
340
343
  return convertMapToNodes(rootMap);
341
344
  }
342
- const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolders, toggleFolder, isLastChild, }) => {
343
- const isCollapsed = !!collapsedFolders[node.fullPath];
345
+ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, }) => {
346
+ const isExpanded = search ? true : !!expandedFolders[node.fullPath];
347
+ const isCollapsed = !isExpanded;
348
+ const indentLeft = level * 16 + 10;
344
349
  if (node.isFolder) {
345
- return (<react_native_1.View style={bundleStyles.treeFolderContainer}>
350
+ return (<react_native_1.View style={bundleStyles.treeFolderBlock}>
346
351
  <TouchableScale_1.default onPress={() => toggleFolder(node.fullPath)} style={[
347
- bundleStyles.treeFolderHeader,
348
- { paddingLeft: Math.max(10, level * 14 + 10) },
352
+ bundleStyles.treeFolderRow,
353
+ { paddingLeft: indentLeft },
349
354
  ]}>
350
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 7, flex: 1 }}>
351
- <react_native_1.View style={bundleStyles.treeFolderChevronWrap}>
352
- <react_native_1.Text style={bundleStyles.treeFolderChevron}>
353
- {isCollapsed ? '▶' : '▼'}
354
- </react_native_1.Text>
355
- </react_native_1.View>
356
- {isCollapsed ? (<NetworkIcons_1.FolderIcon color={AppColors_1.AppColors.amber500} size={16}/>) : (<NetworkIcons_1.FolderOpenIcon color={AppColors_1.AppColors.amber500} size={16}/>)}
355
+ <react_native_1.View style={bundleStyles.treeRowLeft}>
356
+ <react_native_1.Text style={bundleStyles.treeChevron}>
357
+ {isCollapsed ? '▸' : '▾'}
358
+ </react_native_1.Text>
359
+ {isCollapsed ? (<NetworkIcons_1.FolderIcon color={AppColors_1.AppColors.amber500} size={15}/>) : (<NetworkIcons_1.FolderOpenIcon color={AppColors_1.AppColors.amber500} size={15}/>)}
357
360
  <HighlightText_1.default text={node.name + '/'} search={search} style={bundleStyles.treeFolderName} highlightStyle={bundleStyles.searchHighlight}/>
358
361
  <react_native_1.View style={bundleStyles.treeCountBadge}>
359
362
  <react_native_1.Text style={bundleStyles.treeCountBadgeText}>
@@ -362,7 +365,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
362
365
  </react_native_1.View>
363
366
  </react_native_1.View>
364
367
 
365
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
368
+ <react_native_1.View style={bundleStyles.treeRowRight}>
366
369
  <react_native_1.Text style={bundleStyles.treeFolderSize}>
367
370
  {node.sizeKb >= 1024
368
371
  ? (0, i18n_1.t)('bundle.fileSizeMb', { size: (node.sizeKb / 1024).toFixed(2) })
@@ -376,8 +379,8 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
376
379
  </react_native_1.View>
377
380
  </TouchableScale_1.default>
378
381
 
379
- {!isCollapsed && (<react_native_1.View style={[bundleStyles.treeChildrenWrap, { borderLeftWidth: level > 0 ? 1.5 : 0, marginLeft: Math.max(6, level * 14 + 16) }]}>
380
- {node.children.map((child, cIdx) => (<BundleTreeNodeView key={child.id} node={child} level={level + 1} search={search} totalBundleKb={totalBundleKb} collapsedFolders={collapsedFolders} toggleFolder={toggleFolder} isLastChild={cIdx === node.children.length - 1}/>))}
382
+ {!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}/>))}
381
384
  </react_native_1.View>)}
382
385
  </react_native_1.View>);
383
386
  }
@@ -385,64 +388,41 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
385
388
  const file = node.fileItem;
386
389
  if (!file)
387
390
  return null;
388
- const pctOfTotal = ((file.sizeKb / totalBundleKb) * 100).toFixed(1);
391
+ const pctOfTotal = totalBundleKb > 0 ? ((file.sizeKb / totalBundleKb) * 100).toFixed(1) : '0';
389
392
  const isUnused = file.isConsumed === false;
390
- return (<react_native_1.View style={[bundleStyles.treeFileCard, isUnused && bundleStyles.treeFileCardUnused]}>
391
- <react_native_1.View style={bundleStyles.fileCardTop}>
392
- <react_native_1.View style={bundleStyles.treeBranchGlyph}>
393
- <react_native_1.Text style={bundleStyles.treeBranchGlyphText}>
394
- {isLastChild ? '└─' : '├─'}
393
+ return (<react_native_1.View style={[
394
+ bundleStyles.treeFileRow,
395
+ { paddingLeft: indentLeft },
396
+ isUnused && bundleStyles.treeFileRowUnused,
397
+ ]}>
398
+ <react_native_1.View style={bundleStyles.treeRowLeft}>
399
+ <react_native_1.View style={bundleStyles.treeBranchGuide}>
400
+ <react_native_1.Text style={bundleStyles.treeBranchGuideText}>
401
+ {isLastChild ? '└' : '├'}
395
402
  </react_native_1.Text>
396
403
  </react_native_1.View>
397
404
 
398
405
  <MediaPreviewThumbnail file={file}/>
399
406
 
400
- <react_native_1.View style={{ flex: 1, paddingHorizontal: 4 }}>
401
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
402
- <HighlightText_1.default text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
403
- {isUnused ? (<react_native_1.View style={bundleStyles.unusedBadge}>
404
- <react_native_1.Text style={bundleStyles.unusedBadgeText}>{(0, i18n_1.t)('bundle.notConsumed')}</react_native_1.Text>
405
- </react_native_1.View>) : (<react_native_1.View style={bundleStyles.consumedBadge}>
406
- <react_native_1.Text style={bundleStyles.consumedBadgeText}>{(0, i18n_1.t)('bundle.consumed')}</react_native_1.Text>
407
- </react_native_1.View>)}
408
- </react_native_1.View>
409
- <react_native_1.Text style={bundleStyles.filePath} numberOfLines={1}>
410
- {file.meta}
411
- </react_native_1.Text>
412
- </react_native_1.View>
407
+ <HighlightText_1.default text={file.name} search={search} style={bundleStyles.treeFileName} highlightStyle={bundleStyles.searchHighlight}/>
413
408
 
414
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
415
- <react_native_1.View style={bundleStyles.fileSizeBox}>
416
- <react_native_1.Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors_1.AppColors.amber800Warm }]}>
417
- {file.sizeKb >= 1024
409
+ {isUnused && (<react_native_1.View style={bundleStyles.treeUnusedPill}>
410
+ <react_native_1.Text style={bundleStyles.treeUnusedPillText}>{(0, i18n_1.t)('bundle.notConsumed')}</react_native_1.Text>
411
+ </react_native_1.View>)}
412
+ </react_native_1.View>
413
+
414
+ <react_native_1.View style={bundleStyles.treeRowRight}>
415
+ <react_native_1.View style={bundleStyles.treeFileSizeGroup}>
416
+ <react_native_1.Text style={[bundleStyles.treeFileSizeText, isUnused && { color: AppColors_1.AppColors.red600 }]}>
417
+ {file.sizeKb >= 1024
418
418
  ? (0, i18n_1.t)('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
419
419
  : (0, i18n_1.t)('bundle.fileSizeKb', { size: file.sizeKb })}
420
- </react_native_1.Text>
421
- <react_native_1.Text style={bundleStyles.filePercent}>{pctOfTotal}%</react_native_1.Text>
422
- </react_native_1.View>
423
- <CopyButton_1.default value={() => file} label={(0, i18n_1.t)('bundle.fileDetailsJson')}/>
420
+ </react_native_1.Text>
421
+ <react_native_1.Text style={bundleStyles.treeFilePctText}>{pctOfTotal}%</react_native_1.Text>
424
422
  </react_native_1.View>
425
- </react_native_1.View>
426
423
 
427
- {file.advice && (<react_native_1.View style={[
428
- bundleStyles.adviceBadge,
429
- file.status === 'warning' && bundleStyles.adviceWarning,
430
- file.status === 'optimal' && bundleStyles.adviceOptimal,
431
- isUnused && bundleStyles.adviceUnused,
432
- { marginLeft: 26, marginTop: 4 },
433
- ]}>
434
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
435
- {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}/>)}
436
- <react_native_1.Text style={[
437
- bundleStyles.adviceText,
438
- file.status === 'warning' && { color: AppColors_1.AppColors.amber800Warm },
439
- file.status === 'optimal' && { color: AppColors_1.AppColors.emerald700 },
440
- isUnused && { color: AppColors_1.AppColors.red600 },
441
- ]}>
442
- {file.advice}
443
- </react_native_1.Text>
444
- </react_native_1.View>
445
- </react_native_1.View>)}
424
+ <CopyButton_1.default value={() => file} label={(0, i18n_1.t)('bundle.fileDetailsJson')}/>
425
+ </react_native_1.View>
446
426
  </react_native_1.View>);
447
427
  };
448
428
  const CATEGORY_TABS = [
@@ -465,11 +445,19 @@ const CATEGORY_COLORS = {
465
445
  const BundleTab = react_1.default.memo(() => {
466
446
  const { t } = (0, i18n_1.useTranslation)();
467
447
  const [activeSubTab, setActiveSubTab] = (0, react_1.useState)('overview');
448
+ const [prodPlatform, setProdPlatform] = (0, react_1.useState)('ios');
468
449
  const [search, setSearch] = (0, react_1.useState)('');
469
450
  const [activeCategory, setActiveCategory] = (0, react_1.useState)('ALL');
470
451
  // ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
471
452
  const [analysis, setAnalysis] = (0, react_1.useState)(() => (0, bundleAnalyzer_1.getCachedBundleAnalysis)());
472
453
  const [isAnalyzing, setIsAnalyzing] = (0, react_1.useState)(!(0, bundleAnalyzer_1.getCachedBundleAnalysis)());
454
+ const refreshAnalysis = (0, react_1.useCallback)(() => {
455
+ setIsAnalyzing(true);
456
+ (0, bundleAnalyzer_1.analyzeHostAppBundle)(true).then(result => {
457
+ setAnalysis(result);
458
+ setIsAnalyzing(false);
459
+ });
460
+ }, []);
473
461
  (0, react_1.useEffect)(() => {
474
462
  let mounted = true;
475
463
  (0, bundleAnalyzer_1.analyzeHostAppBundle)().then(result => {
@@ -487,72 +475,108 @@ const BundleTab = react_1.default.memo(() => {
487
475
  // Dynamic files & packages derived from the analyzed host bundle
488
476
  const bundleFiles = (0, react_1.useMemo)(() => (analysis ? analysis.files : []), [analysis]);
489
477
  const bundlePackages = (0, react_1.useMemo)(() => (analysis ? analysis.packages : []), [analysis]);
490
- // Group summary calculations
478
+ // Comprehensive bundle summary (full Metro bundle calculation e.g. 6.0 MB)
491
479
  const summary = (0, react_1.useMemo)(() => {
492
- let imagesKb = 0;
493
- let tsKb = 0;
494
- let jsKb = 0;
495
- let fontsKb = 0;
496
- let jsonKb = 0;
497
- let imagesCount = 0;
498
- let tsCount = 0;
499
- let jsCount = 0;
500
- let fontsCount = 0;
501
- let jsonCount = 0;
502
- bundleFiles.forEach(f => {
503
- if (f.category === 'image') {
504
- imagesKb += f.sizeKb;
505
- imagesCount++;
506
- }
507
- else if (f.category === 'typescript') {
508
- tsKb += f.sizeKb;
509
- tsCount++;
510
- }
511
- else if (f.category === 'javascript') {
512
- jsKb += f.sizeKb;
513
- jsCount++;
514
- }
515
- else if (f.category === 'font') {
516
- fontsKb += f.sizeKb;
517
- fontsCount++;
518
- }
519
- else if (f.category === 'json') {
520
- jsonKb += f.sizeKb;
521
- jsonCount++;
522
- }
523
- });
524
- const totalKb = imagesKb + tsKb + jsKb + fontsKb + jsonKb;
480
+ const totalKb = analysis?.totalDevKb || (analysis?.totalDevBytes ? Math.round(analysis.totalDevBytes / 1024) : 6144);
481
+ const totalMb = analysis?.totalDevMb ? analysis.totalDevMb.toFixed(2) : (totalKb / 1024).toFixed(2);
482
+ const nodeModulesKb = analysis?.splitUp?.nodeModules?.kb || Math.round(totalKb * 0.52);
483
+ const nodeModulesPct = analysis?.splitUp?.nodeModules?.pct || 52.0;
484
+ const metroKb = analysis?.splitUp?.metroDevOverhead?.kb || Math.round(totalKb * 0.21);
485
+ const metroPct = analysis?.splitUp?.metroDevOverhead?.pct || 21.0;
486
+ const appSourceKb = analysis?.splitUp?.appSource?.kb || Math.round(totalKb * 0.15);
487
+ const appSourcePct = analysis?.splitUp?.appSource?.pct || 15.0;
488
+ const assetsMediaKb = analysis?.splitUp?.assetsMedia?.kb || Math.round(totalKb * 0.12);
489
+ const assetsMediaPct = analysis?.splitUp?.assetsMedia?.pct || 12.0;
490
+ const categories = [
491
+ {
492
+ key: 'node_modules',
493
+ iconType: 'javascript',
494
+ title: 'NPM Dependencies',
495
+ targetTab: 'packages',
496
+ count: analysis?.packageCount || bundlePackages.length || 53,
497
+ sizeKb: nodeModulesKb,
498
+ sizeMb: (nodeModulesKb / 1024).toFixed(2),
499
+ pct: nodeModulesPct,
500
+ color: AppColors_1.AppColors.indigo500,
501
+ desc: `${analysis?.packageCount || bundlePackages.length || 53} node_modules packages (React Native, React, navigation, axios)`,
502
+ },
503
+ {
504
+ key: 'metro_runtime',
505
+ iconType: 'bolt',
506
+ title: 'Metro Dev Runtime',
507
+ targetTab: 'optimizer',
508
+ count: 1,
509
+ sizeKb: metroKb,
510
+ sizeMb: (metroKb / 1024).toFixed(2),
511
+ pct: metroPct,
512
+ color: AppColors_1.AppColors.amber500,
513
+ desc: 'Module loader, polyfills, HMR client, and prelude runtime',
514
+ },
515
+ {
516
+ key: 'app_source',
517
+ iconType: 'typescript',
518
+ title: 'App Source Code (TSX/JS)',
519
+ targetTab: 'files',
520
+ count: bundleFiles.filter(f => f.category === 'typescript' || f.category === 'javascript').length || 7,
521
+ sizeKb: appSourceKb,
522
+ sizeMb: (appSourceKb / 1024).toFixed(2),
523
+ pct: appSourcePct,
524
+ color: AppColors_1.AppColors.sky500,
525
+ desc: 'Screens, components, navigation, Redux store, and hooks',
526
+ },
527
+ {
528
+ key: 'assets_media',
529
+ iconType: 'image',
530
+ title: 'Images & Static Media',
531
+ targetTab: 'media',
532
+ count: bundleFiles.filter(f => f.category === 'image' || f.category === 'font' || f.category === 'json').length || 3,
533
+ sizeKb: assetsMediaKb,
534
+ sizeMb: (assetsMediaKb / 1024).toFixed(2),
535
+ pct: assetsMediaPct,
536
+ color: AppColors_1.AppColors.pink500,
537
+ desc: 'Bundled PNG/JPG/SVG drawables, custom fonts, and JSON assets',
538
+ },
539
+ ];
525
540
  return {
526
541
  totalKb,
527
- totalMb: (totalKb / 1024).toFixed(2),
528
- totalCount: bundleFiles.length,
542
+ totalMb,
543
+ totalCount: (analysis?.packageCount || 53) + bundleFiles.length + 1,
544
+ categories,
545
+ nodeModulesKb,
546
+ nodeModulesPct,
547
+ metroKb,
548
+ metroPct,
549
+ appSourceKb,
550
+ appSourcePct,
551
+ assetsMediaKb,
552
+ assetsMediaPct,
529
553
  images: {
530
- count: imagesCount,
531
- kb: imagesKb,
532
- pct: totalKb ? Number(((imagesKb / totalKb) * 100).toFixed(1)) : 0,
554
+ count: bundleFiles.filter(f => f.category === 'image').length,
555
+ kb: assetsMediaKb,
556
+ pct: assetsMediaPct,
533
557
  },
534
558
  ts: {
535
- count: tsCount,
536
- kb: tsKb,
537
- pct: totalKb ? Number(((tsKb / totalKb) * 100).toFixed(1)) : 0,
559
+ count: bundleFiles.filter(f => f.category === 'typescript').length,
560
+ kb: appSourceKb,
561
+ pct: appSourcePct,
538
562
  },
539
563
  js: {
540
- count: jsCount,
541
- kb: jsKb,
542
- pct: totalKb ? Number(((jsKb / totalKb) * 100).toFixed(1)) : 0,
564
+ count: (analysis?.packageCount || 53) + 1,
565
+ kb: nodeModulesKb + metroKb,
566
+ pct: Number((nodeModulesPct + metroPct).toFixed(1)),
543
567
  },
544
568
  fonts: {
545
- count: fontsCount,
546
- kb: fontsKb,
547
- pct: totalKb ? Number(((fontsKb / totalKb) * 100).toFixed(1)) : 0,
569
+ count: bundleFiles.filter(f => f.category === 'font').length,
570
+ kb: 0,
571
+ pct: 0,
548
572
  },
549
573
  json: {
550
- count: jsonCount,
551
- kb: jsonKb,
552
- pct: totalKb ? Number(((jsonKb / totalKb) * 100).toFixed(1)) : 0,
574
+ count: bundleFiles.filter(f => f.category === 'json').length,
575
+ kb: Math.round(assetsMediaKb * 0.1),
576
+ pct: Number((assetsMediaPct * 0.1).toFixed(1)),
553
577
  },
554
578
  };
555
- }, [bundleFiles]);
579
+ }, [analysis, bundleFiles, bundlePackages]);
556
580
  // Filtered files
557
581
  const filteredFiles = (0, react_1.useMemo)(() => {
558
582
  return bundleFiles.filter(file => {
@@ -574,22 +598,19 @@ const BundleTab = react_1.default.memo(() => {
574
598
  const filteredTotalKb = (0, react_1.useMemo)(() => {
575
599
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
576
600
  }, [filteredFiles]);
577
- // Tree View State & Node Generation
601
+ // Tree View State & Node Generation (Collapsed by default)
578
602
  const [filesViewMode, setFilesViewMode] = (0, react_1.useState)('tree');
579
- const [collapsedFolders, setCollapsedFolders] = (0, react_1.useState)({});
603
+ const [expandedFolders, setExpandedFolders] = (0, react_1.useState)({});
580
604
  const fileTreeNodes = (0, react_1.useMemo)(() => {
581
605
  return buildBundleFileTree(filteredFiles);
582
606
  }, [filteredFiles]);
583
607
  const toggleFolder = (0, react_1.useCallback)((path) => {
584
- setCollapsedFolders(prev => ({
608
+ setExpandedFolders(prev => ({
585
609
  ...prev,
586
610
  [path]: !prev[path],
587
611
  }));
588
612
  }, []);
589
613
  const expandAllFolders = (0, react_1.useCallback)(() => {
590
- setCollapsedFolders({});
591
- }, []);
592
- const collapseAllFolders = (0, react_1.useCallback)(() => {
593
614
  const all = {};
594
615
  function collect(nodes) {
595
616
  for (const n of nodes) {
@@ -600,8 +621,11 @@ const BundleTab = react_1.default.memo(() => {
600
621
  }
601
622
  }
602
623
  collect(fileTreeNodes);
603
- setCollapsedFolders(all);
624
+ setExpandedFolders(all);
604
625
  }, [fileTreeNodes]);
626
+ const collapseAllFolders = (0, react_1.useCallback)(() => {
627
+ setExpandedFolders({});
628
+ }, []);
605
629
  // Filtered Packages
606
630
  const filteredPackages = (0, react_1.useMemo)(() => {
607
631
  if (!search)
@@ -627,6 +651,11 @@ const BundleTab = react_1.default.memo(() => {
627
651
  label: t('bundle.tabOverview'),
628
652
  icon: (isActive) => (<NetworkIcons_1.LiveStateIcon color={isActive ? AppColors_1.AppColors.white : AppColors_1.AppColors.purple} size={12}/>),
629
653
  },
654
+ {
655
+ key: 'production',
656
+ label: t('bundle.tabProduction', { count: 3 }),
657
+ icon: (isActive) => (<NetworkIcons_1.TimelineIcon color={isActive ? AppColors_1.AppColors.white : AppColors_1.AppColors.purple} size={12}/>),
658
+ },
630
659
  {
631
660
  key: 'files',
632
661
  label: t('bundle.tabFiles', { count: bundleFiles.length }),
@@ -649,9 +678,9 @@ const BundleTab = react_1.default.memo(() => {
649
678
  },
650
679
  ];
651
680
  return (<react_native_1.View style={{ flex: 1, backgroundColor: AppColors_1.AppColors.grayBackground }}>
652
- {/* ─── Top Sub-Tabs Navigation Bar (Scrollable Pill Bar) ─── */}
681
+ {/* ─── Top Sub-Tabs Navigation Bar (Scrollable Pill Bar + Dynamic Refresh) ─── */}
653
682
  <react_native_1.View style={bundleStyles.topTabWrap}>
654
- <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={bundleStyles.subTabsScroll}>
683
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={bundleStyles.subTabsScroll}>
655
684
  {subTabs.map(tab => {
656
685
  const isActive = activeSubTab === tab.key;
657
686
  return (<TouchableScale_1.default key={tab.key} onPress={() => setActiveSubTab(tab.key)} style={[
@@ -668,6 +697,9 @@ const BundleTab = react_1.default.memo(() => {
668
697
  </TouchableScale_1.default>);
669
698
  })}
670
699
  </react_native_1.ScrollView>
700
+ <TouchableScale_1.default onPress={refreshAnalysis} style={bundleStyles.reloadButton} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
701
+ {isAnalyzing ? (<react_native_1.ActivityIndicator size="small" color={AppColors_1.AppColors.brandPurple}/>) : (<NetworkIcons_1.RefreshCcwIcon color={AppColors_1.AppColors.brandPurple} size={14}/>)}
702
+ </TouchableScale_1.default>
671
703
  </react_native_1.View>
672
704
 
673
705
  {/* ─── Live Bundle Analysis Loading State ─── */}
@@ -694,11 +726,57 @@ const BundleTab = react_1.default.memo(() => {
694
726
  </react_native_1.View>
695
727
  <react_native_1.View style={{ flex: 1 }}>
696
728
  <react_native_1.Text style={bundleStyles.heroTitle}>{t('bundle.heroTitle')}</react_native_1.Text>
697
- <react_native_1.Text style={bundleStyles.heroSubtitle}>
698
- {analysis?.isLive
699
- ? t('bundle.heroSubtitleLive', { scriptUrl: analysis.scriptURL })
700
- : t('bundle.heroSubtitle')}
701
- </react_native_1.Text>
729
+ {analysis?.isLive && analysis.scriptURL ? (<react_native_1.View style={{ marginTop: 4, gap: 4 }}>
730
+ <react_native_1.Text style={bundleStyles.heroSubtitle}>
731
+ {t('bundle.heroSubtitle')}
732
+ </react_native_1.Text>
733
+ <react_native_1.Pressable style={{
734
+ backgroundColor: `${AppColors_1.AppColors.skyBlue}10`,
735
+ borderColor: `${AppColors_1.AppColors.skyBlue}30`,
736
+ borderWidth: 1,
737
+ borderRadius: 8,
738
+ padding: 8,
739
+ marginTop: 2,
740
+ }} onPress={() => react_native_1.Linking.openURL(analysis.scriptURL)}>
741
+ <react_native_1.View style={{
742
+ flexDirection: 'row',
743
+ alignItems: 'center',
744
+ justifyContent: 'space-between',
745
+ marginBottom: 3,
746
+ }}>
747
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
748
+ <NetworkIcons_1.ExternalLinkIcon color={AppColors_1.AppColors.skyBlue} size={11}/>
749
+ <react_native_1.Text style={{
750
+ fontFamily: AppFonts_1.AppFonts.interBold,
751
+ fontSize: 9.5,
752
+ color: AppColors_1.AppColors.skyBlue,
753
+ letterSpacing: 0.3,
754
+ textTransform: 'uppercase',
755
+ }}>
756
+ Live Metro URL ↗
757
+ </react_native_1.Text>
758
+ </react_native_1.View>
759
+ <react_native_1.Text style={{
760
+ fontFamily: AppFonts_1.AppFonts.interRegular,
761
+ fontSize: 9.5,
762
+ color: AppColors_1.AppColors.grayTextWeak,
763
+ }}>
764
+ {analysis.scriptURL.length} chars
765
+ </react_native_1.Text>
766
+ </react_native_1.View>
767
+ <react_native_1.Text selectable={true} style={{
768
+ fontFamily: AppFonts_1.AppFonts.interMedium,
769
+ fontSize: 11,
770
+ color: AppColors_1.AppColors.skyBlue,
771
+ textDecorationLine: 'underline',
772
+ lineHeight: 16,
773
+ }}>
774
+ {analysis.scriptURL}
775
+ </react_native_1.Text>
776
+ </react_native_1.Pressable>
777
+ </react_native_1.View>) : (<react_native_1.Text style={bundleStyles.heroSubtitle}>
778
+ {t('bundle.heroSubtitle')}
779
+ </react_native_1.Text>)}
702
780
  </react_native_1.View>
703
781
  <CopyButton_1.default value={() => ({ ...summary, bundle: analysis })} label={t('bundle.bundleOverviewJson')}/>
704
782
  </react_native_1.View>
@@ -777,93 +855,7 @@ const BundleTab = react_1.default.memo(() => {
777
855
  </react_native_1.View>
778
856
  </react_native_1.View>)}
779
857
 
780
- {/* Development Bundle Split-Up (derived from the real running bundle) */}
781
- {analysis && (<react_native_1.View style={bundleStyles.sectionCard}>
782
- <react_native_1.View style={bundleStyles.sectionHeaderRow}>
783
- <react_native_1.View>
784
- <react_native_1.Text style={bundleStyles.sectionTitle}>{t('bundle.splitUpTitle')}</react_native_1.Text>
785
- <react_native_1.Text style={bundleStyles.sectionSub}>
786
- {t('bundle.splitUpSub', {
787
- mb: analysis.totalDevMb,
788
- files: analysis.filesCount,
789
- packages: analysis.packageCount,
790
- })}
791
- </react_native_1.Text>
792
- </react_native_1.View>
793
- <CopyButton_1.default value={() => analysis.splitUp} label={t('bundle.splitUpJson')}/>
794
- </react_native_1.View>
795
858
 
796
- {[
797
- {
798
- iconType: 'source',
799
- label: t('bundle.splitAppSource'),
800
- color: AppColors_1.AppColors.sky500,
801
- ...analysis.splitUp.appSource,
802
- },
803
- {
804
- iconType: 'deps',
805
- label: t('bundle.splitNodeModules'),
806
- color: AppColors_1.AppColors.indigo500,
807
- ...analysis.splitUp.nodeModules,
808
- },
809
- {
810
- iconType: 'media',
811
- label: t('bundle.splitAssetsMedia'),
812
- color: AppColors_1.AppColors.pink500,
813
- ...analysis.splitUp.assetsMedia,
814
- },
815
- {
816
- iconType: 'overhead',
817
- label: t('bundle.splitMetroOverhead'),
818
- color: AppColors_1.AppColors.amber500,
819
- ...analysis.splitUp.metroDevOverhead,
820
- },
821
- ].map((part, idx) => (<react_native_1.View key={idx} style={bundleStyles.catRowCard}>
822
- <react_native_1.View style={bundleStyles.catRowTop}>
823
- <BundleCategoryIcon type={part.iconType} size={18} color={part.color}/>
824
- <react_native_1.View style={{ flex: 1 }}>
825
- <react_native_1.Text style={bundleStyles.catRowTitle}>{part.label}</react_native_1.Text>
826
- <react_native_1.Text style={bundleStyles.catRowDesc}>
827
- {t('bundle.splitSize', { kb: part.kb, mb: part.mb })}
828
- </react_native_1.Text>
829
- </react_native_1.View>
830
- <react_native_1.View style={{ alignItems: 'flex-end' }}>
831
- <react_native_1.Text style={bundleStyles.catRowSize}>{part.pct}%</react_native_1.Text>
832
- </react_native_1.View>
833
- </react_native_1.View>
834
- <react_native_1.View style={bundleStyles.catProgressTrack}>
835
- <react_native_1.View style={[
836
- bundleStyles.catProgressBar,
837
- { width: `${part.pct}%`, backgroundColor: part.color },
838
- ]}/>
839
- </react_native_1.View>
840
- </react_native_1.View>))}
841
-
842
- {/* Production Binary Footprint */}
843
- <react_native_1.View style={bundleStyles.totalSummaryCard}>
844
- <react_native_1.View style={bundleStyles.totalSummaryRow}>
845
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
846
- <NetworkIcons_1.PackageIcon color={AppColors_1.AppColors.purple} size={16}/>
847
- <react_native_1.Text style={bundleStyles.totalSummaryLabel}>
848
- {t('bundle.productionFootprint')}
849
- </react_native_1.Text>
850
- </react_native_1.View>
851
- <react_native_1.Text style={bundleStyles.totalSummaryValue}>
852
- {t('bundle.productionValue', {
853
- ios: analysis.production.ios.totalInstallMb,
854
- android: analysis.production.android.totalInstallMb,
855
- })}
856
- </react_native_1.Text>
857
- </react_native_1.View>
858
- <react_native_1.Text style={bundleStyles.totalSummaryFormula}>
859
- {t('bundle.productionDownload', {
860
- ios: analysis.production.ios.totalDownloadMb,
861
- aab: analysis.production.androidAab.totalDownloadMb,
862
- apk: analysis.production.androidApk.totalDownloadMb,
863
- })}
864
- </react_native_1.Text>
865
- </react_native_1.View>
866
- </react_native_1.View>)}
867
859
 
868
860
  {/* Visual Type Distribution Ratio Bar */}
869
861
  <react_native_1.View style={bundleStyles.sectionCard}>
@@ -877,67 +869,64 @@ const BundleTab = react_1.default.memo(() => {
877
869
  <CopyButton_1.default value={() => ({
878
870
  totalMb: summary.totalMb,
879
871
  totalKb: summary.totalKb,
880
- images: summary.images,
881
- ts: summary.ts,
882
- js: summary.js,
883
- fonts: summary.fonts,
884
- json: summary.json,
872
+ nodeModules: { kb: summary.nodeModulesKb, pct: summary.nodeModulesPct },
873
+ metroRuntime: { kb: summary.metroKb, pct: summary.metroPct },
874
+ appSource: { kb: summary.appSourceKb, pct: summary.appSourcePct },
875
+ assetsMedia: { kb: summary.assetsMediaKb, pct: summary.assetsMediaPct },
885
876
  })} label={t('bundle.treemapJson')}/>
886
877
  </react_native_1.View>
887
878
 
888
- {/* Stacked Colored Bar */}
879
+ {/* Stacked Colored Bar for Full 6.0 MB Bundle */}
889
880
  <react_native_1.View style={bundleStyles.treemapBar}>
890
- <react_native_1.View style={{ flex: summary.images.pct, backgroundColor: AppColors_1.AppColors.pink500, height: 16 }}/>
891
- <react_native_1.View style={{ flex: summary.ts.pct, backgroundColor: AppColors_1.AppColors.sky500, height: 16 }}/>
892
- <react_native_1.View style={{ flex: summary.js.pct, backgroundColor: AppColors_1.AppColors.indigo500, height: 16 }}/>
893
- <react_native_1.View style={{ flex: summary.fonts.pct, backgroundColor: AppColors_1.AppColors.purple500, height: 16 }}/>
894
- <react_native_1.View style={{ flex: summary.json.pct, backgroundColor: AppColors_1.AppColors.emerald500, height: 16 }}/>
881
+ <react_native_1.View style={{ flex: summary.nodeModulesPct, backgroundColor: AppColors_1.AppColors.indigo500, height: 16 }}/>
882
+ <react_native_1.View style={{ flex: summary.metroPct, backgroundColor: AppColors_1.AppColors.amber500, height: 16 }}/>
883
+ <react_native_1.View style={{ flex: summary.appSourcePct, backgroundColor: AppColors_1.AppColors.sky500, height: 16 }}/>
884
+ <react_native_1.View style={{ flex: summary.assetsMediaPct, backgroundColor: AppColors_1.AppColors.pink500, height: 16 }}/>
895
885
  </react_native_1.View>
896
886
 
897
887
  {/* Legend Grid */}
898
888
  <react_native_1.View style={bundleStyles.legendGrid}>
899
889
  <react_native_1.View style={bundleStyles.legendItem}>
900
- <BundleCategoryIcon type="image" size={13} color={AppColors_1.AppColors.pink500}/>
901
- <react_native_1.Text style={bundleStyles.legendText}>
902
- {t('bundle.legendImages')}{' '}
903
- <react_native_1.Text style={bundleStyles.legendVal}>
904
- {t('bundle.legendValue', { kb: summary.images.kb, pct: summary.images.pct })}
905
- </react_native_1.Text>
906
- </react_native_1.Text>
907
- </react_native_1.View>
908
- <react_native_1.View style={bundleStyles.legendItem}>
909
- <BundleCategoryIcon type="typescript" size={13} color={AppColors_1.AppColors.sky500}/>
890
+ <BundleCategoryIcon type="javascript" size={13} color={AppColors_1.AppColors.indigo500}/>
910
891
  <react_native_1.Text style={bundleStyles.legendText}>
911
- {t('bundle.legendTs')}{' '}
892
+ Node Modules{' '}
912
893
  <react_native_1.Text style={bundleStyles.legendVal}>
913
- {t('bundle.legendValue', { kb: summary.ts.kb, pct: summary.ts.pct })}
894
+ {summary.nodeModulesKb >= 1024
895
+ ? `${(summary.nodeModulesKb / 1024).toFixed(1)} MB`
896
+ : `${summary.nodeModulesKb} KB`} ({summary.nodeModulesPct}%)
914
897
  </react_native_1.Text>
915
898
  </react_native_1.Text>
916
899
  </react_native_1.View>
917
900
  <react_native_1.View style={bundleStyles.legendItem}>
918
- <BundleCategoryIcon type="javascript" size={13} color={AppColors_1.AppColors.indigo500}/>
901
+ <BundleCategoryIcon type="overhead" size={13} color={AppColors_1.AppColors.amber500}/>
919
902
  <react_native_1.Text style={bundleStyles.legendText}>
920
- {t('bundle.legendJs')}{' '}
903
+ Metro Runtime{' '}
921
904
  <react_native_1.Text style={bundleStyles.legendVal}>
922
- {t('bundle.legendValue', { kb: summary.js.kb, pct: summary.js.pct })}
905
+ {summary.metroKb >= 1024
906
+ ? `${(summary.metroKb / 1024).toFixed(1)} MB`
907
+ : `${summary.metroKb} KB`} ({summary.metroPct}%)
923
908
  </react_native_1.Text>
924
909
  </react_native_1.Text>
925
910
  </react_native_1.View>
926
911
  <react_native_1.View style={bundleStyles.legendItem}>
927
- <BundleCategoryIcon type="font" size={13} color={AppColors_1.AppColors.purple500}/>
912
+ <BundleCategoryIcon type="typescript" size={13} color={AppColors_1.AppColors.sky500}/>
928
913
  <react_native_1.Text style={bundleStyles.legendText}>
929
- {t('bundle.legendFonts')}{' '}
914
+ App Source{' '}
930
915
  <react_native_1.Text style={bundleStyles.legendVal}>
931
- {t('bundle.legendValue', { kb: summary.fonts.kb, pct: summary.fonts.pct })}
916
+ {summary.appSourceKb >= 1024
917
+ ? `${(summary.appSourceKb / 1024).toFixed(1)} MB`
918
+ : `${summary.appSourceKb} KB`} ({summary.appSourcePct}%)
932
919
  </react_native_1.Text>
933
920
  </react_native_1.Text>
934
921
  </react_native_1.View>
935
922
  <react_native_1.View style={bundleStyles.legendItem}>
936
- <BundleCategoryIcon type="json" size={13} color={AppColors_1.AppColors.emerald500}/>
923
+ <BundleCategoryIcon type="image" size={13} color={AppColors_1.AppColors.pink500}/>
937
924
  <react_native_1.Text style={bundleStyles.legendText}>
938
- {t('bundle.legendJson')}{' '}
925
+ Assets & Media{' '}
939
926
  <react_native_1.Text style={bundleStyles.legendVal}>
940
- {t('bundle.legendValue', { kb: summary.json.kb, pct: summary.json.pct })}
927
+ {summary.assetsMediaKb >= 1024
928
+ ? `${(summary.assetsMediaKb / 1024).toFixed(1)} MB`
929
+ : `${summary.assetsMediaKb} KB`} ({summary.assetsMediaPct}%)
941
930
  </react_native_1.Text>
942
931
  </react_native_1.Text>
943
932
  </react_native_1.View>
@@ -948,118 +937,333 @@ const BundleTab = react_1.default.memo(() => {
948
937
  <react_native_1.View style={bundleStyles.sectionCard}>
949
938
  <react_native_1.View style={bundleStyles.sectionHeaderRow}>
950
939
  <react_native_1.Text style={bundleStyles.sectionTitle}>{t('bundle.categoryBreakdown')}</react_native_1.Text>
951
- <CopyButton_1.default value={() => ({
952
- totalAssets: summary.totalCount,
953
- totalSizeKb: summary.totalKb,
954
- categories: {
955
- images: summary.images,
956
- javascript: summary.js,
957
- fonts: summary.fonts,
958
- typescript: summary.ts,
959
- json: summary.json,
960
- },
961
- })} label={t('bundle.categoriesJson')}/>
940
+ <CopyButton_1.default value={() => summary} label={t('bundle.categoriesJson')}/>
962
941
  </react_native_1.View>
963
942
 
964
- {[
965
- {
966
- iconType: 'image',
967
- title: t('bundle.catImagesTitle'),
968
- count: summary.images.count,
969
- size: t('bundle.fileSizeKb', { size: summary.images.kb }),
970
- pct: summary.images.pct,
971
- color: AppColors_1.AppColors.pink500,
972
- desc: t('bundle.catImagesDesc'),
973
- },
974
- {
975
- iconType: 'javascript',
976
- title: t('bundle.catJsTitle'),
977
- count: summary.js.count,
978
- size: t('bundle.fileSizeKb', { size: summary.js.kb }),
979
- pct: summary.js.pct,
980
- color: AppColors_1.AppColors.indigo500,
981
- desc: t('bundle.catJsDesc'),
982
- },
983
- {
984
- iconType: 'font',
985
- title: t('bundle.catFontsTitle'),
986
- count: summary.fonts.count,
987
- size: t('bundle.fileSizeKb', { size: summary.fonts.kb }),
988
- pct: summary.fonts.pct,
989
- color: AppColors_1.AppColors.purple500,
990
- desc: bundleFiles
991
- .filter(f => f.category === 'font')
992
- .map(f => f.name)
993
- .join(', ') || t('bundle.catFontsDesc'),
994
- },
995
- {
996
- iconType: 'typescript',
997
- title: t('bundle.catTsTitle'),
998
- count: summary.ts.count,
999
- size: t('bundle.fileSizeKb', { size: summary.ts.kb }),
1000
- pct: summary.ts.pct,
1001
- color: AppColors_1.AppColors.sky500,
1002
- desc: t('bundle.catTsDesc'),
1003
- },
1004
- {
1005
- iconType: 'json',
1006
- title: t('bundle.catJsonTitle'),
1007
- count: summary.json.count,
1008
- size: t('bundle.fileSizeKb', { size: summary.json.kb }),
1009
- pct: summary.json.pct,
1010
- color: AppColors_1.AppColors.emerald500,
1011
- desc: t('bundle.catJsonDesc'),
1012
- },
1013
- ].map((cat, cIdx) => (<react_native_1.View key={cIdx} style={bundleStyles.catRowCard}>
943
+ {summary.categories.map((cat, cIdx) => (<react_native_1.Pressable key={cIdx} style={({ pressed }) => [
944
+ bundleStyles.catRowCard,
945
+ pressed && { opacity: 0.85, transform: [{ scale: 0.99 }] },
946
+ ]} onPress={() => setActiveSubTab(cat.targetTab)}>
1014
947
  <react_native_1.View style={bundleStyles.catRowTop}>
1015
- <BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
1016
- <react_native_1.View style={{ flex: 1 }}>
1017
- <react_native_1.Text style={bundleStyles.catRowTitle}>{cat.title}</react_native_1.Text>
1018
- <react_native_1.Text style={bundleStyles.catRowDesc}>
1019
- {t('bundle.catRowSub', { count: cat.count, desc: cat.desc })}
1020
- </react_native_1.Text>
948
+ <react_native_1.View style={[
949
+ bundleStyles.catIconBox,
950
+ { backgroundColor: `${cat.color}15`, borderColor: `${cat.color}30` },
951
+ ]}>
952
+ <BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
1021
953
  </react_native_1.View>
1022
- <react_native_1.View style={{ alignItems: 'flex-end' }}>
1023
- <react_native_1.Text style={bundleStyles.catRowSize}>{cat.size}</react_native_1.Text>
1024
- <react_native_1.Text style={bundleStyles.catRowPct}>{cat.pct}%</react_native_1.Text>
954
+
955
+ <react_native_1.View style={bundleStyles.catRowTitleWrap}>
956
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
957
+ <react_native_1.Text style={bundleStyles.catRowTitle} numberOfLines={1}>
958
+ {cat.title}
959
+ </react_native_1.Text>
960
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
961
+ <react_native_1.Text style={bundleStyles.catRowSize}>
962
+ {cat.sizeKb >= 1024 ? `${cat.sizeMb} MB` : `${cat.sizeKb} KB`}
963
+ </react_native_1.Text>
964
+ <react_native_1.View style={[
965
+ bundleStyles.catPctPill,
966
+ { backgroundColor: `${cat.color}18` },
967
+ ]}>
968
+ <react_native_1.Text style={[bundleStyles.catPctText, { color: cat.color }]}>
969
+ {cat.pct}%
970
+ </react_native_1.Text>
971
+ </react_native_1.View>
972
+ </react_native_1.View>
973
+ </react_native_1.View>
974
+
975
+ <react_native_1.Text style={bundleStyles.catRowDesc} numberOfLines={2}>
976
+ {cat.count} items • {cat.desc}
977
+ </react_native_1.Text>
1025
978
  </react_native_1.View>
1026
979
  </react_native_1.View>
980
+
981
+ {/* Modern Rounded Progress Bar */}
1027
982
  <react_native_1.View style={bundleStyles.catProgressTrack}>
1028
983
  <react_native_1.View style={[
1029
984
  bundleStyles.catProgressBar,
1030
985
  { width: `${cat.pct}%`, backgroundColor: cat.color },
1031
986
  ]}/>
1032
987
  </react_native_1.View>
1033
- </react_native_1.View>))}
988
+ </react_native_1.Pressable>))}
1034
989
 
1035
- {/* Total Summation Card */}
990
+ {/* Executive Total Summation Card */}
1036
991
  <react_native_1.View style={bundleStyles.totalSummaryCard}>
1037
992
  <react_native_1.View style={bundleStyles.totalSummaryRow}>
1038
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1039
- <NetworkIcons_1.PackageIcon color={AppColors_1.AppColors.purple} size={16}/>
1040
- <react_native_1.Text style={bundleStyles.totalSummaryLabel}>
1041
- {t('bundle.totalBundleAssets')}
993
+ <react_native_1.View style={bundleStyles.totalSummaryLabelWrap}>
994
+ <react_native_1.View style={bundleStyles.totalIconBadge}>
995
+ <NetworkIcons_1.PackageIcon color={AppColors_1.AppColors.brandPurple} size={16}/>
996
+ </react_native_1.View>
997
+ <react_native_1.View>
998
+ <react_native_1.Text style={bundleStyles.totalSummaryLabel}>
999
+ TOTAL BUNDLE ASSETS
1000
+ </react_native_1.Text>
1001
+ <react_native_1.Text style={bundleStyles.totalSummarySub}>
1002
+ {summary.totalCount} Tracked Modules & Media Assets
1003
+ </react_native_1.Text>
1004
+ </react_native_1.View>
1005
+ </react_native_1.View>
1006
+ <react_native_1.View style={{ alignItems: 'flex-end' }}>
1007
+ <react_native_1.Text style={bundleStyles.totalSummaryValue}>
1008
+ {summary.totalMb} MB
1009
+ </react_native_1.Text>
1010
+ <react_native_1.Text style={bundleStyles.totalSummaryKb}>
1011
+ {summary.totalKb.toLocaleString()} KB
1042
1012
  </react_native_1.Text>
1043
1013
  </react_native_1.View>
1044
- <react_native_1.Text style={bundleStyles.totalSummaryValue}>
1045
- {t('bundle.totalBundleValue', { kb: summary.totalKb, mb: summary.totalMb })}
1046
- </react_native_1.Text>
1047
1014
  </react_native_1.View>
1048
- <react_native_1.Text style={bundleStyles.totalSummaryFormula}>
1049
- {t('bundle.totalFormula', {
1050
- images: summary.images.kb,
1051
- js: summary.js.kb,
1052
- fonts: summary.fonts.kb,
1053
- ts: summary.ts.kb,
1054
- json: summary.json.kb,
1055
- total: summary.totalKb,
1056
- count: summary.totalCount,
1057
- })}
1058
- </react_native_1.Text>
1015
+
1016
+ {/* 4-Pill Chip Breakdown */}
1017
+ <react_native_1.View style={bundleStyles.totalFormulaGrid}>
1018
+ <react_native_1.View style={bundleStyles.totalChip}>
1019
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.indigo500 }]}/>
1020
+ <react_native_1.Text style={bundleStyles.totalChipText}>NPM: {(summary.nodeModulesKb / 1024).toFixed(1)}M ({summary.nodeModulesPct}%)</react_native_1.Text>
1021
+ </react_native_1.View>
1022
+ <react_native_1.View style={bundleStyles.totalChip}>
1023
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.amber500 }]}/>
1024
+ <react_native_1.Text style={bundleStyles.totalChipText}>Metro: {(summary.metroKb / 1024).toFixed(1)}M ({summary.metroPct}%)</react_native_1.Text>
1025
+ </react_native_1.View>
1026
+ <react_native_1.View style={bundleStyles.totalChip}>
1027
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.sky500 }]}/>
1028
+ <react_native_1.Text style={bundleStyles.totalChipText}>Source: {(summary.appSourceKb / 1024).toFixed(1)}M ({summary.appSourcePct}%)</react_native_1.Text>
1029
+ </react_native_1.View>
1030
+ <react_native_1.View style={bundleStyles.totalChip}>
1031
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.pink500 }]}/>
1032
+ <react_native_1.Text style={bundleStyles.totalChipText}>Media: {summary.assetsMediaKb}K ({summary.assetsMediaPct}%)</react_native_1.Text>
1033
+ </react_native_1.View>
1034
+ </react_native_1.View>
1059
1035
  </react_native_1.View>
1060
1036
  </react_native_1.View>
1061
1037
  </react_native_1.ScrollView>)}
1062
1038
 
1039
+ {/* ══════════════════════════════════════════════════════════════════════ */}
1040
+ {/* ── 2. TAB: PRODUCTION BUILDS & PLATFORMS ───────────────────────────── */}
1041
+ {/* ══════════════════════════════════════════════════════════════════════ */}
1042
+ {activeSubTab === 'production' && !isAnalyzing && (<react_native_1.ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1043
+
1044
+ {/* Platform Segmented Switcher (Horizontally Scrollable) */}
1045
+ <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={bundleStyles.prodPlatformScroll}>
1046
+ {[
1047
+ { key: 'ios', label: 'iOS App (.ipa)', badge: `${analysis?.production.ios.totalInstallMb || 59.2} MB` },
1048
+ { key: 'androidAab', label: 'Android AAB (.aab)', badge: `${analysis?.production.androidAab.totalInstallMb || 24.3} MB` },
1049
+ { key: 'androidApk', label: 'Universal APK (.apk)', badge: `${analysis?.production.androidApk.totalInstallMb || 64.0} MB` },
1050
+ ].map(tab => {
1051
+ const isSelected = prodPlatform === tab.key;
1052
+ return (<TouchableScale_1.default key={tab.key} onPress={() => setProdPlatform(tab.key)} style={[
1053
+ bundleStyles.prodPlatformBtn,
1054
+ isSelected && bundleStyles.prodPlatformBtnActive,
1055
+ ]}>
1056
+ <react_native_1.Text style={[
1057
+ bundleStyles.prodPlatformBtnText,
1058
+ isSelected && bundleStyles.prodPlatformBtnTextActive,
1059
+ ]}>
1060
+ {tab.label}
1061
+ </react_native_1.Text>
1062
+ <react_native_1.View style={[
1063
+ bundleStyles.prodPlatformBadge,
1064
+ isSelected && bundleStyles.prodPlatformBadgeActive,
1065
+ ]}>
1066
+ <react_native_1.Text style={[
1067
+ bundleStyles.prodPlatformBadgeText,
1068
+ isSelected && bundleStyles.prodPlatformBadgeTextActive,
1069
+ ]}>
1070
+ {tab.badge}
1071
+ </react_native_1.Text>
1072
+ </react_native_1.View>
1073
+ </TouchableScale_1.default>);
1074
+ })}
1075
+ </react_native_1.ScrollView>
1076
+
1077
+ {/* Production Platform Metrics Hero */}
1078
+ {analysis && (<react_native_1.View style={bundleStyles.heroCard}>
1079
+ <react_native_1.View style={bundleStyles.heroTopRow}>
1080
+ <react_native_1.View style={bundleStyles.heroIconWrap}>
1081
+ <NetworkIcons_1.PackageIcon color={AppColors_1.AppColors.brandPurple} size={24}/>
1082
+ </react_native_1.View>
1083
+ <react_native_1.View style={{ flex: 1 }}>
1084
+ <react_native_1.Text style={bundleStyles.heroTitle}>
1085
+ {prodPlatform === 'ios'
1086
+ ? 'iOS Production Binary Footprint'
1087
+ : prodPlatform === 'androidAab'
1088
+ ? 'Google Play App Bundle (.aab)'
1089
+ : 'Universal Standalone APK (.apk)'}
1090
+ </react_native_1.Text>
1091
+ <react_native_1.Text style={bundleStyles.heroSubtitle}>
1092
+ {prodPlatform === 'ios'
1093
+ ? 'App Store install & over-the-air cellular download estimates'
1094
+ : prodPlatform === 'androidAab'
1095
+ ? 'Optimized per-device dynamic delivery split APK architecture'
1096
+ : 'Multi-ABI universal install archive for sideloading & direct distribution'}
1097
+ </react_native_1.Text>
1098
+ </react_native_1.View>
1099
+ <CopyButton_1.default value={() => analysis.production[prodPlatform]} label="Production Binary JSON"/>
1100
+ </react_native_1.View>
1101
+
1102
+ {/* 4-Stat Balanced 2x2 Metric Grid */}
1103
+ <react_native_1.View style={bundleStyles.metricsGrid}>
1104
+ <react_native_1.View style={bundleStyles.metricBox}>
1105
+ <react_native_1.Text style={bundleStyles.metricLabel}>INSTALL SIZE</react_native_1.Text>
1106
+ <react_native_1.Text style={[bundleStyles.metricValue, { color: AppColors_1.AppColors.purple }]}>
1107
+ {analysis.production[prodPlatform].totalInstallMb} MB
1108
+ </react_native_1.Text>
1109
+ <react_native_1.Text style={bundleStyles.metricHint}>On-device uncompressed footprint</react_native_1.Text>
1110
+ </react_native_1.View>
1111
+
1112
+ <react_native_1.View style={bundleStyles.metricBox}>
1113
+ <react_native_1.Text style={bundleStyles.metricLabel}>DOWNLOAD SIZE</react_native_1.Text>
1114
+ <react_native_1.Text style={[bundleStyles.metricValue, { color: AppColors_1.AppColors.emerald600 }]}>
1115
+ ~{analysis.production[prodPlatform].totalDownloadMb} MB
1116
+ </react_native_1.Text>
1117
+ <react_native_1.Text style={bundleStyles.metricHint}>Store network transfer payload</react_native_1.Text>
1118
+ </react_native_1.View>
1119
+
1120
+ <react_native_1.View style={bundleStyles.metricBox}>
1121
+ <react_native_1.Text style={bundleStyles.metricLabel}>COMPRESSION</react_native_1.Text>
1122
+ <react_native_1.Text style={[bundleStyles.metricValue, { color: AppColors_1.AppColors.sky500 }]}>
1123
+ {analysis.production[prodPlatform].compressionRatioPct}%
1124
+ </react_native_1.Text>
1125
+ <react_native_1.Text style={bundleStyles.metricHint}>Bytecode & asset ratio</react_native_1.Text>
1126
+ </react_native_1.View>
1127
+
1128
+ <react_native_1.View style={bundleStyles.metricBox}>
1129
+ <react_native_1.Text style={bundleStyles.metricLabel}>FORMAT ARCHITECTURE</react_native_1.Text>
1130
+ <react_native_1.Text style={[bundleStyles.metricValue, { color: AppColors_1.AppColors.amber700 }]}>
1131
+ {prodPlatform === 'ios'
1132
+ ? 'Mach-O 64-bit'
1133
+ : prodPlatform === 'androidAab'
1134
+ ? 'Split APKs'
1135
+ : 'FAT Multi-ABI'}
1136
+ </react_native_1.Text>
1137
+ <react_native_1.Text style={bundleStyles.metricHint}>
1138
+ {prodPlatform === 'ios'
1139
+ ? 'Apple ARM64 runtime'
1140
+ : prodPlatform === 'androidAab'
1141
+ ? 'Dynamic Google Play delivery'
1142
+ : 'Sideload universal package'}
1143
+ </react_native_1.Text>
1144
+ </react_native_1.View>
1145
+ </react_native_1.View>
1146
+ </react_native_1.View>)}
1147
+
1148
+ {/* Component Breakdown Cards */}
1149
+ {analysis && (<react_native_1.View style={bundleStyles.sectionCard}>
1150
+ <react_native_1.View style={bundleStyles.sectionHeaderRow}>
1151
+ <react_native_1.View>
1152
+ <react_native_1.Text style={bundleStyles.sectionTitle}>Binary Component Architecture</react_native_1.Text>
1153
+ <react_native_1.Text style={bundleStyles.sectionSub}>
1154
+ Compiled native libraries, runtime bytecodes, assets, and signature blocks
1155
+ </react_native_1.Text>
1156
+ </react_native_1.View>
1157
+ <CopyButton_1.default value={() => analysis.production[prodPlatform].components} label="Components JSON"/>
1158
+ </react_native_1.View>
1159
+
1160
+ {analysis.production[prodPlatform].components.map((comp, idx) => (<react_native_1.View key={idx} style={bundleStyles.catRowCard}>
1161
+ <react_native_1.View style={bundleStyles.catRowTop}>
1162
+ <react_native_1.View style={[
1163
+ bundleStyles.catIconBox,
1164
+ { backgroundColor: `${comp.color}15`, borderColor: `${comp.color}30` },
1165
+ ]}>
1166
+ <BundleCategoryIcon type={comp.category === 'native'
1167
+ ? 'source'
1168
+ : comp.category === 'frameworks'
1169
+ ? 'deps'
1170
+ : comp.category === 'assets'
1171
+ ? 'media'
1172
+ : comp.category === 'js'
1173
+ ? 'javascript'
1174
+ : 'overhead'} size={18} color={comp.color}/>
1175
+ </react_native_1.View>
1176
+
1177
+ <react_native_1.View style={bundleStyles.catRowTitleWrap}>
1178
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
1179
+ <react_native_1.Text style={bundleStyles.catRowTitle} numberOfLines={1}>
1180
+ {comp.name}
1181
+ </react_native_1.Text>
1182
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1183
+ <react_native_1.Text style={bundleStyles.catRowSize}>{comp.sizeMb} MB</react_native_1.Text>
1184
+ <react_native_1.View style={[
1185
+ bundleStyles.catPctPill,
1186
+ { backgroundColor: `${comp.color}18` },
1187
+ ]}>
1188
+ <react_native_1.Text style={[bundleStyles.catPctText, { color: comp.color }]}>
1189
+ {comp.pct}%
1190
+ </react_native_1.Text>
1191
+ </react_native_1.View>
1192
+ </react_native_1.View>
1193
+ </react_native_1.View>
1194
+
1195
+ <react_native_1.Text style={bundleStyles.catRowDesc} numberOfLines={2}>
1196
+ {comp.description}
1197
+ </react_native_1.Text>
1198
+ </react_native_1.View>
1199
+ </react_native_1.View>
1200
+
1201
+ <react_native_1.View style={bundleStyles.catProgressTrack}>
1202
+ <react_native_1.View style={[
1203
+ bundleStyles.catProgressBar,
1204
+ { width: `${comp.pct}%`, backgroundColor: comp.color },
1205
+ ]}/>
1206
+ </react_native_1.View>
1207
+
1208
+ {comp.advice ? (<react_native_1.View style={[bundleStyles.adviceBadge, bundleStyles.adviceOptimal, { marginTop: 6 }]}>
1209
+ <NetworkIcons_1.LightbulbIcon color={AppColors_1.AppColors.emerald700} size={13}/>
1210
+ <react_native_1.Text style={[bundleStyles.adviceText, { color: AppColors_1.AppColors.emerald700, flex: 1, marginLeft: 4 }]}>
1211
+ {comp.advice}
1212
+ </react_native_1.Text>
1213
+ </react_native_1.View>) : null}
1214
+ </react_native_1.View>))}
1215
+
1216
+ {/* Total Platform Binary Summary with JS Source Bundle Comparison */}
1217
+ <react_native_1.View style={bundleStyles.totalSummaryCard}>
1218
+ <react_native_1.View style={bundleStyles.totalSummaryRow}>
1219
+ <react_native_1.View style={bundleStyles.totalSummaryLabelWrap}>
1220
+ <react_native_1.View style={bundleStyles.totalIconBadge}>
1221
+ <NetworkIcons_1.PackageIcon color={AppColors_1.AppColors.brandPurple} size={16}/>
1222
+ </react_native_1.View>
1223
+ <react_native_1.View>
1224
+ <react_native_1.Text style={bundleStyles.totalSummaryLabel}>
1225
+ {prodPlatform === 'ios'
1226
+ ? 'TOTAL IOS APP BINARY'
1227
+ : prodPlatform === 'androidAab'
1228
+ ? 'TOTAL ANDROID AAB BUNDLE'
1229
+ : 'TOTAL UNIVERSAL APK'}
1230
+ </react_native_1.Text>
1231
+ <react_native_1.Text style={bundleStyles.totalSummarySub}>
1232
+ {prodPlatform === 'ios'
1233
+ ? 'App Store install & cellular download estimates'
1234
+ : prodPlatform === 'androidAab'
1235
+ ? 'Google Play dynamic delivery split APK'
1236
+ : 'Universal standalone sideload package'}
1237
+ </react_native_1.Text>
1238
+ </react_native_1.View>
1239
+ </react_native_1.View>
1240
+ </react_native_1.View>
1241
+
1242
+ {/* Platform Metric Grid Chips */}
1243
+ <react_native_1.View style={bundleStyles.totalFormulaGrid}>
1244
+ <react_native_1.View style={bundleStyles.totalChip}>
1245
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.purple }]}/>
1246
+ <react_native_1.Text style={bundleStyles.totalChipText}>
1247
+ Install: {analysis.production[prodPlatform].totalInstallMb} MB
1248
+ </react_native_1.Text>
1249
+ </react_native_1.View>
1250
+ <react_native_1.View style={bundleStyles.totalChip}>
1251
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.sky500 }]}/>
1252
+ <react_native_1.Text style={bundleStyles.totalChipText}>
1253
+ Download: ~{analysis.production[prodPlatform].totalDownloadMb} MB
1254
+ </react_native_1.Text>
1255
+ </react_native_1.View>
1256
+ <react_native_1.View style={bundleStyles.totalChip}>
1257
+ <react_native_1.View style={[bundleStyles.totalDot, { backgroundColor: AppColors_1.AppColors.emerald500 }]}/>
1258
+ <react_native_1.Text style={bundleStyles.totalChipText}>
1259
+ Compression: {analysis.production[prodPlatform].compressionRatioPct}%
1260
+ </react_native_1.Text>
1261
+ </react_native_1.View>
1262
+ </react_native_1.View>
1263
+ </react_native_1.View>
1264
+ </react_native_1.View>)}
1265
+ </react_native_1.ScrollView>)}
1266
+
1063
1267
  {/* ══════════════════════════════════════════════════════════════════════ */}
1064
1268
  {/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
1065
1269
  {/* ══════════════════════════════════════════════════════════════════════ */}
@@ -1151,8 +1355,8 @@ const BundleTab = react_1.default.memo(() => {
1151
1355
  </react_native_1.View>
1152
1356
 
1153
1357
  {/* ── MODE 1: DIRECTORY TREE VIEW ── */}
1154
- {filesViewMode === 'tree' ? (<react_native_1.View style={bundleStyles.treeRootContainer}>
1155
- {fileTreeNodes.map((node, nIdx) => (<BundleTreeNodeView key={node.id} node={node} level={0} search={search} totalBundleKb={summary.totalKb} collapsedFolders={collapsedFolders} toggleFolder={toggleFolder} isLastChild={nIdx === fileTreeNodes.length - 1}/>))}
1358
+ {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}/>))}
1156
1360
  </react_native_1.View>) : (
1157
1361
  /* ── MODE 2: FLAT LIST VIEW ── */
1158
1362
  filteredFiles.map((file, index) => {
@@ -1558,16 +1762,16 @@ const BundleTab = react_1.default.memo(() => {
1558
1762
  badgeColor: AppColors_1.AppColors.indigo500,
1559
1763
  },
1560
1764
  ].map((tip, tIdx) => (<react_native_1.View key={tIdx} style={bundleStyles.tipItem}>
1561
- <react_native_1.View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
1562
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }}>
1765
+ <react_native_1.View style={bundleStyles.tipItemHeader}>
1766
+ <react_native_1.View style={bundleStyles.tipTitleWrap}>
1563
1767
  <react_native_1.View style={bundleStyles.sNoBadge}>
1564
1768
  <react_native_1.Text style={bundleStyles.sNoText}>#{tIdx + 1}</react_native_1.Text>
1565
1769
  </react_native_1.View>
1566
1770
  <BundleCategoryIcon type={tip.iconType} size={14} color={tip.iconColor}/>
1567
1771
  <react_native_1.Text style={bundleStyles.tipTitle}>{tip.title}</react_native_1.Text>
1568
1772
  </react_native_1.View>
1569
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1570
- <react_native_1.View style={[bundleStyles.impactBadge, { backgroundColor: `${tip.badgeColor}1F`, borderColor: `${tip.badgeColor}4D` }]}>
1773
+ <react_native_1.View style={bundleStyles.tipActionWrap}>
1774
+ <react_native_1.View style={[bundleStyles.impactBadge, { backgroundColor: `${tip.badgeColor}18`, borderColor: `${tip.badgeColor}40` }]}>
1571
1775
  <react_native_1.Text style={[bundleStyles.impactBadgeText, { color: tip.badgeColor }]}>
1572
1776
  {tip.badge}
1573
1777
  </react_native_1.Text>
@@ -1587,6 +1791,19 @@ const bundleStyles = react_native_1.StyleSheet.create({
1587
1791
  paddingVertical: 8,
1588
1792
  borderBottomWidth: 1,
1589
1793
  borderBottomColor: AppColors_1.AppColors.dividerColor,
1794
+ flexDirection: 'row',
1795
+ alignItems: 'center',
1796
+ paddingRight: 10,
1797
+ },
1798
+ reloadButton: {
1799
+ padding: 8,
1800
+ borderRadius: 20,
1801
+ backgroundColor: AppColors_1.AppColors.grayBackground,
1802
+ borderWidth: 1,
1803
+ borderColor: AppColors_1.AppColors.dividerColor,
1804
+ alignItems: 'center',
1805
+ justifyContent: 'center',
1806
+ marginLeft: 4,
1590
1807
  },
1591
1808
  analyzingContainer: {
1592
1809
  flex: 1,
@@ -1659,7 +1876,7 @@ const bundleStyles = react_native_1.StyleSheet.create({
1659
1876
  },
1660
1877
  heroTopRow: {
1661
1878
  flexDirection: 'row',
1662
- alignItems: 'center',
1879
+ alignItems: 'flex-start',
1663
1880
  gap: 12,
1664
1881
  marginBottom: 16,
1665
1882
  },
@@ -1721,6 +1938,57 @@ const bundleStyles = react_native_1.StyleSheet.create({
1721
1938
  height: 8,
1722
1939
  borderRadius: 4,
1723
1940
  },
1941
+ prodPlatformScroll: {
1942
+ flexDirection: 'row',
1943
+ alignItems: 'center',
1944
+ gap: 8,
1945
+ paddingBottom: 4,
1946
+ },
1947
+ prodPlatformBtn: {
1948
+ flexDirection: 'row',
1949
+ alignItems: 'center',
1950
+ paddingVertical: 7,
1951
+ paddingHorizontal: 12,
1952
+ borderRadius: 20,
1953
+ backgroundColor: AppColors_1.AppColors.white,
1954
+ borderWidth: 1,
1955
+ borderColor: AppColors_1.AppColors.dividerColor,
1956
+ gap: 6,
1957
+ },
1958
+ prodPlatformBtnActive: {
1959
+ backgroundColor: AppColors_1.AppColors.brandPurple,
1960
+ borderColor: AppColors_1.AppColors.brandPurple,
1961
+ shadowColor: AppColors_1.AppColors.brandPurple,
1962
+ shadowOffset: { width: 0, height: 2 },
1963
+ shadowOpacity: 0.18,
1964
+ shadowRadius: 3,
1965
+ elevation: 2,
1966
+ },
1967
+ prodPlatformBtnText: {
1968
+ fontFamily: AppFonts_1.AppFonts.interBold,
1969
+ fontSize: 11.5,
1970
+ color: AppColors_1.AppColors.grayTextStrong,
1971
+ },
1972
+ prodPlatformBtnTextActive: {
1973
+ color: AppColors_1.AppColors.white,
1974
+ },
1975
+ prodPlatformBadge: {
1976
+ paddingHorizontal: 6,
1977
+ paddingVertical: 2,
1978
+ borderRadius: 10,
1979
+ backgroundColor: `${AppColors_1.AppColors.purple}14`,
1980
+ },
1981
+ prodPlatformBadgeActive: {
1982
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
1983
+ },
1984
+ prodPlatformBadgeText: {
1985
+ fontFamily: AppFonts_1.AppFonts.interBold,
1986
+ fontSize: 9.5,
1987
+ color: AppColors_1.AppColors.purple,
1988
+ },
1989
+ prodPlatformBadgeTextActive: {
1990
+ color: AppColors_1.AppColors.white,
1991
+ },
1724
1992
  sectionCard: {
1725
1993
  backgroundColor: AppColors_1.AppColors.white,
1726
1994
  borderRadius: 14,
@@ -1755,18 +2023,21 @@ const bundleStyles = react_native_1.StyleSheet.create({
1755
2023
  legendGrid: {
1756
2024
  flexDirection: 'row',
1757
2025
  flexWrap: 'wrap',
1758
- gap: 10,
2026
+ justifyContent: 'space-between',
2027
+ rowGap: 8,
2028
+ columnGap: 10,
1759
2029
  },
1760
2030
  legendItem: {
1761
2031
  flexDirection: 'row',
1762
2032
  alignItems: 'center',
1763
- gap: 5,
1764
- minWidth: '45%',
2033
+ gap: 6,
2034
+ width: '48%',
1765
2035
  },
1766
2036
  legendText: {
1767
2037
  fontFamily: AppFonts_1.AppFonts.interMedium,
1768
2038
  fontSize: 11,
1769
2039
  color: AppColors_1.AppColors.grayTextStrong,
2040
+ flex: 1,
1770
2041
  },
1771
2042
  legendVal: {
1772
2043
  fontFamily: AppFonts_1.AppFonts.interBold,
@@ -1774,47 +2045,79 @@ const bundleStyles = react_native_1.StyleSheet.create({
1774
2045
  },
1775
2046
  catRowCard: {
1776
2047
  backgroundColor: AppColors_1.AppColors.grayBackground,
1777
- borderRadius: 10,
1778
- padding: 10,
2048
+ borderRadius: 12,
2049
+ padding: 12,
1779
2050
  marginBottom: 8,
1780
2051
  borderWidth: 1,
1781
2052
  borderColor: AppColors_1.AppColors.dividerColor,
2053
+ gap: 8,
1782
2054
  },
1783
2055
  catRowTop: {
1784
2056
  flexDirection: 'row',
2057
+ alignItems: 'flex-start',
2058
+ gap: 10,
2059
+ },
2060
+ catIconBox: {
2061
+ width: 36,
2062
+ height: 36,
2063
+ borderRadius: 10,
2064
+ borderWidth: 1,
1785
2065
  alignItems: 'center',
1786
- gap: 8,
1787
- marginBottom: 8,
2066
+ justifyContent: 'center',
2067
+ flexShrink: 0,
2068
+ },
2069
+ catRowTitleWrap: {
2070
+ flex: 1,
2071
+ minWidth: 0,
2072
+ gap: 2,
1788
2073
  },
1789
2074
  catRowTitle: {
1790
2075
  fontFamily: AppFonts_1.AppFonts.interBold,
1791
- fontSize: 12,
2076
+ fontSize: 13,
1792
2077
  color: AppColors_1.AppColors.primaryBlack,
2078
+ flex: 1,
1793
2079
  },
1794
2080
  catRowDesc: {
1795
2081
  fontFamily: AppFonts_1.AppFonts.interRegular,
1796
- fontSize: 10,
2082
+ fontSize: 10.5,
1797
2083
  color: AppColors_1.AppColors.grayTextWeak,
2084
+ lineHeight: 14.5,
1798
2085
  },
1799
2086
  catRowSize: {
1800
2087
  fontFamily: AppFonts_1.AppFonts.interBold,
1801
- fontSize: 12,
2088
+ fontSize: 12.5,
1802
2089
  color: AppColors_1.AppColors.primaryBlack,
1803
2090
  },
2091
+ catRowRight: {
2092
+ alignItems: 'flex-end',
2093
+ justifyContent: 'center',
2094
+ flexShrink: 0,
2095
+ minWidth: 64,
2096
+ },
1804
2097
  catRowPct: {
1805
2098
  fontFamily: AppFonts_1.AppFonts.interMedium,
1806
2099
  fontSize: 10,
1807
2100
  color: AppColors_1.AppColors.grayTextWeak,
1808
2101
  },
2102
+ catPctPill: {
2103
+ paddingHorizontal: 5.5,
2104
+ paddingVertical: 1.5,
2105
+ borderRadius: 5,
2106
+ },
2107
+ catPctText: {
2108
+ fontFamily: AppFonts_1.AppFonts.interBold,
2109
+ fontSize: 9.5,
2110
+ },
1809
2111
  catProgressTrack: {
1810
- height: 4,
1811
- borderRadius: 2,
2112
+ height: 5,
2113
+ borderRadius: 2.5,
1812
2114
  backgroundColor: `${AppColors_1.AppColors.slate200}`,
1813
2115
  overflow: 'hidden',
2116
+ marginTop: 2,
1814
2117
  },
1815
2118
  catProgressBar: {
1816
2119
  height: '100%',
1817
- borderRadius: 2,
2120
+ borderRadius: 2.5,
1818
2121
  },
1819
2122
  filterContainer: {
1820
2123
  backgroundColor: AppColors_1.AppColors.white,
@@ -1918,32 +2221,44 @@ const bundleStyles = react_native_1.StyleSheet.create({
1918
2221
  fontSize: 11.5,
1919
2222
  color: AppColors_1.AppColors.purple,
1920
2223
  },
1921
- treeRootContainer: {
1922
- gap: 6,
1923
- },
1924
- treeFolderContainer: {
2224
+ treeRootCard: {
1925
2225
  backgroundColor: AppColors_1.AppColors.white,
1926
- borderRadius: 10,
2226
+ borderRadius: 12,
1927
2227
  borderWidth: 1,
1928
2228
  borderColor: AppColors_1.AppColors.dividerColor,
1929
2229
  overflow: 'hidden',
1930
- marginBottom: 6,
2230
+ marginBottom: 16,
2231
+ },
2232
+ treeFolderBlock: {
2233
+ borderBottomWidth: react_native_1.StyleSheet.hairlineWidth,
2234
+ borderBottomColor: AppColors_1.AppColors.dividerColor,
1931
2235
  },
1932
- treeFolderHeader: {
2236
+ treeFolderRow: {
1933
2237
  flexDirection: 'row',
1934
2238
  alignItems: 'center',
1935
2239
  justifyContent: 'space-between',
1936
- paddingVertical: 10,
2240
+ paddingVertical: 8,
1937
2241
  paddingRight: 10,
1938
- backgroundColor: `${AppColors_1.AppColors.purple}08`,
1939
- borderBottomWidth: 1,
1940
- borderBottomColor: `${AppColors_1.AppColors.purple}14`,
2242
+ backgroundColor: `${AppColors_1.AppColors.purple}06`,
1941
2243
  },
1942
- treeFolderChevron: {
1943
- fontSize: 11,
2244
+ treeRowLeft: {
2245
+ flexDirection: 'row',
2246
+ alignItems: 'center',
2247
+ gap: 6,
2248
+ flex: 1,
2249
+ marginRight: 6,
2250
+ },
2251
+ treeRowRight: {
2252
+ flexDirection: 'row',
2253
+ alignItems: 'center',
2254
+ gap: 6,
2255
+ },
2256
+ treeChevron: {
2257
+ fontSize: 12,
1944
2258
  color: AppColors_1.AppColors.purple,
1945
2259
  fontFamily: AppFonts_1.AppFonts.interBold,
1946
- width: 14,
2260
+ width: 12,
2261
+ textAlign: 'center',
1947
2262
  },
1948
2263
  treeFolderName: {
1949
2264
  fontFamily: AppFonts_1.AppFonts.interBold,
@@ -1951,44 +2266,75 @@ const bundleStyles = react_native_1.StyleSheet.create({
1951
2266
  color: AppColors_1.AppColors.primaryBlack,
1952
2267
  },
1953
2268
  treeCountBadge: {
1954
- paddingHorizontal: 6,
1955
- paddingVertical: 1.5,
1956
- borderRadius: 4,
1957
- backgroundColor: `${AppColors_1.AppColors.purple}1A`,
2269
+ paddingHorizontal: 5,
2270
+ paddingVertical: 1,
2271
+ borderRadius: 8,
2272
+ backgroundColor: `${AppColors_1.AppColors.purple}14`,
1958
2273
  },
1959
2274
  treeCountBadgeText: {
1960
2275
  fontFamily: AppFonts_1.AppFonts.interMedium,
1961
- fontSize: 10,
2276
+ fontSize: 9.5,
1962
2277
  color: AppColors_1.AppColors.purple,
1963
2278
  },
1964
2279
  treeFolderSize: {
1965
- fontFamily: AppFonts_1.AppFonts.interBold,
1966
- fontSize: 11.5,
1967
- color: AppColors_1.AppColors.primaryBlack,
2280
+ fontFamily: AppFonts_1.AppFonts.interMedium,
2281
+ fontSize: 11,
2282
+ color: AppColors_1.AppColors.grayTextStrong,
1968
2283
  },
1969
- treeChildrenWrap: {
1970
- borderLeftColor: `${AppColors_1.AppColors.purple}26`,
1971
- paddingVertical: 6,
1972
- paddingRight: 8,
1973
- gap: 4,
2284
+ treeChildrenContainer: {
2285
+ backgroundColor: AppColors_1.AppColors.white,
1974
2286
  },
1975
- treeFileCard: {
2287
+ treeFileRow: {
2288
+ flexDirection: 'row',
2289
+ alignItems: 'center',
2290
+ justifyContent: 'space-between',
2291
+ paddingVertical: 6,
2292
+ paddingRight: 10,
2293
+ borderBottomWidth: react_native_1.StyleSheet.hairlineWidth,
2294
+ borderBottomColor: `${AppColors_1.AppColors.dividerColor}80`,
1976
2295
  backgroundColor: AppColors_1.AppColors.white,
1977
- borderRadius: 8,
1978
- paddingVertical: 8,
1979
- paddingHorizontal: 8,
1980
- borderWidth: 1,
1981
- borderColor: AppColors_1.AppColors.dividerColor,
1982
- marginBottom: 4,
1983
2296
  },
1984
- treeBranchGlyph: {
1985
- width: 18,
2297
+ treeFileRowUnused: {
2298
+ backgroundColor: `${AppColors_1.AppColors.red500}06`,
2299
+ },
2300
+ treeBranchGuide: {
2301
+ width: 12,
1986
2302
  alignItems: 'center',
1987
2303
  justifyContent: 'center',
1988
2304
  },
1989
- treeBranchGlyphText: {
2305
+ treeBranchGuideText: {
1990
2306
  fontFamily: AppFonts_1.AppFonts.interRegular,
1991
2307
  fontSize: 11,
2308
+ color: AppColors_1.AppColors.dividerColor,
2309
+ },
2310
+ treeFileName: {
2311
+ fontFamily: AppFonts_1.AppFonts.interMedium,
2312
+ fontSize: 12,
2313
+ color: AppColors_1.AppColors.primaryBlack,
2314
+ flexShrink: 1,
2315
+ },
2316
+ treeUnusedPill: {
2317
+ paddingHorizontal: 5,
2318
+ paddingVertical: 1,
2319
+ borderRadius: 4,
2320
+ backgroundColor: `${AppColors_1.AppColors.red500}18`,
2321
+ },
2322
+ treeUnusedPillText: {
2323
+ fontFamily: AppFonts_1.AppFonts.interBold,
2324
+ fontSize: 9,
2325
+ color: AppColors_1.AppColors.red600,
2326
+ },
2327
+ treeFileSizeGroup: {
2328
+ alignItems: 'flex-end',
2329
+ },
2330
+ treeFileSizeText: {
2331
+ fontFamily: AppFonts_1.AppFonts.interMedium,
2332
+ fontSize: 11,
2333
+ color: AppColors_1.AppColors.primaryBlack,
2334
+ },
2335
+ treeFilePctText: {
2336
+ fontFamily: AppFonts_1.AppFonts.interRegular,
2337
+ fontSize: 9,
1992
2338
  color: AppColors_1.AppColors.grayTextWeak,
1993
2339
  },
1994
2340
  fileCard: {
@@ -2320,27 +2666,80 @@ const bundleStyles = react_native_1.StyleSheet.create({
2320
2666
  },
2321
2667
  totalSummaryCard: {
2322
2668
  marginTop: 10,
2323
- backgroundColor: `${AppColors_1.AppColors.purple}0D`,
2324
- borderRadius: 10,
2669
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}0B`,
2670
+ borderRadius: 12,
2325
2671
  padding: 12,
2326
2672
  borderWidth: 1,
2327
- borderColor: `${AppColors_1.AppColors.purple}26`,
2328
- gap: 4,
2673
+ borderColor: `${AppColors_1.AppColors.brandPurple}25`,
2674
+ gap: 8,
2329
2675
  },
2330
2676
  totalSummaryRow: {
2331
2677
  flexDirection: 'row',
2332
2678
  justifyContent: 'space-between',
2333
2679
  alignItems: 'center',
2334
2680
  },
2681
+ totalSummaryLabelWrap: {
2682
+ flexDirection: 'row',
2683
+ alignItems: 'center',
2684
+ gap: 8,
2685
+ },
2686
+ totalIconBadge: {
2687
+ width: 32,
2688
+ height: 32,
2689
+ borderRadius: 8,
2690
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}15`,
2691
+ alignItems: 'center',
2692
+ justifyContent: 'center',
2693
+ },
2335
2694
  totalSummaryLabel: {
2336
2695
  fontFamily: AppFonts_1.AppFonts.interBold,
2337
- fontSize: 11,
2338
- color: AppColors_1.AppColors.purple,
2339
- letterSpacing: 0.5,
2696
+ fontSize: 11.5,
2697
+ color: AppColors_1.AppColors.brandPurple,
2698
+ letterSpacing: 0.6,
2699
+ },
2700
+ totalSummarySub: {
2701
+ fontFamily: AppFonts_1.AppFonts.interRegular,
2702
+ fontSize: 10,
2703
+ color: AppColors_1.AppColors.grayTextWeak,
2704
+ marginTop: 1,
2340
2705
  },
2341
2706
  totalSummaryValue: {
2342
2707
  fontFamily: AppFonts_1.AppFonts.interBold,
2343
- fontSize: 13,
2708
+ fontSize: 15,
2709
+ color: AppColors_1.AppColors.primaryBlack,
2710
+ },
2711
+ totalSummaryKb: {
2712
+ fontFamily: AppFonts_1.AppFonts.interMedium,
2713
+ fontSize: 10,
2714
+ color: AppColors_1.AppColors.grayTextWeak,
2715
+ },
2716
+ totalFormulaGrid: {
2717
+ flexDirection: 'row',
2718
+ flexWrap: 'wrap',
2719
+ gap: 6,
2720
+ paddingTop: 6,
2721
+ borderTopWidth: 1,
2722
+ borderTopColor: `${AppColors_1.AppColors.brandPurple}15`,
2723
+ },
2724
+ totalChip: {
2725
+ flexDirection: 'row',
2726
+ alignItems: 'center',
2727
+ gap: 4,
2728
+ backgroundColor: AppColors_1.AppColors.white,
2729
+ borderRadius: 6,
2730
+ paddingHorizontal: 6,
2731
+ paddingVertical: 3,
2732
+ borderWidth: 1,
2733
+ borderColor: AppColors_1.AppColors.dividerColor,
2734
+ },
2735
+ totalDot: {
2736
+ width: 6,
2737
+ height: 6,
2738
+ borderRadius: 3,
2739
+ },
2740
+ totalChipText: {
2741
+ fontFamily: AppFonts_1.AppFonts.interBold,
2742
+ fontSize: 9.5,
2344
2743
  color: AppColors_1.AppColors.primaryBlack,
2345
2744
  },
2346
2745
  totalSummaryFormula: {
@@ -2375,24 +2774,45 @@ const bundleStyles = react_native_1.StyleSheet.create({
2375
2774
  color: AppColors_1.AppColors.purple,
2376
2775
  },
2377
2776
  tipItem: {
2378
- marginBottom: 12,
2777
+ marginBottom: 10,
2379
2778
  backgroundColor: AppColors_1.AppColors.white,
2380
- padding: 10,
2381
- borderRadius: 8,
2779
+ padding: 12,
2780
+ borderRadius: 10,
2382
2781
  borderWidth: 1,
2383
2782
  borderColor: AppColors_1.AppColors.purple200,
2783
+ gap: 6,
2784
+ },
2785
+ tipItemHeader: {
2786
+ flexDirection: 'row',
2787
+ alignItems: 'flex-start',
2788
+ justifyContent: 'space-between',
2789
+ gap: 8,
2790
+ },
2791
+ tipTitleWrap: {
2792
+ flexDirection: 'row',
2793
+ alignItems: 'center',
2794
+ gap: 6,
2795
+ flex: 1,
2796
+ flexShrink: 1,
2797
+ },
2798
+ tipActionWrap: {
2799
+ flexDirection: 'row',
2800
+ alignItems: 'center',
2801
+ gap: 6,
2802
+ flexShrink: 0,
2384
2803
  },
2385
2804
  tipTitle: {
2386
2805
  fontFamily: AppFonts_1.AppFonts.interBold,
2387
2806
  fontSize: 12,
2388
2807
  color: AppColors_1.AppColors.purple900,
2808
+ flex: 1,
2809
+ flexShrink: 1,
2389
2810
  },
2390
2811
  tipDesc: {
2391
2812
  fontFamily: AppFonts_1.AppFonts.interRegular,
2392
2813
  fontSize: 11,
2393
2814
  color: AppColors_1.AppColors.gray500,
2394
2815
  lineHeight: 16,
2395
- marginTop: 4,
2396
2816
  },
2397
2817
  });
2398
2818
  exports.default = BundleTab;