@buoy-gg/storage 7.0.41 → 7.0.43

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