react-native-debug-toolkit 3.3.8 → 3.5.1

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 (223) hide show
  1. package/README.md +40 -26
  2. package/README.zh-CN.md +52 -38
  3. package/android/src/main/java/com/reactnativedebugtoolkit/DebugToolkitNativeLogsModule.java +146 -0
  4. package/android/src/main/java/com/reactnativedebugtoolkit/ReactNativeDebugToolkitPackage.java +5 -3
  5. package/ios/DebugToolkitNativeLogs.mm +92 -0
  6. package/lib/commonjs/constants/logLevels.js +19 -0
  7. package/lib/commonjs/constants/logLevels.js.map +1 -0
  8. package/lib/commonjs/core/initialize.js +32 -19
  9. package/lib/commonjs/core/initialize.js.map +1 -1
  10. package/lib/commonjs/features/console/ConsoleLogTab.js +4 -15
  11. package/lib/commonjs/features/console/ConsoleLogTab.js.map +1 -1
  12. package/lib/commonjs/features/console/index.js +15 -8
  13. package/lib/commonjs/features/console/index.js.map +1 -1
  14. package/lib/commonjs/features/nativeLogs/NativeLogTab.js +156 -0
  15. package/lib/commonjs/features/nativeLogs/NativeLogTab.js.map +1 -0
  16. package/lib/commonjs/features/nativeLogs/index.js +97 -0
  17. package/lib/commonjs/features/nativeLogs/index.js.map +1 -0
  18. package/lib/commonjs/features/nativeLogs/nativeLogsBridge.js +71 -0
  19. package/lib/commonjs/features/nativeLogs/nativeLogsBridge.js.map +1 -0
  20. package/lib/commonjs/features/network/NetworkLogTab.js +90 -95
  21. package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
  22. package/lib/commonjs/features/network/index.js +7 -4
  23. package/lib/commonjs/features/network/index.js.map +1 -1
  24. package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js +1046 -0
  25. package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js.map +1 -0
  26. package/lib/commonjs/features/sessionHistory/index.js +104 -0
  27. package/lib/commonjs/features/sessionHistory/index.js.map +1 -0
  28. package/lib/commonjs/features/track/index.js +4 -3
  29. package/lib/commonjs/features/track/index.js.map +1 -1
  30. package/lib/commonjs/index.js +27 -0
  31. package/lib/commonjs/index.js.map +1 -1
  32. package/lib/commonjs/ui/DebugView.js +2 -0
  33. package/lib/commonjs/ui/DebugView.js.map +1 -1
  34. package/lib/commonjs/ui/panel/DebugPanel.js +67 -34
  35. package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
  36. package/lib/commonjs/ui/panel/FeatureIntroCard.js +151 -0
  37. package/lib/commonjs/ui/panel/FeatureIntroCard.js.map +1 -0
  38. package/lib/commonjs/ui/panel/FeatureRail.js +163 -0
  39. package/lib/commonjs/ui/panel/FeatureRail.js.map +1 -0
  40. package/lib/commonjs/ui/panel/FloatPanelView.js +119 -22
  41. package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
  42. package/lib/commonjs/ui/panel/buildFeatureSummary.js +207 -0
  43. package/lib/commonjs/ui/panel/buildFeatureSummary.js.map +1 -0
  44. package/lib/commonjs/ui/panel/filterFeatureSnapshot.js +43 -0
  45. package/lib/commonjs/ui/panel/filterFeatureSnapshot.js.map +1 -0
  46. package/lib/commonjs/ui/theme/colors.js +6 -0
  47. package/lib/commonjs/ui/theme/colors.js.map +1 -1
  48. package/lib/commonjs/utils/DaemonClient.js +30 -8
  49. package/lib/commonjs/utils/DaemonClient.js.map +1 -1
  50. package/lib/commonjs/utils/SessionManager.js +132 -0
  51. package/lib/commonjs/utils/SessionManager.js.map +1 -0
  52. package/lib/commonjs/utils/StorageAdapter.js +104 -0
  53. package/lib/commonjs/utils/StorageAdapter.js.map +1 -0
  54. package/lib/commonjs/utils/createChannelFeature.js +22 -5
  55. package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
  56. package/lib/commonjs/utils/createPersistedObservableStore.js +14 -8
  57. package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
  58. package/lib/commonjs/utils/debugPreferences.js +28 -5
  59. package/lib/commonjs/utils/debugPreferences.js.map +1 -1
  60. package/lib/commonjs/utils/deviceReport.js +5 -1
  61. package/lib/commonjs/utils/deviceReport.js.map +1 -1
  62. package/lib/commonjs/utils/logRuntime.js +32 -0
  63. package/lib/commonjs/utils/logRuntime.js.map +1 -0
  64. package/lib/module/constants/logLevels.js +15 -0
  65. package/lib/module/constants/logLevels.js.map +1 -0
  66. package/lib/module/core/initialize.js +32 -19
  67. package/lib/module/core/initialize.js.map +1 -1
  68. package/lib/module/features/console/ConsoleLogTab.js +1 -12
  69. package/lib/module/features/console/ConsoleLogTab.js.map +1 -1
  70. package/lib/module/features/console/index.js +15 -8
  71. package/lib/module/features/console/index.js.map +1 -1
  72. package/lib/module/features/nativeLogs/NativeLogTab.js +151 -0
  73. package/lib/module/features/nativeLogs/NativeLogTab.js.map +1 -0
  74. package/lib/module/features/nativeLogs/index.js +91 -0
  75. package/lib/module/features/nativeLogs/index.js.map +1 -0
  76. package/lib/module/features/nativeLogs/nativeLogsBridge.js +63 -0
  77. package/lib/module/features/nativeLogs/nativeLogsBridge.js.map +1 -0
  78. package/lib/module/features/network/NetworkLogTab.js +90 -95
  79. package/lib/module/features/network/NetworkLogTab.js.map +1 -1
  80. package/lib/module/features/network/index.js +7 -4
  81. package/lib/module/features/network/index.js.map +1 -1
  82. package/lib/module/features/sessionHistory/SessionHistoryTab.js +1041 -0
  83. package/lib/module/features/sessionHistory/SessionHistoryTab.js.map +1 -0
  84. package/lib/module/features/sessionHistory/index.js +100 -0
  85. package/lib/module/features/sessionHistory/index.js.map +1 -0
  86. package/lib/module/features/track/index.js +4 -3
  87. package/lib/module/features/track/index.js.map +1 -1
  88. package/lib/module/index.js +3 -0
  89. package/lib/module/index.js.map +1 -1
  90. package/lib/module/ui/DebugView.js +2 -0
  91. package/lib/module/ui/DebugView.js.map +1 -1
  92. package/lib/module/ui/panel/DebugPanel.js +67 -34
  93. package/lib/module/ui/panel/DebugPanel.js.map +1 -1
  94. package/lib/module/ui/panel/FeatureIntroCard.js +146 -0
  95. package/lib/module/ui/panel/FeatureIntroCard.js.map +1 -0
  96. package/lib/module/ui/panel/FeatureRail.js +158 -0
  97. package/lib/module/ui/panel/FeatureRail.js.map +1 -0
  98. package/lib/module/ui/panel/FloatPanelView.js +119 -22
  99. package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
  100. package/lib/module/ui/panel/buildFeatureSummary.js +203 -0
  101. package/lib/module/ui/panel/buildFeatureSummary.js.map +1 -0
  102. package/lib/module/ui/panel/filterFeatureSnapshot.js +39 -0
  103. package/lib/module/ui/panel/filterFeatureSnapshot.js.map +1 -0
  104. package/lib/module/ui/theme/colors.js +6 -0
  105. package/lib/module/ui/theme/colors.js.map +1 -1
  106. package/lib/module/utils/DaemonClient.js +30 -8
  107. package/lib/module/utils/DaemonClient.js.map +1 -1
  108. package/lib/module/utils/SessionManager.js +127 -0
  109. package/lib/module/utils/SessionManager.js.map +1 -0
  110. package/lib/module/utils/StorageAdapter.js +96 -0
  111. package/lib/module/utils/StorageAdapter.js.map +1 -0
  112. package/lib/module/utils/createChannelFeature.js +22 -5
  113. package/lib/module/utils/createChannelFeature.js.map +1 -1
  114. package/lib/module/utils/createPersistedObservableStore.js +14 -8
  115. package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
  116. package/lib/module/utils/debugPreferences.js +27 -5
  117. package/lib/module/utils/debugPreferences.js.map +1 -1
  118. package/lib/module/utils/deviceReport.js +4 -1
  119. package/lib/module/utils/deviceReport.js.map +1 -1
  120. package/lib/module/utils/logRuntime.js +26 -0
  121. package/lib/module/utils/logRuntime.js.map +1 -0
  122. package/lib/typescript/src/constants/logLevels.d.ts +3 -0
  123. package/lib/typescript/src/constants/logLevels.d.ts.map +1 -0
  124. package/lib/typescript/src/core/initialize.d.ts +6 -0
  125. package/lib/typescript/src/core/initialize.d.ts.map +1 -1
  126. package/lib/typescript/src/features/console/ConsoleLogTab.d.ts.map +1 -1
  127. package/lib/typescript/src/features/console/index.d.ts +2 -1
  128. package/lib/typescript/src/features/console/index.d.ts.map +1 -1
  129. package/lib/typescript/src/features/nativeLogs/NativeLogTab.d.ts +4 -0
  130. package/lib/typescript/src/features/nativeLogs/NativeLogTab.d.ts.map +1 -0
  131. package/lib/typescript/src/features/nativeLogs/index.d.ts +12 -0
  132. package/lib/typescript/src/features/nativeLogs/index.d.ts.map +1 -0
  133. package/lib/typescript/src/features/nativeLogs/nativeLogsBridge.d.ts +11 -0
  134. package/lib/typescript/src/features/nativeLogs/nativeLogsBridge.d.ts.map +1 -0
  135. package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
  136. package/lib/typescript/src/features/network/index.d.ts +2 -1
  137. package/lib/typescript/src/features/network/index.d.ts.map +1 -1
  138. package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts +20 -0
  139. package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts.map +1 -0
  140. package/lib/typescript/src/features/sessionHistory/index.d.ts +4 -0
  141. package/lib/typescript/src/features/sessionHistory/index.d.ts.map +1 -0
  142. package/lib/typescript/src/features/track/index.d.ts +2 -1
  143. package/lib/typescript/src/features/track/index.d.ts.map +1 -1
  144. package/lib/typescript/src/index.d.ts +7 -1
  145. package/lib/typescript/src/index.d.ts.map +1 -1
  146. package/lib/typescript/src/types/feature.d.ts +1 -1
  147. package/lib/typescript/src/types/feature.d.ts.map +1 -1
  148. package/lib/typescript/src/types/index.d.ts +3 -1
  149. package/lib/typescript/src/types/index.d.ts.map +1 -1
  150. package/lib/typescript/src/types/logs.d.ts +15 -0
  151. package/lib/typescript/src/types/logs.d.ts.map +1 -1
  152. package/lib/typescript/src/ui/DebugView.d.ts.map +1 -1
  153. package/lib/typescript/src/ui/panel/DebugPanel.d.ts +3 -1
  154. package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
  155. package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts +14 -0
  156. package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts.map +1 -0
  157. package/lib/typescript/src/ui/panel/FeatureRail.d.ts +16 -0
  158. package/lib/typescript/src/ui/panel/FeatureRail.d.ts.map +1 -0
  159. package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
  160. package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts +13 -0
  161. package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts.map +1 -0
  162. package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts +3 -0
  163. package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts.map +1 -0
  164. package/lib/typescript/src/ui/theme/colors.d.ts +5 -0
  165. package/lib/typescript/src/ui/theme/colors.d.ts.map +1 -1
  166. package/lib/typescript/src/utils/DaemonClient.d.ts +7 -1
  167. package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
  168. package/lib/typescript/src/utils/SessionManager.d.ts +30 -0
  169. package/lib/typescript/src/utils/SessionManager.d.ts.map +1 -0
  170. package/lib/typescript/src/utils/StorageAdapter.d.ts +38 -0
  171. package/lib/typescript/src/utils/StorageAdapter.d.ts.map +1 -0
  172. package/lib/typescript/src/utils/createChannelFeature.d.ts +2 -0
  173. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
  174. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +4 -1
  175. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
  176. package/lib/typescript/src/utils/debugPreferences.d.ts +1 -3
  177. package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
  178. package/lib/typescript/src/utils/deviceReport.d.ts +1 -0
  179. package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
  180. package/lib/typescript/src/utils/logRuntime.d.ts +13 -0
  181. package/lib/typescript/src/utils/logRuntime.d.ts.map +1 -0
  182. package/node/daemon/src/console/console.html +18 -0
  183. package/node/mcp/src/logs.js +1 -1
  184. package/package.json +9 -1
  185. package/src/constants/logLevels.ts +13 -0
  186. package/src/core/initialize.ts +54 -25
  187. package/src/features/console/ConsoleLogTab.tsx +1 -14
  188. package/src/features/console/index.ts +18 -8
  189. package/src/features/nativeLogs/NativeLogTab.tsx +66 -0
  190. package/src/features/nativeLogs/index.ts +94 -0
  191. package/src/features/nativeLogs/nativeLogsBridge.ts +51 -0
  192. package/src/features/network/NetworkLogTab.tsx +60 -71
  193. package/src/features/network/index.ts +12 -3
  194. package/src/features/sessionHistory/SessionHistoryTab.tsx +693 -0
  195. package/src/features/sessionHistory/index.ts +102 -0
  196. package/src/features/track/index.ts +10 -3
  197. package/src/index.ts +16 -0
  198. package/src/types/feature.ts +3 -1
  199. package/src/types/index.ts +13 -0
  200. package/src/types/logs.ts +17 -0
  201. package/src/ui/DebugView.tsx +2 -0
  202. package/src/ui/panel/DebugPanel.tsx +60 -30
  203. package/src/ui/panel/FeatureIntroCard.tsx +147 -0
  204. package/src/ui/panel/FeatureRail.tsx +165 -0
  205. package/src/ui/panel/FloatPanelView.tsx +123 -15
  206. package/src/ui/panel/buildFeatureSummary.ts +288 -0
  207. package/src/ui/panel/filterFeatureSnapshot.ts +51 -0
  208. package/src/ui/theme/colors.ts +7 -0
  209. package/src/utils/DaemonClient.ts +33 -5
  210. package/src/utils/SessionManager.ts +174 -0
  211. package/src/utils/StorageAdapter.ts +135 -0
  212. package/src/utils/createChannelFeature.ts +28 -6
  213. package/src/utils/createPersistedObservableStore.ts +18 -10
  214. package/src/utils/debugPreferences.ts +38 -7
  215. package/src/utils/deviceReport.ts +5 -1
  216. package/src/utils/logRuntime.ts +39 -0
  217. package/lib/commonjs/ui/panel/FeatureTabBar.js +0 -182
  218. package/lib/commonjs/ui/panel/FeatureTabBar.js.map +0 -1
  219. package/lib/module/ui/panel/FeatureTabBar.js +0 -177
  220. package/lib/module/ui/panel/FeatureTabBar.js.map +0 -1
  221. package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts +0 -13
  222. package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts.map +0 -1
  223. package/src/ui/panel/FeatureTabBar.tsx +0 -204
@@ -0,0 +1,165 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, Pressable } from 'react-native';
3
+ import { Colors } from '../theme/colors';
4
+
5
+ // ─── Label Model ──────────────────────────────────────
6
+
7
+ const SHORT_LABEL_MAP: Record<string, string> = {
8
+ network: 'network',
9
+ console: 'console',
10
+ native: 'native',
11
+ navigation: 'nav',
12
+ zustand: 'zustand',
13
+ track: 'track',
14
+ clipboard: 'clip',
15
+ environment: 'env',
16
+ devConnect: 'dev',
17
+ sessionHistory: 'session',
18
+ thirdPartyLibs: 'libs',
19
+ };
20
+
21
+ export function shortLabelForFeature(label: string, id: string): string {
22
+ const mapped = SHORT_LABEL_MAP[id];
23
+ if (mapped) return mapped;
24
+ const trimmed = label.trim();
25
+ return trimmed.toLowerCase().slice(0, 7);
26
+ }
27
+
28
+ // ─── Rail Component ───────────────────────────────────
29
+
30
+ export interface RailItem {
31
+ id: string;
32
+ label: string;
33
+ dotColor?: string | null;
34
+ count?: number;
35
+ }
36
+
37
+ interface FeatureRailProps {
38
+ items: RailItem[];
39
+ activeIndex: number;
40
+ onSelectTab: (index: number) => void;
41
+ }
42
+
43
+ export function FeatureRail({ items, activeIndex, onSelectTab }: FeatureRailProps) {
44
+ return (
45
+ <View style={styles.rail}>
46
+ <ScrollView
47
+ showsVerticalScrollIndicator={false}
48
+ contentContainerStyle={styles.scrollContent}
49
+ >
50
+ {items.map((item, index) => {
51
+ const isActive = index === activeIndex;
52
+ const short = shortLabelForFeature(item.label, item.id);
53
+ const dotColor = item.dotColor ?? null;
54
+ const hasCount = item.count != null && item.count > 0;
55
+ return (
56
+ <Pressable
57
+ key={item.id}
58
+ onPress={() => onSelectTab(index)}
59
+ style={[styles.item, isActive && styles.activeItem]}
60
+ accessibilityRole="tab"
61
+ accessibilityLabel={item.label}
62
+ accessibilityState={{ selected: isActive }}
63
+ >
64
+ {isActive && <View style={styles.activeBar} />}
65
+ <Text
66
+ style={[styles.itemName, isActive && styles.activeItemName]}
67
+ numberOfLines={1}
68
+ >
69
+ {short}
70
+ </Text>
71
+ <View style={styles.itemMeta}>
72
+ {dotColor && (
73
+ <View style={[styles.dot, { backgroundColor: dotColor }]} />
74
+ )}
75
+ {hasCount && (
76
+ <View style={styles.countPill}>
77
+ <Text style={styles.countText}>{item.count}</Text>
78
+ </View>
79
+ )}
80
+ </View>
81
+ </Pressable>
82
+ );
83
+ })}
84
+ </ScrollView>
85
+ </View>
86
+ );
87
+ }
88
+
89
+ const RAIL_WIDTH = 80;
90
+
91
+ const styles = StyleSheet.create({
92
+ rail: {
93
+ width: RAIL_WIDTH,
94
+ backgroundColor: Colors.railBackground,
95
+ borderRightWidth: StyleSheet.hairlineWidth,
96
+ borderRightColor: Colors.panelDivider,
97
+ },
98
+ scrollContent: {
99
+ paddingVertical: 6,
100
+ paddingHorizontal: 6,
101
+ gap: 3,
102
+ },
103
+ item: {
104
+ minHeight: 56,
105
+ borderRadius: 8,
106
+ justifyContent: 'center',
107
+ alignItems: 'center',
108
+ paddingVertical: 8,
109
+ paddingHorizontal: 4,
110
+ position: 'relative',
111
+ overflow: 'hidden',
112
+ },
113
+ activeItem: {
114
+ backgroundColor: Colors.surface,
115
+ elevation: 2,
116
+ shadowColor: '#000',
117
+ shadowOffset: { width: 0, height: 2 },
118
+ shadowOpacity: 0.06,
119
+ shadowRadius: 4,
120
+ },
121
+ activeBar: {
122
+ position: 'absolute',
123
+ left: 0,
124
+ top: 10,
125
+ bottom: 10,
126
+ width: 3,
127
+ borderRadius: 1.5,
128
+ backgroundColor: Colors.primary,
129
+ },
130
+ itemName: {
131
+ fontSize: 11,
132
+ fontWeight: '700',
133
+ color: Colors.textSecondary,
134
+ letterSpacing: 0.2,
135
+ },
136
+ activeItemName: {
137
+ color: Colors.text,
138
+ fontWeight: '800',
139
+ },
140
+ itemMeta: {
141
+ marginTop: 4,
142
+ flexDirection: 'row',
143
+ alignItems: 'center',
144
+ gap: 3,
145
+ },
146
+ dot: {
147
+ width: 6,
148
+ height: 6,
149
+ borderRadius: 3,
150
+ },
151
+ countPill: {
152
+ minWidth: 18,
153
+ height: 14,
154
+ borderRadius: 7,
155
+ backgroundColor: Colors.background,
156
+ paddingHorizontal: 4,
157
+ alignItems: 'center',
158
+ justifyContent: 'center',
159
+ },
160
+ countText: {
161
+ fontSize: 10,
162
+ fontWeight: '800',
163
+ color: Colors.textSecondary,
164
+ },
165
+ });
@@ -6,11 +6,15 @@ import {
6
6
  Animated,
7
7
  } from 'react-native';
8
8
  import type { AnyDebugFeature } from '../../types';
9
+ import { Colors } from '../theme/colors';
9
10
  import { getPreference, setPreference, KEYS } from '../../utils/debugPreferences';
10
11
  import { FloatIcon } from '../floating/FloatIcon';
11
12
  import { DebugPanel } from './DebugPanel';
12
- import { FeatureTabBar } from './FeatureTabBar';
13
- import type { TabItem } from './FeatureTabBar';
13
+ import { FeatureRail } from './FeatureRail';
14
+ import type { RailItem } from './FeatureRail';
15
+ import { FeatureIntroCard } from './FeatureIntroCard';
16
+ import { buildFeatureSummary } from './buildFeatureSummary';
17
+ import { filterFeatureSnapshot } from './filterFeatureSnapshot';
14
18
  import { resolveStoredTabIndex } from './tabPersistence';
15
19
  import { useTabAnimation } from './useTabAnimation';
16
20
 
@@ -40,6 +44,57 @@ class DebugErrorBoundary extends Component<
40
44
  }
41
45
  }
42
46
 
47
+ // ─── Snapshot helpers ──────────────────────────────────
48
+
49
+ function snapshotCount(feature: AnyDebugFeature): number | undefined {
50
+ try {
51
+ const snap = feature.getSnapshot();
52
+ if (Array.isArray(snap)) return snap.length;
53
+ if (snap && typeof snap === 'object') {
54
+ const obj = snap as Record<string, unknown>;
55
+ if (Array.isArray(obj.items)) return obj.items.length;
56
+ if (Array.isArray(obj.logs)) return obj.logs.length;
57
+ if (Array.isArray(obj.entries)) return obj.entries.length;
58
+ if (Array.isArray(obj.environments)) return obj.environments.length;
59
+ }
60
+ } catch { /* ignore */ }
61
+ return undefined;
62
+ }
63
+
64
+ interface PanelConnectionStatus {
65
+ label: string;
66
+ color: string;
67
+ }
68
+
69
+ interface DevConnectSnapshot {
70
+ isSimulator?: boolean;
71
+ computerHost?: string;
72
+ daemonPort?: string;
73
+ streaming?: boolean;
74
+ }
75
+
76
+ function buildPanelConnectionStatus(features: AnyDebugFeature[]): PanelConnectionStatus {
77
+ const devConnect = features.find((f) => f.name === 'devConnect');
78
+ if (!devConnect) {
79
+ return { label: 'offline desktop sync unavailable', color: Colors.textLight };
80
+ }
81
+
82
+ try {
83
+ const snap = (devConnect.getSnapshot() ?? {}) as DevConnectSnapshot;
84
+ const host = snap.isSimulator ? 'localhost' : snap.computerHost?.trim();
85
+ const port = snap.daemonPort?.trim();
86
+ const target = host && port ? `${host}:${port}` : host || (port ? `port ${port}` : 'not configured');
87
+ return {
88
+ label: `${snap.streaming ? 'live' : 'offline'} ${target}`,
89
+ color: snap.streaming ? Colors.success : Colors.textLight,
90
+ };
91
+ } catch {
92
+ return { label: 'offline desktop sync unavailable', color: Colors.textLight };
93
+ }
94
+ }
95
+
96
+ // ─── Main Component ────────────────────────────────────
97
+
43
98
  interface FloatPanelViewProps {
44
99
  features: AnyDebugFeature[];
45
100
  panelOpen: boolean;
@@ -50,6 +105,8 @@ interface FloatPanelViewProps {
50
105
 
51
106
  export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel, onClearAll }: FloatPanelViewProps) {
52
107
  const [activeTab, setActiveTab] = useState(0);
108
+ const [searchQuery, setSearchQuery] = useState('');
109
+ const [filterBad, setFilterBad] = useState(false);
53
110
  const tabLoaded = useRef(false);
54
111
 
55
112
  // Restore last tab on mount
@@ -74,6 +131,8 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
74
131
  tabCount: features.length,
75
132
  onTabChange: useCallback((index: number) => {
76
133
  tabLoaded.current = true;
134
+ setSearchQuery('');
135
+ setFilterBad(false);
77
136
  setActiveTab(index);
78
137
  const featureName = features[index]?.name;
79
138
  if (featureName) {
@@ -115,7 +174,30 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
115
174
 
116
175
  // Badge (first feature that returns one)
117
176
  const envBadge = features.map((f) => f.badge?.()).find((b) => b != null) ?? null;
118
- const tabs: TabItem[] = features.map((f) => ({ label: f.label, id: f.name }));
177
+
178
+ // Rail items with counts
179
+ const railItems: RailItem[] = features.map((f) => {
180
+ const b = f.badge?.();
181
+ return { id: f.name, label: f.label, dotColor: b?.color ?? null, count: snapshotCount(f) };
182
+ });
183
+
184
+ const panelConnectionStatus = buildPanelConnectionStatus(features);
185
+
186
+ const handleClearAll = useCallback(() => {
187
+ setSearchQuery('');
188
+ setFilterBad(false);
189
+ onClearAll();
190
+ }, [onClearAll]);
191
+
192
+ // Active feature + summary
193
+ const activeFeature = features[activeTab];
194
+ const activeSnapshot = activeFeature?.getSnapshot();
195
+ const activeSummary = activeFeature ? buildFeatureSummary(activeFeature, activeSnapshot) : null;
196
+
197
+ // Filtered snapshot — reuse activeSnapshot to avoid double getSnapshot()
198
+ const filteredSnapshot = activeFeature
199
+ ? filterFeatureSnapshot(activeFeature, activeSnapshot, searchQuery, filterBad ? 'bad' : 'all')
200
+ : null;
119
201
 
120
202
  // Render active feature content
121
203
  const renderFeatureContent = () => {
@@ -124,7 +206,7 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
124
206
  }
125
207
  const feature = features[activeTab];
126
208
  if (!feature) return <Text style={styles.emptyText}>Feature not found</Text>;
127
- const snapshot = feature.getSnapshot();
209
+ const snapshot = filteredSnapshot;
128
210
  const TabComponent = feature.renderContent;
129
211
  if (TabComponent) return <TabComponent snapshot={snapshot} feature={feature} />;
130
212
  return (
@@ -134,6 +216,8 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
134
216
  );
135
217
  };
136
218
 
219
+ const showSearch = activeSummary ? (activeSummary.count != null && activeSummary.count > 0) : false;
220
+
137
221
  return (
138
222
  <DebugErrorBoundary onError={onClosePanel}>
139
223
  <View style={styles.container} pointerEvents="box-none">
@@ -141,18 +225,35 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
141
225
  {panelOpen && (
142
226
  <DebugPanel
143
227
  onClose={onClosePanel}
144
- onClearAll={onClearAll}
228
+ onClearAll={handleClearAll}
229
+ syncLabel={panelConnectionStatus.label}
230
+ syncColor={panelConnectionStatus.color}
145
231
  >
146
- <FeatureTabBar tabs={tabs} activeIndex={activeTab} onSelectTab={switchTab} />
147
- <Animated.View
148
- style={[
149
- styles.contentContainer,
150
- { opacity: contentOpacity, transform: [{ translateX: contentTranslateX }] },
151
- ]}
152
- {...panHandlers}
153
- >
154
- {renderFeatureContent()}
155
- </Animated.View>
232
+ <View style={styles.bodyRow}>
233
+ <FeatureRail items={railItems} activeIndex={activeTab} onSelectTab={switchTab} />
234
+ <View style={styles.contentColumn}>
235
+ {activeFeature && activeSummary && (
236
+ <FeatureIntroCard
237
+ title={activeFeature.label}
238
+ summary={activeSummary}
239
+ filterBad={filterBad}
240
+ onFilterBad={setFilterBad}
241
+ searchQuery={searchQuery}
242
+ onSearchChange={setSearchQuery}
243
+ showSearch={showSearch}
244
+ />
245
+ )}
246
+ <Animated.View
247
+ style={[
248
+ styles.contentContainer,
249
+ { opacity: contentOpacity, transform: [{ translateX: contentTranslateX }] },
250
+ ]}
251
+ {...panHandlers}
252
+ >
253
+ {renderFeatureContent()}
254
+ </Animated.View>
255
+ </View>
256
+ </View>
156
257
  </DebugPanel>
157
258
  )}
158
259
  </View>
@@ -169,6 +270,13 @@ const styles = StyleSheet.create({
169
270
  bottom: 0,
170
271
  zIndex: 999,
171
272
  },
273
+ bodyRow: {
274
+ flex: 1,
275
+ flexDirection: 'row',
276
+ },
277
+ contentColumn: {
278
+ flex: 1,
279
+ },
172
280
  contentContainer: { flex: 1 },
173
281
  emptyText: {
174
282
  padding: 20,
@@ -0,0 +1,288 @@
1
+ import type { AnyDebugFeature } from '../../types';
2
+
3
+ export interface FeatureSummary {
4
+ capabilityText: string;
5
+ count?: number;
6
+ badCount?: number;
7
+ latestLabel?: string;
8
+ statusLabel?: string;
9
+ statusColor?: string;
10
+ filterMode?: 'all' | 'bad';
11
+ supportsBadFilter: boolean;
12
+ }
13
+
14
+ export function buildFeatureSummary(
15
+ feature: AnyDebugFeature,
16
+ snapshot: unknown,
17
+ ): FeatureSummary {
18
+ const name = feature.name;
19
+
20
+ if (name === 'network') return buildNetworkSummary(snapshot);
21
+ if (name === 'console') return buildConsoleSummary(snapshot);
22
+ if (name === 'navigation') return buildNavigationSummary(snapshot);
23
+ if (name === 'zustand') return buildZustandSummary(snapshot);
24
+ if (name === 'track') return buildTrackSummary(snapshot);
25
+ if (name === 'clipboard') return buildClipboardSummary(snapshot);
26
+ if (name === 'environment') return buildEnvironmentSummary(snapshot);
27
+ if (name === 'devConnect') return buildDevConnectSummary(snapshot);
28
+ if (name === 'sessionHistory') return buildSessionHistorySummary(snapshot);
29
+ if (name === 'thirdPartyLibs') return buildThirdPartyLibsSummary(snapshot);
30
+ if (name === 'native') return buildNativeSummary(snapshot);
31
+
32
+ return buildUnknownSummary(snapshot);
33
+ }
34
+
35
+ // ─── Per-feature builders ──────────────────────────────
36
+
37
+ interface NetworkItem {
38
+ request?: { method?: string; url?: string };
39
+ response?: { status?: number };
40
+ error?: string;
41
+ }
42
+
43
+ function buildNetworkSummary(snapshot: unknown): FeatureSummary {
44
+ const items = asArray(snapshot);
45
+ const count = items.length;
46
+ let badCount = 0;
47
+ let latestLabel: string | undefined;
48
+
49
+ if (count > 0) {
50
+ for (const item of items) {
51
+ const n = item as NetworkItem;
52
+ if (n.error || (n.response?.status ?? 0) >= 400) badCount++;
53
+ }
54
+ const last = items[items.length - 1] as NetworkItem;
55
+ const method = last.request?.method?.toUpperCase() ?? '';
56
+ const url = last.request?.url ?? '';
57
+ const status = last.response?.status;
58
+ const path = extractPath(url);
59
+ latestLabel = [method, path, status].filter(Boolean).join(' ');
60
+ }
61
+
62
+ return {
63
+ capabilityText: 'HTTP capture, status, duration, request and response body',
64
+ count,
65
+ badCount: badCount > 0 ? badCount : undefined,
66
+ latestLabel,
67
+ supportsBadFilter: count > 0,
68
+ };
69
+ }
70
+
71
+ interface ConsoleItem {
72
+ level?: string;
73
+ data?: unknown[];
74
+ }
75
+
76
+ function buildConsoleSummary(snapshot: unknown): FeatureSummary {
77
+ const items = asArray(snapshot);
78
+ const count = items.length;
79
+ let badCount = 0;
80
+ let latestLabel: string | undefined;
81
+
82
+ if (count > 0) {
83
+ for (const item of items) {
84
+ const lvl = (item as ConsoleItem).level ?? '';
85
+ if (lvl === 'warn' || lvl === 'error' || lvl === 'fatal') badCount++;
86
+ }
87
+ const last = items[items.length - 1] as ConsoleItem;
88
+ latestLabel = formatConsoleMessage(last.data);
89
+ }
90
+
91
+ return {
92
+ capabilityText: 'Console log capture with level filtering',
93
+ count,
94
+ badCount: badCount > 0 ? badCount : undefined,
95
+ latestLabel,
96
+ supportsBadFilter: count > 0,
97
+ };
98
+ }
99
+
100
+ interface NavigationItem {
101
+ from?: string;
102
+ to?: string;
103
+ }
104
+
105
+ function buildNavigationSummary(snapshot: unknown): FeatureSummary {
106
+ const items = asArray(snapshot);
107
+ const count = items.length;
108
+ let latestLabel: string | undefined;
109
+
110
+ if (count > 0) {
111
+ const last = items[items.length - 1] as NavigationItem;
112
+ const from = last.from ?? '?';
113
+ const to = last.to ?? '?';
114
+ latestLabel = `${from} → ${to}`;
115
+ }
116
+
117
+ return {
118
+ capabilityText: 'Screen navigation tracking with route history',
119
+ count,
120
+ latestLabel,
121
+ supportsBadFilter: false,
122
+ };
123
+ }
124
+
125
+ interface ZustandItem {
126
+ action?: string;
127
+ storeName?: string;
128
+ }
129
+
130
+ function buildZustandSummary(snapshot: unknown): FeatureSummary {
131
+ const items = asArray(snapshot);
132
+ const count = items.length;
133
+ let latestLabel: string | undefined;
134
+
135
+ if (count > 0) {
136
+ const last = items[items.length - 1] as ZustandItem;
137
+ const parts = [last.action, last.storeName].filter(Boolean);
138
+ latestLabel = parts.join(' @ ') || undefined;
139
+ }
140
+
141
+ return {
142
+ capabilityText: 'State change tracking for Zustand stores',
143
+ count,
144
+ latestLabel,
145
+ supportsBadFilter: false,
146
+ };
147
+ }
148
+
149
+ interface TrackItem {
150
+ eventName?: string;
151
+ }
152
+
153
+ function buildTrackSummary(snapshot: unknown): FeatureSummary {
154
+ const items = asArray(snapshot);
155
+ const count = items.length;
156
+ let latestLabel: string | undefined;
157
+
158
+ if (count > 0) {
159
+ const last = items[items.length - 1] as TrackItem;
160
+ latestLabel = last.eventName;
161
+ }
162
+
163
+ return {
164
+ capabilityText: 'Analytics event tracking and inspection',
165
+ count,
166
+ latestLabel,
167
+ supportsBadFilter: false,
168
+ };
169
+ }
170
+
171
+ function buildClipboardSummary(_snapshot: unknown): FeatureSummary {
172
+ return {
173
+ capabilityText: 'Clipboard event monitoring',
174
+ supportsBadFilter: false,
175
+ };
176
+ }
177
+
178
+ interface EnvironmentSnap {
179
+ environments?: Array<{ id: string; label: string }>;
180
+ currentEnvironmentId?: string | null;
181
+ }
182
+
183
+ function buildEnvironmentSummary(snapshot: unknown): FeatureSummary {
184
+ const env = (snapshot ?? {}) as EnvironmentSnap;
185
+ const envs = env.environments ?? [];
186
+ const current = envs.find((e) => e.id === env.currentEnvironmentId);
187
+
188
+ return {
189
+ capabilityText: 'Environment configuration and switching',
190
+ count: envs.length || undefined,
191
+ latestLabel: current?.label,
192
+ statusLabel: current?.label,
193
+ supportsBadFilter: false,
194
+ };
195
+ }
196
+
197
+ interface DevConnectSnap {
198
+ streaming?: boolean;
199
+ computerHost?: string;
200
+ daemonPort?: string;
201
+ }
202
+
203
+ function buildDevConnectSummary(snapshot: unknown): FeatureSummary {
204
+ const s = (snapshot ?? {}) as DevConnectSnap;
205
+ const host = [s.computerHost, s.daemonPort].filter(Boolean).join(':');
206
+
207
+ return {
208
+ capabilityText: 'Desktop sync with daemon connection',
209
+ statusLabel: host ? `${s.streaming ? 'live' : 'offline'} ${host}` : s.streaming ? 'live' : undefined,
210
+ statusColor: s.streaming ? '#34C759' : undefined,
211
+ supportsBadFilter: false,
212
+ };
213
+ }
214
+
215
+ interface SessionHistorySnap {
216
+ sessions?: unknown[];
217
+ currentSessionId?: string;
218
+ logCounts?: Record<string, unknown>;
219
+ }
220
+
221
+ function buildSessionHistorySummary(snapshot: unknown): FeatureSummary {
222
+ const s = (snapshot ?? {}) as SessionHistorySnap;
223
+ const count = s.sessions?.length;
224
+ return {
225
+ capabilityText: 'Session log recording and replay',
226
+ count: count || undefined,
227
+ latestLabel: s.currentSessionId,
228
+ supportsBadFilter: false,
229
+ };
230
+ }
231
+
232
+ function buildThirdPartyLibsSummary(snapshot: unknown): FeatureSummary {
233
+ const items = asArray(snapshot);
234
+ return {
235
+ capabilityText: 'Third-party library inspection and management',
236
+ count: items.length || undefined,
237
+ supportsBadFilter: false,
238
+ };
239
+ }
240
+
241
+ function buildNativeSummary(snapshot: unknown): FeatureSummary {
242
+ const items = asArray(snapshot);
243
+ let badCount = 0;
244
+ for (const item of items) {
245
+ const lvl = (item as { level?: string }).level ?? '';
246
+ if (lvl === 'warn' || lvl === 'error' || lvl === 'fatal') badCount++;
247
+ }
248
+ return {
249
+ capabilityText: 'Native log capture',
250
+ count: items.length || undefined,
251
+ badCount: badCount > 0 ? badCount : undefined,
252
+ supportsBadFilter: items.length > 0,
253
+ };
254
+ }
255
+
256
+ function buildUnknownSummary(snapshot: unknown): FeatureSummary {
257
+ const items = asArray(snapshot);
258
+ return {
259
+ capabilityText: `${items.length} item${items.length !== 1 ? 's' : ''} captured`,
260
+ count: items.length || undefined,
261
+ supportsBadFilter: false,
262
+ };
263
+ }
264
+
265
+ // ─── Helpers ───────────────────────────────────────────
266
+
267
+ function asArray(snap: unknown): unknown[] {
268
+ if (Array.isArray(snap)) return snap;
269
+ return [];
270
+ }
271
+
272
+ function extractPath(url: string): string {
273
+ try {
274
+ const u = new URL(url);
275
+ return u.pathname + u.search;
276
+ } catch {
277
+ return url;
278
+ }
279
+ }
280
+
281
+ function formatConsoleMessage(data: unknown[] | undefined): string | undefined {
282
+ if (!data || data.length === 0) return undefined;
283
+ const joined = data
284
+ .map((d) => (typeof d === 'string' ? d : JSON.stringify(d)))
285
+ .join(' ')
286
+ .slice(0, 60);
287
+ return joined || undefined;
288
+ }
@@ -0,0 +1,51 @@
1
+ import type { AnyDebugFeature } from '../../types';
2
+
3
+ export function filterFeatureSnapshot(
4
+ feature: AnyDebugFeature,
5
+ snapshot: unknown,
6
+ query: string,
7
+ filterMode: 'all' | 'bad',
8
+ ): unknown {
9
+ if (!Array.isArray(snapshot)) return snapshot;
10
+
11
+ const name = feature.name;
12
+ const supportsBad =
13
+ name === 'network' || name === 'console' || name === 'native';
14
+
15
+ return (snapshot as unknown[]).filter((item) => {
16
+ if (filterMode === 'bad' && supportsBad) {
17
+ if (!isBad(name, item as Record<string, unknown>)) return false;
18
+ }
19
+ if (query) {
20
+ const hay = extractSearchableText(item).toLowerCase();
21
+ if (!hay.includes(query.toLowerCase())) return false;
22
+ }
23
+ return true;
24
+ });
25
+ }
26
+
27
+ function isBad(
28
+ featureName: string,
29
+ item: Record<string, unknown>,
30
+ ): boolean {
31
+ if (featureName === 'network') {
32
+ const resp = item.response as Record<string, unknown> | undefined;
33
+ return !!(item.error || ((resp?.status as number) ?? 0) >= 400);
34
+ }
35
+ // console, native
36
+ const lvl = (item.level as string) ?? '';
37
+ return lvl === 'warn' || lvl === 'error' || lvl === 'fatal';
38
+ }
39
+
40
+ function extractSearchableText(item: unknown): string {
41
+ if (item == null) return '';
42
+ if (typeof item === 'string') return item;
43
+ if (typeof item === 'number' || typeof item === 'boolean') return String(item);
44
+ if (Array.isArray(item)) return item.map(extractSearchableText).join(' ');
45
+ const parts: string[] = [];
46
+ for (const val of Object.values(item as Record<string, unknown>)) {
47
+ const t = extractSearchableText(val);
48
+ if (t) parts.push(t);
49
+ }
50
+ return parts.join(' ');
51
+ }