@buoy-gg/storage 3.0.1 → 4.0.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 (71) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/index.js +7 -0
  3. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +25 -4
  4. package/lib/commonjs/storage/components/GameUIStorageStats.js +2 -2
  5. package/lib/commonjs/storage/components/SelectionActionBar.js +16 -3
  6. package/lib/commonjs/storage/components/StorageBrowserMode.js +6 -2
  7. package/lib/commonjs/storage/components/StorageEventDetailContent.js +2 -2
  8. package/lib/commonjs/storage/components/StorageKeyCard.js +5 -5
  9. package/lib/commonjs/storage/components/StorageKeyRow.js +97 -8
  10. package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
  11. package/lib/commonjs/storage/components/StorageModalWithTabs.js +47 -1
  12. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +15 -3
  13. package/lib/commonjs/storage/hooks/useMMKVKeys.js +20 -2
  14. package/lib/commonjs/storage/stores/storageEventStore.js +84 -0
  15. package/lib/commonjs/storage/sync/storageSyncAdapter.js +53 -0
  16. package/lib/commonjs/storage/utils/AsyncStorageListener.js +148 -160
  17. package/lib/commonjs/storage/utils/asyncStorageCompat.js +89 -0
  18. package/lib/commonjs/storage/utils/clearAllStorage.js +2 -1
  19. package/lib/commonjs/storage/utils/mmkvTypeDetection.js +20 -5
  20. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +3 -2
  21. package/lib/commonjs/storage/utils/valueType.js +41 -0
  22. package/lib/module/index.js +5 -0
  23. package/lib/module/storage/components/GameUIStorageBrowser.js +26 -4
  24. package/lib/module/storage/components/GameUIStorageStats.js +3 -2
  25. package/lib/module/storage/components/SelectionActionBar.js +17 -3
  26. package/lib/module/storage/components/StorageBrowserMode.js +6 -2
  27. package/lib/module/storage/components/StorageEventDetailContent.js +2 -2
  28. package/lib/module/storage/components/StorageKeyCard.js +5 -5
  29. package/lib/module/storage/components/StorageKeyRow.js +99 -10
  30. package/lib/module/storage/components/StorageKeySection.js +10 -4
  31. package/lib/module/storage/components/StorageModalWithTabs.js +47 -1
  32. package/lib/module/storage/hooks/useAsyncStorageKeys.js +16 -4
  33. package/lib/module/storage/hooks/useMMKVKeys.js +21 -3
  34. package/lib/module/storage/stores/storageEventStore.js +84 -0
  35. package/lib/module/storage/sync/storageSyncAdapter.js +48 -0
  36. package/lib/module/storage/utils/AsyncStorageListener.js +124 -135
  37. package/lib/module/storage/utils/asyncStorageCompat.js +81 -0
  38. package/lib/module/storage/utils/clearAllStorage.js +2 -1
  39. package/lib/module/storage/utils/mmkvTypeDetection.js +20 -5
  40. package/lib/module/storage/utils/storageTimeTravelUtils.js +3 -2
  41. package/lib/module/storage/utils/valueType.js +39 -0
  42. package/lib/typescript/index.d.ts +1 -0
  43. package/lib/typescript/index.d.ts.map +1 -1
  44. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +5 -1
  45. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  46. package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -1
  47. package/lib/typescript/storage/components/SelectionActionBar.d.ts +3 -1
  48. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  49. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +3 -1
  50. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
  51. package/lib/typescript/storage/components/StorageKeyRow.d.ts +7 -1
  52. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  53. package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
  54. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
  55. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  56. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
  57. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
  58. package/lib/typescript/storage/stores/storageEventStore.d.ts +8 -0
  59. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
  60. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +31 -0
  61. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -0
  62. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +20 -0
  63. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  64. package/lib/typescript/storage/utils/asyncStorageCompat.d.ts +30 -0
  65. package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +1 -0
  66. package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +1 -1
  67. package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +1 -1
  68. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
  69. package/lib/typescript/storage/utils/valueType.d.ts +13 -0
  70. package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
  71. package/package.json +6 -6
package/README.md CHANGED
@@ -570,7 +570,7 @@ Required storage keys can be validated for:
570
570
  ## Dependencies
571
571
 
572
572
  - `@buoy/shared-ui` - Common UI components and utilities
573
- - `@react-native-async-storage/async-storage` - AsyncStorage implementation (peer dependency)
573
+ - `@react-native-async-storage/async-storage` - AsyncStorage implementation (peer dependency; supports v2 and v3 — `^2.0.0 || ^3.1.0`. Avoid 3.0.x, which has an Android build issue fixed upstream in 3.1.0.)
574
574
  - `react-native-mmkv` - MMKV storage implementation (optional peer dependency)
575
575
  - React and React Native (peer dependencies)
576
576
 
@@ -153,6 +153,12 @@ Object.defineProperty(exports, "storageEventStore", {
153
153
  return _storageEventStore.storageEventStore;
154
154
  }
155
155
  });
156
+ Object.defineProperty(exports, "storageSyncAdapter", {
157
+ enumerable: true,
158
+ get: function () {
159
+ return _storageSyncAdapter.storageSyncAdapter;
160
+ }
161
+ });
156
162
  Object.defineProperty(exports, "storageToolPreset", {
157
163
  enumerable: true,
158
164
  get: function () {
@@ -234,4 +240,5 @@ var _mmkvAvailability = require("./storage/utils/mmkvAvailability");
234
240
  var _mmkvTypeDetection = require("./storage/utils/mmkvTypeDetection");
235
241
  var _storageTimeTravelUtils = require("./storage/utils/storageTimeTravelUtils");
236
242
  var _MMKVInstanceRegistry = require("./storage/utils/MMKVInstanceRegistry");
243
+ var _storageSyncAdapter = require("./storage/sync/storageSyncAdapter");
237
244
  var _storageEventStore = require("./storage/stores/storageEventStore");
@@ -63,7 +63,9 @@ function GameUIStorageBrowser({
63
63
  storageDataRef,
64
64
  eventCountByKey,
65
65
  onViewHistory,
66
- enabledStorageTypes
66
+ enabledStorageTypes,
67
+ pinnedKeys,
68
+ onTogglePin
67
69
  }) {
68
70
  const isPro = (0, _license.useIsPro)();
69
71
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
@@ -173,6 +175,15 @@ function GameUIStorageBrowser({
173
175
  refresh();
174
176
  }, [refresh]);
175
177
 
178
+ // Hide the selected keys by adding each to the ignored-pattern filter store,
179
+ // then exit selection mode. Non-destructive — keys can be unhidden from the
180
+ // filters UI.
181
+ const handleHideKeys = (0, _react.useCallback)(keys => {
182
+ keys.forEach(k => onAddPattern?.(k.key));
183
+ setSelectedKeyIds(new Set());
184
+ setIsSelectMode(false);
185
+ }, [onAddPattern]);
186
+
176
187
  // Memoized export data for copy functionality
177
188
  const copyExportData = (0, _react.useMemo)(() => {
178
189
  const allKeys = allStorageKeys;
@@ -395,8 +406,14 @@ function GameUIStorageBrowser({
395
406
  // NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
396
407
  // No need to filter again here
397
408
 
409
+ // Float pinned keys to the top (stable — preserves existing order otherwise)
410
+ if (pinnedKeys && pinnedKeys.size > 0) {
411
+ const pinned = keys.filter(k => pinnedKeys.has(k.key));
412
+ const rest = keys.filter(k => !pinnedKeys.has(k.key));
413
+ keys = [...pinned, ...rest];
414
+ }
398
415
  return keys;
399
- }, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes]);
416
+ }, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes, pinnedKeys]);
400
417
 
401
418
  // For free users, limit visible keys to FREE_TIER_KEY_LIMIT
402
419
  const visibleKeys = (0, _react.useMemo)(() => {
@@ -618,6 +635,7 @@ function GameUIStorageBrowser({
618
635
  instance: inst.instance
619
636
  })),
620
637
  onDeleteComplete: handleDeleteComplete,
638
+ onHideKeys: handleHideKeys,
621
639
  onSelectAll: handleSelectAll,
622
640
  onClearSelection: handleClearSelection,
623
641
  totalVisibleKeys: visibleKeys.length
@@ -630,7 +648,10 @@ function GameUIStorageBrowser({
630
648
  selectedKeys: selectedKeyIds,
631
649
  onSelectionChange: handleSelectionChange,
632
650
  eventCountByKey: eventCountByKey,
633
- onViewHistory: onViewHistory
651
+ onViewHistory: onViewHistory,
652
+ onHideKey: key => handleHideKeys([key]),
653
+ pinnedKeys: pinnedKeys,
654
+ onTogglePin: onTogglePin
634
655
  }), hasLockedKeys && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
635
656
  style: styles.limitBanner,
636
657
  onPress: () => setShowUpgradeModal(true),
@@ -684,7 +705,7 @@ const styles = _reactNative.StyleSheet.create({
684
705
  paddingBottom: 32
685
706
  },
686
707
  backgroundGrid: {
687
- ..._reactNative.StyleSheet.absoluteFillObject,
708
+ ..._sharedUi.absoluteFill,
688
709
  opacity: 0.006,
689
710
  backgroundColor: _sharedUi.gameUIColors.info
690
711
  },
@@ -517,7 +517,7 @@ const styles = _reactNative.StyleSheet.create({
517
517
  shadowRadius: 8
518
518
  },
519
519
  healthGridOverlay: {
520
- ..._reactNative.StyleSheet.absoluteFillObject,
520
+ ..._sharedUi.absoluteFill,
521
521
  opacity: 0.1,
522
522
  borderWidth: 1,
523
523
  borderColor: "rgba(255, 255, 255, 0.1)",
@@ -540,7 +540,7 @@ const styles = _reactNative.StyleSheet.create({
540
540
  backgroundColor: "rgba(255, 0, 0, 0.02)"
541
541
  },
542
542
  cardGlow: {
543
- ..._reactNative.StyleSheet.absoluteFillObject,
543
+ ..._sharedUi.absoluteFill,
544
544
  opacity: 0.3
545
545
  },
546
546
  cardHeader: {
@@ -8,13 +8,13 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
10
10
  var _license = require("@buoy-gg/license");
11
- var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
11
+ var _asyncStorageCompat = require("../utils/asyncStorageCompat");
12
12
  var _jsxRuntime = require("react/jsx-runtime");
13
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
13
  function SelectionActionBar({
15
14
  selectedKeys,
16
15
  mmkvInstances,
17
16
  onDeleteComplete,
17
+ onHideKeys,
18
18
  onSelectAll,
19
19
  onClearSelection,
20
20
  totalVisibleKeys
@@ -45,6 +45,12 @@ function SelectionActionBar({
45
45
  };
46
46
  };
47
47
 
48
+ // Hide selected keys (adds them to the filter store; non-destructive)
49
+ const handleHideSelected = () => {
50
+ if (selectedCount === 0) return;
51
+ onHideKeys?.(selectedKeys);
52
+ };
53
+
48
54
  // Handle delete selected keys
49
55
  const handleDeleteSelected = () => {
50
56
  if (selectedCount === 0) return;
@@ -68,7 +74,7 @@ function SelectionActionBar({
68
74
 
69
75
  // Delete AsyncStorage keys
70
76
  if (asyncKeys.length > 0) {
71
- await _asyncStorage.default.multiRemove(asyncKeys.map(k => k.key));
77
+ await (0, _asyncStorageCompat.removeMany)(asyncKeys.map(k => k.key));
72
78
  }
73
79
 
74
80
  // Delete MMKV keys
@@ -131,6 +137,13 @@ function SelectionActionBar({
131
137
  success: _sharedUi.macOSColors.semantic.success,
132
138
  error: _sharedUi.macOSColors.semantic.error
133
139
  }
140
+ }), onHideKeys && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
141
+ style: styles.actionButton,
142
+ onPress: handleHideSelected,
143
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
144
+ size: 14,
145
+ color: _sharedUi.macOSColors.semantic.warning
146
+ })
134
147
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
135
148
  style: styles.actionButton,
136
149
  onPress: handleDeleteSelected,
@@ -20,7 +20,9 @@ function StorageBrowserMode({
20
20
  storageDataRef,
21
21
  eventCountByKey,
22
22
  onViewHistory,
23
- enabledStorageTypes
23
+ enabledStorageTypes,
24
+ pinnedKeys,
25
+ onTogglePin
24
26
  }) {
25
27
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_GameUIStorageBrowser.GameUIStorageBrowser, {
26
28
  requiredStorageKeys: requiredStorageKeys,
@@ -32,6 +34,8 @@ function StorageBrowserMode({
32
34
  storageDataRef: storageDataRef,
33
35
  eventCountByKey: eventCountByKey,
34
36
  onViewHistory: onViewHistory,
35
- enabledStorageTypes: enabledStorageTypes
37
+ enabledStorageTypes: enabledStorageTypes,
38
+ pinnedKeys: pinnedKeys,
39
+ onTogglePin: onTogglePin
36
40
  });
37
41
  }
@@ -241,7 +241,7 @@ function StorageEventDetailContent({
241
241
  children: "CURRENT VALUE"
242
242
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
243
243
  style: styles.valueHeaderBadges,
244
- children: [action && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
244
+ children: [action ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
245
245
  style: [styles.actionBadge, {
246
246
  backgroundColor: `${actionColor}20`
247
247
  }],
@@ -251,7 +251,7 @@ function StorageEventDetailContent({
251
251
  }],
252
252
  children: (0, _storageActionHelpers.translateStorageAction)(action)
253
253
  })
254
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
254
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
255
255
  style: styles.typeBadge,
256
256
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
257
257
  style: styles.typeText,
@@ -150,10 +150,10 @@ function StorageKeyCard({
150
150
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
151
151
  style: styles.storageKeyText,
152
152
  children: storageKey.key
153
- }), storageKey.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
153
+ }), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
154
154
  style: styles.descriptionText,
155
155
  children: storageKey.description
156
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
156
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
157
157
  style: styles.cardHeaderMeta,
158
158
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
159
159
  style: [styles.statusBadge, {
@@ -282,13 +282,13 @@ function StorageKeyCard({
282
282
  style: styles.typeHelperText,
283
283
  children: ["Current type: ", (0, _valueType.getValueTypeLabel)(storageKey.value)]
284
284
  })]
285
- }), storageKey.lastUpdated && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
285
+ }), storageKey.lastUpdated ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
286
286
  style: styles.metaInfo,
287
287
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
288
288
  style: styles.metaLabel,
289
- children: ["Last updated123: ", storageKey.lastUpdated.toLocaleString()]
289
+ children: ["Last updated: ", storageKey.lastUpdated.toLocaleString()]
290
290
  })
291
- })]
291
+ }) : null]
292
292
  }), isExpanded && !hasValue && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
293
293
  style: styles.cardBody,
294
294
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -81,11 +81,18 @@ function StorageKeyRow({
81
81
  isSelected = false,
82
82
  onSelectionChange,
83
83
  eventCount,
84
- onViewHistory
84
+ onViewHistory,
85
+ onHideKey,
86
+ isPinned = false,
87
+ onTogglePin
85
88
  }) {
86
89
  const config = getStatusConfig(storageKey.status);
87
90
  const hasValue = storageKey.value !== undefined && storageKey.value !== null;
88
91
 
92
+ // Booleans get a dedicated colored badge instead of an inline text preview so
93
+ // their value is scannable at a glance.
94
+ const isBoolean = hasValue && typeof storageKey.value === "boolean";
95
+
89
96
  // Format primary text - show the key
90
97
  const primaryText = storageKey.key;
91
98
 
@@ -189,7 +196,7 @@ function StorageKeyRow({
189
196
  style: styles.expandedExpected,
190
197
  children: String(storageKey.expectedValue)
191
198
  })]
192
- }), storageKey.description && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
199
+ }), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
193
200
  style: styles.expandedRow,
194
201
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
195
202
  style: styles.expandedLabel,
@@ -198,7 +205,46 @@ function StorageKeyRow({
198
205
  style: styles.expandedDescription,
199
206
  children: storageKey.description
200
207
  })]
201
- })]
208
+ }) : null, onTogglePin || onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
209
+ style: styles.actionRow,
210
+ children: [onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
211
+ style: [styles.actionChip, isPinned && styles.actionChipActive],
212
+ onPress: () => onTogglePin(storageKey.key),
213
+ hitSlop: {
214
+ top: 6,
215
+ bottom: 6,
216
+ left: 6,
217
+ right: 6
218
+ },
219
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
220
+ size: 12,
221
+ color: _sharedUi.macOSColors.semantic.info
222
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
223
+ style: [styles.actionChipText, {
224
+ color: _sharedUi.macOSColors.semantic.info
225
+ }],
226
+ children: isPinned ? "Unpin" : "Pin to top"
227
+ })]
228
+ }) : null, onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
229
+ style: styles.actionChip,
230
+ onPress: () => onHideKey(storageKey),
231
+ hitSlop: {
232
+ top: 6,
233
+ bottom: 6,
234
+ left: 6,
235
+ right: 6
236
+ },
237
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
238
+ size: 12,
239
+ color: _sharedUi.macOSColors.semantic.warning
240
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
241
+ style: [styles.actionChipText, {
242
+ color: _sharedUi.macOSColors.semantic.warning
243
+ }],
244
+ children: "Hide from list"
245
+ })]
246
+ }) : null]
247
+ }) : null]
202
248
  });
203
249
 
204
250
  // Handle checkbox press in select mode
@@ -233,17 +279,28 @@ function StorageKeyRow({
233
279
  statusLabel: config.label,
234
280
  statusSublabel: config.sublabel,
235
281
  primaryText: primaryText,
236
- secondaryText: hasValue ? (0, _valueType.getValueTypeLabel)(storageKey.value) : undefined,
282
+ secondaryText: hasValue ? isBoolean ? (0, _valueType.getValueTypeLabel)(storageKey.value) : (0, _valueType.getValueTypeWithPreview)(storageKey.value) : undefined,
283
+ secondaryAccessory: isBoolean ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
284
+ size: "sm",
285
+ color: storageKey.value ? _sharedUi.macOSColors.semantic.success : _sharedUi.macOSColors.semantic.error,
286
+ children: storageKey.value ? "true" : "false"
287
+ }) : undefined,
237
288
  expandedContent: expandedContent,
238
289
  isExpanded: isExpanded,
239
290
  expandedGlowColor: config.color,
240
- customBadge: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
241
- color: getStorageTypeColor(storageKey.storageType),
242
- children: storageTypeLabel
291
+ customBadge: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
292
+ style: styles.badgeRow,
293
+ children: [isPinned && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
294
+ size: 12,
295
+ color: _sharedUi.macOSColors.semantic.info
296
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
297
+ color: getStorageTypeColor(storageKey.storageType),
298
+ children: storageTypeLabel
299
+ })]
243
300
  }),
244
301
  showChevron: !isSelectMode,
245
302
  onPress: isSelectMode ? handleCheckboxPress : onPress ? () => onPress(storageKey) : undefined
246
- }), eventCount != null && eventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
303
+ }), eventCount != null && eventCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
247
304
  style: [styles.absCountBadge, {
248
305
  backgroundColor: _sharedUi.macOSColors.semantic.warning + "22",
249
306
  borderColor: _sharedUi.macOSColors.semantic.warning + "55"
@@ -272,6 +329,11 @@ const getStorageTypeColor = storageType => {
272
329
  }
273
330
  };
274
331
  const styles = _reactNative.StyleSheet.create({
332
+ badgeRow: {
333
+ flexDirection: "row",
334
+ alignItems: "center",
335
+ gap: 6
336
+ },
275
337
  expandedContainer: {
276
338
  gap: 8
277
339
  },
@@ -345,6 +407,33 @@ const styles = _reactNative.StyleSheet.create({
345
407
  fontWeight: "700",
346
408
  fontFamily: "monospace"
347
409
  },
410
+ actionRow: {
411
+ flexDirection: "row",
412
+ alignItems: "center",
413
+ flexWrap: "wrap",
414
+ gap: 8,
415
+ marginTop: 4
416
+ },
417
+ actionChip: {
418
+ flexDirection: "row",
419
+ alignItems: "center",
420
+ gap: 6,
421
+ paddingVertical: 6,
422
+ paddingHorizontal: 10,
423
+ borderRadius: 6,
424
+ backgroundColor: _sharedUi.macOSColors.background.card,
425
+ borderWidth: 1,
426
+ borderColor: _sharedUi.macOSColors.border.default
427
+ },
428
+ actionChipActive: {
429
+ backgroundColor: _sharedUi.macOSColors.semantic.info + "15",
430
+ borderColor: _sharedUi.macOSColors.semantic.info + "44"
431
+ },
432
+ actionChipText: {
433
+ fontSize: 11,
434
+ fontWeight: "600",
435
+ fontFamily: "monospace"
436
+ },
348
437
  viewHistoryButton: {
349
438
  marginLeft: 4
350
439
  },
@@ -27,7 +27,10 @@ function StorageKeySection({
27
27
  selectedKeys = new Set(),
28
28
  onSelectionChange,
29
29
  eventCountByKey,
30
- onViewHistory
30
+ onViewHistory,
31
+ onHideKey,
32
+ pinnedKeys,
33
+ onTogglePin
31
34
  }) {
32
35
  const [expandedKey, setExpandedKey] = (0, _react.useState)(null);
33
36
  const handleKeyPress = (0, _react.useCallback)(storageKey => {
@@ -60,14 +63,14 @@ function StorageKeySection({
60
63
  if (keys.length === 0) return null;
61
64
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
62
65
  style: styles.sectionContainer,
63
- children: [title && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
66
+ children: [title ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
64
67
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
65
68
  children: title
66
69
  }), count >= 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
67
70
  count: count,
68
71
  color: headerColor
69
72
  })]
70
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
73
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
71
74
  style: styles.sectionContent,
72
75
  children: keys.map(storageKey => {
73
76
  // Create unique key by combining storage type, instance ID (if present), and key name
@@ -80,7 +83,10 @@ function StorageKeySection({
80
83
  isSelected: selectedKeys.has(uniqueKey),
81
84
  onSelectionChange: onSelectionChange,
82
85
  eventCount: eventCountByKey?.[storageKey.key],
83
- onViewHistory: onViewHistory ? () => onViewHistory(storageKey.key) : undefined
86
+ onViewHistory: onViewHistory ? () => onViewHistory(storageKey.key) : undefined,
87
+ onHideKey: onHideKey,
88
+ isPinned: pinnedKeys?.has(storageKey.key),
89
+ onTogglePin: onTogglePin
84
90
  }, uniqueKey);
85
91
  })
86
92
  })]
@@ -60,8 +60,10 @@ function StorageModalWithTabs({
60
60
  );
61
61
  const [enabledStorageTypes, setEnabledStorageTypes] = (0, _react.useState)(new Set(['async', 'mmkv', 'secure']) // All enabled by default
62
62
  );
63
+ const [pinnedKeys, setPinnedKeys] = (0, _react.useState)(new Set());
63
64
  const lastEventRef = (0, _react.useRef)(null);
64
65
  const hasLoadedFilters = (0, _react.useRef)(false);
66
+ const hasLoadedPins = (0, _react.useRef)(false);
65
67
  const hasLoadedTabState = (0, _react.useRef)(false);
66
68
  const hasLoadedMonitoringState = (0, _react.useRef)(false);
67
69
 
@@ -176,6 +178,37 @@ function StorageModalWithTabs({
176
178
  saveFilters();
177
179
  }, [ignoredPatterns]);
178
180
 
181
+ // Load persisted pinned keys on mount
182
+ (0, _react.useEffect)(() => {
183
+ if (!visible || hasLoadedPins.current) return;
184
+ const loadPins = async () => {
185
+ try {
186
+ const stored = await _asyncStorage.default.getItem(_sharedUi.devToolsStorageKeys.storage.pinnedKeys());
187
+ if (stored) {
188
+ setPinnedKeys(new Set(JSON.parse(stored)));
189
+ }
190
+ hasLoadedPins.current = true;
191
+ } catch (error) {
192
+ // Failed to load pinned keys
193
+ }
194
+ };
195
+ loadPins();
196
+ }, [visible]);
197
+
198
+ // Save pinned keys when they change
199
+ (0, _react.useEffect)(() => {
200
+ if (!hasLoadedPins.current) return; // Don't save on initial load
201
+
202
+ const savePins = async () => {
203
+ try {
204
+ await _asyncStorage.default.setItem(_sharedUi.devToolsStorageKeys.storage.pinnedKeys(), JSON.stringify(Array.from(pinnedKeys)));
205
+ } catch (error) {
206
+ // Failed to save pinned keys
207
+ }
208
+ };
209
+ savePins();
210
+ }, [pinnedKeys]);
211
+
179
212
  // Event listener setup - now handled by useStorageEvents hook
180
213
  // The hook subscribes to the centralized storageEventStore
181
214
 
@@ -210,6 +243,17 @@ function StorageModalWithTabs({
210
243
  const handleAddPattern = (0, _react.useCallback)(pattern => {
211
244
  setIgnoredPatterns(prev => new Set([...prev, pattern]));
212
245
  }, []);
246
+ const handleTogglePin = (0, _react.useCallback)(key => {
247
+ setPinnedKeys(prev => {
248
+ const next = new Set(prev);
249
+ if (next.has(key)) {
250
+ next.delete(key);
251
+ } else {
252
+ next.add(key);
253
+ }
254
+ return next;
255
+ });
256
+ }, []);
213
257
  const handleToggleFilters = (0, _react.useCallback)(() => {
214
258
  setShowFilters(!showFilters);
215
259
  }, [showFilters]);
@@ -508,7 +552,9 @@ function StorageModalWithTabs({
508
552
  storageDataRef: storageDataRef,
509
553
  eventCountByKey: eventCountByKey,
510
554
  onViewHistory: handleViewHistoryFromBrowser,
511
- enabledStorageTypes: enabledStorageTypes
555
+ enabledStorageTypes: enabledStorageTypes,
556
+ pinnedKeys: pinnedKeys,
557
+ onTogglePin: handleTogglePin
512
558
  });
513
559
  }
514
560
 
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.useAsyncStorageKeys = useAsyncStorageKeys;
7
7
  var _react = require("react");
8
8
  var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
9
+ var _asyncStorageCompat = require("../utils/asyncStorageCompat");
9
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
11
  function useAsyncStorageKeys(requiredStorageKeys = []) {
11
12
  // State management
@@ -13,8 +14,18 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
13
14
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
14
15
  const [error, setError] = (0, _react.useState)(null);
15
16
 
17
+ // Callers routinely pass a fresh array literal (or rely on the `= []`
18
+ // default, which is a new array every render). Depending on its identity
19
+ // would recreate `fetchStorageData` each render, re-fire the effect, and
20
+ // loop AsyncStorage.getAllKeys/multiGet forever. Depend on a stable content
21
+ // signature instead, and read the latest array via a ref inside the fetch.
22
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
23
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
24
+ requiredRef.current = requiredStorageKeys;
25
+
16
26
  // Fetch all keys and values from AsyncStorage
17
27
  const fetchStorageData = (0, _react.useCallback)(async () => {
28
+ const requiredStorageKeys = requiredRef.current;
18
29
  setIsLoading(true);
19
30
  setError(null);
20
31
  try {
@@ -26,8 +37,8 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
26
37
  return;
27
38
  }
28
39
 
29
- // 2. Get all values using multiGet
30
- const allKeyValuePairs = await _asyncStorage.default.multiGet(allKeys);
40
+ // 2. Get all values (compat helper normalizes v2 multiGet / v3 getMany)
41
+ const allKeyValuePairs = await (0, _asyncStorageCompat.readMany)(allKeys);
31
42
 
32
43
  // 3. Process keys into StorageKeyInfo format
33
44
  const allStorageKeys = [];
@@ -111,7 +122,8 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
111
122
  } finally {
112
123
  setIsLoading(false);
113
124
  }
114
- }, [requiredStorageKeys]);
125
+ // eslint-disable-next-line react-hooks/exhaustive-deps
126
+ }, [requiredSignature]);
115
127
 
116
128
  // Initial fetch
117
129
  (0, _react.useEffect)(() => {
@@ -76,8 +76,16 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
76
76
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
77
77
  const [error, setError] = (0, _react.useState)(null);
78
78
 
79
+ // Depend on a stable content signature rather than the array identity — a
80
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
81
+ // fetch effect. Read the latest array via ref inside the fetch.
82
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
83
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
84
+ requiredRef.current = requiredStorageKeys;
85
+
79
86
  // Fetch all keys and values from MMKV instance
80
87
  const fetchStorageData = (0, _react.useCallback)(() => {
88
+ const requiredStorageKeys = requiredRef.current;
81
89
  setIsLoading(true);
82
90
  setError(null);
83
91
  try {
@@ -209,7 +217,8 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
209
217
  } finally {
210
218
  setIsLoading(false);
211
219
  }
212
- }, [instance, instanceId, requiredStorageKeys]);
220
+ // eslint-disable-next-line react-hooks/exhaustive-deps
221
+ }, [instance, instanceId, requiredSignature]);
213
222
 
214
223
  // Initial fetch
215
224
  (0, _react.useEffect)(() => {
@@ -255,7 +264,15 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
255
264
  const [storageKeys, setStorageKeys] = (0, _react.useState)([]);
256
265
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
257
266
  const [error, setError] = (0, _react.useState)(null);
267
+
268
+ // Depend on a stable content signature rather than the array identity — a
269
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
270
+ // fetch effect. Read the latest array via ref inside the fetch.
271
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
272
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
273
+ requiredRef.current = requiredStorageKeys;
258
274
  const fetchStorageData = (0, _react.useCallback)(() => {
275
+ const requiredStorageKeys = requiredRef.current;
259
276
  setIsLoading(true);
260
277
  setError(null);
261
278
  try {
@@ -349,7 +366,8 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
349
366
  } finally {
350
367
  setIsLoading(false);
351
368
  }
352
- }, [instances, requiredStorageKeys]);
369
+ // eslint-disable-next-line react-hooks/exhaustive-deps
370
+ }, [instances, requiredSignature]);
353
371
  (0, _react.useEffect)(() => {
354
372
  fetchStorageData();
355
373
  }, [fetchStorageData]);