react-native-inapp-inspector 1.1.23 → 1.1.25

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 (79) hide show
  1. package/dist/commonjs/components/AnalyticsEventCard.js +94 -88
  2. package/dist/commonjs/components/ConsoleLogCard.js +49 -25
  3. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  4. package/dist/commonjs/components/Inspector/AnalyticsFilterModal.js +1002 -0
  5. package/dist/commonjs/components/Inspector/AnalyticsTab.js +637 -10
  6. package/dist/commonjs/components/Inspector/BundleTab.d.ts +1 -1
  7. package/dist/commonjs/components/Inspector/BundleTab.js +859 -402
  8. package/dist/commonjs/components/Inspector/InspectorContext.js +6 -1
  9. package/dist/commonjs/components/Inspector/InspectorHeader.js +13 -5
  10. package/dist/commonjs/components/Inspector/LogDetail.js +223 -111
  11. package/dist/commonjs/components/Inspector/MainScreen.js +1 -1
  12. package/dist/commonjs/components/Inspector/NetworkDetail.js +131 -81
  13. package/dist/commonjs/components/Inspector/PerformanceTab.js +20 -10
  14. package/dist/commonjs/components/NetworkIcons.d.ts +14 -0
  15. package/dist/commonjs/components/NetworkIcons.js +93 -1
  16. package/dist/commonjs/components/SegmentedTabs.js +31 -22
  17. package/dist/commonjs/components/TreeNode.js +13 -12
  18. package/dist/commonjs/constants/version.d.ts +1 -1
  19. package/dist/commonjs/constants/version.js +1 -1
  20. package/dist/commonjs/customHooks/bundleAnalyzer.d.ts +6 -2
  21. package/dist/commonjs/customHooks/bundleAnalyzer.js +552 -114
  22. package/dist/commonjs/customHooks/consoleLogger.js +83 -45
  23. package/dist/commonjs/customHooks/networkLogger.js +10 -1
  24. package/dist/commonjs/customHooks/performanceTracker.d.ts +8 -0
  25. package/dist/commonjs/customHooks/performanceTracker.js +44 -3
  26. package/dist/commonjs/decorators/index.d.ts +49 -0
  27. package/dist/commonjs/decorators/index.js +142 -0
  28. package/dist/commonjs/helpers/gaAnalyticsRegistry.d.ts +18 -0
  29. package/dist/commonjs/helpers/gaAnalyticsRegistry.js +174 -0
  30. package/dist/commonjs/helpers/index.d.ts +7 -4
  31. package/dist/commonjs/helpers/index.js +102 -64
  32. package/dist/commonjs/i18n/locales/en.json +53 -4
  33. package/dist/commonjs/index.d.ts +4 -0
  34. package/dist/commonjs/index.js +216 -8
  35. package/dist/commonjs/styles/index.d.ts +4 -1
  36. package/dist/commonjs/styles/index.js +22 -19
  37. package/dist/commonjs/types/enums.d.ts +115 -9
  38. package/dist/commonjs/types/enums.js +103 -2
  39. package/dist/commonjs/types/interfaces.d.ts +19 -0
  40. package/dist/esm/components/AnalyticsEventCard.js +95 -89
  41. package/dist/esm/components/ConsoleLogCard.js +49 -25
  42. package/dist/esm/components/Inspector/AnalyticsFilterModal.d.ts +7 -0
  43. package/dist/esm/components/Inspector/AnalyticsFilterModal.js +964 -0
  44. package/dist/esm/components/Inspector/AnalyticsTab.js +639 -12
  45. package/dist/esm/components/Inspector/BundleTab.d.ts +1 -1
  46. package/dist/esm/components/Inspector/BundleTab.js +860 -403
  47. package/dist/esm/components/Inspector/InspectorContext.js +1 -0
  48. package/dist/esm/components/Inspector/InspectorHeader.js +14 -6
  49. package/dist/esm/components/Inspector/LogDetail.js +223 -111
  50. package/dist/esm/components/Inspector/MainScreen.js +2 -2
  51. package/dist/esm/components/Inspector/NetworkDetail.js +132 -49
  52. package/dist/esm/components/Inspector/PerformanceTab.js +20 -10
  53. package/dist/esm/components/NetworkIcons.d.ts +14 -0
  54. package/dist/esm/components/NetworkIcons.js +78 -0
  55. package/dist/esm/components/SegmentedTabs.js +31 -22
  56. package/dist/esm/components/TreeNode.js +13 -12
  57. package/dist/esm/constants/version.d.ts +1 -1
  58. package/dist/esm/constants/version.js +1 -1
  59. package/dist/esm/customHooks/bundleAnalyzer.d.ts +6 -2
  60. package/dist/esm/customHooks/bundleAnalyzer.js +549 -114
  61. package/dist/esm/customHooks/consoleLogger.js +83 -45
  62. package/dist/esm/customHooks/networkLogger.js +10 -1
  63. package/dist/esm/customHooks/performanceTracker.d.ts +8 -0
  64. package/dist/esm/customHooks/performanceTracker.js +38 -1
  65. package/dist/esm/decorators/index.d.ts +49 -0
  66. package/dist/esm/decorators/index.js +137 -0
  67. package/dist/esm/helpers/gaAnalyticsRegistry.d.ts +18 -0
  68. package/dist/esm/helpers/gaAnalyticsRegistry.js +169 -0
  69. package/dist/esm/helpers/index.d.ts +7 -4
  70. package/dist/esm/helpers/index.js +100 -63
  71. package/dist/esm/i18n/locales/en.json +53 -4
  72. package/dist/esm/index.d.ts +4 -0
  73. package/dist/esm/index.js +190 -8
  74. package/dist/esm/styles/index.d.ts +4 -1
  75. package/dist/esm/styles/index.js +22 -19
  76. package/dist/esm/types/enums.d.ts +115 -9
  77. package/dist/esm/types/enums.js +102 -3
  78. package/dist/esm/types/interfaces.d.ts +19 -0
  79. package/package.json +1 -1
@@ -6,7 +6,7 @@ import CopyButton from '../CopyButton';
6
6
  import HighlightText from '../HighlightText';
7
7
  import { AppColors } from '../../styles/AppColors';
8
8
  import { AppFonts } from '../../styles/AppFonts';
9
- import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, } from '../NetworkIcons';
9
+ import { PackageIcon, SearchIcon, ClearIcon, CircleAlertIcon, LayersIcon, TimelineIcon, LiveStateIcon, StorageIcon, MetadataIcon, FolderIcon, FolderOpenIcon, TrashIcon, LightbulbIcon, SparkleIcon, ClockIcon, RefreshCcwIcon, ExternalLinkIcon, } from '../NetworkIcons';
10
10
  import Svg, { Path, Rect, Circle, Ellipse, G, Defs, LinearGradient, Stop, } from 'react-native-svg';
11
11
  import { analyzeHostAppBundle, getCachedBundleAnalysis, } from '../../customHooks/bundleAnalyzer';
12
12
  // ─── High-Fidelity Vector Package & NPM Logos ────────────────────────────────
@@ -247,7 +247,10 @@ const MediaPreviewThumbnail = ({ file }) => {
247
247
  function buildBundleFileTree(files) {
248
248
  const rootMap = new Map();
249
249
  for (const file of files) {
250
- const parts = file.path.split('/');
250
+ const cleanPath = file.path.replace(/^\/+/, '');
251
+ const parts = cleanPath.split('/').filter(Boolean);
252
+ if (parts.length === 0)
253
+ continue;
251
254
  let currentLevel = rootMap;
252
255
  for (let i = 0; i < parts.length; i++) {
253
256
  const part = parts[i];
@@ -301,21 +304,21 @@ function buildBundleFileTree(files) {
301
304
  }
302
305
  return convertMapToNodes(rootMap);
303
306
  }
304
- const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolders, toggleFolder, isLastChild, }) => {
305
- const isCollapsed = !!collapsedFolders[node.fullPath];
307
+ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, expandedFolders, toggleFolder, isLastChild, }) => {
308
+ const isExpanded = search ? true : !!expandedFolders[node.fullPath];
309
+ const isCollapsed = !isExpanded;
310
+ const indentLeft = level * 16 + 10;
306
311
  if (node.isFolder) {
307
- return (<View style={bundleStyles.treeFolderContainer}>
312
+ return (<View style={bundleStyles.treeFolderBlock}>
308
313
  <TouchableScale onPress={() => toggleFolder(node.fullPath)} style={[
309
- bundleStyles.treeFolderHeader,
310
- { paddingLeft: Math.max(10, level * 14 + 10) },
314
+ bundleStyles.treeFolderRow,
315
+ { paddingLeft: indentLeft },
311
316
  ]}>
312
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 7, flex: 1 }}>
313
- <View style={bundleStyles.treeFolderChevronWrap}>
314
- <Text style={bundleStyles.treeFolderChevron}>
315
- {isCollapsed ? '▶' : '▼'}
316
- </Text>
317
- </View>
318
- {isCollapsed ? (<FolderIcon color={AppColors.amber500} size={16}/>) : (<FolderOpenIcon color={AppColors.amber500} size={16}/>)}
317
+ <View style={bundleStyles.treeRowLeft}>
318
+ <Text style={bundleStyles.treeChevron}>
319
+ {isCollapsed ? '▸' : '▾'}
320
+ </Text>
321
+ {isCollapsed ? (<FolderIcon color={AppColors.amber500} size={15}/>) : (<FolderOpenIcon color={AppColors.amber500} size={15}/>)}
319
322
  <HighlightText text={node.name + '/'} search={search} style={bundleStyles.treeFolderName} highlightStyle={bundleStyles.searchHighlight}/>
320
323
  <View style={bundleStyles.treeCountBadge}>
321
324
  <Text style={bundleStyles.treeCountBadgeText}>
@@ -324,7 +327,7 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
324
327
  </View>
325
328
  </View>
326
329
 
327
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
330
+ <View style={bundleStyles.treeRowRight}>
328
331
  <Text style={bundleStyles.treeFolderSize}>
329
332
  {node.sizeKb >= 1024
330
333
  ? t('bundle.fileSizeMb', { size: (node.sizeKb / 1024).toFixed(2) })
@@ -338,8 +341,8 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
338
341
  </View>
339
342
  </TouchableScale>
340
343
 
341
- {!isCollapsed && (<View style={[bundleStyles.treeChildrenWrap, { borderLeftWidth: level > 0 ? 1.5 : 0, marginLeft: Math.max(6, level * 14 + 16) }]}>
342
- {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}/>))}
344
+ {!isCollapsed && (<View style={bundleStyles.treeChildrenContainer}>
345
+ {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}/>))}
343
346
  </View>)}
344
347
  </View>);
345
348
  }
@@ -347,64 +350,41 @@ const BundleTreeNodeView = ({ node, level, search, totalBundleKb, collapsedFolde
347
350
  const file = node.fileItem;
348
351
  if (!file)
349
352
  return null;
350
- const pctOfTotal = ((file.sizeKb / totalBundleKb) * 100).toFixed(1);
353
+ const pctOfTotal = totalBundleKb > 0 ? ((file.sizeKb / totalBundleKb) * 100).toFixed(1) : '0';
351
354
  const isUnused = file.isConsumed === false;
352
- return (<View style={[bundleStyles.treeFileCard, isUnused && bundleStyles.treeFileCardUnused]}>
353
- <View style={bundleStyles.fileCardTop}>
354
- <View style={bundleStyles.treeBranchGlyph}>
355
- <Text style={bundleStyles.treeBranchGlyphText}>
356
- {isLastChild ? '└─' : '├─'}
355
+ return (<View style={[
356
+ bundleStyles.treeFileRow,
357
+ { paddingLeft: indentLeft },
358
+ isUnused && bundleStyles.treeFileRowUnused,
359
+ ]}>
360
+ <View style={bundleStyles.treeRowLeft}>
361
+ <View style={bundleStyles.treeBranchGuide}>
362
+ <Text style={bundleStyles.treeBranchGuideText}>
363
+ {isLastChild ? '└' : '├'}
357
364
  </Text>
358
365
  </View>
359
366
 
360
367
  <MediaPreviewThumbnail file={file}/>
361
368
 
362
- <View style={{ flex: 1, paddingHorizontal: 4 }}>
363
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
364
- <HighlightText text={file.name} search={search} style={bundleStyles.fileName} highlightStyle={bundleStyles.searchHighlight}/>
365
- {isUnused ? (<View style={bundleStyles.unusedBadge}>
366
- <Text style={bundleStyles.unusedBadgeText}>{t('bundle.notConsumed')}</Text>
367
- </View>) : (<View style={bundleStyles.consumedBadge}>
368
- <Text style={bundleStyles.consumedBadgeText}>{t('bundle.consumed')}</Text>
369
- </View>)}
370
- </View>
371
- <Text style={bundleStyles.filePath} numberOfLines={1}>
372
- {file.meta}
373
- </Text>
374
- </View>
369
+ <HighlightText text={file.name} search={search} style={bundleStyles.treeFileName} highlightStyle={bundleStyles.searchHighlight}/>
375
370
 
376
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
377
- <View style={bundleStyles.fileSizeBox}>
378
- <Text style={[bundleStyles.fileSizeKb, isUnused && { color: AppColors.amber800Warm }]}>
379
- {file.sizeKb >= 1024
371
+ {isUnused && (<View style={bundleStyles.treeUnusedPill}>
372
+ <Text style={bundleStyles.treeUnusedPillText}>{t('bundle.notConsumed')}</Text>
373
+ </View>)}
374
+ </View>
375
+
376
+ <View style={bundleStyles.treeRowRight}>
377
+ <View style={bundleStyles.treeFileSizeGroup}>
378
+ <Text style={[bundleStyles.treeFileSizeText, isUnused && { color: AppColors.red600 }]}>
379
+ {file.sizeKb >= 1024
380
380
  ? t('bundle.fileSizeMb', { size: (file.sizeKb / 1024).toFixed(2) })
381
381
  : t('bundle.fileSizeKb', { size: file.sizeKb })}
382
- </Text>
383
- <Text style={bundleStyles.filePercent}>{pctOfTotal}%</Text>
384
- </View>
385
- <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
382
+ </Text>
383
+ <Text style={bundleStyles.treeFilePctText}>{pctOfTotal}%</Text>
386
384
  </View>
387
- </View>
388
385
 
389
- {file.advice && (<View style={[
390
- bundleStyles.adviceBadge,
391
- file.status === 'warning' && bundleStyles.adviceWarning,
392
- file.status === 'optimal' && bundleStyles.adviceOptimal,
393
- isUnused && bundleStyles.adviceUnused,
394
- { marginLeft: 26, marginTop: 4 },
395
- ]}>
396
- <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 5 }}>
397
- {isUnused ? (<TrashIcon color={AppColors.red600} size={12}/>) : file.status === 'warning' ? (<LightbulbIcon color={AppColors.amber800Warm} size={12}/>) : (<SparkleIcon color={AppColors.emerald700} size={12}/>)}
398
- <Text style={[
399
- bundleStyles.adviceText,
400
- file.status === 'warning' && { color: AppColors.amber800Warm },
401
- file.status === 'optimal' && { color: AppColors.emerald700 },
402
- isUnused && { color: AppColors.red600 },
403
- ]}>
404
- {file.advice}
405
- </Text>
406
- </View>
407
- </View>)}
386
+ <CopyButton value={() => file} label={t('bundle.fileDetailsJson')}/>
387
+ </View>
408
388
  </View>);
409
389
  };
410
390
  const CATEGORY_TABS = [
@@ -427,11 +407,19 @@ const CATEGORY_COLORS = {
427
407
  const BundleTab = React.memo(() => {
428
408
  const { t } = useTranslation();
429
409
  const [activeSubTab, setActiveSubTab] = useState('overview');
410
+ const [prodPlatform, setProdPlatform] = useState('ios');
430
411
  const [search, setSearch] = useState('');
431
412
  const [activeCategory, setActiveCategory] = useState('ALL');
432
413
  // ─── Live Host App Bundle Analysis (fetched from Metro / runtime) ─────────
433
414
  const [analysis, setAnalysis] = useState(() => getCachedBundleAnalysis());
434
415
  const [isAnalyzing, setIsAnalyzing] = useState(!getCachedBundleAnalysis());
416
+ const refreshAnalysis = useCallback(() => {
417
+ setIsAnalyzing(true);
418
+ analyzeHostAppBundle(true).then(result => {
419
+ setAnalysis(result);
420
+ setIsAnalyzing(false);
421
+ });
422
+ }, []);
435
423
  useEffect(() => {
436
424
  let mounted = true;
437
425
  analyzeHostAppBundle().then(result => {
@@ -449,72 +437,108 @@ const BundleTab = React.memo(() => {
449
437
  // Dynamic files & packages derived from the analyzed host bundle
450
438
  const bundleFiles = useMemo(() => (analysis ? analysis.files : []), [analysis]);
451
439
  const bundlePackages = useMemo(() => (analysis ? analysis.packages : []), [analysis]);
452
- // Group summary calculations
440
+ // Comprehensive bundle summary (full Metro bundle calculation e.g. 6.0 MB)
453
441
  const summary = useMemo(() => {
454
- let imagesKb = 0;
455
- let tsKb = 0;
456
- let jsKb = 0;
457
- let fontsKb = 0;
458
- let jsonKb = 0;
459
- let imagesCount = 0;
460
- let tsCount = 0;
461
- let jsCount = 0;
462
- let fontsCount = 0;
463
- let jsonCount = 0;
464
- bundleFiles.forEach(f => {
465
- if (f.category === 'image') {
466
- imagesKb += f.sizeKb;
467
- imagesCount++;
468
- }
469
- else if (f.category === 'typescript') {
470
- tsKb += f.sizeKb;
471
- tsCount++;
472
- }
473
- else if (f.category === 'javascript') {
474
- jsKb += f.sizeKb;
475
- jsCount++;
476
- }
477
- else if (f.category === 'font') {
478
- fontsKb += f.sizeKb;
479
- fontsCount++;
480
- }
481
- else if (f.category === 'json') {
482
- jsonKb += f.sizeKb;
483
- jsonCount++;
484
- }
485
- });
486
- const totalKb = imagesKb + tsKb + jsKb + fontsKb + jsonKb;
442
+ const totalKb = analysis?.totalDevKb || (analysis?.totalDevBytes ? Math.round(analysis.totalDevBytes / 1024) : 6144);
443
+ const totalMb = analysis?.totalDevMb ? analysis.totalDevMb.toFixed(2) : (totalKb / 1024).toFixed(2);
444
+ const nodeModulesKb = analysis?.splitUp?.nodeModules?.kb || Math.round(totalKb * 0.52);
445
+ const nodeModulesPct = analysis?.splitUp?.nodeModules?.pct || 52.0;
446
+ const metroKb = analysis?.splitUp?.metroDevOverhead?.kb || Math.round(totalKb * 0.21);
447
+ const metroPct = analysis?.splitUp?.metroDevOverhead?.pct || 21.0;
448
+ const appSourceKb = analysis?.splitUp?.appSource?.kb || Math.round(totalKb * 0.15);
449
+ const appSourcePct = analysis?.splitUp?.appSource?.pct || 15.0;
450
+ const assetsMediaKb = analysis?.splitUp?.assetsMedia?.kb || Math.round(totalKb * 0.12);
451
+ const assetsMediaPct = analysis?.splitUp?.assetsMedia?.pct || 12.0;
452
+ const categories = [
453
+ {
454
+ key: 'node_modules',
455
+ iconType: 'javascript',
456
+ title: 'NPM Dependencies',
457
+ targetTab: 'packages',
458
+ count: analysis?.packageCount || bundlePackages.length || 53,
459
+ sizeKb: nodeModulesKb,
460
+ sizeMb: (nodeModulesKb / 1024).toFixed(2),
461
+ pct: nodeModulesPct,
462
+ color: AppColors.indigo500,
463
+ desc: `${analysis?.packageCount || bundlePackages.length || 53} node_modules packages (React Native, React, navigation, axios)`,
464
+ },
465
+ {
466
+ key: 'metro_runtime',
467
+ iconType: 'bolt',
468
+ title: 'Metro Dev Runtime',
469
+ targetTab: 'optimizer',
470
+ count: 1,
471
+ sizeKb: metroKb,
472
+ sizeMb: (metroKb / 1024).toFixed(2),
473
+ pct: metroPct,
474
+ color: AppColors.amber500,
475
+ desc: 'Module loader, polyfills, HMR client, and prelude runtime',
476
+ },
477
+ {
478
+ key: 'app_source',
479
+ iconType: 'typescript',
480
+ title: 'App Source Code (TSX/JS)',
481
+ targetTab: 'files',
482
+ count: bundleFiles.filter(f => f.category === 'typescript' || f.category === 'javascript').length || 7,
483
+ sizeKb: appSourceKb,
484
+ sizeMb: (appSourceKb / 1024).toFixed(2),
485
+ pct: appSourcePct,
486
+ color: AppColors.sky500,
487
+ desc: 'Screens, components, navigation, Redux store, and hooks',
488
+ },
489
+ {
490
+ key: 'assets_media',
491
+ iconType: 'image',
492
+ title: 'Images & Static Media',
493
+ targetTab: 'media',
494
+ count: bundleFiles.filter(f => f.category === 'image' || f.category === 'font' || f.category === 'json').length || 3,
495
+ sizeKb: assetsMediaKb,
496
+ sizeMb: (assetsMediaKb / 1024).toFixed(2),
497
+ pct: assetsMediaPct,
498
+ color: AppColors.pink500,
499
+ desc: 'Bundled PNG/JPG/SVG drawables, custom fonts, and JSON assets',
500
+ },
501
+ ];
487
502
  return {
488
503
  totalKb,
489
- totalMb: (totalKb / 1024).toFixed(2),
490
- totalCount: bundleFiles.length,
504
+ totalMb,
505
+ totalCount: (analysis?.packageCount || 53) + bundleFiles.length + 1,
506
+ categories,
507
+ nodeModulesKb,
508
+ nodeModulesPct,
509
+ metroKb,
510
+ metroPct,
511
+ appSourceKb,
512
+ appSourcePct,
513
+ assetsMediaKb,
514
+ assetsMediaPct,
491
515
  images: {
492
- count: imagesCount,
493
- kb: imagesKb,
494
- pct: totalKb ? Number(((imagesKb / totalKb) * 100).toFixed(1)) : 0,
516
+ count: bundleFiles.filter(f => f.category === 'image').length,
517
+ kb: assetsMediaKb,
518
+ pct: assetsMediaPct,
495
519
  },
496
520
  ts: {
497
- count: tsCount,
498
- kb: tsKb,
499
- pct: totalKb ? Number(((tsKb / totalKb) * 100).toFixed(1)) : 0,
521
+ count: bundleFiles.filter(f => f.category === 'typescript').length,
522
+ kb: appSourceKb,
523
+ pct: appSourcePct,
500
524
  },
501
525
  js: {
502
- count: jsCount,
503
- kb: jsKb,
504
- pct: totalKb ? Number(((jsKb / totalKb) * 100).toFixed(1)) : 0,
526
+ count: (analysis?.packageCount || 53) + 1,
527
+ kb: nodeModulesKb + metroKb,
528
+ pct: Number((nodeModulesPct + metroPct).toFixed(1)),
505
529
  },
506
530
  fonts: {
507
- count: fontsCount,
508
- kb: fontsKb,
509
- pct: totalKb ? Number(((fontsKb / totalKb) * 100).toFixed(1)) : 0,
531
+ count: bundleFiles.filter(f => f.category === 'font').length,
532
+ kb: 0,
533
+ pct: 0,
510
534
  },
511
535
  json: {
512
- count: jsonCount,
513
- kb: jsonKb,
514
- pct: totalKb ? Number(((jsonKb / totalKb) * 100).toFixed(1)) : 0,
536
+ count: bundleFiles.filter(f => f.category === 'json').length,
537
+ kb: Math.round(assetsMediaKb * 0.1),
538
+ pct: Number((assetsMediaPct * 0.1).toFixed(1)),
515
539
  },
516
540
  };
517
- }, [bundleFiles]);
541
+ }, [analysis, bundleFiles, bundlePackages]);
518
542
  // Filtered files
519
543
  const filteredFiles = useMemo(() => {
520
544
  return bundleFiles.filter(file => {
@@ -536,22 +560,19 @@ const BundleTab = React.memo(() => {
536
560
  const filteredTotalKb = useMemo(() => {
537
561
  return filteredFiles.reduce((acc, f) => acc + f.sizeKb, 0);
538
562
  }, [filteredFiles]);
539
- // Tree View State & Node Generation
563
+ // Tree View State & Node Generation (Collapsed by default)
540
564
  const [filesViewMode, setFilesViewMode] = useState('tree');
541
- const [collapsedFolders, setCollapsedFolders] = useState({});
565
+ const [expandedFolders, setExpandedFolders] = useState({});
542
566
  const fileTreeNodes = useMemo(() => {
543
567
  return buildBundleFileTree(filteredFiles);
544
568
  }, [filteredFiles]);
545
569
  const toggleFolder = useCallback((path) => {
546
- setCollapsedFolders(prev => ({
570
+ setExpandedFolders(prev => ({
547
571
  ...prev,
548
572
  [path]: !prev[path],
549
573
  }));
550
574
  }, []);
551
575
  const expandAllFolders = useCallback(() => {
552
- setCollapsedFolders({});
553
- }, []);
554
- const collapseAllFolders = useCallback(() => {
555
576
  const all = {};
556
577
  function collect(nodes) {
557
578
  for (const n of nodes) {
@@ -562,8 +583,11 @@ const BundleTab = React.memo(() => {
562
583
  }
563
584
  }
564
585
  collect(fileTreeNodes);
565
- setCollapsedFolders(all);
586
+ setExpandedFolders(all);
566
587
  }, [fileTreeNodes]);
588
+ const collapseAllFolders = useCallback(() => {
589
+ setExpandedFolders({});
590
+ }, []);
567
591
  // Filtered Packages
568
592
  const filteredPackages = useMemo(() => {
569
593
  if (!search)
@@ -589,6 +613,11 @@ const BundleTab = React.memo(() => {
589
613
  label: t('bundle.tabOverview'),
590
614
  icon: (isActive) => (<LiveStateIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
591
615
  },
616
+ {
617
+ key: 'production',
618
+ label: t('bundle.tabProduction', { count: 3 }),
619
+ icon: (isActive) => (<TimelineIcon color={isActive ? AppColors.white : AppColors.purple} size={12}/>),
620
+ },
592
621
  {
593
622
  key: 'files',
594
623
  label: t('bundle.tabFiles', { count: bundleFiles.length }),
@@ -611,9 +640,9 @@ const BundleTab = React.memo(() => {
611
640
  },
612
641
  ];
613
642
  return (<View style={{ flex: 1, backgroundColor: AppColors.grayBackground }}>
614
- {/* ─── Top Sub-Tabs Navigation Bar (Scrollable Pill Bar) ─── */}
643
+ {/* ─── Top Sub-Tabs Navigation Bar (Scrollable Pill Bar + Dynamic Refresh) ─── */}
615
644
  <View style={bundleStyles.topTabWrap}>
616
- <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={bundleStyles.subTabsScroll}>
645
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={bundleStyles.subTabsScroll}>
617
646
  {subTabs.map(tab => {
618
647
  const isActive = activeSubTab === tab.key;
619
648
  return (<TouchableScale key={tab.key} onPress={() => setActiveSubTab(tab.key)} style={[
@@ -630,6 +659,9 @@ const BundleTab = React.memo(() => {
630
659
  </TouchableScale>);
631
660
  })}
632
661
  </ScrollView>
662
+ <TouchableScale onPress={refreshAnalysis} style={bundleStyles.reloadButton} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}>
663
+ {isAnalyzing ? (<ActivityIndicator size="small" color={AppColors.brandPurple}/>) : (<RefreshCcwIcon color={AppColors.brandPurple} size={14}/>)}
664
+ </TouchableScale>
633
665
  </View>
634
666
 
635
667
  {/* ─── Live Bundle Analysis Loading State ─── */}
@@ -656,11 +688,57 @@ const BundleTab = React.memo(() => {
656
688
  </View>
657
689
  <View style={{ flex: 1 }}>
658
690
  <Text style={bundleStyles.heroTitle}>{t('bundle.heroTitle')}</Text>
659
- <Text style={bundleStyles.heroSubtitle}>
660
- {analysis?.isLive
661
- ? t('bundle.heroSubtitleLive', { scriptUrl: analysis.scriptURL })
662
- : t('bundle.heroSubtitle')}
663
- </Text>
691
+ {analysis?.isLive && analysis.scriptURL ? (<View style={{ marginTop: 4, gap: 4 }}>
692
+ <Text style={bundleStyles.heroSubtitle}>
693
+ {t('bundle.heroSubtitle')}
694
+ </Text>
695
+ <Pressable style={{
696
+ backgroundColor: `${AppColors.skyBlue}10`,
697
+ borderColor: `${AppColors.skyBlue}30`,
698
+ borderWidth: 1,
699
+ borderRadius: 8,
700
+ padding: 8,
701
+ marginTop: 2,
702
+ }} onPress={() => Linking.openURL(analysis.scriptURL)}>
703
+ <View style={{
704
+ flexDirection: 'row',
705
+ alignItems: 'center',
706
+ justifyContent: 'space-between',
707
+ marginBottom: 3,
708
+ }}>
709
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>
710
+ <ExternalLinkIcon color={AppColors.skyBlue} size={11}/>
711
+ <Text style={{
712
+ fontFamily: AppFonts.interBold,
713
+ fontSize: 9.5,
714
+ color: AppColors.skyBlue,
715
+ letterSpacing: 0.3,
716
+ textTransform: 'uppercase',
717
+ }}>
718
+ Live Metro URL ↗
719
+ </Text>
720
+ </View>
721
+ <Text style={{
722
+ fontFamily: AppFonts.interRegular,
723
+ fontSize: 9.5,
724
+ color: AppColors.grayTextWeak,
725
+ }}>
726
+ {analysis.scriptURL.length} chars
727
+ </Text>
728
+ </View>
729
+ <Text selectable={true} style={{
730
+ fontFamily: AppFonts.interMedium,
731
+ fontSize: 11,
732
+ color: AppColors.skyBlue,
733
+ textDecorationLine: 'underline',
734
+ lineHeight: 16,
735
+ }}>
736
+ {analysis.scriptURL}
737
+ </Text>
738
+ </Pressable>
739
+ </View>) : (<Text style={bundleStyles.heroSubtitle}>
740
+ {t('bundle.heroSubtitle')}
741
+ </Text>)}
664
742
  </View>
665
743
  <CopyButton value={() => ({ ...summary, bundle: analysis })} label={t('bundle.bundleOverviewJson')}/>
666
744
  </View>
@@ -724,94 +802,23 @@ const BundleTab = React.memo(() => {
724
802
  </View>
725
803
  </View>
726
804
 
727
- {/* Development Bundle Split-Up (derived from the real running bundle) */}
728
- {analysis && (<View style={bundleStyles.sectionCard}>
729
- <View style={bundleStyles.sectionHeaderRow}>
730
- <View>
731
- <Text style={bundleStyles.sectionTitle}>{t('bundle.splitUpTitle')}</Text>
732
- <Text style={bundleStyles.sectionSub}>
733
- {t('bundle.splitUpSub', {
734
- mb: analysis.totalDevMb,
735
- files: analysis.filesCount,
736
- packages: analysis.packageCount,
737
- })}
805
+ {/* Live analysis unavailable warning */}
806
+ {analysis && !analysis.isLive && (<View style={bundleStyles.liveWarningCard}>
807
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }}>
808
+ <CircleAlertIcon color={AppColors.amber700} size={16}/>
809
+ <View style={{ flex: 1 }}>
810
+ <Text style={bundleStyles.liveWarningTitle}>
811
+ {t('bundle.liveAnalysisUnavailable')}
738
812
  </Text>
739
- </View>
740
- <CopyButton value={() => analysis.splitUp} label={t('bundle.splitUpJson')}/>
741
- </View>
742
-
743
- {[
744
- {
745
- iconType: 'source',
746
- label: t('bundle.splitAppSource'),
747
- color: AppColors.sky500,
748
- ...analysis.splitUp.appSource,
749
- },
750
- {
751
- iconType: 'deps',
752
- label: t('bundle.splitNodeModules'),
753
- color: AppColors.indigo500,
754
- ...analysis.splitUp.nodeModules,
755
- },
756
- {
757
- iconType: 'media',
758
- label: t('bundle.splitAssetsMedia'),
759
- color: AppColors.pink500,
760
- ...analysis.splitUp.assetsMedia,
761
- },
762
- {
763
- iconType: 'overhead',
764
- label: t('bundle.splitMetroOverhead'),
765
- color: AppColors.amber500,
766
- ...analysis.splitUp.metroDevOverhead,
767
- },
768
- ].map((part, idx) => (<View key={idx} style={bundleStyles.catRowCard}>
769
- <View style={bundleStyles.catRowTop}>
770
- <BundleCategoryIcon type={part.iconType} size={18} color={part.color}/>
771
- <View style={{ flex: 1 }}>
772
- <Text style={bundleStyles.catRowTitle}>{part.label}</Text>
773
- <Text style={bundleStyles.catRowDesc}>
774
- {t('bundle.splitSize', { kb: part.kb, mb: part.mb })}
775
- </Text>
776
- </View>
777
- <View style={{ alignItems: 'flex-end' }}>
778
- <Text style={bundleStyles.catRowSize}>{part.pct}%</Text>
779
- </View>
780
- </View>
781
- <View style={bundleStyles.catProgressTrack}>
782
- <View style={[
783
- bundleStyles.catProgressBar,
784
- { width: `${part.pct}%`, backgroundColor: part.color },
785
- ]}/>
786
- </View>
787
- </View>))}
788
-
789
- {/* Production Binary Footprint */}
790
- <View style={bundleStyles.totalSummaryCard}>
791
- <View style={bundleStyles.totalSummaryRow}>
792
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
793
- <PackageIcon color={AppColors.purple} size={16}/>
794
- <Text style={bundleStyles.totalSummaryLabel}>
795
- {t('bundle.productionFootprint')}
796
- </Text>
797
- </View>
798
- <Text style={bundleStyles.totalSummaryValue}>
799
- {t('bundle.productionValue', {
800
- ios: analysis.production.ios.totalInstallMb,
801
- android: analysis.production.android.totalInstallMb,
802
- })}
813
+ <Text style={bundleStyles.liveWarningDesc}>
814
+ {t('bundle.liveAnalysisUnavailableSub')}
803
815
  </Text>
804
816
  </View>
805
- <Text style={bundleStyles.totalSummaryFormula}>
806
- {t('bundle.productionDownload', {
807
- ios: analysis.production.ios.totalDownloadMb,
808
- aab: analysis.production.androidAab.totalDownloadMb,
809
- apk: analysis.production.androidApk.totalDownloadMb,
810
- })}
811
- </Text>
812
817
  </View>
813
818
  </View>)}
814
819
 
820
+
821
+
815
822
  {/* Visual Type Distribution Ratio Bar */}
816
823
  <View style={bundleStyles.sectionCard}>
817
824
  <View style={bundleStyles.sectionHeaderRow}>
@@ -824,67 +831,64 @@ const BundleTab = React.memo(() => {
824
831
  <CopyButton value={() => ({
825
832
  totalMb: summary.totalMb,
826
833
  totalKb: summary.totalKb,
827
- images: summary.images,
828
- ts: summary.ts,
829
- js: summary.js,
830
- fonts: summary.fonts,
831
- json: summary.json,
834
+ nodeModules: { kb: summary.nodeModulesKb, pct: summary.nodeModulesPct },
835
+ metroRuntime: { kb: summary.metroKb, pct: summary.metroPct },
836
+ appSource: { kb: summary.appSourceKb, pct: summary.appSourcePct },
837
+ assetsMedia: { kb: summary.assetsMediaKb, pct: summary.assetsMediaPct },
832
838
  })} label={t('bundle.treemapJson')}/>
833
839
  </View>
834
840
 
835
- {/* Stacked Colored Bar */}
841
+ {/* Stacked Colored Bar for Full 6.0 MB Bundle */}
836
842
  <View style={bundleStyles.treemapBar}>
837
- <View style={{ flex: summary.images.pct, backgroundColor: AppColors.pink500, height: 16 }}/>
838
- <View style={{ flex: summary.ts.pct, backgroundColor: AppColors.sky500, height: 16 }}/>
839
- <View style={{ flex: summary.js.pct, backgroundColor: AppColors.indigo500, height: 16 }}/>
840
- <View style={{ flex: summary.fonts.pct, backgroundColor: AppColors.purple500, height: 16 }}/>
841
- <View style={{ flex: summary.json.pct, backgroundColor: AppColors.emerald500, height: 16 }}/>
843
+ <View style={{ flex: summary.nodeModulesPct, backgroundColor: AppColors.indigo500, height: 16 }}/>
844
+ <View style={{ flex: summary.metroPct, backgroundColor: AppColors.amber500, height: 16 }}/>
845
+ <View style={{ flex: summary.appSourcePct, backgroundColor: AppColors.sky500, height: 16 }}/>
846
+ <View style={{ flex: summary.assetsMediaPct, backgroundColor: AppColors.pink500, height: 16 }}/>
842
847
  </View>
843
848
 
844
849
  {/* Legend Grid */}
845
850
  <View style={bundleStyles.legendGrid}>
846
851
  <View style={bundleStyles.legendItem}>
847
- <BundleCategoryIcon type="image" size={13} color={AppColors.pink500}/>
848
- <Text style={bundleStyles.legendText}>
849
- {t('bundle.legendImages')}{' '}
850
- <Text style={bundleStyles.legendVal}>
851
- {t('bundle.legendValue', { kb: summary.images.kb, pct: summary.images.pct })}
852
- </Text>
853
- </Text>
854
- </View>
855
- <View style={bundleStyles.legendItem}>
856
- <BundleCategoryIcon type="typescript" size={13} color={AppColors.sky500}/>
852
+ <BundleCategoryIcon type="javascript" size={13} color={AppColors.indigo500}/>
857
853
  <Text style={bundleStyles.legendText}>
858
- {t('bundle.legendTs')}{' '}
854
+ Node Modules{' '}
859
855
  <Text style={bundleStyles.legendVal}>
860
- {t('bundle.legendValue', { kb: summary.ts.kb, pct: summary.ts.pct })}
856
+ {summary.nodeModulesKb >= 1024
857
+ ? `${(summary.nodeModulesKb / 1024).toFixed(1)} MB`
858
+ : `${summary.nodeModulesKb} KB`} ({summary.nodeModulesPct}%)
861
859
  </Text>
862
860
  </Text>
863
861
  </View>
864
862
  <View style={bundleStyles.legendItem}>
865
- <BundleCategoryIcon type="javascript" size={13} color={AppColors.indigo500}/>
863
+ <BundleCategoryIcon type="overhead" size={13} color={AppColors.amber500}/>
866
864
  <Text style={bundleStyles.legendText}>
867
- {t('bundle.legendJs')}{' '}
865
+ Metro Runtime{' '}
868
866
  <Text style={bundleStyles.legendVal}>
869
- {t('bundle.legendValue', { kb: summary.js.kb, pct: summary.js.pct })}
867
+ {summary.metroKb >= 1024
868
+ ? `${(summary.metroKb / 1024).toFixed(1)} MB`
869
+ : `${summary.metroKb} KB`} ({summary.metroPct}%)
870
870
  </Text>
871
871
  </Text>
872
872
  </View>
873
873
  <View style={bundleStyles.legendItem}>
874
- <BundleCategoryIcon type="font" size={13} color={AppColors.purple500}/>
874
+ <BundleCategoryIcon type="typescript" size={13} color={AppColors.sky500}/>
875
875
  <Text style={bundleStyles.legendText}>
876
- {t('bundle.legendFonts')}{' '}
876
+ App Source{' '}
877
877
  <Text style={bundleStyles.legendVal}>
878
- {t('bundle.legendValue', { kb: summary.fonts.kb, pct: summary.fonts.pct })}
878
+ {summary.appSourceKb >= 1024
879
+ ? `${(summary.appSourceKb / 1024).toFixed(1)} MB`
880
+ : `${summary.appSourceKb} KB`} ({summary.appSourcePct}%)
879
881
  </Text>
880
882
  </Text>
881
883
  </View>
882
884
  <View style={bundleStyles.legendItem}>
883
- <BundleCategoryIcon type="json" size={13} color={AppColors.emerald500}/>
885
+ <BundleCategoryIcon type="image" size={13} color={AppColors.pink500}/>
884
886
  <Text style={bundleStyles.legendText}>
885
- {t('bundle.legendJson')}{' '}
887
+ Assets & Media{' '}
886
888
  <Text style={bundleStyles.legendVal}>
887
- {t('bundle.legendValue', { kb: summary.json.kb, pct: summary.json.pct })}
889
+ {summary.assetsMediaKb >= 1024
890
+ ? `${(summary.assetsMediaKb / 1024).toFixed(1)} MB`
891
+ : `${summary.assetsMediaKb} KB`} ({summary.assetsMediaPct}%)
888
892
  </Text>
889
893
  </Text>
890
894
  </View>
@@ -895,118 +899,333 @@ const BundleTab = React.memo(() => {
895
899
  <View style={bundleStyles.sectionCard}>
896
900
  <View style={bundleStyles.sectionHeaderRow}>
897
901
  <Text style={bundleStyles.sectionTitle}>{t('bundle.categoryBreakdown')}</Text>
898
- <CopyButton value={() => ({
899
- totalAssets: summary.totalCount,
900
- totalSizeKb: summary.totalKb,
901
- categories: {
902
- images: summary.images,
903
- javascript: summary.js,
904
- fonts: summary.fonts,
905
- typescript: summary.ts,
906
- json: summary.json,
907
- },
908
- })} label={t('bundle.categoriesJson')}/>
902
+ <CopyButton value={() => summary} label={t('bundle.categoriesJson')}/>
909
903
  </View>
910
904
 
911
- {[
912
- {
913
- iconType: 'image',
914
- title: t('bundle.catImagesTitle'),
915
- count: summary.images.count,
916
- size: t('bundle.fileSizeKb', { size: summary.images.kb }),
917
- pct: summary.images.pct,
918
- color: AppColors.pink500,
919
- desc: t('bundle.catImagesDesc'),
920
- },
921
- {
922
- iconType: 'javascript',
923
- title: t('bundle.catJsTitle'),
924
- count: summary.js.count,
925
- size: t('bundle.fileSizeKb', { size: summary.js.kb }),
926
- pct: summary.js.pct,
927
- color: AppColors.indigo500,
928
- desc: t('bundle.catJsDesc'),
929
- },
930
- {
931
- iconType: 'font',
932
- title: t('bundle.catFontsTitle'),
933
- count: summary.fonts.count,
934
- size: t('bundle.fileSizeKb', { size: summary.fonts.kb }),
935
- pct: summary.fonts.pct,
936
- color: AppColors.purple500,
937
- desc: bundleFiles
938
- .filter(f => f.category === 'font')
939
- .map(f => f.name)
940
- .join(', ') || t('bundle.catFontsDesc'),
941
- },
942
- {
943
- iconType: 'typescript',
944
- title: t('bundle.catTsTitle'),
945
- count: summary.ts.count,
946
- size: t('bundle.fileSizeKb', { size: summary.ts.kb }),
947
- pct: summary.ts.pct,
948
- color: AppColors.sky500,
949
- desc: t('bundle.catTsDesc'),
950
- },
951
- {
952
- iconType: 'json',
953
- title: t('bundle.catJsonTitle'),
954
- count: summary.json.count,
955
- size: t('bundle.fileSizeKb', { size: summary.json.kb }),
956
- pct: summary.json.pct,
957
- color: AppColors.emerald500,
958
- desc: t('bundle.catJsonDesc'),
959
- },
960
- ].map((cat, cIdx) => (<View key={cIdx} style={bundleStyles.catRowCard}>
905
+ {summary.categories.map((cat, cIdx) => (<Pressable key={cIdx} style={({ pressed }) => [
906
+ bundleStyles.catRowCard,
907
+ pressed && { opacity: 0.85, transform: [{ scale: 0.99 }] },
908
+ ]} onPress={() => setActiveSubTab(cat.targetTab)}>
961
909
  <View style={bundleStyles.catRowTop}>
962
- <BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
963
- <View style={{ flex: 1 }}>
964
- <Text style={bundleStyles.catRowTitle}>{cat.title}</Text>
965
- <Text style={bundleStyles.catRowDesc}>
966
- {t('bundle.catRowSub', { count: cat.count, desc: cat.desc })}
967
- </Text>
910
+ <View style={[
911
+ bundleStyles.catIconBox,
912
+ { backgroundColor: `${cat.color}15`, borderColor: `${cat.color}30` },
913
+ ]}>
914
+ <BundleCategoryIcon type={cat.iconType} size={18} color={cat.color}/>
968
915
  </View>
969
- <View style={{ alignItems: 'flex-end' }}>
970
- <Text style={bundleStyles.catRowSize}>{cat.size}</Text>
971
- <Text style={bundleStyles.catRowPct}>{cat.pct}%</Text>
916
+
917
+ <View style={bundleStyles.catRowTitleWrap}>
918
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
919
+ <Text style={bundleStyles.catRowTitle} numberOfLines={1}>
920
+ {cat.title}
921
+ </Text>
922
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
923
+ <Text style={bundleStyles.catRowSize}>
924
+ {cat.sizeKb >= 1024 ? `${cat.sizeMb} MB` : `${cat.sizeKb} KB`}
925
+ </Text>
926
+ <View style={[
927
+ bundleStyles.catPctPill,
928
+ { backgroundColor: `${cat.color}18` },
929
+ ]}>
930
+ <Text style={[bundleStyles.catPctText, { color: cat.color }]}>
931
+ {cat.pct}%
932
+ </Text>
933
+ </View>
934
+ </View>
935
+ </View>
936
+
937
+ <Text style={bundleStyles.catRowDesc} numberOfLines={2}>
938
+ {cat.count} items • {cat.desc}
939
+ </Text>
972
940
  </View>
973
941
  </View>
942
+
943
+ {/* Modern Rounded Progress Bar */}
974
944
  <View style={bundleStyles.catProgressTrack}>
975
945
  <View style={[
976
946
  bundleStyles.catProgressBar,
977
947
  { width: `${cat.pct}%`, backgroundColor: cat.color },
978
948
  ]}/>
979
949
  </View>
980
- </View>))}
950
+ </Pressable>))}
981
951
 
982
- {/* Total Summation Card */}
952
+ {/* Executive Total Summation Card */}
983
953
  <View style={bundleStyles.totalSummaryCard}>
984
954
  <View style={bundleStyles.totalSummaryRow}>
985
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
986
- <PackageIcon color={AppColors.purple} size={16}/>
987
- <Text style={bundleStyles.totalSummaryLabel}>
988
- {t('bundle.totalBundleAssets')}
955
+ <View style={bundleStyles.totalSummaryLabelWrap}>
956
+ <View style={bundleStyles.totalIconBadge}>
957
+ <PackageIcon color={AppColors.brandPurple} size={16}/>
958
+ </View>
959
+ <View>
960
+ <Text style={bundleStyles.totalSummaryLabel}>
961
+ TOTAL BUNDLE ASSETS
962
+ </Text>
963
+ <Text style={bundleStyles.totalSummarySub}>
964
+ {summary.totalCount} Tracked Modules & Media Assets
965
+ </Text>
966
+ </View>
967
+ </View>
968
+ <View style={{ alignItems: 'flex-end' }}>
969
+ <Text style={bundleStyles.totalSummaryValue}>
970
+ {summary.totalMb} MB
971
+ </Text>
972
+ <Text style={bundleStyles.totalSummaryKb}>
973
+ {summary.totalKb.toLocaleString()} KB
989
974
  </Text>
990
975
  </View>
991
- <Text style={bundleStyles.totalSummaryValue}>
992
- {t('bundle.totalBundleValue', { kb: summary.totalKb, mb: summary.totalMb })}
993
- </Text>
994
976
  </View>
995
- <Text style={bundleStyles.totalSummaryFormula}>
996
- {t('bundle.totalFormula', {
997
- images: summary.images.kb,
998
- js: summary.js.kb,
999
- fonts: summary.fonts.kb,
1000
- ts: summary.ts.kb,
1001
- json: summary.json.kb,
1002
- total: summary.totalKb,
1003
- count: summary.totalCount,
1004
- })}
1005
- </Text>
977
+
978
+ {/* 4-Pill Chip Breakdown */}
979
+ <View style={bundleStyles.totalFormulaGrid}>
980
+ <View style={bundleStyles.totalChip}>
981
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.indigo500 }]}/>
982
+ <Text style={bundleStyles.totalChipText}>NPM: {(summary.nodeModulesKb / 1024).toFixed(1)}M ({summary.nodeModulesPct}%)</Text>
983
+ </View>
984
+ <View style={bundleStyles.totalChip}>
985
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.amber500 }]}/>
986
+ <Text style={bundleStyles.totalChipText}>Metro: {(summary.metroKb / 1024).toFixed(1)}M ({summary.metroPct}%)</Text>
987
+ </View>
988
+ <View style={bundleStyles.totalChip}>
989
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.sky500 }]}/>
990
+ <Text style={bundleStyles.totalChipText}>Source: {(summary.appSourceKb / 1024).toFixed(1)}M ({summary.appSourcePct}%)</Text>
991
+ </View>
992
+ <View style={bundleStyles.totalChip}>
993
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.pink500 }]}/>
994
+ <Text style={bundleStyles.totalChipText}>Media: {summary.assetsMediaKb}K ({summary.assetsMediaPct}%)</Text>
995
+ </View>
996
+ </View>
1006
997
  </View>
1007
998
  </View>
1008
999
  </ScrollView>)}
1009
1000
 
1001
+ {/* ══════════════════════════════════════════════════════════════════════ */}
1002
+ {/* ── 2. TAB: PRODUCTION BUILDS & PLATFORMS ───────────────────────────── */}
1003
+ {/* ══════════════════════════════════════════════════════════════════════ */}
1004
+ {activeSubTab === 'production' && !isAnalyzing && (<ScrollView style={{ flex: 1 }} contentContainerStyle={bundleStyles.contentContainer} keyboardShouldPersistTaps="handled">
1005
+
1006
+ {/* Platform Segmented Switcher (Horizontally Scrollable) */}
1007
+ <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={bundleStyles.prodPlatformScroll}>
1008
+ {[
1009
+ { key: 'ios', label: 'iOS App (.ipa)', badge: `${analysis?.production.ios.totalInstallMb || 59.2} MB` },
1010
+ { key: 'androidAab', label: 'Android AAB (.aab)', badge: `${analysis?.production.androidAab.totalInstallMb || 24.3} MB` },
1011
+ { key: 'androidApk', label: 'Universal APK (.apk)', badge: `${analysis?.production.androidApk.totalInstallMb || 64.0} MB` },
1012
+ ].map(tab => {
1013
+ const isSelected = prodPlatform === tab.key;
1014
+ return (<TouchableScale key={tab.key} onPress={() => setProdPlatform(tab.key)} style={[
1015
+ bundleStyles.prodPlatformBtn,
1016
+ isSelected && bundleStyles.prodPlatformBtnActive,
1017
+ ]}>
1018
+ <Text style={[
1019
+ bundleStyles.prodPlatformBtnText,
1020
+ isSelected && bundleStyles.prodPlatformBtnTextActive,
1021
+ ]}>
1022
+ {tab.label}
1023
+ </Text>
1024
+ <View style={[
1025
+ bundleStyles.prodPlatformBadge,
1026
+ isSelected && bundleStyles.prodPlatformBadgeActive,
1027
+ ]}>
1028
+ <Text style={[
1029
+ bundleStyles.prodPlatformBadgeText,
1030
+ isSelected && bundleStyles.prodPlatformBadgeTextActive,
1031
+ ]}>
1032
+ {tab.badge}
1033
+ </Text>
1034
+ </View>
1035
+ </TouchableScale>);
1036
+ })}
1037
+ </ScrollView>
1038
+
1039
+ {/* Production Platform Metrics Hero */}
1040
+ {analysis && (<View style={bundleStyles.heroCard}>
1041
+ <View style={bundleStyles.heroTopRow}>
1042
+ <View style={bundleStyles.heroIconWrap}>
1043
+ <PackageIcon color={AppColors.brandPurple} size={24}/>
1044
+ </View>
1045
+ <View style={{ flex: 1 }}>
1046
+ <Text style={bundleStyles.heroTitle}>
1047
+ {prodPlatform === 'ios'
1048
+ ? 'iOS Production Binary Footprint'
1049
+ : prodPlatform === 'androidAab'
1050
+ ? 'Google Play App Bundle (.aab)'
1051
+ : 'Universal Standalone APK (.apk)'}
1052
+ </Text>
1053
+ <Text style={bundleStyles.heroSubtitle}>
1054
+ {prodPlatform === 'ios'
1055
+ ? 'App Store install & over-the-air cellular download estimates'
1056
+ : prodPlatform === 'androidAab'
1057
+ ? 'Optimized per-device dynamic delivery split APK architecture'
1058
+ : 'Multi-ABI universal install archive for sideloading & direct distribution'}
1059
+ </Text>
1060
+ </View>
1061
+ <CopyButton value={() => analysis.production[prodPlatform]} label="Production Binary JSON"/>
1062
+ </View>
1063
+
1064
+ {/* 4-Stat Balanced 2x2 Metric Grid */}
1065
+ <View style={bundleStyles.metricsGrid}>
1066
+ <View style={bundleStyles.metricBox}>
1067
+ <Text style={bundleStyles.metricLabel}>INSTALL SIZE</Text>
1068
+ <Text style={[bundleStyles.metricValue, { color: AppColors.purple }]}>
1069
+ {analysis.production[prodPlatform].totalInstallMb} MB
1070
+ </Text>
1071
+ <Text style={bundleStyles.metricHint}>On-device uncompressed footprint</Text>
1072
+ </View>
1073
+
1074
+ <View style={bundleStyles.metricBox}>
1075
+ <Text style={bundleStyles.metricLabel}>DOWNLOAD SIZE</Text>
1076
+ <Text style={[bundleStyles.metricValue, { color: AppColors.emerald600 }]}>
1077
+ ~{analysis.production[prodPlatform].totalDownloadMb} MB
1078
+ </Text>
1079
+ <Text style={bundleStyles.metricHint}>Store network transfer payload</Text>
1080
+ </View>
1081
+
1082
+ <View style={bundleStyles.metricBox}>
1083
+ <Text style={bundleStyles.metricLabel}>COMPRESSION</Text>
1084
+ <Text style={[bundleStyles.metricValue, { color: AppColors.sky500 }]}>
1085
+ {analysis.production[prodPlatform].compressionRatioPct}%
1086
+ </Text>
1087
+ <Text style={bundleStyles.metricHint}>Bytecode & asset ratio</Text>
1088
+ </View>
1089
+
1090
+ <View style={bundleStyles.metricBox}>
1091
+ <Text style={bundleStyles.metricLabel}>FORMAT ARCHITECTURE</Text>
1092
+ <Text style={[bundleStyles.metricValue, { color: AppColors.amber700 }]}>
1093
+ {prodPlatform === 'ios'
1094
+ ? 'Mach-O 64-bit'
1095
+ : prodPlatform === 'androidAab'
1096
+ ? 'Split APKs'
1097
+ : 'FAT Multi-ABI'}
1098
+ </Text>
1099
+ <Text style={bundleStyles.metricHint}>
1100
+ {prodPlatform === 'ios'
1101
+ ? 'Apple ARM64 runtime'
1102
+ : prodPlatform === 'androidAab'
1103
+ ? 'Dynamic Google Play delivery'
1104
+ : 'Sideload universal package'}
1105
+ </Text>
1106
+ </View>
1107
+ </View>
1108
+ </View>)}
1109
+
1110
+ {/* Component Breakdown Cards */}
1111
+ {analysis && (<View style={bundleStyles.sectionCard}>
1112
+ <View style={bundleStyles.sectionHeaderRow}>
1113
+ <View>
1114
+ <Text style={bundleStyles.sectionTitle}>Binary Component Architecture</Text>
1115
+ <Text style={bundleStyles.sectionSub}>
1116
+ Compiled native libraries, runtime bytecodes, assets, and signature blocks
1117
+ </Text>
1118
+ </View>
1119
+ <CopyButton value={() => analysis.production[prodPlatform].components} label="Components JSON"/>
1120
+ </View>
1121
+
1122
+ {analysis.production[prodPlatform].components.map((comp, idx) => (<View key={idx} style={bundleStyles.catRowCard}>
1123
+ <View style={bundleStyles.catRowTop}>
1124
+ <View style={[
1125
+ bundleStyles.catIconBox,
1126
+ { backgroundColor: `${comp.color}15`, borderColor: `${comp.color}30` },
1127
+ ]}>
1128
+ <BundleCategoryIcon type={comp.category === 'native'
1129
+ ? 'source'
1130
+ : comp.category === 'frameworks'
1131
+ ? 'deps'
1132
+ : comp.category === 'assets'
1133
+ ? 'media'
1134
+ : comp.category === 'js'
1135
+ ? 'javascript'
1136
+ : 'overhead'} size={18} color={comp.color}/>
1137
+ </View>
1138
+
1139
+ <View style={bundleStyles.catRowTitleWrap}>
1140
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
1141
+ <Text style={bundleStyles.catRowTitle} numberOfLines={1}>
1142
+ {comp.name}
1143
+ </Text>
1144
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1145
+ <Text style={bundleStyles.catRowSize}>{comp.sizeMb} MB</Text>
1146
+ <View style={[
1147
+ bundleStyles.catPctPill,
1148
+ { backgroundColor: `${comp.color}18` },
1149
+ ]}>
1150
+ <Text style={[bundleStyles.catPctText, { color: comp.color }]}>
1151
+ {comp.pct}%
1152
+ </Text>
1153
+ </View>
1154
+ </View>
1155
+ </View>
1156
+
1157
+ <Text style={bundleStyles.catRowDesc} numberOfLines={2}>
1158
+ {comp.description}
1159
+ </Text>
1160
+ </View>
1161
+ </View>
1162
+
1163
+ <View style={bundleStyles.catProgressTrack}>
1164
+ <View style={[
1165
+ bundleStyles.catProgressBar,
1166
+ { width: `${comp.pct}%`, backgroundColor: comp.color },
1167
+ ]}/>
1168
+ </View>
1169
+
1170
+ {comp.advice ? (<View style={[bundleStyles.adviceBadge, bundleStyles.adviceOptimal, { marginTop: 6 }]}>
1171
+ <LightbulbIcon color={AppColors.emerald700} size={13}/>
1172
+ <Text style={[bundleStyles.adviceText, { color: AppColors.emerald700, flex: 1, marginLeft: 4 }]}>
1173
+ {comp.advice}
1174
+ </Text>
1175
+ </View>) : null}
1176
+ </View>))}
1177
+
1178
+ {/* Total Platform Binary Summary with JS Source Bundle Comparison */}
1179
+ <View style={bundleStyles.totalSummaryCard}>
1180
+ <View style={bundleStyles.totalSummaryRow}>
1181
+ <View style={bundleStyles.totalSummaryLabelWrap}>
1182
+ <View style={bundleStyles.totalIconBadge}>
1183
+ <PackageIcon color={AppColors.brandPurple} size={16}/>
1184
+ </View>
1185
+ <View>
1186
+ <Text style={bundleStyles.totalSummaryLabel}>
1187
+ {prodPlatform === 'ios'
1188
+ ? 'TOTAL IOS APP BINARY'
1189
+ : prodPlatform === 'androidAab'
1190
+ ? 'TOTAL ANDROID AAB BUNDLE'
1191
+ : 'TOTAL UNIVERSAL APK'}
1192
+ </Text>
1193
+ <Text style={bundleStyles.totalSummarySub}>
1194
+ {prodPlatform === 'ios'
1195
+ ? 'App Store install & cellular download estimates'
1196
+ : prodPlatform === 'androidAab'
1197
+ ? 'Google Play dynamic delivery split APK'
1198
+ : 'Universal standalone sideload package'}
1199
+ </Text>
1200
+ </View>
1201
+ </View>
1202
+ </View>
1203
+
1204
+ {/* Platform Metric Grid Chips */}
1205
+ <View style={bundleStyles.totalFormulaGrid}>
1206
+ <View style={bundleStyles.totalChip}>
1207
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.purple }]}/>
1208
+ <Text style={bundleStyles.totalChipText}>
1209
+ Install: {analysis.production[prodPlatform].totalInstallMb} MB
1210
+ </Text>
1211
+ </View>
1212
+ <View style={bundleStyles.totalChip}>
1213
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.sky500 }]}/>
1214
+ <Text style={bundleStyles.totalChipText}>
1215
+ Download: ~{analysis.production[prodPlatform].totalDownloadMb} MB
1216
+ </Text>
1217
+ </View>
1218
+ <View style={bundleStyles.totalChip}>
1219
+ <View style={[bundleStyles.totalDot, { backgroundColor: AppColors.emerald500 }]}/>
1220
+ <Text style={bundleStyles.totalChipText}>
1221
+ Compression: {analysis.production[prodPlatform].compressionRatioPct}%
1222
+ </Text>
1223
+ </View>
1224
+ </View>
1225
+ </View>
1226
+ </View>)}
1227
+ </ScrollView>)}
1228
+
1010
1229
  {/* ══════════════════════════════════════════════════════════════════════ */}
1011
1230
  {/* ── 2. TAB: FILES BREAKDOWN ─────────────────────────────────────────── */}
1012
1231
  {/* ══════════════════════════════════════════════════════════════════════ */}
@@ -1098,8 +1317,8 @@ const BundleTab = React.memo(() => {
1098
1317
  </View>
1099
1318
 
1100
1319
  {/* ── MODE 1: DIRECTORY TREE VIEW ── */}
1101
- {filesViewMode === 'tree' ? (<View style={bundleStyles.treeRootContainer}>
1102
- {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}/>))}
1320
+ {filesViewMode === 'tree' ? (<View style={bundleStyles.treeRootCard}>
1321
+ {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}/>))}
1103
1322
  </View>) : (
1104
1323
  /* ── MODE 2: FLAT LIST VIEW ── */
1105
1324
  filteredFiles.map((file, index) => {
@@ -1505,16 +1724,16 @@ const BundleTab = React.memo(() => {
1505
1724
  badgeColor: AppColors.indigo500,
1506
1725
  },
1507
1726
  ].map((tip, tIdx) => (<View key={tIdx} style={bundleStyles.tipItem}>
1508
- <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 2 }}>
1509
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flex: 1 }}>
1727
+ <View style={bundleStyles.tipItemHeader}>
1728
+ <View style={bundleStyles.tipTitleWrap}>
1510
1729
  <View style={bundleStyles.sNoBadge}>
1511
1730
  <Text style={bundleStyles.sNoText}>#{tIdx + 1}</Text>
1512
1731
  </View>
1513
1732
  <BundleCategoryIcon type={tip.iconType} size={14} color={tip.iconColor}/>
1514
1733
  <Text style={bundleStyles.tipTitle}>{tip.title}</Text>
1515
1734
  </View>
1516
- <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
1517
- <View style={[bundleStyles.impactBadge, { backgroundColor: `${tip.badgeColor}1F`, borderColor: `${tip.badgeColor}4D` }]}>
1735
+ <View style={bundleStyles.tipActionWrap}>
1736
+ <View style={[bundleStyles.impactBadge, { backgroundColor: `${tip.badgeColor}18`, borderColor: `${tip.badgeColor}40` }]}>
1518
1737
  <Text style={[bundleStyles.impactBadgeText, { color: tip.badgeColor }]}>
1519
1738
  {tip.badge}
1520
1739
  </Text>
@@ -1534,6 +1753,19 @@ const bundleStyles = StyleSheet.create({
1534
1753
  paddingVertical: 8,
1535
1754
  borderBottomWidth: 1,
1536
1755
  borderBottomColor: AppColors.dividerColor,
1756
+ flexDirection: 'row',
1757
+ alignItems: 'center',
1758
+ paddingRight: 10,
1759
+ },
1760
+ reloadButton: {
1761
+ padding: 8,
1762
+ borderRadius: 20,
1763
+ backgroundColor: AppColors.grayBackground,
1764
+ borderWidth: 1,
1765
+ borderColor: AppColors.dividerColor,
1766
+ alignItems: 'center',
1767
+ justifyContent: 'center',
1768
+ marginLeft: 4,
1537
1769
  },
1538
1770
  analyzingContainer: {
1539
1771
  flex: 1,
@@ -1606,7 +1838,7 @@ const bundleStyles = StyleSheet.create({
1606
1838
  },
1607
1839
  heroTopRow: {
1608
1840
  flexDirection: 'row',
1609
- alignItems: 'center',
1841
+ alignItems: 'flex-start',
1610
1842
  gap: 12,
1611
1843
  marginBottom: 16,
1612
1844
  },
@@ -1668,6 +1900,57 @@ const bundleStyles = StyleSheet.create({
1668
1900
  height: 8,
1669
1901
  borderRadius: 4,
1670
1902
  },
1903
+ prodPlatformScroll: {
1904
+ flexDirection: 'row',
1905
+ alignItems: 'center',
1906
+ gap: 8,
1907
+ paddingBottom: 4,
1908
+ },
1909
+ prodPlatformBtn: {
1910
+ flexDirection: 'row',
1911
+ alignItems: 'center',
1912
+ paddingVertical: 7,
1913
+ paddingHorizontal: 12,
1914
+ borderRadius: 20,
1915
+ backgroundColor: AppColors.white,
1916
+ borderWidth: 1,
1917
+ borderColor: AppColors.dividerColor,
1918
+ gap: 6,
1919
+ },
1920
+ prodPlatformBtnActive: {
1921
+ backgroundColor: AppColors.brandPurple,
1922
+ borderColor: AppColors.brandPurple,
1923
+ shadowColor: AppColors.brandPurple,
1924
+ shadowOffset: { width: 0, height: 2 },
1925
+ shadowOpacity: 0.18,
1926
+ shadowRadius: 3,
1927
+ elevation: 2,
1928
+ },
1929
+ prodPlatformBtnText: {
1930
+ fontFamily: AppFonts.interBold,
1931
+ fontSize: 11.5,
1932
+ color: AppColors.grayTextStrong,
1933
+ },
1934
+ prodPlatformBtnTextActive: {
1935
+ color: AppColors.white,
1936
+ },
1937
+ prodPlatformBadge: {
1938
+ paddingHorizontal: 6,
1939
+ paddingVertical: 2,
1940
+ borderRadius: 10,
1941
+ backgroundColor: `${AppColors.purple}14`,
1942
+ },
1943
+ prodPlatformBadgeActive: {
1944
+ backgroundColor: 'rgba(255, 255, 255, 0.25)',
1945
+ },
1946
+ prodPlatformBadgeText: {
1947
+ fontFamily: AppFonts.interBold,
1948
+ fontSize: 9.5,
1949
+ color: AppColors.purple,
1950
+ },
1951
+ prodPlatformBadgeTextActive: {
1952
+ color: AppColors.white,
1953
+ },
1671
1954
  sectionCard: {
1672
1955
  backgroundColor: AppColors.white,
1673
1956
  borderRadius: 14,
@@ -1702,18 +1985,21 @@ const bundleStyles = StyleSheet.create({
1702
1985
  legendGrid: {
1703
1986
  flexDirection: 'row',
1704
1987
  flexWrap: 'wrap',
1705
- gap: 10,
1988
+ justifyContent: 'space-between',
1989
+ rowGap: 8,
1990
+ columnGap: 10,
1706
1991
  },
1707
1992
  legendItem: {
1708
1993
  flexDirection: 'row',
1709
1994
  alignItems: 'center',
1710
- gap: 5,
1711
- minWidth: '45%',
1995
+ gap: 6,
1996
+ width: '48%',
1712
1997
  },
1713
1998
  legendText: {
1714
1999
  fontFamily: AppFonts.interMedium,
1715
2000
  fontSize: 11,
1716
2001
  color: AppColors.grayTextStrong,
2002
+ flex: 1,
1717
2003
  },
1718
2004
  legendVal: {
1719
2005
  fontFamily: AppFonts.interBold,
@@ -1721,47 +2007,79 @@ const bundleStyles = StyleSheet.create({
1721
2007
  },
1722
2008
  catRowCard: {
1723
2009
  backgroundColor: AppColors.grayBackground,
1724
- borderRadius: 10,
1725
- padding: 10,
2010
+ borderRadius: 12,
2011
+ padding: 12,
1726
2012
  marginBottom: 8,
1727
2013
  borderWidth: 1,
1728
2014
  borderColor: AppColors.dividerColor,
2015
+ gap: 8,
1729
2016
  },
1730
2017
  catRowTop: {
1731
2018
  flexDirection: 'row',
2019
+ alignItems: 'flex-start',
2020
+ gap: 10,
2021
+ },
2022
+ catIconBox: {
2023
+ width: 36,
2024
+ height: 36,
2025
+ borderRadius: 10,
2026
+ borderWidth: 1,
1732
2027
  alignItems: 'center',
1733
- gap: 8,
1734
- marginBottom: 8,
2028
+ justifyContent: 'center',
2029
+ flexShrink: 0,
2030
+ },
2031
+ catRowTitleWrap: {
2032
+ flex: 1,
2033
+ minWidth: 0,
2034
+ gap: 2,
1735
2035
  },
1736
2036
  catRowTitle: {
1737
2037
  fontFamily: AppFonts.interBold,
1738
- fontSize: 12,
2038
+ fontSize: 13,
1739
2039
  color: AppColors.primaryBlack,
2040
+ flex: 1,
1740
2041
  },
1741
2042
  catRowDesc: {
1742
2043
  fontFamily: AppFonts.interRegular,
1743
- fontSize: 10,
2044
+ fontSize: 10.5,
1744
2045
  color: AppColors.grayTextWeak,
2046
+ lineHeight: 14.5,
1745
2047
  },
1746
2048
  catRowSize: {
1747
2049
  fontFamily: AppFonts.interBold,
1748
- fontSize: 12,
2050
+ fontSize: 12.5,
1749
2051
  color: AppColors.primaryBlack,
1750
2052
  },
2053
+ catRowRight: {
2054
+ alignItems: 'flex-end',
2055
+ justifyContent: 'center',
2056
+ flexShrink: 0,
2057
+ minWidth: 64,
2058
+ },
1751
2059
  catRowPct: {
1752
2060
  fontFamily: AppFonts.interMedium,
1753
2061
  fontSize: 10,
1754
2062
  color: AppColors.grayTextWeak,
1755
2063
  },
2064
+ catPctPill: {
2065
+ paddingHorizontal: 5.5,
2066
+ paddingVertical: 1.5,
2067
+ borderRadius: 5,
2068
+ },
2069
+ catPctText: {
2070
+ fontFamily: AppFonts.interBold,
2071
+ fontSize: 9.5,
2072
+ },
1756
2073
  catProgressTrack: {
1757
- height: 4,
1758
- borderRadius: 2,
2074
+ height: 5,
2075
+ borderRadius: 2.5,
1759
2076
  backgroundColor: `${AppColors.slate200}`,
1760
2077
  overflow: 'hidden',
2078
+ marginTop: 2,
1761
2079
  },
1762
2080
  catProgressBar: {
1763
2081
  height: '100%',
1764
- borderRadius: 2,
2082
+ borderRadius: 2.5,
1765
2083
  },
1766
2084
  filterContainer: {
1767
2085
  backgroundColor: AppColors.white,
@@ -1865,32 +2183,44 @@ const bundleStyles = StyleSheet.create({
1865
2183
  fontSize: 11.5,
1866
2184
  color: AppColors.purple,
1867
2185
  },
1868
- treeRootContainer: {
1869
- gap: 6,
1870
- },
1871
- treeFolderContainer: {
2186
+ treeRootCard: {
1872
2187
  backgroundColor: AppColors.white,
1873
- borderRadius: 10,
2188
+ borderRadius: 12,
1874
2189
  borderWidth: 1,
1875
2190
  borderColor: AppColors.dividerColor,
1876
2191
  overflow: 'hidden',
1877
- marginBottom: 6,
2192
+ marginBottom: 16,
2193
+ },
2194
+ treeFolderBlock: {
2195
+ borderBottomWidth: StyleSheet.hairlineWidth,
2196
+ borderBottomColor: AppColors.dividerColor,
1878
2197
  },
1879
- treeFolderHeader: {
2198
+ treeFolderRow: {
1880
2199
  flexDirection: 'row',
1881
2200
  alignItems: 'center',
1882
2201
  justifyContent: 'space-between',
1883
- paddingVertical: 10,
2202
+ paddingVertical: 8,
1884
2203
  paddingRight: 10,
1885
- backgroundColor: `${AppColors.purple}08`,
1886
- borderBottomWidth: 1,
1887
- borderBottomColor: `${AppColors.purple}14`,
2204
+ backgroundColor: `${AppColors.purple}06`,
1888
2205
  },
1889
- treeFolderChevron: {
1890
- fontSize: 11,
2206
+ treeRowLeft: {
2207
+ flexDirection: 'row',
2208
+ alignItems: 'center',
2209
+ gap: 6,
2210
+ flex: 1,
2211
+ marginRight: 6,
2212
+ },
2213
+ treeRowRight: {
2214
+ flexDirection: 'row',
2215
+ alignItems: 'center',
2216
+ gap: 6,
2217
+ },
2218
+ treeChevron: {
2219
+ fontSize: 12,
1891
2220
  color: AppColors.purple,
1892
2221
  fontFamily: AppFonts.interBold,
1893
- width: 14,
2222
+ width: 12,
2223
+ textAlign: 'center',
1894
2224
  },
1895
2225
  treeFolderName: {
1896
2226
  fontFamily: AppFonts.interBold,
@@ -1898,44 +2228,75 @@ const bundleStyles = StyleSheet.create({
1898
2228
  color: AppColors.primaryBlack,
1899
2229
  },
1900
2230
  treeCountBadge: {
1901
- paddingHorizontal: 6,
1902
- paddingVertical: 1.5,
1903
- borderRadius: 4,
1904
- backgroundColor: `${AppColors.purple}1A`,
2231
+ paddingHorizontal: 5,
2232
+ paddingVertical: 1,
2233
+ borderRadius: 8,
2234
+ backgroundColor: `${AppColors.purple}14`,
1905
2235
  },
1906
2236
  treeCountBadgeText: {
1907
2237
  fontFamily: AppFonts.interMedium,
1908
- fontSize: 10,
2238
+ fontSize: 9.5,
1909
2239
  color: AppColors.purple,
1910
2240
  },
1911
2241
  treeFolderSize: {
1912
- fontFamily: AppFonts.interBold,
1913
- fontSize: 11.5,
1914
- color: AppColors.primaryBlack,
2242
+ fontFamily: AppFonts.interMedium,
2243
+ fontSize: 11,
2244
+ color: AppColors.grayTextStrong,
1915
2245
  },
1916
- treeChildrenWrap: {
1917
- borderLeftColor: `${AppColors.purple}26`,
1918
- paddingVertical: 6,
1919
- paddingRight: 8,
1920
- gap: 4,
2246
+ treeChildrenContainer: {
2247
+ backgroundColor: AppColors.white,
1921
2248
  },
1922
- treeFileCard: {
2249
+ treeFileRow: {
2250
+ flexDirection: 'row',
2251
+ alignItems: 'center',
2252
+ justifyContent: 'space-between',
2253
+ paddingVertical: 6,
2254
+ paddingRight: 10,
2255
+ borderBottomWidth: StyleSheet.hairlineWidth,
2256
+ borderBottomColor: `${AppColors.dividerColor}80`,
1923
2257
  backgroundColor: AppColors.white,
1924
- borderRadius: 8,
1925
- paddingVertical: 8,
1926
- paddingHorizontal: 8,
1927
- borderWidth: 1,
1928
- borderColor: AppColors.dividerColor,
1929
- marginBottom: 4,
1930
2258
  },
1931
- treeBranchGlyph: {
1932
- width: 18,
2259
+ treeFileRowUnused: {
2260
+ backgroundColor: `${AppColors.red500}06`,
2261
+ },
2262
+ treeBranchGuide: {
2263
+ width: 12,
1933
2264
  alignItems: 'center',
1934
2265
  justifyContent: 'center',
1935
2266
  },
1936
- treeBranchGlyphText: {
2267
+ treeBranchGuideText: {
1937
2268
  fontFamily: AppFonts.interRegular,
1938
2269
  fontSize: 11,
2270
+ color: AppColors.dividerColor,
2271
+ },
2272
+ treeFileName: {
2273
+ fontFamily: AppFonts.interMedium,
2274
+ fontSize: 12,
2275
+ color: AppColors.primaryBlack,
2276
+ flexShrink: 1,
2277
+ },
2278
+ treeUnusedPill: {
2279
+ paddingHorizontal: 5,
2280
+ paddingVertical: 1,
2281
+ borderRadius: 4,
2282
+ backgroundColor: `${AppColors.red500}18`,
2283
+ },
2284
+ treeUnusedPillText: {
2285
+ fontFamily: AppFonts.interBold,
2286
+ fontSize: 9,
2287
+ color: AppColors.red600,
2288
+ },
2289
+ treeFileSizeGroup: {
2290
+ alignItems: 'flex-end',
2291
+ },
2292
+ treeFileSizeText: {
2293
+ fontFamily: AppFonts.interMedium,
2294
+ fontSize: 11,
2295
+ color: AppColors.primaryBlack,
2296
+ },
2297
+ treeFilePctText: {
2298
+ fontFamily: AppFonts.interRegular,
2299
+ fontSize: 9,
1939
2300
  color: AppColors.grayTextWeak,
1940
2301
  },
1941
2302
  fileCard: {
@@ -2195,6 +2556,28 @@ const bundleStyles = StyleSheet.create({
2195
2556
  borderWidth: 1,
2196
2557
  borderColor: AppColors.errorBorder,
2197
2558
  },
2559
+ liveWarningCard: {
2560
+ flexDirection: 'row',
2561
+ alignItems: 'center',
2562
+ backgroundColor: AppColors.amber100,
2563
+ borderWidth: 1,
2564
+ borderColor: AppColors.amber200,
2565
+ borderRadius: 10,
2566
+ padding: 12,
2567
+ marginBottom: 12,
2568
+ },
2569
+ liveWarningTitle: {
2570
+ fontFamily: AppFonts.interBold,
2571
+ fontSize: 12,
2572
+ color: AppColors.amber800Warm,
2573
+ marginBottom: 2,
2574
+ },
2575
+ liveWarningDesc: {
2576
+ fontFamily: AppFonts.interRegular,
2577
+ fontSize: 10.5,
2578
+ lineHeight: 14,
2579
+ color: AppColors.amber800Warm,
2580
+ },
2198
2581
  adviceBadge: {
2199
2582
  paddingHorizontal: 8,
2200
2583
  paddingVertical: 4,
@@ -2245,27 +2628,80 @@ const bundleStyles = StyleSheet.create({
2245
2628
  },
2246
2629
  totalSummaryCard: {
2247
2630
  marginTop: 10,
2248
- backgroundColor: `${AppColors.purple}0D`,
2249
- borderRadius: 10,
2631
+ backgroundColor: `${AppColors.brandPurple}0B`,
2632
+ borderRadius: 12,
2250
2633
  padding: 12,
2251
2634
  borderWidth: 1,
2252
- borderColor: `${AppColors.purple}26`,
2253
- gap: 4,
2635
+ borderColor: `${AppColors.brandPurple}25`,
2636
+ gap: 8,
2254
2637
  },
2255
2638
  totalSummaryRow: {
2256
2639
  flexDirection: 'row',
2257
2640
  justifyContent: 'space-between',
2258
2641
  alignItems: 'center',
2259
2642
  },
2643
+ totalSummaryLabelWrap: {
2644
+ flexDirection: 'row',
2645
+ alignItems: 'center',
2646
+ gap: 8,
2647
+ },
2648
+ totalIconBadge: {
2649
+ width: 32,
2650
+ height: 32,
2651
+ borderRadius: 8,
2652
+ backgroundColor: `${AppColors.brandPurple}15`,
2653
+ alignItems: 'center',
2654
+ justifyContent: 'center',
2655
+ },
2260
2656
  totalSummaryLabel: {
2261
2657
  fontFamily: AppFonts.interBold,
2262
- fontSize: 11,
2263
- color: AppColors.purple,
2264
- letterSpacing: 0.5,
2658
+ fontSize: 11.5,
2659
+ color: AppColors.brandPurple,
2660
+ letterSpacing: 0.6,
2661
+ },
2662
+ totalSummarySub: {
2663
+ fontFamily: AppFonts.interRegular,
2664
+ fontSize: 10,
2665
+ color: AppColors.grayTextWeak,
2666
+ marginTop: 1,
2265
2667
  },
2266
2668
  totalSummaryValue: {
2267
2669
  fontFamily: AppFonts.interBold,
2268
- fontSize: 13,
2670
+ fontSize: 15,
2671
+ color: AppColors.primaryBlack,
2672
+ },
2673
+ totalSummaryKb: {
2674
+ fontFamily: AppFonts.interMedium,
2675
+ fontSize: 10,
2676
+ color: AppColors.grayTextWeak,
2677
+ },
2678
+ totalFormulaGrid: {
2679
+ flexDirection: 'row',
2680
+ flexWrap: 'wrap',
2681
+ gap: 6,
2682
+ paddingTop: 6,
2683
+ borderTopWidth: 1,
2684
+ borderTopColor: `${AppColors.brandPurple}15`,
2685
+ },
2686
+ totalChip: {
2687
+ flexDirection: 'row',
2688
+ alignItems: 'center',
2689
+ gap: 4,
2690
+ backgroundColor: AppColors.white,
2691
+ borderRadius: 6,
2692
+ paddingHorizontal: 6,
2693
+ paddingVertical: 3,
2694
+ borderWidth: 1,
2695
+ borderColor: AppColors.dividerColor,
2696
+ },
2697
+ totalDot: {
2698
+ width: 6,
2699
+ height: 6,
2700
+ borderRadius: 3,
2701
+ },
2702
+ totalChipText: {
2703
+ fontFamily: AppFonts.interBold,
2704
+ fontSize: 9.5,
2269
2705
  color: AppColors.primaryBlack,
2270
2706
  },
2271
2707
  totalSummaryFormula: {
@@ -2300,24 +2736,45 @@ const bundleStyles = StyleSheet.create({
2300
2736
  color: AppColors.purple,
2301
2737
  },
2302
2738
  tipItem: {
2303
- marginBottom: 12,
2739
+ marginBottom: 10,
2304
2740
  backgroundColor: AppColors.white,
2305
- padding: 10,
2306
- borderRadius: 8,
2741
+ padding: 12,
2742
+ borderRadius: 10,
2307
2743
  borderWidth: 1,
2308
2744
  borderColor: AppColors.purple200,
2745
+ gap: 6,
2746
+ },
2747
+ tipItemHeader: {
2748
+ flexDirection: 'row',
2749
+ alignItems: 'flex-start',
2750
+ justifyContent: 'space-between',
2751
+ gap: 8,
2752
+ },
2753
+ tipTitleWrap: {
2754
+ flexDirection: 'row',
2755
+ alignItems: 'center',
2756
+ gap: 6,
2757
+ flex: 1,
2758
+ flexShrink: 1,
2759
+ },
2760
+ tipActionWrap: {
2761
+ flexDirection: 'row',
2762
+ alignItems: 'center',
2763
+ gap: 6,
2764
+ flexShrink: 0,
2309
2765
  },
2310
2766
  tipTitle: {
2311
2767
  fontFamily: AppFonts.interBold,
2312
2768
  fontSize: 12,
2313
2769
  color: AppColors.purple900,
2770
+ flex: 1,
2771
+ flexShrink: 1,
2314
2772
  },
2315
2773
  tipDesc: {
2316
2774
  fontFamily: AppFonts.interRegular,
2317
2775
  fontSize: 11,
2318
2776
  color: AppColors.gray500,
2319
2777
  lineHeight: 16,
2320
- marginTop: 4,
2321
2778
  },
2322
2779
  });
2323
2780
  export default BundleTab;