react-native-inapp-inspector 1.0.10 → 1.0.12

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.
package/README.md CHANGED
@@ -21,6 +21,14 @@ A premium, self-contained, and interactive in-app debugger for React Native appl
21
21
 
22
22
  ---
23
23
 
24
+ ## Video Walkthrough
25
+
26
+ Watch the library in action, demonstrating network inspection, Redux state tree analysis, WebView debugging, and console logging:
27
+
28
+ [🎬 Download or watch the Video Walkthrough](https://raw.githubusercontent.com/vengatmacuser/react-native-inapp-inspector/main/example/guidance/Video-WalkThrough.mp4)
29
+
30
+ ---
31
+
24
32
  ## Installation
25
33
 
26
34
  Install the package as a development dependency in your React Native project:
@@ -4,3 +4,14 @@ export declare const ReduxTreeView: ({ state, lastActionMap, search, }: {
4
4
  lastActionMap: Record<string, any>;
5
5
  search?: string;
6
6
  }) => React.JSX.Element;
7
+ export declare const ReduxActionTimeline: ({ history, onClear, search, }: {
8
+ history: Array<{
9
+ id: number;
10
+ type: string;
11
+ payload: any;
12
+ timestamp: string;
13
+ affectedSlices: string[];
14
+ }>;
15
+ onClear: () => void;
16
+ search?: string;
17
+ }) => React.JSX.Element;
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ReduxTreeView = void 0;
36
+ exports.ReduxActionTimeline = exports.ReduxTreeView = void 0;
37
37
  const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const AppColors_1 = require("../styles/AppColors");
@@ -210,6 +210,81 @@ const ReduxTreeView = ({ state, lastActionMap, search, }) => {
210
210
  </react_native_1.View>);
211
211
  };
212
212
  exports.ReduxTreeView = ReduxTreeView;
213
+ const ReduxActionTimeline = ({ history, onClear, search, }) => {
214
+ const [expandedActionId, setExpandedActionId] = (0, react_1.useState)(null);
215
+ const toggleExpand = (id) => {
216
+ setExpandedActionId(prev => (prev === id ? null : id));
217
+ };
218
+ const filteredHistory = history.filter(action => {
219
+ if (!search)
220
+ return true;
221
+ const s = search.toLowerCase();
222
+ if (action.type.toLowerCase().includes(s))
223
+ return true;
224
+ if (action.affectedSlices.some(slice => slice.toLowerCase().includes(s)))
225
+ return true;
226
+ if (action.payload && typeof action.payload === 'object') {
227
+ return JSON.stringify(action.payload).toLowerCase().includes(s);
228
+ }
229
+ return false;
230
+ });
231
+ return (<react_native_1.View style={timelineStyles.container}>
232
+ <react_native_1.View style={timelineStyles.headerRow}>
233
+ <react_native_1.Text style={timelineStyles.headerTitle}>⚡ Dispatched Actions ({filteredHistory.length})</react_native_1.Text>
234
+ {history.length > 0 && (<react_native_1.Pressable onPress={onClear} style={timelineStyles.clearBtn}>
235
+ <react_native_1.Text style={timelineStyles.clearBtnText}>Clear Log</react_native_1.Text>
236
+ </react_native_1.Pressable>)}
237
+ </react_native_1.View>
238
+
239
+ {filteredHistory.length === 0 ? (<react_native_1.View style={timelineStyles.emptyContainer}>
240
+ <react_native_1.Text style={timelineStyles.emptyText}>
241
+ {history.length === 0
242
+ ? 'No actions dispatched yet.\nDispatch actions in your application to see the timeline.'
243
+ : 'No matching actions found.'}
244
+ </react_native_1.Text>
245
+ </react_native_1.View>) : (<react_native_1.View style={timelineStyles.listContainer}>
246
+ {filteredHistory.map((item, index) => {
247
+ const isLast = index === filteredHistory.length - 1;
248
+ const isExpanded = expandedActionId === item.id;
249
+ return (<react_native_1.View key={item.id} style={timelineStyles.timelineItem}>
250
+ {/* Visual Line */}
251
+ <react_native_1.View style={[timelineStyles.verticalLine, isLast && { bottom: '50%' }]}/>
252
+ <react_native_1.View style={timelineStyles.circleIndicator}>
253
+ <react_native_1.View style={timelineStyles.circleInner}/>
254
+ </react_native_1.View>
255
+
256
+ {/* Card */}
257
+ <react_native_1.Pressable onPress={() => toggleExpand(item.id)} style={[
258
+ timelineStyles.card,
259
+ isExpanded && { borderColor: AppColors_1.AppColors.purple, backgroundColor: AppColors_1.AppColors.purpleShade50 },
260
+ ]}>
261
+ <react_native_1.View style={timelineStyles.cardHeader}>
262
+ <react_native_1.View style={timelineStyles.typeBadge}>
263
+ <react_native_1.Text style={timelineStyles.typeText}>{item.type}</react_native_1.Text>
264
+ </react_native_1.View>
265
+ <react_native_1.Text style={timelineStyles.timestamp}>{item.timestamp}</react_native_1.Text>
266
+ </react_native_1.View>
267
+
268
+ {item.affectedSlices.length > 0 && (<react_native_1.View style={timelineStyles.slicesRow}>
269
+ <react_native_1.Text style={timelineStyles.slicesLabel}>Affected:</react_native_1.Text>
270
+ {item.affectedSlices.map(slice => (<react_native_1.View key={slice} style={timelineStyles.slicePill}>
271
+ <react_native_1.Text style={timelineStyles.sliceText}>{slice}</react_native_1.Text>
272
+ </react_native_1.View>))}
273
+ </react_native_1.View>)}
274
+
275
+ {isExpanded && (<react_native_1.View style={timelineStyles.payloadContainer}>
276
+ <react_native_1.Text style={timelineStyles.payloadTitle}>Payload</react_native_1.Text>
277
+ {item.payload !== null && typeof item.payload === 'object' ? (<ReduxValueNode name="action.payload" value={item.payload} level={0} search={search}/>) : (<react_native_1.Text style={timelineStyles.primitivePayload}>
278
+ {item.payload === null ? 'null' : String(item.payload)}
279
+ </react_native_1.Text>)}
280
+ </react_native_1.View>)}
281
+ </react_native_1.Pressable>
282
+ </react_native_1.View>);
283
+ })}
284
+ </react_native_1.View>)}
285
+ </react_native_1.View>);
286
+ };
287
+ exports.ReduxActionTimeline = ReduxActionTimeline;
213
288
  const reduxValueStyles = react_native_1.StyleSheet.create({
214
289
  treeNodeBlock: {
215
290
  marginTop: 4,
@@ -261,6 +336,158 @@ const reduxValueStyles = react_native_1.StyleSheet.create({
261
336
  color: AppColors_1.AppColors.grayTextWeak,
262
337
  },
263
338
  });
339
+ const timelineStyles = react_native_1.StyleSheet.create({
340
+ container: {
341
+ flex: 1,
342
+ },
343
+ headerRow: {
344
+ flexDirection: 'row',
345
+ alignItems: 'center',
346
+ justifyContent: 'space-between',
347
+ marginBottom: 12,
348
+ },
349
+ headerTitle: {
350
+ fontFamily: AppFonts_1.AppFonts.interBold,
351
+ fontSize: 14,
352
+ color: AppColors_1.AppColors.primaryBlack,
353
+ },
354
+ clearBtn: {
355
+ backgroundColor: 'rgba(239, 68, 68, 0.08)',
356
+ borderColor: 'rgba(239, 68, 68, 0.2)',
357
+ borderWidth: 1,
358
+ paddingHorizontal: 8,
359
+ paddingVertical: 4,
360
+ borderRadius: 6,
361
+ },
362
+ clearBtnText: {
363
+ fontFamily: AppFonts_1.AppFonts.interBold,
364
+ fontSize: 10,
365
+ color: '#EF4444',
366
+ },
367
+ emptyContainer: {
368
+ paddingVertical: 32,
369
+ alignItems: 'center',
370
+ justifyContent: 'center',
371
+ },
372
+ emptyText: {
373
+ fontFamily: AppFonts_1.AppFonts.interRegular,
374
+ fontSize: 12,
375
+ color: AppColors_1.AppColors.grayTextWeak,
376
+ textAlign: 'center',
377
+ lineHeight: 18,
378
+ },
379
+ listContainer: {
380
+ paddingLeft: 12,
381
+ },
382
+ timelineItem: {
383
+ position: 'relative',
384
+ paddingLeft: 20,
385
+ marginBottom: 12,
386
+ },
387
+ verticalLine: {
388
+ position: 'absolute',
389
+ left: 4,
390
+ top: 0,
391
+ bottom: -12,
392
+ width: 1,
393
+ backgroundColor: AppColors_1.AppColors.dividerColor,
394
+ },
395
+ circleIndicator: {
396
+ position: 'absolute',
397
+ left: 0,
398
+ top: 10,
399
+ width: 9,
400
+ height: 9,
401
+ borderRadius: 4.5,
402
+ backgroundColor: AppColors_1.AppColors.purpleShade50,
403
+ borderWidth: 1,
404
+ borderColor: AppColors_1.AppColors.purple,
405
+ alignItems: 'center',
406
+ justifyContent: 'center',
407
+ },
408
+ circleInner: {
409
+ width: 3,
410
+ height: 3,
411
+ borderRadius: 1.5,
412
+ backgroundColor: AppColors_1.AppColors.purple,
413
+ },
414
+ card: {
415
+ backgroundColor: AppColors_1.AppColors.primaryLight,
416
+ borderWidth: 1,
417
+ borderColor: AppColors_1.AppColors.grayBorderSecondary,
418
+ borderRadius: 8,
419
+ padding: 10,
420
+ },
421
+ cardHeader: {
422
+ flexDirection: 'row',
423
+ alignItems: 'center',
424
+ justifyContent: 'space-between',
425
+ gap: 12,
426
+ },
427
+ typeBadge: {
428
+ backgroundColor: 'rgba(104,75,155,0.08)',
429
+ borderColor: 'rgba(104,75,155,0.18)',
430
+ borderWidth: 1,
431
+ borderRadius: 6,
432
+ paddingHorizontal: 6,
433
+ paddingVertical: 2.5,
434
+ flexShrink: 1,
435
+ },
436
+ typeText: {
437
+ fontFamily: AppFonts_1.AppFonts.interBold,
438
+ fontSize: 11,
439
+ color: AppColors_1.AppColors.purple,
440
+ },
441
+ timestamp: {
442
+ fontFamily: AppFonts_1.AppFonts.interRegular,
443
+ fontSize: 10,
444
+ color: AppColors_1.AppColors.grayTextWeak,
445
+ },
446
+ slicesRow: {
447
+ flexDirection: 'row',
448
+ alignItems: 'center',
449
+ flexWrap: 'wrap',
450
+ gap: 4,
451
+ marginTop: 6,
452
+ },
453
+ slicesLabel: {
454
+ fontFamily: AppFonts_1.AppFonts.interMedium,
455
+ fontSize: 10,
456
+ color: AppColors_1.AppColors.grayTextWeak,
457
+ marginRight: 2,
458
+ },
459
+ slicePill: {
460
+ backgroundColor: AppColors_1.AppColors.grayBackground,
461
+ borderColor: AppColors_1.AppColors.dividerColor,
462
+ borderWidth: 1,
463
+ borderRadius: 4,
464
+ paddingHorizontal: 4,
465
+ paddingVertical: 1,
466
+ },
467
+ sliceText: {
468
+ fontFamily: AppFonts_1.AppFonts.interMedium,
469
+ fontSize: 9,
470
+ color: AppColors_1.AppColors.grayText,
471
+ },
472
+ payloadContainer: {
473
+ marginTop: 10,
474
+ borderTopWidth: 1,
475
+ borderTopColor: AppColors_1.AppColors.dividerColor,
476
+ paddingTop: 8,
477
+ },
478
+ payloadTitle: {
479
+ fontFamily: AppFonts_1.AppFonts.interBold,
480
+ fontSize: 10,
481
+ color: AppColors_1.AppColors.grayTextWeak,
482
+ textTransform: 'uppercase',
483
+ marginBottom: 4,
484
+ },
485
+ primitivePayload: {
486
+ fontFamily: AppFonts_1.AppFonts.interRegular,
487
+ fontSize: 11,
488
+ color: AppColors_1.AppColors.grayTextStrong,
489
+ },
490
+ });
264
491
  const styles = react_native_1.StyleSheet.create({
265
492
  container: {
266
493
  flex: 1,
@@ -3,6 +3,14 @@ export declare const setReduxAutoRefresh: (val: boolean) => void;
3
3
  export declare const getReduxAutoRefresh: () => boolean;
4
4
  export declare const getLastActionForReducer: () => Record<string, any>;
5
5
  export declare const clearLastActionForReducer: () => void;
6
+ export declare const getActionHistory: () => {
7
+ id: number;
8
+ type: string;
9
+ payload: any;
10
+ timestamp: string;
11
+ affectedSlices: string[];
12
+ }[];
13
+ export declare const clearActionHistory: () => void;
6
14
  export declare const setReduxState: (state: any) => void;
7
15
  export declare const subscribeReduxState: (cb: () => void) => () => void;
8
16
  export declare const connectReduxStore: (store: any) => void;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.connectReduxStore = exports.subscribeReduxState = exports.setReduxState = exports.clearLastActionForReducer = exports.getLastActionForReducer = exports.getReduxAutoRefresh = exports.setReduxAutoRefresh = exports.getReduxState = void 0;
3
+ exports.connectReduxStore = exports.subscribeReduxState = exports.setReduxState = exports.clearActionHistory = exports.getActionHistory = exports.clearLastActionForReducer = exports.getLastActionForReducer = exports.getReduxAutoRefresh = exports.setReduxAutoRefresh = exports.getReduxState = void 0;
4
4
  let currentReduxState = null;
5
5
  const listeners = new Set();
6
6
  let globalReduxAutoRefresh = true;
7
7
  let lastActionForReducer = {};
8
+ let actionHistory = [];
8
9
  const getReduxState = () => currentReduxState;
9
10
  exports.getReduxState = getReduxState;
10
11
  const setReduxAutoRefresh = (val) => {
@@ -20,6 +21,13 @@ const clearLastActionForReducer = () => {
20
21
  listeners.forEach(cb => cb());
21
22
  };
22
23
  exports.clearLastActionForReducer = clearLastActionForReducer;
24
+ const getActionHistory = () => actionHistory;
25
+ exports.getActionHistory = getActionHistory;
26
+ const clearActionHistory = () => {
27
+ actionHistory = [];
28
+ listeners.forEach(cb => cb());
29
+ };
30
+ exports.clearActionHistory = clearActionHistory;
23
31
  const setReduxState = (state) => {
24
32
  currentReduxState = state;
25
33
  listeners.forEach(cb => cb());
@@ -44,6 +52,7 @@ const connectReduxStore = (store) => {
44
52
  const result = originalDispatch(action);
45
53
  const nextState = store.getState();
46
54
  // Map the dispatched action to state slices that actually changed
55
+ const affectedSlices = [];
47
56
  if (prevState &&
48
57
  nextState &&
49
58
  typeof prevState === 'object' &&
@@ -52,13 +61,27 @@ const connectReduxStore = (store) => {
52
61
  typeof action === 'object') {
53
62
  Object.keys(nextState).forEach(key => {
54
63
  if (prevState[key] !== nextState[key]) {
55
- lastActionForReducer[key] = {
64
+ const actionObj = {
56
65
  type: action.type || 'UNKNOWN_ACTION',
57
66
  payload: action.payload !== undefined ? action.payload : null,
58
67
  timestamp: new Date().toLocaleTimeString(),
59
68
  };
69
+ lastActionForReducer[key] = actionObj;
70
+ affectedSlices.push(key);
60
71
  }
61
72
  });
73
+ // Push to history
74
+ actionHistory.unshift({
75
+ id: Date.now() + Math.random(),
76
+ type: action.type || 'UNKNOWN_ACTION',
77
+ payload: action.payload !== undefined ? action.payload : null,
78
+ timestamp: new Date().toLocaleTimeString(),
79
+ affectedSlices,
80
+ });
81
+ // Cap size at 50
82
+ if (actionHistory.length > 50) {
83
+ actionHistory.pop();
84
+ }
62
85
  }
63
86
  if (globalReduxAutoRefresh) {
64
87
  (0, exports.setReduxState)(nextState);
@@ -158,6 +158,7 @@ const NetworkInspector = ({ enabled = true }) => {
158
158
  const [search, setSearch] = (0, react_1.useState)('');
159
159
  const [detailSearch, setDetailSearch] = (0, react_1.useState)('');
160
160
  const [reduxSearch, setReduxSearch] = (0, react_1.useState)('');
161
+ const [reduxActiveSubTab, setReduxActiveSubTab] = (0, react_1.useState)('timeline');
161
162
  const [apiDetailActiveTab, setApiDetailActiveTab] = (0, react_1.useState)('response');
162
163
  (0, react_1.useEffect)(() => {
163
164
  if (enabled && visible) {
@@ -998,7 +999,7 @@ const NetworkInspector = ({ enabled = true }) => {
998
999
  borderWidth: 1,
999
1000
  borderColor: 'rgba(255, 255, 255, 0.1)'
1000
1001
  }}>
1001
- <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interBold, fontSize: 10.5, color: '#FFFFFF' }}>v1.0.10</react_native_1.Text>
1002
+ <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interBold, fontSize: 10.5, color: '#FFFFFF' }}>v1.0.12</react_native_1.Text>
1002
1003
  </react_native_1.View>
1003
1004
  </react_native_1.View>
1004
1005
  </react_native_linear_gradient_1.default>
@@ -1732,6 +1733,7 @@ const NetworkInspector = ({ enabled = true }) => {
1732
1733
  }
1733
1734
  // Build hierarchical tree: Store -> Reducers -> Action -> Data
1734
1735
  const lastActionMap = (0, reduxLogger_1.getLastActionForReducer)();
1736
+ const actionHistory = (0, reduxLogger_1.getActionHistory)();
1735
1737
  return (<react_native_1.ScrollView style={styles_1.default.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
1736
1738
  {/* Top Summary Card */}
1737
1739
  <react_native_1.View style={{
@@ -1768,6 +1770,51 @@ const NetworkInspector = ({ enabled = true }) => {
1768
1770
  <CopyButton_1.default value={() => reduxState} label="Overall Store"/>
1769
1771
  </react_native_1.View>
1770
1772
 
1773
+ {/* Tab View Selection Segments */}
1774
+ <react_native_1.View style={{
1775
+ flexDirection: 'row',
1776
+ backgroundColor: AppColors_1.AppColors.grayBackground,
1777
+ borderRadius: 10,
1778
+ padding: 3,
1779
+ marginHorizontal: 16,
1780
+ marginBottom: 12,
1781
+ borderWidth: 1,
1782
+ borderColor: AppColors_1.AppColors.dividerColor,
1783
+ }}>
1784
+ <react_native_1.TouchableOpacity onPress={() => setReduxActiveSubTab('timeline')} style={{
1785
+ flex: 1,
1786
+ paddingVertical: 6,
1787
+ alignItems: 'center',
1788
+ justifyContent: 'center',
1789
+ borderRadius: 8,
1790
+ backgroundColor: reduxActiveSubTab === 'timeline' ? AppColors_1.AppColors.purple : 'transparent',
1791
+ }}>
1792
+ <react_native_1.Text style={{
1793
+ fontFamily: AppFonts_1.AppFonts.interBold,
1794
+ fontSize: 11,
1795
+ color: reduxActiveSubTab === 'timeline' ? '#FFFFFF' : AppColors_1.AppColors.grayText,
1796
+ }}>
1797
+ ⚡ Action Timeline
1798
+ </react_native_1.Text>
1799
+ </react_native_1.TouchableOpacity>
1800
+ <react_native_1.TouchableOpacity onPress={() => setReduxActiveSubTab('tree')} style={{
1801
+ flex: 1,
1802
+ paddingVertical: 6,
1803
+ alignItems: 'center',
1804
+ justifyContent: 'center',
1805
+ borderRadius: 8,
1806
+ backgroundColor: reduxActiveSubTab === 'tree' ? AppColors_1.AppColors.purple : 'transparent',
1807
+ }}>
1808
+ <react_native_1.Text style={{
1809
+ fontFamily: AppFonts_1.AppFonts.interBold,
1810
+ fontSize: 11,
1811
+ color: reduxActiveSubTab === 'tree' ? '#FFFFFF' : AppColors_1.AppColors.grayText,
1812
+ }}>
1813
+ 🏪 Store Tree
1814
+ </react_native_1.Text>
1815
+ </react_native_1.TouchableOpacity>
1816
+ </react_native_1.View>
1817
+
1771
1818
  {/* Search Bar */}
1772
1819
  <react_native_1.View style={{
1773
1820
  flexDirection: 'row',
@@ -1781,7 +1828,7 @@ const NetworkInspector = ({ enabled = true }) => {
1781
1828
  borderColor: AppColors_1.AppColors.dividerColor,
1782
1829
  height: 36,
1783
1830
  }}>
1784
- <react_native_1.TextInput placeholder="Search Redux keys or values..." placeholderTextColor={AppColors_1.AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
1831
+ <react_native_1.TextInput placeholder={reduxActiveSubTab === 'timeline' ? "Search actions or payloads..." : "Search Redux keys or values..."} placeholderTextColor={AppColors_1.AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
1785
1832
  flex: 1,
1786
1833
  fontFamily: AppFonts_1.AppFonts.interRegular,
1787
1834
  fontSize: 12,
@@ -1793,7 +1840,7 @@ const NetworkInspector = ({ enabled = true }) => {
1793
1840
  </react_native_1.Pressable>)}
1794
1841
  </react_native_1.View>
1795
1842
 
1796
- {/* Main Tree Card */}
1843
+ {/* Main Content Card */}
1797
1844
  <react_native_1.View style={{
1798
1845
  backgroundColor: AppColors_1.AppColors.primaryLight,
1799
1846
  borderRadius: 12,
@@ -1802,7 +1849,7 @@ const NetworkInspector = ({ enabled = true }) => {
1802
1849
  marginHorizontal: 16,
1803
1850
  padding: 12,
1804
1851
  }}>
1805
- <ReduxTreeView_1.ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>
1852
+ {reduxActiveSubTab === 'timeline' ? (<ReduxTreeView_1.ReduxActionTimeline history={actionHistory} onClear={reduxLogger_1.clearActionHistory} search={reduxSearch}/>) : (<ReduxTreeView_1.ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>)}
1806
1853
  </react_native_1.View>
1807
1854
  </react_native_1.ScrollView>);
1808
1855
  };
@@ -1877,7 +1924,7 @@ const NetworkInspector = ({ enabled = true }) => {
1877
1924
  <react_native_1.View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
1878
1925
  <react_native_1.Animated.View style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: '#4ADE80', opacity: activePulseAnim }}/>
1879
1926
  <react_native_1.Text style={{ fontFamily: AppFonts_1.AppFonts.interMedium, fontSize: 10, color: 'rgba(255,255,255,0.78)', letterSpacing: 0.3 }}>
1880
- Active • {react_native_1.Platform.OS === 'ios' ? 'iOS' : 'Android'} (v1.0.10)
1927
+ Active • {react_native_1.Platform.OS === 'ios' ? 'iOS' : 'Android'} (v1.0.12)
1881
1928
  </react_native_1.Text>
1882
1929
  </react_native_1.View>
1883
1930
  </react_native_1.View>
@@ -4,3 +4,14 @@ export declare const ReduxTreeView: ({ state, lastActionMap, search, }: {
4
4
  lastActionMap: Record<string, any>;
5
5
  search?: string;
6
6
  }) => React.JSX.Element;
7
+ export declare const ReduxActionTimeline: ({ history, onClear, search, }: {
8
+ history: Array<{
9
+ id: number;
10
+ type: string;
11
+ payload: any;
12
+ timestamp: string;
13
+ affectedSlices: string[];
14
+ }>;
15
+ onClear: () => void;
16
+ search?: string;
17
+ }) => React.JSX.Element;
@@ -173,6 +173,80 @@ export const ReduxTreeView = ({ state, lastActionMap, search, }) => {
173
173
  </View>)}
174
174
  </View>);
175
175
  };
176
+ export const ReduxActionTimeline = ({ history, onClear, search, }) => {
177
+ const [expandedActionId, setExpandedActionId] = useState(null);
178
+ const toggleExpand = (id) => {
179
+ setExpandedActionId(prev => (prev === id ? null : id));
180
+ };
181
+ const filteredHistory = history.filter(action => {
182
+ if (!search)
183
+ return true;
184
+ const s = search.toLowerCase();
185
+ if (action.type.toLowerCase().includes(s))
186
+ return true;
187
+ if (action.affectedSlices.some(slice => slice.toLowerCase().includes(s)))
188
+ return true;
189
+ if (action.payload && typeof action.payload === 'object') {
190
+ return JSON.stringify(action.payload).toLowerCase().includes(s);
191
+ }
192
+ return false;
193
+ });
194
+ return (<View style={timelineStyles.container}>
195
+ <View style={timelineStyles.headerRow}>
196
+ <Text style={timelineStyles.headerTitle}>⚡ Dispatched Actions ({filteredHistory.length})</Text>
197
+ {history.length > 0 && (<Pressable onPress={onClear} style={timelineStyles.clearBtn}>
198
+ <Text style={timelineStyles.clearBtnText}>Clear Log</Text>
199
+ </Pressable>)}
200
+ </View>
201
+
202
+ {filteredHistory.length === 0 ? (<View style={timelineStyles.emptyContainer}>
203
+ <Text style={timelineStyles.emptyText}>
204
+ {history.length === 0
205
+ ? 'No actions dispatched yet.\nDispatch actions in your application to see the timeline.'
206
+ : 'No matching actions found.'}
207
+ </Text>
208
+ </View>) : (<View style={timelineStyles.listContainer}>
209
+ {filteredHistory.map((item, index) => {
210
+ const isLast = index === filteredHistory.length - 1;
211
+ const isExpanded = expandedActionId === item.id;
212
+ return (<View key={item.id} style={timelineStyles.timelineItem}>
213
+ {/* Visual Line */}
214
+ <View style={[timelineStyles.verticalLine, isLast && { bottom: '50%' }]}/>
215
+ <View style={timelineStyles.circleIndicator}>
216
+ <View style={timelineStyles.circleInner}/>
217
+ </View>
218
+
219
+ {/* Card */}
220
+ <Pressable onPress={() => toggleExpand(item.id)} style={[
221
+ timelineStyles.card,
222
+ isExpanded && { borderColor: AppColors.purple, backgroundColor: AppColors.purpleShade50 },
223
+ ]}>
224
+ <View style={timelineStyles.cardHeader}>
225
+ <View style={timelineStyles.typeBadge}>
226
+ <Text style={timelineStyles.typeText}>{item.type}</Text>
227
+ </View>
228
+ <Text style={timelineStyles.timestamp}>{item.timestamp}</Text>
229
+ </View>
230
+
231
+ {item.affectedSlices.length > 0 && (<View style={timelineStyles.slicesRow}>
232
+ <Text style={timelineStyles.slicesLabel}>Affected:</Text>
233
+ {item.affectedSlices.map(slice => (<View key={slice} style={timelineStyles.slicePill}>
234
+ <Text style={timelineStyles.sliceText}>{slice}</Text>
235
+ </View>))}
236
+ </View>)}
237
+
238
+ {isExpanded && (<View style={timelineStyles.payloadContainer}>
239
+ <Text style={timelineStyles.payloadTitle}>Payload</Text>
240
+ {item.payload !== null && typeof item.payload === 'object' ? (<ReduxValueNode name="action.payload" value={item.payload} level={0} search={search}/>) : (<Text style={timelineStyles.primitivePayload}>
241
+ {item.payload === null ? 'null' : String(item.payload)}
242
+ </Text>)}
243
+ </View>)}
244
+ </Pressable>
245
+ </View>);
246
+ })}
247
+ </View>)}
248
+ </View>);
249
+ };
176
250
  const reduxValueStyles = StyleSheet.create({
177
251
  treeNodeBlock: {
178
252
  marginTop: 4,
@@ -224,6 +298,158 @@ const reduxValueStyles = StyleSheet.create({
224
298
  color: AppColors.grayTextWeak,
225
299
  },
226
300
  });
301
+ const timelineStyles = StyleSheet.create({
302
+ container: {
303
+ flex: 1,
304
+ },
305
+ headerRow: {
306
+ flexDirection: 'row',
307
+ alignItems: 'center',
308
+ justifyContent: 'space-between',
309
+ marginBottom: 12,
310
+ },
311
+ headerTitle: {
312
+ fontFamily: AppFonts.interBold,
313
+ fontSize: 14,
314
+ color: AppColors.primaryBlack,
315
+ },
316
+ clearBtn: {
317
+ backgroundColor: 'rgba(239, 68, 68, 0.08)',
318
+ borderColor: 'rgba(239, 68, 68, 0.2)',
319
+ borderWidth: 1,
320
+ paddingHorizontal: 8,
321
+ paddingVertical: 4,
322
+ borderRadius: 6,
323
+ },
324
+ clearBtnText: {
325
+ fontFamily: AppFonts.interBold,
326
+ fontSize: 10,
327
+ color: '#EF4444',
328
+ },
329
+ emptyContainer: {
330
+ paddingVertical: 32,
331
+ alignItems: 'center',
332
+ justifyContent: 'center',
333
+ },
334
+ emptyText: {
335
+ fontFamily: AppFonts.interRegular,
336
+ fontSize: 12,
337
+ color: AppColors.grayTextWeak,
338
+ textAlign: 'center',
339
+ lineHeight: 18,
340
+ },
341
+ listContainer: {
342
+ paddingLeft: 12,
343
+ },
344
+ timelineItem: {
345
+ position: 'relative',
346
+ paddingLeft: 20,
347
+ marginBottom: 12,
348
+ },
349
+ verticalLine: {
350
+ position: 'absolute',
351
+ left: 4,
352
+ top: 0,
353
+ bottom: -12,
354
+ width: 1,
355
+ backgroundColor: AppColors.dividerColor,
356
+ },
357
+ circleIndicator: {
358
+ position: 'absolute',
359
+ left: 0,
360
+ top: 10,
361
+ width: 9,
362
+ height: 9,
363
+ borderRadius: 4.5,
364
+ backgroundColor: AppColors.purpleShade50,
365
+ borderWidth: 1,
366
+ borderColor: AppColors.purple,
367
+ alignItems: 'center',
368
+ justifyContent: 'center',
369
+ },
370
+ circleInner: {
371
+ width: 3,
372
+ height: 3,
373
+ borderRadius: 1.5,
374
+ backgroundColor: AppColors.purple,
375
+ },
376
+ card: {
377
+ backgroundColor: AppColors.primaryLight,
378
+ borderWidth: 1,
379
+ borderColor: AppColors.grayBorderSecondary,
380
+ borderRadius: 8,
381
+ padding: 10,
382
+ },
383
+ cardHeader: {
384
+ flexDirection: 'row',
385
+ alignItems: 'center',
386
+ justifyContent: 'space-between',
387
+ gap: 12,
388
+ },
389
+ typeBadge: {
390
+ backgroundColor: 'rgba(104,75,155,0.08)',
391
+ borderColor: 'rgba(104,75,155,0.18)',
392
+ borderWidth: 1,
393
+ borderRadius: 6,
394
+ paddingHorizontal: 6,
395
+ paddingVertical: 2.5,
396
+ flexShrink: 1,
397
+ },
398
+ typeText: {
399
+ fontFamily: AppFonts.interBold,
400
+ fontSize: 11,
401
+ color: AppColors.purple,
402
+ },
403
+ timestamp: {
404
+ fontFamily: AppFonts.interRegular,
405
+ fontSize: 10,
406
+ color: AppColors.grayTextWeak,
407
+ },
408
+ slicesRow: {
409
+ flexDirection: 'row',
410
+ alignItems: 'center',
411
+ flexWrap: 'wrap',
412
+ gap: 4,
413
+ marginTop: 6,
414
+ },
415
+ slicesLabel: {
416
+ fontFamily: AppFonts.interMedium,
417
+ fontSize: 10,
418
+ color: AppColors.grayTextWeak,
419
+ marginRight: 2,
420
+ },
421
+ slicePill: {
422
+ backgroundColor: AppColors.grayBackground,
423
+ borderColor: AppColors.dividerColor,
424
+ borderWidth: 1,
425
+ borderRadius: 4,
426
+ paddingHorizontal: 4,
427
+ paddingVertical: 1,
428
+ },
429
+ sliceText: {
430
+ fontFamily: AppFonts.interMedium,
431
+ fontSize: 9,
432
+ color: AppColors.grayText,
433
+ },
434
+ payloadContainer: {
435
+ marginTop: 10,
436
+ borderTopWidth: 1,
437
+ borderTopColor: AppColors.dividerColor,
438
+ paddingTop: 8,
439
+ },
440
+ payloadTitle: {
441
+ fontFamily: AppFonts.interBold,
442
+ fontSize: 10,
443
+ color: AppColors.grayTextWeak,
444
+ textTransform: 'uppercase',
445
+ marginBottom: 4,
446
+ },
447
+ primitivePayload: {
448
+ fontFamily: AppFonts.interRegular,
449
+ fontSize: 11,
450
+ color: AppColors.grayTextStrong,
451
+ },
452
+ });
227
453
  const styles = StyleSheet.create({
228
454
  container: {
229
455
  flex: 1,
@@ -3,6 +3,14 @@ export declare const setReduxAutoRefresh: (val: boolean) => void;
3
3
  export declare const getReduxAutoRefresh: () => boolean;
4
4
  export declare const getLastActionForReducer: () => Record<string, any>;
5
5
  export declare const clearLastActionForReducer: () => void;
6
+ export declare const getActionHistory: () => {
7
+ id: number;
8
+ type: string;
9
+ payload: any;
10
+ timestamp: string;
11
+ affectedSlices: string[];
12
+ }[];
13
+ export declare const clearActionHistory: () => void;
6
14
  export declare const setReduxState: (state: any) => void;
7
15
  export declare const subscribeReduxState: (cb: () => void) => () => void;
8
16
  export declare const connectReduxStore: (store: any) => void;
@@ -2,6 +2,7 @@ let currentReduxState = null;
2
2
  const listeners = new Set();
3
3
  let globalReduxAutoRefresh = true;
4
4
  let lastActionForReducer = {};
5
+ let actionHistory = [];
5
6
  export const getReduxState = () => currentReduxState;
6
7
  export const setReduxAutoRefresh = (val) => {
7
8
  globalReduxAutoRefresh = val;
@@ -12,6 +13,11 @@ export const clearLastActionForReducer = () => {
12
13
  lastActionForReducer = {};
13
14
  listeners.forEach(cb => cb());
14
15
  };
16
+ export const getActionHistory = () => actionHistory;
17
+ export const clearActionHistory = () => {
18
+ actionHistory = [];
19
+ listeners.forEach(cb => cb());
20
+ };
15
21
  export const setReduxState = (state) => {
16
22
  currentReduxState = state;
17
23
  listeners.forEach(cb => cb());
@@ -34,6 +40,7 @@ export const connectReduxStore = (store) => {
34
40
  const result = originalDispatch(action);
35
41
  const nextState = store.getState();
36
42
  // Map the dispatched action to state slices that actually changed
43
+ const affectedSlices = [];
37
44
  if (prevState &&
38
45
  nextState &&
39
46
  typeof prevState === 'object' &&
@@ -42,13 +49,27 @@ export const connectReduxStore = (store) => {
42
49
  typeof action === 'object') {
43
50
  Object.keys(nextState).forEach(key => {
44
51
  if (prevState[key] !== nextState[key]) {
45
- lastActionForReducer[key] = {
52
+ const actionObj = {
46
53
  type: action.type || 'UNKNOWN_ACTION',
47
54
  payload: action.payload !== undefined ? action.payload : null,
48
55
  timestamp: new Date().toLocaleTimeString(),
49
56
  };
57
+ lastActionForReducer[key] = actionObj;
58
+ affectedSlices.push(key);
50
59
  }
51
60
  });
61
+ // Push to history
62
+ actionHistory.unshift({
63
+ id: Date.now() + Math.random(),
64
+ type: action.type || 'UNKNOWN_ACTION',
65
+ payload: action.payload !== undefined ? action.payload : null,
66
+ timestamp: new Date().toLocaleTimeString(),
67
+ affectedSlices,
68
+ });
69
+ // Cap size at 50
70
+ if (actionHistory.length > 50) {
71
+ actionHistory.pop();
72
+ }
52
73
  }
53
74
  if (globalReduxAutoRefresh) {
54
75
  setReduxState(nextState);
package/dist/esm/index.js CHANGED
@@ -11,7 +11,7 @@ import CopyButton from './components/CopyButton';
11
11
  import SectionHeader from './components/SectionHeader';
12
12
  import EmptyState from './components/EmptyState';
13
13
  import JsonViewer from './components/JsonViewer';
14
- import { ReduxTreeView } from './components/ReduxTreeView';
14
+ import { ReduxTreeView, ReduxActionTimeline } from './components/ReduxTreeView';
15
15
  import DomainHeader from './components/DomainHeader';
16
16
  import DiffViewer from './components/DiffViewer';
17
17
  import LogCard from './components/LogCard';
@@ -98,7 +98,7 @@ const previewInspectScript = `
98
98
  })();
99
99
  true;
100
100
  `;
101
- import { getReduxState, subscribeReduxState, setReduxAutoRefresh, getLastActionForReducer, } from './customHooks/reduxLogger';
101
+ import { getReduxState, subscribeReduxState, setReduxAutoRefresh, getLastActionForReducer, getActionHistory, clearActionHistory, } from './customHooks/reduxLogger';
102
102
  import { METHOD_COLORS, STATUS_FILTERS } from './constants';
103
103
  const NavigationTracker = ({ onStateChange }) => {
104
104
  const navState = useNavigationState(state => state);
@@ -119,6 +119,7 @@ const NetworkInspector = ({ enabled = true }) => {
119
119
  const [search, setSearch] = useState('');
120
120
  const [detailSearch, setDetailSearch] = useState('');
121
121
  const [reduxSearch, setReduxSearch] = useState('');
122
+ const [reduxActiveSubTab, setReduxActiveSubTab] = useState('timeline');
122
123
  const [apiDetailActiveTab, setApiDetailActiveTab] = useState('response');
123
124
  useEffect(() => {
124
125
  if (enabled && visible) {
@@ -959,7 +960,7 @@ const NetworkInspector = ({ enabled = true }) => {
959
960
  borderWidth: 1,
960
961
  borderColor: 'rgba(255, 255, 255, 0.1)'
961
962
  }}>
962
- <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: '#FFFFFF' }}>v1.0.10</Text>
963
+ <Text style={{ fontFamily: AppFonts.interBold, fontSize: 10.5, color: '#FFFFFF' }}>v1.0.12</Text>
963
964
  </View>
964
965
  </View>
965
966
  </LinearGradient>
@@ -1693,6 +1694,7 @@ const NetworkInspector = ({ enabled = true }) => {
1693
1694
  }
1694
1695
  // Build hierarchical tree: Store -> Reducers -> Action -> Data
1695
1696
  const lastActionMap = getLastActionForReducer();
1697
+ const actionHistory = getActionHistory();
1696
1698
  return (<ScrollView style={styles.detailScroll} contentContainerStyle={{ paddingBottom: 24 }}>
1697
1699
  {/* Top Summary Card */}
1698
1700
  <View style={{
@@ -1729,6 +1731,51 @@ const NetworkInspector = ({ enabled = true }) => {
1729
1731
  <CopyButton value={() => reduxState} label="Overall Store"/>
1730
1732
  </View>
1731
1733
 
1734
+ {/* Tab View Selection Segments */}
1735
+ <View style={{
1736
+ flexDirection: 'row',
1737
+ backgroundColor: AppColors.grayBackground,
1738
+ borderRadius: 10,
1739
+ padding: 3,
1740
+ marginHorizontal: 16,
1741
+ marginBottom: 12,
1742
+ borderWidth: 1,
1743
+ borderColor: AppColors.dividerColor,
1744
+ }}>
1745
+ <TouchableOpacity onPress={() => setReduxActiveSubTab('timeline')} style={{
1746
+ flex: 1,
1747
+ paddingVertical: 6,
1748
+ alignItems: 'center',
1749
+ justifyContent: 'center',
1750
+ borderRadius: 8,
1751
+ backgroundColor: reduxActiveSubTab === 'timeline' ? AppColors.purple : 'transparent',
1752
+ }}>
1753
+ <Text style={{
1754
+ fontFamily: AppFonts.interBold,
1755
+ fontSize: 11,
1756
+ color: reduxActiveSubTab === 'timeline' ? '#FFFFFF' : AppColors.grayText,
1757
+ }}>
1758
+ ⚡ Action Timeline
1759
+ </Text>
1760
+ </TouchableOpacity>
1761
+ <TouchableOpacity onPress={() => setReduxActiveSubTab('tree')} style={{
1762
+ flex: 1,
1763
+ paddingVertical: 6,
1764
+ alignItems: 'center',
1765
+ justifyContent: 'center',
1766
+ borderRadius: 8,
1767
+ backgroundColor: reduxActiveSubTab === 'tree' ? AppColors.purple : 'transparent',
1768
+ }}>
1769
+ <Text style={{
1770
+ fontFamily: AppFonts.interBold,
1771
+ fontSize: 11,
1772
+ color: reduxActiveSubTab === 'tree' ? '#FFFFFF' : AppColors.grayText,
1773
+ }}>
1774
+ 🏪 Store Tree
1775
+ </Text>
1776
+ </TouchableOpacity>
1777
+ </View>
1778
+
1732
1779
  {/* Search Bar */}
1733
1780
  <View style={{
1734
1781
  flexDirection: 'row',
@@ -1742,7 +1789,7 @@ const NetworkInspector = ({ enabled = true }) => {
1742
1789
  borderColor: AppColors.dividerColor,
1743
1790
  height: 36,
1744
1791
  }}>
1745
- <TextInput placeholder="Search Redux keys or values..." placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
1792
+ <TextInput placeholder={reduxActiveSubTab === 'timeline' ? "Search actions or payloads..." : "Search Redux keys or values..."} placeholderTextColor={AppColors.grayTextWeak} value={reduxSearch} onChangeText={setReduxSearch} style={{
1746
1793
  flex: 1,
1747
1794
  fontFamily: AppFonts.interRegular,
1748
1795
  fontSize: 12,
@@ -1754,7 +1801,7 @@ const NetworkInspector = ({ enabled = true }) => {
1754
1801
  </Pressable>)}
1755
1802
  </View>
1756
1803
 
1757
- {/* Main Tree Card */}
1804
+ {/* Main Content Card */}
1758
1805
  <View style={{
1759
1806
  backgroundColor: AppColors.primaryLight,
1760
1807
  borderRadius: 12,
@@ -1763,7 +1810,7 @@ const NetworkInspector = ({ enabled = true }) => {
1763
1810
  marginHorizontal: 16,
1764
1811
  padding: 12,
1765
1812
  }}>
1766
- <ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>
1813
+ {reduxActiveSubTab === 'timeline' ? (<ReduxActionTimeline history={actionHistory} onClear={clearActionHistory} search={reduxSearch}/>) : (<ReduxTreeView state={reduxState} lastActionMap={lastActionMap} search={reduxSearch}/>)}
1767
1814
  </View>
1768
1815
  </ScrollView>);
1769
1816
  };
@@ -1838,7 +1885,7 @@ const NetworkInspector = ({ enabled = true }) => {
1838
1885
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
1839
1886
  <Animated.View style={{ width: 6, height: 6, borderRadius: 3, backgroundColor: '#4ADE80', opacity: activePulseAnim }}/>
1840
1887
  <Text style={{ fontFamily: AppFonts.interMedium, fontSize: 10, color: 'rgba(255,255,255,0.78)', letterSpacing: 0.3 }}>
1841
- Active • {Platform.OS === 'ios' ? 'iOS' : 'Android'} (v1.0.10)
1888
+ Active • {Platform.OS === 'ios' ? 'iOS' : 'Android'} (v1.0.12)
1842
1889
  </Text>
1843
1890
  </View>
1844
1891
  </View>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-inapp-inspector",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "A self-contained network, console, analytics, and webview inspector for React Native applications.",
5
5
  "repository": {
6
6
  "type": "git",