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,693 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ Pressable,
7
+ ScrollView,
8
+ ActivityIndicator,
9
+ } from 'react-native';
10
+ import { Colors, getMethodColor } from '../../ui/theme/colors';
11
+ import { CollapsibleSection } from '../../ui/shared/CollapsibleSection';
12
+ import { JsonView } from '../../ui/shared/JsonView';
13
+ import { CopyButton } from '../../ui/shared/CopyButton';
14
+ import { LogListScreen } from '../../ui/shared/LogListScreen';
15
+ import { safeStringify } from '../../utils/safeStringify';
16
+ import { fmt } from '../../utils/copyToComputer';
17
+ import { LEVEL_COLORS, LEVEL_ICONS } from '../../constants/logLevels';
18
+ import type { DebugFeature, DebugFeatureRenderProps, LogFeatureKey, LogSession } from '../../types';
19
+
20
+ // ── Types ──────────────────────────────────────────────────────────────
21
+
22
+ export type LogCounts = Record<LogFeatureKey, number>;
23
+
24
+ export interface SessionHistoryState {
25
+ sessions: LogSession[];
26
+ currentSessionId: string;
27
+ loading: boolean;
28
+ selectedSession: SelectedSession | null;
29
+ storageType: string;
30
+ logCounts: Record<string, LogCounts>;
31
+ }
32
+
33
+ export interface SessionHistoryFeature extends DebugFeature<SessionHistoryState> {
34
+ loadSession: (sessionId: string | null) => void;
35
+ }
36
+
37
+ export interface SelectedSession {
38
+ sessionId: string;
39
+ logs: Record<LogFeatureKey, unknown[]>;
40
+ }
41
+
42
+ interface FlatLogEntry {
43
+ id: string;
44
+ type: LogFeatureKey;
45
+ timestamp: number;
46
+ raw: unknown;
47
+ }
48
+
49
+ // ── Constants ──────────────────────────────────────────────────────────
50
+
51
+ const FEATURE_LABELS: Record<LogFeatureKey, string> = {
52
+ console_logs: 'Console',
53
+ network_logs: 'Network',
54
+ native_logs: 'Native',
55
+ track_logs: 'Track',
56
+ };
57
+
58
+ const FEATURE_COLORS: Record<LogFeatureKey, string> = {
59
+ console_logs: Colors.info,
60
+ network_logs: Colors.success,
61
+ native_logs: '#FF9500',
62
+ track_logs: Colors.purple,
63
+ };
64
+
65
+ const FEATURE_KEYS: LogFeatureKey[] = Object.keys(FEATURE_LABELS) as LogFeatureKey[];
66
+
67
+ // ── Helpers ────────────────────────────────────────────────────────────
68
+
69
+ function relativeTime(ts: number): string {
70
+ const diff = Date.now() - ts;
71
+ const mins = Math.floor(diff / 60000);
72
+ if (mins < 1) return 'Just now';
73
+ if (mins < 60) return `${mins}m ago`;
74
+ const hours = Math.floor(mins / 60);
75
+ if (hours < 24) return `${hours}h ago`;
76
+ const days = Math.floor(hours / 24);
77
+ if (days === 1) return 'Yesterday';
78
+ if (days < 7) return `${days}d ago`;
79
+ return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
80
+ }
81
+
82
+ function formatTime(ts: number): string {
83
+ return new Date(ts).toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
84
+ }
85
+
86
+ function formatFullDate(ts: number): string {
87
+ const d = new Date(ts);
88
+ const now = new Date();
89
+ const isToday = d.toDateString() === now.toDateString();
90
+ const time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
91
+ if (isToday) return `Today ${time}`;
92
+ return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) + ' ' + time;
93
+ }
94
+
95
+ function totalCounts(counts: LogCounts): number {
96
+ return (counts.console_logs ?? 0) + (counts.network_logs ?? 0) + (counts.track_logs ?? 0);
97
+ }
98
+
99
+ function totalLogs(logs: Record<LogFeatureKey, unknown[]>): number {
100
+ return Object.values(logs).reduce((sum, arr) => sum + arr.length, 0);
101
+ }
102
+
103
+ function shortId(id: string): string {
104
+ return id.slice(-6).toUpperCase();
105
+ }
106
+
107
+ function flattenLogs(logs: Record<LogFeatureKey, unknown[]>, filter: DetailFilter): FlatLogEntry[] {
108
+ const keys: LogFeatureKey[] = filter === 'all'
109
+ ? ['console_logs', 'network_logs', 'track_logs']
110
+ : [filter];
111
+ const entries: FlatLogEntry[] = [];
112
+ for (const key of keys) {
113
+ const items = logs[key] ?? [];
114
+ items.forEach((raw, i) => {
115
+ const e = raw as Record<string, any>;
116
+ entries.push({
117
+ id: `${key}-${i}`,
118
+ type: key,
119
+ timestamp: e.timestamp ?? 0,
120
+ raw,
121
+ });
122
+ });
123
+ }
124
+ entries.sort((a, b) => b.timestamp - a.timestamp);
125
+ return entries;
126
+ }
127
+
128
+ function toRecord(entry: FlatLogEntry): Record<string, any> {
129
+ return entry.raw as Record<string, any>;
130
+ }
131
+
132
+ function shortenUrl(url: string): string {
133
+ try {
134
+ const u = new URL(url);
135
+ return u.pathname + u.search;
136
+ } catch {
137
+ return url;
138
+ }
139
+ }
140
+
141
+ // ── Main Tab ───────────────────────────────────────────────────────────
142
+
143
+ export const SessionHistoryTab: React.FC<DebugFeatureRenderProps<SessionHistoryState>> = React.memo(
144
+ ({ snapshot, feature }) => {
145
+ const { sessions, currentSessionId, loading, selectedSession, logCounts } = snapshot;
146
+
147
+ const handleSelectSession = useCallback(
148
+ (session: LogSession) => {
149
+ if (session.id === currentSessionId) return;
150
+ (feature as SessionHistoryFeature).loadSession(session.id);
151
+ },
152
+ [feature, currentSessionId],
153
+ );
154
+
155
+ const handleBack = useCallback(() => {
156
+ (feature as SessionHistoryFeature).loadSession(null);
157
+ }, [feature]);
158
+
159
+ if (loading) {
160
+ return (
161
+ <View style={s.center}>
162
+ <ActivityIndicator color={Colors.primary} />
163
+ </View>
164
+ );
165
+ }
166
+
167
+ if (selectedSession) {
168
+ return (
169
+ <SessionDetail
170
+ logs={selectedSession.logs}
171
+ sessionId={selectedSession.sessionId}
172
+ onBack={handleBack}
173
+ />
174
+ );
175
+ }
176
+
177
+ const previousSessions = sessions.filter((s) => s.id !== currentSessionId);
178
+ const currentSession = sessions.find((s) => s.id === currentSessionId);
179
+
180
+ if (previousSessions.length === 0) {
181
+ return (
182
+ <View style={s.center}>
183
+ <View style={s.emptyClock}>
184
+ <Text style={s.emptyClockText}>⏱</Text>
185
+ </View>
186
+ <Text style={s.emptyTitle}>No session history</Text>
187
+ <Text style={s.emptySub}>
188
+ Previous sessions will appear here after you restart the app.
189
+ </Text>
190
+ </View>
191
+ );
192
+ }
193
+
194
+ return (
195
+ <ScrollView style={s.container} contentContainerStyle={s.scrollContent}>
196
+ {currentSession && (
197
+ <View style={s.currentCard}>
198
+ <View style={s.currentRow}>
199
+ <View style={s.pulseDot} />
200
+ <Text style={s.currentLabel}>CURRENT</Text>
201
+ </View>
202
+ <Text style={s.currentTime}>{formatFullDate(currentSession.startedAt)}</Text>
203
+ </View>
204
+ )}
205
+
206
+ <View style={s.timelineSection}>
207
+ <Text style={s.timelineHeader}>PREVIOUS SESSIONS</Text>
208
+ {previousSessions.map((session, idx) => {
209
+ const counts = logCounts[session.id];
210
+ const total = counts ? totalCounts(counts) : 0;
211
+ return (
212
+ <Pressable
213
+ key={session.id}
214
+ style={s.timelineRow}
215
+ onPress={() => handleSelectSession(session)}
216
+ android_ripple={{ color: 'rgba(0,0,0,0.04)' }}
217
+ >
218
+ <View style={s.railCol}>
219
+ <View style={s.railDot} />
220
+ {idx < previousSessions.length - 1 && <View style={s.railLine} />}
221
+ </View>
222
+
223
+ <View style={s.historyCard}>
224
+ <View style={s.cardTop}>
225
+ <Text style={s.cardTime}>{formatTime(session.startedAt)}</Text>
226
+ <Text style={s.cardRelative}>{relativeTime(session.startedAt)}</Text>
227
+ </View>
228
+
229
+ {counts && total > 0 ? (
230
+ <View style={s.pillRow}>
231
+ {FEATURE_KEYS.map((key) => {
232
+ const c = counts[key] ?? 0;
233
+ if (c === 0) return null;
234
+ return (
235
+ <View key={key} style={[s.pill, { backgroundColor: FEATURE_COLORS[key] + '18' }]}>
236
+ <View style={[s.pillDot, { backgroundColor: FEATURE_COLORS[key] }]} />
237
+ <Text style={[s.pillText, { color: FEATURE_COLORS[key] }]}>
238
+ {c} {FEATURE_LABELS[key]}
239
+ </Text>
240
+ </View>
241
+ );
242
+ })}
243
+ </View>
244
+ ) : (
245
+ <Text style={s.cardEmpty}>No logs recorded</Text>
246
+ )}
247
+
248
+ <View style={s.cardFooter}>
249
+ <Text style={s.cardId}>#{shortId(session.id)}</Text>
250
+ <Text style={s.cardChevron}>›</Text>
251
+ </View>
252
+ </View>
253
+ </Pressable>
254
+ );
255
+ })}
256
+ </View>
257
+ </ScrollView>
258
+ );
259
+ },
260
+ );
261
+
262
+ // ── Session Detail (uses LogListScreen for tap-to-detail) ──────────────
263
+
264
+ type DetailFilter = 'all' | LogFeatureKey;
265
+
266
+ const SessionDetail: React.FC<{
267
+ logs: Record<LogFeatureKey, unknown[]>;
268
+ sessionId: string;
269
+ onBack: () => void;
270
+ }> = React.memo(({ logs, sessionId, onBack }) => {
271
+ const [filter, setFilter] = useState<DetailFilter>('all');
272
+ const total = totalLogs(logs);
273
+
274
+ const flatEntries = useMemo(() => flattenLogs(logs, filter), [logs, filter]);
275
+
276
+ return (
277
+ <View style={s.container}>
278
+ <View style={s.sessionHeader}>
279
+ <Pressable onPress={onBack} hitSlop={12} style={s.headerBackBtn}>
280
+ <Text style={s.headerArrow}>‹</Text>
281
+ <Text style={s.headerBackLabel}>Sessions</Text>
282
+ </Pressable>
283
+ <Text style={s.headerMeta}>#{shortId(sessionId)}</Text>
284
+ </View>
285
+
286
+ <LogListScreen
287
+ data={flatEntries}
288
+ reversed={false}
289
+ emptyText={filter === 'all' ? 'No logs in this session' : `No ${FEATURE_LABELS[filter]} logs`}
290
+ renderListHeader={() => (
291
+ <View style={s.filterBar}>
292
+ <FilterChip label="All" count={total} active={filter === 'all'} onPress={() => setFilter('all')} color={Colors.text} />
293
+ {FEATURE_KEYS.map((key) => {
294
+ const c = (logs[key] ?? []).length;
295
+ return (
296
+ <FilterChip
297
+ key={key}
298
+ label={FEATURE_LABELS[key]}
299
+ count={c}
300
+ active={filter === key}
301
+ onPress={() => setFilter(key)}
302
+ color={FEATURE_COLORS[key]}
303
+ />
304
+ );
305
+ })}
306
+ </View>
307
+ )}
308
+ renderRow={(item) => <LogRow entry={item} />}
309
+ renderDetailHeader={(item) => <LogDetailHeader entry={item} />}
310
+ renderDetailBody={(item) => <LogDetailBody entry={item} />}
311
+ />
312
+ </View>
313
+ );
314
+ });
315
+
316
+ // ── Filter Chips ───────────────────────────────────────────────────────
317
+
318
+ const FilterChip: React.FC<{
319
+ label: string;
320
+ count: number;
321
+ active: boolean;
322
+ onPress: () => void;
323
+ color: string;
324
+ }> = React.memo(({ label, count, active, onPress, color }) => (
325
+ <Pressable
326
+ onPress={onPress}
327
+ style={[s.chip, active && { backgroundColor: color }]}
328
+ hitSlop={4}
329
+ >
330
+ <Text style={[s.chipLabel, active && s.chipLabelActive]}>
331
+ {label} <Text style={[s.chipCount, active && s.chipCountActive]}>{count}</Text>
332
+ </Text>
333
+ </Pressable>
334
+ ));
335
+
336
+ // ── Log List Row ───────────────────────────────────────────────────────
337
+
338
+ const LogRow: React.FC<{ entry: FlatLogEntry }> = React.memo(({ entry }) => {
339
+ const e = toRecord(entry);
340
+
341
+ if (entry.type === 'console_logs') {
342
+ const level = e.level ?? 'log';
343
+ const msg = Array.isArray(e.data)
344
+ ? e.data.map((d: any) => (typeof d === 'string' ? d : safeStringify(d))).join(' ')
345
+ : safeStringify(e.data);
346
+ return (
347
+ <View style={s.rowContent}>
348
+ <View style={[s.levelDot, { backgroundColor: LEVEL_COLORS[level] ?? '#8E8E93' }]}>
349
+ <Text style={s.levelIcon}>{LEVEL_ICONS[level] ?? '●'}</Text>
350
+ </View>
351
+ <View style={s.rowBody}>
352
+ <Text style={s.rowMsg} numberOfLines={2}>{msg}</Text>
353
+ <Text style={s.rowTime}>{new Date(e.timestamp).toLocaleTimeString()}</Text>
354
+ </View>
355
+ </View>
356
+ );
357
+ }
358
+
359
+ if (entry.type === 'network_logs') {
360
+ const method = e.request?.method ?? '?';
361
+ const url = e.request?.url ?? '';
362
+ const status = e.response?.status;
363
+ const ok = !e.error && (!status || status < 400);
364
+ return (
365
+ <View style={s.rowContent}>
366
+ <View style={[s.statusBar, { backgroundColor: ok ? Colors.success : Colors.error }]} />
367
+ <View style={s.rowBody}>
368
+ <View style={s.rowMeta}>
369
+ <Text style={[s.methodText, { color: getMethodColor(method) }]}>{method}</Text>
370
+ {status != null && (
371
+ <View style={[s.miniPill, { backgroundColor: ok ? Colors.success : Colors.error }]}>
372
+ <Text style={s.miniPillText}>{status}</Text>
373
+ </View>
374
+ )}
375
+ </View>
376
+ <Text style={[s.rowMsg, !ok && { color: Colors.error }]} numberOfLines={1}>{shortenUrl(url)}</Text>
377
+ <Text style={s.rowTime}>{new Date(e.timestamp).toLocaleTimeString()}</Text>
378
+ </View>
379
+ </View>
380
+ );
381
+ }
382
+
383
+ // track_logs
384
+ const name = e.eventName ?? e.action ?? 'Event';
385
+ return (
386
+ <View style={s.rowContent}>
387
+ <View style={s.trackDot} />
388
+ <View style={s.rowBody}>
389
+ <Text style={s.rowMsg} numberOfLines={1}>{name}</Text>
390
+ <Text style={s.rowTime}>{e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : ''}</Text>
391
+ </View>
392
+ </View>
393
+ );
394
+ });
395
+
396
+ // ── Log Detail Header ──────────────────────────────────────────────────
397
+
398
+ const LogDetailHeader: React.FC<{ entry: FlatLogEntry }> = React.memo(({ entry }) => {
399
+ const e = toRecord(entry);
400
+
401
+ if (entry.type === 'console_logs') {
402
+ const level = e.level ?? 'log';
403
+ return (
404
+ <>
405
+ <View style={[s.levelBadge, { backgroundColor: LEVEL_COLORS[level] ?? '#8E8E93' }]}>
406
+ <Text style={s.levelBadgeText}>{level.toUpperCase()}</Text>
407
+ </View>
408
+ <Text style={s.detailTimestamp}>{new Date(e.timestamp).toLocaleString()}</Text>
409
+ </>
410
+ );
411
+ }
412
+
413
+ if (entry.type === 'network_logs') {
414
+ const method = e.request?.method ?? '?';
415
+ const status = e.response?.status;
416
+ const ok = !e.error && (!status || status < 400);
417
+ return (
418
+ <View style={s.networkHeaderBadges}>
419
+ <View style={[s.methodBadge, { backgroundColor: getMethodColor(method) }]}>
420
+ <Text style={s.methodBadgeText}>{method}</Text>
421
+ </View>
422
+ {status != null && (
423
+ <View style={[s.statusPill, { backgroundColor: ok ? Colors.success : Colors.error }]}>
424
+ <Text style={s.statusPillText}>{status}</Text>
425
+ </View>
426
+ )}
427
+ {e.duration != null && <Text style={s.durationText}>{e.duration}ms</Text>}
428
+ </View>
429
+ );
430
+ }
431
+
432
+ return (
433
+ <Text style={s.detailTimestamp}>
434
+ {new Date(e.timestamp).toLocaleString()}
435
+ </Text>
436
+ );
437
+ });
438
+
439
+ // ── Log Detail Body ────────────────────────────────────────────────────
440
+
441
+ const LogDetailBody: React.FC<{ entry: FlatLogEntry }> = React.memo(({ entry }) => {
442
+ const e = toRecord(entry);
443
+
444
+ if (entry.type === 'console_logs') {
445
+ const data = Array.isArray(e.data) ? e.data : [e.data];
446
+ return (
447
+ <ScrollView style={s.detailBody} contentContainerStyle={s.detailBodyContent}>
448
+ {data.map((d: any, i: number) => {
449
+ const formatted = typeof d === 'object' && d !== null ? fmt(d) : String(d);
450
+ return (
451
+ <CollapsibleSection
452
+ key={i}
453
+ title={typeof d === 'object' && d !== null ? `Arg ${i + 1} (object)` : `Arg ${i + 1}`}
454
+ initiallyExpanded={i === 0}
455
+ >
456
+ <View style={s.sectionWithCopy}>
457
+ <CopyButton text={formatted} label={`Arg ${i + 1}`} />
458
+ {typeof d === 'object' && d !== null ? (
459
+ <JsonView data={d} maxHeight={250} />
460
+ ) : (
461
+ <Text style={s.plainText} selectable>{String(d)}</Text>
462
+ )}
463
+ </View>
464
+ </CollapsibleSection>
465
+ );
466
+ })}
467
+ </ScrollView>
468
+ );
469
+ }
470
+
471
+ if (entry.type === 'network_logs') {
472
+ return (
473
+ <ScrollView style={s.detailBody} contentContainerStyle={s.detailBodyContent}>
474
+ {/* URL */}
475
+ <View style={s.urlCard}>
476
+ <Text style={s.urlText} selectable numberOfLines={3}>{e.request?.url}</Text>
477
+ <CopyButton text={e.request?.url ?? ''} label="URL" />
478
+ </View>
479
+
480
+ {/* Error */}
481
+ {e.error && (
482
+ <View style={s.errorBox}>
483
+ <Text style={s.errorIcon}>⚠</Text>
484
+ <Text style={s.errorText}>{e.error}</Text>
485
+ </View>
486
+ )}
487
+
488
+ {/* Request Body */}
489
+ <CollapsibleSection title="Request Body" initiallyExpanded>
490
+ {e.request?.body != null ? (
491
+ <View style={s.sectionWithCopy}>
492
+ <CopyButton text={fmt(e.request.body)} label="Request Body" />
493
+ <JsonView data={e.request.body} maxHeight={250} />
494
+ </View>
495
+ ) : (
496
+ <Text style={s.emptySection}>No request body</Text>
497
+ )}
498
+ </CollapsibleSection>
499
+
500
+ {/* Request Headers */}
501
+ {e.request?.headers && (
502
+ <CollapsibleSection title="Request Headers">
503
+ <View style={s.sectionWithCopy}>
504
+ <CopyButton text={fmt(e.request.headers)} label="Request Headers" />
505
+ <JsonView data={e.request.headers} maxHeight={200} />
506
+ </View>
507
+ </CollapsibleSection>
508
+ )}
509
+
510
+ {/* Response Body */}
511
+ <CollapsibleSection title="Response Body" initiallyExpanded>
512
+ {e.response?.data != null ? (
513
+ <View style={s.sectionWithCopy}>
514
+ <CopyButton text={fmt(e.response.data)} label="Response Body" />
515
+ <JsonView data={e.response.data} maxHeight={300} />
516
+ </View>
517
+ ) : (
518
+ <Text style={s.emptySection}>No response body</Text>
519
+ )}
520
+ </CollapsibleSection>
521
+
522
+ {/* Response Headers */}
523
+ {e.response?.headers && (
524
+ <CollapsibleSection title="Response Headers">
525
+ <View style={s.sectionWithCopy}>
526
+ <CopyButton text={fmt(e.response.headers)} label="Response Headers" />
527
+ <JsonView data={e.response.headers} maxHeight={200} />
528
+ </View>
529
+ </CollapsibleSection>
530
+ )}
531
+ </ScrollView>
532
+ );
533
+ }
534
+
535
+ return (
536
+ <ScrollView style={s.detailBody} contentContainerStyle={s.detailBodyContent}>
537
+ <View style={s.sectionWithCopy}>
538
+ <CopyButton text={fmt(entry.raw)} label="Event" />
539
+ <JsonView data={entry.raw} maxHeight={400} />
540
+ </View>
541
+ </ScrollView>
542
+ );
543
+ });
544
+
545
+ // ── Styles ─────────────────────────────────────────────────────────────
546
+
547
+ const s = StyleSheet.create({
548
+ container: { flex: 1, backgroundColor: Colors.background },
549
+ scrollContent: { paddingTop: 12, paddingBottom: 60 },
550
+
551
+ // Center (loading / empty)
552
+ center: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: Colors.background, padding: 32 },
553
+ emptyClock: {
554
+ width: 64, height: 64, borderRadius: 32,
555
+ backgroundColor: Colors.surface,
556
+ alignItems: 'center', justifyContent: 'center',
557
+ borderWidth: 1, borderColor: Colors.border,
558
+ marginBottom: 16,
559
+ },
560
+ emptyClockText: { fontSize: 28 },
561
+ emptyTitle: { fontSize: 17, fontWeight: '600', color: Colors.text, marginBottom: 6 },
562
+ emptySub: { fontSize: 14, color: Colors.textSecondary, textAlign: 'center', lineHeight: 20 },
563
+
564
+ // Current session
565
+ currentCard: {
566
+ backgroundColor: Colors.primary,
567
+ marginHorizontal: 16,
568
+ borderRadius: 14,
569
+ padding: 16,
570
+ marginBottom: 20,
571
+ },
572
+ currentRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 6 },
573
+ pulseDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: '#4CD964', marginRight: 8 },
574
+ currentLabel: { fontSize: 11, fontWeight: '700', color: 'rgba(255,255,255,0.7)', letterSpacing: 0.8 },
575
+ currentTime: { fontSize: 15, color: '#FFFFFF', fontWeight: '500' },
576
+
577
+ // Timeline
578
+ timelineSection: { paddingHorizontal: 16 },
579
+ timelineHeader: { fontSize: 11, fontWeight: '700', color: Colors.textLight, letterSpacing: 0.8, marginBottom: 12 },
580
+ timelineRow: { flexDirection: 'row' },
581
+
582
+ railCol: { width: 24, alignItems: 'center', paddingTop: 14 },
583
+ railDot: {
584
+ width: 10, height: 10, borderRadius: 5,
585
+ backgroundColor: Colors.surface,
586
+ borderWidth: 2, borderColor: Colors.textLight,
587
+ zIndex: 1,
588
+ },
589
+ railLine: { width: 2, flex: 1, backgroundColor: Colors.border, marginTop: -1 },
590
+
591
+ historyCard: {
592
+ flex: 1,
593
+ backgroundColor: Colors.surface,
594
+ borderRadius: 12,
595
+ padding: 14,
596
+ marginBottom: 10,
597
+ marginLeft: 8,
598
+ },
599
+ cardTop: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 8 },
600
+ cardTime: { fontSize: 15, fontWeight: '600', color: Colors.text },
601
+ cardRelative: { fontSize: 13, color: Colors.textSecondary },
602
+ cardEmpty: { fontSize: 13, color: Colors.textLight, marginBottom: 4 },
603
+
604
+ pillRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginBottom: 8 },
605
+ pill: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 8, paddingVertical: 3, borderRadius: 6 },
606
+ pillDot: { width: 6, height: 6, borderRadius: 3, marginRight: 5 },
607
+ pillText: { fontSize: 12, fontWeight: '600' },
608
+
609
+ cardFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 4 },
610
+ cardId: { fontSize: 11, color: Colors.textLight, fontFamily: 'Courier', fontWeight: '500' },
611
+ cardChevron: { fontSize: 20, color: Colors.textLight, fontWeight: '400' },
612
+
613
+ // ── Session detail header ──
614
+ sessionHeader: {
615
+ flexDirection: 'row',
616
+ alignItems: 'center',
617
+ justifyContent: 'space-between',
618
+ paddingHorizontal: 16,
619
+ paddingVertical: 10,
620
+ backgroundColor: Colors.surface,
621
+ borderBottomWidth: StyleSheet.hairlineWidth,
622
+ borderBottomColor: Colors.border,
623
+ },
624
+ headerBackBtn: { flexDirection: 'row', alignItems: 'center', paddingVertical: 4 },
625
+ headerArrow: { fontSize: 22, color: Colors.primary, fontWeight: '400', marginRight: 2 },
626
+ headerBackLabel: { fontSize: 15, color: Colors.primary, fontWeight: '600' },
627
+ headerMeta: { fontSize: 12, color: Colors.textSecondary, fontFamily: 'Courier' },
628
+
629
+ // ── Filter bar ──
630
+ filterBar: {
631
+ flexDirection: 'row',
632
+ paddingHorizontal: 12,
633
+ paddingVertical: 8,
634
+ backgroundColor: Colors.background,
635
+ gap: 6,
636
+ },
637
+ chip: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 8, backgroundColor: Colors.surface },
638
+ chipLabel: { fontSize: 13, fontWeight: '500', color: Colors.textSecondary },
639
+ chipLabelActive: { color: '#FFFFFF' },
640
+ chipCount: { fontSize: 12, fontWeight: '600', color: Colors.textLight },
641
+ chipCountActive: { color: 'rgba(255,255,255,0.8)' },
642
+
643
+ // ── Log rows (inside LogListScreen cards) ──
644
+ rowContent: { flexDirection: 'row', padding: 14, alignItems: 'flex-start' },
645
+ levelDot: {
646
+ width: 24, height: 24, borderRadius: 12,
647
+ alignItems: 'center', justifyContent: 'center',
648
+ marginRight: 12, marginTop: 1,
649
+ },
650
+ levelIcon: { color: '#FFF', fontSize: 11, fontWeight: '700' },
651
+ statusBar: { width: 3, borderRadius: 2, marginRight: 12, minHeight: 36 },
652
+ rowBody: { flex: 1 },
653
+ rowMsg: { fontSize: 14, color: Colors.text, lineHeight: 20 },
654
+ rowTime: { fontSize: 12, color: Colors.textSecondary, marginTop: 4 },
655
+ rowMeta: { flexDirection: 'row', alignItems: 'center', marginBottom: 4, gap: 6 },
656
+ methodText: { fontSize: 13, fontWeight: '700' },
657
+ miniPill: { paddingHorizontal: 7, paddingVertical: 2, borderRadius: 4 },
658
+ miniPillText: { color: '#FFF', fontSize: 10, fontWeight: '700' },
659
+ trackDot: { width: 10, height: 10, borderRadius: 5, backgroundColor: Colors.purple, marginRight: 10, marginTop: 5 },
660
+
661
+ // ── Log detail header ──
662
+ levelBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6 },
663
+ levelBadgeText: { color: '#FFF', fontSize: 12, fontWeight: '700' },
664
+ detailTimestamp: { flex: 1, fontSize: 13, color: Colors.textSecondary, textAlign: 'right' },
665
+ networkHeaderBadges: { flexDirection: 'row', alignItems: 'center', gap: 8, flex: 1 },
666
+ methodBadge: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6 },
667
+ methodBadgeText: { color: '#FFF', fontSize: 12, fontWeight: '700' },
668
+ statusPill: { paddingHorizontal: 9, paddingVertical: 3, borderRadius: 6 },
669
+ statusPillText: { color: '#FFF', fontSize: 11, fontWeight: '700' },
670
+ durationText: { fontSize: 12, color: Colors.textSecondary, fontWeight: '500' },
671
+
672
+ // ── Log detail body ──
673
+ detailBody: { flex: 1 },
674
+ detailBodyContent: { padding: 12, paddingBottom: 40 },
675
+ sectionWithCopy: { gap: 8 },
676
+ plainText: { fontFamily: 'Courier', fontSize: 13, color: Colors.text, lineHeight: 20 },
677
+
678
+ urlCard: {
679
+ flexDirection: 'row', alignItems: 'center',
680
+ backgroundColor: Colors.surface,
681
+ borderRadius: 12, padding: 14, marginBottom: 8, gap: 8,
682
+ },
683
+ urlText: { flex: 1, fontSize: 13, color: Colors.textSecondary, lineHeight: 18 },
684
+ emptySection: { fontSize: 13, color: Colors.textLight, paddingVertical: 4 },
685
+ errorBox: {
686
+ backgroundColor: 'rgba(255,59,48,0.06)',
687
+ borderWidth: 1, borderColor: 'rgba(255,59,48,0.15)',
688
+ borderRadius: 10, padding: 12, marginBottom: 10,
689
+ flexDirection: 'row', alignItems: 'flex-start', gap: 8,
690
+ },
691
+ errorIcon: { fontSize: 14, color: Colors.error },
692
+ errorText: { flex: 1, fontSize: 13, color: Colors.error, lineHeight: 18 },
693
+ });