react-native-inapp-inspector 1.1.28 → 1.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.java +29 -80
  2. package/dist/commonjs/components/AppHeaderLogo.js +12 -15
  3. package/dist/commonjs/components/ConsoleLogCard.js +31 -7
  4. package/dist/commonjs/components/CopyButton.js +15 -1
  5. package/dist/commonjs/components/DomainHeader.js +56 -28
  6. package/dist/commonjs/components/HighlightText.js +7 -1
  7. package/dist/commonjs/components/Inspector/AnalyticsTab.js +35 -21
  8. package/dist/commonjs/components/Inspector/InspectorHeader.js +17 -3
  9. package/dist/commonjs/components/Inspector/LogDetail.js +8 -5
  10. package/dist/commonjs/components/Inspector/MainScreen.js +4 -0
  11. package/dist/commonjs/components/Inspector/NetworkDetail.js +24 -24
  12. package/dist/commonjs/components/JsonViewer.js +25 -3
  13. package/dist/commonjs/components/LogCard.js +103 -11
  14. package/dist/commonjs/components/Toast.d.ts +3 -0
  15. package/dist/commonjs/components/Toast.js +152 -0
  16. package/dist/commonjs/components/TouchableScale.d.ts +1 -0
  17. package/dist/commonjs/components/TouchableScale.js +3 -3
  18. package/dist/commonjs/components/TreeNode.js +1 -1
  19. package/dist/commonjs/constants/index.js +6 -6
  20. package/dist/commonjs/constants/version.d.ts +1 -1
  21. package/dist/commonjs/constants/version.js +1 -1
  22. package/dist/commonjs/helpers/index.d.ts +4 -0
  23. package/dist/commonjs/helpers/index.js +91 -92
  24. package/dist/commonjs/helpers/toast.d.ts +4 -0
  25. package/dist/commonjs/helpers/toast.js +20 -0
  26. package/dist/commonjs/i18n/locales/en.json +4 -1
  27. package/dist/commonjs/styles/index.d.ts +60 -14
  28. package/dist/commonjs/styles/index.js +71 -26
  29. package/dist/esm/components/AppHeaderLogo.js +12 -15
  30. package/dist/esm/components/ConsoleLogCard.js +33 -9
  31. package/dist/esm/components/CopyButton.js +15 -1
  32. package/dist/esm/components/DomainHeader.js +56 -28
  33. package/dist/esm/components/HighlightText.js +7 -1
  34. package/dist/esm/components/Inspector/AnalyticsTab.js +36 -22
  35. package/dist/esm/components/Inspector/InspectorHeader.js +18 -4
  36. package/dist/esm/components/Inspector/LogDetail.js +10 -7
  37. package/dist/esm/components/Inspector/MainScreen.js +4 -0
  38. package/dist/esm/components/Inspector/NetworkDetail.js +25 -25
  39. package/dist/esm/components/JsonViewer.js +25 -3
  40. package/dist/esm/components/LogCard.js +105 -13
  41. package/dist/esm/components/Toast.d.ts +3 -0
  42. package/dist/esm/components/Toast.js +117 -0
  43. package/dist/esm/components/TouchableScale.d.ts +1 -0
  44. package/dist/esm/components/TouchableScale.js +3 -3
  45. package/dist/esm/components/TreeNode.js +1 -1
  46. package/dist/esm/constants/index.js +6 -6
  47. package/dist/esm/constants/version.d.ts +1 -1
  48. package/dist/esm/constants/version.js +1 -1
  49. package/dist/esm/helpers/index.d.ts +4 -0
  50. package/dist/esm/helpers/index.js +86 -92
  51. package/dist/esm/helpers/toast.d.ts +4 -0
  52. package/dist/esm/helpers/toast.js +15 -0
  53. package/dist/esm/i18n/locales/en.json +4 -1
  54. package/dist/esm/styles/index.d.ts +60 -14
  55. package/dist/esm/styles/index.js +71 -26
  56. package/ios/NetworkInspectorModule.m +0 -10
  57. package/package.json +4 -1
@@ -1,8 +1,5 @@
1
1
  package com.inappinspector;
2
2
 
3
- import android.content.ClipData;
4
- import android.content.ClipboardManager;
5
- import android.content.Context;
6
3
  import android.os.Handler;
7
4
  import android.os.Looper;
8
5
  import android.util.Log;
@@ -31,7 +28,6 @@ public class NetworkInspectorModule extends ReactContextBaseJavaModule {
31
28
  public NetworkInspectorModule(ReactApplicationContext reactContext) {
32
29
  super(reactContext);
33
30
  this.reactContext = reactContext;
34
- setupNativeCrashProtection();
35
31
  }
36
32
 
37
33
  @NonNull
@@ -41,55 +37,39 @@ public class NetworkInspectorModule extends ReactContextBaseJavaModule {
41
37
  }
42
38
 
43
39
  private void setupNativeCrashProtection() {
44
- if (isProtectionEnabled) return;
45
- isProtectionEnabled = true;
46
-
47
- try {
48
- defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
49
- Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
50
- @Override
51
- public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
52
- try {
53
- StringWriter sw = new StringWriter();
54
- PrintWriter pw = new PrintWriter(sw);
55
- throwable.printStackTrace(pw);
56
- String stackTrace = sw.toString();
57
- String message = throwable.getMessage() != null ? throwable.getMessage() : throwable.toString();
58
-
59
- Log.e(TAG, "Intercepted Android Native Crash: " + message, throwable);
60
-
61
- sendCrashEventToJS(message, stackTrace, thread.getName());
62
- } catch (Exception e) {
63
- Log.e(TAG, "Failed to dispatch native crash event", e);
64
- }
65
-
66
- // Keep main looper processing to prevent instant process exit
67
- if (Looper.myLooper() != null) {
68
- while (true) {
69
- try {
70
- Looper.loop();
71
- } catch (Throwable inner) {
72
- Log.e(TAG, "Caught inner loop exception", inner);
73
- }
74
- }
75
- }
76
- }
77
- });
78
- } catch (Exception e) {
79
- Log.e(TAG, "Error installing uncaught exception handler", e);
40
+ if (isProtectionEnabled) {
41
+ return;
80
42
  }
43
+ defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
44
+ Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
45
+ try {
46
+ handleNativeCrash(thread, throwable);
47
+ } catch (Exception e) {
48
+ Log.e(TAG, "Error in native crash handler", e);
49
+ } finally {
50
+ if (defaultHandler != null) {
51
+ defaultHandler.uncaughtException(thread, throwable);
52
+ }
53
+ }
54
+ });
55
+ isProtectionEnabled = true;
56
+ Log.i(TAG, "Native crash protection enabled");
81
57
  }
82
58
 
83
- private void sendCrashEventToJS(String message, String stackTrace, String threadName) {
59
+ private void handleNativeCrash(Thread thread, Throwable throwable) {
84
60
  try {
85
- if (reactContext != null && reactContext.hasActiveReactInstance()) {
86
- WritableMap params = Arguments.createMap();
87
- params.putString("platform", "android");
88
- params.putString("message", message);
89
- params.putString("stack", stackTrace);
90
- params.putString("thread", threadName);
91
- params.putDouble("timestamp", System.currentTimeMillis());
92
-
61
+ StringWriter sw = new StringWriter();
62
+ PrintWriter pw = new PrintWriter(sw);
63
+ throwable.printStackTrace(pw);
64
+ String stackTrace = sw.toString();
65
+
66
+ WritableMap params = Arguments.createMap();
67
+ params.putString("error", throwable.getMessage() != null ? throwable.getMessage() : "Unknown native crash");
68
+ params.putString("name", throwable.getClass().getName());
69
+ params.putString("stack", stackTrace);
70
+ params.putDouble("timestamp", System.currentTimeMillis());
71
+
72
+ if (reactContext.hasActiveReactInstance()) {
93
73
  reactContext
94
74
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
95
75
  .emit("onNativeCrash", params);
@@ -105,37 +85,6 @@ public class NetworkInspectorModule extends ReactContextBaseJavaModule {
105
85
  promise.resolve(true);
106
86
  }
107
87
 
108
- @ReactMethod
109
- public void copyToClipboard(String text, Promise promise) {
110
- try {
111
- Handler mainHandler = new Handler(Looper.getMainLooper());
112
- mainHandler.post(() -> {
113
- try {
114
- ClipboardManager clipboard = (ClipboardManager) reactContext.getSystemService(Context.CLIPBOARD_SERVICE);
115
- if (clipboard != null) {
116
- ClipData clip = ClipData.newPlainText("NetworkInspector", text != null ? text : "");
117
- clipboard.setPrimaryClip(clip);
118
- if (promise != null) {
119
- promise.resolve(true);
120
- }
121
- return;
122
- }
123
- if (promise != null) {
124
- promise.resolve(false);
125
- }
126
- } catch (Exception e) {
127
- if (promise != null) {
128
- promise.reject("CLIPBOARD_ERROR", e.getMessage(), e);
129
- }
130
- }
131
- });
132
- } catch (Exception e) {
133
- if (promise != null) {
134
- promise.reject("CLIPBOARD_ERROR", e.getMessage(), e);
135
- }
136
- }
137
- }
138
-
139
88
  @ReactMethod
140
89
  public void addListener(String eventName) {
141
90
  // Required for RN built-in Event Emitter
@@ -40,13 +40,12 @@ exports.AppHeaderLogo = void 0;
40
40
  const react_1 = __importStar(require("react"));
41
41
  const react_native_1 = require("react-native");
42
42
  const BrandSquareIcon_1 = __importDefault(require("./BrandSquareIcon"));
43
- const AppColors_1 = require("../styles/AppColors");
44
43
  const AppHeaderLogo = ({ size = 46, customIcon, }) => {
45
44
  const [loadError, setLoadError] = (0, react_1.useState)(false);
46
- // 1. If customIcon is explicitly provided, render it directly
45
+ // 1. If customIcon is explicitly provided, render it directly edge-to-edge
47
46
  if (customIcon) {
48
47
  return (<react_native_1.View style={[logoStyles.container, { width: size, height: size }]}>
49
- <react_native_1.Image source={customIcon} style={{ width: size - 4, height: size - 4, borderRadius: 10 }} resizeMode="cover"/>
48
+ <react_native_1.Image source={customIcon} style={logoStyles.image} resizeMode="cover"/>
50
49
  </react_native_1.View>);
51
50
  }
52
51
  // 2. Dynamically query installed native app icon (AppIcon on iOS, ic_launcher on Android)
@@ -57,29 +56,27 @@ const AppHeaderLogo = ({ size = 46, customIcon, }) => {
57
56
  default: { uri: 'AppIcon' },
58
57
  });
59
58
  return (<react_native_1.View style={[logoStyles.container, { width: size, height: size }]}>
60
- <react_native_1.Image source={nativeSource} style={{ width: size - 4, height: size - 4, borderRadius: 10 }} resizeMode="cover" onError={() => setLoadError(true)}/>
59
+ <react_native_1.Image source={nativeSource} style={logoStyles.image} resizeMode="cover" onError={() => setLoadError(true)}/>
61
60
  </react_native_1.View>);
62
61
  }
63
62
  // 3. Fallback: Inspector's Signature Owl Brand Logo
64
63
  return (<react_native_1.View style={[logoStyles.container, { width: size, height: size }]}>
65
- <BrandSquareIcon_1.default size={size - 4}/>
64
+ <BrandSquareIcon_1.default size={size}/>
66
65
  </react_native_1.View>);
67
66
  };
68
67
  exports.AppHeaderLogo = AppHeaderLogo;
69
68
  const logoStyles = react_native_1.StyleSheet.create({
70
69
  container: {
70
+ borderRadius: 10,
71
+ overflow: 'hidden',
72
+ backgroundColor: 'transparent',
71
73
  alignItems: 'center',
72
74
  justifyContent: 'center',
73
- borderRadius: 12,
74
- backgroundColor: `${AppColors_1.AppColors.white}24`,
75
- borderWidth: 1.5,
76
- borderColor: `${AppColors_1.AppColors.white}4D`,
77
- overflow: 'hidden',
78
- shadowColor: AppColors_1.AppColors.primaryBlack,
79
- shadowOffset: { width: 0, height: 2 },
80
- shadowOpacity: 0.15,
81
- shadowRadius: 4,
82
- elevation: 3,
75
+ },
76
+ image: {
77
+ width: '100%',
78
+ height: '100%',
79
+ borderRadius: 10,
83
80
  },
84
81
  });
85
82
  exports.default = exports.AppHeaderLogo;
@@ -232,18 +232,34 @@ exports.ConsoleLogCard = react_1.default.memo(function ConsoleLogCard({ item, se
232
232
  <react_native_1.Text style={styles.footerText}>{(0, helpers_1.formatTime)(item.timestamp)}</react_native_1.Text>
233
233
  {parsedCaller && (<>
234
234
  <react_native_1.View style={styles.footerDot}/>
235
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, maxWidth: 170 }}>
235
+ <react_native_1.Pressable onPress={e => {
236
+ e.stopPropagation?.();
237
+ (0, helpers_1.openInVSCode)(parsedCaller.rawFilePath ||
238
+ parsedCaller.fullPath ||
239
+ parsedCaller.fileName, parsedCaller.lineNumber, parsedCaller.columnNumber);
240
+ }} hitSlop={8} style={{
241
+ flexDirection: 'row',
242
+ alignItems: 'center',
243
+ gap: 4,
244
+ maxWidth: 185,
245
+ backgroundColor: `${AppColors_1.AppColors.sky600}10`,
246
+ borderColor: `${AppColors_1.AppColors.sky600}2B`,
247
+ borderWidth: 1,
248
+ borderRadius: 5,
249
+ paddingHorizontal: 5,
250
+ paddingVertical: 1.5,
251
+ }}>
236
252
  {parsedCaller.fileExt && parsedCaller.fileExt !== 'other' && (<react_native_1.View style={{
237
253
  backgroundColor: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
238
- ? `${AppColors_1.AppColors.brandPurple}18`
239
- : `${AppColors_1.AppColors.teal600}18`,
254
+ ? `${AppColors_1.AppColors.brandPurple}22`
255
+ : `${AppColors_1.AppColors.teal600}22`,
240
256
  borderRadius: 3,
241
257
  paddingHorizontal: 3.5,
242
- paddingVertical: 1,
258
+ paddingVertical: 0.5,
243
259
  }}>
244
260
  <react_native_1.Text style={{
245
261
  fontFamily: AppFonts_1.AppFonts.interBold,
246
- fontSize: 8.5,
262
+ fontSize: 8,
247
263
  color: parsedCaller.fileExt === 'tsx' || parsedCaller.fileExt === 'ts'
248
264
  ? AppColors_1.AppColors.brandPurple
249
265
  : AppColors_1.AppColors.teal600,
@@ -251,11 +267,19 @@ exports.ConsoleLogCard = react_1.default.memo(function ConsoleLogCard({ item, se
251
267
  {parsedCaller.fileExt.toUpperCase()}
252
268
  </react_native_1.Text>
253
269
  </react_native_1.View>)}
254
- <react_native_1.Text style={[styles.footerText, styles.footerCaller]} numberOfLines={1} ellipsizeMode="middle">
270
+ <react_native_1.Text style={[
271
+ styles.footerText,
272
+ {
273
+ fontFamily: AppFonts_1.AppFonts.interBold,
274
+ color: AppColors_1.AppColors.sky600,
275
+ fontSize: 9.5,
276
+ },
277
+ ]} numberOfLines={1} ellipsizeMode="middle">
255
278
  {parsedCaller.fileName}
256
279
  {parsedCaller.lineNumber ? `:${parsedCaller.lineNumber}` : ''}
257
280
  </react_native_1.Text>
258
- </react_native_1.View>
281
+ <NetworkIcons_1.ExternalLinkIcon color={AppColors_1.AppColors.sky600} size={8.5}/>
282
+ </react_native_1.Pressable>
259
283
  </>)}
260
284
  </react_native_1.View>
261
285
 
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const react_1 = __importStar(require("react"));
40
+ const react_native_1 = require("react-native");
40
41
  // Components
41
42
  const TouchableScale_1 = __importDefault(require("./TouchableScale"));
42
43
  // Helpers
@@ -54,6 +55,19 @@ const CopyButton = react_1.default.memo(({ value, label, iconType = 'copy' }) =>
54
55
  setCopied(true);
55
56
  setTimeout(() => setCopied(false), 1200);
56
57
  }, [value, label]);
58
+ const handleLongPress = (0, react_1.useCallback)(() => {
59
+ try {
60
+ const resolvedValue = typeof value === 'function' ? value() : value;
61
+ const textToShare = typeof resolvedValue === 'string'
62
+ ? resolvedValue
63
+ : JSON.stringify(resolvedValue, null, 2);
64
+ react_native_1.Share.share({
65
+ message: textToShare,
66
+ title: label,
67
+ });
68
+ }
69
+ catch { }
70
+ }, [value, label]);
57
71
  const containerStyle = [
58
72
  styles_1.default.iconSquareBtn,
59
73
  copied && styles_1.default.iconSquareBtnSuccess,
@@ -63,7 +77,7 @@ const CopyButton = react_1.default.memo(({ value, label, iconType = 'copy' }) =>
63
77
  : iconType === 'fetch'
64
78
  ? NetworkIcons_1.FetchIcon
65
79
  : NetworkIcons_1.CopyIcon;
66
- return (<TouchableScale_1.default onPress={handlePress} hitSlop={12} style={containerStyle}>
80
+ return (<TouchableScale_1.default onPress={handlePress} onLongPress={handleLongPress} hitSlop={12} style={containerStyle}>
67
81
  {copied ? (<NetworkIcons_1.CheckIcon color={AppColors_1.AppColors.greenColor} size={14}/>) : (<IconComponent color={AppColors_1.AppColors.grayTextWeak} size={14}/>)}
68
82
  </TouchableScale_1.default>);
69
83
  });
@@ -40,6 +40,7 @@ const react_1 = __importStar(require("react"));
40
40
  const react_native_1 = require("react-native");
41
41
  // Stylesheet
42
42
  const AppColors_1 = require("../styles/AppColors");
43
+ const AppFonts_1 = require("../styles/AppFonts");
43
44
  // Helpers
44
45
  const helpers_1 = require("../helpers");
45
46
  // Assets
@@ -73,10 +74,61 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
73
74
  <react_native_1.View style={[styles_1.default.domainIconWrap, { backgroundColor: `${color}18` }]}>
74
75
  <NetworkIcons_1.ScreenIcon color={color} size={14}/>
75
76
  </react_native_1.View>
76
- <react_native_1.View style={{ flex: 1, justifyContent: 'center' }}>
77
- <react_native_1.Text style={styles_1.default.domainTitleText} numberOfLines={1}>
78
- {pageName}
79
- </react_native_1.Text>
77
+ <react_native_1.View style={{ flex: 1, justifyContent: 'center', minWidth: 0 }}>
78
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>
79
+ <react_native_1.Text style={[styles_1.default.domainTitleText, { flexShrink: 1 }]} numberOfLines={1}>
80
+ {pageName}
81
+ </react_native_1.Text>
82
+ {total > 0 && (<react_native_1.View style={{
83
+ flexDirection: 'row',
84
+ alignItems: 'center',
85
+ gap: 4,
86
+ backgroundColor: stats.failed > 0
87
+ ? '#FEE2E2'
88
+ : stats.loading > 0
89
+ ? '#FEF3C7'
90
+ : '#D1FAE5',
91
+ paddingHorizontal: 5,
92
+ paddingVertical: 1.5,
93
+ borderRadius: 4,
94
+ }}>
95
+ <react_native_1.View style={{
96
+ width: 24,
97
+ height: 3.5,
98
+ borderRadius: 2,
99
+ backgroundColor: '#E5E7EB',
100
+ flexDirection: 'row',
101
+ overflow: 'hidden',
102
+ }}>
103
+ {pctSuccess > 0 && (<react_native_1.View style={{
104
+ width: `${pctSuccess}%`,
105
+ height: '100%',
106
+ backgroundColor: '#10B981',
107
+ }}/>)}
108
+ {pctFailed > 0 && (<react_native_1.View style={{
109
+ width: `${pctFailed}%`,
110
+ height: '100%',
111
+ backgroundColor: '#EF4444',
112
+ }}/>)}
113
+ {pctLoading > 0 && (<react_native_1.View style={{
114
+ width: `${pctLoading}%`,
115
+ height: '100%',
116
+ backgroundColor: '#F59E0B',
117
+ }}/>)}
118
+ </react_native_1.View>
119
+ <react_native_1.Text style={{
120
+ fontFamily: AppFonts_1.AppFonts.interBold,
121
+ fontSize: 8.5,
122
+ color: stats.failed > 0
123
+ ? '#991B1B'
124
+ : stats.loading > 0
125
+ ? '#92400E'
126
+ : '#065F46',
127
+ }}>
128
+ {pctSuccess}%
129
+ </react_native_1.Text>
130
+ </react_native_1.View>)}
131
+ </react_native_1.View>
80
132
  <react_native_1.Text style={styles_1.default.domainSummaryText} numberOfLines={1}>
81
133
  {(0, helpers_1.formatDateTime)(timestamp)}
82
134
  </react_native_1.Text>
@@ -132,30 +184,6 @@ const DomainHeader = ({ pageName, color, stats, activeFilters, onToggleFilter, i
132
184
  })}
133
185
  </react_native_1.View>
134
186
  </react_native_1.View>
135
-
136
- {total > 0 && (<react_native_1.View style={styles_1.default.domainProgressTrack}>
137
- {pctSuccess > 0 && (<react_native_1.View style={[
138
- styles_1.default.domainProgressSegment,
139
- {
140
- width: `${pctSuccess}%`,
141
- backgroundColor: AppColors_1.AppColors.greenColor,
142
- },
143
- ]}/>)}
144
- {pctFailed > 0 && (<react_native_1.View style={[
145
- styles_1.default.domainProgressSegment,
146
- {
147
- width: `${pctFailed}%`,
148
- backgroundColor: AppColors_1.AppColors.errorColor,
149
- },
150
- ]}/>)}
151
- {pctLoading > 0 && (<react_native_1.View style={[
152
- styles_1.default.domainProgressSegment,
153
- {
154
- width: `${pctLoading}%`,
155
- backgroundColor: AppColors_1.AppColors.darkOrange,
156
- },
157
- ]}/>)}
158
- </react_native_1.View>)}
159
187
  </react_native_1.Pressable>);
160
188
  };
161
189
  exports.default = DomainHeader;
@@ -8,6 +8,12 @@ const react_native_1 = require("react-native");
8
8
  // Helpers
9
9
  const helpers_1 = require("../helpers");
10
10
  const HighlightText = react_1.default.memo(function HighlightText({ text, search, style, highlightStyle, detectLinks, numberOfLines, ...rest }) {
11
+ // Optimization: For huge strings (> 20KB) with no active search, bypass all splitting/regex overhead
12
+ if (!text || (text.length > 20000 && !search && !detectLinks)) {
13
+ return (<react_native_1.Text style={style} numberOfLines={numberOfLines} {...rest}>
14
+ {text}
15
+ </react_native_1.Text>);
16
+ }
11
17
  // Regex to detect absolute URLs
12
18
  const URL_REGEX = /(https?:\/\/[^\s]+)/g;
13
19
  const renderNormalOrHighlighted = (subText, keyPrefix) => {
@@ -22,7 +28,7 @@ const HighlightText = react_1.default.memo(function HighlightText({ text, search
22
28
  </react_native_1.Text>) : (<react_native_1.Text key={i}>{part}</react_native_1.Text>))}
23
29
  </react_native_1.Text>);
24
30
  };
25
- if (detectLinks && text) {
31
+ if (detectLinks && text && text.length < 20000) {
26
32
  const parts = text.split(URL_REGEX);
27
33
  return (<react_native_1.Text style={style} numberOfLines={numberOfLines} {...rest}>
28
34
  {parts.map((part, i) => {
@@ -337,41 +337,55 @@ const AnalyticsHeader = react_1.default.memo(() => {
337
337
  </react_native_1.View>
338
338
  </react_native_1.View>
339
339
 
340
- {/* ── High Precision SVG Spline Area Graph ── */}
340
+ {/* ── Crashlytics-Style Multi-Bar Histogram with Trendline Overlay ── */}
341
341
  <react_native_1.View style={{ height: chartHeight, position: 'relative' }}>
342
342
  <react_native_svg_1.default width="100%" height={chartHeight} viewBox={`0 0 ${svgWidth} ${chartHeight}`}>
343
343
  <react_native_svg_1.Defs>
344
- <react_native_svg_1.LinearGradient id="telemetryAreaGrad" x1="0" y1="0" x2="0" y2="1">
345
- <react_native_svg_1.Stop offset="0%" stopColor={AppColors_1.AppColors.brandPurple} stopOpacity="0.4"/>
346
- <react_native_svg_1.Stop offset="80%" stopColor={AppColors_1.AppColors.brandPurple} stopOpacity="0.05"/>
347
- <react_native_svg_1.Stop offset="100%" stopColor={AppColors_1.AppColors.brandPurple} stopOpacity="0.0"/>
344
+ <react_native_svg_1.LinearGradient id="crashlyticsBarGrad" x1="0" y1="0" x2="0" y2="1">
345
+ <react_native_svg_1.Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.9"/>
346
+ <react_native_svg_1.Stop offset="100%" stopColor="#6366F1" stopOpacity="0.6"/>
347
+ </react_native_svg_1.LinearGradient>
348
+ <react_native_svg_1.LinearGradient id="crashlyticsAreaGrad" x1="0" y1="0" x2="0" y2="1">
349
+ <react_native_svg_1.Stop offset="0%" stopColor="#8B5CF6" stopOpacity="0.25"/>
350
+ <react_native_svg_1.Stop offset="80%" stopColor="#8B5CF6" stopOpacity="0.04"/>
351
+ <react_native_svg_1.Stop offset="100%" stopColor="#8B5CF6" stopOpacity="0.0"/>
348
352
  </react_native_svg_1.LinearGradient>
349
353
  </react_native_svg_1.Defs>
350
354
 
351
- {/* Baseline & Grid lines */}
352
- <react_native_svg_1.Line x1="8" y1={chartHeight - 6} x2={svgWidth - 8} y2={chartHeight - 6} stroke={AppColors_1.AppColors.dividerColor} strokeWidth="1"/>
353
- <react_native_svg_1.Line x1="8" y1={chartHeight / 2} x2={svgWidth - 8} y2={chartHeight / 2} stroke={AppColors_1.AppColors.dividerColor} strokeWidth="0.8" strokeDasharray="3,3"/>
354
-
355
- {/* Glowing Area Fill */}
356
- <react_native_svg_1.Path d={histogramData.areaPath} fill="url(#telemetryAreaGrad)"/>
355
+ {/* Subtle Grid Guidelines */}
356
+ <react_native_svg_1.Line x1="6" y1={chartHeight - 6} x2={svgWidth - 6} y2={chartHeight - 6} stroke="#E2E8F0" strokeWidth="1"/>
357
+ <react_native_svg_1.Line x1="6" y1={chartHeight / 2} x2={svgWidth - 6} y2={chartHeight / 2} stroke="#F1F5F9" strokeWidth="1" strokeDasharray="4,4"/>
357
358
 
358
- {/* Smooth Spline Wave Stroke */}
359
- <react_native_svg_1.Path d={histogramData.linePath} fill="none" stroke={AppColors_1.AppColors.brandPurple} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
359
+ {/* Glowing Area Underlay */}
360
+ <react_native_svg_1.Path d={histogramData.areaPath} fill="url(#crashlyticsAreaGrad)"/>
360
361
 
361
- {/* Data Points & Active Bucket Indicators */}
362
+ {/* Vertical Crashlytics Histogram Bars */}
362
363
  {histogramData.buckets.map((b, idx) => {
363
364
  const isSelected = selectedBucketIdx === idx;
364
- return (<react_native_svg_1.G key={b.index}>
365
- {isSelected && (<react_native_svg_1.Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke={AppColors_1.AppColors.brandPurple} strokeWidth="1.2" strokeDasharray="2,2"/>)}
365
+ const barWidth = 10;
366
+ const barX = b.x - barWidth / 2;
367
+ const barHeight = Math.max(3, chartHeight - 8 - b.y);
368
+ const barY = chartHeight - 8 - barHeight;
369
+ return (<react_native_svg_1.G key={`bar-${b.index}`}>
370
+ {/* Background slot */}
371
+ <react_native_svg_1.Rect x={barX} y={6} width={barWidth} height={chartHeight - 14} rx={3} fill={isSelected ? '#EDE9FE' : '#F8FAFC'}/>
366
372
 
367
- {b.total > 0 && (<react_native_svg_1.Circle cx={b.x} cy={b.y} r={isSelected ? 4.5 : 2.8} fill={isSelected ? AppColors_1.AppColors.brandPurple : AppColors_1.AppColors.white} stroke={AppColors_1.AppColors.brandPurple} strokeWidth={isSelected ? 2 : 1.5}/>)}
373
+ {/* Active Event Bar */}
374
+ {b.total > 0 && (<react_native_svg_1.Rect x={barX} y={barY} width={barWidth} height={barHeight} rx={3} fill="url(#crashlyticsBarGrad)"/>)}
368
375
 
369
- {b.isLatest && (<react_native_svg_1.G>
370
- <react_native_svg_1.Circle cx={b.x} cy={b.y} r="6.5" fill={AppColors_1.AppColors.brandPurple} opacity="0.22"/>
371
- <react_native_svg_1.Circle cx={b.x} cy={b.y} r="3" fill={AppColors_1.AppColors.brandPurple}/>
372
- </react_native_svg_1.G>)}
376
+ {/* Selected Cursor Line */}
377
+ {isSelected && (<react_native_svg_1.Line x1={b.x} y1={4} x2={b.x} y2={chartHeight - 6} stroke="#8B5CF6" strokeWidth="1.5" strokeDasharray="2,2"/>)}
373
378
  </react_native_svg_1.G>);
374
379
  })}
380
+
381
+ {/* Crashlytics Apex Trendline */}
382
+ <react_native_svg_1.Path d={histogramData.linePath} fill="none" stroke="#8B5CF6" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
383
+
384
+ {/* Pulsing Dot on Current Active Time Bucket */}
385
+ {histogramData.buckets.length > 0 && (<react_native_svg_1.G>
386
+ <react_native_svg_1.Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="5.5" fill="#8B5CF6" opacity="0.25"/>
387
+ <react_native_svg_1.Circle cx={histogramData.buckets[histogramData.buckets.length - 1].x} cy={histogramData.buckets[histogramData.buckets.length - 1].y} r="3" fill="#7C3AED"/>
388
+ </react_native_svg_1.G>)}
375
389
  </react_native_svg_1.default>
376
390
 
377
391
  {/* Invisible Touch Columns for Interactive Bucket Selection */}
@@ -146,9 +146,9 @@ const InspectorHeader = react_1.default.memo(() => {
146
146
  flex: 1,
147
147
  }}>
148
148
  <AppHeaderLogo_1.default size={46} customIcon={appIcon}/>
149
- <react_native_1.View style={{ gap: 2, flex: 1 }}>
150
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
151
- <react_native_1.Text style={[styles_1.default.headerTitle]} numberOfLines={1}>
149
+ <react_native_1.View style={{ gap: 2, flex: 1, minWidth: 0 }}>
150
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, flexWrap: 'nowrap', minWidth: 0 }}>
151
+ <react_native_1.Text style={[styles_1.default.headerTitle, { flexShrink: 1 }]} numberOfLines={1}>
152
152
  {(0, helpers_1.getAppName)()}
153
153
  </react_native_1.Text>
154
154
  <react_native_1.View style={[
@@ -372,6 +372,20 @@ const InspectorHeader = react_1.default.memo(() => {
372
372
  : '—'}
373
373
  </react_native_1.Text>
374
374
  </react_native_1.View>
375
+ {selected.response != null && (<react_native_1.View style={{
376
+ flexDirection: 'row',
377
+ alignItems: 'center',
378
+ gap: 4,
379
+ paddingHorizontal: 8,
380
+ paddingVertical: 3,
381
+ borderRadius: 20,
382
+ backgroundColor: `${AppColors_1.AppColors.white}29`,
383
+ }}>
384
+ <NetworkIcons_1.SizeIcon color={AppColors_1.AppColors.white} size={11}/>
385
+ <react_native_1.Text style={styles_1.default.headerSubTitle}>
386
+ {(0, helpers_1.getSize)(selected.response)}
387
+ </react_native_1.Text>
388
+ </react_native_1.View>)}
375
389
  </react_native_1.View>
376
390
  </react_native_1.View>) : activeTab === 'analytics' && selectedEvent != null ? (<react_native_1.View style={styles_1.default.headerDetailCenter}>
377
391
  <react_native_1.View style={styles_1.default.headerDetailRow}>
@@ -307,16 +307,18 @@ const LogDetail = react_1.default.memo(() => {
307
307
  : originFrame?.fileName || t('console.consoleLog')}
308
308
  </react_native_1.Text>
309
309
 
310
- {originFrame?.fileName && (<react_native_1.View style={[
310
+ {originFrame?.fileName && (<react_native_1.Pressable onPress={() => (0, helpers_1.openInVSCode)(originFrame.rawFilePath ||
311
+ originFrame.fullPath ||
312
+ originFrame.fileName, originFrame.lineNumber, originFrame.columnNumber)} hitSlop={8} style={[
311
313
  styles_1.default.metaChip,
312
314
  {
313
- backgroundColor: `${AppColors_1.AppColors.brandPurple}12`,
314
- borderColor: `${AppColors_1.AppColors.brandPurple}30`,
315
+ backgroundColor: `${AppColors_1.AppColors.brandPurple}15`,
316
+ borderColor: `${AppColors_1.AppColors.brandPurple}40`,
315
317
  paddingHorizontal: 7,
316
318
  paddingVertical: 2,
317
319
  },
318
320
  ]}>
319
- <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 3, flexWrap: 'wrap', flexShrink: 1 }}>
321
+ <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 4, flexShrink: 1 }}>
320
322
  <NetworkIcons_1.DocIcon color={AppColors_1.AppColors.brandPurple} size={10}/>
321
323
  <react_native_1.Text style={[
322
324
  styles_1.default.metaChipText,
@@ -326,8 +328,9 @@ const LogDetail = react_1.default.memo(() => {
326
328
  {originFrame.lineNumber ? `:${originFrame.lineNumber}` : ''}
327
329
  {originFrame.columnNumber ? `:${originFrame.columnNumber}` : ''}
328
330
  </react_native_1.Text>
331
+ <NetworkIcons_1.ExternalLinkIcon color={AppColors_1.AppColors.brandPurple} size={9}/>
329
332
  </react_native_1.View>
330
- </react_native_1.View>)}
333
+ </react_native_1.Pressable>)}
331
334
  </react_native_1.View>
332
335
 
333
336
  {/* Enhanced metadata metrics row */}
@@ -56,6 +56,7 @@ const PerformanceTab_1 = __importDefault(require("./PerformanceTab"));
56
56
  const CrashTab_1 = __importDefault(require("./CrashTab"));
57
57
  const CrashDetail_1 = __importDefault(require("./CrashDetail"));
58
58
  const SettingsPanel_1 = __importDefault(require("./SettingsPanel"));
59
+ const Toast_1 = __importDefault(require("../Toast"));
59
60
  const styles_1 = __importDefault(require("../../styles"));
60
61
  const AppColors_1 = require("../../styles/AppColors");
61
62
  const NavigationTracker_1 = __importDefault(require("./NavigationTracker"));
@@ -153,6 +154,9 @@ const MainScreen = () => {
153
154
  ]}>
154
155
  <SettingsPanel_1.default />
155
156
  </react_native_1.View>)}
157
+
158
+ {/* Bottom floating toast notification */}
159
+ <Toast_1.default />
156
160
  </react_native_1.View>
157
161
  </react_native_1.View>
158
162
  </ErrorBoundary_1.default>)}