rn-backstage 1.0.0 → 1.2.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 (72) hide show
  1. package/README.md +74 -23
  2. package/lib/commonjs/Backstage.js +55 -5
  3. package/lib/commonjs/Backstage.js.map +1 -1
  4. package/lib/commonjs/components/BackstagePanel.js +36 -5
  5. package/lib/commonjs/components/BackstagePanel.js.map +1 -1
  6. package/lib/commonjs/components/FlagsTab.js +255 -0
  7. package/lib/commonjs/components/FlagsTab.js.map +1 -0
  8. package/lib/commonjs/components/NetworkItem.js +537 -0
  9. package/lib/commonjs/components/NetworkItem.js.map +1 -0
  10. package/lib/commonjs/components/NetworkTab.js +274 -0
  11. package/lib/commonjs/components/NetworkTab.js.map +1 -0
  12. package/lib/commonjs/constants.js +11 -3
  13. package/lib/commonjs/constants.js.map +1 -1
  14. package/lib/commonjs/index.js +6 -0
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/log-interceptor.js +8 -1
  17. package/lib/commonjs/log-interceptor.js.map +1 -1
  18. package/lib/commonjs/network-interceptor.js +417 -0
  19. package/lib/commonjs/network-interceptor.js.map +1 -0
  20. package/lib/commonjs/types.js +9 -1
  21. package/lib/commonjs/types.js.map +1 -1
  22. package/lib/module/Backstage.js +56 -6
  23. package/lib/module/Backstage.js.map +1 -1
  24. package/lib/module/components/BackstagePanel.js +36 -5
  25. package/lib/module/components/BackstagePanel.js.map +1 -1
  26. package/lib/module/components/FlagsTab.js +250 -0
  27. package/lib/module/components/FlagsTab.js.map +1 -0
  28. package/lib/module/components/NetworkItem.js +532 -0
  29. package/lib/module/components/NetworkItem.js.map +1 -0
  30. package/lib/module/components/NetworkTab.js +269 -0
  31. package/lib/module/components/NetworkTab.js.map +1 -0
  32. package/lib/module/constants.js +10 -2
  33. package/lib/module/constants.js.map +1 -1
  34. package/lib/module/index.js +1 -1
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/module/log-interceptor.js +8 -1
  37. package/lib/module/log-interceptor.js.map +1 -1
  38. package/lib/module/network-interceptor.js +409 -0
  39. package/lib/module/network-interceptor.js.map +1 -0
  40. package/lib/module/types.js +10 -0
  41. package/lib/module/types.js.map +1 -1
  42. package/lib/typescript/Backstage.d.ts.map +1 -1
  43. package/lib/typescript/components/BackstagePanel.d.ts +7 -1
  44. package/lib/typescript/components/BackstagePanel.d.ts.map +1 -1
  45. package/lib/typescript/components/FlagsTab.d.ts +9 -0
  46. package/lib/typescript/components/FlagsTab.d.ts.map +1 -0
  47. package/lib/typescript/components/NetworkItem.d.ts +9 -0
  48. package/lib/typescript/components/NetworkItem.d.ts.map +1 -0
  49. package/lib/typescript/components/NetworkTab.d.ts +11 -0
  50. package/lib/typescript/components/NetworkTab.d.ts.map +1 -0
  51. package/lib/typescript/constants.d.ts +8 -0
  52. package/lib/typescript/constants.d.ts.map +1 -1
  53. package/lib/typescript/index.d.ts +2 -2
  54. package/lib/typescript/index.d.ts.map +1 -1
  55. package/lib/typescript/log-interceptor.d.ts +3 -1
  56. package/lib/typescript/log-interceptor.d.ts.map +1 -1
  57. package/lib/typescript/network-interceptor.d.ts +35 -0
  58. package/lib/typescript/network-interceptor.d.ts.map +1 -0
  59. package/lib/typescript/types.d.ts +46 -0
  60. package/lib/typescript/types.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/Backstage.tsx +64 -6
  63. package/src/components/BackstagePanel.tsx +46 -5
  64. package/src/components/FlagsTab.tsx +249 -0
  65. package/src/components/InfoTab.tsx +1 -1
  66. package/src/components/NetworkItem.tsx +536 -0
  67. package/src/components/NetworkTab.tsx +274 -0
  68. package/src/constants.ts +11 -2
  69. package/src/index.ts +3 -1
  70. package/src/log-interceptor.ts +12 -1
  71. package/src/network-interceptor.ts +468 -0
  72. package/src/types.ts +60 -0
@@ -14,6 +14,28 @@ export interface LogEntry {
14
14
  data?: unknown;
15
15
  timestamp: number;
16
16
  }
17
+ export declare enum NetworkState {
18
+ pending = "pending",
19
+ completed = "completed",
20
+ error = "error"
21
+ }
22
+ export interface NetworkEntry {
23
+ id: string;
24
+ method: string;
25
+ url: string;
26
+ startTime: number;
27
+ endTime?: number;
28
+ duration?: number;
29
+ status?: number;
30
+ statusText?: string;
31
+ requestHeaders?: Record<string, string>;
32
+ responseHeaders?: Record<string, string>;
33
+ requestBody?: string;
34
+ responseBody?: string;
35
+ responseSize?: number;
36
+ error?: string;
37
+ state: NetworkState;
38
+ }
17
39
  export interface AppInfoItem {
18
40
  label: string;
19
41
  value: string;
@@ -26,6 +48,16 @@ export interface QuickAction {
26
48
  destructive?: boolean;
27
49
  testID?: string;
28
50
  }
51
+ export interface FeatureFlag {
52
+ /** Unique key for this flag */
53
+ key: string;
54
+ /** Display label */
55
+ label: string;
56
+ /** Current value */
57
+ value: boolean;
58
+ /** Optional description shown below the label */
59
+ description?: string;
60
+ }
29
61
  export interface BackstageTab {
30
62
  key: string;
31
63
  title: string;
@@ -67,6 +99,10 @@ export interface BackstageProps {
67
99
  state?: Record<string, unknown>;
68
100
  /** Custom action buttons in the Info tab */
69
101
  quickActions?: QuickAction[];
102
+ /** Feature flags to display with toggle switches */
103
+ featureFlags?: FeatureFlag[];
104
+ /** Callback when a feature flag is toggled */
105
+ onToggleFeatureFlag?: (key: string, value: boolean) => void;
70
106
  /** Maximum number of logs to retain in memory. Default: 500 */
71
107
  maxLogs?: number;
72
108
  /** Log messages containing these strings will be excluded */
@@ -85,6 +121,16 @@ export interface BackstageProps {
85
121
  initialY?: number;
86
122
  /** Text displayed on the floating pill. Defaults to appVersion or "DEV" */
87
123
  pillText?: string;
124
+ /** Whether to enable network request interception. Default: true */
125
+ enableNetworkInspector?: boolean;
126
+ /** Maximum number of network entries to retain in memory. Default: 500 */
127
+ maxNetworkEntries?: number;
128
+ /** Maximum body size (bytes) to capture per request/response. Default: 65536 (64KB) */
129
+ maxNetworkBodySize?: number;
130
+ /** URL substrings to exclude from network capture */
131
+ networkFilters?: string[];
132
+ /** Auto-filter console.logs from network callbacks (e.g., Axios interceptors) out of the Logs tab. Default: true */
133
+ autoFilterNetworkLogs?: boolean;
88
134
  }
89
135
  export interface BackstageStyleOverrides {
90
136
  pillStyle?: StyleProp<ViewStyle>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rn-backstage",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A zero-dependency developer/QA debug panel for React Native apps",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
package/src/Backstage.tsx CHANGED
@@ -7,11 +7,16 @@ import React, {
7
7
  useState,
8
8
  } from 'react'
9
9
  import { StyleSheet, View } from 'react-native'
10
- import { DEFAULT_MAX_LOGS } from './constants'
10
+ import { DEFAULT_MAX_LOGS, DEFAULT_MAX_NETWORK_ENTRIES } from './constants'
11
11
  import { installInterceptor, LogBuffer, uninstallInterceptor } from './log-interceptor'
12
+ import {
13
+ installNetworkInterceptor,
14
+ NetworkBuffer,
15
+ uninstallNetworkInterceptor,
16
+ } from './network-interceptor'
12
17
  import { FloatingPill } from './components/FloatingPill'
13
18
  import { BackstagePanel } from './components/BackstagePanel'
14
- import type { BackstageProps, BackstageRef, LogEntry } from './types'
19
+ import type { BackstageProps, BackstageRef, LogEntry, NetworkEntry } from './types'
15
20
 
16
21
  // ─── Component ───────────────────────────────────────────────────────────────
17
22
 
@@ -25,6 +30,8 @@ export const Backstage = forwardRef<BackstageRef, BackstageProps>(
25
30
  deviceInfo,
26
31
  state,
27
32
  quickActions,
33
+ featureFlags,
34
+ onToggleFeatureFlag,
28
35
  maxLogs = DEFAULT_MAX_LOGS,
29
36
  logFilters,
30
37
  onCopyLogs,
@@ -34,21 +41,29 @@ export const Backstage = forwardRef<BackstageRef, BackstageProps>(
34
41
  initialX,
35
42
  initialY,
36
43
  pillText,
44
+ enableNetworkInspector = true,
45
+ maxNetworkEntries = DEFAULT_MAX_NETWORK_ENTRIES,
46
+ maxNetworkBodySize,
47
+ networkFilters,
48
+ autoFilterNetworkLogs = true,
37
49
  },
38
50
  ref,
39
51
  ) => {
40
52
  const [panelVisible, setPanelVisible] = useState(false)
41
53
  const [hasError, setHasError] = useState(false)
42
54
  const [logs, setLogs] = useState<LogEntry[]>([])
55
+ const [networkEntries, setNetworkEntries] = useState<NetworkEntry[]>([])
43
56
 
44
57
  const logBuffer = useRef(new LogBuffer(maxLogs))
58
+ const networkBuffer = useRef(new NetworkBuffer(maxNetworkEntries))
45
59
 
46
60
  // ── Panel controls ────────────────────────────────────────
47
61
 
48
62
  const openPanel = useCallback(() => {
49
63
  setPanelVisible(true)
50
- // Sync logs when opening panel
64
+ // Sync logs and network entries when opening panel
51
65
  setLogs(logBuffer.current.getAll())
66
+ setNetworkEntries(networkBuffer.current.getAll())
52
67
  }, [])
53
68
 
54
69
  const closePanel = useCallback(() => {
@@ -61,12 +76,23 @@ export const Backstage = forwardRef<BackstageRef, BackstageProps>(
61
76
  setHasError(false)
62
77
  }, [])
63
78
 
64
- // ── Refresh logs ──────────────────────────────────────────
79
+ // ── Refresh logs ──────────────────────────────────────────────────
65
80
 
66
81
  const refreshLogs = useCallback(() => {
67
82
  setLogs(logBuffer.current.getAll())
68
83
  }, [])
69
84
 
85
+ // ── Network controls ──────────────────────────────────────────────
86
+
87
+ const refreshNetwork = useCallback(() => {
88
+ setNetworkEntries(networkBuffer.current.getAll())
89
+ }, [])
90
+
91
+ const clearNetwork = useCallback(() => {
92
+ networkBuffer.current.clear()
93
+ setNetworkEntries([])
94
+ }, [])
95
+
70
96
  // ── Install console interceptor ───────────────────────────
71
97
 
72
98
  useEffect(() => {
@@ -79,12 +105,38 @@ export const Backstage = forwardRef<BackstageRef, BackstageProps>(
79
105
  }
80
106
  }
81
107
 
82
- installInterceptor(handleLogEntry, logFilters)
108
+ installInterceptor(handleLogEntry, logFilters, {
109
+ autoFilterNetworkLogs: enableNetworkInspector && autoFilterNetworkLogs,
110
+ })
83
111
 
84
112
  return () => {
85
113
  uninstallInterceptor()
86
114
  }
87
- }, [logFilters])
115
+ }, [logFilters, enableNetworkInspector, autoFilterNetworkLogs])
116
+
117
+ // ── Install network interceptor ───────────────────────────────
118
+
119
+ useEffect(() => {
120
+ if (!enableNetworkInspector) return
121
+
122
+ const handleNetworkEntry = (entry: NetworkEntry) => {
123
+ networkBuffer.current.upsert(entry)
124
+
125
+ // Auto-refresh if panel is open
126
+ if (panelVisible) {
127
+ setNetworkEntries(networkBuffer.current.getAll())
128
+ }
129
+ }
130
+
131
+ installNetworkInterceptor(handleNetworkEntry, {
132
+ filters: networkFilters,
133
+ maxBodySize: maxNetworkBodySize,
134
+ })
135
+
136
+ return () => {
137
+ uninstallNetworkInterceptor()
138
+ }
139
+ }, [enableNetworkInspector, networkFilters, maxNetworkBodySize, panelVisible])
88
140
 
89
141
  // ── Expose ref methods ────────────────────────────────────
90
142
 
@@ -119,9 +171,15 @@ export const Backstage = forwardRef<BackstageRef, BackstageProps>(
119
171
  deviceInfo={deviceInfo}
120
172
  state={state}
121
173
  quickActions={quickActions}
174
+ featureFlags={featureFlags}
175
+ onToggleFeatureFlag={onToggleFeatureFlag}
122
176
  logs={logs}
123
177
  onRefreshLogs={refreshLogs}
124
178
  onCopyLogs={onCopyLogs}
179
+ networkEntries={networkEntries}
180
+ onRefreshNetwork={refreshNetwork}
181
+ onClearNetwork={clearNetwork}
182
+ onCopyNetwork={onCopyLogs}
125
183
  extraTabs={extraTabs}
126
184
  styles={propStyles}
127
185
  >
@@ -12,11 +12,15 @@ import { DarkTheme, MonospaceFont, TestIDs } from '../constants'
12
12
  import { TabBar } from './TabBar'
13
13
  import { InfoTab } from './InfoTab'
14
14
  import { LogsTab } from './LogsTab'
15
+ import { NetworkTab } from './NetworkTab'
16
+ import { FlagsTab } from './FlagsTab'
15
17
  import type {
16
18
  AppInfoItem,
17
19
  BackstageStyleOverrides,
18
20
  BackstageTab,
21
+ FeatureFlag,
19
22
  LogEntry,
23
+ NetworkEntry,
20
24
  QuickAction,
21
25
  } from '../types'
22
26
 
@@ -33,12 +37,20 @@ interface BackstagePanelProps {
33
37
  deviceInfo?: AppInfoItem[]
34
38
  state?: Record<string, unknown>
35
39
  quickActions?: QuickAction[]
40
+ featureFlags?: FeatureFlag[]
41
+ onToggleFeatureFlag?: (key: string, value: boolean) => void
36
42
 
37
43
  // Logs tab props
38
44
  logs: LogEntry[]
39
45
  onRefreshLogs: () => void
40
46
  onCopyLogs?: (logs: string) => void
41
47
 
48
+ // Network tab props
49
+ networkEntries: NetworkEntry[]
50
+ onRefreshNetwork: () => void
51
+ onClearNetwork: () => void
52
+ onCopyNetwork?: (text: string) => void
53
+
42
54
  // Extensibility
43
55
  extraTabs?: BackstageTab[]
44
56
 
@@ -47,13 +59,17 @@ interface BackstagePanelProps {
47
59
  children?: React.ReactNode
48
60
  }
49
61
 
50
- // ─── Built-in Tab Definitions ────────────────────────────────────────────────
62
+ // ─── Built-in Tab Definitions ────────────────────────────────────────────
51
63
 
52
- const BUILT_IN_TABS = [
64
+ const ALWAYS_ON_TABS = [
53
65
  { key: 'info', title: 'Info', icon: 'ℹ️' },
54
- { key: 'logs', title: 'Logs', icon: '📋' },
66
+ { key: 'network', title: 'Network', icon: '🌐' },
55
67
  ]
56
68
 
69
+ const FLAGS_TAB = { key: 'flags', title: 'Flags', icon: '🎚' }
70
+
71
+ const LOGS_TAB = { key: 'logs', title: 'Logs', icon: '📋' }
72
+
57
73
  // ─── Component ───────────────────────────────────────────────────────────────
58
74
 
59
75
  export const BackstagePanel: React.FC<BackstagePanelProps> = ({
@@ -65,18 +81,27 @@ export const BackstagePanel: React.FC<BackstagePanelProps> = ({
65
81
  deviceInfo,
66
82
  state,
67
83
  quickActions,
84
+ featureFlags,
85
+ onToggleFeatureFlag,
68
86
  logs,
69
87
  onRefreshLogs,
70
88
  onCopyLogs,
89
+ networkEntries,
90
+ onRefreshNetwork,
91
+ onClearNetwork,
92
+ onCopyNetwork,
71
93
  extraTabs = [],
72
94
  styles: propStyles,
73
95
  children,
74
96
  }) => {
75
97
  const [activeTab, setActiveTab] = useState('info')
76
98
 
77
- // Compose all tabs: built-in + extra
99
+ // Compose all tabs: built-in (conditionally including Flags) + extra
100
+ const hasFlags = featureFlags && featureFlags.length > 0
78
101
  const allTabs = [
79
- ...BUILT_IN_TABS,
102
+ ...ALWAYS_ON_TABS,
103
+ ...(hasFlags ? [FLAGS_TAB] : []),
104
+ LOGS_TAB,
80
105
  ...extraTabs.map(t => ({ key: t.key, title: t.title, icon: t.icon })),
81
106
  ]
82
107
 
@@ -98,6 +123,22 @@ export const BackstagePanel: React.FC<BackstagePanelProps> = ({
98
123
  {children}
99
124
  </InfoTab>
100
125
  )
126
+ case 'flags':
127
+ return (
128
+ <FlagsTab
129
+ flags={featureFlags ?? []}
130
+ onToggle={onToggleFeatureFlag}
131
+ />
132
+ )
133
+ case 'network':
134
+ return (
135
+ <NetworkTab
136
+ entries={networkEntries}
137
+ onRefresh={onRefreshNetwork}
138
+ onClear={onClearNetwork}
139
+ onCopy={onCopyNetwork}
140
+ />
141
+ )
101
142
  case 'logs':
102
143
  return (
103
144
  <LogsTab
@@ -0,0 +1,249 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { ScrollView, StyleSheet, Switch, Text, TextInput, View } from 'react-native'
3
+ import { DarkTheme, MonospaceFont } from '../constants'
4
+ import type { FeatureFlag } from '../types'
5
+
6
+ // ─── Types ───────────────────────────────────────────────────────────────────
7
+
8
+ interface FlagsTabProps {
9
+ flags: FeatureFlag[]
10
+ onToggle?: (key: string, value: boolean) => void
11
+ }
12
+
13
+ // ─── Component ───────────────────────────────────────────────────────────────
14
+
15
+ export const FlagsTab: React.FC<FlagsTabProps> = ({ flags, onToggle }) => {
16
+ const [searchText, setSearchText] = useState('')
17
+
18
+ const filteredFlags = useMemo(() => {
19
+ if (!searchText.trim()) return flags
20
+ const query = searchText.toLowerCase()
21
+ return flags.filter(
22
+ f =>
23
+ f.label.toLowerCase().includes(query) ||
24
+ f.key.toLowerCase().includes(query) ||
25
+ (f.description && f.description.toLowerCase().includes(query)),
26
+ )
27
+ }, [flags, searchText])
28
+
29
+ // Stats
30
+ const enabledCount = flags.filter(f => f.value).length
31
+
32
+ return (
33
+ <View style={styles.container}>
34
+ {/* Search Bar */}
35
+ <View style={styles.searchContainer}>
36
+ <View style={styles.searchInputWrapper}>
37
+ <Text style={styles.searchIcon}>⌕</Text>
38
+ <TextInput
39
+ style={styles.searchInput}
40
+ placeholder="Filter flags..."
41
+ placeholderTextColor={DarkTheme.textMuted}
42
+ value={searchText}
43
+ onChangeText={setSearchText}
44
+ autoCapitalize="none"
45
+ autoCorrect={false}
46
+ clearButtonMode="while-editing"
47
+ returnKeyType="search"
48
+ />
49
+ </View>
50
+ </View>
51
+
52
+ {/* Stats Bar */}
53
+ <View style={styles.statsBar}>
54
+ <Text style={styles.statText}>
55
+ {filteredFlags.length === flags.length
56
+ ? `${flags.length} flag${flags.length !== 1 ? 's' : ''}`
57
+ : `${filteredFlags.length} of ${flags.length}`}
58
+ </Text>
59
+ <Text style={styles.statText}>
60
+ <Text style={styles.statEnabled}>{enabledCount} on</Text>
61
+ {' · '}
62
+ <Text style={styles.statDisabled}>{flags.length - enabledCount} off</Text>
63
+ </Text>
64
+ </View>
65
+
66
+ {/* Flag List */}
67
+ <ScrollView
68
+ style={styles.list}
69
+ contentContainerStyle={
70
+ filteredFlags.length === 0 ? styles.emptyContainer : styles.listContent
71
+ }
72
+ showsVerticalScrollIndicator={false}
73
+ >
74
+ {filteredFlags.length === 0 ? (
75
+ <View style={styles.emptyState}>
76
+ <Text style={styles.emptyIcon}>🎚</Text>
77
+ <Text style={styles.emptyTitle}>No flags match</Text>
78
+ <Text style={styles.emptySubtitle}>Try adjusting your search</Text>
79
+ </View>
80
+ ) : (
81
+ filteredFlags.map((flag, index) => (
82
+ <View key={flag.key}>
83
+ <View style={styles.flagRow}>
84
+ <View style={styles.flagInfo}>
85
+ <View style={styles.flagHeader}>
86
+ <Text style={styles.flagLabel}>{flag.label}</Text>
87
+ <Text style={[styles.flagKey, flag.value && styles.flagKeyActive]}>
88
+ {flag.key}
89
+ </Text>
90
+ </View>
91
+ {flag.description && (
92
+ <Text style={styles.flagDescription}>{flag.description}</Text>
93
+ )}
94
+ </View>
95
+ <Switch
96
+ testID={`backstage.flag.${flag.key}`}
97
+ value={flag.value}
98
+ onValueChange={(newValue: boolean) => onToggle?.(flag.key, newValue)}
99
+ trackColor={{ false: DarkTheme.border, true: DarkTheme.accent }}
100
+ thumbColor={flag.value ? '#FFFFFF' : DarkTheme.textMuted}
101
+ ios_backgroundColor={DarkTheme.border}
102
+ />
103
+ </View>
104
+ {index < filteredFlags.length - 1 && <View style={styles.divider} />}
105
+ </View>
106
+ ))
107
+ )}
108
+ </ScrollView>
109
+ </View>
110
+ )
111
+ }
112
+
113
+ // ─── Styles ──────────────────────────────────────────────────────────────────
114
+
115
+ const styles = StyleSheet.create({
116
+ container: {
117
+ flex: 1,
118
+ },
119
+ searchContainer: {
120
+ paddingHorizontal: 14,
121
+ paddingVertical: 10,
122
+ borderBottomWidth: 1,
123
+ borderBottomColor: DarkTheme.border,
124
+ },
125
+ searchInputWrapper: {
126
+ flexDirection: 'row',
127
+ alignItems: 'center',
128
+ backgroundColor: DarkTheme.surfaceElevated,
129
+ borderRadius: 10,
130
+ borderWidth: 1,
131
+ borderColor: DarkTheme.border,
132
+ paddingHorizontal: 12,
133
+ height: 38,
134
+ },
135
+ searchIcon: {
136
+ fontSize: 16,
137
+ color: DarkTheme.textMuted,
138
+ marginRight: 8,
139
+ },
140
+ searchInput: {
141
+ flex: 1,
142
+ fontFamily: MonospaceFont,
143
+ fontSize: 13,
144
+ color: DarkTheme.text,
145
+ padding: 0,
146
+ },
147
+ statsBar: {
148
+ flexDirection: 'row',
149
+ justifyContent: 'space-between',
150
+ alignItems: 'center',
151
+ paddingHorizontal: 16,
152
+ paddingVertical: 6,
153
+ backgroundColor: DarkTheme.surface,
154
+ },
155
+ statText: {
156
+ fontFamily: MonospaceFont,
157
+ fontSize: 11,
158
+ color: DarkTheme.textMuted,
159
+ },
160
+ statEnabled: {
161
+ color: DarkTheme.success,
162
+ fontWeight: '700',
163
+ },
164
+ statDisabled: {
165
+ color: DarkTheme.textMuted,
166
+ },
167
+ list: {
168
+ flex: 1,
169
+ },
170
+ listContent: {
171
+ paddingHorizontal: 16,
172
+ paddingVertical: 8,
173
+ },
174
+ emptyContainer: {
175
+ flex: 1,
176
+ justifyContent: 'center',
177
+ },
178
+ emptyState: {
179
+ alignItems: 'center',
180
+ justifyContent: 'center',
181
+ padding: 40,
182
+ },
183
+ emptyIcon: {
184
+ fontSize: 48,
185
+ marginBottom: 16,
186
+ opacity: 0.5,
187
+ },
188
+ emptyTitle: {
189
+ fontFamily: MonospaceFont,
190
+ fontSize: 16,
191
+ fontWeight: '700',
192
+ color: DarkTheme.textSecondary,
193
+ marginBottom: 8,
194
+ },
195
+ emptySubtitle: {
196
+ fontFamily: MonospaceFont,
197
+ fontSize: 13,
198
+ color: DarkTheme.textMuted,
199
+ textAlign: 'center',
200
+ },
201
+ flagRow: {
202
+ flexDirection: 'row',
203
+ justifyContent: 'space-between',
204
+ alignItems: 'center',
205
+ paddingVertical: 14,
206
+ paddingHorizontal: 4,
207
+ },
208
+ flagInfo: {
209
+ flex: 1,
210
+ marginRight: 16,
211
+ },
212
+ flagHeader: {
213
+ flexDirection: 'row',
214
+ alignItems: 'center',
215
+ gap: 8,
216
+ },
217
+ flagLabel: {
218
+ fontFamily: MonospaceFont,
219
+ fontSize: 14,
220
+ color: DarkTheme.text,
221
+ fontWeight: '600',
222
+ },
223
+ flagKey: {
224
+ fontFamily: MonospaceFont,
225
+ fontSize: 10,
226
+ color: DarkTheme.textMuted,
227
+ backgroundColor: DarkTheme.surfaceElevated,
228
+ borderRadius: 4,
229
+ paddingHorizontal: 6,
230
+ paddingVertical: 1,
231
+ overflow: 'hidden',
232
+ },
233
+ flagKeyActive: {
234
+ color: DarkTheme.accent,
235
+ backgroundColor: DarkTheme.accentDim,
236
+ },
237
+ flagDescription: {
238
+ fontFamily: MonospaceFont,
239
+ fontSize: 12,
240
+ color: DarkTheme.textMuted,
241
+ marginTop: 4,
242
+ lineHeight: 18,
243
+ },
244
+ divider: {
245
+ height: 1,
246
+ backgroundColor: DarkTheme.border,
247
+ marginHorizontal: 4,
248
+ },
249
+ })
@@ -148,7 +148,7 @@ export const InfoTab: React.FC<InfoTabProps> = ({
148
148
  </View>
149
149
  )}
150
150
 
151
- {/* ── Quick Actions Section ───────────────────────────────── */}
151
+ {/* ── Quick Actions Section ─────────────────────────────────────── */}
152
152
  {quickActions.length > 0 && (
153
153
  <View style={styles.section}>
154
154
  <SectionHeader title="QUICK ACTIONS" titleStyle={propStyles?.sectionTitleStyle} />