react-native-debug-toolkit 3.3.2 → 3.3.4

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.
@@ -37,7 +37,6 @@ import {
37
37
  } from './devConnectPreferences';
38
38
  import {
39
39
  applyMetroBundle,
40
- clearNativeDiagnostics,
41
40
  getNativeDiagnostics,
42
41
  resetMetroBundle,
43
42
  type NativeDiagnostics,
@@ -80,7 +79,6 @@ export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<Dev
80
79
  const [metroBusy, setMetroBusy] = useState(false);
81
80
  const [diagData, setDiagData] = useState<NativeDiagnostics | null>(null);
82
81
  const [diagOpen, setDiagOpen] = useState(false);
83
- const [diagBusy, setDiagBusy] = useState(false);
84
82
 
85
83
  const isSim = snapshot.isSimulator;
86
84
 
@@ -88,6 +86,23 @@ export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<Dev
88
86
  (feature as unknown as DevConnectFeatureControls).updateSettings?.(patch);
89
87
  }, [feature]);
90
88
 
89
+ useEffect(() => {
90
+ getNativeDiagnostics().then((result) => {
91
+ if (result) {
92
+ setDiagData(result);
93
+ console.info(
94
+ `[DevConnect] debugBuild=${result.isDebugBuild} appDelegate=${result.appDelegateClass} persistedHost=${result.persistedMetroHost ?? 'none'}`,
95
+ );
96
+ }
97
+ }).catch(() => {});
98
+ }, []);
99
+
100
+ const refreshDiag = useCallback(() => {
101
+ getNativeDiagnostics().then((result) => {
102
+ if (result) setDiagData(result);
103
+ }).catch(() => {});
104
+ }, []);
105
+
91
106
  useEffect(() => {
92
107
  setComputerHost(snapshot.computerHost);
93
108
  }, [snapshot.computerHost]);
@@ -381,24 +396,11 @@ export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<Dev
381
396
  }
382
397
  }, [snapshot.nativeMetroAvailable]);
383
398
 
384
- const readDiag = useCallback(async () => {
385
- setDiagBusy(true);
386
- try {
387
- const result = await getNativeDiagnostics();
388
- setDiagData(result);
389
- setDiagOpen(true);
390
- } finally {
391
- setDiagBusy(false);
392
- }
393
- }, []);
394
-
395
- const clearDiag = useCallback(async () => {
396
- await clearNativeDiagnostics();
397
- setDiagData(null);
398
- }, []);
399
-
399
+ // Metro host switching only works in Debug builds. diagData is iOS-populated; when we know
400
+ // it's a Release build, disable the controls (Android reports null → stays enabled).
401
+ const metroReleaseBlocked = diagData ? !diagData.isDebugBuild : false;
400
402
  const canConnect = isSim || (Boolean(normalizeComputerHost(computerHost)) && Boolean(normalizePort(daemonPort)));
401
- const canUseMetro = Boolean(metroTarget) && snapshot.nativeMetroAvailable && !metroBusy;
403
+ const canUseMetro = Boolean(metroTarget) && snapshot.nativeMetroAvailable && !metroBusy && !metroReleaseBlocked;
402
404
  const busy = sending || syncState === 'checking';
403
405
  const subnetPrefix = snapshot.subnetPrefix;
404
406
  const ipPlaceholder = subnetPrefix ? `${subnetPrefix}...` : '192.168.1.10';
@@ -505,9 +507,20 @@ export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<Dev
505
507
  {message ? <Text style={styles.message}>{message}</Text> : null}
506
508
 
507
509
  <View style={styles.section}>
508
- <Text style={styles.sectionTitle}>Remote JS Bundle</Text>
510
+ <View style={styles.sectionTitleRow}>
511
+ <Text style={styles.sectionTitle}>Remote JS Bundle</Text>
512
+ {diagData ? (
513
+ <View style={styles.swizzleBadge}>
514
+ <View style={[styles.swizzleDot, diagData.isDebugBuild ? styles.dotGreen : styles.dotRed]} />
515
+ <Text style={styles.swizzleBadgeText}>
516
+ {diagData.isDebugBuild ? 'debug build' : 'release: disabled'}
517
+ </Text>
518
+ </View>
519
+ ) : null}
520
+ </View>
509
521
  <Text style={styles.sectionDesc}>
510
- Apply this Metro host to React Native dev settings and reload the app.
522
+ Starts from the embedded bundle. After you apply a computer IP, hot-reloads from that
523
+ Metro. Debug builds only — use Reset to go back to the embedded bundle.
511
524
  </Text>
512
525
 
513
526
  {!metroUrls ? (
@@ -551,70 +564,66 @@ export function DevConnectTab({ snapshot, feature }: DebugFeatureRenderProps<Dev
551
564
  {!snapshot.nativeMetroAvailable ? (
552
565
  <Text style={styles.hint}>Native DevConnect requires pod install / Gradle sync and app rebuild.</Text>
553
566
  ) : null}
567
+
568
+ {metroReleaseBlocked ? (
569
+ <Text style={styles.diagWarning}>
570
+ ⚠ This is a Release build. Metro host switching is disabled — RN loads the embedded
571
+ bundle and strips the packager machinery in Release. Run a Debug build to switch hosts.
572
+ </Text>
573
+ ) : null}
554
574
  </View>
555
575
 
556
- {snapshot.nativeMetroAvailable ? (
576
+ {snapshot.nativeMetroAvailable && diagData ? (
557
577
  <View style={styles.section}>
558
578
  <TouchableOpacity
559
579
  style={styles.diagHeader}
560
- onPress={() => setDiagOpen((v) => !v)}
580
+ onPress={() => { setDiagOpen((v) => !v); refreshDiag(); }}
561
581
  activeOpacity={0.7}
562
582
  >
563
- <Text style={styles.sectionTitle}>Swizzle Diagnostics</Text>
583
+ <Text style={styles.sectionTitle}>iOS Bundle Status</Text>
564
584
  <Text style={styles.diagChevron}>{diagOpen ? '▲' : '▼'}</Text>
565
585
  </TouchableOpacity>
566
586
 
567
587
  {diagOpen ? (
568
588
  <View style={styles.diagCard}>
569
- {diagData ? (
570
- <>
571
- <View style={styles.diagRow}>
572
- <Text style={styles.diagKey}>swizzleInstalled</Text>
573
- <Text style={[styles.diagVal, diagData.swizzleInstalled ? styles.diagGood : styles.diagBad]}>
574
- {diagData.swizzleInstalled ? 'YES' : 'NO'}
575
- </Text>
576
- </View>
577
- <View style={styles.diagRow}>
578
- <Text style={styles.diagKey}>swizzleInvoked</Text>
579
- <Text style={[styles.diagVal, diagData.swizzleInvoked ? styles.diagGood : styles.diagWarn]}>
580
- {diagData.swizzleInvoked ? 'YES' : 'NO'}
581
- </Text>
582
- </View>
583
- <View style={styles.diagRow}>
584
- <Text style={styles.diagKey}>persistedHost</Text>
585
- <Text style={styles.diagVal}>{diagData.persistedMetroHost ?? '—'}</Text>
586
- </View>
587
- {diagData.log.length > 0 ? (
588
- <View style={styles.diagLog}>
589
- {diagData.log.map((entry, i) => (
590
- <Text key={i} style={styles.diagLogEntry}>{entry}</Text>
591
- ))}
592
- </View>
593
- ) : (
594
- <Text style={styles.diagEmpty}>No log entries yet.</Text>
595
- )}
596
- </>
597
- ) : (
598
- <Text style={styles.diagEmpty}>Tap Read to fetch diagnostics.</Text>
599
- )}
600
-
601
- <View style={styles.diagActions}>
602
- <TouchableOpacity
603
- style={[styles.diagBtn, diagBusy && styles.buttonDisabled]}
604
- onPress={readDiag}
605
- disabled={diagBusy}
606
- activeOpacity={0.75}
607
- >
608
- <Text style={styles.diagBtnText}>{diagBusy ? 'Reading…' : 'Read'}</Text>
609
- </TouchableOpacity>
610
- <TouchableOpacity
611
- style={[styles.diagBtn, styles.diagBtnSecondary]}
612
- onPress={clearDiag}
613
- activeOpacity={0.75}
614
- >
615
- <Text style={[styles.diagBtnText, styles.diagBtnSecondaryText]}>Clear</Text>
616
- </TouchableOpacity>
589
+ <View style={styles.diagRow}>
590
+ <Text style={styles.diagKey}>AppDelegate</Text>
591
+ <Text style={styles.diagVal}>{diagData.appDelegateClass}</Text>
592
+ </View>
593
+ <View style={styles.diagRow}>
594
+ <Text style={styles.diagKey}>packagerHost</Text>
595
+ <Text style={styles.diagVal}>{diagData.persistedMetroHost ?? '—'}</Text>
596
+ </View>
597
+ <View style={styles.diagRow}>
598
+ <Text style={styles.diagKey}>embedded</Text>
599
+ <Text style={[styles.diagVal, diagData.hasEmbeddedBundle ? styles.diagGood : styles.diagWarn]}>
600
+ {diagData.hasEmbeddedBundle ? 'main.jsbundle' : 'missing'}
601
+ </Text>
602
+ </View>
603
+ <View style={styles.diagRow}>
604
+ <Text style={styles.diagKey}>build</Text>
605
+ <Text style={[styles.diagVal, diagData.isDebugBuild ? styles.diagGood : styles.diagWarn]}>
606
+ {diagData.isDebugBuild ? 'Debug' : 'Release'}
607
+ </Text>
617
608
  </View>
609
+ <View style={styles.diagRow}>
610
+ <Text style={styles.diagKey}>embedded-first</Text>
611
+ <Text style={[styles.diagVal, diagData.embeddedFirstHookInstalled ? styles.diagGood : styles.diagWarn]}>
612
+ {diagData.embeddedFirstHookInstalled ? 'active' : 'inactive'}
613
+ </Text>
614
+ </View>
615
+ {!diagData.embeddedFirstHookInstalled ? (
616
+ <Text style={styles.diagWarning}>
617
+ ⚠ Embedded-first hook not active (bundleRoot=
618
+ {diagData.bundleRootHookInstalled ? 'Y' : 'N'}). Rebuild after pod install.
619
+ Without it, Debug may still try Metro on launch.
620
+ </Text>
621
+ ) : diagData.hasEmbeddedBundle === false ? (
622
+ <Text style={styles.diagWarning}>
623
+ ⚠ main.jsbundle is missing from the app package. Build with an embedded bundle
624
+ (e.g. export/bundle) or cold start cannot use offline JS.
625
+ </Text>
626
+ ) : null}
618
627
  </View>
619
628
  ) : null}
620
629
  </View>
@@ -723,6 +732,12 @@ const styles = StyleSheet.create({
723
732
  paddingVertical: 7,
724
733
  },
725
734
  urlText: { flex: 1, fontSize: 13, fontFamily: 'Courier', color: Colors.text },
735
+ sectionTitleRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 },
736
+ swizzleBadge: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 7, paddingVertical: 3, borderRadius: 10, backgroundColor: Colors.surface, borderWidth: 1, borderColor: Colors.border },
737
+ swizzleDot: { width: 6, height: 6, borderRadius: 3 },
738
+ dotGreen: { backgroundColor: '#34C759' },
739
+ dotRed: { backgroundColor: '#FF3B30' },
740
+ swizzleBadgeText: { fontSize: 11, color: Colors.textSecondary, fontFamily: 'Courier' },
726
741
  diagHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 },
727
742
  diagChevron: { fontSize: 12, color: Colors.textSecondary },
728
743
  diagCard: {
@@ -736,26 +751,11 @@ const styles = StyleSheet.create({
736
751
  diagKey: { fontSize: 12, color: Colors.textSecondary, fontFamily: 'Courier' },
737
752
  diagVal: { fontSize: 12, color: Colors.text, fontFamily: 'Courier', fontWeight: '600' },
738
753
  diagGood: { color: '#34C759' },
739
- diagBad: { color: '#FF3B30' },
740
754
  diagWarn: { color: '#FF9500' },
741
- diagLog: {
742
- marginTop: 8,
743
- borderTopWidth: StyleSheet.hairlineWidth,
744
- borderTopColor: Colors.border,
745
- paddingTop: 8,
746
- gap: 4,
747
- },
748
- diagLogEntry: { fontSize: 11, fontFamily: 'Courier', color: Colors.textSecondary, lineHeight: 16 },
749
- diagEmpty: { fontSize: 12, color: Colors.textLight, fontStyle: 'italic', paddingVertical: 4 },
750
- diagActions: { flexDirection: 'row', gap: 8, marginTop: 10 },
751
- diagBtn: {
752
- flex: 1,
753
- alignItems: 'center',
754
- paddingVertical: 8,
755
- borderRadius: 8,
756
- backgroundColor: Colors.primary,
755
+ diagWarning: {
756
+ marginTop: 10,
757
+ fontSize: 11,
758
+ color: '#FF9500',
759
+ lineHeight: 16,
757
760
  },
758
- diagBtnSecondary: { backgroundColor: 'transparent', borderWidth: 1, borderColor: Colors.border },
759
- diagBtnText: { fontSize: 13, fontWeight: '600', color: '#fff' },
760
- diagBtnSecondaryText: { color: Colors.primary },
761
761
  });
@@ -3,10 +3,15 @@ import { NativeModules } from 'react-native';
3
3
  import { buildMetroTarget } from './devConnectUtils';
4
4
 
5
5
  export interface NativeDiagnostics {
6
- log: string[];
7
- swizzleInstalled: boolean;
8
- swizzleInvoked: boolean;
9
6
  persistedMetroHost: string | null;
7
+ appDelegateClass: string;
8
+ // Metro host switching only works in Debug builds (RN strips the packager machinery in
9
+ // Release). False means the Remote JS Bundle controls should be disabled.
10
+ isDebugBuild: boolean;
11
+ hasEmbeddedBundle?: boolean;
12
+ embeddedFirstHookInstalled?: boolean;
13
+ packagerHookInstalled?: boolean;
14
+ bundleRootHookInstalled?: boolean;
10
15
  }
11
16
 
12
17
  interface DebugToolkitDevConnectNativeModule {
@@ -17,7 +22,6 @@ interface DebugToolkitDevConnectNativeModule {
17
22
  isDebugBuild?: () => Promise<boolean>;
18
23
  getPreference?: (key: string) => Promise<string | null>;
19
24
  getDiagnostics?: () => Promise<NativeDiagnostics>;
20
- clearDiagnostics?: () => Promise<void>;
21
25
  }
22
26
 
23
27
  type MetroBundleFailureReason =
@@ -174,15 +178,3 @@ export async function getNativeDiagnostics(): Promise<NativeDiagnostics | null>
174
178
  return null;
175
179
  }
176
180
  }
177
-
178
- export async function clearNativeDiagnostics(): Promise<void> {
179
- const nativeModule = getNativeModule();
180
- if (!nativeModule?.clearDiagnostics) {
181
- return;
182
- }
183
- try {
184
- await nativeModule.clearDiagnostics();
185
- } catch {
186
- // best-effort
187
- }
188
- }