@buoy-gg/storage 1.7.2

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 (187) hide show
  1. package/README.md +607 -0
  2. package/lib/commonjs/index.js +34 -0
  3. package/lib/commonjs/package.json +1 -0
  4. package/lib/commonjs/preset.js +94 -0
  5. package/lib/commonjs/storage/components/DiffViewer/DiffOptionsPanel.js +356 -0
  6. package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +29 -0
  7. package/lib/commonjs/storage/components/DiffViewer/components/DiffSummary.js +121 -0
  8. package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +419 -0
  9. package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +122 -0
  10. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +924 -0
  11. package/lib/commonjs/storage/components/GameUIStorageStats.js +746 -0
  12. package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +257 -0
  13. package/lib/commonjs/storage/components/MMKVInstanceSelector.js +418 -0
  14. package/lib/commonjs/storage/components/SelectionActionBar.js +224 -0
  15. package/lib/commonjs/storage/components/StorageActionButtons.js +239 -0
  16. package/lib/commonjs/storage/components/StorageActions.js +192 -0
  17. package/lib/commonjs/storage/components/StorageBrowserMode.js +31 -0
  18. package/lib/commonjs/storage/components/StorageEventDetailContent.js +1025 -0
  19. package/lib/commonjs/storage/components/StorageEventFilterView.js +141 -0
  20. package/lib/commonjs/storage/components/StorageEventListener.js +357 -0
  21. package/lib/commonjs/storage/components/StorageEventsSection.js +24 -0
  22. package/lib/commonjs/storage/components/StorageFilterCards.js +345 -0
  23. package/lib/commonjs/storage/components/StorageFilterViewV2.js +42 -0
  24. package/lib/commonjs/storage/components/StorageKeyCard.js +516 -0
  25. package/lib/commonjs/storage/components/StorageKeyRow.js +356 -0
  26. package/lib/commonjs/storage/components/StorageKeySection.js +105 -0
  27. package/lib/commonjs/storage/components/StorageKeyStats.js +344 -0
  28. package/lib/commonjs/storage/components/StorageModalWithTabs.js +871 -0
  29. package/lib/commonjs/storage/components/StorageSection.js +43 -0
  30. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +126 -0
  31. package/lib/commonjs/storage/hooks/useMMKVInstances.js +221 -0
  32. package/lib/commonjs/storage/hooks/useMMKVKeys.js +362 -0
  33. package/lib/commonjs/storage/hooks/useTickEverySecond.js +21 -0
  34. package/lib/commonjs/storage/index.js +148 -0
  35. package/lib/commonjs/storage/types.js +5 -0
  36. package/lib/commonjs/storage/utils/AsyncStorageListener.js +510 -0
  37. package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +202 -0
  38. package/lib/commonjs/storage/utils/MMKVListener.js +380 -0
  39. package/lib/commonjs/storage/utils/clearAllStorage.js +47 -0
  40. package/lib/commonjs/storage/utils/index.js +180 -0
  41. package/lib/commonjs/storage/utils/lineDiff.js +363 -0
  42. package/lib/commonjs/storage/utils/mmkvAvailability.js +62 -0
  43. package/lib/commonjs/storage/utils/mmkvTypeDetection.js +139 -0
  44. package/lib/commonjs/storage/utils/objectDiff.js +157 -0
  45. package/lib/commonjs/storage/utils/safeAsyncStorage.js +140 -0
  46. package/lib/commonjs/storage/utils/storageActionHelpers.js +46 -0
  47. package/lib/commonjs/storage/utils/storageQueryUtils.js +35 -0
  48. package/lib/commonjs/storage/utils/valueType.js +18 -0
  49. package/lib/module/index.js +7 -0
  50. package/lib/module/preset.js +89 -0
  51. package/lib/module/storage/components/DiffViewer/DiffOptionsPanel.js +352 -0
  52. package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +25 -0
  53. package/lib/module/storage/components/DiffViewer/components/DiffSummary.js +117 -0
  54. package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +415 -0
  55. package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +118 -0
  56. package/lib/module/storage/components/GameUIStorageBrowser.js +922 -0
  57. package/lib/module/storage/components/GameUIStorageStats.js +742 -0
  58. package/lib/module/storage/components/MMKVInstanceInfoPanel.js +253 -0
  59. package/lib/module/storage/components/MMKVInstanceSelector.js +414 -0
  60. package/lib/module/storage/components/SelectionActionBar.js +221 -0
  61. package/lib/module/storage/components/StorageActionButtons.js +236 -0
  62. package/lib/module/storage/components/StorageActions.js +189 -0
  63. package/lib/module/storage/components/StorageBrowserMode.js +27 -0
  64. package/lib/module/storage/components/StorageEventDetailContent.js +1020 -0
  65. package/lib/module/storage/components/StorageEventFilterView.js +137 -0
  66. package/lib/module/storage/components/StorageEventListener.js +354 -0
  67. package/lib/module/storage/components/StorageEventsSection.js +20 -0
  68. package/lib/module/storage/components/StorageFilterCards.js +341 -0
  69. package/lib/module/storage/components/StorageFilterViewV2.js +38 -0
  70. package/lib/module/storage/components/StorageKeyCard.js +513 -0
  71. package/lib/module/storage/components/StorageKeyRow.js +353 -0
  72. package/lib/module/storage/components/StorageKeySection.js +101 -0
  73. package/lib/module/storage/components/StorageKeyStats.js +340 -0
  74. package/lib/module/storage/components/StorageModalWithTabs.js +867 -0
  75. package/lib/module/storage/components/StorageSection.js +40 -0
  76. package/lib/module/storage/hooks/useAsyncStorageKeys.js +121 -0
  77. package/lib/module/storage/hooks/useMMKVInstances.js +216 -0
  78. package/lib/module/storage/hooks/useMMKVKeys.js +359 -0
  79. package/lib/module/storage/hooks/useTickEverySecond.js +18 -0
  80. package/lib/module/storage/index.js +25 -0
  81. package/lib/module/storage/types.js +3 -0
  82. package/lib/module/storage/utils/AsyncStorageListener.js +500 -0
  83. package/lib/module/storage/utils/MMKVInstanceRegistry.js +196 -0
  84. package/lib/module/storage/utils/MMKVListener.js +367 -0
  85. package/lib/module/storage/utils/clearAllStorage.js +42 -0
  86. package/lib/module/storage/utils/index.js +22 -0
  87. package/lib/module/storage/utils/lineDiff.js +359 -0
  88. package/lib/module/storage/utils/mmkvAvailability.js +56 -0
  89. package/lib/module/storage/utils/mmkvTypeDetection.js +133 -0
  90. package/lib/module/storage/utils/objectDiff.js +153 -0
  91. package/lib/module/storage/utils/safeAsyncStorage.js +134 -0
  92. package/lib/module/storage/utils/storageActionHelpers.js +42 -0
  93. package/lib/module/storage/utils/storageQueryUtils.js +30 -0
  94. package/lib/module/storage/utils/valueType.js +14 -0
  95. package/lib/typescript/index.d.ts +3 -0
  96. package/lib/typescript/index.d.ts.map +1 -0
  97. package/lib/typescript/preset.d.ts +90 -0
  98. package/lib/typescript/preset.d.ts.map +1 -0
  99. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts +18 -0
  100. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts.map +1 -0
  101. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +7 -0
  102. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -0
  103. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts +12 -0
  104. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts.map +1 -0
  105. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +13 -0
  106. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -0
  107. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts +64 -0
  108. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +1 -0
  109. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +16 -0
  110. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -0
  111. package/lib/typescript/storage/components/GameUIStorageStats.d.ts +7 -0
  112. package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -0
  113. package/lib/typescript/storage/components/MMKVInstanceInfoPanel.d.ts +42 -0
  114. package/lib/typescript/storage/components/MMKVInstanceInfoPanel.d.ts.map +1 -0
  115. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts +35 -0
  116. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts.map +1 -0
  117. package/lib/typescript/storage/components/SelectionActionBar.d.ts +21 -0
  118. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -0
  119. package/lib/typescript/storage/components/StorageActionButtons.d.ts +21 -0
  120. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +1 -0
  121. package/lib/typescript/storage/components/StorageActions.d.ts +10 -0
  122. package/lib/typescript/storage/components/StorageActions.d.ts.map +1 -0
  123. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +18 -0
  124. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -0
  125. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +40 -0
  126. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -0
  127. package/lib/typescript/storage/components/StorageEventFilterView.d.ts +11 -0
  128. package/lib/typescript/storage/components/StorageEventFilterView.d.ts.map +1 -0
  129. package/lib/typescript/storage/components/StorageEventListener.d.ts +6 -0
  130. package/lib/typescript/storage/components/StorageEventListener.d.ts.map +1 -0
  131. package/lib/typescript/storage/components/StorageEventsSection.d.ts +7 -0
  132. package/lib/typescript/storage/components/StorageEventsSection.d.ts.map +1 -0
  133. package/lib/typescript/storage/components/StorageFilterCards.d.ts +36 -0
  134. package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -0
  135. package/lib/typescript/storage/components/StorageFilterViewV2.d.ts +9 -0
  136. package/lib/typescript/storage/components/StorageFilterViewV2.d.ts.map +1 -0
  137. package/lib/typescript/storage/components/StorageKeyCard.d.ts +17 -0
  138. package/lib/typescript/storage/components/StorageKeyCard.d.ts.map +1 -0
  139. package/lib/typescript/storage/components/StorageKeyRow.d.ts +15 -0
  140. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -0
  141. package/lib/typescript/storage/components/StorageKeySection.d.ts +25 -0
  142. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -0
  143. package/lib/typescript/storage/components/StorageKeyStats.d.ts +15 -0
  144. package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -0
  145. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +13 -0
  146. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -0
  147. package/lib/typescript/storage/components/StorageSection.d.ts +10 -0
  148. package/lib/typescript/storage/components/StorageSection.d.ts.map +1 -0
  149. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts +10 -0
  150. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -0
  151. package/lib/typescript/storage/hooks/useMMKVInstances.d.ts +114 -0
  152. package/lib/typescript/storage/hooks/useMMKVInstances.d.ts.map +1 -0
  153. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts +94 -0
  154. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -0
  155. package/lib/typescript/storage/hooks/useTickEverySecond.d.ts +6 -0
  156. package/lib/typescript/storage/hooks/useTickEverySecond.d.ts.map +1 -0
  157. package/lib/typescript/storage/index.d.ts +15 -0
  158. package/lib/typescript/storage/index.d.ts.map +1 -0
  159. package/lib/typescript/storage/types.d.ts +41 -0
  160. package/lib/typescript/storage/types.d.ts.map +1 -0
  161. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +195 -0
  162. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -0
  163. package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts +224 -0
  164. package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -0
  165. package/lib/typescript/storage/utils/MMKVListener.d.ts +218 -0
  166. package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -0
  167. package/lib/typescript/storage/utils/clearAllStorage.d.ts +11 -0
  168. package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +1 -0
  169. package/lib/typescript/storage/utils/index.d.ts +8 -0
  170. package/lib/typescript/storage/utils/index.d.ts.map +1 -0
  171. package/lib/typescript/storage/utils/lineDiff.d.ts +34 -0
  172. package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -0
  173. package/lib/typescript/storage/utils/mmkvAvailability.d.ts +23 -0
  174. package/lib/typescript/storage/utils/mmkvAvailability.d.ts.map +1 -0
  175. package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts +71 -0
  176. package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +1 -0
  177. package/lib/typescript/storage/utils/objectDiff.d.ts +35 -0
  178. package/lib/typescript/storage/utils/objectDiff.d.ts.map +1 -0
  179. package/lib/typescript/storage/utils/safeAsyncStorage.d.ts +56 -0
  180. package/lib/typescript/storage/utils/safeAsyncStorage.d.ts.map +1 -0
  181. package/lib/typescript/storage/utils/storageActionHelpers.d.ts +5 -0
  182. package/lib/typescript/storage/utils/storageActionHelpers.d.ts.map +1 -0
  183. package/lib/typescript/storage/utils/storageQueryUtils.d.ts +6 -0
  184. package/lib/typescript/storage/utils/storageQueryUtils.d.ts.map +1 -0
  185. package/lib/typescript/storage/utils/valueType.d.ts +3 -0
  186. package/lib/typescript/storage/utils/valueType.d.ts.map +1 -0
  187. package/package.json +68 -0
@@ -0,0 +1,922 @@
1
+ "use strict";
2
+
3
+ import { useMemo, useCallback, useState, useEffect } from "react";
4
+ import { StyleSheet, Text, View, TouchableOpacity, ScrollView } from "react-native";
5
+ import { Database, Zap, ProBadge, ProUpgradeModal } from "@buoy-gg/shared-ui";
6
+ import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
7
+ import { StorageKeySection } from "./StorageKeySection";
8
+ import { StorageFilterCards } from "./StorageFilterCards";
9
+ import { useAsyncStorageKeys } from "../hooks/useAsyncStorageKeys";
10
+ import { addListener } from "../utils/AsyncStorageListener";
11
+ import { useMultiMMKVKeys } from "../hooks/useMMKVKeys";
12
+ import { useMMKVInstances } from "../hooks/useMMKVInstances";
13
+ import { isMMKVAvailable } from "../utils/mmkvAvailability";
14
+ import { StorageActionButtons } from "./StorageActionButtons";
15
+ import { SelectionActionBar } from "./SelectionActionBar";
16
+
17
+ // Conditionally import MMKV listener
18
+ let addMMKVListener;
19
+ if (isMMKVAvailable()) {
20
+ const listener = require("../utils/MMKVListener");
21
+ addMMKVListener = listener.addMMKVListener;
22
+ }
23
+
24
+ // Import shared Game UI components
25
+ import { gameUIColors, macOSColors, HardDrive } from "@buoy-gg/shared-ui";
26
+
27
+ // Lazy load the license hooks to avoid circular dependencies
28
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
29
+ let _useIsPro = null;
30
+ let _licenseLoadAttempted = false;
31
+ function loadLicenseModule() {
32
+ if (_licenseLoadAttempted) return;
33
+ _licenseLoadAttempted = true;
34
+ try {
35
+ const mod = require("@buoy-gg/license");
36
+ if (mod) {
37
+ _useIsPro = mod.useIsPro ?? null;
38
+ }
39
+ } catch {
40
+ // License package not available
41
+ }
42
+ }
43
+ function getUseIsPro() {
44
+ loadLicenseModule();
45
+ return _useIsPro ?? (() => false);
46
+ }
47
+
48
+ // MMKV Instance color palette - consistent colors per instance
49
+ const INSTANCE_COLORS = [macOSColors.semantic.info,
50
+ // Blue
51
+ macOSColors.semantic.success,
52
+ // Green
53
+ macOSColors.semantic.warning,
54
+ // Orange
55
+ macOSColors.semantic.debug,
56
+ // Purple
57
+ '#FF6B9D',
58
+ // Pink
59
+ '#00D9FF' // Cyan
60
+ ];
61
+
62
+ /**
63
+ * Get consistent color for an MMKV instance based on its ID
64
+ * Uses simple hash to ensure same instance always gets same color
65
+ */
66
+ function getInstanceColor(instanceId) {
67
+ const hash = instanceId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
68
+ return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
69
+ }
70
+
71
+ /** Free tier limit for storage keys */
72
+ export const FREE_TIER_KEY_LIMIT = 25;
73
+ export function GameUIStorageBrowser({
74
+ requiredStorageKeys = [],
75
+ showFilters = false,
76
+ ignoredPatterns = new Set(["@react_buoy"]),
77
+ onTogglePattern,
78
+ onAddPattern,
79
+ searchQuery = "",
80
+ storageDataRef
81
+ }) {
82
+ // Check Pro status internally
83
+ const useIsPro = getUseIsPro();
84
+ const isPro = useIsPro();
85
+ const [showUpgradeModal, setShowUpgradeModal] = useState(false);
86
+ const [activeFilter, setActiveFilter] = useState("all");
87
+ const [activeStorageType, setActiveStorageType] = useState("all");
88
+ const [selectedMMKVInstance, setSelectedMMKVInstance] = useState(null);
89
+
90
+ // Selection mode state
91
+ const [isSelectMode, setIsSelectMode] = useState(false);
92
+ const [selectedKeyIds, setSelectedKeyIds] = useState(new Set());
93
+
94
+ // Get all MMKV instances
95
+ const {
96
+ instances: mmkvInstances
97
+ } = useMMKVInstances(false);
98
+
99
+ // REMOVED: Auto-selection is now handled by the instance navbar
100
+ // Users can manually select an instance from the navbar if they want to filter by instance
101
+ // By default, selectedMMKVInstance is null, which shows ALL MMKV keys
102
+
103
+ // Use AsyncStorage hook
104
+ const {
105
+ storageKeys: asyncStorageKeys,
106
+ isLoading: isLoadingAsync,
107
+ error: asyncError,
108
+ refresh: refreshAsync
109
+ } = useAsyncStorageKeys(requiredStorageKeys);
110
+
111
+ // Memoize the instances array to prevent infinite re-renders
112
+ const mmkvInstancesForHook = useMemo(() => mmkvInstances.map(inst => ({
113
+ instance: inst.instance,
114
+ id: inst.id
115
+ })), [mmkvInstances]);
116
+
117
+ // Use MMKV hook - always fetch from ALL instances, then filter by selected instance
118
+ const {
119
+ storageKeys: allMMKVKeys,
120
+ isLoading: isLoadingMMKV,
121
+ error: mmkvError,
122
+ refresh: refreshMMKV
123
+ } = useMultiMMKVKeys(mmkvInstancesForHook, requiredStorageKeys);
124
+
125
+ // Filter MMKV keys by selected instance (if one is selected)
126
+ const mmkvStorageKeys = useMemo(() => {
127
+ if (!selectedMMKVInstance) {
128
+ return allMMKVKeys; // Show all MMKV keys
129
+ }
130
+ return allMMKVKeys.filter(k => k.instanceId === selectedMMKVInstance);
131
+ }, [allMMKVKeys, selectedMMKVInstance]);
132
+
133
+ // Merge storage keys from AsyncStorage and MMKV
134
+ const allStorageKeys = useMemo(() => {
135
+ return [...asyncStorageKeys, ...mmkvStorageKeys];
136
+ }, [asyncStorageKeys, mmkvStorageKeys]);
137
+
138
+ // Determine loading and error states
139
+ const isLoading = isLoadingAsync || isLoadingMMKV;
140
+ const error = asyncError || mmkvError;
141
+
142
+ // Combined refresh function
143
+ const refresh = useCallback(() => {
144
+ refreshAsync();
145
+ refreshMMKV();
146
+ }, [refreshAsync, refreshMMKV]);
147
+
148
+ // Generate unique identifier for a storage key
149
+ const getKeyIdentifier = useCallback(storageKey => {
150
+ return storageKey.instanceId ? `${storageKey.storageType}-${storageKey.instanceId}-${storageKey.key}` : `${storageKey.storageType}-${storageKey.key}`;
151
+ }, []);
152
+
153
+ // Toggle select mode
154
+ const handleToggleSelectMode = useCallback(() => {
155
+ setIsSelectMode(prev => {
156
+ if (prev) {
157
+ // When exiting select mode, clear selection
158
+ setSelectedKeyIds(new Set());
159
+ }
160
+ return !prev;
161
+ });
162
+ }, []);
163
+
164
+ // Handle selection change for a single key
165
+ const handleSelectionChange = useCallback((storageKey, selected) => {
166
+ const keyId = getKeyIdentifier(storageKey);
167
+ setSelectedKeyIds(prev => {
168
+ const newSet = new Set(prev);
169
+ if (selected) {
170
+ newSet.add(keyId);
171
+ } else {
172
+ newSet.delete(keyId);
173
+ }
174
+ return newSet;
175
+ });
176
+ }, [getKeyIdentifier]);
177
+
178
+ // Clear selection and exit select mode after deletion
179
+ const handleDeleteComplete = useCallback(() => {
180
+ setSelectedKeyIds(new Set());
181
+ setIsSelectMode(false);
182
+ refresh();
183
+ }, [refresh]);
184
+
185
+ // Memoized export data for copy functionality
186
+ const copyExportData = useMemo(() => {
187
+ const allKeys = allStorageKeys;
188
+
189
+ // Calculate stats
190
+ const stats = {
191
+ valid: allKeys.filter(k => k.status === 'required_present' || k.status === 'optional_present').length,
192
+ missing: allKeys.filter(k => k.status === 'required_missing').length,
193
+ issues: allKeys.filter(k => k.status === 'required_wrong_value' || k.status === 'required_wrong_type').length,
194
+ total: allKeys.length
195
+ };
196
+
197
+ // Group by storage type
198
+ const asyncKeys = allKeys.filter(k => k.storageType === 'async');
199
+ const mmkvKeys = allKeys.filter(k => k.storageType === 'mmkv');
200
+ const secureKeys = allKeys.filter(k => k.storageType === 'secure');
201
+
202
+ // Build structured export
203
+ return {
204
+ summary: {
205
+ valid: stats.valid,
206
+ missing: stats.missing,
207
+ issues: stats.issues,
208
+ total: stats.total,
209
+ timestamp: new Date().toISOString()
210
+ },
211
+ asyncStorage: asyncKeys.reduce((acc, k) => {
212
+ acc[k.key] = k.value;
213
+ return acc;
214
+ }, {}),
215
+ mmkv: mmkvKeys.reduce((acc, k) => {
216
+ const instanceId = k.instanceId || 'default';
217
+ if (!acc[instanceId]) acc[instanceId] = {};
218
+ acc[instanceId][k.key] = k.value;
219
+ return acc;
220
+ }, {}),
221
+ secure: secureKeys.reduce((acc, k) => {
222
+ acc[k.key] = k.value;
223
+ return acc;
224
+ }, {})
225
+ };
226
+ }, [allStorageKeys]);
227
+
228
+ // Update storage data ref for copy functionality
229
+ useEffect(() => {
230
+ if (storageDataRef) {
231
+ storageDataRef.current = allStorageKeys;
232
+ }
233
+ }, [allStorageKeys, storageDataRef]);
234
+
235
+ // Auto-refresh on storage events (AsyncStorage)
236
+ useEffect(() => {
237
+ let timeoutId;
238
+ const unsubscribe = addListener(event => {
239
+ clearTimeout(timeoutId);
240
+ timeoutId = setTimeout(() => {
241
+ refreshAsync(); // Auto-refresh AsyncStorage when it changes
242
+ }, 100); // 100ms debounce
243
+ });
244
+ return () => {
245
+ clearTimeout(timeoutId);
246
+ unsubscribe();
247
+ };
248
+ }, [refreshAsync]);
249
+
250
+ // Auto-refresh on MMKV storage events (only if MMKV is available)
251
+ useEffect(() => {
252
+ if (!isMMKVAvailable() || !addMMKVListener) {
253
+ return; // Skip if MMKV not available
254
+ }
255
+ let timeoutId;
256
+ const unsubscribe = addMMKVListener(event => {
257
+ // Only refresh if event is for the selected instance
258
+ if (event.instanceId === selectedMMKVInstance) {
259
+ clearTimeout(timeoutId);
260
+ timeoutId = setTimeout(() => {
261
+ refreshMMKV(); // Auto-refresh MMKV when it changes
262
+ }, 100); // 100ms debounce
263
+ }
264
+ });
265
+ return () => {
266
+ clearTimeout(timeoutId);
267
+ unsubscribe();
268
+ };
269
+ }, [refreshMMKV, selectedMMKVInstance]);
270
+
271
+ // Calculate stats from ALL keys (not filtered) - base stats for display
272
+ const stats = useMemo(() => {
273
+ const allKeys = allStorageKeys;
274
+ const appKeys = allKeys.filter(k => !isDevToolsStorageKey(k.key));
275
+ const devKeys = allKeys.filter(k => isDevToolsStorageKey(k.key));
276
+ const storageStats = {
277
+ totalCount: allKeys.length,
278
+ requiredCount: appKeys.filter(k => k.category === "required").length,
279
+ missingCount: appKeys.filter(k => k.status === "required_missing").length,
280
+ wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
281
+ wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
282
+ presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
283
+ optionalCount: appKeys.filter(k => k.category === "optional").length,
284
+ // Count keys by their actual storageType property
285
+ mmkvCount: allKeys.filter(k => k.storageType === "mmkv").length,
286
+ asyncCount: allKeys.filter(k => k.storageType === "async").length,
287
+ secureCount: allKeys.filter(k => k.storageType === "secure").length,
288
+ devToolsCount: devKeys.length
289
+ };
290
+ return storageStats;
291
+ }, [allStorageKeys]);
292
+
293
+ // Calculate stats for tab badges based on current filters
294
+ const tabStats = useMemo(() => {
295
+ // Start with all keys, excluding devtools keys
296
+ let keysForStats = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key));
297
+
298
+ // If a specific storage type is selected, filter by that type first
299
+ if (activeStorageType !== "all") {
300
+ keysForStats = keysForStats.filter(k => k.storageType === activeStorageType);
301
+ }
302
+
303
+ // Calculate status counts from filtered keys
304
+ const validCount = keysForStats.filter(k => k.status === "required_present" || k.status === "optional_present").length;
305
+ const missingCount = keysForStats.filter(k => k.status === "required_missing").length;
306
+ const issuesCount = keysForStats.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value").length;
307
+
308
+ // Calculate storage type counts based on active status filter
309
+ let asyncCount = 0;
310
+ let mmkvCount = 0;
311
+ let secureCount = 0;
312
+ let totalCount = 0;
313
+ if (activeFilter === "all") {
314
+ // Valid: only keys with valid status
315
+ asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && (k.status === "required_present" || k.status === "optional_present")).length;
316
+ mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && (k.status === "required_present" || k.status === "optional_present")).length;
317
+ secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && (k.status === "required_present" || k.status === "optional_present")).length;
318
+ totalCount = asyncCount + mmkvCount + secureCount;
319
+ } else if (activeFilter === "missing") {
320
+ // Missing: only keys with missing status
321
+ asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && k.status === "required_missing").length;
322
+ mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && k.status === "required_missing").length;
323
+ secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && k.status === "required_missing").length;
324
+ totalCount = asyncCount + mmkvCount + secureCount;
325
+ } else if (activeFilter === "issues") {
326
+ // Issues: only keys with issue status
327
+ asyncCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "async" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
328
+ mmkvCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "mmkv" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
329
+ secureCount = allStorageKeys.filter(k => !isDevToolsStorageKey(k.key) && k.storageType === "secure" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
330
+ totalCount = asyncCount + mmkvCount + secureCount;
331
+ }
332
+ return {
333
+ validCount,
334
+ missingCount,
335
+ issuesCount,
336
+ asyncCount,
337
+ mmkvCount,
338
+ secureCount,
339
+ totalCount
340
+ };
341
+ }, [allStorageKeys, activeFilter, activeStorageType]);
342
+
343
+ // Sort all keys by priority (issues first)
344
+ const sortedKeys = useMemo(() => {
345
+ // Sort by status priority: errors first, then warnings, then valid
346
+ return [...allStorageKeys].sort((a, b) => {
347
+ const priorityMap = {
348
+ required_missing: 1,
349
+ required_wrong_type: 2,
350
+ required_wrong_value: 3,
351
+ required_present: 4,
352
+ optional_present: 5
353
+ };
354
+ return (priorityMap[a.status] || 999) - (priorityMap[b.status] || 999);
355
+ });
356
+ }, [allStorageKeys]);
357
+
358
+ // Get all unique keys for filter suggestions
359
+ const allUniqueKeys = useMemo(() => {
360
+ return Array.from(new Set(allStorageKeys.map(k => k.key))).sort();
361
+ }, [allStorageKeys]);
362
+
363
+ // Filter keys based on active filter, storage type, ignored patterns, and search
364
+ const filteredKeys = useMemo(() => {
365
+ let keys = sortedKeys;
366
+
367
+ // Apply ignored pattern filter first
368
+ keys = keys.filter(k => {
369
+ // Check if key matches any ignored pattern
370
+ const shouldIgnore = Array.from(ignoredPatterns).some(pattern => k.key.includes(pattern));
371
+ return !shouldIgnore;
372
+ });
373
+
374
+ // Apply search filter
375
+ if (searchQuery) {
376
+ const query = searchQuery.toLowerCase();
377
+ keys = keys.filter(k => k.key.toLowerCase().includes(query));
378
+ }
379
+
380
+ // Apply status filter
381
+ switch (activeFilter) {
382
+ case "all":
383
+ // "All" (Valid) shows only keys without issues
384
+ keys = keys.filter(k => k.status === "required_present" || k.status === "optional_present");
385
+ break;
386
+ case "missing":
387
+ keys = keys.filter(k => k.status === "required_missing");
388
+ break;
389
+ case "issues":
390
+ keys = keys.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value");
391
+ break;
392
+ }
393
+
394
+ // Apply storage type filter
395
+ if (activeStorageType !== "all") {
396
+ keys = keys.filter(k => k.storageType === activeStorageType);
397
+ }
398
+
399
+ // NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
400
+ // No need to filter again here
401
+
402
+ return keys;
403
+ }, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance]);
404
+
405
+ // For free users, limit visible keys to FREE_TIER_KEY_LIMIT
406
+ const visibleKeys = useMemo(() => {
407
+ if (isPro) return filteredKeys;
408
+ return filteredKeys.slice(0, FREE_TIER_KEY_LIMIT);
409
+ }, [filteredKeys, isPro]);
410
+
411
+ // Calculate how many keys are locked (only those matching current filter)
412
+ const lockedKeyCount = useMemo(() => {
413
+ if (isPro) return 0;
414
+ return Math.max(0, filteredKeys.length - FREE_TIER_KEY_LIMIT);
415
+ }, [filteredKeys.length, isPro]);
416
+ const hasLockedKeys = lockedKeyCount > 0;
417
+
418
+ // Calculate stats from FILTERED keys to show actual visible counts
419
+ const filteredStats = useMemo(() => {
420
+ const appKeys = filteredKeys.filter(k => !isDevToolsStorageKey(k.key));
421
+ const storageStats = {
422
+ totalCount: filteredKeys.length,
423
+ requiredCount: appKeys.filter(k => k.category === "required").length,
424
+ missingCount: appKeys.filter(k => k.status === "required_missing").length,
425
+ wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
426
+ wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
427
+ presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
428
+ optionalCount: appKeys.filter(k => k.category === "optional").length,
429
+ mmkvCount: filteredKeys.filter(k => k.storageType === "mmkv").length,
430
+ asyncCount: filteredKeys.filter(k => k.storageType === "async").length,
431
+ secureCount: filteredKeys.filter(k => k.storageType === "secure").length,
432
+ devToolsCount: filteredKeys.filter(k => isDevToolsStorageKey(k.key)).length
433
+ };
434
+ return storageStats;
435
+ }, [filteredKeys]);
436
+
437
+ // Get selected keys as StorageKeyInfo objects (from visible keys only)
438
+ const selectedKeysInfo = useMemo(() => {
439
+ return visibleKeys.filter(key => selectedKeyIds.has(getKeyIdentifier(key)));
440
+ }, [visibleKeys, selectedKeyIds, getKeyIdentifier]);
441
+
442
+ // Select all visible keys (only unlocked keys can be selected)
443
+ const handleSelectAll = useCallback(() => {
444
+ const allIds = new Set(visibleKeys.map(getKeyIdentifier));
445
+ setSelectedKeyIds(allIds);
446
+ }, [visibleKeys, getKeyIdentifier]);
447
+
448
+ // Clear all selections
449
+ const handleClearSelection = useCallback(() => {
450
+ setSelectedKeyIds(new Set());
451
+ }, []);
452
+
453
+ // Calculate health percentage
454
+ const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : stats.totalCount > 0 ? 100 : 0;
455
+ const healthStatus = healthPercentage >= 90 ? "OPTIMAL" : healthPercentage >= 70 ? "WARNING" : "CRITICAL";
456
+ const healthColor = healthPercentage >= 90 ? gameUIColors.success : healthPercentage >= 70 ? gameUIColors.warning : gameUIColors.error;
457
+
458
+ // Loading state
459
+ if (isLoading && allStorageKeys.length === 0) {
460
+ return /*#__PURE__*/_jsxs(View, {
461
+ style: styles.emptyState,
462
+ children: [/*#__PURE__*/_jsx(Database, {
463
+ size: 48,
464
+ color: macOSColors.text.muted
465
+ }), /*#__PURE__*/_jsx(Text, {
466
+ style: styles.emptyTitle,
467
+ children: "Loading storage keys..."
468
+ })]
469
+ });
470
+ }
471
+
472
+ // Error state
473
+ if (error) {
474
+ return /*#__PURE__*/_jsxs(View, {
475
+ style: styles.emptyState,
476
+ children: [/*#__PURE__*/_jsx(Database, {
477
+ size: 48,
478
+ color: gameUIColors.error
479
+ }), /*#__PURE__*/_jsx(Text, {
480
+ style: [styles.emptyTitle, {
481
+ color: gameUIColors.error
482
+ }],
483
+ children: "Error loading storage"
484
+ }), /*#__PURE__*/_jsx(Text, {
485
+ style: styles.emptySubtitle,
486
+ children: error.message
487
+ })]
488
+ });
489
+ }
490
+ return /*#__PURE__*/_jsxs(ScrollView, {
491
+ style: styles.scrollContainer,
492
+ contentContainerStyle: styles.container,
493
+ showsVerticalScrollIndicator: false,
494
+ children: [/*#__PURE__*/_jsx(View, {
495
+ style: styles.backgroundGrid
496
+ }), /*#__PURE__*/_jsx(StorageFilterCards, {
497
+ stats: stats,
498
+ tabStats: tabStats,
499
+ healthPercentage: healthPercentage,
500
+ healthStatus: healthStatus,
501
+ healthColor: healthColor,
502
+ activeFilter: activeFilter,
503
+ onFilterChange: setActiveFilter,
504
+ activeStorageType: activeStorageType,
505
+ onStorageTypeChange: setActiveStorageType
506
+ }), activeStorageType === "mmkv" && mmkvInstances.length === 0 && /*#__PURE__*/_jsxs(View, {
507
+ style: styles.emptyMMKVState,
508
+ children: [/*#__PURE__*/_jsx(Text, {
509
+ style: styles.emptyMMKVTitle,
510
+ children: "No MMKV Instances Detected"
511
+ }), /*#__PURE__*/_jsx(Text, {
512
+ style: styles.emptyMMKVSubtitle,
513
+ children: "MMKV instances must be registered with registerMMKVInstance()"
514
+ }), /*#__PURE__*/_jsx(Text, {
515
+ style: styles.emptyMMKVCode,
516
+ children: `import { registerMMKVInstance } from '@buoy-gg/storage';\n\nconst storage = createMMKV();\nregisterMMKVInstance('mmkv.default', storage);`
517
+ })]
518
+ }), activeStorageType === "mmkv" && mmkvInstances.length > 0 && /*#__PURE__*/_jsxs(View, {
519
+ style: styles.instanceNavbar,
520
+ children: [/*#__PURE__*/_jsxs(View, {
521
+ style: styles.instanceNavbarHeader,
522
+ children: [/*#__PURE__*/_jsx(Text, {
523
+ style: styles.instanceNavbarLabel,
524
+ children: "INSTANCES"
525
+ }), !isPro && mmkvInstances.length > 1 && /*#__PURE__*/_jsx(ProBadge, {
526
+ style: {
527
+ transform: [{
528
+ scale: 0.85
529
+ }]
530
+ }
531
+ })]
532
+ }), /*#__PURE__*/_jsxs(ScrollView, {
533
+ horizontal: true,
534
+ showsHorizontalScrollIndicator: false,
535
+ contentContainerStyle: styles.instanceNavbarScroll,
536
+ children: [(isPro || mmkvInstances.length === 1) && /*#__PURE__*/_jsxs(TouchableOpacity, {
537
+ onPress: () => setSelectedMMKVInstance(null),
538
+ style: [styles.instanceNavbarButton, selectedMMKVInstance === null && styles.instanceNavbarButtonActive],
539
+ children: [/*#__PURE__*/_jsx(HardDrive, {
540
+ size: 12,
541
+ color: selectedMMKVInstance === null ? macOSColors.text.primary : macOSColors.text.secondary
542
+ }), /*#__PURE__*/_jsx(Text, {
543
+ style: [styles.instanceNavbarButtonText, selectedMMKVInstance === null && styles.instanceNavbarButtonTextActive],
544
+ children: "All"
545
+ }), /*#__PURE__*/_jsx(View, {
546
+ style: styles.instanceNavbarBadge,
547
+ children: /*#__PURE__*/_jsx(Text, {
548
+ style: styles.instanceNavbarBadgeText,
549
+ children: mmkvInstances.reduce((sum, inst) => sum + inst.keyCount, 0)
550
+ })
551
+ })]
552
+ }), mmkvInstances.map((inst, index) => {
553
+ const isLocked = !isPro && index > 0;
554
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
555
+ onPress: () => {
556
+ if (isLocked) {
557
+ setShowUpgradeModal(true);
558
+ } else {
559
+ setSelectedMMKVInstance(inst.id);
560
+ }
561
+ },
562
+ style: [styles.instanceNavbarButton, inst.id === selectedMMKVInstance && styles.instanceNavbarButtonActive, {
563
+ borderColor: isLocked ? macOSColors.border.default : getInstanceColor(inst.id) + '40',
564
+ backgroundColor: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) + '20' : macOSColors.background.card,
565
+ opacity: isLocked ? 0.5 : 1
566
+ }],
567
+ children: [/*#__PURE__*/_jsx(HardDrive, {
568
+ size: 12,
569
+ color: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) : macOSColors.text.secondary
570
+ }), /*#__PURE__*/_jsx(Text, {
571
+ style: [styles.instanceNavbarButtonText, inst.id === selectedMMKVInstance && {
572
+ color: getInstanceColor(inst.id),
573
+ fontWeight: '700'
574
+ }],
575
+ children: inst.id
576
+ }), /*#__PURE__*/_jsx(View, {
577
+ style: [styles.instanceNavbarBadge, inst.id === selectedMMKVInstance && {
578
+ backgroundColor: getInstanceColor(inst.id) + '30'
579
+ }],
580
+ children: /*#__PURE__*/_jsx(Text, {
581
+ style: [styles.instanceNavbarBadgeText, inst.id === selectedMMKVInstance && {
582
+ color: getInstanceColor(inst.id)
583
+ }],
584
+ children: inst.keyCount
585
+ })
586
+ })]
587
+ }, inst.id);
588
+ })]
589
+ })]
590
+ }), filteredKeys.length > 0 ? /*#__PURE__*/_jsxs(View, {
591
+ style: styles.keysSection,
592
+ children: [/*#__PURE__*/_jsxs(View, {
593
+ style: styles.sectionHeader,
594
+ children: [/*#__PURE__*/_jsxs(View, {
595
+ style: styles.sectionHeaderLeft,
596
+ children: [/*#__PURE__*/_jsxs(Text, {
597
+ style: styles.sectionTitle,
598
+ children: [activeFilter === "all" ? "KEYS" : activeFilter === "missing" ? "MISSING KEYS" : "ISSUES TO FIX", activeStorageType !== "all" && ` (${activeStorageType.toUpperCase()})`]
599
+ }), /*#__PURE__*/_jsx(View, {
600
+ style: styles.countBadge,
601
+ children: /*#__PURE__*/_jsx(Text, {
602
+ style: styles.countText,
603
+ children: visibleKeys.length
604
+ })
605
+ })]
606
+ }), /*#__PURE__*/_jsx(StorageActionButtons, {
607
+ copyValue: copyExportData,
608
+ mmkvInstances: mmkvInstances.map(inst => ({
609
+ id: inst.id,
610
+ instance: inst.instance
611
+ })),
612
+ activeStorageType: activeStorageType,
613
+ onClearComplete: refresh,
614
+ isSelectMode: isSelectMode,
615
+ onToggleSelectMode: handleToggleSelectMode,
616
+ selectedCount: selectedKeyIds.size
617
+ })]
618
+ }), isSelectMode && /*#__PURE__*/_jsx(SelectionActionBar, {
619
+ selectedKeys: selectedKeysInfo,
620
+ mmkvInstances: mmkvInstances.map(inst => ({
621
+ id: inst.id,
622
+ instance: inst.instance
623
+ })),
624
+ onDeleteComplete: handleDeleteComplete,
625
+ onSelectAll: handleSelectAll,
626
+ onClearSelection: handleClearSelection,
627
+ totalVisibleKeys: visibleKeys.length
628
+ }), /*#__PURE__*/_jsx(StorageKeySection, {
629
+ title: "",
630
+ count: -1,
631
+ keys: visibleKeys,
632
+ emptyMessage: "",
633
+ isSelectMode: isSelectMode,
634
+ selectedKeys: selectedKeyIds,
635
+ onSelectionChange: handleSelectionChange
636
+ }), hasLockedKeys && /*#__PURE__*/_jsxs(TouchableOpacity, {
637
+ style: styles.limitBanner,
638
+ onPress: () => setShowUpgradeModal(true),
639
+ activeOpacity: 0.8,
640
+ children: [/*#__PURE__*/_jsxs(View, {
641
+ style: styles.limitBannerContent,
642
+ children: [/*#__PURE__*/_jsxs(View, {
643
+ style: styles.limitBannerLeft,
644
+ children: [/*#__PURE__*/_jsx(Zap, {
645
+ size: 16,
646
+ color: "#20C997"
647
+ }), /*#__PURE__*/_jsx(Text, {
648
+ style: styles.limitBannerText,
649
+ children: searchQuery ? `${lockedKeyCount} matching ${lockedKeyCount === 1 ? 'key' : 'keys'} in locked storage` : `${lockedKeyCount} keys locked`
650
+ })]
651
+ }), /*#__PURE__*/_jsx(ProBadge, {})]
652
+ }), /*#__PURE__*/_jsx(Text, {
653
+ style: styles.limitBannerSubtext,
654
+ children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
655
+ })]
656
+ })]
657
+ }) : /*#__PURE__*/_jsxs(View, {
658
+ style: styles.emptyState,
659
+ children: [/*#__PURE__*/_jsx(Database, {
660
+ size: 32,
661
+ color: macOSColors.text.muted
662
+ }), /*#__PURE__*/_jsx(Text, {
663
+ style: styles.emptyTitle,
664
+ children: searchQuery ? "No results found" : activeFilter === "all" ? "No valid keys found" : activeFilter === "missing" ? "No missing keys" : "No issues found"
665
+ }), /*#__PURE__*/_jsx(Text, {
666
+ style: styles.emptySubtitle,
667
+ children: searchQuery ? `No keys matching "${searchQuery}"` : activeFilter === "all" ? "All keys have issues or are missing" : activeFilter === "missing" ? "All required keys are present" : "All storage keys are correctly configured"
668
+ })]
669
+ }), /*#__PURE__*/_jsx(Text, {
670
+ style: styles.techFooter,
671
+ children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
672
+ }), /*#__PURE__*/_jsx(ProUpgradeModal, {
673
+ visible: showUpgradeModal,
674
+ onClose: () => setShowUpgradeModal(false),
675
+ featureName: "Storage Keys"
676
+ })]
677
+ });
678
+ }
679
+ const styles = StyleSheet.create({
680
+ scrollContainer: {
681
+ flex: 1,
682
+ backgroundColor: gameUIColors.background
683
+ },
684
+ container: {
685
+ padding: 12,
686
+ paddingBottom: 32
687
+ },
688
+ backgroundGrid: {
689
+ ...StyleSheet.absoluteFillObject,
690
+ opacity: 0.006,
691
+ backgroundColor: gameUIColors.info
692
+ },
693
+ techFooter: {
694
+ fontSize: 8,
695
+ color: gameUIColors.muted,
696
+ fontFamily: "monospace",
697
+ textAlign: "center",
698
+ marginTop: 20,
699
+ letterSpacing: 1,
700
+ opacity: 0.5
701
+ },
702
+ // Keys section
703
+ keysSection: {
704
+ marginTop: 8,
705
+ backgroundColor: macOSColors.background.base,
706
+ borderRadius: 12,
707
+ padding: 12,
708
+ borderWidth: 1,
709
+ borderColor: macOSColors.border.default
710
+ },
711
+ sectionHeader: {
712
+ flexDirection: "row",
713
+ justifyContent: "space-between",
714
+ alignItems: "center",
715
+ marginBottom: 10,
716
+ paddingBottom: 6,
717
+ borderBottomWidth: 1,
718
+ borderBottomColor: macOSColors.border.default
719
+ },
720
+ sectionHeaderLeft: {
721
+ flexDirection: "row",
722
+ alignItems: "center",
723
+ gap: 8
724
+ },
725
+ sectionTitle: {
726
+ fontSize: 12,
727
+ fontWeight: "600",
728
+ color: macOSColors.text.secondary,
729
+ letterSpacing: 0.4,
730
+ textTransform: "uppercase"
731
+ },
732
+ countBadge: {
733
+ backgroundColor: macOSColors.background.card,
734
+ paddingHorizontal: 8,
735
+ paddingVertical: 3,
736
+ borderRadius: 6,
737
+ borderWidth: 1,
738
+ borderColor: macOSColors.border.default
739
+ },
740
+ countText: {
741
+ fontSize: 11,
742
+ fontWeight: "600",
743
+ color: macOSColors.text.primary,
744
+ fontFamily: "monospace"
745
+ },
746
+ // Empty state
747
+ emptyState: {
748
+ alignItems: "center",
749
+ justifyContent: "center",
750
+ paddingVertical: 48
751
+ },
752
+ emptyTitle: {
753
+ fontSize: 16,
754
+ fontWeight: "600",
755
+ color: macOSColors.text.primary,
756
+ marginTop: 12,
757
+ marginBottom: 8
758
+ },
759
+ emptySubtitle: {
760
+ fontSize: 13,
761
+ color: macOSColors.text.secondary,
762
+ textAlign: "center"
763
+ },
764
+ // Empty MMKV state
765
+ emptyMMKVState: {
766
+ backgroundColor: macOSColors.background.card,
767
+ borderRadius: 12,
768
+ padding: 20,
769
+ borderWidth: 1,
770
+ borderColor: macOSColors.border.default,
771
+ marginTop: 8,
772
+ alignItems: "center"
773
+ },
774
+ emptyMMKVTitle: {
775
+ fontSize: 14,
776
+ fontWeight: "600",
777
+ color: macOSColors.text.primary,
778
+ marginBottom: 6
779
+ },
780
+ emptyMMKVSubtitle: {
781
+ fontSize: 12,
782
+ color: macOSColors.text.secondary,
783
+ textAlign: "center",
784
+ marginBottom: 12
785
+ },
786
+ emptyMMKVCode: {
787
+ fontSize: 10,
788
+ fontFamily: "monospace",
789
+ color: macOSColors.semantic.info,
790
+ backgroundColor: macOSColors.background.input,
791
+ padding: 12,
792
+ borderRadius: 8,
793
+ borderWidth: 1,
794
+ borderColor: macOSColors.border.default,
795
+ alignSelf: "stretch"
796
+ },
797
+ // Instance Navbar styles
798
+ instanceNavbar: {
799
+ marginTop: 12,
800
+ marginBottom: 8,
801
+ backgroundColor: macOSColors.background.card,
802
+ borderRadius: 10,
803
+ borderWidth: 1,
804
+ borderColor: macOSColors.border.default,
805
+ padding: 10,
806
+ shadowColor: '#000',
807
+ shadowOpacity: 0.08,
808
+ shadowRadius: 6,
809
+ shadowOffset: {
810
+ width: 0,
811
+ height: 2
812
+ }
813
+ },
814
+ instanceNavbarHeader: {
815
+ flexDirection: 'row',
816
+ alignItems: 'center',
817
+ justifyContent: 'space-between',
818
+ marginBottom: 8,
819
+ paddingLeft: 2
820
+ },
821
+ instanceNavbarLabel: {
822
+ fontSize: 9,
823
+ fontWeight: '700',
824
+ color: macOSColors.text.muted,
825
+ textTransform: 'uppercase',
826
+ letterSpacing: 1
827
+ },
828
+ instanceNavbarScroll: {
829
+ gap: 8,
830
+ paddingRight: 8
831
+ },
832
+ instanceNavbarButton: {
833
+ flexDirection: 'row',
834
+ alignItems: 'center',
835
+ gap: 6,
836
+ paddingHorizontal: 12,
837
+ paddingVertical: 8,
838
+ backgroundColor: macOSColors.background.card,
839
+ borderRadius: 8,
840
+ borderWidth: 1,
841
+ borderColor: macOSColors.border.default,
842
+ minWidth: 90
843
+ },
844
+ instanceNavbarButtonActive: {
845
+ borderWidth: 1.5,
846
+ shadowColor: '#000',
847
+ shadowOpacity: 0.1,
848
+ shadowRadius: 4,
849
+ shadowOffset: {
850
+ width: 0,
851
+ height: 1
852
+ }
853
+ },
854
+ instanceNavbarButtonText: {
855
+ fontSize: 11,
856
+ fontWeight: '600',
857
+ color: macOSColors.text.secondary,
858
+ fontFamily: 'monospace',
859
+ flex: 1
860
+ },
861
+ instanceNavbarButtonTextActive: {
862
+ fontWeight: '700',
863
+ color: macOSColors.text.primary
864
+ },
865
+ instanceNavbarBadge: {
866
+ backgroundColor: macOSColors.background.input,
867
+ paddingHorizontal: 6,
868
+ paddingVertical: 2,
869
+ borderRadius: 6,
870
+ minWidth: 24,
871
+ alignItems: 'center'
872
+ },
873
+ instanceNavbarBadgeText: {
874
+ fontSize: 10,
875
+ fontWeight: '700',
876
+ color: macOSColors.text.secondary,
877
+ fontFamily: 'monospace'
878
+ },
879
+ clearSearchButton: {
880
+ marginTop: 16,
881
+ paddingHorizontal: 16,
882
+ paddingVertical: 8,
883
+ backgroundColor: macOSColors.background.hover,
884
+ borderRadius: 6,
885
+ borderWidth: 1,
886
+ borderColor: macOSColors.border.default
887
+ },
888
+ clearSearchText: {
889
+ fontSize: 13,
890
+ color: macOSColors.text.primary,
891
+ fontWeight: "500"
892
+ },
893
+ // Free tier limit banner
894
+ limitBanner: {
895
+ backgroundColor: "#1A1A1A",
896
+ borderRadius: 10,
897
+ borderWidth: 1,
898
+ borderColor: "#20C997" + "40",
899
+ padding: 12,
900
+ marginTop: 12
901
+ },
902
+ limitBannerContent: {
903
+ flexDirection: "row",
904
+ alignItems: "center",
905
+ justifyContent: "space-between",
906
+ marginBottom: 4
907
+ },
908
+ limitBannerLeft: {
909
+ flexDirection: "row",
910
+ alignItems: "center",
911
+ gap: 8
912
+ },
913
+ limitBannerText: {
914
+ color: "#E0E0E0",
915
+ fontSize: 13,
916
+ fontWeight: "600"
917
+ },
918
+ limitBannerSubtext: {
919
+ color: "#888888",
920
+ fontSize: 11
921
+ }
922
+ });