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
@@ -0,0 +1,102 @@
1
+ import { SessionHistoryTab, type SessionHistoryState, type SelectedSession, type LogCounts, type SessionHistoryFeature } from './SessionHistoryTab';
2
+ import type { LogFeatureKey } from '../../types';
3
+ import { createDebugTab } from '../../utils/createDebugTab';
4
+ import { getDefaultLogRuntime, type LogRuntimeContext } from '../../utils/logRuntime';
5
+
6
+ const FEATURE_KEYS: LogFeatureKey[] = ['console_logs', 'network_logs', 'track_logs'];
7
+
8
+ export function createSessionHistoryFeature(
9
+ runtime: LogRuntimeContext = getDefaultLogRuntime(),
10
+ ): SessionHistoryFeature {
11
+ let listeners: Array<() => void> = [];
12
+ let sessions = runtime.sessionManager.getCurrentSession() ? [runtime.sessionManager.getCurrentSession()] : [];
13
+ let currentSessionId = runtime.sessionManager.getCurrentSession().id;
14
+ let loading = false;
15
+ let selected: SelectedSession | null = null;
16
+ let initialized = false;
17
+ let logCounts: Record<string, LogCounts> = {};
18
+
19
+ function notify() {
20
+ listeners.forEach((l) => l());
21
+ }
22
+
23
+ function getSnapshot(): SessionHistoryState {
24
+ return { sessions, currentSessionId, loading, selectedSession: selected, storageType: runtime.logStorage.constructor.name, logCounts };
25
+ }
26
+
27
+ async function loadLogCounts(sessionIds: string[]) {
28
+ const counts: Record<string, LogCounts> = {};
29
+ await Promise.all(
30
+ sessionIds.map(async (id) => {
31
+ const c: LogCounts = { console_logs: 0, network_logs: 0, track_logs: 0 };
32
+ await Promise.all(
33
+ FEATURE_KEYS.map(async (key) => {
34
+ c[key] = await runtime.sessionManager.getSessionLogCount(id, key);
35
+ }),
36
+ );
37
+ counts[id] = c;
38
+ }),
39
+ );
40
+ return counts;
41
+ }
42
+
43
+ async function loadSession(sessionId: string | null) {
44
+ if (sessionId === null) {
45
+ selected = null;
46
+ notify();
47
+ return;
48
+ }
49
+
50
+ loading = true;
51
+ selected = null;
52
+ notify();
53
+
54
+ const logs: Record<LogFeatureKey, unknown[]> = {} as Record<LogFeatureKey, unknown[]>;
55
+ await Promise.all(
56
+ FEATURE_KEYS.map(async (key) => {
57
+ logs[key] = await runtime.sessionManager.loadSessionLogs(sessionId, key);
58
+ }),
59
+ );
60
+
61
+ loading = false;
62
+ selected = { sessionId, logs };
63
+ notify();
64
+ }
65
+
66
+ const feature: SessionHistoryFeature = {
67
+ ...createDebugTab<SessionHistoryState>({
68
+ name: 'sessionHistory',
69
+ label: 'Sessions',
70
+ getSnapshot,
71
+ render: SessionHistoryTab,
72
+ setup: async () => {
73
+ if (initialized) return;
74
+ initialized = true;
75
+ loading = true;
76
+ notify();
77
+ try {
78
+ sessions = await runtime.sessionManager.getSessionHistory();
79
+ currentSessionId = runtime.sessionManager.getCurrentSession().id;
80
+ logCounts = await loadLogCounts(sessions.map((s) => s.id));
81
+ } catch (e) {
82
+ console.warn('[SessionHistory] setup error:', e);
83
+ }
84
+ loading = false;
85
+ notify();
86
+ },
87
+ cleanup: () => {
88
+ initialized = false;
89
+ selected = null;
90
+ },
91
+ subscribe: (listener) => {
92
+ listeners.push(listener);
93
+ return () => {
94
+ listeners = listeners.filter((l) => l !== listener);
95
+ };
96
+ },
97
+ }),
98
+ loadSession,
99
+ };
100
+
101
+ return feature;
102
+ }
@@ -2,7 +2,7 @@ import { TrackLogTab } from './TrackLogTab';
2
2
  import type { DebugFeature, TrackLogEntry } from '../../types';
3
3
  import { createEventChannel } from '../../utils/createEventChannel';
4
4
  import { createChannelFeature } from '../../utils/createChannelFeature';
5
- import { KEYS } from '../../utils/debugPreferences';
5
+ import { getDefaultLogRuntime, type LogRuntimeContext } from '../../utils/logRuntime';
6
6
 
7
7
  export interface TrackEventData {
8
8
  eventName: string;
@@ -22,7 +22,10 @@ export interface TrackFeatureConfig {
22
22
  maxLogs?: number;
23
23
  }
24
24
 
25
- export const createTrackFeature = (config?: TrackFeatureConfig): DebugFeature<TrackLogEntry[]> =>
25
+ export const createTrackFeature = (
26
+ config?: TrackFeatureConfig,
27
+ runtime: LogRuntimeContext = getDefaultLogRuntime(),
28
+ ): DebugFeature<TrackLogEntry[]> =>
26
29
  createChannelFeature(
27
30
  () => trackChannel,
28
31
  (payload, id) => ({ ...payload, id }),
@@ -31,7 +34,11 @@ export const createTrackFeature = (config?: TrackFeatureConfig): DebugFeature<Tr
31
34
  label: 'Track',
32
35
  renderContent: TrackLogTab,
33
36
  maxLogs: config?.maxLogs,
34
- persist: { storageKey: KEYS.trackLogs, maxPersist: 50 },
37
+ persist: {
38
+ storage: runtime.logStorage,
39
+ storageKey: runtime.sessionManager.getLogStorageKey('track_logs'),
40
+ maxPersist: 50,
41
+ },
35
42
  },
36
43
  );
37
44
 
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export type { EnvironmentFeatureAPI } from './features/environment';
22
22
  export { createClipboardFeature } from './features/clipboard';
23
23
  export { createDevConnectFeature } from './features/devConnect';
24
24
  export type { DevConnectState } from './features/devConnect';
25
+ export { createSessionHistoryFeature } from './features/sessionHistory';
25
26
 
26
27
  // Hooks
27
28
  export { useNavigationLogger } from './features/navigation/useNavigationLogger';
@@ -47,6 +48,16 @@ export type {
47
48
  ReportToDaemonOptions,
48
49
  } from './utils/DaemonClient';
49
50
  export { getDefaultDaemonEndpoint } from './utils/DaemonClient';
51
+ export {
52
+ createDefaultLogStorage,
53
+ MemoryStorageAdapter,
54
+ } from './utils/StorageAdapter';
55
+ export type { StorageAdapter } from './utils/StorageAdapter';
56
+ export type {
57
+ LogFeatureKey,
58
+ LogSession,
59
+ SessionManagerOptions,
60
+ } from './utils/SessionManager';
50
61
 
51
62
  // Types
52
63
  export type {
@@ -10,7 +10,8 @@ export type BuiltInFeatureName =
10
10
  | 'track'
11
11
  | 'environment'
12
12
  | 'clipboard'
13
- | 'devConnect';
13
+ | 'devConnect'
14
+ | 'sessionHistory';
14
15
 
15
16
  export interface DebugFeatureRenderProps<TSnapshot = unknown> {
16
17
  snapshot: TSnapshot;
@@ -28,3 +28,13 @@ export type {
28
28
  ThirdPartyLib,
29
29
  ThirdPartyLibAction,
30
30
  } from './thirdPartyLibs';
31
+
32
+ export type {
33
+ StorageAdapter,
34
+ } from '../utils/StorageAdapter';
35
+
36
+ export type {
37
+ LogFeatureKey,
38
+ LogSession,
39
+ SessionManagerOptions,
40
+ } from '../utils/SessionManager';
@@ -57,6 +57,7 @@ export function DebugView({
57
57
  track: true,
58
58
  clipboard: true,
59
59
  devConnect: true,
60
+ sessionHistory: true,
60
61
  ...features,
61
62
  };
62
63
 
@@ -14,10 +14,12 @@ import { Colors } from '../theme/colors';
14
14
  interface DebugPanelProps {
15
15
  onClose: () => void;
16
16
  onClearAll: () => void;
17
+ syncLabel?: string;
18
+ syncColor?: string;
17
19
  children: React.ReactNode;
18
20
  }
19
21
 
20
- export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
22
+ export function DebugPanel({ onClose, onClearAll, syncLabel, syncColor, children }: DebugPanelProps) {
21
23
  const { height: screenHeight } = useWindowDimensions();
22
24
  const panelTranslateY = useRef(new Animated.Value(screenHeight)).current;
23
25
  const backdropOpacity = useRef(new Animated.Value(0)).current;
@@ -99,20 +101,35 @@ export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
99
101
  <View style={styles.dragIndicator} />
100
102
  </View>
101
103
  <View style={styles.header}>
102
- <Text style={styles.headerTitle}>Debug Toolkit</Text>
104
+ <View style={styles.headerLeft}>
105
+ <Text style={styles.headerTitle}>Debug Toolkit</Text>
106
+ {syncLabel && (
107
+ <View style={styles.syncRow}>
108
+ <View style={[styles.syncDot, syncColor ? { backgroundColor: syncColor } : null]} />
109
+ <Text style={styles.syncText} numberOfLines={1}>{syncLabel}</Text>
110
+ </View>
111
+ )}
112
+ </View>
103
113
  <View style={styles.headerButtons}>
104
114
  <TouchableOpacity
105
115
  onPress={() => {
106
116
  onClearAll();
107
117
  closePanel();
108
118
  }}
109
- style={styles.clearButton}
119
+ style={styles.iconButton}
110
120
  activeOpacity={0.6}
121
+ accessibilityLabel="Clear all"
122
+ accessibilityRole="button"
111
123
  >
112
- <Text style={styles.clearButtonText}>Clear</Text>
124
+ <Text style={styles.iconButtonText}>C</Text>
113
125
  </TouchableOpacity>
114
- <Pressable onPress={closePanel} style={styles.closeButton}>
115
- <Text style={styles.closeButtonText}>✕</Text>
126
+ <Pressable
127
+ onPress={closePanel}
128
+ style={styles.iconButton}
129
+ accessibilityLabel="Close panel"
130
+ accessibilityRole="button"
131
+ >
132
+ <Text style={styles.iconButtonText}>✕</Text>
116
133
  </Pressable>
117
134
  </View>
118
135
  </View>
@@ -156,13 +173,13 @@ const styles = StyleSheet.create({
156
173
  },
157
174
  dragHandle: {
158
175
  width: '100%',
159
- height: 28,
176
+ height: 26,
160
177
  alignItems: 'center',
161
178
  justifyContent: 'center',
162
179
  backgroundColor: Colors.surface,
163
180
  },
164
181
  dragIndicator: {
165
- width: 40,
182
+ width: 42,
166
183
  height: 4,
167
184
  borderRadius: 2,
168
185
  backgroundColor: Colors.textLight,
@@ -172,44 +189,57 @@ const styles = StyleSheet.create({
172
189
  flexDirection: 'row',
173
190
  justifyContent: 'space-between',
174
191
  alignItems: 'center',
175
- paddingHorizontal: 20,
176
- paddingTop: 6,
192
+ paddingHorizontal: 14,
193
+ paddingTop: 8,
177
194
  paddingBottom: 10,
178
195
  backgroundColor: Colors.surface,
196
+ borderBottomWidth: StyleSheet.hairlineWidth,
197
+ borderBottomColor: Colors.border,
198
+ },
199
+ headerLeft: {
200
+ flex: 1,
201
+ minWidth: 0,
179
202
  },
180
203
  headerTitle: {
181
204
  fontSize: 17,
182
- fontWeight: '600',
205
+ fontWeight: '700',
183
206
  color: Colors.text,
184
207
  },
185
- headerButtons: {
208
+ syncRow: {
186
209
  flexDirection: 'row',
187
210
  alignItems: 'center',
188
- gap: 12,
211
+ gap: 6,
212
+ marginTop: 4,
189
213
  },
190
- clearButton: {
191
- paddingHorizontal: 12,
192
- paddingVertical: 6,
193
- borderRadius: 8,
194
- backgroundColor: `${Colors.error}0F`,
214
+ syncDot: {
215
+ width: 7,
216
+ height: 7,
217
+ borderRadius: 3.5,
218
+ backgroundColor: Colors.success,
195
219
  },
196
- clearButtonText: {
197
- color: Colors.error,
198
- fontSize: 14,
199
- fontWeight: '500',
220
+ syncText: {
221
+ fontSize: 11,
222
+ fontWeight: '700',
223
+ color: Colors.textSecondary,
200
224
  },
201
- closeButton: {
202
- width: 30,
203
- height: 30,
204
- borderRadius: 15,
225
+ headerButtons: {
226
+ flexDirection: 'row',
227
+ alignItems: 'center',
228
+ gap: 7,
229
+ },
230
+ iconButton: {
231
+ width: 32,
232
+ height: 32,
233
+ borderRadius: 8,
234
+ borderWidth: 1,
235
+ borderColor: Colors.border,
205
236
  backgroundColor: Colors.background,
206
237
  alignItems: 'center',
207
238
  justifyContent: 'center',
208
239
  },
209
- closeButtonText: {
210
- fontSize: 16,
211
- fontWeight: '400',
240
+ iconButtonText: {
241
+ fontSize: 14,
242
+ fontWeight: '700',
212
243
  color: Colors.textSecondary,
213
- lineHeight: 16,
214
244
  },
215
245
  });
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import { View, Text, Pressable, StyleSheet } from 'react-native';
3
+ import { Colors } from '../theme/colors';
4
+ import type { FeatureSummary } from './buildFeatureSummary';
5
+
6
+ function hexWithAlpha(hex: string, alpha: string): string {
7
+ if (/^#[0-9a-fA-F]{6}$/.test(hex)) return hex + alpha;
8
+ return Colors.signalDefaultBg;
9
+ }
10
+
11
+ interface FeatureIntroCardProps {
12
+ title: string;
13
+ summary: FeatureSummary;
14
+ filterBad: boolean;
15
+ onFilterBad: (bad: boolean) => void;
16
+ }
17
+
18
+ export function FeatureIntroCard({
19
+ title,
20
+ summary,
21
+ filterBad,
22
+ onFilterBad,
23
+ }: FeatureIntroCardProps) {
24
+ const {
25
+ statusLabel,
26
+ statusColor,
27
+ supportsBadFilter,
28
+ } = summary;
29
+
30
+ return (
31
+ <View style={styles.card}>
32
+ <View style={styles.titleRow}>
33
+ <Text style={styles.title} numberOfLines={1}>{title}</Text>
34
+ {statusLabel && (
35
+ <View style={[styles.statusChip, statusColor && { backgroundColor: hexWithAlpha(statusColor, '18') }]}>
36
+ <Text style={[styles.statusText, statusColor && { color: statusColor }]} numberOfLines={1}>
37
+ {statusLabel}
38
+ </Text>
39
+ </View>
40
+ )}
41
+ </View>
42
+ {supportsBadFilter && (
43
+ <View style={styles.actionRow}>
44
+ <Pressable
45
+ style={[styles.chip, !filterBad && styles.chipActive]}
46
+ onPress={() => onFilterBad(false)}
47
+ >
48
+ <Text style={[styles.chipText, !filterBad && styles.chipTextActive]}>All</Text>
49
+ </Pressable>
50
+ {supportsBadFilter && (
51
+ <Pressable
52
+ style={[styles.chip, filterBad && styles.chipBadActive]}
53
+ onPress={() => onFilterBad(true)}
54
+ >
55
+ <Text style={[styles.chipText, filterBad && styles.chipTextBad]}>Bad</Text>
56
+ </Pressable>
57
+ )}
58
+ </View>
59
+ )}
60
+ </View>
61
+ );
62
+ }
63
+
64
+ const styles = StyleSheet.create({
65
+ card: {
66
+ paddingHorizontal: 14,
67
+ paddingVertical: 10,
68
+ borderBottomWidth: StyleSheet.hairlineWidth,
69
+ borderBottomColor: Colors.border,
70
+ backgroundColor: Colors.background,
71
+ },
72
+ titleRow: {
73
+ flexDirection: 'row',
74
+ alignItems: 'center',
75
+ gap: 8,
76
+ },
77
+ title: {
78
+ fontSize: 17,
79
+ fontWeight: '700',
80
+ color: Colors.text,
81
+ flex: 1,
82
+ },
83
+ statusChip: {
84
+ paddingHorizontal: 8,
85
+ paddingVertical: 2,
86
+ borderRadius: 8,
87
+ backgroundColor: Colors.signalDefaultBg,
88
+ },
89
+ statusText: {
90
+ fontSize: 11,
91
+ fontWeight: '700',
92
+ color: Colors.textSecondary,
93
+ },
94
+ actionRow: {
95
+ flexDirection: 'row',
96
+ gap: 6,
97
+ marginTop: 8,
98
+ },
99
+ chip: {
100
+ height: 26,
101
+ paddingHorizontal: 12,
102
+ justifyContent: 'center',
103
+ borderRadius: 13,
104
+ borderWidth: 1,
105
+ borderColor: Colors.border,
106
+ backgroundColor: Colors.surface,
107
+ },
108
+ chipActive: {
109
+ backgroundColor: Colors.text,
110
+ borderColor: Colors.text,
111
+ },
112
+ chipBadActive: {
113
+ backgroundColor: Colors.error,
114
+ borderColor: Colors.error,
115
+ },
116
+ chipText: {
117
+ fontSize: 11,
118
+ fontWeight: '700',
119
+ color: Colors.textSecondary,
120
+ },
121
+ chipTextActive: {
122
+ color: '#fff',
123
+ },
124
+ chipTextBad: {
125
+ color: '#fff',
126
+ },
127
+ });
@@ -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: 9,
132
+ fontWeight: '700',
133
+ color: Colors.textSecondary,
134
+ letterSpacing: 0,
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: 8,
162
+ fontWeight: '800',
163
+ color: Colors.textSecondary,
164
+ },
165
+ });