@buoy-gg/storage 7.0.40 → 7.0.42

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 (215) hide show
  1. package/README.md +11 -86
  2. package/lib/commonjs/index.js +1 -346
  3. package/lib/commonjs/preset.js +1 -80
  4. package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +1 -36
  5. package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -12
  6. package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +1 -24
  7. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +3 -1258
  8. package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -257
  9. package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -418
  10. package/lib/commonjs/storage/components/SelectionActionBar.js +1 -207
  11. package/lib/commonjs/storage/components/StorageActionButtons.js +1 -241
  12. package/lib/commonjs/storage/components/StorageBrowserMode.js +1 -61
  13. package/lib/commonjs/storage/components/StorageEventActionButton.js +1 -121
  14. package/lib/commonjs/storage/components/StorageEventCard.js +1 -117
  15. package/lib/commonjs/storage/components/StorageEventDetailContent.js +1 -494
  16. package/lib/commonjs/storage/components/StorageEventFilterView.js +1 -159
  17. package/lib/commonjs/storage/components/StorageEventsSection.js +1 -24
  18. package/lib/commonjs/storage/components/StorageFilterCards.js +1 -231
  19. package/lib/commonjs/storage/components/StorageKeyCard.js +1 -520
  20. package/lib/commonjs/storage/components/StorageKeyDetailScreen.js +1 -579
  21. package/lib/commonjs/storage/components/StorageKeyRow.js +1 -559
  22. package/lib/commonjs/storage/components/StorageKeySection.js +1 -160
  23. package/lib/commonjs/storage/components/StorageKeyStats.js +1 -344
  24. package/lib/commonjs/storage/components/StorageModalWithTabs.js +1 -954
  25. package/lib/commonjs/storage/components/StorageSaveNotice.js +1 -171
  26. package/lib/commonjs/storage/components/StorageSection.js +1 -43
  27. package/lib/commonjs/storage/components/StorageTypeFilterBar.js +1 -143
  28. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +1 -141
  29. package/lib/commonjs/storage/hooks/useMMKVInstances.js +1 -210
  30. package/lib/commonjs/storage/hooks/useMMKVKeys.js +1 -376
  31. package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +1 -159
  32. package/lib/commonjs/storage/hooks/useStorageEvents.js +1 -61
  33. package/lib/commonjs/storage/stores/storageEventStore.js +1 -312
  34. package/lib/commonjs/storage/sync/storageSyncAdapter.js +1 -478
  35. package/lib/commonjs/storage/types.js +1 -5
  36. package/lib/commonjs/storage/utils/AsyncStorageListener.js +1 -691
  37. package/lib/commonjs/storage/utils/AsyncStorageListener.web.js +1 -0
  38. package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +1 -203
  39. package/lib/commonjs/storage/utils/MMKVListener.js +1 -416
  40. package/lib/commonjs/storage/utils/SecureStoreRegistry.js +1 -200
  41. package/lib/commonjs/storage/utils/asyncStorageCompat.js +1 -89
  42. package/lib/commonjs/storage/utils/browserStorageLifecycle.js +1 -0
  43. package/lib/commonjs/storage/utils/browserStorageLifecycle.web.js +1 -0
  44. package/lib/commonjs/storage/utils/clearAllStorage.js +1 -48
  45. package/lib/commonjs/storage/utils/deleteStorageKeys.js +1 -82
  46. package/lib/commonjs/storage/utils/lineDiff.js +1 -30
  47. package/lib/commonjs/storage/utils/mmkvAvailability.js +1 -62
  48. package/lib/commonjs/storage/utils/mmkvAvailability.web.js +1 -0
  49. package/lib/commonjs/storage/utils/mmkvTypeDetection.js +1 -154
  50. package/lib/commonjs/storage/utils/requiredStorageKeyScope.js +1 -17
  51. package/lib/commonjs/storage/utils/setStorageValue.js +1 -218
  52. package/lib/commonjs/storage/utils/snapshotProvider.js +1 -382
  53. package/lib/commonjs/storage/utils/storageActionHelpers.js +1 -46
  54. package/lib/commonjs/storage/utils/storageBrowserRefresh.js +1 -49
  55. package/lib/commonjs/storage/utils/storageLabels.js +1 -0
  56. package/lib/commonjs/storage/utils/storageLabels.web.js +1 -0
  57. package/lib/commonjs/storage/utils/storageOps.js +1 -53
  58. package/lib/commonjs/storage/utils/storageQueryUtils.js +1 -35
  59. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +1 -288
  60. package/lib/commonjs/storage/utils/valueType.js +1 -83
  61. package/lib/module/index.js +1 -112
  62. package/lib/module/preset.js +1 -76
  63. package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +1 -32
  64. package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -9
  65. package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +1 -10
  66. package/lib/module/storage/components/GameUIStorageBrowser.js +3 -1256
  67. package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -253
  68. package/lib/module/storage/components/MMKVInstanceSelector.js +1 -414
  69. package/lib/module/storage/components/SelectionActionBar.js +1 -204
  70. package/lib/module/storage/components/StorageActionButtons.js +1 -237
  71. package/lib/module/storage/components/StorageBrowserMode.js +1 -57
  72. package/lib/module/storage/components/StorageEventActionButton.js +1 -118
  73. package/lib/module/storage/components/StorageEventCard.js +1 -112
  74. package/lib/module/storage/components/StorageEventDetailContent.js +1 -490
  75. package/lib/module/storage/components/StorageEventFilterView.js +1 -155
  76. package/lib/module/storage/components/StorageEventsSection.js +1 -20
  77. package/lib/module/storage/components/StorageFilterCards.js +1 -227
  78. package/lib/module/storage/components/StorageKeyCard.js +1 -517
  79. package/lib/module/storage/components/StorageKeyDetailScreen.js +1 -577
  80. package/lib/module/storage/components/StorageKeyRow.js +1 -554
  81. package/lib/module/storage/components/StorageKeySection.js +1 -155
  82. package/lib/module/storage/components/StorageKeyStats.js +1 -341
  83. package/lib/module/storage/components/StorageModalWithTabs.js +1 -949
  84. package/lib/module/storage/components/StorageSaveNotice.js +1 -168
  85. package/lib/module/storage/components/StorageSection.js +1 -40
  86. package/lib/module/storage/components/StorageTypeFilterBar.js +1 -138
  87. package/lib/module/storage/hooks/useAsyncStorageKeys.js +1 -136
  88. package/lib/module/storage/hooks/useMMKVInstances.js +1 -204
  89. package/lib/module/storage/hooks/useMMKVKeys.js +1 -371
  90. package/lib/module/storage/hooks/useSecureStoreKeys.js +1 -156
  91. package/lib/module/storage/hooks/useStorageEvents.js +1 -58
  92. package/lib/module/storage/stores/storageEventStore.js +1 -301
  93. package/lib/module/storage/sync/storageSyncAdapter.js +1 -474
  94. package/lib/module/storage/types.js +1 -3
  95. package/lib/module/storage/utils/AsyncStorageListener.js +1 -679
  96. package/lib/module/storage/utils/AsyncStorageListener.web.js +1 -0
  97. package/lib/module/storage/utils/MMKVInstanceRegistry.js +1 -197
  98. package/lib/module/storage/utils/MMKVListener.js +1 -403
  99. package/lib/module/storage/utils/SecureStoreRegistry.js +1 -194
  100. package/lib/module/storage/utils/asyncStorageCompat.js +1 -81
  101. package/lib/module/storage/utils/browserStorageLifecycle.js +1 -0
  102. package/lib/module/storage/utils/browserStorageLifecycle.web.js +1 -0
  103. package/lib/module/storage/utils/clearAllStorage.js +1 -43
  104. package/lib/module/storage/utils/deleteStorageKeys.js +1 -78
  105. package/lib/module/storage/utils/lineDiff.js +1 -12
  106. package/lib/module/storage/utils/mmkvAvailability.js +1 -56
  107. package/lib/module/storage/utils/mmkvAvailability.web.js +1 -0
  108. package/lib/module/storage/utils/mmkvTypeDetection.js +1 -148
  109. package/lib/module/storage/utils/requiredStorageKeyScope.js +1 -13
  110. package/lib/module/storage/utils/setStorageValue.js +1 -209
  111. package/lib/module/storage/utils/snapshotProvider.js +1 -380
  112. package/lib/module/storage/utils/storageActionHelpers.js +1 -42
  113. package/lib/module/storage/utils/storageBrowserRefresh.js +1 -44
  114. package/lib/module/storage/utils/storageLabels.js +1 -0
  115. package/lib/module/storage/utils/storageLabels.web.js +1 -0
  116. package/lib/module/storage/utils/storageOps.js +1 -46
  117. package/lib/module/storage/utils/storageQueryUtils.js +1 -30
  118. package/lib/module/storage/utils/storageTimeTravelUtils.js +1 -281
  119. package/lib/module/storage/utils/valueType.js +1 -76
  120. package/lib/typescript/index.d.ts +1 -0
  121. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts +0 -8
  122. package/lib/typescript/storage/components/StorageEventCard.d.ts +0 -7
  123. package/lib/typescript/storage/components/StorageKeyRow.d.ts +2 -6
  124. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +0 -15
  125. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +2 -2
  126. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +7 -13
  127. package/lib/typescript/storage/utils/AsyncStorageListener.web.d.ts +24 -0
  128. package/lib/typescript/storage/utils/browserStorageLifecycle.d.ts +3 -0
  129. package/lib/typescript/storage/utils/browserStorageLifecycle.web.d.ts +2 -0
  130. package/lib/typescript/storage/utils/mmkvAvailability.web.d.ts +4 -0
  131. package/lib/typescript/storage/utils/storageLabels.d.ts +8 -0
  132. package/lib/typescript/storage/utils/storageLabels.web.d.ts +8 -0
  133. package/lib/web/index.mjs +4 -0
  134. package/package.json +40 -14
  135. package/lib/commonjs/storage/components/DiffViewer/DiffOptionsPanel.js +0 -356
  136. package/lib/commonjs/storage/components/DiffViewer/components/DiffSummary.js +0 -122
  137. package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +0 -414
  138. package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +0 -61
  139. package/lib/commonjs/storage/components/StorageValueEditModal.js +0 -24
  140. package/lib/commonjs/storage/hooks/useKeyEditor.js +0 -31
  141. package/lib/commonjs/storage/hooks/useTreeDraft.js +0 -25
  142. package/lib/module/storage/components/DiffViewer/DiffOptionsPanel.js +0 -352
  143. package/lib/module/storage/components/DiffViewer/components/DiffSummary.js +0 -118
  144. package/lib/module/storage/components/StorageKeyEditorScreen.js +0 -410
  145. package/lib/module/storage/components/StorageScalarKeyEditor.js +0 -57
  146. package/lib/module/storage/components/StorageValueEditModal.js +0 -20
  147. package/lib/module/storage/hooks/useKeyEditor.js +0 -27
  148. package/lib/module/storage/hooks/useTreeDraft.js +0 -21
  149. package/lib/typescript/index.d.ts.map +0 -1
  150. package/lib/typescript/preset.d.ts.map +0 -1
  151. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts +0 -18
  152. package/lib/typescript/storage/components/DiffViewer/DiffOptionsPanel.d.ts.map +0 -1
  153. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +0 -1
  154. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts +0 -12
  155. package/lib/typescript/storage/components/DiffViewer/components/DiffSummary.d.ts.map +0 -1
  156. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +0 -1
  157. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +0 -1
  158. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +0 -1
  159. package/lib/typescript/storage/components/MMKVInstanceInfoPanel.d.ts.map +0 -1
  160. package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts.map +0 -1
  161. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +0 -1
  162. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +0 -1
  163. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +0 -1
  164. package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +0 -1
  165. package/lib/typescript/storage/components/StorageEventCard.d.ts.map +0 -1
  166. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +0 -1
  167. package/lib/typescript/storage/components/StorageEventFilterView.d.ts.map +0 -1
  168. package/lib/typescript/storage/components/StorageEventsSection.d.ts.map +0 -1
  169. package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +0 -1
  170. package/lib/typescript/storage/components/StorageKeyCard.d.ts.map +0 -1
  171. package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts.map +0 -1
  172. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +0 -42
  173. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +0 -1
  174. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +0 -1
  175. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +0 -1
  176. package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +0 -1
  177. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +0 -1
  178. package/lib/typescript/storage/components/StorageSaveNotice.d.ts.map +0 -1
  179. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +0 -24
  180. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +0 -1
  181. package/lib/typescript/storage/components/StorageSection.d.ts.map +0 -1
  182. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +0 -1
  183. package/lib/typescript/storage/components/StorageValueEditModal.d.ts +0 -14
  184. package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +0 -1
  185. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +0 -1
  186. package/lib/typescript/storage/hooks/useKeyEditor.d.ts +0 -27
  187. package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +0 -1
  188. package/lib/typescript/storage/hooks/useMMKVInstances.d.ts.map +0 -1
  189. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +0 -1
  190. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +0 -1
  191. package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +0 -1
  192. package/lib/typescript/storage/hooks/useTreeDraft.d.ts +0 -16
  193. package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +0 -1
  194. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +0 -1
  195. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +0 -1
  196. package/lib/typescript/storage/types.d.ts.map +0 -1
  197. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +0 -1
  198. package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +0 -1
  199. package/lib/typescript/storage/utils/MMKVListener.d.ts.map +0 -1
  200. package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +0 -1
  201. package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +0 -1
  202. package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +0 -1
  203. package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +0 -1
  204. package/lib/typescript/storage/utils/lineDiff.d.ts.map +0 -1
  205. package/lib/typescript/storage/utils/mmkvAvailability.d.ts.map +0 -1
  206. package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +0 -1
  207. package/lib/typescript/storage/utils/requiredStorageKeyScope.d.ts.map +0 -1
  208. package/lib/typescript/storage/utils/setStorageValue.d.ts.map +0 -1
  209. package/lib/typescript/storage/utils/snapshotProvider.d.ts.map +0 -1
  210. package/lib/typescript/storage/utils/storageActionHelpers.d.ts.map +0 -1
  211. package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +0 -1
  212. package/lib/typescript/storage/utils/storageOps.d.ts.map +0 -1
  213. package/lib/typescript/storage/utils/storageQueryUtils.d.ts.map +0 -1
  214. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +0 -1
  215. package/lib/typescript/storage/utils/valueType.d.ts.map +0 -1
@@ -1,1259 +1,4 @@
1
- "use strict";
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.FREE_TIER_KEY_LIMIT=void 0;exports.GameUIStorageBrowser=GameUIStorageBrowser;var _storageLabels=require("../utils/storageLabels");var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _StorageKeySection=require("./StorageKeySection");var _StorageKeyDetailScreen=require("./StorageKeyDetailScreen");var _StorageFilterCards=require("./StorageFilterCards");var _useAsyncStorageKeys=require("../hooks/useAsyncStorageKeys");var _AsyncStorageListener=require("../utils/AsyncStorageListener");var _useMMKVKeys=require("../hooks/useMMKVKeys");var _useSecureStoreKeys=require("../hooks/useSecureStoreKeys");var _useMMKVInstances=require("../hooks/useMMKVInstances");var _StorageSaveNotice=require("./StorageSaveNotice");var _StorageActionButtons=require("./StorageActionButtons");var _SelectionActionBar=require("./SelectionActionBar");var _MMKVListener=require("../utils/MMKVListener");var _storageBrowserRefresh=require("../utils/storageBrowserRefresh");var _deleteStorageKeys=require("../utils/deleteStorageKeys");var _setStorageValue=require("../utils/setStorageValue");var _license=require("@buoy-gg/license");var _jsxRuntime=require("react/jsx-runtime");const INSTANCE_COLORS=[_sharedUi.macOSColors.semantic.info,_sharedUi.macOSColors.semantic.success,_sharedUi.macOSColors.semantic.warning,_sharedUi.macOSColors.semantic.debug,"#FF6B9D","#00D9FF"];function getInstanceColor(M){const U=M.split("").reduce((K,P)=>K+P.charCodeAt(0),0);return INSTANCE_COLORS[U%INSTANCE_COLORS.length]}const FREE_TIER_KEY_LIMIT=exports.FREE_TIER_KEY_LIMIT=_sharedUi.TIER_LIMITS["storage.keys"].free;function GameUIStorageBrowser({requiredStorageKeys:M=[],showFilters:U=false,ignoredPatterns:K=new Set(["@react_buoy"]),onTogglePattern:P,onAddPattern:$,searchQuery:m="",storageDataRef:R,eventCountByKey:G,lastWriteByKey:Ze,onViewHistory:A,enabledStorageTypes:I,statusFilter:Y,onStatusFilterChange:J,onStatusCountsChange:X,pinnedKeys:S,onTogglePin:Z,storageTypeFilter:Q,onStorageTypeFilterChange:ee,applySafeAreaInset:fe,detailKey:q=null,onOpenKey:Se,onCloseDetail:te}){const p=(0,_license.useIsPro)();const B=(0,_sharedUi.useLimit)("storage.keys");const re=(0,_sharedUi.useLimit)("storage.mmkvInstances");const[D,T]=(0,_react.useState)(false);(0,_react.useEffect)(()=>{if(D&&p){T(false)}},[D,p]);const[pe,be]=(0,_react.useState)("all");const L=Y!=null&&J!=null;const y=L?Y:pe;const ve=L?J:be;const[Ce,Te]=(0,_react.useState)("all");const oe=Q!=null&&ee!=null;const d=oe?Q:Ce;const xe=oe?ee:Te;const[l,se]=(0,_react.useState)(null);const we=(0,_react.useRef)(null);const Me=(0,_react.useRef)(0);const ne=(0,_react.useRef)(new Map);const _e=(0,_react.useCallback)((e,t)=>{if(t)ne.current.set(e,t);else ne.current.delete(e)},[]);const[ae,ie]=(0,_react.useState)(0);const[F,z]=(0,_react.useState)(false);const[V,x]=(0,_react.useState)(new Set);const{instances:c}=(0,_useMMKVInstances.useMMKVInstances)(false);const{storageKeys:le,isLoading:ke,error:Ke,refresh:j}=(0,_useAsyncStorageKeys.useAsyncStorageKeys)(M);const Ie=(0,_react.useMemo)(()=>c.map(e=>({instance:e.instance,id:e.id})),[c]);const{storageKeys:O,isLoading:qe,error:Be,refresh:N}=(0,_useMMKVKeys.useMultiMMKVKeys)(Ie,M);const ce=(0,_react.useMemo)(()=>{if(!l){return O}return O.filter(e=>e.instanceId===l)},[O,l]);const{storageKeys:ue,isLoading:Ve,error:je,refresh:de}=(0,_useSecureStoreKeys.useSecureStoreKeys)(M);const a=(0,_react.useMemo)(()=>{return[...le,...ce,...ue]},[le,ce,ue]);const b=(0,_react.useMemo)(()=>{if(!q)return null;const e=(0,_StorageKeySection.storageRowId)(q);return a.find(t=>(0,_StorageKeySection.storageRowId)(t)===e)??q},[q,a]);const Ne=ke||qe||Ve;const ge=Ke||Be||je;const h=(0,_react.useCallback)(()=>{j();N();de()},[j,N,de]);const w=(0,_react.useCallback)(e=>{return e.instanceId?`${e.storageType}-${e.instanceId}-${e.key}`:`${e.storageType}-${e.key}`},[]);const Ee=(0,_react.useCallback)(()=>{z(e=>{if(e){x(new Set)}return!e})},[]);const Re=(0,_react.useCallback)((e,t)=>{const r=w(e);x(u=>{const s=new Set(u);if(t){s.add(r)}else{s.delete(r)}return s})},[w]);const Ae=(0,_react.useCallback)(()=>{x(new Set);z(false);h()},[h]);const W=(0,_react.useCallback)(e=>{e.forEach(t=>$?.(t.key));x(new Set);z(false)},[$]);const H=(0,_react.useCallback)(async(e,t)=>{if(!p){T(true);throw new Error("Editing storage values is a Pro feature.")}await(0,_setStorageValue.setStorageValue)(e,t,c);h();ie(r=>r+1)},[p,c,h]);const De=(0,_react.useCallback)(e=>{void H(e,JSON.stringify(!e.value)).catch(t=>{_reactNative.Alert.alert("Couldn't save",t instanceof Error?t.message:String(t))})},[H]);const ye=(0,_react.useCallback)((e,t)=>{if(!p){T(true);return}_reactNative.Alert.alert("Delete key",`Delete "${e.key}"? This cannot be undone.`,[{text:"Cancel",style:"cancel"},{text:"Delete",style:"destructive",onPress:async()=>{try{await(0,_deleteStorageKeys.deleteStorageKeys)([e],c.map(r=>({id:r.id,instance:r.instance})));h();t?.()}catch(r){_reactNative.Alert.alert("Error",`Failed to delete key: ${r}`)}}}])},[p,c,h]);const he=(0,_react.useCallback)(e=>(0,_setStorageValue.storageEditBlockedReason)(e,c),[c]);const Le=(0,_react.useMemo)(()=>{const e=a;const t={valid:e.filter(n=>n.status==="required_present"||n.status==="optional_present").length,missing:e.filter(n=>n.status==="required_missing").length,issues:e.filter(n=>n.status==="required_wrong_value"||n.status==="required_wrong_type").length,total:e.length};const r=e.filter(n=>n.storageType==="async");const u=e.filter(n=>n.storageType==="mmkv");const s=e.filter(n=>n.storageType==="secure");return{summary:{valid:t.valid,missing:t.missing,issues:t.issues,total:t.total,timestamp:new Date().toISOString()},asyncStorage:r.reduce((n,i)=>{n[i.key]=i.value;return n},{}),mmkv:u.reduce((n,i)=>{const f=i.instanceId||"default";if(!n[f])n[f]={};n[f][i.key]=i.value;return n},{}),secure:s.reduce((n,i)=>{n[i.key]=i.value;return n},{})}},[a]);(0,_react.useEffect)(()=>{if(R){R.current=a}},[a,R]);(0,_react.useEffect)(()=>{let e;const t=(0,_AsyncStorageListener.addListener)(r=>{clearTimeout(e);e=setTimeout(()=>{j()},100)});return()=>{clearTimeout(e);t()}},[j]);(0,_react.useEffect)(()=>{let e;const t=(0,_storageBrowserRefresh.onStorageBrowserRefresh)(()=>{clearTimeout(e);e=setTimeout(h,100)});return()=>{clearTimeout(e);t()}},[h]);(0,_react.useEffect)(()=>{let e;const t=(0,_MMKVListener.addMMKVListener)(r=>{const u=l===null||r.instanceId===l;if(u){clearTimeout(e);e=setTimeout(()=>{N()},100)}});return()=>{clearTimeout(e);t()}},[N,l]);const _=(0,_react.useMemo)(()=>{const e=a;const t=e.filter(s=>!(0,_sharedUi.isDevToolsStorageKey)(s.key));const r=e.filter(s=>(0,_sharedUi.isDevToolsStorageKey)(s.key));const u={totalCount:e.length,requiredCount:t.filter(s=>s.category==="required").length,missingCount:t.filter(s=>s.status==="required_missing").length,wrongValueCount:t.filter(s=>s.status==="required_wrong_value").length,wrongTypeCount:t.filter(s=>s.status==="required_wrong_type").length,presentRequiredCount:t.filter(s=>s.status==="required_present").length,optionalCount:t.filter(s=>s.category==="optional").length,mmkvCount:e.filter(s=>s.storageType==="mmkv").length,asyncCount:e.filter(s=>s.storageType==="async").length,secureCount:e.filter(s=>s.storageType==="secure").length,devToolsCount:r.length};return u},[a]);const v=(0,_react.useMemo)(()=>{let e=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key));if(d!=="all"){e=e.filter(o=>o.storageType===d)}const t=e.filter(o=>o.status==="required_present"||o.status==="optional_present").length;const r=e.filter(o=>o.status==="required_missing").length;const u=e.filter(o=>o.status==="required_wrong_type"||o.status==="required_wrong_value").length;let s=0;let n=0;let i=0;let f=0;if(y==="all"){s=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="async"&&(o.status==="required_present"||o.status==="optional_present")).length;n=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="mmkv"&&(o.status==="required_present"||o.status==="optional_present")).length;i=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="secure"&&(o.status==="required_present"||o.status==="optional_present")).length;f=s+n+i}else if(y==="missing"){s=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="async"&&o.status==="required_missing").length;n=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="mmkv"&&o.status==="required_missing").length;i=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="secure"&&o.status==="required_missing").length;f=s+n+i}else if(y==="issues"){s=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="async"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;n=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="mmkv"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;i=a.filter(o=>!(0,_sharedUi.isDevToolsStorageKey)(o.key)&&o.storageType==="secure"&&(o.status==="required_wrong_type"||o.status==="required_wrong_value")).length;f=s+n+i}return{validCount:t,missingCount:r,issuesCount:u,asyncCount:s,mmkvCount:n,secureCount:i,totalCount:f}},[a,y,d]);(0,_react.useEffect)(()=>{X?.({validCount:v.validCount,missingCount:v.missingCount,issuesCount:v.issuesCount})},[X,v.validCount,v.missingCount,v.issuesCount]);const me=(0,_react.useMemo)(()=>{return[...a].sort((e,t)=>{const r={required_missing:1,required_wrong_type:2,required_wrong_value:3,required_present:4,optional_present:5};return(r[e.status]||999)-(r[t.status]||999)})},[a]);const Qe=(0,_react.useMemo)(()=>{return Array.from(new Set(a.map(e=>e.key))).sort()},[a]);const g=(0,_react.useMemo)(()=>{let e=me;if(I&&I.size>0){e=e.filter(t=>I.has(t.storageType))}e=e.filter(t=>{const r=Array.from(K).some(u=>t.key.includes(u));return!r});if(m){const t=m.toLowerCase();e=e.filter(r=>r.key.toLowerCase().includes(t))}switch(y){case"all":e=e.filter(t=>t.status==="required_present"||t.status==="optional_present");break;case"missing":e=e.filter(t=>t.status==="required_missing");break;case"issues":e=e.filter(t=>t.status==="required_wrong_type"||t.status==="required_wrong_value");break}if(d!=="all"){e=e.filter(t=>t.storageType===d)}if(S&&S.size>0){const t=e.filter(u=>S.has(u.key));const r=e.filter(u=>!S.has(u.key));e=[...t,...r]}return e},[me,y,d,K,m,l,I,S]);const C=(0,_react.useMemo)(()=>g.slice(0,B),[g,B]);const E=(0,_react.useMemo)(()=>{return Math.max(0,g.length-B)},[g.length,B]);const Fe=E>0;const et=(0,_react.useMemo)(()=>{const e=g.filter(r=>!(0,_sharedUi.isDevToolsStorageKey)(r.key));const t={totalCount:g.length,requiredCount:e.filter(r=>r.category==="required").length,missingCount:e.filter(r=>r.status==="required_missing").length,wrongValueCount:e.filter(r=>r.status==="required_wrong_value").length,wrongTypeCount:e.filter(r=>r.status==="required_wrong_type").length,presentRequiredCount:e.filter(r=>r.status==="required_present").length,optionalCount:e.filter(r=>r.category==="optional").length,mmkvCount:g.filter(r=>r.storageType==="mmkv").length,asyncCount:g.filter(r=>r.storageType==="async").length,secureCount:g.filter(r=>r.storageType==="secure").length,devToolsCount:g.filter(r=>(0,_sharedUi.isDevToolsStorageKey)(r.key)).length};return t},[g]);const ze=(0,_react.useMemo)(()=>{return C.filter(e=>V.has(w(e)))},[C,V,w]);const Oe=(0,_react.useCallback)(()=>{const e=new Set(C.map(w));x(e)},[C,w]);const We=(0,_react.useCallback)(()=>{x(new Set)},[]);const k=_.requiredCount>0?Math.round(_.presentRequiredCount/_.requiredCount*100):_.totalCount>0?100:0;const He=k>=90?"OPTIMAL":k>=70?"WARNING":"CRITICAL";const Ue=k>=90?_sharedUi.gameUIColors.success:k>=70?_sharedUi.gameUIColors.warning:_sharedUi.gameUIColors.error;if(b){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.root,children:(0,_jsxRuntime.jsx)(_StorageKeyDetailScreen.StorageKeyDetailScreen,{storageKey:b,editBlockedReason:he(b),eventCount:G?.[b.key],onSave:e=>H(b,e),onViewHistory:A?()=>A(b.key):void 0,onDeleteKey:e=>ye(e,()=>te?.()),onHideKey:e=>{W([e]);te?.()},isPinned:S?.has(b.key)??false,onTogglePin:Z,applySafeAreaInset:fe})})}if(Ne&&a.length===0){return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.emptyState,children:[(0,_jsxRuntime.jsx)(_sharedUi.Database,{size:48,color:_sharedUi.night.color.textTertiary}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyTitle,children:"Loading storage keys..."})]})}if(ge){return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.emptyState,children:[(0,_jsxRuntime.jsx)(_sharedUi.Database,{size:48,color:_sharedUi.gameUIColors.error}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.emptyTitle,{color:_sharedUi.gameUIColors.error}],children:"Error loading storage"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptySubtitle,children:ge.message})]})}const Pe=(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[d==="mmkv"&&c.length===0&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.emptyMMKVState,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyMMKVTitle,children:"No MMKV Instances Detected"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyMMKVSubtitle,children:"MMKV instances must be registered with registerMMKVInstance()"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyMMKVCode,children:`import { registerMMKVInstance } from '@buoy-gg/storage';
2
2
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.FREE_TIER_KEY_LIMIT = void 0;
7
- exports.GameUIStorageBrowser = GameUIStorageBrowser;
8
- var _react = require("react");
9
- var _reactNative = require("react-native");
10
- var _sharedUi = require("@buoy-gg/shared-ui");
11
- var _StorageKeySection = require("./StorageKeySection");
12
- var _StorageKeyDetailScreen = require("./StorageKeyDetailScreen");
13
- var _StorageFilterCards = require("./StorageFilterCards");
14
- var _useAsyncStorageKeys = require("../hooks/useAsyncStorageKeys");
15
- var _AsyncStorageListener = require("../utils/AsyncStorageListener");
16
- var _useMMKVKeys = require("../hooks/useMMKVKeys");
17
- var _useSecureStoreKeys = require("../hooks/useSecureStoreKeys");
18
- var _useMMKVInstances = require("../hooks/useMMKVInstances");
19
- var _StorageSaveNotice = require("./StorageSaveNotice");
20
- var _StorageActionButtons = require("./StorageActionButtons");
21
- var _SelectionActionBar = require("./SelectionActionBar");
22
- var _MMKVListener = require("../utils/MMKVListener");
23
- var _storageBrowserRefresh = require("../utils/storageBrowserRefresh");
24
- var _deleteStorageKeys = require("../utils/deleteStorageKeys");
25
- var _setStorageValue = require("../utils/setStorageValue");
26
- var _license = require("@buoy-gg/license");
27
- var _jsxRuntime = require("react/jsx-runtime");
28
- // Pure JS (no react-native-mmkv dependency) — works with any registered
29
- // instance, including remote-backed wrappers on the desktop dashboard.
30
-
31
- // Import shared Game UI components
32
-
33
- // MMKV Instance color palette - consistent colors per instance
34
- const INSTANCE_COLORS = [_sharedUi.macOSColors.semantic.info,
35
- // Blue
36
- _sharedUi.macOSColors.semantic.success,
37
- // Green
38
- _sharedUi.macOSColors.semantic.warning,
39
- // Orange
40
- _sharedUi.macOSColors.semantic.debug,
41
- // Purple
42
- '#FF6B9D',
43
- // Pink
44
- '#00D9FF' // Cyan
45
- ];
46
-
47
- /**
48
- * Get consistent color for an MMKV instance based on its ID
49
- * Uses simple hash to ensure same instance always gets same color
50
- */
51
- function getInstanceColor(instanceId) {
52
- const hash = instanceId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
53
- return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
54
- }
55
-
56
- /**
57
- * @deprecated Read the live limit with `useLimit("storage.keys")` — this only
58
- * reports the FREE tier's value and is blind to the anonymous tier. Kept
59
- * because it is part of the package's public exports.
60
- */
61
- const FREE_TIER_KEY_LIMIT = exports.FREE_TIER_KEY_LIMIT = _sharedUi.TIER_LIMITS["storage.keys"].free;
62
- function GameUIStorageBrowser({
63
- requiredStorageKeys = [],
64
- showFilters = false,
65
- ignoredPatterns = new Set(["@react_buoy"]),
66
- onTogglePattern,
67
- onAddPattern,
68
- searchQuery = "",
69
- storageDataRef,
70
- eventCountByKey,
71
- lastWriteByKey,
72
- onViewHistory,
73
- enabledStorageTypes,
74
- statusFilter,
75
- onStatusFilterChange,
76
- onStatusCountsChange,
77
- pinnedKeys,
78
- onTogglePin,
79
- storageTypeFilter,
80
- onStorageTypeFilterChange,
81
- applySafeAreaInset,
82
- detailKey = null,
83
- onOpenKey,
84
- onCloseDetail
85
- }) {
86
- const isPro = (0, _license.useIsPro)();
87
- const keyLimit = (0, _sharedUi.useLimit)("storage.keys");
88
- const mmkvInstanceLimit = (0, _sharedUi.useLimit)("storage.mmkvInstances");
89
- const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
90
-
91
- // Auto-close upgrade modal when user becomes Pro
92
- (0, _react.useEffect)(() => {
93
- if (showUpgradeModal && isPro) {
94
- setShowUpgradeModal(false);
95
- }
96
- }, [showUpgradeModal, isPro]);
97
- const [internalFilter, setInternalFilter] = (0, _react.useState)("all");
98
- // Controlled when the host supplies both halves; uncontrolled otherwise.
99
- const isFilterControlled = statusFilter != null && onStatusFilterChange != null;
100
- const activeFilter = isFilterControlled ? statusFilter : internalFilter;
101
- const setActiveFilter = isFilterControlled ? onStatusFilterChange : setInternalFilter;
102
- const [internalStorageType, setInternalStorageType] = (0, _react.useState)("all");
103
- // Controlled when the host supplies both halves — the modal does, so the
104
- // Events tab shows the same backend you picked here.
105
- const isStorageTypeControlled = storageTypeFilter != null && onStorageTypeFilterChange != null;
106
- const activeStorageType = isStorageTypeControlled ? storageTypeFilter : internalStorageType;
107
- const setActiveStorageType = isStorageTypeControlled ? onStorageTypeFilterChange : setInternalStorageType;
108
- const [selectedMMKVInstance, setSelectedMMKVInstance] = (0, _react.useState)(null);
109
- const scrollRef = (0, _react.useRef)(null);
110
- const scrollOffsetRef = (0, _react.useRef)(0);
111
- const rowNodesRef = (0, _react.useRef)(new Map());
112
- const handleRowRef = (0, _react.useCallback)((id, node) => {
113
- if (node) rowNodesRef.current.set(id, node);else rowNodesRef.current.delete(id);
114
- }, []);
115
-
116
- /**
117
- * Shown after every successful write, with the same sentence every time.
118
- *
119
- * An earlier version looked up which live store owned the key and offered to
120
- * make that store re-read it. That required knowing what is linked to what,
121
- * and every route to that knowledge is a guess — key names collide across
122
- * backends, only zustand stores register at all, and a store can register
123
- * after the lookup ran. A message that is right most of the time is the
124
- * problem this tool has, not the fix for it.
125
- *
126
- * There is also no general mechanism to find: the app owns its cache, and
127
- * only the app can invalidate it. So the tool promises the one thing that is
128
- * true for every key, every backend and every persistence library — this is
129
- * what loads next launch — and "make it live now" lives in the state tools,
130
- * where the store you mean is the one you are looking at.
131
- */
132
- /**
133
- * Bumped on every save rather than flipped to `true`.
134
- *
135
- * The toast unmounts itself when it fades, and a boolean already `true` would
136
- * not change on the next save — so a second edit would show nothing. The
137
- * counter is the toast's `key`, so each save mounts a fresh one and the
138
- * animation restarts from the beginning.
139
- */
140
- const [saveToastId, setSaveToastId] = (0, _react.useState)(0);
141
-
142
- // Selection mode state
143
- const [isSelectMode, setIsSelectMode] = (0, _react.useState)(false);
144
- const [selectedKeyIds, setSelectedKeyIds] = (0, _react.useState)(new Set());
145
-
146
- // Get all MMKV instances
147
- const {
148
- instances: mmkvInstances
149
- } = (0, _useMMKVInstances.useMMKVInstances)(false);
150
-
151
- // REMOVED: Auto-selection is now handled by the instance navbar
152
- // Users can manually select an instance from the navbar if they want to filter by instance
153
- // By default, selectedMMKVInstance is null, which shows ALL MMKV keys
154
-
155
- // Use AsyncStorage hook
156
- const {
157
- storageKeys: asyncStorageKeys,
158
- isLoading: isLoadingAsync,
159
- error: asyncError,
160
- refresh: refreshAsync
161
- } = (0, _useAsyncStorageKeys.useAsyncStorageKeys)(requiredStorageKeys);
162
-
163
- // Memoize the instances array to prevent infinite re-renders
164
- const mmkvInstancesForHook = (0, _react.useMemo)(() => mmkvInstances.map(inst => ({
165
- instance: inst.instance,
166
- id: inst.id
167
- })), [mmkvInstances]);
168
-
169
- // Use MMKV hook - always fetch from ALL instances, then filter by selected instance
170
- const {
171
- storageKeys: allMMKVKeys,
172
- isLoading: isLoadingMMKV,
173
- error: mmkvError,
174
- refresh: refreshMMKV
175
- } = (0, _useMMKVKeys.useMultiMMKVKeys)(mmkvInstancesForHook, requiredStorageKeys);
176
-
177
- // Filter MMKV keys by selected instance (if one is selected)
178
- const mmkvStorageKeys = (0, _react.useMemo)(() => {
179
- if (!selectedMMKVInstance) {
180
- return allMMKVKeys; // Show all MMKV keys
181
- }
182
- return allMMKVKeys.filter(k => k.instanceId === selectedMMKVInstance);
183
- }, [allMMKVKeys, selectedMMKVInstance]);
184
-
185
- // Use SecureStore hook — only surfaces keys declared via registerSecureStoreKeys()
186
- const {
187
- storageKeys: secureStorageKeys,
188
- isLoading: isLoadingSecure,
189
- error: secureError,
190
- refresh: refreshSecure
191
- } = (0, _useSecureStoreKeys.useSecureStoreKeys)();
192
-
193
- // Merge storage keys from AsyncStorage, MMKV, and SecureStore
194
- const allStorageKeys = (0, _react.useMemo)(() => {
195
- return [...asyncStorageKeys, ...mmkvStorageKeys, ...secureStorageKeys];
196
- }, [asyncStorageKeys, mmkvStorageKeys, secureStorageKeys]);
197
-
198
- /**
199
- * The CURRENT snapshot of the key the detail screen is open on. `detailKey`
200
- * is only an identity — resolving it fresh each pass is what makes the
201
- * detail screen live: the app writes, the snapshot refreshes, the screen
202
- * follows. Falls back to the captured info because the backend hooks
203
- * transiently EMPTY their lists mid-refresh — treating "missing from this
204
- * render's list" as "deleted" closed the screen the moment it opened, so
205
- * absence here is never acted on (deletion closes explicitly, from the
206
- * delete flow itself).
207
- */
208
- const detailInfo = (0, _react.useMemo)(() => {
209
- if (!detailKey) return null;
210
- const id = (0, _StorageKeySection.storageRowId)(detailKey);
211
- return allStorageKeys.find(k => (0, _StorageKeySection.storageRowId)(k) === id) ?? detailKey;
212
- }, [detailKey, allStorageKeys]);
213
-
214
- // Determine loading and error states
215
- const isLoading = isLoadingAsync || isLoadingMMKV || isLoadingSecure;
216
- const error = asyncError || mmkvError || secureError;
217
-
218
- // Combined refresh function
219
- const refresh = (0, _react.useCallback)(() => {
220
- refreshAsync();
221
- refreshMMKV();
222
- refreshSecure();
223
- }, [refreshAsync, refreshMMKV, refreshSecure]);
224
-
225
- // Generate unique identifier for a storage key
226
- const getKeyIdentifier = (0, _react.useCallback)(storageKey => {
227
- return storageKey.instanceId ? `${storageKey.storageType}-${storageKey.instanceId}-${storageKey.key}` : `${storageKey.storageType}-${storageKey.key}`;
228
- }, []);
229
-
230
- // Toggle select mode
231
- const handleToggleSelectMode = (0, _react.useCallback)(() => {
232
- setIsSelectMode(prev => {
233
- if (prev) {
234
- // When exiting select mode, clear selection
235
- setSelectedKeyIds(new Set());
236
- }
237
- return !prev;
238
- });
239
- }, []);
240
-
241
- // Handle selection change for a single key
242
- const handleSelectionChange = (0, _react.useCallback)((storageKey, selected) => {
243
- const keyId = getKeyIdentifier(storageKey);
244
- setSelectedKeyIds(prev => {
245
- const newSet = new Set(prev);
246
- if (selected) {
247
- newSet.add(keyId);
248
- } else {
249
- newSet.delete(keyId);
250
- }
251
- return newSet;
252
- });
253
- }, [getKeyIdentifier]);
254
-
255
- // Clear selection and exit select mode after deletion
256
- const handleDeleteComplete = (0, _react.useCallback)(() => {
257
- setSelectedKeyIds(new Set());
258
- setIsSelectMode(false);
259
- refresh();
260
- }, [refresh]);
261
-
262
- // Hide the selected keys by adding each to the ignored-pattern filter store,
263
- // then exit selection mode. Non-destructive — keys can be unhidden from the
264
- // filters UI.
265
- const handleHideKeys = (0, _react.useCallback)(keys => {
266
- keys.forEach(k => onAddPattern?.(k.key));
267
- setSelectedKeyIds(new Set());
268
- setIsSelectMode(false);
269
- }, [onAddPattern]);
270
-
271
- /**
272
- * Write an edited value back to the device.
273
- *
274
- * Pro is checked here rather than on the Edit button because the technical
275
- * blocks (`storageEditBlockedReason`) and the licence are different kinds of
276
- * "no": the former means this key can never be edited and the row says so
277
- * permanently, the latter is an upsell. Throwing lets the editor keep the
278
- * draft on screen instead of discarding what was typed.
279
- */
280
- const handleSaveValue = (0, _react.useCallback)(async (storageKey, raw) => {
281
- if (!isPro) {
282
- setShowUpgradeModal(true);
283
- throw new Error("Editing storage values is a Pro feature.");
284
- }
285
- await (0, _setStorageValue.setStorageValue)(storageKey, raw, mmkvInstances);
286
- refresh();
287
- setSaveToastId(id => id + 1);
288
- }, [isPro, mmkvInstances, refresh]);
289
-
290
- /**
291
- * Flip a boolean key straight from its card.
292
- *
293
- * Goes through `handleSaveValue` like every other write, so the Pro gate and
294
- * the type preservation are the same ones the editor uses — this is only a
295
- * shortcut past the UI, not past the rules.
296
- */
297
- const handleToggleKey = (0, _react.useCallback)(storageKey => {
298
- void handleSaveValue(storageKey, JSON.stringify(!storageKey.value)).catch(e => {
299
- _reactNative.Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
300
- });
301
- }, [handleSaveValue]);
302
-
303
- /**
304
- * Delete ONE key, from its own card.
305
- *
306
- * Routed through `deleteStorageKeys` exactly like the selection bar, so the
307
- * per-backend fan-out keeps having a single implementation — the last time
308
- * that fan-out was wrong (an MMKV method renamed upstream) it was wrong in
309
- * silence, and a second copy is how one backend quietly stops being deleted.
310
- *
311
- * Pro is checked here rather than on the button because it's an upsell, not
312
- * a capability: the row shows the action and explains the wall on press.
313
- */
314
- const handleDeleteKey = (0, _react.useCallback)((storageKey, onDeleted) => {
315
- if (!isPro) {
316
- setShowUpgradeModal(true);
317
- return;
318
- }
319
- _reactNative.Alert.alert("Delete key", `Delete "${storageKey.key}"? This cannot be undone.`, [{
320
- text: "Cancel",
321
- style: "cancel"
322
- }, {
323
- text: "Delete",
324
- style: "destructive",
325
- onPress: async () => {
326
- try {
327
- await (0, _deleteStorageKeys.deleteStorageKeys)([storageKey], mmkvInstances.map(inst => ({
328
- id: inst.id,
329
- instance: inst.instance
330
- })));
331
- refresh();
332
- // Only after the delete actually landed — a cancelled alert
333
- // or a failed delete leaves the caller's screen alone.
334
- onDeleted?.();
335
- } catch (error) {
336
- _reactNative.Alert.alert("Error", `Failed to delete key: ${error}`);
337
- }
338
- }
339
- }]);
340
- }, [isPro, mmkvInstances, refresh]);
341
-
342
- /** Permanent, per-key reasons a value can't be edited (never the licence). */
343
- const editBlockedFor = (0, _react.useCallback)(storageKey => (0, _setStorageValue.storageEditBlockedReason)(storageKey, mmkvInstances), [mmkvInstances]);
344
-
345
- // Memoized export data for copy functionality
346
- const copyExportData = (0, _react.useMemo)(() => {
347
- const allKeys = allStorageKeys;
348
-
349
- // Calculate stats
350
- const stats = {
351
- valid: allKeys.filter(k => k.status === 'required_present' || k.status === 'optional_present').length,
352
- missing: allKeys.filter(k => k.status === 'required_missing').length,
353
- issues: allKeys.filter(k => k.status === 'required_wrong_value' || k.status === 'required_wrong_type').length,
354
- total: allKeys.length
355
- };
356
-
357
- // Group by storage type
358
- const asyncKeys = allKeys.filter(k => k.storageType === 'async');
359
- const mmkvKeys = allKeys.filter(k => k.storageType === 'mmkv');
360
- const secureKeys = allKeys.filter(k => k.storageType === 'secure');
361
-
362
- // Build structured export
363
- return {
364
- summary: {
365
- valid: stats.valid,
366
- missing: stats.missing,
367
- issues: stats.issues,
368
- total: stats.total,
369
- timestamp: new Date().toISOString()
370
- },
371
- asyncStorage: asyncKeys.reduce((acc, k) => {
372
- acc[k.key] = k.value;
373
- return acc;
374
- }, {}),
375
- mmkv: mmkvKeys.reduce((acc, k) => {
376
- const instanceId = k.instanceId || 'default';
377
- if (!acc[instanceId]) acc[instanceId] = {};
378
- acc[instanceId][k.key] = k.value;
379
- return acc;
380
- }, {}),
381
- secure: secureKeys.reduce((acc, k) => {
382
- acc[k.key] = k.value;
383
- return acc;
384
- }, {})
385
- };
386
- }, [allStorageKeys]);
387
-
388
- // Update storage data ref for copy functionality
389
- (0, _react.useEffect)(() => {
390
- if (storageDataRef) {
391
- storageDataRef.current = allStorageKeys;
392
- }
393
- }, [allStorageKeys, storageDataRef]);
394
-
395
- // Auto-refresh on storage events (AsyncStorage)
396
- (0, _react.useEffect)(() => {
397
- let timeoutId;
398
- const unsubscribe = (0, _AsyncStorageListener.addListener)(event => {
399
- clearTimeout(timeoutId);
400
- timeoutId = setTimeout(() => {
401
- refreshAsync(); // Auto-refresh AsyncStorage when it changes
402
- }, 100); // 100ms debounce
403
- });
404
- return () => {
405
- clearTimeout(timeoutId);
406
- unsubscribe();
407
- };
408
- }, [refreshAsync]);
409
-
410
- // Auto-refresh when something outside this component knows the underlying
411
- // storage changed. On device the listeners above cover it; in remote-mirror
412
- // mode (desktop dashboard) local capture is suppressed and they never fire,
413
- // so the dashboard signals each incoming device snapshot this way — without
414
- // it the browse tab is frozen at whatever it read on mount.
415
- (0, _react.useEffect)(() => {
416
- let timeoutId;
417
- const unsubscribe = (0, _storageBrowserRefresh.onStorageBrowserRefresh)(() => {
418
- clearTimeout(timeoutId);
419
- timeoutId = setTimeout(refresh, 100);
420
- });
421
- return () => {
422
- clearTimeout(timeoutId);
423
- unsubscribe();
424
- };
425
- }, [refresh]);
426
-
427
- // Auto-refresh on MMKV storage events
428
- (0, _react.useEffect)(() => {
429
- let timeoutId;
430
- const unsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
431
- // Refresh when the changed instance is one we're actually showing.
432
- // `selectedMMKVInstance` is null by default ("All instances"), so
433
- // comparing it to event.instanceId directly was never true for the view
434
- // every user starts on — MMKV values sat stale in the browse list until
435
- // it remounted, even though the write was recorded in Events.
436
- const affectsView = selectedMMKVInstance === null || event.instanceId === selectedMMKVInstance;
437
- if (affectsView) {
438
- clearTimeout(timeoutId);
439
- timeoutId = setTimeout(() => {
440
- refreshMMKV(); // Auto-refresh MMKV when it changes
441
- }, 100); // 100ms debounce
442
- }
443
- });
444
- return () => {
445
- clearTimeout(timeoutId);
446
- unsubscribe();
447
- };
448
- }, [refreshMMKV, selectedMMKVInstance]);
449
-
450
- // Calculate stats from ALL keys (not filtered) - base stats for display
451
- const stats = (0, _react.useMemo)(() => {
452
- const allKeys = allStorageKeys;
453
- const appKeys = allKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key));
454
- const devKeys = allKeys.filter(k => (0, _sharedUi.isDevToolsStorageKey)(k.key));
455
- const storageStats = {
456
- totalCount: allKeys.length,
457
- requiredCount: appKeys.filter(k => k.category === "required").length,
458
- missingCount: appKeys.filter(k => k.status === "required_missing").length,
459
- wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
460
- wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
461
- presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
462
- optionalCount: appKeys.filter(k => k.category === "optional").length,
463
- // Count keys by their actual storageType property
464
- mmkvCount: allKeys.filter(k => k.storageType === "mmkv").length,
465
- asyncCount: allKeys.filter(k => k.storageType === "async").length,
466
- secureCount: allKeys.filter(k => k.storageType === "secure").length,
467
- devToolsCount: devKeys.length
468
- };
469
- return storageStats;
470
- }, [allStorageKeys]);
471
-
472
- // Calculate stats for tab badges based on current filters
473
- const tabStats = (0, _react.useMemo)(() => {
474
- // Start with all keys, excluding devtools keys
475
- let keysForStats = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key));
476
-
477
- // If a specific storage type is selected, filter by that type first
478
- if (activeStorageType !== "all") {
479
- keysForStats = keysForStats.filter(k => k.storageType === activeStorageType);
480
- }
481
-
482
- // Calculate status counts from filtered keys
483
- const validCount = keysForStats.filter(k => k.status === "required_present" || k.status === "optional_present").length;
484
- const missingCount = keysForStats.filter(k => k.status === "required_missing").length;
485
- const issuesCount = keysForStats.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value").length;
486
-
487
- // Calculate storage type counts based on active status filter
488
- let asyncCount = 0;
489
- let mmkvCount = 0;
490
- let secureCount = 0;
491
- let totalCount = 0;
492
- if (activeFilter === "all") {
493
- // Valid: only keys with valid status
494
- asyncCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "async" && (k.status === "required_present" || k.status === "optional_present")).length;
495
- mmkvCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "mmkv" && (k.status === "required_present" || k.status === "optional_present")).length;
496
- secureCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "secure" && (k.status === "required_present" || k.status === "optional_present")).length;
497
- totalCount = asyncCount + mmkvCount + secureCount;
498
- } else if (activeFilter === "missing") {
499
- // Missing: only keys with missing status
500
- asyncCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "async" && k.status === "required_missing").length;
501
- mmkvCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "mmkv" && k.status === "required_missing").length;
502
- secureCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "secure" && k.status === "required_missing").length;
503
- totalCount = asyncCount + mmkvCount + secureCount;
504
- } else if (activeFilter === "issues") {
505
- // Issues: only keys with issue status
506
- asyncCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "async" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
507
- mmkvCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "mmkv" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
508
- secureCount = allStorageKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key) && k.storageType === "secure" && (k.status === "required_wrong_type" || k.status === "required_wrong_value")).length;
509
- totalCount = asyncCount + mmkvCount + secureCount;
510
- }
511
- return {
512
- validCount,
513
- missingCount,
514
- issuesCount,
515
- asyncCount,
516
- mmkvCount,
517
- secureCount,
518
- totalCount
519
- };
520
- }, [allStorageKeys, activeFilter, activeStorageType]);
521
-
522
- // Push the status counts to a host toolbar that renders them (desktop).
523
- (0, _react.useEffect)(() => {
524
- onStatusCountsChange?.({
525
- validCount: tabStats.validCount,
526
- missingCount: tabStats.missingCount,
527
- issuesCount: tabStats.issuesCount
528
- });
529
- }, [onStatusCountsChange, tabStats.validCount, tabStats.missingCount, tabStats.issuesCount]);
530
-
531
- // Sort all keys by priority (issues first)
532
- const sortedKeys = (0, _react.useMemo)(() => {
533
- // Sort by status priority: errors first, then warnings, then valid
534
- return [...allStorageKeys].sort((a, b) => {
535
- const priorityMap = {
536
- required_missing: 1,
537
- required_wrong_type: 2,
538
- required_wrong_value: 3,
539
- required_present: 4,
540
- optional_present: 5
541
- };
542
- return (priorityMap[a.status] || 999) - (priorityMap[b.status] || 999);
543
- });
544
- }, [allStorageKeys]);
545
-
546
- // Get all unique keys for filter suggestions
547
- const allUniqueKeys = (0, _react.useMemo)(() => {
548
- return Array.from(new Set(allStorageKeys.map(k => k.key))).sort();
549
- }, [allStorageKeys]);
550
-
551
- // Filter keys based on active filter, storage type, ignored patterns, and search
552
- const filteredKeys = (0, _react.useMemo)(() => {
553
- let keys = sortedKeys;
554
-
555
- // Apply enabled storage types filter (from parent unified filter)
556
- if (enabledStorageTypes && enabledStorageTypes.size > 0) {
557
- keys = keys.filter(k => enabledStorageTypes.has(k.storageType));
558
- }
559
-
560
- // Apply ignored pattern filter
561
- keys = keys.filter(k => {
562
- // Check if key matches any ignored pattern
563
- const shouldIgnore = Array.from(ignoredPatterns).some(pattern => k.key.includes(pattern));
564
- return !shouldIgnore;
565
- });
566
-
567
- // Apply search filter
568
- if (searchQuery) {
569
- const query = searchQuery.toLowerCase();
570
- keys = keys.filter(k => k.key.toLowerCase().includes(query));
571
- }
572
-
573
- // Apply status filter
574
- switch (activeFilter) {
575
- case "all":
576
- // "All" (Valid) shows only keys without issues
577
- keys = keys.filter(k => k.status === "required_present" || k.status === "optional_present");
578
- break;
579
- case "missing":
580
- keys = keys.filter(k => k.status === "required_missing");
581
- break;
582
- case "issues":
583
- keys = keys.filter(k => k.status === "required_wrong_type" || k.status === "required_wrong_value");
584
- break;
585
- }
586
-
587
- // Apply storage type filter
588
- if (activeStorageType !== "all") {
589
- keys = keys.filter(k => k.storageType === activeStorageType);
590
- }
591
-
592
- // NOTE: MMKV instance filtering is now handled in mmkvStorageKeys memo (line 128)
593
- // No need to filter again here
594
-
595
- // Float pinned keys to the top (stable — preserves existing order otherwise)
596
- if (pinnedKeys && pinnedKeys.size > 0) {
597
- const pinned = keys.filter(k => pinnedKeys.has(k.key));
598
- const rest = keys.filter(k => !pinnedKeys.has(k.key));
599
- keys = [...pinned, ...rest];
600
- }
601
- return keys;
602
- }, [sortedKeys, activeFilter, activeStorageType, ignoredPatterns, searchQuery, selectedMMKVInstance, enabledStorageTypes, pinnedKeys]);
603
-
604
- // Pro is Infinity here, so slice() returns everything.
605
- const visibleKeys = (0, _react.useMemo)(() => filteredKeys.slice(0, keyLimit), [filteredKeys, keyLimit]);
606
-
607
- // Calculate how many keys are locked (only those matching current filter)
608
- const lockedKeyCount = (0, _react.useMemo)(() => {
609
- return Math.max(0, filteredKeys.length - keyLimit);
610
- }, [filteredKeys.length, keyLimit]);
611
- const hasLockedKeys = lockedKeyCount > 0;
612
-
613
- // Calculate stats from FILTERED keys to show actual visible counts
614
- const filteredStats = (0, _react.useMemo)(() => {
615
- const appKeys = filteredKeys.filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key));
616
- const storageStats = {
617
- totalCount: filteredKeys.length,
618
- requiredCount: appKeys.filter(k => k.category === "required").length,
619
- missingCount: appKeys.filter(k => k.status === "required_missing").length,
620
- wrongValueCount: appKeys.filter(k => k.status === "required_wrong_value").length,
621
- wrongTypeCount: appKeys.filter(k => k.status === "required_wrong_type").length,
622
- presentRequiredCount: appKeys.filter(k => k.status === "required_present").length,
623
- optionalCount: appKeys.filter(k => k.category === "optional").length,
624
- mmkvCount: filteredKeys.filter(k => k.storageType === "mmkv").length,
625
- asyncCount: filteredKeys.filter(k => k.storageType === "async").length,
626
- secureCount: filteredKeys.filter(k => k.storageType === "secure").length,
627
- devToolsCount: filteredKeys.filter(k => (0, _sharedUi.isDevToolsStorageKey)(k.key)).length
628
- };
629
- return storageStats;
630
- }, [filteredKeys]);
631
-
632
- // Get selected keys as StorageKeyInfo objects (from visible keys only)
633
- const selectedKeysInfo = (0, _react.useMemo)(() => {
634
- return visibleKeys.filter(key => selectedKeyIds.has(getKeyIdentifier(key)));
635
- }, [visibleKeys, selectedKeyIds, getKeyIdentifier]);
636
-
637
- // Select all visible keys (only unlocked keys can be selected)
638
- const handleSelectAll = (0, _react.useCallback)(() => {
639
- const allIds = new Set(visibleKeys.map(getKeyIdentifier));
640
- setSelectedKeyIds(allIds);
641
- }, [visibleKeys, getKeyIdentifier]);
642
-
643
- // Clear all selections
644
- const handleClearSelection = (0, _react.useCallback)(() => {
645
- setSelectedKeyIds(new Set());
646
- }, []);
647
-
648
- /**
649
- * Serialized size of everything in storage, for the table's footer summary.
650
- * Keyed on `allStorageKeys` rather than the filtered set: it answers "how much
651
- * is in here", which a filter should not change.
652
- */
653
- // Calculate health percentage
654
- const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : stats.totalCount > 0 ? 100 : 0;
655
- const healthStatus = healthPercentage >= 90 ? "OPTIMAL" : healthPercentage >= 70 ? "WARNING" : "CRITICAL";
656
- const healthColor = healthPercentage >= 90 ? _sharedUi.gameUIColors.success : healthPercentage >= 70 ? _sharedUi.gameUIColors.warning : _sharedUi.gameUIColors.error;
657
-
658
- // The detail screen REPLACES the list as the modal's whole body — the host
659
- // modal's header carries back + the key's name, the React Query
660
- // interaction (and the Zustand tool's). All hooks above have run; this
661
- // return only swaps what's painted.
662
- if (detailInfo) {
663
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
664
- style: styles.root,
665
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageKeyDetailScreen.StorageKeyDetailScreen, {
666
- storageKey: detailInfo,
667
- editBlockedReason: editBlockedFor(detailInfo),
668
- eventCount: eventCountByKey?.[detailInfo.key],
669
- onSave: raw => handleSaveValue(detailInfo, raw),
670
- onViewHistory: onViewHistory ? () => onViewHistory(detailInfo.key) : undefined,
671
- onDeleteKey: k => handleDeleteKey(k, () => onCloseDetail?.()),
672
- onHideKey: k => {
673
- handleHideKeys([k]);
674
- onCloseDetail?.();
675
- },
676
- isPinned: pinnedKeys?.has(detailInfo.key) ?? false,
677
- onTogglePin: onTogglePin,
678
- applySafeAreaInset: applySafeAreaInset
679
- })
680
- });
681
- }
682
-
683
- // Loading state
684
- if (isLoading && allStorageKeys.length === 0) {
685
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
686
- style: styles.emptyState,
687
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
688
- size: 48,
689
- color: _sharedUi.night.color.textTertiary
690
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
691
- style: styles.emptyTitle,
692
- children: "Loading storage keys..."
693
- })]
694
- });
695
- }
696
-
697
- // Error state
698
- if (error) {
699
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
700
- style: styles.emptyState,
701
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
702
- size: 48,
703
- color: _sharedUi.gameUIColors.error
704
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
705
- style: [styles.emptyTitle, {
706
- color: _sharedUi.gameUIColors.error
707
- }],
708
- children: "Error loading storage"
709
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
710
- style: styles.emptySubtitle,
711
- children: error.message
712
- })]
713
- });
714
- }
715
-
716
- /* ---------------------------------------------------------------- *
717
- * Subtrees the card list composes. Extracted rather than inlined so Pro
718
- * gating and MMKV
719
- * instance selection or the empty-state copy.
720
- * ---------------------------------------------------------------- */
721
-
722
- const mmkvSection = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
723
- children: [activeStorageType === "mmkv" && mmkvInstances.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
724
- style: styles.emptyMMKVState,
725
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
726
- style: styles.emptyMMKVTitle,
727
- children: "No MMKV Instances Detected"
728
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
729
- style: styles.emptyMMKVSubtitle,
730
- children: "MMKV instances must be registered with registerMMKVInstance()"
731
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
732
- style: styles.emptyMMKVCode,
733
- children: `import { registerMMKVInstance } from '@buoy-gg/storage';\n\nconst storage = createMMKV();\nregisterMMKVInstance('mmkv.default', storage);`
734
- })]
735
- }), activeStorageType === "mmkv" && mmkvInstances.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
736
- style: styles.instanceNavbar,
737
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
738
- style: styles.instanceNavbarHeader,
739
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
740
- style: styles.instanceNavbarLabel,
741
- children: "INSTANCES"
742
- }), mmkvInstances.length > mmkvInstanceLimit && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {
743
- style: {
744
- transform: [{
745
- scale: 0.85
746
- }]
747
- }
748
- })]
749
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
750
- horizontal: true,
751
- showsHorizontalScrollIndicator: false,
752
- contentContainerStyle: styles.instanceNavbarScroll,
753
- children: [(isPro || mmkvInstances.length === 1) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
754
- onPress: () => setSelectedMMKVInstance(null),
755
- testID: "buoy-storage-instance-all",
756
- accessibilityRole: "button",
757
- accessibilityLabel: "All MMKV instances",
758
- style: [styles.instanceNavbarButton, selectedMMKVInstance === null && styles.instanceNavbarButtonActive],
759
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.HardDrive, {
760
- size: 12,
761
- color: selectedMMKVInstance === null ? _sharedUi.night.color.text : _sharedUi.night.color.textSecondary
762
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
763
- style: [styles.instanceNavbarButtonText, selectedMMKVInstance === null && styles.instanceNavbarButtonTextActive],
764
- children: "All"
765
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
766
- style: styles.instanceNavbarBadge,
767
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
768
- style: styles.instanceNavbarBadgeText,
769
- children: mmkvInstances.reduce((sum, inst) => sum + inst.keyCount, 0)
770
- })
771
- })]
772
- }), mmkvInstances.map((inst, index) => {
773
- const isLocked = index >= mmkvInstanceLimit;
774
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
775
- testID: `buoy-storage-instance-${inst.id}`,
776
- accessibilityRole: "button",
777
- accessibilityLabel: `MMKV instance ${inst.id}`,
778
- onPress: () => {
779
- if (isLocked) {
780
- setShowUpgradeModal(true);
781
- } else {
782
- setSelectedMMKVInstance(inst.id);
783
- }
784
- },
785
- style: [styles.instanceNavbarButton, inst.id === selectedMMKVInstance && styles.instanceNavbarButtonActive, {
786
- borderColor: isLocked ? _sharedUi.night.color.border : getInstanceColor(inst.id) + '40',
787
- backgroundColor: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) + '20' : _sharedUi.night.color.surfaceElevated,
788
- opacity: isLocked ? 0.5 : 1
789
- }],
790
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.HardDrive, {
791
- size: 12,
792
- color: inst.id === selectedMMKVInstance ? getInstanceColor(inst.id) : _sharedUi.night.color.textSecondary
793
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
794
- style: [styles.instanceNavbarButtonText, inst.id === selectedMMKVInstance && {
795
- color: getInstanceColor(inst.id),
796
- fontWeight: '700'
797
- }],
798
- children: inst.id
799
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
800
- style: [styles.instanceNavbarBadge, inst.id === selectedMMKVInstance && {
801
- backgroundColor: getInstanceColor(inst.id) + '30'
802
- }],
803
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
804
- style: [styles.instanceNavbarBadgeText, inst.id === selectedMMKVInstance && {
805
- color: getInstanceColor(inst.id)
806
- }],
807
- children: inst.keyCount
808
- })
809
- })]
810
- }, inst.id);
811
- })]
812
- })]
813
- })]
814
- });
815
- const actionButtonsNode = /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageActionButtons.StorageActionButtons, {
816
- copyValue: copyExportData,
817
- mmkvInstances: mmkvInstances.map(inst => ({
818
- id: inst.id,
819
- instance: inst.instance
820
- })),
821
- activeStorageType: activeStorageType,
822
- onClearComplete: refresh,
823
- isSelectMode: isSelectMode,
824
- onToggleSelectMode: handleToggleSelectMode,
825
- selectedCount: selectedKeyIds.size
826
- });
827
-
828
- /* Selection Action Bar - Only show in select mode with selections */
829
- const selectionBarNode = isSelectMode ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_SelectionActionBar.SelectionActionBar, {
830
- selectedKeys: selectedKeysInfo,
831
- mmkvInstances: mmkvInstances.map(inst => ({
832
- id: inst.id,
833
- instance: inst.instance
834
- })),
835
- onDeleteComplete: handleDeleteComplete,
836
- onHideKeys: handleHideKeys,
837
- onSelectAll: handleSelectAll,
838
- onClearSelection: handleClearSelection,
839
- totalVisibleKeys: visibleKeys.length
840
- }) : null;
841
- const lockedBannerNode = hasLockedKeys ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
842
- style: styles.limitBanner,
843
- onPress: () => setShowUpgradeModal(true),
844
- activeOpacity: 0.8,
845
- testID: "buoy-storage-locked-banner",
846
- accessibilityRole: "button",
847
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
848
- style: styles.limitBannerContent,
849
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
850
- style: styles.limitBannerLeft,
851
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
852
- size: 16,
853
- color: _sharedUi.night.color.accent
854
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
855
- style: styles.limitBannerText,
856
- children: searchQuery ? `${lockedKeyCount} matching ${lockedKeyCount === 1 ? 'key' : 'keys'} in locked storage` : `${lockedKeyCount} keys locked`
857
- })]
858
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
859
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
860
- style: styles.limitBannerSubtext,
861
- children: searchQuery ? "Upgrade to Pro to search your full storage" : "Upgrade to Pro to unlock full storage access"
862
- })]
863
- }) : null;
864
- const emptyStateNode = (() => {
865
- // Short-circuits on the first real key, so this costs nothing.
866
- const hasAnyKeys = allStorageKeys.some(k => !(0, _sharedUi.isDevToolsStorageKey)(k.key));
867
-
868
- // Three causes here are unlike any other tool's: an empty MISSING or
869
- // ISSUES list is a PASS, not a failure, and an empty VALID list means
870
- // everything is broken. Those skip the shared resolver and say so.
871
- let content;
872
- if (searchQuery || !hasAnyKeys) {
873
- content = (0, _sharedUi.resolveListEmptyState)({
874
- subject: "keys",
875
- verb: "registered",
876
- search: searchQuery,
877
- total: hasAnyKeys ? 1 : 0,
878
- waiting: {
879
- // Previously this fell into the "all" branch and claimed every key
880
- // had an issue — of the zero keys that exist.
881
- otto: "empty",
882
- title: "No storage keys",
883
- message: "Nothing is registered for this tool to read."
884
- }
885
- });
886
- if (searchQuery && hasAnyKeys) {
887
- // No count: the pre-search total would mean re-running the whole key
888
- // pipeline a second time for a number the sentence does not need.
889
- content = {
890
- ...content,
891
- message: "No keys match. Clear the search to see them."
892
- };
893
- }
894
- } else if (activeFilter === "missing") {
895
- content = {
896
- otto: "thumbsup",
897
- title: "No missing keys",
898
- message: "All required keys are present"
899
- };
900
- } else if (activeFilter === "issues") {
901
- content = {
902
- otto: "cool",
903
- title: "No issues found",
904
- message: "All storage keys are correctly configured"
905
- };
906
- } else {
907
- content = {
908
- otto: "broken",
909
- title: "No valid keys found",
910
- message: "All keys have issues or are missing"
911
- };
912
- }
913
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
914
- ...content,
915
- testID: "buoy-storage-keys-empty"
916
- });
917
- })();
918
- const upgradeModalNode = /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
919
- visible: showUpgradeModal,
920
- onClose: () => setShowUpgradeModal(false),
921
- featureName: "Storage Keys"
922
- });
923
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
924
- style: styles.root,
925
- children: [saveToastId > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageSaveNotice.StorageSaveNotice, {
926
- onDone: () => setSaveToastId(0)
927
- }, saveToastId) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView
928
- // Named because `by.type("RCTScrollView")` doesn't match on Fabric
929
- // (renamed class) — automation scrolls below-the-fold rows by this id.
930
- , {
931
- testID: "buoy-storage-scroll",
932
- ref: scrollRef,
933
- onScroll: e => {
934
- scrollOffsetRef.current = e.nativeEvent.contentOffset.y;
935
- },
936
- scrollEventThrottle: 32,
937
- style: styles.scrollContainer,
938
- contentContainerStyle: styles.container,
939
- showsVerticalScrollIndicator: false,
940
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
941
- style: styles.backgroundGrid
942
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageFilterCards.StorageFilterCards, {
943
- stats: stats,
944
- tabStats: tabStats,
945
- healthPercentage: healthPercentage,
946
- healthStatus: healthStatus,
947
- healthColor: healthColor,
948
- activeFilter: activeFilter,
949
- onFilterChange: setActiveFilter,
950
- activeStorageType: activeStorageType,
951
- onStorageTypeChange: setActiveStorageType
952
- // Same rule the wide bar follows: when a host toolbar owns the status
953
- // filter it also renders the counters, so ours would be a second copy.
954
- ,
955
- showStatus: !isFilterControlled
956
- }), mmkvSection, filteredKeys.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
957
- style: styles.keysSection,
958
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
959
- style: styles.sectionHeader,
960
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
961
- style: styles.sectionHeaderLeft,
962
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
963
- style: styles.sectionTitle,
964
- children: [activeFilter === "all" ? "KEYS" : activeFilter === "missing" ? "MISSING KEYS" : "ISSUES TO FIX", activeStorageType !== "all" && ` (${activeStorageType.toUpperCase()})`]
965
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
966
- style: styles.countBadge,
967
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
968
- style: styles.countText,
969
- children: visibleKeys.length
970
- })
971
- })]
972
- }), actionButtonsNode]
973
- }), selectionBarNode, /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageKeySection.StorageKeySection, {
974
- title: "",
975
- count: -1,
976
- keys: visibleKeys,
977
- emptyMessage: "",
978
- isSelectMode: isSelectMode,
979
- selectedKeys: selectedKeyIds,
980
- onSelectionChange: handleSelectionChange,
981
- eventCountByKey: eventCountByKey,
982
- onViewHistory: onViewHistory,
983
- onHideKey: key => handleHideKeys([key]),
984
- onToggleKey: handleToggleKey,
985
- onDeleteKey: handleDeleteKey,
986
- editBlockedFor: editBlockedFor,
987
- pinnedKeys: pinnedKeys,
988
- onTogglePin: onTogglePin,
989
- onRowRef: handleRowRef,
990
- onOpenKey: onOpenKey
991
- }), lockedBannerNode]
992
- }) : emptyStateNode, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
993
- style: styles.techFooter,
994
- children: "ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"
995
- }), upgradeModalNode]
996
- })]
997
- });
998
- }
999
- const styles = _reactNative.StyleSheet.create({
1000
- /** Measures the space the columns actually get; `flex: 1` so it fills the
1001
- * parent exactly as the bare ScrollView it wraps used to. */
1002
- root: {
1003
- flex: 1,
1004
- minHeight: 0
1005
- },
1006
- /**
1007
- * No background of its own: the tree editor paints an opaque screen, while a
1008
- * scalar key shows only a modal and wants the list visible behind its scrim.
1009
- */
1010
- editorOverlay: {
1011
- ..._sharedUi.absoluteFill,
1012
- zIndex: 10
1013
- },
1014
- scrollContainer: {
1015
- flex: 1,
1016
- // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
1017
- backgroundColor: "transparent"
1018
- },
1019
- container: {
1020
- padding: 12,
1021
- // Generous so a key card expanded at the bottom of the list can scroll
1022
- // its full detail block above the modal edge.
1023
- paddingBottom: 120
1024
- },
1025
- backgroundGrid: {
1026
- ..._sharedUi.absoluteFill,
1027
- opacity: 0.006,
1028
- backgroundColor: _sharedUi.gameUIColors.info
1029
- },
1030
- techFooter: {
1031
- fontSize: 8,
1032
- color: _sharedUi.night.color.textTertiary,
1033
- fontFamily: _sharedUi.monoFont,
1034
- textAlign: "center",
1035
- marginTop: 20,
1036
- letterSpacing: 1,
1037
- opacity: 0.5
1038
- },
1039
- // Keys section
1040
- keysSection: {
1041
- marginTop: 8,
1042
- backgroundColor: _sharedUi.night.color.bg,
1043
- borderRadius: 12,
1044
- padding: 12,
1045
- borderWidth: 1,
1046
- borderColor: _sharedUi.night.color.border
1047
- },
1048
- sectionHeader: {
1049
- flexDirection: "row",
1050
- justifyContent: "space-between",
1051
- alignItems: "center",
1052
- marginBottom: 10,
1053
- paddingBottom: 6,
1054
- borderBottomWidth: 1,
1055
- borderBottomColor: _sharedUi.night.color.separator
1056
- },
1057
- sectionHeaderLeft: {
1058
- flexDirection: "row",
1059
- alignItems: "center",
1060
- gap: 8
1061
- },
1062
- sectionTitle: {
1063
- fontSize: 12,
1064
- fontWeight: "600",
1065
- color: _sharedUi.night.color.textSecondary,
1066
- letterSpacing: 0.4,
1067
- textTransform: "uppercase"
1068
- },
1069
- countBadge: {
1070
- backgroundColor: _sharedUi.night.color.surfaceElevated,
1071
- paddingHorizontal: 8,
1072
- paddingVertical: 3,
1073
- borderRadius: 6,
1074
- borderWidth: 1,
1075
- borderColor: _sharedUi.night.color.border
1076
- },
1077
- countText: {
1078
- fontSize: 11,
1079
- fontWeight: "600",
1080
- color: _sharedUi.night.color.text,
1081
- fontFamily: _sharedUi.monoFont
1082
- },
1083
- // Empty state
1084
- emptyState: {
1085
- alignItems: "center",
1086
- justifyContent: "center",
1087
- paddingVertical: 48
1088
- },
1089
- emptyTitle: {
1090
- fontSize: 16,
1091
- fontWeight: "600",
1092
- color: _sharedUi.night.color.text,
1093
- marginTop: 12,
1094
- marginBottom: 8
1095
- },
1096
- emptySubtitle: {
1097
- fontSize: 13,
1098
- color: _sharedUi.night.color.textSecondary,
1099
- textAlign: "center"
1100
- },
1101
- // Empty MMKV state
1102
- emptyMMKVState: {
1103
- backgroundColor: _sharedUi.night.color.surface,
1104
- borderRadius: 12,
1105
- padding: 20,
1106
- borderWidth: 1,
1107
- borderColor: _sharedUi.night.color.border,
1108
- marginTop: 8,
1109
- alignItems: "center"
1110
- },
1111
- emptyMMKVTitle: {
1112
- fontSize: 14,
1113
- fontWeight: "600",
1114
- color: _sharedUi.night.color.text,
1115
- marginBottom: 6
1116
- },
1117
- emptyMMKVSubtitle: {
1118
- fontSize: 12,
1119
- color: _sharedUi.night.color.textSecondary,
1120
- textAlign: "center",
1121
- marginBottom: 12
1122
- },
1123
- emptyMMKVCode: {
1124
- fontSize: 10,
1125
- fontFamily: _sharedUi.monoFont,
1126
- color: _sharedUi.night.color.accent,
1127
- backgroundColor: _sharedUi.night.color.bg,
1128
- padding: 12,
1129
- borderRadius: 8,
1130
- borderWidth: 1,
1131
- borderColor: _sharedUi.night.color.border,
1132
- alignSelf: "stretch"
1133
- },
1134
- // Instance Navbar styles
1135
- instanceNavbar: {
1136
- marginTop: 12,
1137
- marginBottom: 8,
1138
- backgroundColor: _sharedUi.night.color.surface,
1139
- borderRadius: 10,
1140
- borderWidth: 1,
1141
- borderColor: _sharedUi.night.color.border,
1142
- padding: 10,
1143
- shadowColor: '#000',
1144
- shadowOpacity: 0.08,
1145
- shadowRadius: 6,
1146
- shadowOffset: {
1147
- width: 0,
1148
- height: 2
1149
- }
1150
- },
1151
- instanceNavbarHeader: {
1152
- flexDirection: 'row',
1153
- alignItems: 'center',
1154
- justifyContent: 'space-between',
1155
- marginBottom: 8,
1156
- paddingLeft: 2
1157
- },
1158
- instanceNavbarLabel: {
1159
- fontSize: 9,
1160
- fontWeight: '700',
1161
- color: _sharedUi.night.color.textTertiary,
1162
- textTransform: "uppercase",
1163
- letterSpacing: 1
1164
- },
1165
- instanceNavbarScroll: {
1166
- gap: 8,
1167
- paddingRight: 8
1168
- },
1169
- instanceNavbarButton: {
1170
- flexDirection: 'row',
1171
- alignItems: 'center',
1172
- gap: 6,
1173
- paddingHorizontal: 12,
1174
- paddingVertical: 8,
1175
- backgroundColor: _sharedUi.night.color.surfaceElevated,
1176
- borderRadius: 8,
1177
- borderWidth: 1,
1178
- borderColor: _sharedUi.night.color.border,
1179
- minWidth: 90
1180
- },
1181
- instanceNavbarButtonActive: {
1182
- borderWidth: 1.5,
1183
- shadowColor: '#000',
1184
- shadowOpacity: 0.1,
1185
- shadowRadius: 4,
1186
- shadowOffset: {
1187
- width: 0,
1188
- height: 1
1189
- }
1190
- },
1191
- instanceNavbarButtonText: {
1192
- fontSize: 11,
1193
- fontWeight: '600',
1194
- color: _sharedUi.night.color.textSecondary,
1195
- fontFamily: _sharedUi.monoFont,
1196
- flex: 1
1197
- },
1198
- instanceNavbarButtonTextActive: {
1199
- fontWeight: '700',
1200
- color: _sharedUi.night.color.text
1201
- },
1202
- instanceNavbarBadge: {
1203
- backgroundColor: _sharedUi.night.color.bg,
1204
- paddingHorizontal: 6,
1205
- paddingVertical: 2,
1206
- borderRadius: 6,
1207
- minWidth: 24,
1208
- alignItems: 'center'
1209
- },
1210
- instanceNavbarBadgeText: {
1211
- fontSize: 10,
1212
- fontWeight: '700',
1213
- color: _sharedUi.night.color.textSecondary,
1214
- fontFamily: _sharedUi.monoFont
1215
- },
1216
- clearSearchButton: {
1217
- marginTop: 16,
1218
- paddingHorizontal: 16,
1219
- paddingVertical: 8,
1220
- backgroundColor: _sharedUi.night.color.surfaceElevated,
1221
- borderRadius: 6,
1222
- borderWidth: 1,
1223
- borderColor: _sharedUi.night.color.border
1224
- },
1225
- clearSearchText: {
1226
- fontSize: 13,
1227
- color: _sharedUi.night.color.text,
1228
- fontWeight: "500"
1229
- },
1230
- // Free tier limit banner
1231
- limitBanner: {
1232
- backgroundColor: _sharedUi.night.color.surface,
1233
- borderRadius: 10,
1234
- borderWidth: 1,
1235
- borderColor: _sharedUi.night.color.accentBorderStrong,
1236
- padding: 12,
1237
- marginTop: 12
1238
- },
1239
- limitBannerContent: {
1240
- flexDirection: "row",
1241
- alignItems: "center",
1242
- justifyContent: "space-between",
1243
- marginBottom: 4
1244
- },
1245
- limitBannerLeft: {
1246
- flexDirection: "row",
1247
- alignItems: "center",
1248
- gap: 8
1249
- },
1250
- limitBannerText: {
1251
- color: _sharedUi.night.color.text,
1252
- fontSize: 13,
1253
- fontWeight: "600"
1254
- },
1255
- limitBannerSubtext: {
1256
- color: _sharedUi.night.color.textTertiary,
1257
- fontSize: 11
1258
- }
1259
- });
3
+ const storage = createMMKV();
4
+ registerMMKVInstance('mmkv.default', storage);`})]}),d==="mmkv"&&c.length>0&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.instanceNavbar,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.instanceNavbarHeader,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.instanceNavbarLabel,children:"INSTANCES"}),c.length>re&&(0,_jsxRuntime.jsx)(_sharedUi.ProBadge,{style:{transform:[{scale:.85}]}})]}),(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{horizontal:true,showsHorizontalScrollIndicator:false,contentContainerStyle:styles.instanceNavbarScroll,children:[(p||c.length===1)&&(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{onPress:()=>se(null),testID:"buoy-storage-instance-all",accessibilityRole:"button",accessibilityLabel:`All ${_storageLabels.storageLabels.mmkv} instances`,style:[styles.instanceNavbarButton,l===null&&styles.instanceNavbarButtonActive],children:[(0,_jsxRuntime.jsx)(_sharedUi.HardDrive,{size:12,color:l===null?_sharedUi.night.color.text:_sharedUi.night.color.textSecondary}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.instanceNavbarButtonText,l===null&&styles.instanceNavbarButtonTextActive],children:"All"}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.instanceNavbarBadge,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.instanceNavbarBadgeText,children:c.reduce((e,t)=>e+t.keyCount,0)})})]}),c.map((e,t)=>{const r=t>=re;return(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{testID:`buoy-storage-instance-${e.id}`,accessibilityRole:"button",accessibilityLabel:`MMKV instance ${e.id}`,onPress:()=>{if(r){T(true)}else{se(e.id)}},style:[styles.instanceNavbarButton,e.id===l&&styles.instanceNavbarButtonActive,{borderColor:r?_sharedUi.night.color.border:getInstanceColor(e.id)+"40",backgroundColor:e.id===l?getInstanceColor(e.id)+"20":_sharedUi.night.color.surfaceElevated,opacity:r?.5:1}],children:[(0,_jsxRuntime.jsx)(_sharedUi.HardDrive,{size:12,color:e.id===l?getInstanceColor(e.id):_sharedUi.night.color.textSecondary}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.instanceNavbarButtonText,e.id===l&&{color:getInstanceColor(e.id),fontWeight:"700"}],children:e.id}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.instanceNavbarBadge,e.id===l&&{backgroundColor:getInstanceColor(e.id)+"30"}],children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.instanceNavbarBadgeText,e.id===l&&{color:getInstanceColor(e.id)}],children:e.keyCount})})]},e.id)})]})]})]});const $e=(0,_jsxRuntime.jsx)(_StorageActionButtons.StorageActionButtons,{copyValue:Le,mmkvInstances:c.map(e=>({id:e.id,instance:e.instance})),activeStorageType:d,onClearComplete:h,isSelectMode:F,onToggleSelectMode:Ee,selectedCount:V.size});const Ge=F?(0,_jsxRuntime.jsx)(_SelectionActionBar.SelectionActionBar,{selectedKeys:ze,mmkvInstances:c.map(e=>({id:e.id,instance:e.instance})),onDeleteComplete:Ae,onHideKeys:W,onSelectAll:Oe,onClearSelection:We,totalVisibleKeys:C.length}):null;const Ye=Fe?(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.limitBanner,onPress:()=>T(true),activeOpacity:.8,testID:"buoy-storage-locked-banner",accessibilityRole:"button",children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.limitBannerContent,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.limitBannerLeft,children:[(0,_jsxRuntime.jsx)(_sharedUi.Zap,{size:16,color:_sharedUi.night.color.accent}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.limitBannerText,children:m?`${E} matching ${E===1?"key":"keys"} in locked storage`:`${E} keys locked`})]}),(0,_jsxRuntime.jsx)(_sharedUi.ProBadge,{})]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.limitBannerSubtext,children:m?"Upgrade to Pro to search your full storage":"Upgrade to Pro to unlock full storage access"})]}):null;const Je=(()=>{const e=a.some(r=>!(0,_sharedUi.isDevToolsStorageKey)(r.key));let t;if(m||!e){t=(0,_sharedUi.resolveListEmptyState)({subject:"keys",verb:"registered",search:m,total:e?1:0,waiting:{otto:"empty",title:"No storage keys",message:"Nothing is registered for this tool to read."}});if(m&&e){t={...t,message:"No keys match. Clear the search to see them."}}}else if(y==="missing"){t={otto:"thumbsup",title:"No missing keys",message:"All required keys are present"}}else if(y==="issues"){t={otto:"cool",title:"No issues found",message:"All storage keys are correctly configured"}}else{t={otto:"broken",title:"No valid keys found",message:"All keys have issues or are missing"}}return(0,_jsxRuntime.jsx)(_sharedUi.ToolEmptyState,{...t,testID:"buoy-storage-keys-empty"})})();const Xe=(0,_jsxRuntime.jsx)(_sharedUi.ProUpgradeModal,{visible:D,onClose:()=>T(false),featureName:"Storage Keys"});return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.root,children:[ae>0?(0,_jsxRuntime.jsx)(_StorageSaveNotice.StorageSaveNotice,{onDone:()=>ie(0)},ae):null,(0,_jsxRuntime.jsxs)(_reactNative.ScrollView,{testID:"buoy-storage-scroll",ref:we,onScroll:e=>{Me.current=e.nativeEvent.contentOffset.y},scrollEventThrottle:32,style:styles.scrollContainer,contentContainerStyle:styles.container,showsVerticalScrollIndicator:false,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.backgroundGrid}),(0,_jsxRuntime.jsx)(_StorageFilterCards.StorageFilterCards,{stats:_,tabStats:v,healthPercentage:k,healthStatus:He,healthColor:Ue,activeFilter:y,onFilterChange:ve,activeStorageType:d,onStorageTypeChange:xe,showStatus:!L}),Pe,g.length>0?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.keysSection,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sectionHeader,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sectionHeaderLeft,children:[(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.sectionTitle,children:[y==="all"?"KEYS":y==="missing"?"MISSING KEYS":"ISSUES TO FIX",d!=="all"&&` (${d.toUpperCase()})`]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.countBadge,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.countText,children:C.length})})]}),$e]}),Ge,(0,_jsxRuntime.jsx)(_StorageKeySection.StorageKeySection,{title:"",count:-1,keys:C,emptyMessage:"",isSelectMode:F,selectedKeys:V,onSelectionChange:Re,eventCountByKey:G,onViewHistory:A,onHideKey:e=>W([e]),onToggleKey:De,onDeleteKey:ye,editBlockedFor:he,pinnedKeys:S,onTogglePin:Z,onRowRef:_e,onOpenKey:Se}),Ye]}):Je,(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.techFooter,children:"ASYNC STORAGE | MMKV | SECURE STORAGE BACKENDS"}),Xe]})]})}const styles=_reactNative.StyleSheet.create({root:{flex:1,minHeight:0},editorOverlay:{..._sharedUi.absoluteFill,zIndex:10},scrollContainer:{flex:1,backgroundColor:"transparent"},container:{padding:12,paddingBottom:120},backgroundGrid:{..._sharedUi.absoluteFill,opacity:.006,backgroundColor:_sharedUi.gameUIColors.info},techFooter:{fontSize:8,color:_sharedUi.night.color.textTertiary,fontFamily:_sharedUi.monoFont,textAlign:"center",marginTop:20,letterSpacing:1,opacity:.5},keysSection:{marginTop:8,backgroundColor:_sharedUi.night.color.bg,borderRadius:12,padding:12,borderWidth:1,borderColor:_sharedUi.night.color.border},sectionHeader:{flexDirection:"row",justifyContent:"space-between",alignItems:"center",marginBottom:10,paddingBottom:6,borderBottomWidth:1,borderBottomColor:_sharedUi.night.color.separator},sectionHeaderLeft:{flexDirection:"row",alignItems:"center",gap:8},sectionTitle:{fontSize:12,fontWeight:"600",color:_sharedUi.night.color.textSecondary,letterSpacing:.4,textTransform:"uppercase"},countBadge:{backgroundColor:_sharedUi.night.color.surfaceElevated,paddingHorizontal:8,paddingVertical:3,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.border},countText:{fontSize:11,fontWeight:"600",color:_sharedUi.night.color.text,fontFamily:_sharedUi.monoFont},emptyState:{alignItems:"center",justifyContent:"center",paddingVertical:48},emptyTitle:{fontSize:16,fontWeight:"600",color:_sharedUi.night.color.text,marginTop:12,marginBottom:8},emptySubtitle:{fontSize:13,color:_sharedUi.night.color.textSecondary,textAlign:"center"},emptyMMKVState:{backgroundColor:_sharedUi.night.color.surface,borderRadius:12,padding:20,borderWidth:1,borderColor:_sharedUi.night.color.border,marginTop:8,alignItems:"center"},emptyMMKVTitle:{fontSize:14,fontWeight:"600",color:_sharedUi.night.color.text,marginBottom:6},emptyMMKVSubtitle:{fontSize:12,color:_sharedUi.night.color.textSecondary,textAlign:"center",marginBottom:12},emptyMMKVCode:{fontSize:10,fontFamily:_sharedUi.monoFont,color:_sharedUi.night.color.accent,backgroundColor:_sharedUi.night.color.bg,padding:12,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.border,alignSelf:"stretch"},instanceNavbar:{marginTop:12,marginBottom:8,backgroundColor:_sharedUi.night.color.surface,borderRadius:10,borderWidth:1,borderColor:_sharedUi.night.color.border,padding:10,shadowColor:"#000",shadowOpacity:.08,shadowRadius:6,shadowOffset:{width:0,height:2}},instanceNavbarHeader:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:8,paddingLeft:2},instanceNavbarLabel:{fontSize:9,fontWeight:"700",color:_sharedUi.night.color.textTertiary,textTransform:"uppercase",letterSpacing:1},instanceNavbarScroll:{gap:8,paddingRight:8},instanceNavbarButton:{flexDirection:"row",alignItems:"center",gap:6,paddingHorizontal:12,paddingVertical:8,backgroundColor:_sharedUi.night.color.surfaceElevated,borderRadius:8,borderWidth:1,borderColor:_sharedUi.night.color.border,minWidth:90},instanceNavbarButtonActive:{borderWidth:1.5,shadowColor:"#000",shadowOpacity:.1,shadowRadius:4,shadowOffset:{width:0,height:1}},instanceNavbarButtonText:{fontSize:11,fontWeight:"600",color:_sharedUi.night.color.textSecondary,fontFamily:_sharedUi.monoFont,flex:1},instanceNavbarButtonTextActive:{fontWeight:"700",color:_sharedUi.night.color.text},instanceNavbarBadge:{backgroundColor:_sharedUi.night.color.bg,paddingHorizontal:6,paddingVertical:2,borderRadius:6,minWidth:24,alignItems:"center"},instanceNavbarBadgeText:{fontSize:10,fontWeight:"700",color:_sharedUi.night.color.textSecondary,fontFamily:_sharedUi.monoFont},clearSearchButton:{marginTop:16,paddingHorizontal:16,paddingVertical:8,backgroundColor:_sharedUi.night.color.surfaceElevated,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.border},clearSearchText:{fontSize:13,color:_sharedUi.night.color.text,fontWeight:"500"},limitBanner:{backgroundColor:_sharedUi.night.color.surface,borderRadius:10,borderWidth:1,borderColor:_sharedUi.night.color.accentBorderStrong,padding:12,marginTop:12},limitBannerContent:{flexDirection:"row",alignItems:"center",justifyContent:"space-between",marginBottom:4},limitBannerLeft:{flexDirection:"row",alignItems:"center",gap:8},limitBannerText:{color:_sharedUi.night.color.text,fontSize:13,fontWeight:"600"},limitBannerSubtext:{color:_sharedUi.night.color.textTertiary,fontSize:11}});