react-native-debug-toolkit 3.3.8 → 3.5.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 (192) hide show
  1. package/lib/commonjs/constants/logLevels.js +19 -0
  2. package/lib/commonjs/constants/logLevels.js.map +1 -0
  3. package/lib/commonjs/core/initialize.js +30 -19
  4. package/lib/commonjs/core/initialize.js.map +1 -1
  5. package/lib/commonjs/features/console/ConsoleLogTab.js +4 -15
  6. package/lib/commonjs/features/console/ConsoleLogTab.js.map +1 -1
  7. package/lib/commonjs/features/console/index.js +15 -8
  8. package/lib/commonjs/features/console/index.js.map +1 -1
  9. package/lib/commonjs/features/network/NetworkLogTab.js +91 -93
  10. package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
  11. package/lib/commonjs/features/network/index.js +7 -4
  12. package/lib/commonjs/features/network/index.js.map +1 -1
  13. package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js +1044 -0
  14. package/lib/commonjs/features/sessionHistory/SessionHistoryTab.js.map +1 -0
  15. package/lib/commonjs/features/sessionHistory/index.js +103 -0
  16. package/lib/commonjs/features/sessionHistory/index.js.map +1 -0
  17. package/lib/commonjs/features/track/index.js +4 -3
  18. package/lib/commonjs/features/track/index.js.map +1 -1
  19. package/lib/commonjs/index.js +20 -0
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/ui/DebugView.js +1 -0
  22. package/lib/commonjs/ui/DebugView.js.map +1 -1
  23. package/lib/commonjs/ui/panel/DebugPanel.js +67 -34
  24. package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
  25. package/lib/commonjs/ui/panel/FeatureIntroCard.js +131 -0
  26. package/lib/commonjs/ui/panel/FeatureIntroCard.js.map +1 -0
  27. package/lib/commonjs/ui/panel/FeatureRail.js +163 -0
  28. package/lib/commonjs/ui/panel/FeatureRail.js.map +1 -0
  29. package/lib/commonjs/ui/panel/FloatPanelView.js +147 -22
  30. package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
  31. package/lib/commonjs/ui/panel/buildFeatureSummary.js +207 -0
  32. package/lib/commonjs/ui/panel/buildFeatureSummary.js.map +1 -0
  33. package/lib/commonjs/ui/panel/filterFeatureSnapshot.js +43 -0
  34. package/lib/commonjs/ui/panel/filterFeatureSnapshot.js.map +1 -0
  35. package/lib/commonjs/ui/theme/colors.js +6 -0
  36. package/lib/commonjs/ui/theme/colors.js.map +1 -1
  37. package/lib/commonjs/utils/DaemonClient.js +30 -8
  38. package/lib/commonjs/utils/DaemonClient.js.map +1 -1
  39. package/lib/commonjs/utils/SessionManager.js +132 -0
  40. package/lib/commonjs/utils/SessionManager.js.map +1 -0
  41. package/lib/commonjs/utils/StorageAdapter.js +104 -0
  42. package/lib/commonjs/utils/StorageAdapter.js.map +1 -0
  43. package/lib/commonjs/utils/createChannelFeature.js +22 -5
  44. package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
  45. package/lib/commonjs/utils/createPersistedObservableStore.js +14 -8
  46. package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
  47. package/lib/commonjs/utils/debugPreferences.js +28 -5
  48. package/lib/commonjs/utils/debugPreferences.js.map +1 -1
  49. package/lib/commonjs/utils/deviceReport.js +5 -1
  50. package/lib/commonjs/utils/deviceReport.js.map +1 -1
  51. package/lib/commonjs/utils/logRuntime.js +32 -0
  52. package/lib/commonjs/utils/logRuntime.js.map +1 -0
  53. package/lib/module/constants/logLevels.js +15 -0
  54. package/lib/module/constants/logLevels.js.map +1 -0
  55. package/lib/module/core/initialize.js +30 -19
  56. package/lib/module/core/initialize.js.map +1 -1
  57. package/lib/module/features/console/ConsoleLogTab.js +1 -12
  58. package/lib/module/features/console/ConsoleLogTab.js.map +1 -1
  59. package/lib/module/features/console/index.js +15 -8
  60. package/lib/module/features/console/index.js.map +1 -1
  61. package/lib/module/features/network/NetworkLogTab.js +91 -93
  62. package/lib/module/features/network/NetworkLogTab.js.map +1 -1
  63. package/lib/module/features/network/index.js +7 -4
  64. package/lib/module/features/network/index.js.map +1 -1
  65. package/lib/module/features/sessionHistory/SessionHistoryTab.js +1039 -0
  66. package/lib/module/features/sessionHistory/SessionHistoryTab.js.map +1 -0
  67. package/lib/module/features/sessionHistory/index.js +99 -0
  68. package/lib/module/features/sessionHistory/index.js.map +1 -0
  69. package/lib/module/features/track/index.js +4 -3
  70. package/lib/module/features/track/index.js.map +1 -1
  71. package/lib/module/index.js +3 -0
  72. package/lib/module/index.js.map +1 -1
  73. package/lib/module/ui/DebugView.js +1 -0
  74. package/lib/module/ui/DebugView.js.map +1 -1
  75. package/lib/module/ui/panel/DebugPanel.js +67 -34
  76. package/lib/module/ui/panel/DebugPanel.js.map +1 -1
  77. package/lib/module/ui/panel/FeatureIntroCard.js +126 -0
  78. package/lib/module/ui/panel/FeatureIntroCard.js.map +1 -0
  79. package/lib/module/ui/panel/FeatureRail.js +158 -0
  80. package/lib/module/ui/panel/FeatureRail.js.map +1 -0
  81. package/lib/module/ui/panel/FloatPanelView.js +148 -23
  82. package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
  83. package/lib/module/ui/panel/buildFeatureSummary.js +203 -0
  84. package/lib/module/ui/panel/buildFeatureSummary.js.map +1 -0
  85. package/lib/module/ui/panel/filterFeatureSnapshot.js +39 -0
  86. package/lib/module/ui/panel/filterFeatureSnapshot.js.map +1 -0
  87. package/lib/module/ui/theme/colors.js +6 -0
  88. package/lib/module/ui/theme/colors.js.map +1 -1
  89. package/lib/module/utils/DaemonClient.js +30 -8
  90. package/lib/module/utils/DaemonClient.js.map +1 -1
  91. package/lib/module/utils/SessionManager.js +127 -0
  92. package/lib/module/utils/SessionManager.js.map +1 -0
  93. package/lib/module/utils/StorageAdapter.js +96 -0
  94. package/lib/module/utils/StorageAdapter.js.map +1 -0
  95. package/lib/module/utils/createChannelFeature.js +22 -5
  96. package/lib/module/utils/createChannelFeature.js.map +1 -1
  97. package/lib/module/utils/createPersistedObservableStore.js +14 -8
  98. package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
  99. package/lib/module/utils/debugPreferences.js +27 -5
  100. package/lib/module/utils/debugPreferences.js.map +1 -1
  101. package/lib/module/utils/deviceReport.js +4 -1
  102. package/lib/module/utils/deviceReport.js.map +1 -1
  103. package/lib/module/utils/logRuntime.js +26 -0
  104. package/lib/module/utils/logRuntime.js.map +1 -0
  105. package/lib/typescript/src/constants/logLevels.d.ts +3 -0
  106. package/lib/typescript/src/constants/logLevels.d.ts.map +1 -0
  107. package/lib/typescript/src/core/initialize.d.ts +4 -0
  108. package/lib/typescript/src/core/initialize.d.ts.map +1 -1
  109. package/lib/typescript/src/features/console/ConsoleLogTab.d.ts.map +1 -1
  110. package/lib/typescript/src/features/console/index.d.ts +2 -1
  111. package/lib/typescript/src/features/console/index.d.ts.map +1 -1
  112. package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
  113. package/lib/typescript/src/features/network/index.d.ts +2 -1
  114. package/lib/typescript/src/features/network/index.d.ts.map +1 -1
  115. package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts +20 -0
  116. package/lib/typescript/src/features/sessionHistory/SessionHistoryTab.d.ts.map +1 -0
  117. package/lib/typescript/src/features/sessionHistory/index.d.ts +4 -0
  118. package/lib/typescript/src/features/sessionHistory/index.d.ts.map +1 -0
  119. package/lib/typescript/src/features/track/index.d.ts +2 -1
  120. package/lib/typescript/src/features/track/index.d.ts.map +1 -1
  121. package/lib/typescript/src/index.d.ts +4 -0
  122. package/lib/typescript/src/index.d.ts.map +1 -1
  123. package/lib/typescript/src/types/feature.d.ts +1 -1
  124. package/lib/typescript/src/types/feature.d.ts.map +1 -1
  125. package/lib/typescript/src/types/index.d.ts +2 -0
  126. package/lib/typescript/src/types/index.d.ts.map +1 -1
  127. package/lib/typescript/src/ui/DebugView.d.ts.map +1 -1
  128. package/lib/typescript/src/ui/panel/DebugPanel.d.ts +3 -1
  129. package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
  130. package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts +11 -0
  131. package/lib/typescript/src/ui/panel/FeatureIntroCard.d.ts.map +1 -0
  132. package/lib/typescript/src/ui/panel/FeatureRail.d.ts +16 -0
  133. package/lib/typescript/src/ui/panel/FeatureRail.d.ts.map +1 -0
  134. package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
  135. package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts +13 -0
  136. package/lib/typescript/src/ui/panel/buildFeatureSummary.d.ts.map +1 -0
  137. package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts +3 -0
  138. package/lib/typescript/src/ui/panel/filterFeatureSnapshot.d.ts.map +1 -0
  139. package/lib/typescript/src/ui/theme/colors.d.ts +5 -0
  140. package/lib/typescript/src/ui/theme/colors.d.ts.map +1 -1
  141. package/lib/typescript/src/utils/DaemonClient.d.ts +7 -1
  142. package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
  143. package/lib/typescript/src/utils/SessionManager.d.ts +30 -0
  144. package/lib/typescript/src/utils/SessionManager.d.ts.map +1 -0
  145. package/lib/typescript/src/utils/StorageAdapter.d.ts +38 -0
  146. package/lib/typescript/src/utils/StorageAdapter.d.ts.map +1 -0
  147. package/lib/typescript/src/utils/createChannelFeature.d.ts +2 -0
  148. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
  149. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +4 -1
  150. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
  151. package/lib/typescript/src/utils/debugPreferences.d.ts +1 -3
  152. package/lib/typescript/src/utils/debugPreferences.d.ts.map +1 -1
  153. package/lib/typescript/src/utils/deviceReport.d.ts +1 -0
  154. package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
  155. package/lib/typescript/src/utils/logRuntime.d.ts +13 -0
  156. package/lib/typescript/src/utils/logRuntime.d.ts.map +1 -0
  157. package/package.json +9 -1
  158. package/src/constants/logLevels.ts +13 -0
  159. package/src/core/initialize.ts +49 -25
  160. package/src/features/console/ConsoleLogTab.tsx +1 -14
  161. package/src/features/console/index.ts +18 -8
  162. package/src/features/network/NetworkLogTab.tsx +61 -71
  163. package/src/features/network/index.ts +12 -3
  164. package/src/features/sessionHistory/SessionHistoryTab.tsx +691 -0
  165. package/src/features/sessionHistory/index.ts +102 -0
  166. package/src/features/track/index.ts +10 -3
  167. package/src/index.ts +11 -0
  168. package/src/types/feature.ts +2 -1
  169. package/src/types/index.ts +10 -0
  170. package/src/ui/DebugView.tsx +1 -0
  171. package/src/ui/panel/DebugPanel.tsx +60 -30
  172. package/src/ui/panel/FeatureIntroCard.tsx +127 -0
  173. package/src/ui/panel/FeatureRail.tsx +165 -0
  174. package/src/ui/panel/FloatPanelView.tsx +154 -15
  175. package/src/ui/panel/buildFeatureSummary.ts +288 -0
  176. package/src/ui/panel/filterFeatureSnapshot.ts +51 -0
  177. package/src/ui/theme/colors.ts +7 -0
  178. package/src/utils/DaemonClient.ts +33 -5
  179. package/src/utils/SessionManager.ts +174 -0
  180. package/src/utils/StorageAdapter.ts +135 -0
  181. package/src/utils/createChannelFeature.ts +28 -6
  182. package/src/utils/createPersistedObservableStore.ts +18 -10
  183. package/src/utils/debugPreferences.ts +38 -7
  184. package/src/utils/deviceReport.ts +5 -1
  185. package/src/utils/logRuntime.ts +39 -0
  186. package/lib/commonjs/ui/panel/FeatureTabBar.js +0 -182
  187. package/lib/commonjs/ui/panel/FeatureTabBar.js.map +0 -1
  188. package/lib/module/ui/panel/FeatureTabBar.js +0 -177
  189. package/lib/module/ui/panel/FeatureTabBar.js.map +0 -1
  190. package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts +0 -13
  191. package/lib/typescript/src/ui/panel/FeatureTabBar.d.ts.map +0 -1
  192. package/src/ui/panel/FeatureTabBar.tsx +0 -204
@@ -2,15 +2,20 @@ import React, { Component, useCallback, useEffect, useRef, useState } from 'reac
2
2
  import {
3
3
  View,
4
4
  Text,
5
+ TextInput,
5
6
  StyleSheet,
6
7
  Animated,
7
8
  } from 'react-native';
8
9
  import type { AnyDebugFeature } from '../../types';
10
+ import { Colors } from '../theme/colors';
9
11
  import { getPreference, setPreference, KEYS } from '../../utils/debugPreferences';
10
12
  import { FloatIcon } from '../floating/FloatIcon';
11
13
  import { DebugPanel } from './DebugPanel';
12
- import { FeatureTabBar } from './FeatureTabBar';
13
- import type { TabItem } from './FeatureTabBar';
14
+ import { FeatureRail } from './FeatureRail';
15
+ import type { RailItem } from './FeatureRail';
16
+ import { FeatureIntroCard } from './FeatureIntroCard';
17
+ import { buildFeatureSummary } from './buildFeatureSummary';
18
+ import { filterFeatureSnapshot } from './filterFeatureSnapshot';
14
19
  import { resolveStoredTabIndex } from './tabPersistence';
15
20
  import { useTabAnimation } from './useTabAnimation';
16
21
 
@@ -40,6 +45,57 @@ class DebugErrorBoundary extends Component<
40
45
  }
41
46
  }
42
47
 
48
+ // ─── Snapshot helpers ──────────────────────────────────
49
+
50
+ function snapshotCount(feature: AnyDebugFeature): number | undefined {
51
+ try {
52
+ const snap = feature.getSnapshot();
53
+ if (Array.isArray(snap)) return snap.length;
54
+ if (snap && typeof snap === 'object') {
55
+ const obj = snap as Record<string, unknown>;
56
+ if (Array.isArray(obj.items)) return obj.items.length;
57
+ if (Array.isArray(obj.logs)) return obj.logs.length;
58
+ if (Array.isArray(obj.entries)) return obj.entries.length;
59
+ if (Array.isArray(obj.environments)) return obj.environments.length;
60
+ }
61
+ } catch { /* ignore */ }
62
+ return undefined;
63
+ }
64
+
65
+ interface PanelConnectionStatus {
66
+ label: string;
67
+ color: string;
68
+ }
69
+
70
+ interface DevConnectSnapshot {
71
+ isSimulator?: boolean;
72
+ computerHost?: string;
73
+ daemonPort?: string;
74
+ streaming?: boolean;
75
+ }
76
+
77
+ function buildPanelConnectionStatus(features: AnyDebugFeature[]): PanelConnectionStatus {
78
+ const devConnect = features.find((f) => f.name === 'devConnect');
79
+ if (!devConnect) {
80
+ return { label: 'offline desktop sync unavailable', color: Colors.textLight };
81
+ }
82
+
83
+ try {
84
+ const snap = (devConnect.getSnapshot() ?? {}) as DevConnectSnapshot;
85
+ const host = snap.isSimulator ? 'localhost' : snap.computerHost?.trim();
86
+ const port = snap.daemonPort?.trim();
87
+ const target = host && port ? `${host}:${port}` : host || (port ? `port ${port}` : 'not configured');
88
+ return {
89
+ label: `${snap.streaming ? 'live' : 'offline'} ${target}`,
90
+ color: snap.streaming ? Colors.success : Colors.textLight,
91
+ };
92
+ } catch {
93
+ return { label: 'offline desktop sync unavailable', color: Colors.textLight };
94
+ }
95
+ }
96
+
97
+ // ─── Main Component ────────────────────────────────────
98
+
43
99
  interface FloatPanelViewProps {
44
100
  features: AnyDebugFeature[];
45
101
  panelOpen: boolean;
@@ -50,6 +106,8 @@ interface FloatPanelViewProps {
50
106
 
51
107
  export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel, onClearAll }: FloatPanelViewProps) {
52
108
  const [activeTab, setActiveTab] = useState(0);
109
+ const [searchQuery, setSearchQuery] = useState('');
110
+ const [filterBad, setFilterBad] = useState(false);
53
111
  const tabLoaded = useRef(false);
54
112
 
55
113
  // Restore last tab on mount
@@ -74,6 +132,8 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
74
132
  tabCount: features.length,
75
133
  onTabChange: useCallback((index: number) => {
76
134
  tabLoaded.current = true;
135
+ setSearchQuery('');
136
+ setFilterBad(false);
77
137
  setActiveTab(index);
78
138
  const featureName = features[index]?.name;
79
139
  if (featureName) {
@@ -115,7 +175,30 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
115
175
 
116
176
  // Badge (first feature that returns one)
117
177
  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 }));
178
+
179
+ // Rail items with counts
180
+ const railItems: RailItem[] = features.map((f) => {
181
+ const b = f.badge?.();
182
+ return { id: f.name, label: f.label, dotColor: b?.color ?? null, count: snapshotCount(f) };
183
+ });
184
+
185
+ const panelConnectionStatus = buildPanelConnectionStatus(features);
186
+
187
+ const handleClearAll = useCallback(() => {
188
+ setSearchQuery('');
189
+ setFilterBad(false);
190
+ onClearAll();
191
+ }, [onClearAll]);
192
+
193
+ // Active feature + summary
194
+ const activeFeature = features[activeTab];
195
+ const activeSnapshot = activeFeature?.getSnapshot();
196
+ const activeSummary = activeFeature ? buildFeatureSummary(activeFeature, activeSnapshot) : null;
197
+
198
+ // Filtered snapshot — reuse activeSnapshot to avoid double getSnapshot()
199
+ const filteredSnapshot = activeFeature
200
+ ? filterFeatureSnapshot(activeFeature, activeSnapshot, searchQuery, filterBad ? 'bad' : 'all')
201
+ : null;
119
202
 
120
203
  // Render active feature content
121
204
  const renderFeatureContent = () => {
@@ -124,7 +207,7 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
124
207
  }
125
208
  const feature = features[activeTab];
126
209
  if (!feature) return <Text style={styles.emptyText}>Feature not found</Text>;
127
- const snapshot = feature.getSnapshot();
210
+ const snapshot = filteredSnapshot;
128
211
  const TabComponent = feature.renderContent;
129
212
  if (TabComponent) return <TabComponent snapshot={snapshot} feature={feature} />;
130
213
  return (
@@ -134,6 +217,8 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
134
217
  );
135
218
  };
136
219
 
220
+ const showSearch = activeSummary ? (activeSummary.count != null && activeSummary.count > 0) : false;
221
+
137
222
  return (
138
223
  <DebugErrorBoundary onError={onClosePanel}>
139
224
  <View style={styles.container} pointerEvents="box-none">
@@ -141,18 +226,44 @@ export function FloatPanelView({ features, panelOpen, onOpenPanel, onClosePanel,
141
226
  {panelOpen && (
142
227
  <DebugPanel
143
228
  onClose={onClosePanel}
144
- onClearAll={onClearAll}
229
+ onClearAll={handleClearAll}
230
+ syncLabel={panelConnectionStatus.label}
231
+ syncColor={panelConnectionStatus.color}
145
232
  >
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>
233
+ <View style={styles.bodyRow}>
234
+ <FeatureRail items={railItems} activeIndex={activeTab} onSelectTab={switchTab} />
235
+ <View style={styles.contentColumn}>
236
+ {activeFeature && activeSummary && (
237
+ <FeatureIntroCard
238
+ title={activeFeature.label}
239
+ summary={activeSummary}
240
+ filterBad={filterBad}
241
+ onFilterBad={setFilterBad}
242
+ />
243
+ )}
244
+ {showSearch && (
245
+ <View style={styles.toolbar}>
246
+ <TextInput
247
+ style={styles.searchInput}
248
+ placeholder="Search..."
249
+ placeholderTextColor={Colors.textLight}
250
+ value={searchQuery}
251
+ onChangeText={setSearchQuery}
252
+ returnKeyType="search"
253
+ />
254
+ </View>
255
+ )}
256
+ <Animated.View
257
+ style={[
258
+ styles.contentContainer,
259
+ { opacity: contentOpacity, transform: [{ translateX: contentTranslateX }] },
260
+ ]}
261
+ {...panHandlers}
262
+ >
263
+ {renderFeatureContent()}
264
+ </Animated.View>
265
+ </View>
266
+ </View>
156
267
  </DebugPanel>
157
268
  )}
158
269
  </View>
@@ -169,6 +280,34 @@ const styles = StyleSheet.create({
169
280
  bottom: 0,
170
281
  zIndex: 999,
171
282
  },
283
+ bodyRow: {
284
+ flex: 1,
285
+ flexDirection: 'row',
286
+ },
287
+ contentColumn: {
288
+ flex: 1,
289
+ },
290
+ toolbar: {
291
+ flexDirection: 'row',
292
+ alignItems: 'center',
293
+ gap: 8,
294
+ paddingHorizontal: 10,
295
+ paddingVertical: 8,
296
+ borderBottomWidth: StyleSheet.hairlineWidth,
297
+ borderBottomColor: Colors.border,
298
+ backgroundColor: Colors.background,
299
+ },
300
+ searchInput: {
301
+ flex: 1,
302
+ height: 34,
303
+ borderWidth: 1,
304
+ borderColor: Colors.border,
305
+ borderRadius: 8,
306
+ backgroundColor: Colors.surface,
307
+ paddingHorizontal: 10,
308
+ fontSize: 13,
309
+ color: Colors.text,
310
+ },
172
311
  contentContainer: { flex: 1 },
173
312
  emptyText: {
174
313
  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
+ }
@@ -14,6 +14,13 @@ export const Colors = {
14
14
 
15
15
  purple: '#AF52DE',
16
16
 
17
+ // Panel-specific
18
+ railBackground: '#E8EEF6',
19
+ panelDivider: '#CED8E4',
20
+ signalRedBg: '#FFE9E7',
21
+ signalAmberBg: '#FFF1D6',
22
+ signalDefaultBg: '#E7EDF5',
23
+
17
24
  get: '#007AFF',
18
25
  post: '#34C759',
19
26
  put: '#FF9500',
@@ -56,6 +56,7 @@ export interface ReportToDaemonOptions extends DebugDeviceReportOptions {
56
56
  endpoint?: string;
57
57
  timeoutMs?: number;
58
58
  token?: string;
59
+ session?: SessionInfo;
59
60
  }
60
61
 
61
62
  export interface ReportResult {
@@ -89,6 +90,7 @@ type AbortControllerLike = { signal: unknown; abort: () => void };
89
90
  type AbortControllerCtor = new () => AbortControllerLike;
90
91
 
91
92
  type SendResult = 'ok' | 'retry' | 'auth_failed';
93
+ type SessionProvider = () => SessionInfo;
92
94
 
93
95
  // ---- Constants ----
94
96
 
@@ -186,6 +188,7 @@ export class DaemonClient {
186
188
  private _onEndpointDetected: ((url: string) => void) | undefined;
187
189
  private _restorePromise: Promise<void> | null = null;
188
190
  private _sessionId: SessionInfo | null = null;
191
+ private _sessionProvider: SessionProvider | null = null;
189
192
  private _onConnectionChange: (() => void) | undefined;
190
193
 
191
194
  constructor(options: DaemonClientOptions) {
@@ -268,9 +271,7 @@ export class DaemonClient {
268
271
  connect(options: StreamToDaemonOptions = {}): void {
269
272
  if (this._stream) return;
270
273
 
271
- if (!this._sessionId) {
272
- this._sessionId = { id: generateSessionId(), startedAt: Date.now() };
273
- }
274
+ const session = this.resolveSession();
274
275
 
275
276
  const endpoint = options.endpoint || this.resolveEndpoint();
276
277
  const reportUrl = buildDaemonUrl(endpoint, '/report');
@@ -288,7 +289,7 @@ export class DaemonClient {
288
289
  debounceMs: options.debounceMs || DEFAULT_DEBOUNCE_MS,
289
290
  timeoutMs: Math.max(0, options.timeoutMs ?? DEFAULT_TIMEOUT_MS),
290
291
  deviceId: null,
291
- session: this._sessionId,
292
+ session,
292
293
  sending: false,
293
294
  debounceTimer: null,
294
295
  retryTimer: null,
@@ -367,6 +368,14 @@ export class DaemonClient {
367
368
  this._onConnectionChange = callback;
368
369
  }
369
370
 
371
+ setSessionProvider(provider: SessionProvider): void {
372
+ this._sessionProvider = provider;
373
+ }
374
+
375
+ clearSessionProvider(): void {
376
+ this._sessionProvider = null;
377
+ }
378
+
370
379
  // --- Restore (init-time reconnect) ---
371
380
 
372
381
  async restore(): Promise<void> {
@@ -411,7 +420,10 @@ export class DaemonClient {
411
420
  async reportOnce(options: ReportToDaemonOptions = {}): Promise<ReportResult> {
412
421
  const endpoint = options.endpoint ?? this.resolveEndpoint();
413
422
  const reportUrl = buildDaemonUrl(endpoint, '/report');
414
- const report = createDebugDeviceReport(options);
423
+ const report = createDebugDeviceReport({
424
+ ...options,
425
+ session: options.session ?? this.resolveSession(),
426
+ });
415
427
  const fetchImpl = this.resolveFetch();
416
428
 
417
429
  this.notifyEndpoint(endpoint);
@@ -475,6 +487,7 @@ export class DaemonClient {
475
487
  this._streamingEnabled = null;
476
488
  this._restorePromise = null;
477
489
  this._sessionId = null;
490
+ this._sessionProvider = null;
478
491
  }
479
492
 
480
493
  // ---- Private: Transport ----
@@ -498,6 +511,21 @@ export class DaemonClient {
498
511
  this._onEndpointDetected?.(url);
499
512
  }
500
513
 
514
+ private resolveSession(): SessionInfo {
515
+ if (this._sessionProvider) {
516
+ try {
517
+ return this._sessionProvider();
518
+ } catch {
519
+ // fall through to internal session
520
+ }
521
+ }
522
+
523
+ if (!this._sessionId) {
524
+ this._sessionId = { id: generateSessionId(), startedAt: Date.now() };
525
+ }
526
+ return this._sessionId;
527
+ }
528
+
501
529
  private emitStatus(status: StreamStatus): void {
502
530
  try {
503
531
  this._stream?.onStatus?.(status);