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