@buoy-gg/storage 7.0.4 → 7.0.6

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 (148) hide show
  1. package/lib/commonjs/index.js +77 -1
  2. package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
  3. package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
  4. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
  5. package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
  6. package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
  7. package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
  8. package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
  9. package/lib/commonjs/storage/components/StorageActions.js +6 -6
  10. package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
  11. package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
  12. package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
  13. package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
  14. package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
  15. package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
  16. package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
  17. package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
  18. package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
  19. package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
  20. package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
  21. package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
  22. package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
  23. package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
  24. package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
  25. package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
  26. package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
  27. package/lib/commonjs/storage/index.js +29 -0
  28. package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
  29. package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
  30. package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
  31. package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
  32. package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
  33. package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
  34. package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
  35. package/lib/commonjs/storage/utils/index.js +13 -0
  36. package/lib/commonjs/storage/utils/lineDiff.js +21 -354
  37. package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
  38. package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
  39. package/lib/commonjs/storage/utils/storageOps.js +53 -0
  40. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
  41. package/lib/commonjs/storage/utils/valueType.js +24 -0
  42. package/lib/module/index.js +18 -1
  43. package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
  44. package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
  45. package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
  46. package/lib/module/storage/components/GameUIStorageStats.js +7 -7
  47. package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
  48. package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
  49. package/lib/module/storage/components/SelectionActionBar.js +2 -36
  50. package/lib/module/storage/components/StorageActions.js +7 -7
  51. package/lib/module/storage/components/StorageBrowserMode.js +16 -2
  52. package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
  53. package/lib/module/storage/components/StorageEventListener.js +14 -14
  54. package/lib/module/storage/components/StorageFilterCards.js +13 -135
  55. package/lib/module/storage/components/StorageKeyCard.js +1 -1
  56. package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
  57. package/lib/module/storage/components/StorageKeyRow.js +55 -17
  58. package/lib/module/storage/components/StorageKeySection.js +11 -5
  59. package/lib/module/storage/components/StorageKeyStats.js +11 -10
  60. package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
  61. package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
  62. package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
  63. package/lib/module/storage/components/StorageValueEditModal.js +357 -0
  64. package/lib/module/storage/hooks/useKeyEditor.js +100 -0
  65. package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
  66. package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
  67. package/lib/module/storage/hooks/useTreeDraft.js +103 -0
  68. package/lib/module/storage/index.js +3 -0
  69. package/lib/module/storage/stores/storageEventStore.js +4 -2
  70. package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
  71. package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
  72. package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
  73. package/lib/module/storage/utils/MMKVListener.js +40 -4
  74. package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
  75. package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
  76. package/lib/module/storage/utils/index.js +3 -0
  77. package/lib/module/storage/utils/lineDiff.js +7 -354
  78. package/lib/module/storage/utils/setStorageValue.js +209 -0
  79. package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
  80. package/lib/module/storage/utils/storageOps.js +46 -0
  81. package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
  82. package/lib/module/storage/utils/valueType.js +23 -0
  83. package/lib/typescript/index.d.ts +12 -1
  84. package/lib/typescript/index.d.ts.map +1 -1
  85. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
  86. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
  87. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
  88. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
  89. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
  90. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  91. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  92. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
  93. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
  94. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
  95. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
  96. package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
  97. package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
  98. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
  99. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
  100. package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
  101. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  102. package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
  103. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
  104. package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
  105. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
  106. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  107. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
  108. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
  109. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
  110. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
  111. package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
  112. package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
  113. package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
  114. package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
  115. package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
  116. package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
  117. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
  118. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
  119. package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
  120. package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
  121. package/lib/typescript/storage/index.d.ts +2 -0
  122. package/lib/typescript/storage/index.d.ts.map +1 -1
  123. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
  124. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
  125. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
  126. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  127. package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
  128. package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
  129. package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
  130. package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
  131. package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
  132. package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
  133. package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
  134. package/lib/typescript/storage/utils/index.d.ts +1 -0
  135. package/lib/typescript/storage/utils/index.d.ts.map +1 -1
  136. package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
  137. package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
  138. package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
  139. package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
  140. package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
  141. package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
  142. package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
  143. package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
  144. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
  145. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
  146. package/lib/typescript/storage/utils/valueType.d.ts +13 -0
  147. package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
  148. package/package.json +5 -5
@@ -9,6 +9,7 @@ var _react = require("react");
9
9
  var _reactNative = require("react-native");
10
10
  var _sharedUi = require("@buoy-gg/shared-ui");
11
11
  var _StorageKeySection = require("./StorageKeySection");
12
+ var _StorageKeyEditorScreen = require("./StorageKeyEditorScreen");
12
13
  var _StorageFilterCards = require("./StorageFilterCards");
13
14
  var _useAsyncStorageKeys = require("../hooks/useAsyncStorageKeys");
14
15
  var _AsyncStorageListener = require("../utils/AsyncStorageListener");
@@ -18,6 +19,8 @@ var _useMMKVInstances = require("../hooks/useMMKVInstances");
18
19
  var _StorageActionButtons = require("./StorageActionButtons");
19
20
  var _SelectionActionBar = require("./SelectionActionBar");
20
21
  var _MMKVListener = require("../utils/MMKVListener");
22
+ var _storageBrowserRefresh = require("../utils/storageBrowserRefresh");
23
+ var _setStorageValue = require("../utils/setStorageValue");
21
24
  var _license = require("@buoy-gg/license");
22
25
  var _jsxRuntime = require("react/jsx-runtime");
23
26
  // Pure JS (no react-native-mmkv dependency) — works with any registered
@@ -59,10 +62,17 @@ function GameUIStorageBrowser({
59
62
  searchQuery = "",
60
63
  storageDataRef,
61
64
  eventCountByKey,
65
+ lastWriteByKey,
62
66
  onViewHistory,
63
67
  enabledStorageTypes,
68
+ statusFilter,
69
+ onStatusFilterChange,
70
+ onStatusCountsChange,
64
71
  pinnedKeys,
65
- onTogglePin
72
+ onTogglePin,
73
+ storageTypeFilter,
74
+ onStorageTypeFilterChange,
75
+ applySafeAreaInset
66
76
  }) {
67
77
  const isPro = (0, _license.useIsPro)();
68
78
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
@@ -73,10 +83,29 @@ function GameUIStorageBrowser({
73
83
  setShowUpgradeModal(false);
74
84
  }
75
85
  }, [showUpgradeModal, isPro]);
76
- const [activeFilter, setActiveFilter] = (0, _react.useState)("all");
77
- const [activeStorageType, setActiveStorageType] = (0, _react.useState)("all");
86
+ const [internalFilter, setInternalFilter] = (0, _react.useState)("all");
87
+ // Controlled when the host supplies both halves; uncontrolled otherwise.
88
+ const isFilterControlled = statusFilter != null && onStatusFilterChange != null;
89
+ const activeFilter = isFilterControlled ? statusFilter : internalFilter;
90
+ const setActiveFilter = isFilterControlled ? onStatusFilterChange : setInternalFilter;
91
+ const [internalStorageType, setInternalStorageType] = (0, _react.useState)("all");
92
+ // Controlled when the host supplies both halves — the modal does, so the
93
+ // Events tab shows the same backend you picked here.
94
+ const isStorageTypeControlled = storageTypeFilter != null && onStorageTypeFilterChange != null;
95
+ const activeStorageType = isStorageTypeControlled ? storageTypeFilter : internalStorageType;
96
+ const setActiveStorageType = isStorageTypeControlled ? onStorageTypeFilterChange : setInternalStorageType;
78
97
  const [selectedMMKVInstance, setSelectedMMKVInstance] = (0, _react.useState)(null);
79
98
 
99
+ /**
100
+ * The key the full-screen editor is open on, if any.
101
+ *
102
+ * Held here rather than in the row because the editor covers the whole
103
+ * browser, and because the write it eventually performs is `handleSaveValue`
104
+ * — which needs the MMKV instance registry and the refresh fan-out that live
105
+ * at this level.
106
+ */
107
+ const [editingKey, setEditingKey] = (0, _react.useState)(null);
108
+
80
109
  // Selection mode state
81
110
  const [isSelectMode, setIsSelectMode] = (0, _react.useState)(false);
82
111
  const [selectedKeyIds, setSelectedKeyIds] = (0, _react.useState)(new Set());
@@ -190,6 +219,40 @@ function GameUIStorageBrowser({
190
219
  setIsSelectMode(false);
191
220
  }, [onAddPattern]);
192
221
 
222
+ /**
223
+ * Write an edited value back to the device.
224
+ *
225
+ * Pro is checked here rather than on the Edit button because the technical
226
+ * blocks (`storageEditBlockedReason`) and the licence are different kinds of
227
+ * "no": the former means this key can never be edited and the row says so
228
+ * permanently, the latter is an upsell. Throwing lets the editor keep the
229
+ * draft on screen instead of discarding what was typed.
230
+ */
231
+ const handleSaveValue = (0, _react.useCallback)(async (storageKey, raw) => {
232
+ if (!isPro) {
233
+ setShowUpgradeModal(true);
234
+ throw new Error("Editing storage values is a Pro feature.");
235
+ }
236
+ await (0, _setStorageValue.setStorageValue)(storageKey, raw, mmkvInstances);
237
+ refresh();
238
+ }, [isPro, mmkvInstances, refresh]);
239
+
240
+ /**
241
+ * Flip a boolean key straight from its card.
242
+ *
243
+ * Goes through `handleSaveValue` like every other write, so the Pro gate and
244
+ * the type preservation are the same ones the editor uses — this is only a
245
+ * shortcut past the UI, not past the rules.
246
+ */
247
+ const handleToggleKey = (0, _react.useCallback)(storageKey => {
248
+ void handleSaveValue(storageKey, JSON.stringify(!storageKey.value)).catch(e => {
249
+ _reactNative.Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
250
+ });
251
+ }, [handleSaveValue]);
252
+
253
+ /** Permanent, per-key reasons a value can't be edited (never the licence). */
254
+ const editBlockedFor = (0, _react.useCallback)(storageKey => (0, _setStorageValue.storageEditBlockedReason)(storageKey, mmkvInstances), [mmkvInstances]);
255
+
193
256
  // Memoized export data for copy functionality
194
257
  const copyExportData = (0, _react.useMemo)(() => {
195
258
  const allKeys = allStorageKeys;
@@ -255,12 +318,34 @@ function GameUIStorageBrowser({
255
318
  };
256
319
  }, [refreshAsync]);
257
320
 
321
+ // Auto-refresh when something outside this component knows the underlying
322
+ // storage changed. On device the listeners above cover it; in remote-mirror
323
+ // mode (desktop dashboard) local capture is suppressed and they never fire,
324
+ // so the dashboard signals each incoming device snapshot this way — without
325
+ // it the browse tab is frozen at whatever it read on mount.
326
+ (0, _react.useEffect)(() => {
327
+ let timeoutId;
328
+ const unsubscribe = (0, _storageBrowserRefresh.onStorageBrowserRefresh)(() => {
329
+ clearTimeout(timeoutId);
330
+ timeoutId = setTimeout(refresh, 100);
331
+ });
332
+ return () => {
333
+ clearTimeout(timeoutId);
334
+ unsubscribe();
335
+ };
336
+ }, [refresh]);
337
+
258
338
  // Auto-refresh on MMKV storage events
259
339
  (0, _react.useEffect)(() => {
260
340
  let timeoutId;
261
341
  const unsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
262
- // Only refresh if event is for the selected instance
263
- if (event.instanceId === selectedMMKVInstance) {
342
+ // Refresh when the changed instance is one we're actually showing.
343
+ // `selectedMMKVInstance` is null by default ("All instances"), so
344
+ // comparing it to event.instanceId directly was never true for the view
345
+ // every user starts on — MMKV values sat stale in the browse list until
346
+ // it remounted, even though the write was recorded in Events.
347
+ const affectsView = selectedMMKVInstance === null || event.instanceId === selectedMMKVInstance;
348
+ if (affectsView) {
264
349
  clearTimeout(timeoutId);
265
350
  timeoutId = setTimeout(() => {
266
351
  refreshMMKV(); // Auto-refresh MMKV when it changes
@@ -345,6 +430,15 @@ function GameUIStorageBrowser({
345
430
  };
346
431
  }, [allStorageKeys, activeFilter, activeStorageType]);
347
432
 
433
+ // Push the status counts to a host toolbar that renders them (desktop).
434
+ (0, _react.useEffect)(() => {
435
+ onStatusCountsChange?.({
436
+ validCount: tabStats.validCount,
437
+ missingCount: tabStats.missingCount,
438
+ issuesCount: tabStats.issuesCount
439
+ });
440
+ }, [onStatusCountsChange, tabStats.validCount, tabStats.missingCount, tabStats.issuesCount]);
441
+
348
442
  // Sort all keys by priority (issues first)
349
443
  const sortedKeys = (0, _react.useMemo)(() => {
350
444
  // Sort by status priority: errors first, then warnings, then valid
@@ -466,6 +560,11 @@ function GameUIStorageBrowser({
466
560
  setSelectedKeyIds(new Set());
467
561
  }, []);
468
562
 
563
+ /**
564
+ * Serialized size of everything in storage, for the table's footer summary.
565
+ * Keyed on `allStorageKeys` rather than the filtered set: it answers "how much
566
+ * is in here", which a filter should not change.
567
+ */
469
568
  // Calculate health percentage
470
569
  const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : stats.totalCount > 0 ? 100 : 0;
471
570
  const healthStatus = healthPercentage >= 90 ? "OPTIMAL" : healthPercentage >= 70 ? "WARNING" : "CRITICAL";
@@ -503,23 +602,15 @@ function GameUIStorageBrowser({
503
602
  })]
504
603
  });
505
604
  }
506
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
507
- style: styles.scrollContainer,
508
- contentContainerStyle: styles.container,
509
- showsVerticalScrollIndicator: false,
510
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
511
- style: styles.backgroundGrid
512
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageFilterCards.StorageFilterCards, {
513
- stats: stats,
514
- tabStats: tabStats,
515
- healthPercentage: healthPercentage,
516
- healthStatus: healthStatus,
517
- healthColor: healthColor,
518
- activeFilter: activeFilter,
519
- onFilterChange: setActiveFilter,
520
- activeStorageType: activeStorageType,
521
- onStorageTypeChange: setActiveStorageType
522
- }), activeStorageType === "mmkv" && mmkvInstances.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
605
+
606
+ /* ---------------------------------------------------------------- *
607
+ * Subtrees the card list composes. Extracted rather than inlined so Pro
608
+ * gating and MMKV
609
+ * instance selection or the empty-state copy.
610
+ * ---------------------------------------------------------------- */
611
+
612
+ const mmkvSection = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
613
+ children: [activeStorageType === "mmkv" && mmkvInstances.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
523
614
  style: styles.emptyMMKVState,
524
615
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
525
616
  style: styles.emptyMMKVTitle,
@@ -603,102 +694,159 @@ function GameUIStorageBrowser({
603
694
  }, inst.id);
604
695
  })]
605
696
  })]
606
- }), filteredKeys.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
607
- style: styles.keysSection,
697
+ })]
698
+ });
699
+ const actionButtonsNode = /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageActionButtons.StorageActionButtons, {
700
+ copyValue: copyExportData,
701
+ mmkvInstances: mmkvInstances.map(inst => ({
702
+ id: inst.id,
703
+ instance: inst.instance
704
+ })),
705
+ activeStorageType: activeStorageType,
706
+ onClearComplete: refresh,
707
+ isSelectMode: isSelectMode,
708
+ onToggleSelectMode: handleToggleSelectMode,
709
+ selectedCount: selectedKeyIds.size
710
+ });
711
+
712
+ /* Selection Action Bar - Only show in select mode with selections */
713
+ const selectionBarNode = isSelectMode ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_SelectionActionBar.SelectionActionBar, {
714
+ selectedKeys: selectedKeysInfo,
715
+ mmkvInstances: mmkvInstances.map(inst => ({
716
+ id: inst.id,
717
+ instance: inst.instance
718
+ })),
719
+ onDeleteComplete: handleDeleteComplete,
720
+ onHideKeys: handleHideKeys,
721
+ onSelectAll: handleSelectAll,
722
+ onClearSelection: handleClearSelection,
723
+ totalVisibleKeys: visibleKeys.length
724
+ }) : null;
725
+ const lockedBannerNode = hasLockedKeys ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
726
+ style: styles.limitBanner,
727
+ onPress: () => setShowUpgradeModal(true),
728
+ activeOpacity: 0.8,
729
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
730
+ style: styles.limitBannerContent,
608
731
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
609
- style: styles.sectionHeader,
610
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
611
- style: styles.sectionHeaderLeft,
612
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
613
- style: styles.sectionTitle,
614
- children: [activeFilter === "all" ? "KEYS" : activeFilter === "missing" ? "MISSING KEYS" : "ISSUES TO FIX", activeStorageType !== "all" && ` (${activeStorageType.toUpperCase()})`]
615
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
616
- style: styles.countBadge,
617
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
618
- style: styles.countText,
619
- children: visibleKeys.length
620
- })
621
- })]
622
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageActionButtons.StorageActionButtons, {
623
- copyValue: copyExportData,
624
- mmkvInstances: mmkvInstances.map(inst => ({
625
- id: inst.id,
626
- instance: inst.instance
627
- })),
628
- activeStorageType: activeStorageType,
629
- onClearComplete: refresh,
630
- isSelectMode: isSelectMode,
631
- onToggleSelectMode: handleToggleSelectMode,
632
- selectedCount: selectedKeyIds.size
633
- })]
634
- }), isSelectMode && /*#__PURE__*/(0, _jsxRuntime.jsx)(_SelectionActionBar.SelectionActionBar, {
635
- selectedKeys: selectedKeysInfo,
636
- mmkvInstances: mmkvInstances.map(inst => ({
637
- id: inst.id,
638
- instance: inst.instance
639
- })),
640
- onDeleteComplete: handleDeleteComplete,
641
- onHideKeys: handleHideKeys,
642
- onSelectAll: handleSelectAll,
643
- onClearSelection: handleClearSelection,
644
- totalVisibleKeys: visibleKeys.length
645
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageKeySection.StorageKeySection, {
646
- title: "",
647
- count: -1,
648
- keys: visibleKeys,
649
- emptyMessage: "",
650
- isSelectMode: isSelectMode,
651
- selectedKeys: selectedKeyIds,
652
- onSelectionChange: handleSelectionChange,
653
- eventCountByKey: eventCountByKey,
654
- onViewHistory: onViewHistory,
655
- onHideKey: key => handleHideKeys([key]),
656
- pinnedKeys: pinnedKeys,
657
- onTogglePin: onTogglePin
658
- }), hasLockedKeys && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
659
- style: styles.limitBanner,
660
- onPress: () => setShowUpgradeModal(true),
661
- activeOpacity: 0.8,
662
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
663
- style: styles.limitBannerContent,
664
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
665
- style: styles.limitBannerLeft,
666
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
667
- size: 16,
668
- color: "#20C997"
669
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
670
- style: styles.limitBannerText,
671
- children: searchQuery ? `${lockedKeyCount} matching ${lockedKeyCount === 1 ? 'key' : 'keys'} in locked storage` : `${lockedKeyCount} keys locked`
672
- })]
673
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
732
+ style: styles.limitBannerLeft,
733
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
734
+ size: 16,
735
+ color: _sharedUi.buoyColors.primary
674
736
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
675
- style: styles.limitBannerSubtext,
676
- children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
737
+ style: styles.limitBannerText,
738
+ children: searchQuery ? `${lockedKeyCount} matching ${lockedKeyCount === 1 ? 'key' : 'keys'} in locked storage` : `${lockedKeyCount} keys locked`
677
739
  })]
678
- })]
679
- }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
680
- style: styles.emptyState,
681
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
682
- size: 32,
683
- color: _sharedUi.macOSColors.text.muted
684
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
685
- style: styles.emptyTitle,
686
- children: searchQuery ? "No results found" : activeFilter === "all" ? "No valid keys found" : activeFilter === "missing" ? "No missing keys" : "No issues found"
687
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
688
- style: styles.emptySubtitle,
689
- 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"
690
- })]
740
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
691
741
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
692
- style: styles.techFooter,
693
- children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
694
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
695
- visible: showUpgradeModal,
696
- onClose: () => setShowUpgradeModal(false),
697
- featureName: "Storage Keys"
742
+ style: styles.limitBannerSubtext,
743
+ children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
744
+ })]
745
+ }) : null;
746
+ const emptyStateNode = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
747
+ style: styles.emptyState,
748
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
749
+ size: 32,
750
+ color: _sharedUi.macOSColors.text.muted
751
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
752
+ style: styles.emptyTitle,
753
+ children: searchQuery ? "No results found" : activeFilter === "all" ? "No valid keys found" : activeFilter === "missing" ? "No missing keys" : "No issues found"
754
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
755
+ style: styles.emptySubtitle,
756
+ 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"
698
757
  })]
699
758
  });
759
+ const upgradeModalNode = /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
760
+ visible: showUpgradeModal,
761
+ onClose: () => setShowUpgradeModal(false),
762
+ featureName: "Storage Keys"
763
+ });
764
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
765
+ style: styles.root,
766
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
767
+ style: styles.scrollContainer,
768
+ contentContainerStyle: styles.container,
769
+ showsVerticalScrollIndicator: false,
770
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
771
+ style: styles.backgroundGrid
772
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageFilterCards.StorageFilterCards, {
773
+ stats: stats,
774
+ tabStats: tabStats,
775
+ healthPercentage: healthPercentage,
776
+ healthStatus: healthStatus,
777
+ healthColor: healthColor,
778
+ activeFilter: activeFilter,
779
+ onFilterChange: setActiveFilter,
780
+ activeStorageType: activeStorageType,
781
+ onStorageTypeChange: setActiveStorageType
782
+ // Same rule the wide bar follows: when a host toolbar owns the status
783
+ // filter it also renders the counters, so ours would be a second copy.
784
+ ,
785
+ showStatus: !isFilterControlled
786
+ }), mmkvSection, filteredKeys.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
787
+ style: styles.keysSection,
788
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
789
+ style: styles.sectionHeader,
790
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
791
+ style: styles.sectionHeaderLeft,
792
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
793
+ style: styles.sectionTitle,
794
+ children: [activeFilter === "all" ? "KEYS" : activeFilter === "missing" ? "MISSING KEYS" : "ISSUES TO FIX", activeStorageType !== "all" && ` (${activeStorageType.toUpperCase()})`]
795
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
796
+ style: styles.countBadge,
797
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
798
+ style: styles.countText,
799
+ children: visibleKeys.length
800
+ })
801
+ })]
802
+ }), actionButtonsNode]
803
+ }), selectionBarNode, /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageKeySection.StorageKeySection, {
804
+ title: "",
805
+ count: -1,
806
+ keys: visibleKeys,
807
+ emptyMessage: "",
808
+ isSelectMode: isSelectMode,
809
+ selectedKeys: selectedKeyIds,
810
+ onSelectionChange: handleSelectionChange,
811
+ eventCountByKey: eventCountByKey,
812
+ onViewHistory: onViewHistory,
813
+ onHideKey: key => handleHideKeys([key]),
814
+ onEditKey: setEditingKey,
815
+ onToggleKey: handleToggleKey,
816
+ editBlockedFor: editBlockedFor,
817
+ pinnedKeys: pinnedKeys,
818
+ onTogglePin: onTogglePin
819
+ }), lockedBannerNode]
820
+ }) : emptyStateNode, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
821
+ style: styles.techFooter,
822
+ children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
823
+ }), upgradeModalNode]
824
+ }), editingKey ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
825
+ style: styles.editorOverlay,
826
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageKeyEditorScreen.StorageKeyEditorScreen, {
827
+ storageKey: editingKey,
828
+ onSave: raw => handleSaveValue(editingKey, raw),
829
+ onClose: () => setEditingKey(null),
830
+ applySafeAreaInset: applySafeAreaInset
831
+ })
832
+ }) : null]
833
+ });
700
834
  }
701
835
  const styles = _reactNative.StyleSheet.create({
836
+ /** Measures the space the columns actually get; `flex: 1` so it fills the
837
+ * parent exactly as the bare ScrollView it wraps used to. */
838
+ root: {
839
+ flex: 1,
840
+ minHeight: 0
841
+ },
842
+ /**
843
+ * No background of its own: the tree editor paints an opaque screen, while a
844
+ * scalar key shows only a modal and wants the list visible behind its scrim.
845
+ */
846
+ editorOverlay: {
847
+ ..._sharedUi.absoluteFill,
848
+ zIndex: 10
849
+ },
702
850
  scrollContainer: {
703
851
  flex: 1,
704
852
  backgroundColor: _sharedUi.gameUIColors.background
@@ -844,7 +992,7 @@ const styles = _reactNative.StyleSheet.create({
844
992
  fontSize: 9,
845
993
  fontWeight: '700',
846
994
  color: _sharedUi.macOSColors.text.muted,
847
- textTransform: 'uppercase',
995
+ textTransform: "uppercase",
848
996
  letterSpacing: 1
849
997
  },
850
998
  instanceNavbarScroll: {
@@ -914,10 +1062,10 @@ const styles = _reactNative.StyleSheet.create({
914
1062
  },
915
1063
  // Free tier limit banner
916
1064
  limitBanner: {
917
- backgroundColor: "#1A1A1A",
1065
+ backgroundColor: _sharedUi.buoyColors.card,
918
1066
  borderRadius: 10,
919
1067
  borderWidth: 1,
920
- borderColor: "#20C997" + "40",
1068
+ borderColor: _sharedUi.buoyColors.primary + "40",
921
1069
  padding: 12,
922
1070
  marginTop: 12
923
1071
  },
@@ -933,12 +1081,12 @@ const styles = _reactNative.StyleSheet.create({
933
1081
  gap: 8
934
1082
  },
935
1083
  limitBannerText: {
936
- color: "#E0E0E0",
1084
+ color: _sharedUi.buoyColors.text,
937
1085
  fontSize: 13,
938
1086
  fontWeight: "600"
939
1087
  },
940
1088
  limitBannerSubtext: {
941
- color: "#888888",
1089
+ color: _sharedUi.buoyColors.textMuted,
942
1090
  fontSize: 11
943
1091
  }
944
1092
  });
@@ -431,7 +431,7 @@ const styles = _reactNative.StyleSheet.create({
431
431
  marginBottom: 20,
432
432
  paddingBottom: 12,
433
433
  borderBottomWidth: 1,
434
- borderBottomColor: "rgba(255, 255, 255, 0.1)"
434
+ borderBottomColor: "rgba(128, 128, 128, 0.2)"
435
435
  },
436
436
  headerLeft: {
437
437
  gap: 2
@@ -502,7 +502,7 @@ const styles = _reactNative.StyleSheet.create({
502
502
  },
503
503
  healthBarBg: {
504
504
  height: 6,
505
- backgroundColor: "rgba(255, 255, 255, 0.05)",
505
+ backgroundColor: "rgba(128, 128, 128, 0.1)",
506
506
  borderRadius: 3,
507
507
  overflow: "hidden"
508
508
  },
@@ -520,7 +520,7 @@ const styles = _reactNative.StyleSheet.create({
520
520
  ..._sharedUi.absoluteFill,
521
521
  opacity: 0.1,
522
522
  borderWidth: 1,
523
- borderColor: "rgba(255, 255, 255, 0.1)",
523
+ borderColor: "rgba(128, 128, 128, 0.2)",
524
524
  borderRadius: 3
525
525
  },
526
526
  // Stats grid
@@ -637,11 +637,11 @@ const styles = _reactNative.StyleSheet.create({
637
637
  backendItem: {
638
638
  flexDirection: "row",
639
639
  alignItems: "center",
640
- backgroundColor: "rgba(255, 255, 255, 0.02)",
640
+ backgroundColor: "rgba(128, 128, 128, 0.04)",
641
641
  borderRadius: 8,
642
642
  padding: 10,
643
643
  borderWidth: 1,
644
- borderColor: "rgba(255, 255, 255, 0.05)"
644
+ borderColor: "rgba(128, 128, 128, 0.1)"
645
645
  },
646
646
  backendIcon: {
647
647
  width: 24,
@@ -684,7 +684,7 @@ const styles = _reactNative.StyleSheet.create({
684
684
  alignItems: "center",
685
685
  paddingTop: 12,
686
686
  borderTopWidth: 1,
687
- borderTopColor: "rgba(255, 255, 255, 0.1)"
687
+ borderTopColor: "rgba(128, 128, 128, 0.2)"
688
688
  },
689
689
  bottomStats: {
690
690
  flex: 1,
@@ -706,7 +706,7 @@ const styles = _reactNative.StyleSheet.create({
706
706
  bottomDivider: {
707
707
  width: 1,
708
708
  height: 20,
709
- backgroundColor: "rgba(255, 255, 255, 0.1)"
709
+ backgroundColor: "rgba(128, 128, 128, 0.2)"
710
710
  },
711
711
  // Empty state
712
712
  emptyState: {
@@ -193,7 +193,7 @@ const styles = _reactNative.StyleSheet.create({
193
193
  fontSize: 11,
194
194
  fontWeight: '600',
195
195
  color: _sharedUi.macOSColors.text.primary,
196
- textTransform: 'uppercase',
196
+ textTransform: "uppercase",
197
197
  letterSpacing: 0.5
198
198
  },
199
199
  // Info grid
@@ -412,7 +412,7 @@ const styles = _reactNative.StyleSheet.create({
412
412
  keyCountLabel: {
413
413
  fontSize: 9,
414
414
  color: _sharedUi.macOSColors.text.muted,
415
- textTransform: 'uppercase',
415
+ textTransform: "uppercase",
416
416
  letterSpacing: 0.5
417
417
  }
418
418
  });
@@ -8,8 +8,7 @@ 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 _asyncStorageCompat = require("../utils/asyncStorageCompat");
12
- var _SecureStoreRegistry = require("../utils/SecureStoreRegistry");
11
+ var _deleteStorageKeys = require("../utils/deleteStorageKeys");
13
12
  var _jsxRuntime = require("react/jsx-runtime");
14
13
  function SelectionActionBar({
15
14
  selectedKeys,
@@ -69,40 +68,7 @@ function SelectionActionBar({
69
68
  style: "destructive",
70
69
  onPress: async () => {
71
70
  try {
72
- // Group keys by storage type
73
- const asyncKeys = selectedKeys.filter(k => k.storageType === "async");
74
- const mmkvKeys = selectedKeys.filter(k => k.storageType === "mmkv");
75
- const secureKeys = selectedKeys.filter(k => k.storageType === "secure");
76
-
77
- // Delete AsyncStorage keys
78
- if (asyncKeys.length > 0) {
79
- await (0, _asyncStorageCompat.removeMany)(asyncKeys.map(k => k.key));
80
- }
81
-
82
- // Delete SecureStore keys (via the registry, which knows each
83
- // key's keychainService options)
84
- for (const k of secureKeys) {
85
- await _SecureStoreRegistry.secureStoreRegistry.deleteValue(k.key);
86
- }
87
-
88
- // Delete MMKV keys
89
- if (mmkvKeys.length > 0) {
90
- // Group by instance
91
- const byInstance = {};
92
- mmkvKeys.forEach(k => {
93
- const instanceId = k.instanceId || "default";
94
- if (!byInstance[instanceId]) byInstance[instanceId] = [];
95
- byInstance[instanceId].push(k.key);
96
- });
97
-
98
- // Delete from each instance
99
- Object.entries(byInstance).forEach(([instanceId, keys]) => {
100
- const inst = mmkvInstances.find(i => i.id === instanceId);
101
- if (inst) {
102
- keys.forEach(key => inst.instance.delete(key));
103
- }
104
- });
105
- }
71
+ await (0, _deleteStorageKeys.deleteStorageKeys)(selectedKeys, mmkvInstances);
106
72
  onDeleteComplete?.();
107
73
  } catch (error) {
108
74
  _reactNative.Alert.alert("Error", `Failed to delete keys: ${error}`);
@@ -93,7 +93,7 @@ function StorageActions({
93
93
  accessibilityLabel: "Refresh storage",
94
94
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
95
95
  size: 16,
96
- color: isRefreshing ? "#10B981" : "#9CA3AF"
96
+ color: isRefreshing ? "#10B981" : _sharedUi.buoyColors.textSecondary
97
97
  })
98
98
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton, {
99
99
  value: storageKeys,
@@ -129,11 +129,11 @@ const styles = _reactNative.StyleSheet.create({
129
129
  justifyContent: "space-between",
130
130
  paddingHorizontal: 12,
131
131
  paddingVertical: 8,
132
- backgroundColor: "rgba(255, 255, 255, 0.03)",
132
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
133
133
  borderRadius: 8,
134
134
  marginBottom: 12,
135
135
  borderWidth: 1,
136
- borderColor: "rgba(255, 255, 255, 0.08)"
136
+ borderColor: "rgba(128, 128, 128, 0.16)"
137
137
  },
138
138
  leftSection: {
139
139
  flexDirection: "row",
@@ -141,7 +141,7 @@ const styles = _reactNative.StyleSheet.create({
141
141
  gap: 8
142
142
  },
143
143
  keyCount: {
144
- color: "#9CA3AF",
144
+ color: _sharedUi.buoyColors.textSecondary,
145
145
  fontSize: 12,
146
146
  fontWeight: "500"
147
147
  },
@@ -165,9 +165,9 @@ const styles = _reactNative.StyleSheet.create({
165
165
  width: 32,
166
166
  height: 32,
167
167
  borderRadius: 6,
168
- backgroundColor: "rgba(255, 255, 255, 0.03)",
168
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
169
169
  borderWidth: 1,
170
- borderColor: "rgba(255, 255, 255, 0.08)",
170
+ borderColor: "rgba(128, 128, 128, 0.16)",
171
171
  alignItems: "center",
172
172
  justifyContent: "center"
173
173
  },