@buoy-gg/storage 1.7.7 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/commonjs/index.js +219 -16
  2. package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  3. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +9 -23
  4. package/lib/commonjs/storage/components/SelectionActionBar.js +8 -22
  5. package/lib/commonjs/storage/components/StorageActionButtons.js +8 -22
  6. package/lib/commonjs/storage/components/StorageActions.js +8 -22
  7. package/lib/commonjs/storage/components/StorageEventActionButton.js +120 -0
  8. package/lib/commonjs/storage/components/StorageEventCard.js +112 -0
  9. package/lib/commonjs/storage/components/StorageEventDetailContent.js +331 -822
  10. package/lib/commonjs/storage/components/StorageModalWithTabs.js +43 -200
  11. package/lib/commonjs/storage/hooks/useStorageEvents.js +98 -0
  12. package/lib/commonjs/storage/index.js +111 -2
  13. package/lib/commonjs/storage/stores/storageEventStore.js +243 -0
  14. package/lib/commonjs/storage/utils/AsyncStorageListener.js +164 -35
  15. package/lib/commonjs/storage/utils/index.js +37 -0
  16. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +251 -0
  17. package/lib/module/index.js +74 -3
  18. package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  19. package/lib/module/storage/components/GameUIStorageBrowser.js +9 -23
  20. package/lib/module/storage/components/SelectionActionBar.js +9 -24
  21. package/lib/module/storage/components/StorageActionButtons.js +9 -24
  22. package/lib/module/storage/components/StorageActions.js +9 -24
  23. package/lib/module/storage/components/StorageEventActionButton.js +117 -0
  24. package/lib/module/storage/components/StorageEventCard.js +107 -0
  25. package/lib/module/storage/components/StorageEventDetailContent.js +332 -824
  26. package/lib/module/storage/components/StorageModalWithTabs.js +45 -202
  27. package/lib/module/storage/hooks/useStorageEvents.js +95 -0
  28. package/lib/module/storage/index.js +7 -1
  29. package/lib/module/storage/stores/storageEventStore.js +231 -0
  30. package/lib/module/storage/utils/AsyncStorageListener.js +159 -33
  31. package/lib/module/storage/utils/index.js +4 -1
  32. package/lib/module/storage/utils/storageTimeTravelUtils.js +245 -0
  33. package/lib/typescript/index.d.ts +36 -1
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts +1 -1
  36. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +1 -1
  37. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  38. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  39. package/lib/typescript/storage/components/StorageActionButtons.d.ts +0 -2
  40. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +1 -1
  41. package/lib/typescript/storage/components/StorageActions.d.ts.map +1 -1
  42. package/lib/typescript/storage/components/StorageEventActionButton.d.ts +37 -0
  43. package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +1 -0
  44. package/lib/typescript/storage/components/StorageEventCard.d.ts +40 -0
  45. package/lib/typescript/storage/components/StorageEventCard.d.ts.map +1 -0
  46. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +11 -3
  47. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
  48. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  49. package/lib/typescript/storage/hooks/useStorageEvents.d.ts +51 -0
  50. package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +1 -0
  51. package/lib/typescript/storage/index.d.ts +4 -0
  52. package/lib/typescript/storage/index.d.ts.map +1 -1
  53. package/lib/typescript/storage/stores/storageEventStore.d.ts +113 -0
  54. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -0
  55. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +38 -1
  56. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  57. package/lib/typescript/storage/utils/index.d.ts +2 -1
  58. package/lib/typescript/storage/utils/index.d.ts.map +1 -1
  59. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +35 -0
  60. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -0
  61. package/package.json +20 -4
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.canUndo = canUndo;
7
+ exports.jumpToState = jumpToState;
8
+ exports.undoOperation = undoOperation;
9
+ var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
10
+ var _AsyncStorageListener = require("./AsyncStorageListener");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /**
13
+ * Storage Time-Travel Utilities
14
+ *
15
+ * Provides jump and undo functionality for AsyncStorage operations.
16
+ */
17
+
18
+ /**
19
+ * Undo a single storage operation
20
+ *
21
+ * Restores the previous value(s) before the operation occurred.
22
+ * This pauses event capture to avoid creating new events during restoration.
23
+ *
24
+ * @param event - The storage event to undo
25
+ */
26
+ async function undoOperation(event) {
27
+ const {
28
+ action,
29
+ data
30
+ } = event;
31
+
32
+ // Pause capture to avoid creating events during restoration
33
+ (0, _AsyncStorageListener.pauseCapture)();
34
+ try {
35
+ switch (action) {
36
+ case "setItem":
37
+ case "mergeItem":
38
+ if (data?.key) {
39
+ if (data.prevValue === null || data.prevValue === undefined) {
40
+ // Key didn't exist before, remove it
41
+ await _asyncStorage.default.removeItem(data.key);
42
+ } else {
43
+ // Restore previous value
44
+ await _asyncStorage.default.setItem(data.key, data.prevValue);
45
+ }
46
+ }
47
+ break;
48
+ case "removeItem":
49
+ if (data?.key && data.prevValue !== null && data.prevValue !== undefined) {
50
+ // Restore the removed value
51
+ await _asyncStorage.default.setItem(data.key, data.prevValue);
52
+ }
53
+ break;
54
+ case "multiSet":
55
+ case "multiMerge":
56
+ if (data?.prevPairs && data.prevPairs.length > 0) {
57
+ // Restore all previous values
58
+ for (const [key, value] of data.prevPairs) {
59
+ if (value === null) {
60
+ await _asyncStorage.default.removeItem(key);
61
+ } else {
62
+ await _asyncStorage.default.setItem(key, value);
63
+ }
64
+ }
65
+ }
66
+ break;
67
+ case "multiRemove":
68
+ if (data?.prevPairs && data.prevPairs.length > 0) {
69
+ // Restore all removed values
70
+ for (const [key, value] of data.prevPairs) {
71
+ if (value !== null) {
72
+ await _asyncStorage.default.setItem(key, value);
73
+ }
74
+ }
75
+ }
76
+ break;
77
+ case "clear":
78
+ if (data?.prevPairs && data.prevPairs.length > 0) {
79
+ // Restore all cleared key-value pairs
80
+ const pairsToRestore = data.prevPairs.filter(([, value]) => value !== null);
81
+ if (pairsToRestore.length > 0) {
82
+ await _asyncStorage.default.multiSet(pairsToRestore);
83
+ }
84
+ }
85
+ break;
86
+ default:
87
+ throw new Error(`Cannot undo unknown action type: ${action}`);
88
+ }
89
+ } finally {
90
+ // Always resume capture, even if an error occurred
91
+ (0, _AsyncStorageListener.resumeCapture)();
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Jump to a specific point in storage history
97
+ *
98
+ * This reconstructs the storage state as it was after a specific event
99
+ * by clearing storage and replaying all events up to that point.
100
+ *
101
+ * Note: This is a destructive operation - it will clear current storage
102
+ * and replay events to reach the target state.
103
+ *
104
+ * @param events - All events in chronological order (oldest first)
105
+ * @param targetEventIndex - The index of the event to jump to (0-based)
106
+ */
107
+ async function jumpToState(events, targetEventIndex) {
108
+ if (targetEventIndex < 0 || targetEventIndex >= events.length) {
109
+ throw new Error(`Invalid event index: ${targetEventIndex}`);
110
+ }
111
+
112
+ // Pause capture to avoid creating events during replay
113
+ (0, _AsyncStorageListener.pauseCapture)();
114
+ try {
115
+ // Clear all storage first
116
+ await _asyncStorage.default.clear();
117
+
118
+ // Build up the state by applying each event up to and including the target
119
+ // We use the final values from each event, not the operations themselves
120
+ const stateMap = new Map();
121
+ for (let i = 0; i <= targetEventIndex; i++) {
122
+ const event = events[i];
123
+ applyEventToStateMap(event, stateMap);
124
+ }
125
+
126
+ // Apply the final state
127
+ const pairsToSet = [];
128
+ for (const [key, value] of stateMap) {
129
+ if (value !== null) {
130
+ pairsToSet.push([key, value]);
131
+ }
132
+ }
133
+ if (pairsToSet.length > 0) {
134
+ await _asyncStorage.default.multiSet(pairsToSet);
135
+ }
136
+ } finally {
137
+ // Always resume capture
138
+ (0, _AsyncStorageListener.resumeCapture)();
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Helper to apply an event's effects to a state map
144
+ */
145
+ function applyEventToStateMap(event, stateMap) {
146
+ const {
147
+ action,
148
+ data
149
+ } = event;
150
+ switch (action) {
151
+ case "setItem":
152
+ if (data?.key && data?.value !== undefined) {
153
+ stateMap.set(data.key, data.value);
154
+ }
155
+ break;
156
+ case "removeItem":
157
+ if (data?.key) {
158
+ stateMap.set(data.key, null);
159
+ }
160
+ break;
161
+ case "mergeItem":
162
+ if (data?.key && data?.value !== undefined) {
163
+ const existing = stateMap.get(data.key);
164
+ if (existing) {
165
+ try {
166
+ // Merge JSON objects
167
+ const existingObj = JSON.parse(existing);
168
+ const newObj = JSON.parse(data.value);
169
+ const merged = {
170
+ ...existingObj,
171
+ ...newObj
172
+ };
173
+ stateMap.set(data.key, JSON.stringify(merged));
174
+ } catch {
175
+ // If not valid JSON, just overwrite
176
+ stateMap.set(data.key, data.value);
177
+ }
178
+ } else {
179
+ stateMap.set(data.key, data.value);
180
+ }
181
+ }
182
+ break;
183
+ case "multiSet":
184
+ if (data?.pairs) {
185
+ for (const [key, value] of data.pairs) {
186
+ stateMap.set(key, value);
187
+ }
188
+ }
189
+ break;
190
+ case "multiRemove":
191
+ if (data?.keys) {
192
+ for (const key of data.keys) {
193
+ stateMap.set(key, null);
194
+ }
195
+ }
196
+ break;
197
+ case "multiMerge":
198
+ if (data?.pairs) {
199
+ for (const [key, value] of data.pairs) {
200
+ const existing = stateMap.get(key);
201
+ if (existing) {
202
+ try {
203
+ const existingObj = JSON.parse(existing);
204
+ const newObj = JSON.parse(value);
205
+ const merged = {
206
+ ...existingObj,
207
+ ...newObj
208
+ };
209
+ stateMap.set(key, JSON.stringify(merged));
210
+ } catch {
211
+ stateMap.set(key, value);
212
+ }
213
+ } else {
214
+ stateMap.set(key, value);
215
+ }
216
+ }
217
+ }
218
+ break;
219
+ case "clear":
220
+ // Clear the entire state map
221
+ stateMap.clear();
222
+ break;
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Check if an operation can be undone
228
+ *
229
+ * Some operations may not have the previous value captured.
230
+ */
231
+ function canUndo(event) {
232
+ const {
233
+ action,
234
+ data
235
+ } = event;
236
+ switch (action) {
237
+ case "setItem":
238
+ case "mergeItem":
239
+ case "removeItem":
240
+ // Can undo if we have the key
241
+ return !!data?.key;
242
+ case "multiSet":
243
+ case "multiRemove":
244
+ case "multiMerge":
245
+ case "clear":
246
+ // Can undo if we have previous pairs
247
+ return !!data?.prevPairs && data.prevPairs.length > 0;
248
+ default:
249
+ return false;
250
+ }
251
+ }
@@ -1,7 +1,78 @@
1
1
  "use strict";
2
2
 
3
- // Export preset configuration (easiest way to add to FloatingDevTools!)
3
+ /**
4
+ * @buoy-gg/storage
5
+ *
6
+ * Storage monitoring tool for React Native DevTools.
7
+ * Supports AsyncStorage and MMKV.
8
+ *
9
+ * PUBLIC API - Only these exports are supported for external use.
10
+ * Internal listeners and capture controls are not exported to prevent
11
+ * bypassing the tool's intended usage patterns.
12
+ */
13
+
14
+ // =============================================================================
15
+ // PRESET (Primary entry point for users)
16
+ // =============================================================================
4
17
  export { storageToolPreset, createStorageTool } from "./preset";
5
18
 
6
- // Export all storage utilities and components
7
- export * from "./storage";
19
+ // =============================================================================
20
+ // COMPONENTS (For custom UI implementations)
21
+ // =============================================================================
22
+ export { StorageSection } from "./storage/components/StorageSection";
23
+ export { StorageModalWithTabs } from "./storage/components/StorageModalWithTabs";
24
+ export { StorageKeyCard } from "./storage/components/StorageKeyCard";
25
+ export { StorageKeyStatsSection } from "./storage/components/StorageKeyStats";
26
+ export { StorageKeySection } from "./storage/components/StorageKeySection";
27
+ export { StorageBrowserMode } from "./storage/components/StorageBrowserMode";
28
+ export { StorageEventsSection } from "./storage/components/StorageEventsSection";
29
+ export { StorageEventCard, getValueType } from "./storage/components/StorageEventCard";
30
+ export { StorageEventDetailContent, StorageEventDetailFooter } from "./storage/components/StorageEventDetailContent";
31
+ export { MMKVInstanceSelector } from "./storage/components/MMKVInstanceSelector";
32
+ export { MMKVInstanceInfoPanel } from "./storage/components/MMKVInstanceInfoPanel";
33
+
34
+ // =============================================================================
35
+ // HOOKS (For consuming storage data in custom components)
36
+ // =============================================================================
37
+ export { useAsyncStorageKeys } from "./storage/hooks/useAsyncStorageKeys";
38
+ export { useMMKVKeys, useMultiMMKVKeys } from "./storage/hooks/useMMKVKeys";
39
+ export { useMMKVInstances, useMMKVInstance, useMMKVInstanceExists } from "./storage/hooks/useMMKVInstances";
40
+ export { useStorageEvents } from "./storage/hooks/useStorageEvents";
41
+
42
+ // =============================================================================
43
+ // TYPES
44
+ // =============================================================================
45
+
46
+ // =============================================================================
47
+ // UTILITIES (Public helpers only)
48
+ // =============================================================================
49
+ export { clearAllAppStorage } from "./storage/utils/clearAllStorage";
50
+ export { isMMKVAvailable, getMMKVClass, getMMKVUnavailableMessage } from "./storage/utils/mmkvAvailability";
51
+ export { detectMMKVType, formatMMKVValue, isTypeMatch } from "./storage/utils/mmkvTypeDetection";
52
+ export { undoOperation, jumpToState, canUndo } from "./storage/utils/storageTimeTravelUtils";
53
+ // =============================================================================
54
+ // MMKV INSTANCE REGISTRATION (For users to register their MMKV instances)
55
+ // =============================================================================
56
+ export { registerMMKVInstance, unregisterMMKVInstance } from "./storage/utils/MMKVInstanceRegistry";
57
+
58
+ // =============================================================================
59
+ // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
60
+ // =============================================================================
61
+ /** @internal */
62
+ export { storageEventStore } from "./storage/stores/storageEventStore";
63
+
64
+ // =============================================================================
65
+ // NOT EXPORTED (Internal capture controls)
66
+ // =============================================================================
67
+ // The following are intentionally NOT exported to prevent bypassing:
68
+ // - startStorageCapture, stopStorageCapture, pauseStorageCapture, resumeStorageCapture
69
+ // - subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents
70
+ // - isStorageCapturing
71
+ // - startListening, stopListening, addListener, removeAllListeners (AsyncStorage)
72
+ // - pauseCapture, resumeCapture, isPaused, isListening, getListenerCount
73
+ // - asyncStorageListener instance
74
+ // - addMMKVInstance, removeMMKVInstance, removeAllMMKVInstances (MMKV listener controls)
75
+ // - addMMKVListener, removeAllMMKVListeners, isMMKVInstanceMonitored
76
+ // - getMonitoredMMKVInstances, getMMKVInstanceCount, getMMKVListenerCount, isMMKVListening
77
+ // - mmkvListener instance
78
+ // - mmkvInstanceRegistry instance
@@ -50,63 +50,54 @@ export const gitClassicTheme = {
50
50
 
51
51
  /**
52
52
  * Dev Tools Default Theme
53
- * Clean dark theme using our gameUIColors
53
+ * Clean dark theme matching Buoy website brand colors
54
54
  */
55
55
  export const devToolsDefaultTheme = {
56
56
  name: "Dev Tools Default",
57
- description: "Clean dark theme with our game UI colors",
58
- // Use our gameUIColors-inspired dark theme
59
- background: "#0A0E1A",
60
- // Dark background
61
- panelBackground: "#0F1420",
62
- // Slightly lighter panel
63
- headerBackground: "#141925",
64
- // Header background
65
-
66
- // Diff colors with our cyan/yellow/red scheme
67
- addedBackground: "rgba(74, 255, 159, 0.1)",
68
- // Green-cyan for additions
69
- removedBackground: "rgba(255, 82, 82, 0.1)",
70
- // Red for removals
71
- modifiedBackground: "rgba(0, 184, 230, 0.1)",
72
- // Cyan for modifications
57
+ description: "Clean dark theme with Buoy brand colors",
58
+ // Surface colors (matching website dark theme)
59
+ background: "#121212",
60
+ panelBackground: "#1A1A1A",
61
+ headerBackground: "#1A1A1A",
62
+ // Diff colors using website's semantic colors
63
+ // Added: Primary teal (#20C997)
64
+ // Removed: Error red (#EF4444)
65
+ // Modified: Secondary purple (#9B70E0)
66
+ addedBackground: "rgba(32, 201, 151, 0.12)",
67
+ removedBackground: "rgba(239, 68, 68, 0.12)",
68
+ modifiedBackground: "rgba(155, 112, 224, 0.12)",
73
69
  unchangedBackground: "transparent",
74
70
  contextBackground: "rgba(255, 255, 255, 0.02)",
75
71
  // Text colors
76
- addedText: "#4AFF9F",
77
- // Bright green-cyan
78
- removedText: "#FF5252",
79
- // Bright red
80
- modifiedText: "#00B8E6",
81
- // Bright cyan
82
- unchangedText: "#B8BFC9",
83
- // Muted text
84
-
72
+ addedText: "#20C997",
73
+ removedText: "#EF4444",
74
+ modifiedText: "#9B70E0",
75
+ unchangedText: "#E0E0E0",
85
76
  // Word-level highlights
86
- addedWordHighlight: "rgba(74, 255, 159, 0.3)",
87
- removedWordHighlight: "rgba(255, 82, 82, 0.3)",
77
+ addedWordHighlight: "rgba(32, 201, 151, 0.3)",
78
+ removedWordHighlight: "rgba(239, 68, 68, 0.3)",
88
79
  // UI elements
89
- lineNumberBackground: "#0A0E1A",
90
- lineNumberText: "#7A8599",
91
- lineNumberBorder: "#1F2937",
80
+ lineNumberBackground: "#121212",
81
+ lineNumberText: "#A0A0A0",
82
+ lineNumberBorder: "#333333",
92
83
  // Markers
93
- markerAddedBackground: "rgba(74, 255, 159, 0.2)",
94
- markerRemovedBackground: "rgba(255, 82, 82, 0.2)",
95
- markerModifiedBackground: "rgba(0, 184, 230, 0.2)",
96
- markerText: "#7A8599",
84
+ markerAddedBackground: "rgba(32, 201, 151, 0.2)",
85
+ markerRemovedBackground: "rgba(239, 68, 68, 0.2)",
86
+ markerModifiedBackground: "rgba(155, 112, 224, 0.2)",
87
+ markerText: "#A0A0A0",
97
88
  // Borders and dividers
98
- borderColor: "#1F2937",
99
- dividerColor: "#1F2937",
89
+ borderColor: "#333333",
90
+ dividerColor: "#333333",
100
91
  // Summary bar
101
- summaryBackground: "#0F1420",
102
- summaryAddedText: "#4AFF9F",
103
- summaryRemovedText: "#FF5252",
104
- summaryModifiedText: "#00B8E6",
92
+ summaryBackground: "#1A1A1A",
93
+ summaryAddedText: "#20C997",
94
+ summaryRemovedText: "#EF4444",
95
+ summaryModifiedText: "#9B70E0",
105
96
  // Empty state
106
- emptyStateText: "#7A8599",
97
+ emptyStateText: "#888888",
107
98
  // Separator
108
- separatorBackground: "#141925",
109
- separatorText: "#7A8599"
99
+ separatorBackground: "#1A1A1A",
100
+ separatorText: "#A0A0A0"
110
101
  };
111
102
 
112
103
  /**
@@ -23,29 +23,10 @@ if (isMMKVAvailable()) {
23
23
 
24
24
  // Import shared Game UI components
25
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
- }
26
+ import { useIsPro } from "@buoy-gg/license";
47
27
 
48
28
  // MMKV Instance color palette - consistent colors per instance
29
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
30
  const INSTANCE_COLORS = [macOSColors.semantic.info,
50
31
  // Blue
51
32
  macOSColors.semantic.success,
@@ -79,10 +60,15 @@ export function GameUIStorageBrowser({
79
60
  searchQuery = "",
80
61
  storageDataRef
81
62
  }) {
82
- // Check Pro status internally
83
- const useIsPro = getUseIsPro();
84
63
  const isPro = useIsPro();
85
64
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
65
+
66
+ // Auto-close upgrade modal when user becomes Pro
67
+ useEffect(() => {
68
+ if (showUpgradeModal && isPro) {
69
+ setShowUpgradeModal(false);
70
+ }
71
+ }, [showUpgradeModal, isPro]);
86
72
  const [activeFilter, setActiveFilter] = useState("all");
87
73
  const [activeStorageType, setActiveStorageType] = useState("all");
88
74
  const [selectedMMKVInstance, setSelectedMMKVInstance] = useState(null);
@@ -1,31 +1,12 @@
1
1
  "use strict";
2
2
 
3
- import { useState } from "react";
3
+ import { useState, useEffect } from "react";
4
4
  import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
5
5
  import { Trash2, CopyButton, CheckSquare, ProUpgradeModal } from "@buoy-gg/shared-ui";
6
6
  import { macOSColors } from "@buoy-gg/shared-ui";
7
+ import { useIsPro } from "@buoy-gg/license";
7
8
  import AsyncStorage from "@react-native-async-storage/async-storage";
8
-
9
- // Lazy load the license hooks to avoid circular dependencies
10
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- let _useIsPro = null;
12
- let _licenseLoadAttempted = false;
13
- function loadLicenseModule() {
14
- if (_licenseLoadAttempted) return;
15
- _licenseLoadAttempted = true;
16
- try {
17
- const mod = require("@buoy-gg/license");
18
- if (mod) {
19
- _useIsPro = mod.useIsPro ?? null;
20
- }
21
- } catch {
22
- // License package not available
23
- }
24
- }
25
- function getUseIsPro() {
26
- loadLicenseModule();
27
- return _useIsPro ?? (() => false);
28
- }
29
10
  export function SelectionActionBar({
30
11
  selectedKeys,
31
12
  mmkvInstances,
@@ -35,10 +16,14 @@ export function SelectionActionBar({
35
16
  totalVisibleKeys
36
17
  }) {
37
18
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
38
-
39
- // Check Pro status internally
40
- const useIsPro = getUseIsPro();
41
19
  const isPro = useIsPro();
20
+
21
+ // Auto-close upgrade modal when user becomes Pro
22
+ useEffect(() => {
23
+ if (showUpgradeModal && isPro) {
24
+ setShowUpgradeModal(false);
25
+ }
26
+ }, [showUpgradeModal, isPro]);
42
27
  const selectedCount = selectedKeys.length;
43
28
  const allSelected = selectedCount === totalVisibleKeys && totalVisibleKeys > 0;
44
29
 
@@ -1,31 +1,12 @@
1
1
  "use strict";
2
2
 
3
- import { useState } from "react";
3
+ import { useState, useEffect } from "react";
4
4
  import { View, Text, StyleSheet, TouchableOpacity, Alert } from "react-native";
5
5
  import { Trash2, CopyButton, SquareDashed, X, ProUpgradeModal } from "@buoy-gg/shared-ui";
6
6
  import { macOSColors } from "@buoy-gg/shared-ui";
7
+ import { useIsPro } from "@buoy-gg/license";
7
8
  import AsyncStorage from "@react-native-async-storage/async-storage";
8
-
9
- // Lazy load the license hooks to avoid circular dependencies
10
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- let _useIsPro = null;
12
- let _licenseLoadAttempted = false;
13
- function loadLicenseModule() {
14
- if (_licenseLoadAttempted) return;
15
- _licenseLoadAttempted = true;
16
- try {
17
- const mod = require("@buoy-gg/license");
18
- if (mod) {
19
- _useIsPro = mod.useIsPro ?? null;
20
- }
21
- } catch {
22
- // License package not available
23
- }
24
- }
25
- function getUseIsPro() {
26
- loadLicenseModule();
27
- return _useIsPro ?? (() => false);
28
- }
29
10
  export function StorageActionButtons({
30
11
  copyValue,
31
12
  mmkvInstances = [],
@@ -36,10 +17,14 @@ export function StorageActionButtons({
36
17
  selectedCount = 0
37
18
  }) {
38
19
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
39
-
40
- // Check Pro status internally
41
- const useIsPro = getUseIsPro();
42
20
  const isPro = useIsPro();
21
+
22
+ // Auto-close upgrade modal when user becomes Pro
23
+ useEffect(() => {
24
+ if (showUpgradeModal && isPro) {
25
+ setShowUpgradeModal(false);
26
+ }
27
+ }, [showUpgradeModal, isPro]);
43
28
  const handleClearAsyncStorage = () => {
44
29
  // Gate clear behind Pro
45
30
  if (!isPro) {
@@ -1,30 +1,11 @@
1
1
  "use strict";
2
2
 
3
3
  import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
4
- import { useState, useCallback } from "react";
4
+ import { useState, useCallback, useEffect } from "react";
5
5
  import { clearAllStorageIncludingDevTools } from "../utils/clearAllStorage";
6
6
  import { RefreshCw, Trash2, CopyButton, ProUpgradeModal } from "@buoy-gg/shared-ui";
7
-
8
- // Lazy load the license hooks to avoid circular dependencies
7
+ import { useIsPro } from "@buoy-gg/license";
9
8
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
10
- let _useIsPro = null;
11
- let _licenseLoadAttempted = false;
12
- function loadLicenseModule() {
13
- if (_licenseLoadAttempted) return;
14
- _licenseLoadAttempted = true;
15
- try {
16
- const mod = require("@buoy-gg/license");
17
- if (mod) {
18
- _useIsPro = mod.useIsPro ?? null;
19
- }
20
- } catch {
21
- // License package not available
22
- }
23
- }
24
- function getUseIsPro() {
25
- loadLicenseModule();
26
- return _useIsPro ?? (() => false);
27
- }
28
9
  export function StorageActions({
29
10
  storageKeys,
30
11
  onClearAll,
@@ -33,10 +14,14 @@ export function StorageActions({
33
14
  }) {
34
15
  const [isRefreshing, setIsRefreshing] = useState(false);
35
16
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
36
-
37
- // Check Pro status internally
38
- const useIsPro = getUseIsPro();
39
17
  const isPro = useIsPro();
18
+
19
+ // Auto-close upgrade modal when user becomes Pro
20
+ useEffect(() => {
21
+ if (showUpgradeModal && isPro) {
22
+ setShowUpgradeModal(false);
23
+ }
24
+ }, [showUpgradeModal, isPro]);
40
25
  const handleRefresh = useCallback(async () => {
41
26
  setIsRefreshing(true);
42
27
  try {