react-native-prod-debugger 1.0.0

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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/lib/commonjs/core/DebugBubble.js +139 -0
  4. package/lib/commonjs/core/DebugBubble.js.map +1 -0
  5. package/lib/commonjs/core/DebugOverlay.js +184 -0
  6. package/lib/commonjs/core/DebugOverlay.js.map +1 -0
  7. package/lib/commonjs/core/DebuggerProvider.js +174 -0
  8. package/lib/commonjs/core/DebuggerProvider.js.map +1 -0
  9. package/lib/commonjs/core/GestureDetector.js +83 -0
  10. package/lib/commonjs/core/GestureDetector.js.map +1 -0
  11. package/lib/commonjs/core/PluginRegistry.js +124 -0
  12. package/lib/commonjs/core/PluginRegistry.js.map +1 -0
  13. package/lib/commonjs/core/theme.js +50 -0
  14. package/lib/commonjs/core/theme.js.map +1 -0
  15. package/lib/commonjs/core/types.js +6 -0
  16. package/lib/commonjs/core/types.js.map +1 -0
  17. package/lib/commonjs/core/useDebugger.js +27 -0
  18. package/lib/commonjs/core/useDebugger.js.map +1 -0
  19. package/lib/commonjs/core/utils.js +239 -0
  20. package/lib/commonjs/core/utils.js.map +1 -0
  21. package/lib/commonjs/index.js +132 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/commonjs/plugins/console/ConsoleInterceptor.js +103 -0
  24. package/lib/commonjs/plugins/console/ConsoleInterceptor.js.map +1 -0
  25. package/lib/commonjs/plugins/console/ConsoleViewerPlugin.js +269 -0
  26. package/lib/commonjs/plugins/console/ConsoleViewerPlugin.js.map +1 -0
  27. package/lib/commonjs/plugins/crashReporter/CrashReporterPlugin.js +363 -0
  28. package/lib/commonjs/plugins/crashReporter/CrashReporterPlugin.js.map +1 -0
  29. package/lib/commonjs/plugins/customActions/CustomActionsPlugin.js +218 -0
  30. package/lib/commonjs/plugins/customActions/CustomActionsPlugin.js.map +1 -0
  31. package/lib/commonjs/plugins/customActions/actionStore.js +46 -0
  32. package/lib/commonjs/plugins/customActions/actionStore.js.map +1 -0
  33. package/lib/commonjs/plugins/deepLinkTester/DeepLinkTesterPlugin.js +268 -0
  34. package/lib/commonjs/plugins/deepLinkTester/DeepLinkTesterPlugin.js.map +1 -0
  35. package/lib/commonjs/plugins/deviceInfo/DeviceInfoPlugin.js +184 -0
  36. package/lib/commonjs/plugins/deviceInfo/DeviceInfoPlugin.js.map +1 -0
  37. package/lib/commonjs/plugins/featureFlags/FeatureFlagsPlugin.js +381 -0
  38. package/lib/commonjs/plugins/featureFlags/FeatureFlagsPlugin.js.map +1 -0
  39. package/lib/commonjs/plugins/featureFlags/flagStore.js +125 -0
  40. package/lib/commonjs/plugins/featureFlags/flagStore.js.map +1 -0
  41. package/lib/commonjs/plugins/navigationInspector/NavigationInspectorPlugin.js +250 -0
  42. package/lib/commonjs/plugins/navigationInspector/NavigationInspectorPlugin.js.map +1 -0
  43. package/lib/commonjs/plugins/navigationInspector/navigationStore.js +65 -0
  44. package/lib/commonjs/plugins/navigationInspector/navigationStore.js.map +1 -0
  45. package/lib/commonjs/plugins/network/NetworkInspectorPlugin.js +709 -0
  46. package/lib/commonjs/plugins/network/NetworkInspectorPlugin.js.map +1 -0
  47. package/lib/commonjs/plugins/network/NetworkInterceptor.js +305 -0
  48. package/lib/commonjs/plugins/network/NetworkInterceptor.js.map +1 -0
  49. package/lib/commonjs/plugins/performance/PerformanceMonitorPlugin.js +261 -0
  50. package/lib/commonjs/plugins/performance/PerformanceMonitorPlugin.js.map +1 -0
  51. package/lib/commonjs/plugins/remoteConfig/RemoteConfigPlugin.js +265 -0
  52. package/lib/commonjs/plugins/remoteConfig/RemoteConfigPlugin.js.map +1 -0
  53. package/lib/commonjs/plugins/remoteConfig/remoteConfigStore.js +50 -0
  54. package/lib/commonjs/plugins/remoteConfig/remoteConfigStore.js.map +1 -0
  55. package/lib/commonjs/plugins/stateInspector/StateInspectorPlugin.js +378 -0
  56. package/lib/commonjs/plugins/stateInspector/StateInspectorPlugin.js.map +1 -0
  57. package/lib/commonjs/plugins/stateInspector/stateAdapterRegistry.js +62 -0
  58. package/lib/commonjs/plugins/stateInspector/stateAdapterRegistry.js.map +1 -0
  59. package/lib/commonjs/plugins/storageBrowser/StorageBrowserPlugin.js +496 -0
  60. package/lib/commonjs/plugins/storageBrowser/StorageBrowserPlugin.js.map +1 -0
  61. package/lib/commonjs/plugins/storageBrowser/storageAdapterRegistry.js +44 -0
  62. package/lib/commonjs/plugins/storageBrowser/storageAdapterRegistry.js.map +1 -0
  63. package/lib/commonjs/plugins/timeline/TimelinePlugin.js +294 -0
  64. package/lib/commonjs/plugins/timeline/TimelinePlugin.js.map +1 -0
  65. package/lib/commonjs/plugins/timeline/timelineStore.js +61 -0
  66. package/lib/commonjs/plugins/timeline/timelineStore.js.map +1 -0
  67. package/lib/module/core/DebugBubble.js +131 -0
  68. package/lib/module/core/DebugBubble.js.map +1 -0
  69. package/lib/module/core/DebugOverlay.js +176 -0
  70. package/lib/module/core/DebugOverlay.js.map +1 -0
  71. package/lib/module/core/DebuggerProvider.js +167 -0
  72. package/lib/module/core/DebuggerProvider.js.map +1 -0
  73. package/lib/module/core/GestureDetector.js +75 -0
  74. package/lib/module/core/GestureDetector.js.map +1 -0
  75. package/lib/module/core/PluginRegistry.js +116 -0
  76. package/lib/module/core/PluginRegistry.js.map +1 -0
  77. package/lib/module/core/theme.js +43 -0
  78. package/lib/module/core/theme.js.map +1 -0
  79. package/lib/module/core/types.js +2 -0
  80. package/lib/module/core/types.js.map +1 -0
  81. package/lib/module/core/useDebugger.js +21 -0
  82. package/lib/module/core/useDebugger.js.map +1 -0
  83. package/lib/module/core/utils.js +219 -0
  84. package/lib/module/core/utils.js.map +1 -0
  85. package/lib/module/index.js +44 -0
  86. package/lib/module/index.js.map +1 -0
  87. package/lib/module/plugins/console/ConsoleInterceptor.js +97 -0
  88. package/lib/module/plugins/console/ConsoleInterceptor.js.map +1 -0
  89. package/lib/module/plugins/console/ConsoleViewerPlugin.js +262 -0
  90. package/lib/module/plugins/console/ConsoleViewerPlugin.js.map +1 -0
  91. package/lib/module/plugins/crashReporter/CrashReporterPlugin.js +357 -0
  92. package/lib/module/plugins/crashReporter/CrashReporterPlugin.js.map +1 -0
  93. package/lib/module/plugins/customActions/CustomActionsPlugin.js +211 -0
  94. package/lib/module/plugins/customActions/CustomActionsPlugin.js.map +1 -0
  95. package/lib/module/plugins/customActions/actionStore.js +38 -0
  96. package/lib/module/plugins/customActions/actionStore.js.map +1 -0
  97. package/lib/module/plugins/deepLinkTester/DeepLinkTesterPlugin.js +261 -0
  98. package/lib/module/plugins/deepLinkTester/DeepLinkTesterPlugin.js.map +1 -0
  99. package/lib/module/plugins/deviceInfo/DeviceInfoPlugin.js +177 -0
  100. package/lib/module/plugins/deviceInfo/DeviceInfoPlugin.js.map +1 -0
  101. package/lib/module/plugins/featureFlags/FeatureFlagsPlugin.js +374 -0
  102. package/lib/module/plugins/featureFlags/FeatureFlagsPlugin.js.map +1 -0
  103. package/lib/module/plugins/featureFlags/flagStore.js +117 -0
  104. package/lib/module/plugins/featureFlags/flagStore.js.map +1 -0
  105. package/lib/module/plugins/navigationInspector/NavigationInspectorPlugin.js +243 -0
  106. package/lib/module/plugins/navigationInspector/NavigationInspectorPlugin.js.map +1 -0
  107. package/lib/module/plugins/navigationInspector/navigationStore.js +58 -0
  108. package/lib/module/plugins/navigationInspector/navigationStore.js.map +1 -0
  109. package/lib/module/plugins/network/NetworkInspectorPlugin.js +703 -0
  110. package/lib/module/plugins/network/NetworkInspectorPlugin.js.map +1 -0
  111. package/lib/module/plugins/network/NetworkInterceptor.js +299 -0
  112. package/lib/module/plugins/network/NetworkInterceptor.js.map +1 -0
  113. package/lib/module/plugins/performance/PerformanceMonitorPlugin.js +254 -0
  114. package/lib/module/plugins/performance/PerformanceMonitorPlugin.js.map +1 -0
  115. package/lib/module/plugins/remoteConfig/RemoteConfigPlugin.js +258 -0
  116. package/lib/module/plugins/remoteConfig/RemoteConfigPlugin.js.map +1 -0
  117. package/lib/module/plugins/remoteConfig/remoteConfigStore.js +43 -0
  118. package/lib/module/plugins/remoteConfig/remoteConfigStore.js.map +1 -0
  119. package/lib/module/plugins/stateInspector/StateInspectorPlugin.js +372 -0
  120. package/lib/module/plugins/stateInspector/StateInspectorPlugin.js.map +1 -0
  121. package/lib/module/plugins/stateInspector/stateAdapterRegistry.js +54 -0
  122. package/lib/module/plugins/stateInspector/stateAdapterRegistry.js.map +1 -0
  123. package/lib/module/plugins/storageBrowser/StorageBrowserPlugin.js +489 -0
  124. package/lib/module/plugins/storageBrowser/StorageBrowserPlugin.js.map +1 -0
  125. package/lib/module/plugins/storageBrowser/storageAdapterRegistry.js +36 -0
  126. package/lib/module/plugins/storageBrowser/storageAdapterRegistry.js.map +1 -0
  127. package/lib/module/plugins/timeline/TimelinePlugin.js +287 -0
  128. package/lib/module/plugins/timeline/TimelinePlugin.js.map +1 -0
  129. package/lib/module/plugins/timeline/timelineStore.js +54 -0
  130. package/lib/module/plugins/timeline/timelineStore.js.map +1 -0
  131. package/lib/typescript/core/DebugBubble.d.ts +22 -0
  132. package/lib/typescript/core/DebugBubble.d.ts.map +1 -0
  133. package/lib/typescript/core/DebugOverlay.d.ts +18 -0
  134. package/lib/typescript/core/DebugOverlay.d.ts.map +1 -0
  135. package/lib/typescript/core/DebuggerProvider.d.ts +25 -0
  136. package/lib/typescript/core/DebuggerProvider.d.ts.map +1 -0
  137. package/lib/typescript/core/GestureDetector.d.ts +20 -0
  138. package/lib/typescript/core/GestureDetector.d.ts.map +1 -0
  139. package/lib/typescript/core/PluginRegistry.d.ts +41 -0
  140. package/lib/typescript/core/PluginRegistry.d.ts.map +1 -0
  141. package/lib/typescript/core/theme.d.ts +11 -0
  142. package/lib/typescript/core/theme.d.ts.map +1 -0
  143. package/lib/typescript/core/types.d.ts +269 -0
  144. package/lib/typescript/core/types.d.ts.map +1 -0
  145. package/lib/typescript/core/useDebugger.d.ts +14 -0
  146. package/lib/typescript/core/useDebugger.d.ts.map +1 -0
  147. package/lib/typescript/core/utils.d.ts +46 -0
  148. package/lib/typescript/core/utils.d.ts.map +1 -0
  149. package/lib/typescript/index.d.ts +23 -0
  150. package/lib/typescript/index.d.ts.map +1 -0
  151. package/lib/typescript/plugins/console/ConsoleInterceptor.d.ts +32 -0
  152. package/lib/typescript/plugins/console/ConsoleInterceptor.d.ts.map +1 -0
  153. package/lib/typescript/plugins/console/ConsoleViewerPlugin.d.ts +3 -0
  154. package/lib/typescript/plugins/console/ConsoleViewerPlugin.d.ts.map +1 -0
  155. package/lib/typescript/plugins/crashReporter/CrashReporterPlugin.d.ts +3 -0
  156. package/lib/typescript/plugins/crashReporter/CrashReporterPlugin.d.ts.map +1 -0
  157. package/lib/typescript/plugins/customActions/CustomActionsPlugin.d.ts +3 -0
  158. package/lib/typescript/plugins/customActions/CustomActionsPlugin.d.ts.map +1 -0
  159. package/lib/typescript/plugins/customActions/actionStore.d.ts +16 -0
  160. package/lib/typescript/plugins/customActions/actionStore.d.ts.map +1 -0
  161. package/lib/typescript/plugins/deepLinkTester/DeepLinkTesterPlugin.d.ts +3 -0
  162. package/lib/typescript/plugins/deepLinkTester/DeepLinkTesterPlugin.d.ts.map +1 -0
  163. package/lib/typescript/plugins/deviceInfo/DeviceInfoPlugin.d.ts +3 -0
  164. package/lib/typescript/plugins/deviceInfo/DeviceInfoPlugin.d.ts.map +1 -0
  165. package/lib/typescript/plugins/featureFlags/FeatureFlagsPlugin.d.ts +3 -0
  166. package/lib/typescript/plugins/featureFlags/FeatureFlagsPlugin.d.ts.map +1 -0
  167. package/lib/typescript/plugins/featureFlags/flagStore.d.ts +45 -0
  168. package/lib/typescript/plugins/featureFlags/flagStore.d.ts.map +1 -0
  169. package/lib/typescript/plugins/navigationInspector/NavigationInspectorPlugin.d.ts +3 -0
  170. package/lib/typescript/plugins/navigationInspector/NavigationInspectorPlugin.d.ts.map +1 -0
  171. package/lib/typescript/plugins/navigationInspector/navigationStore.d.ts +30 -0
  172. package/lib/typescript/plugins/navigationInspector/navigationStore.d.ts.map +1 -0
  173. package/lib/typescript/plugins/network/NetworkInspectorPlugin.d.ts +3 -0
  174. package/lib/typescript/plugins/network/NetworkInspectorPlugin.d.ts.map +1 -0
  175. package/lib/typescript/plugins/network/NetworkInterceptor.d.ts +42 -0
  176. package/lib/typescript/plugins/network/NetworkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/plugins/performance/PerformanceMonitorPlugin.d.ts +3 -0
  178. package/lib/typescript/plugins/performance/PerformanceMonitorPlugin.d.ts.map +1 -0
  179. package/lib/typescript/plugins/remoteConfig/RemoteConfigPlugin.d.ts +3 -0
  180. package/lib/typescript/plugins/remoteConfig/RemoteConfigPlugin.d.ts.map +1 -0
  181. package/lib/typescript/plugins/remoteConfig/remoteConfigStore.d.ts +20 -0
  182. package/lib/typescript/plugins/remoteConfig/remoteConfigStore.d.ts.map +1 -0
  183. package/lib/typescript/plugins/stateInspector/StateInspectorPlugin.d.ts +3 -0
  184. package/lib/typescript/plugins/stateInspector/StateInspectorPlugin.d.ts.map +1 -0
  185. package/lib/typescript/plugins/stateInspector/stateAdapterRegistry.d.ts +34 -0
  186. package/lib/typescript/plugins/stateInspector/stateAdapterRegistry.d.ts.map +1 -0
  187. package/lib/typescript/plugins/storageBrowser/StorageBrowserPlugin.d.ts +3 -0
  188. package/lib/typescript/plugins/storageBrowser/StorageBrowserPlugin.d.ts.map +1 -0
  189. package/lib/typescript/plugins/storageBrowser/storageAdapterRegistry.d.ts +16 -0
  190. package/lib/typescript/plugins/storageBrowser/storageAdapterRegistry.d.ts.map +1 -0
  191. package/lib/typescript/plugins/timeline/TimelinePlugin.d.ts +3 -0
  192. package/lib/typescript/plugins/timeline/TimelinePlugin.d.ts.map +1 -0
  193. package/lib/typescript/plugins/timeline/timelineStore.d.ts +24 -0
  194. package/lib/typescript/plugins/timeline/timelineStore.d.ts.map +1 -0
  195. package/package.json +131 -0
  196. package/src/core/DebugBubble.tsx +149 -0
  197. package/src/core/DebugOverlay.tsx +211 -0
  198. package/src/core/DebuggerProvider.tsx +211 -0
  199. package/src/core/GestureDetector.tsx +95 -0
  200. package/src/core/PluginRegistry.ts +118 -0
  201. package/src/core/theme.ts +41 -0
  202. package/src/core/types.ts +339 -0
  203. package/src/core/useDebugger.ts +24 -0
  204. package/src/core/utils.ts +221 -0
  205. package/src/index.ts +67 -0
  206. package/src/plugins/console/ConsoleInterceptor.ts +110 -0
  207. package/src/plugins/console/ConsoleViewerPlugin.tsx +241 -0
  208. package/src/plugins/crashReporter/CrashReporterPlugin.tsx +316 -0
  209. package/src/plugins/customActions/CustomActionsPlugin.tsx +199 -0
  210. package/src/plugins/customActions/actionStore.ts +49 -0
  211. package/src/plugins/deepLinkTester/DeepLinkTesterPlugin.tsx +213 -0
  212. package/src/plugins/deviceInfo/DeviceInfoPlugin.tsx +153 -0
  213. package/src/plugins/featureFlags/FeatureFlagsPlugin.tsx +338 -0
  214. package/src/plugins/featureFlags/flagStore.ts +118 -0
  215. package/src/plugins/navigationInspector/NavigationInspectorPlugin.tsx +170 -0
  216. package/src/plugins/navigationInspector/navigationStore.ts +70 -0
  217. package/src/plugins/network/NetworkInspectorPlugin.tsx +598 -0
  218. package/src/plugins/network/NetworkInterceptor.ts +344 -0
  219. package/src/plugins/performance/PerformanceMonitorPlugin.tsx +194 -0
  220. package/src/plugins/remoteConfig/RemoteConfigPlugin.tsx +205 -0
  221. package/src/plugins/remoteConfig/remoteConfigStore.ts +48 -0
  222. package/src/plugins/stateInspector/StateInspectorPlugin.tsx +342 -0
  223. package/src/plugins/stateInspector/stateAdapterRegistry.ts +66 -0
  224. package/src/plugins/storageBrowser/StorageBrowserPlugin.tsx +410 -0
  225. package/src/plugins/storageBrowser/storageAdapterRegistry.ts +47 -0
  226. package/src/plugins/timeline/TimelinePlugin.tsx +242 -0
  227. package/src/plugins/timeline/timelineStore.ts +65 -0
@@ -0,0 +1,338 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ FlatList,
6
+ TouchableOpacity,
7
+ Switch,
8
+ TextInput,
9
+ StyleSheet,
10
+ ScrollView,
11
+ } from 'react-native';
12
+ import type { PluginComponentProps, FeatureFlag, DebuggerPlugin } from '../../core/types';
13
+ import { flagStore } from './flagStore';
14
+
15
+ const FeatureFlagsPanel: React.FC<PluginComponentProps> = ({ theme }) => {
16
+ const [flags, setFlags] = useState<FeatureFlag[]>([]);
17
+ const [searchQuery, setSearchQuery] = useState('');
18
+ const [groupFilter, setGroupFilter] = useState<string | null>(null);
19
+ const [editingFlag, setEditingFlag] = useState<string | null>(null);
20
+ const [editValue, setEditValue] = useState('');
21
+
22
+ useEffect(() => {
23
+ const unsubscribe = flagStore.subscribe(setFlags);
24
+ return unsubscribe;
25
+ }, []);
26
+
27
+ const groups = useMemo(() => {
28
+ const groupSet = new Set<string>();
29
+ for (const flag of flags) {
30
+ if (flag.group) groupSet.add(flag.group);
31
+ }
32
+ return Array.from(groupSet).sort();
33
+ }, [flags]);
34
+
35
+ const filteredFlags = useMemo(() => {
36
+ let filtered = flags;
37
+ if (groupFilter) {
38
+ filtered = filtered.filter((f) => f.group === groupFilter);
39
+ }
40
+ if (searchQuery) {
41
+ const q = searchQuery.toLowerCase();
42
+ filtered = filtered.filter(
43
+ (f) =>
44
+ f.key.toLowerCase().includes(q) ||
45
+ f.label?.toLowerCase().includes(q) ||
46
+ f.description?.toLowerCase().includes(q),
47
+ );
48
+ }
49
+ return filtered;
50
+ }, [flags, searchQuery, groupFilter]);
51
+
52
+ const handleToggle = useCallback((key: string, currentValue: boolean) => {
53
+ flagStore.override(key, !currentValue);
54
+ }, []);
55
+
56
+ const handleSaveEdit = useCallback(
57
+ (flag: FeatureFlag) => {
58
+ let parsed: FeatureFlag['currentValue'] = editValue;
59
+ if (flag.type === 'number') {
60
+ parsed = Number(editValue);
61
+ if (isNaN(parsed as number)) return;
62
+ } else if (flag.type === 'json') {
63
+ try {
64
+ parsed = JSON.parse(editValue);
65
+ } catch {
66
+ return;
67
+ }
68
+ }
69
+ flagStore.override(flag.key, parsed);
70
+ setEditingFlag(null);
71
+ },
72
+ [editValue],
73
+ );
74
+
75
+ const handleResetAll = useCallback(() => {
76
+ flagStore.resetAll();
77
+ }, []);
78
+
79
+ const isOverridden = useCallback((flag: FeatureFlag) => {
80
+ return JSON.stringify(flag.currentValue) !== JSON.stringify(flag.defaultValue);
81
+ }, []);
82
+
83
+ if (flags.length === 0) {
84
+ return (
85
+ <View style={styles.emptyContainer}>
86
+ <Text style={styles.emptyIcon}>🚩</Text>
87
+ <Text style={[styles.emptyTitle, { color: theme.text }]}>No Feature Flags</Text>
88
+ <Text style={[styles.emptyDesc, { color: theme.textMuted }]}>
89
+ Register flags using:{'\n\n'}
90
+ <Text style={{ fontFamily: 'monospace', color: theme.codeText }}>
91
+ {'registerFlag({\n key: "dark_mode",\n type: "boolean",\n defaultValue: false\n})'}
92
+ </Text>
93
+ </Text>
94
+ </View>
95
+ );
96
+ }
97
+
98
+ return (
99
+ <View style={styles.container}>
100
+ {/* Header */}
101
+ <View
102
+ style={[
103
+ styles.headerBar,
104
+ { backgroundColor: theme.surface, borderBottomColor: theme.border },
105
+ ]}
106
+ >
107
+ <Text style={[styles.headerText, { color: theme.text }]}>{flags.length} flags</Text>
108
+ <TouchableOpacity onPress={handleResetAll} activeOpacity={0.7}>
109
+ <Text style={[styles.resetText, { color: theme.warning }]}>Reset All</Text>
110
+ </TouchableOpacity>
111
+ </View>
112
+
113
+ {/* Groups */}
114
+ {groups.length > 0 && (
115
+ <ScrollView
116
+ horizontal
117
+ showsHorizontalScrollIndicator={false}
118
+ style={[styles.groupBar, { backgroundColor: theme.surface }]}
119
+ contentContainerStyle={styles.groupBarContent}
120
+ >
121
+ <TouchableOpacity
122
+ style={[
123
+ styles.groupChip,
124
+ {
125
+ backgroundColor: !groupFilter ? theme.accent : theme.surfaceAlt,
126
+ borderColor: theme.border,
127
+ },
128
+ ]}
129
+ onPress={() => setGroupFilter(null)}
130
+ activeOpacity={0.7}
131
+ >
132
+ <Text
133
+ style={[styles.groupChipText, { color: !groupFilter ? '#FFF' : theme.textSecondary }]}
134
+ >
135
+ All
136
+ </Text>
137
+ </TouchableOpacity>
138
+ {groups.map((group) => (
139
+ <TouchableOpacity
140
+ key={group}
141
+ style={[
142
+ styles.groupChip,
143
+ {
144
+ backgroundColor: groupFilter === group ? theme.accent : theme.surfaceAlt,
145
+ borderColor: theme.border,
146
+ },
147
+ ]}
148
+ onPress={() => setGroupFilter(groupFilter === group ? null : group)}
149
+ activeOpacity={0.7}
150
+ >
151
+ <Text
152
+ style={[
153
+ styles.groupChipText,
154
+ { color: groupFilter === group ? '#FFF' : theme.textSecondary },
155
+ ]}
156
+ >
157
+ {group}
158
+ </Text>
159
+ </TouchableOpacity>
160
+ ))}
161
+ </ScrollView>
162
+ )}
163
+
164
+ {/* Search */}
165
+ <View style={[styles.searchContainer, { backgroundColor: theme.surface }]}>
166
+ <TextInput
167
+ style={[
168
+ styles.searchInput,
169
+ { color: theme.text, backgroundColor: theme.surfaceAlt, borderColor: theme.border },
170
+ ]}
171
+ placeholder="Search flags..."
172
+ placeholderTextColor={theme.textMuted}
173
+ value={searchQuery}
174
+ onChangeText={setSearchQuery}
175
+ autoCapitalize="none"
176
+ />
177
+ </View>
178
+
179
+ {/* Flags List */}
180
+ <FlatList
181
+ data={filteredFlags}
182
+ keyExtractor={(item) => item.key}
183
+ renderItem={({ item }) => (
184
+ <View
185
+ style={[
186
+ styles.flagRow,
187
+ {
188
+ backgroundColor: isOverridden(item) ? `${theme.warning}10` : theme.surface,
189
+ borderBottomColor: theme.border,
190
+ },
191
+ ]}
192
+ >
193
+ <View style={styles.flagInfo}>
194
+ <View style={styles.flagNameRow}>
195
+ <Text style={[styles.flagKey, { color: theme.text }]}>
196
+ {item.label || item.key}
197
+ </Text>
198
+ {isOverridden(item) && (
199
+ <TouchableOpacity onPress={() => flagStore.reset(item.key)} activeOpacity={0.7}>
200
+ <Text style={[styles.resetFlag, { color: theme.warning }]}>Reset</Text>
201
+ </TouchableOpacity>
202
+ )}
203
+ </View>
204
+ {item.description && (
205
+ <Text style={[styles.flagDesc, { color: theme.textMuted }]}>
206
+ {item.description}
207
+ </Text>
208
+ )}
209
+ <Text style={[styles.flagMeta, { color: theme.textMuted }]}>
210
+ {item.key} • {item.type}
211
+ {item.group ? ` • ${item.group}` : ''}
212
+ </Text>
213
+ </View>
214
+ <View style={styles.flagControl}>
215
+ {item.type === 'boolean' ? (
216
+ <Switch
217
+ value={item.currentValue as boolean}
218
+ onValueChange={() => handleToggle(item.key, item.currentValue as boolean)}
219
+ trackColor={{ false: theme.border, true: theme.accent }}
220
+ thumbColor="#FFFFFF"
221
+ />
222
+ ) : editingFlag === item.key ? (
223
+ <View style={styles.editContainer}>
224
+ <TextInput
225
+ style={[
226
+ styles.editInput,
227
+ {
228
+ color: theme.text,
229
+ borderColor: theme.accent,
230
+ backgroundColor: theme.surfaceAlt,
231
+ },
232
+ ]}
233
+ value={editValue}
234
+ onChangeText={setEditValue}
235
+ autoFocus
236
+ autoCapitalize="none"
237
+ />
238
+ <View style={styles.editButtons}>
239
+ <TouchableOpacity onPress={() => handleSaveEdit(item)} activeOpacity={0.7}>
240
+ <Text style={[styles.editSave, { color: theme.success }]}>Save</Text>
241
+ </TouchableOpacity>
242
+ <TouchableOpacity onPress={() => setEditingFlag(null)} activeOpacity={0.7}>
243
+ <Text style={[styles.editCancel, { color: theme.textMuted }]}>Cancel</Text>
244
+ </TouchableOpacity>
245
+ </View>
246
+ </View>
247
+ ) : (
248
+ <TouchableOpacity
249
+ onPress={() => {
250
+ setEditingFlag(item.key);
251
+ setEditValue(
252
+ typeof item.currentValue === 'object'
253
+ ? JSON.stringify(item.currentValue, null, 2)
254
+ : String(item.currentValue),
255
+ );
256
+ }}
257
+ style={[styles.valueButton, { backgroundColor: theme.surfaceAlt }]}
258
+ activeOpacity={0.7}
259
+ >
260
+ <Text style={[styles.valueText, { color: theme.text }]} numberOfLines={1}>
261
+ {typeof item.currentValue === 'object'
262
+ ? JSON.stringify(item.currentValue)
263
+ : String(item.currentValue)}
264
+ </Text>
265
+ </TouchableOpacity>
266
+ )}
267
+ </View>
268
+ </View>
269
+ )}
270
+ />
271
+ </View>
272
+ );
273
+ };
274
+
275
+ const styles = StyleSheet.create({
276
+ container: { flex: 1 },
277
+ emptyContainer: {
278
+ flex: 1,
279
+ alignItems: 'center',
280
+ justifyContent: 'center',
281
+ paddingHorizontal: 32,
282
+ },
283
+ emptyIcon: { fontSize: 48, marginBottom: 16 },
284
+ emptyTitle: { fontSize: 16, fontWeight: '700', marginBottom: 8 },
285
+ emptyDesc: { fontSize: 13, textAlign: 'center', lineHeight: 20 },
286
+ headerBar: {
287
+ flexDirection: 'row',
288
+ justifyContent: 'space-between',
289
+ alignItems: 'center',
290
+ paddingHorizontal: 12,
291
+ paddingVertical: 8,
292
+ borderBottomWidth: StyleSheet.hairlineWidth,
293
+ },
294
+ headerText: { fontSize: 13, fontWeight: '600' },
295
+ resetText: { fontSize: 12, fontWeight: '700' },
296
+ groupBar: { maxHeight: 44 },
297
+ groupBarContent: { paddingHorizontal: 12, paddingVertical: 8, gap: 6 },
298
+ groupChip: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, borderWidth: 1 },
299
+ groupChipText: { fontSize: 11, fontWeight: '600' },
300
+ searchContainer: { paddingHorizontal: 12, paddingBottom: 8 },
301
+ searchInput: { height: 36, borderRadius: 8, paddingHorizontal: 12, fontSize: 13, borderWidth: 1 },
302
+ flagRow: {
303
+ paddingHorizontal: 12,
304
+ paddingVertical: 10,
305
+ borderBottomWidth: StyleSheet.hairlineWidth,
306
+ },
307
+ flagInfo: { marginBottom: 8 },
308
+ flagNameRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
309
+ flagKey: { fontSize: 14, fontWeight: '600' },
310
+ resetFlag: { fontSize: 11, fontWeight: '600' },
311
+ flagDesc: { fontSize: 12, marginTop: 2 },
312
+ flagMeta: { fontSize: 10, marginTop: 2, fontFamily: 'monospace' },
313
+ flagControl: { alignItems: 'flex-end' },
314
+ editContainer: { width: '100%' },
315
+ editInput: {
316
+ height: 36,
317
+ borderRadius: 8,
318
+ paddingHorizontal: 12,
319
+ fontSize: 13,
320
+ borderWidth: 1,
321
+ marginBottom: 8,
322
+ },
323
+ editButtons: { flexDirection: 'row', gap: 12, justifyContent: 'flex-end' },
324
+ editSave: { fontSize: 13, fontWeight: '700' },
325
+ editCancel: { fontSize: 13 },
326
+ valueButton: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, maxWidth: 200 },
327
+ valueText: { fontSize: 12, fontFamily: 'monospace' },
328
+ });
329
+
330
+ export function createFeatureFlagsPlugin(): DebuggerPlugin {
331
+ return {
332
+ id: 'feature-flags',
333
+ name: 'Flags',
334
+ icon: '🚩',
335
+ component: FeatureFlagsPanel,
336
+ order: 40,
337
+ };
338
+ }
@@ -0,0 +1,118 @@
1
+ import type { FeatureFlag } from '../../core/types';
2
+
3
+ type FlagListener = (flags: FeatureFlag[]) => void;
4
+
5
+ /**
6
+ * Feature Flag Store — In-memory registry for feature flags.
7
+ *
8
+ * Flags can be registered by the consumer app and toggled at runtime
9
+ * via the debugger overlay. Overrides persist in-memory (optionally in AsyncStorage).
10
+ */
11
+ class FlagStoreClass {
12
+ private flags: Map<string, FeatureFlag> = new Map();
13
+ private listeners: Set<FlagListener> = new Set();
14
+ private changeCallbacks: Map<string, (value: FeatureFlag['currentValue']) => void> = new Map();
15
+
16
+ /** Register a feature flag. */
17
+ register(
18
+ flag: Omit<FeatureFlag, 'currentValue'> & { currentValue?: FeatureFlag['currentValue'] },
19
+ ): void {
20
+ const existing = this.flags.get(flag.key);
21
+ this.flags.set(flag.key, {
22
+ ...flag,
23
+ currentValue: existing?.currentValue ?? flag.currentValue ?? flag.defaultValue,
24
+ });
25
+ this.notify();
26
+ }
27
+
28
+ /** Get a flag value. Returns the overridden value if set, otherwise the default. */
29
+ get(key: string): FeatureFlag['currentValue'] | undefined {
30
+ return this.flags.get(key)?.currentValue;
31
+ }
32
+
33
+ /** Get a flag definition. */
34
+ getFlag(key: string): FeatureFlag | undefined {
35
+ return this.flags.get(key);
36
+ }
37
+
38
+ /** Get all flags. */
39
+ getAll(): FeatureFlag[] {
40
+ return Array.from(this.flags.values());
41
+ }
42
+
43
+ /** Override a flag value. */
44
+ override(key: string, value: FeatureFlag['currentValue']): void {
45
+ const flag = this.flags.get(key);
46
+ if (!flag) return;
47
+ this.flags.set(key, { ...flag, currentValue: value });
48
+ // Fire change callback
49
+ const cb = this.changeCallbacks.get(key);
50
+ if (cb) cb(value);
51
+ this.notify();
52
+ }
53
+
54
+ /** Reset a single flag to its default value. */
55
+ reset(key: string): void {
56
+ const flag = this.flags.get(key);
57
+ if (!flag) return;
58
+ this.flags.set(key, { ...flag, currentValue: flag.defaultValue });
59
+ const cb = this.changeCallbacks.get(key);
60
+ if (cb) cb(flag.defaultValue);
61
+ this.notify();
62
+ }
63
+
64
+ /** Reset all flags to defaults. */
65
+ resetAll(): void {
66
+ for (const [key, flag] of this.flags) {
67
+ this.flags.set(key, { ...flag, currentValue: flag.defaultValue });
68
+ const cb = this.changeCallbacks.get(key);
69
+ if (cb) cb(flag.defaultValue);
70
+ }
71
+ this.notify();
72
+ }
73
+
74
+ /** Register a callback when a specific flag changes. */
75
+ onChange(key: string, callback: (value: FeatureFlag['currentValue']) => void): () => void {
76
+ this.changeCallbacks.set(key, callback);
77
+ return () => this.changeCallbacks.delete(key);
78
+ }
79
+
80
+ /** Subscribe to all flag changes. */
81
+ subscribe(listener: FlagListener): () => void {
82
+ this.listeners.add(listener);
83
+ listener(this.getAll());
84
+ return () => this.listeners.delete(listener);
85
+ }
86
+
87
+ private notify(): void {
88
+ const snapshot = this.getAll();
89
+ for (const listener of this.listeners) {
90
+ try {
91
+ listener(snapshot);
92
+ } catch {
93
+ /* ignore */
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ const flagStore = new FlagStoreClass();
100
+
101
+ /** Register a feature flag. */
102
+ export function registerFlag(
103
+ flag: Omit<FeatureFlag, 'currentValue'> & { currentValue?: FeatureFlag['currentValue'] },
104
+ ): void {
105
+ flagStore.register(flag);
106
+ }
107
+
108
+ /** Reset all flag overrides. */
109
+ export function resetFlags(): void {
110
+ flagStore.resetAll();
111
+ }
112
+
113
+ /** Get a flag's current value. */
114
+ export function getFlag(key: string): FeatureFlag['currentValue'] | undefined {
115
+ return flagStore.get(key);
116
+ }
117
+
118
+ export { flagStore };
@@ -0,0 +1,170 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
3
+ import type { PluginComponentProps, DebuggerPlugin } from '../../core/types';
4
+ import { navigationStore } from './navigationStore';
5
+ import { safeStringify, copyToClipboard, formatTimestamp } from '../../core/utils';
6
+
7
+ const NavigationInspectorPanel: React.FC<PluginComponentProps> = ({ theme }) => {
8
+ const [state, setState] = useState<Record<string, unknown> | null>(null);
9
+ const [currentRoute, setCurrentRoute] = useState<{
10
+ name: string;
11
+ params?: Record<string, unknown>;
12
+ } | null>(null);
13
+ const [history, setHistory] = useState<
14
+ { name: string; params?: Record<string, unknown>; timestamp: number }[]
15
+ >([]);
16
+ const [, forceUpdate] = useState(0);
17
+
18
+ const refresh = useCallback(() => {
19
+ setState(navigationStore.getState() as Record<string, unknown> | null);
20
+ setCurrentRoute(navigationStore.getCurrentRoute());
21
+ setHistory(navigationStore.getHistory());
22
+ forceUpdate((n) => n + 1);
23
+ }, []);
24
+
25
+ useEffect(() => {
26
+ const unsub = navigationStore.subscribe(refresh);
27
+ refresh();
28
+ const interval = setInterval(refresh, 2000);
29
+ return () => {
30
+ unsub();
31
+ clearInterval(interval);
32
+ };
33
+ }, [refresh]);
34
+
35
+ const hasRef = !!navigationStore.getRef();
36
+
37
+ if (!hasRef) {
38
+ return (
39
+ <View style={styles.emptyContainer}>
40
+ <Text style={styles.emptyIcon}>🧭</Text>
41
+ <Text style={[styles.emptyTitle, { color: theme.text }]}>No Navigation Ref</Text>
42
+ <Text style={[styles.emptyDesc, { color: theme.textMuted }]}>
43
+ Pass your navigation ref:{'\n\n'}
44
+ <Text style={{ fontFamily: 'monospace', color: theme.codeText }}>
45
+ {'setNavigationRef(navigationRef)'}
46
+ </Text>
47
+ </Text>
48
+ </View>
49
+ );
50
+ }
51
+
52
+ return (
53
+ <ScrollView style={styles.container}>
54
+ {/* Current Route */}
55
+ {currentRoute && (
56
+ <View style={[styles.section, { backgroundColor: theme.surface }]}>
57
+ <Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>CURRENT ROUTE</Text>
58
+ <View style={[styles.routeCard, { backgroundColor: theme.accent }]}>
59
+ <Text style={styles.routeName}>{currentRoute.name}</Text>
60
+ </View>
61
+ {currentRoute.params && Object.keys(currentRoute.params).length > 0 && (
62
+ <TouchableOpacity
63
+ style={[styles.paramsBlock, { backgroundColor: theme.codeBackground }]}
64
+ onLongPress={() => copyToClipboard(safeStringify(currentRoute.params))}
65
+ activeOpacity={0.7}
66
+ >
67
+ <Text style={[styles.paramsTitle, { color: theme.textSecondary }]}>Params</Text>
68
+ <Text style={[styles.paramsText, { color: theme.codeText }]} selectable>
69
+ {safeStringify(currentRoute.params)}
70
+ </Text>
71
+ </TouchableOpacity>
72
+ )}
73
+ </View>
74
+ )}
75
+
76
+ {/* Navigation State */}
77
+ {state && (
78
+ <View style={[styles.section, { backgroundColor: theme.surface }]}>
79
+ <View style={styles.sectionHeader}>
80
+ <Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>STATE TREE</Text>
81
+ <TouchableOpacity
82
+ onPress={() => copyToClipboard(safeStringify(state))}
83
+ activeOpacity={0.7}
84
+ >
85
+ <Text style={[styles.copyBtn, { color: theme.accent }]}>Copy</Text>
86
+ </TouchableOpacity>
87
+ </View>
88
+ <View style={[styles.codeBlock, { backgroundColor: theme.codeBackground }]}>
89
+ <Text style={[styles.codeText, { color: theme.codeText }]} selectable>
90
+ {String(safeStringify(state))}
91
+ </Text>
92
+ </View>
93
+ </View>
94
+ )}
95
+
96
+ {/* History */}
97
+ {history.length > 0 && (
98
+ <View style={[styles.section, { backgroundColor: theme.surface }]}>
99
+ <Text style={[styles.sectionTitle, { color: theme.textSecondary }]}>ROUTE HISTORY</Text>
100
+ {history.map((entry, i) => (
101
+ <View key={i} style={[styles.historyRow, { borderBottomColor: theme.border }]}>
102
+ <View style={styles.historyInfo}>
103
+ <Text style={[styles.historyRoute, { color: theme.text }]}>{entry.name}</Text>
104
+ <Text style={[styles.historyTime, { color: theme.textMuted }]}>
105
+ {formatTimestamp(entry.timestamp)}
106
+ </Text>
107
+ </View>
108
+ </View>
109
+ ))}
110
+ </View>
111
+ )}
112
+
113
+ <View style={{ height: 40 }} />
114
+ </ScrollView>
115
+ );
116
+ };
117
+
118
+ const styles = StyleSheet.create({
119
+ container: { flex: 1 },
120
+ emptyContainer: {
121
+ flex: 1,
122
+ alignItems: 'center',
123
+ justifyContent: 'center',
124
+ paddingHorizontal: 32,
125
+ },
126
+ emptyIcon: { fontSize: 48, marginBottom: 16 },
127
+ emptyTitle: { fontSize: 16, fontWeight: '700', marginBottom: 8 },
128
+ emptyDesc: { fontSize: 13, textAlign: 'center', lineHeight: 20 },
129
+ section: { margin: 12, borderRadius: 12, padding: 12 },
130
+ sectionTitle: {
131
+ fontSize: 11,
132
+ fontWeight: '700',
133
+ textTransform: 'uppercase',
134
+ letterSpacing: 1,
135
+ marginBottom: 8,
136
+ },
137
+ sectionHeader: {
138
+ flexDirection: 'row',
139
+ justifyContent: 'space-between',
140
+ alignItems: 'center',
141
+ marginBottom: 8,
142
+ },
143
+ copyBtn: { fontSize: 12, fontWeight: '600' },
144
+ routeCard: { padding: 12, borderRadius: 8, alignItems: 'center' },
145
+ routeName: { fontSize: 18, fontWeight: '800', color: '#FFF' },
146
+ paramsBlock: { padding: 12, borderRadius: 8, marginTop: 8 },
147
+ paramsTitle: { fontSize: 10, fontWeight: '700', marginBottom: 4, textTransform: 'uppercase' },
148
+ paramsText: { fontSize: 11, fontFamily: 'monospace', lineHeight: 16 },
149
+ codeBlock: { padding: 12, borderRadius: 8 },
150
+ codeText: { fontSize: 11, fontFamily: 'monospace', lineHeight: 16 },
151
+ historyRow: {
152
+ flexDirection: 'row',
153
+ alignItems: 'center',
154
+ paddingVertical: 8,
155
+ borderBottomWidth: StyleSheet.hairlineWidth,
156
+ },
157
+ historyInfo: { flex: 1, flexDirection: 'row', justifyContent: 'space-between' },
158
+ historyRoute: { fontSize: 13, fontWeight: '600' },
159
+ historyTime: { fontSize: 10 },
160
+ });
161
+
162
+ export function createNavigationInspectorPlugin(): DebuggerPlugin {
163
+ return {
164
+ id: 'navigation-inspector',
165
+ name: 'Nav',
166
+ icon: '🧭',
167
+ component: NavigationInspectorPanel,
168
+ order: 90,
169
+ };
170
+ }
@@ -0,0 +1,70 @@
1
+ type Listener = () => void;
2
+
3
+ class NavigationStoreClass {
4
+ private ref: { current: unknown } | null = null;
5
+ private history: { name: string; params?: Record<string, unknown>; timestamp: number }[] = [];
6
+ private listeners: Set<Listener> = new Set();
7
+ private maxHistory = 50;
8
+
9
+ setRef(ref: { current: unknown }): void {
10
+ this.ref = ref;
11
+ this.notify();
12
+ }
13
+
14
+ getRef(): unknown {
15
+ return this.ref?.current;
16
+ }
17
+
18
+ addHistoryEntry(name: string, params?: Record<string, unknown>): void {
19
+ this.history = [{ name, params, timestamp: Date.now() }, ...this.history].slice(
20
+ 0,
21
+ this.maxHistory,
22
+ );
23
+ this.notify();
24
+ }
25
+
26
+ getHistory() {
27
+ return [...this.history];
28
+ }
29
+
30
+ getState(): unknown {
31
+ try {
32
+ const nav = this.ref?.current as { getRootState?: () => unknown } | undefined;
33
+ return nav?.getRootState?.() ?? null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ getCurrentRoute(): { name: string; params?: Record<string, unknown> } | null {
40
+ try {
41
+ const nav = this.ref?.current as
42
+ | { getCurrentRoute?: () => { name: string; params?: Record<string, unknown> } }
43
+ | undefined;
44
+ return nav?.getCurrentRoute?.() ?? null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ subscribe(listener: Listener): () => void {
51
+ this.listeners.add(listener);
52
+ return () => this.listeners.delete(listener);
53
+ }
54
+
55
+ private notify(): void {
56
+ for (const l of this.listeners) {
57
+ try {
58
+ l();
59
+ } catch {
60
+ /* ignore */
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ export const navigationStore = new NavigationStoreClass();
67
+
68
+ export function setNavigationRef(ref: { current: unknown }): void {
69
+ navigationStore.setRef(ref);
70
+ }