@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,954 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.FREE_TIER_EVENT_LIMIT = void 0;
7
- exports.StorageModalWithTabs = StorageModalWithTabs;
8
- exports.StorageToolView = StorageToolView;
9
- var _react = require("react");
10
- var _reactNative = require("react-native");
11
- var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
12
- var _sharedUi = require("@buoy-gg/shared-ui");
13
- var _StorageBrowserMode = require("./StorageBrowserMode");
14
- var _StorageTypeFilterBar = require("./StorageTypeFilterBar");
15
- var _clearAllStorage = require("../utils/clearAllStorage");
16
- var _useStorageEvents = require("../hooks/useStorageEvents");
17
- var _StorageEventDetailContent = require("./StorageEventDetailContent");
18
- var _StorageEventFilterView = require("./StorageEventFilterView");
19
- var _StorageEventCard = require("./StorageEventCard");
20
- var _license = require("@buoy-gg/license");
21
- var _jsxRuntime = require("react/jsx-runtime");
22
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
23
- // Note: StorageEvent type is now imported from useStorageEvents hook
24
- // which handles both AsyncStorage and MMKV events via the centralized store
25
-
26
- /**
27
- * @deprecated Read the live limit with `useLimit("storage.events")` — this only
28
- * reports the FREE tier's value and is blind to the anonymous tier. Kept
29
- * because it is part of the package's public exports.
30
- */
31
- const FREE_TIER_EVENT_LIMIT = exports.FREE_TIER_EVENT_LIMIT = _sharedUi.TIER_LIMITS["storage.events"].free;
32
- /**
33
- * Persistence recipes. These keys were always written straight to
34
- * AsyncStorage (not the FileSystem-first persistentStorage), so they keep
35
- * that backend — a user's saved tab, filters and pins survive the upgrade.
36
- */
37
- const ASYNC_STORAGE_BACKEND = {
38
- getItem: key => _asyncStorage.default.getItem(key),
39
- setItem: (key, value) => _asyncStorage.default.setItem(key, value)
40
- };
41
- const TAB_PERSISTENCE = {
42
- storage: ASYNC_STORAGE_BACKEND,
43
- // Stored as the bare tab name, not JSON.
44
- parse: raw => raw === "browser" || raw === "events" ? raw : undefined,
45
- serialize: tab => tab
46
- };
47
- const BOOLEAN_PERSISTENCE = {
48
- storage: ASYNC_STORAGE_BACKEND,
49
- // Stored as "true" / "false".
50
- parse: raw => raw === "true",
51
- serialize: value => value.toString()
52
- };
53
- const STRING_SET_PERSISTENCE = {
54
- storage: ASYNC_STORAGE_BACKEND,
55
- parse: raw => new Set(JSON.parse(raw)),
56
- serialize: set => JSON.stringify(Array.from(set))
57
- };
58
- const IGNORED_PATTERNS_PERSISTENCE = {
59
- ...STRING_SET_PERSISTENCE,
60
- parse: raw => {
61
- const filters = JSON.parse(raw);
62
- // Always ensure @react_buoy is included to hide dev tool keys
63
- if (!filters.includes("@react_buoy")) filters.push("@react_buoy");
64
- return new Set(filters);
65
- }
66
- };
67
- function StorageModalWithTabs({
68
- visible,
69
- onClose,
70
- onBack,
71
- onMinimize,
72
- enableSharedModalDimensions = false,
73
- requiredStorageKeys = [],
74
- inline = false,
75
- onTabChange,
76
- onRefresh,
77
- onEventSelect
78
- }) {
79
- const isPro = (0, _license.useIsPro)();
80
- const eventLimit = (0, _sharedUi.useLimit)("storage.events");
81
- const [activeTab, setActiveTab] = (0, _sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.activeTab(), "browser", TAB_PERSISTENCE);
82
- (0, _react.useEffect)(() => {
83
- onTabChange?.(activeTab);
84
- }, [activeTab, onTabChange]);
85
-
86
- // Storage Browser state
87
- const [searchQuery, setSearchQuery] = (0, _react.useState)("");
88
- /**
89
- * The key whose detail screen (live editor + info) is open. Lives at the
90
- * MODAL level because the modal's header becomes back + the key's name
91
- * while it's open — the React Query interaction, same as the Zustand tool.
92
- */
93
- const [detailKey, setDetailKey] = (0, _react.useState)(null);
94
- const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
95
-
96
- // Local state to control subscription (true = subscribed, false = unsubscribed)
97
- const [isListeningEnabled, setIsListeningEnabled] = (0, _sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.isMonitoring(), true, BOOLEAN_PERSISTENCE);
98
-
99
- // Event Listener state - using centralized store via hook
100
- // Pass enabled option to control subscription - this allows events tool to
101
- // continue receiving events while storage UI has toggled off
102
- const {
103
- events,
104
- clearEvents: clearStorageEvents,
105
- isCapturing
106
- } = (0, _useStorageEvents.useStorageEvents)({
107
- enabled: isListeningEnabled
108
- });
109
- const [selectedConversationKey, setSelectedConversationKey] = (0, _react.useState)(null);
110
- const [selectedHistoryKey, setSelectedHistoryKey] = (0, _react.useState)(null);
111
- const [selectedHistoryEventIndex, setSelectedHistoryEventIndex] = (0, _react.useState)(null);
112
- const [selectedEventIndex, setSelectedEventIndex] = (0, _react.useState)(0);
113
- const [showFilters, setShowFilters] = (0, _react.useState)(false);
114
- const [ignoredPatterns, setIgnoredPatterns] = (0, _sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.eventFilters(),
115
- // Auto-hide dev tool keys by default
116
- new Set(["@react_buoy", "@RNAsyncStorage", "redux-persist", "persist:"]), IGNORED_PATTERNS_PERSISTENCE);
117
- /**
118
- * Which backend both tabs are looking at.
119
- *
120
- * Owned here rather than inside the browser because the Keys and Events tabs
121
- * answer questions about the same three backends — filter the key list to
122
- * Keychain, switch to Events to see what wrote to it, and the filter should
123
- * still be Keychain.
124
- *
125
- * Distinct from `enabledStorageTypes` below: that's the persisted "capture
126
- * this backend at all" setting from the Filters view. This is a view filter,
127
- * and it never hides data the other one already dropped.
128
- */
129
- const [storageTypeFilter, setStorageTypeFilter] = (0, _react.useState)("all");
130
- const [enabledStorageTypes, setEnabledStorageTypes] = (0, _react.useState)(new Set(['async', 'mmkv', 'secure']) // All enabled by default
131
- );
132
- const [pinnedKeys, setPinnedKeys] = (0, _sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.pinnedKeys(), new Set(), STRING_SET_PERSISTENCE);
133
- const lastEventRef = (0, _react.useRef)(null);
134
-
135
- // isListening = enabled and capturing (has subscribers)
136
- const isListening = isListeningEnabled && isCapturing;
137
-
138
- // Track the modal's presentation mode so the stepper footer only reserves
139
- // safe-area bottom padding while docked as a bottom sheet. In floating mode
140
- // the footer isn't pinned to the screen edge, so that inset is dead space.
141
- const [modalMode, setModalMode] = (0, _react.useState)("bottomSheet");
142
- const handleModeChange = (0, _react.useCallback)(mode => {
143
- setModalMode(mode);
144
- }, []);
145
-
146
- // Timer removed - using useTickEveryMinute hook instead
147
-
148
- // Event listener setup - now handled by useStorageEvents hook
149
- // The hook subscribes to the centralized storageEventStore
150
-
151
- const handleToggleListening = (0, _react.useCallback)(() => {
152
- // Toggle subscription - this actually subscribes/unsubscribes from the store
153
- setIsListeningEnabled(prev => !prev);
154
- }, []);
155
- const handleClearEvents = (0, _react.useCallback)(() => {
156
- clearStorageEvents();
157
- setSelectedConversationKey(null);
158
- setSelectedEventIndex(0);
159
- }, [clearStorageEvents]);
160
- const handleViewHistoryFromBrowser = (0, _react.useCallback)(key => {
161
- setSelectedHistoryKey(key);
162
- setSelectedHistoryEventIndex(null);
163
- }, []);
164
- const handleBackFromHistory = (0, _react.useCallback)(() => {
165
- setSelectedHistoryKey(null);
166
- setSelectedHistoryEventIndex(null);
167
- }, []);
168
- const handleTogglePattern = (0, _react.useCallback)(pattern => {
169
- setIgnoredPatterns(prev => {
170
- const next = new Set(prev);
171
- if (next.has(pattern)) {
172
- next.delete(pattern);
173
- } else {
174
- next.add(pattern);
175
- }
176
- return next;
177
- });
178
- }, []);
179
- const handleAddPattern = (0, _react.useCallback)(pattern => {
180
- setIgnoredPatterns(prev => new Set([...prev, pattern]));
181
- }, []);
182
- const handleTogglePin = (0, _react.useCallback)(key => {
183
- setPinnedKeys(prev => {
184
- const next = new Set(prev);
185
- if (next.has(key)) {
186
- next.delete(key);
187
- } else {
188
- next.add(key);
189
- }
190
- return next;
191
- });
192
- }, []);
193
- const handleToggleFilters = (0, _react.useCallback)(() => {
194
- setShowFilters(!showFilters);
195
- }, [showFilters]);
196
- const handleToggleStorageType = (0, _react.useCallback)(type => {
197
- setEnabledStorageTypes(prev => {
198
- const next = new Set(prev);
199
- if (next.has(type)) {
200
- next.delete(type);
201
- } else {
202
- next.add(type);
203
- }
204
- return next;
205
- });
206
- }, []);
207
-
208
- // Storage Browser handlers
209
- const storageDataRef = (0, _react.useRef)([]);
210
- const getStorageSnapshot = (0, _react.useCallback)(() => {
211
- const keys = storageDataRef.current;
212
- return {
213
- timestamp: new Date().toISOString(),
214
- totalKeys: keys.length,
215
- asyncStorage: keys.filter(k => k.storageType === 'async').reduce((acc, k) => {
216
- acc[k.key] = (0, _sharedUi.truncatePayload)(k.value);
217
- return acc;
218
- }, {}),
219
- mmkv: keys.filter(k => k.storageType === 'mmkv').reduce((acc, k) => {
220
- const id = k.instanceId || 'default';
221
- if (!acc[id]) acc[id] = {};
222
- acc[id][k.key] = (0, _sharedUi.truncatePayload)(k.value);
223
- return acc;
224
- }, {}),
225
- secure: keys.filter(k => k.storageType === 'secure').reduce((acc, k) => {
226
- acc[k.key] = (0, _sharedUi.truncatePayload)(k.value);
227
- return acc;
228
- }, {})
229
- };
230
- }, []);
231
- const handlePurgeStorage = (0, _react.useCallback)(async () => {
232
- _reactNative.Alert.alert("Clear Storage", "Choose what to clear:", [{
233
- text: "Cancel",
234
- style: "cancel"
235
- }, {
236
- text: "Clear App Storage",
237
- onPress: async () => {
238
- try {
239
- await (0, _clearAllStorage.clearAllAppStorage)();
240
- // Refresh will be handled by GameUIStorageBrowser
241
- } catch (error) {
242
- _reactNative.Alert.alert("Error", "Failed to clear app storage");
243
- }
244
- }
245
- }, {
246
- text: "Clear All (Including Dev Tools)",
247
- style: "destructive",
248
- onPress: async () => {
249
- try {
250
- await (0, _clearAllStorage.clearAllStorageIncludingDevTools)();
251
- // Refresh will be handled by GameUIStorageBrowser
252
- } catch (error) {
253
- _reactNative.Alert.alert("Error", "Failed to clear all storage");
254
- }
255
- }
256
- }]);
257
- }, []);
258
- const getValueType = value => {
259
- const parsed = (0, _sharedUi.parseValue)(value);
260
- if (parsed === null) return "null";
261
- if (parsed === undefined) return "undefined";
262
- if (Array.isArray(parsed)) return "array";
263
- if (typeof parsed === "boolean") return "boolean";
264
- if (typeof parsed === "number") return "number";
265
- if (typeof parsed === "string") return "string";
266
- if (typeof parsed === "object") return "object";
267
- return "undefined";
268
- };
269
-
270
- // Get all unique keys from events AND from AsyncStorage for filter view
271
- const [allStorageKeys, setAllStorageKeys] = (0, _react.useState)([]);
272
- (0, _react.useEffect)(() => {
273
- const fetchAllKeys = async () => {
274
- try {
275
- const keys = await _asyncStorage.default.getAllKeys();
276
- setAllStorageKeys([...keys].sort());
277
- } catch (error) {
278
- // Failed to fetch keys
279
- }
280
- };
281
-
282
- // Fetch all keys for both tabs when visible
283
- if (visible) {
284
- fetchAllKeys();
285
- }
286
- }, [visible]);
287
- const allEventKeys = (0, _react.useMemo)(() => {
288
- // Combine storage keys with event keys for comprehensive filter view
289
- const keys = new Set(allStorageKeys);
290
- events.forEach(event => {
291
- if (event.data?.key) {
292
- keys.add(event.data.key);
293
- }
294
- });
295
- return Array.from(keys).sort();
296
- }, [events, allStorageKeys]);
297
-
298
- // Group events by key and create conversations
299
- const conversations = (0, _react.useMemo)(() => {
300
- const keyMap = new Map();
301
- events.forEach(event => {
302
- if (!event.data?.key) return;
303
- const key = event.data.key;
304
-
305
- // Filter by enabled storage types
306
- if (!enabledStorageTypes.has(event.storageType)) return;
307
-
308
- // Filter out keys that match ignored patterns
309
- const shouldIgnore = Array.from(ignoredPatterns).some(pattern => key.includes(pattern));
310
- if (shouldIgnore) return;
311
- const existing = keyMap.get(key);
312
- if (!existing) {
313
- keyMap.set(key, {
314
- key,
315
- lastEvent: event,
316
- events: [event],
317
- totalOperations: 1,
318
- currentValue: event.data.value,
319
- valueType: getValueType(event.data.value),
320
- storageTypes: new Set([event.storageType])
321
- });
322
- } else {
323
- existing.events.push(event);
324
- existing.totalOperations++;
325
- existing.storageTypes.add(event.storageType);
326
-
327
- // Update last event if this one is newer
328
- if (event.timestamp > existing.lastEvent.timestamp) {
329
- existing.lastEvent = event;
330
- existing.currentValue = event.data.value;
331
- existing.valueType = getValueType(event.data.value);
332
- }
333
- }
334
- });
335
-
336
- // Convert to array and sort by last updated
337
- return Array.from(keyMap.values()).sort((a, b) => b.lastEvent.timestamp.getTime() - a.lastEvent.timestamp.getTime());
338
- }, [events, ignoredPatterns, enabledStorageTypes]);
339
-
340
- // Get the live selected conversation from the current conversations array
341
- const selectedConversation = (0, _react.useMemo)(() => {
342
- if (!selectedConversationKey) return null;
343
- return conversations.find(c => c.key === selectedConversationKey) || null;
344
- }, [selectedConversationKey, conversations]);
345
-
346
- // Event count per key for storage browser badges
347
- const eventCountByKey = (0, _react.useMemo)(() => {
348
- const counts = {};
349
- conversations.forEach(conv => {
350
- counts[conv.key] = conv.totalOperations;
351
- });
352
- return counts;
353
- }, [conversations]);
354
-
355
- /**
356
- * When each key was last written, for the wide table's "Last write" column.
357
- * Derived from the recorded event stream, NOT from the key read: a storage
358
- * read has no timestamp of its own, so a key we never witnessed being written
359
- * has no honest answer here and is simply absent.
360
- */
361
- const lastWriteByKey = (0, _react.useMemo)(() => {
362
- const at = {};
363
- conversations.forEach(conv => {
364
- at[conv.key] = conv.lastEvent.timestamp.getTime();
365
- });
366
- return at;
367
- }, [conversations]);
368
-
369
- // Selected history conversation (from browser tab "view history")
370
- const selectedHistoryConversation = (0, _react.useMemo)(() => {
371
- if (!selectedHistoryKey) return null;
372
- return conversations.find(c => c.key === selectedHistoryKey) || null;
373
- }, [selectedHistoryKey, conversations]);
374
-
375
- // Wire-form snapshots omit oversized values. Tell a remote host which
376
- // event(s) are on screen so it can fetch them — selected + previous, because
377
- // the detail pane diffs those two.
378
- (0, _react.useEffect)(() => {
379
- if (!onEventSelect) return;
380
- const conversation = selectedConversation ? selectedConversation : selectedHistoryEventIndex !== null ? selectedHistoryConversation : null;
381
- const index = selectedConversation ? selectedEventIndex : selectedHistoryEventIndex;
382
- if (!conversation || index == null) return;
383
- const sorted = [...conversation.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
384
- const selected = sorted[index];
385
- const previous = sorted[index - 1];
386
- if (selected) onEventSelect(selected);
387
- if (previous) onEventSelect(previous);
388
- }, [onEventSelect, selectedConversation, selectedEventIndex, selectedHistoryConversation, selectedHistoryEventIndex]);
389
-
390
- /**
391
- * Events passing everything EXCEPT the backend pills.
392
- *
393
- * Split out so the pills can show what each one would reveal. Counting the
394
- * already-filtered list would zero out the three types you didn't pick, which
395
- * tells you nothing about where to look next.
396
- */
397
- const eventsBeforeTypeFilter = (0, _react.useMemo)(() => {
398
- return events.filter(event => {
399
- if (!event.data?.key) return false;
400
- if (!enabledStorageTypes.has(event.storageType)) return false;
401
- const key = event.data.key;
402
- return !Array.from(ignoredPatterns).some(p => key.includes(p));
403
- });
404
- }, [events, ignoredPatterns, enabledStorageTypes]);
405
- const eventTypeCounts = (0, _react.useMemo)(() => (0, _StorageTypeFilterBar.countByStorageType)(eventsBeforeTypeFilter), [eventsBeforeTypeFilter]);
406
-
407
- // Filtered raw events for the events tab (chronological stream, newest-first)
408
- const filteredEvents = (0, _react.useMemo)(() => {
409
- if (storageTypeFilter === "all") return eventsBeforeTypeFilter;
410
- return eventsBeforeTypeFilter.filter(event => event.storageType === storageTypeFilter);
411
- }, [eventsBeforeTypeFilter, storageTypeFilter]);
412
- const getEventsSnapshot = (0, _react.useCallback)(() => {
413
- return filteredEvents.map(event => ({
414
- key: event.data?.key,
415
- action: event.action,
416
- value: (0, _sharedUi.truncatePayload)(event.data?.value),
417
- storageType: event.storageType,
418
- timestamp: event.timestamp.toISOString()
419
- }));
420
- }, [filteredEvents]);
421
-
422
- // Pro is Infinity here, so slice() returns everything and the locked count
423
- // floors at 0 — no tier branching needed at the call site.
424
- const visibleEvents = (0, _react.useMemo)(() => filteredEvents.slice(0, eventLimit), [filteredEvents, eventLimit]);
425
- const lockedEventCount = (0, _react.useMemo)(() => Math.max(0, filteredEvents.length - eventLimit), [filteredEvents.length, eventLimit]);
426
-
427
- // FlatList optimization constants
428
- const END_REACHED_THRESHOLD = 0.8;
429
-
430
- // keyExtractor for raw event FlatList
431
- const eventKeyExtractor = (0, _react.useCallback)(item => item.id, []);
432
-
433
- // When a raw event card is pressed, open that key's conversation detail at the matching event
434
- const handleRawEventPress = (0, _react.useCallback)(item => {
435
- const conv = conversations.find(c => c.key === item.data?.key);
436
- if (!conv) return;
437
- const sortedEvents = [...conv.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
438
- const eventIndex = sortedEvents.findIndex(e => e.timestamp.getTime() === item.timestamp.getTime());
439
- setSelectedConversationKey(conv.key);
440
- setSelectedEventIndex(eventIndex >= 0 ? eventIndex : 0);
441
- }, [conversations]);
442
-
443
- // Render a single raw event card
444
- const renderEventItem = (0, _react.useCallback)(({
445
- item
446
- }) => {
447
- const cardData = {
448
- key: item.data?.key || "",
449
- lastAction: item.action,
450
- totalOperations: 1,
451
- lastEventTimestamp: item.timestamp,
452
- storageTypes: new Set([item.storageType]),
453
- valueType: getValueType(item.data?.value)
454
- };
455
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventCard.StorageEventCard, {
456
- data: cardData,
457
- onPress: () => handleRawEventPress(item),
458
- testID: `buoy-storage-event-${item.id}`
459
- });
460
- }, [handleRawEventPress]);
461
- if (!visible) return null;
462
- const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : _sharedUi.devToolsStorageKeys.storage.modal();
463
- const renderContent = () => {
464
- // Filters overlay — applies to both tabs
465
- if (showFilters) {
466
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventFilterView.StorageEventFilterView, {
467
- ignoredPatterns: ignoredPatterns,
468
- onTogglePattern: handleTogglePattern,
469
- onAddPattern: handleAddPattern,
470
- availableKeys: allEventKeys,
471
- enabledStorageTypes: enabledStorageTypes,
472
- onToggleStorageType: handleToggleStorageType
473
- });
474
- }
475
- if (activeTab === "browser") {
476
- // History flow for a specific key (from "view history" in browser tab)
477
- if (selectedHistoryKey && selectedHistoryConversation) {
478
- // Level 2: detail view for a specific event
479
- if (selectedHistoryEventIndex !== null) {
480
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
481
- style: styles.contentWrapper,
482
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailContent, {
483
- conversation: selectedHistoryConversation,
484
- selectedEventIndex: selectedHistoryEventIndex,
485
- onEventIndexChange: setSelectedHistoryEventIndex,
486
- disableInternalFooter: true
487
- })
488
- });
489
- }
490
-
491
- // Level 1: event list for this key (newest-first)
492
- const ascSortedEvents = [...selectedHistoryConversation.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
493
- const newestFirstEvents = [...ascSortedEvents].reverse();
494
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
495
- testID: "buoy-storage-history-list",
496
- style: styles.flexFill,
497
- data: newestFirstEvents,
498
- keyExtractor: item => item.id,
499
- renderItem: ({
500
- item
501
- }) => {
502
- const ascIdx = ascSortedEvents.findIndex(e => e.timestamp.getTime() === item.timestamp.getTime());
503
- const cardData = {
504
- key: item.data?.key || "",
505
- lastAction: item.action,
506
- totalOperations: 1,
507
- lastEventTimestamp: item.timestamp,
508
- storageTypes: new Set([item.storageType]),
509
- valueType: getValueType(item.data?.value)
510
- };
511
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventCard.StorageEventCard, {
512
- data: cardData,
513
- onPress: () => setSelectedHistoryEventIndex(ascIdx >= 0 ? ascIdx : 0),
514
- testID: `buoy-storage-history-event-${item.id}`
515
- });
516
- },
517
- contentContainerStyle: styles.listContent,
518
- ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
519
- style: styles.separator
520
- }),
521
- initialNumToRender: 20
522
- });
523
- }
524
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageBrowserMode.StorageBrowserMode, {
525
- requiredStorageKeys: requiredStorageKeys,
526
- ignoredPatterns: ignoredPatterns,
527
- onTogglePattern: handleTogglePattern,
528
- onAddPattern: handleAddPattern,
529
- searchQuery: searchQuery,
530
- storageDataRef: storageDataRef,
531
- eventCountByKey: eventCountByKey,
532
- lastWriteByKey: lastWriteByKey,
533
- onViewHistory: handleViewHistoryFromBrowser,
534
- enabledStorageTypes: enabledStorageTypes,
535
- pinnedKeys: pinnedKeys,
536
- onTogglePin: handleTogglePin
537
- // Only a docked sheet sits on the screen's bottom edge; a floating
538
- // window's own bottom is well above the home indicator.
539
- ,
540
- applySafeAreaInset: modalMode === "bottomSheet",
541
- storageTypeFilter: storageTypeFilter,
542
- onStorageTypeFilterChange: setStorageTypeFilter,
543
- detailKey: detailKey,
544
- onOpenKey: setDetailKey,
545
- onCloseDetail: () => setDetailKey(null)
546
- });
547
- }
548
-
549
- // Events tab content
550
- if (selectedConversation) {
551
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
552
- style: styles.contentWrapper,
553
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailContent, {
554
- conversation: selectedConversation,
555
- selectedEventIndex: selectedEventIndex,
556
- onEventIndexChange: setSelectedEventIndex,
557
- disableInternalFooter: true
558
- })
559
- });
560
- }
561
-
562
- // The pills render ABOVE the empty state as well as the list. Filtering to
563
- // a backend that happens to have no events would otherwise hide the only
564
- // control that undoes it, leaving the tab looking permanently broken.
565
- const eventTypeFilterBar = /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
566
- style: styles.eventFilterBar,
567
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageTypeFilterBar.StorageTypeFilterBar, {
568
- active: storageTypeFilter,
569
- onChange: setStorageTypeFilter,
570
- counts: eventTypeCounts
571
- })
572
- });
573
- if (filteredEvents.length === 0) {
574
- const filtered = storageTypeFilter !== "all";
575
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
576
- style: styles.flexFill,
577
- children: [eventTypeFilterBar, /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
578
- testID: "buoy-storage-events-empty"
579
- // Three literal causes, no search on this tab — so it renders
580
- // through the shared shell without the shared resolver. The
581
- // backend pills need their own sentence naming the pill that
582
- // undoes them, which the generic "Filtered Out" would drop.
583
- ,
584
- ...(!isListening ? {
585
- otto: "paused",
586
- title: "Event listener is paused",
587
- message: "Press play to start monitoring"
588
- } : filtered ? {
589
- otto: "filtering",
590
- title: `No ${storageTypeFilter} events`,
591
- message: "Other backends have events — pick All Types to see them"
592
- } : {
593
- otto: "listening",
594
- title: "No storage events yet",
595
- message: "Storage operations will appear here"
596
- })
597
- })]
598
- });
599
- }
600
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
601
- style: styles.flexFill,
602
- children: [eventTypeFilterBar, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
603
- testID: "buoy-storage-events-list",
604
- style: styles.flexFill,
605
- data: visibleEvents,
606
- renderItem: renderEventItem,
607
- keyExtractor: eventKeyExtractor,
608
- onEndReachedThreshold: END_REACHED_THRESHOLD,
609
- contentContainerStyle: styles.listContent,
610
- ListFooterComponent: lockedEventCount > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
611
- style: styles.lockedBannerContainer,
612
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProFeatureBanner, {
613
- featureName: `${lockedEventCount} event${lockedEventCount > 1 ? 's' : ''} locked`,
614
- description: "Upgrade to Pro to unlock full event history"
615
- })
616
- }) : null,
617
- initialNumToRender: 10,
618
- maxToRenderPerBatch: 10,
619
- windowSize: 10
620
- })]
621
- });
622
- };
623
-
624
- // The stepper renders nothing for a single-event conversation
625
- // (EventStepperFooter bails at totalItems <= 1), but an element is truthy
626
- // either way — so `footerHeight={footerNode ? 68 : 0}` below used to reserve
627
- // 68px for a footer that drew nothing, leaving a dead band under the content.
628
- // Gate the node on the event count so the reserve matches what's painted.
629
- const footerNode = selectedConversation && selectedConversation.events.length > 1 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter, {
630
- conversation: selectedConversation,
631
- selectedEventIndex: selectedEventIndex,
632
- onEventIndexChange: setSelectedEventIndex,
633
- applySafeAreaInset: modalMode === "bottomSheet"
634
- }) : selectedHistoryConversation && selectedHistoryEventIndex !== null && selectedHistoryConversation.events.length > 1 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter, {
635
- conversation: selectedHistoryConversation,
636
- selectedEventIndex: selectedHistoryEventIndex,
637
- onEventIndexChange: setSelectedHistoryEventIndex,
638
- applySafeAreaInset: modalMode === "bottomSheet"
639
- }) : null;
640
-
641
- // Determine the appropriate back handler based on current view state
642
- const currentBackHandler = showFilters ? () => setShowFilters(false) : selectedHistoryEventIndex !== null ? () => setSelectedHistoryEventIndex(null) : selectedHistoryKey ? handleBackFromHistory : selectedConversation ? () => {
643
- setSelectedConversationKey(null);
644
- setSelectedEventIndex(0);
645
- } : onBack;
646
- const headerContent = showFilters ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
647
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
648
- onBack: () => setShowFilters(false)
649
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
650
- title: "Filters"
651
- })]
652
- }) : selectedHistoryKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
653
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
654
- onBack: selectedHistoryEventIndex !== null ? () => setSelectedHistoryEventIndex(null) : handleBackFromHistory
655
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
656
- title: `${selectedHistoryKey} History`,
657
- centered: true
658
- })]
659
- }) : selectedConversation ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
660
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
661
- onBack: () => {
662
- setSelectedConversationKey(null);
663
- setSelectedEventIndex(0);
664
- }
665
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
666
- title: selectedConversation.key
667
- })]
668
- }) : detailKey ?
669
- /*#__PURE__*/
670
- // The key detail screen — back + the key's name, the React Query
671
- // interaction. AFTER history in this chain: history is reached FROM
672
- // the detail and stacks over it, so its back returns here.
673
- (0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
674
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
675
- onBack: () => setDetailKey(null)
676
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
677
- title: detailKey.key,
678
- centered: true,
679
- testID: "buoy-storage-detail-title"
680
- })]
681
- }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
682
- children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
683
- onBack: onBack
684
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
685
- title: "",
686
- children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SearchBar, {
687
- value: searchQuery,
688
- onChange: setSearchQuery,
689
- onClear: () => {
690
- setSearchQuery("");
691
- setIsSearchActive(false);
692
- },
693
- placeholder: "Search storage keys...",
694
- testID: "buoy-storage-search",
695
- autoFocus: true,
696
- onSubmitEditing: () => setIsSearchActive(false),
697
- returnKeyType: "search",
698
- containerStyle: styles.headerSearchContainer
699
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
700
- tabs: [{
701
- key: "browser",
702
- label: "Storage"
703
- }, {
704
- key: "events",
705
- label: `Events${filteredEvents.length > 0 ? ` (${filteredEvents.length})` : ""}`
706
- }],
707
- activeTab: activeTab,
708
- testIDPrefix: "buoy-storage-tab",
709
- onTabChange: tab => {
710
- setActiveTab(tab);
711
- setSelectedHistoryKey(null);
712
- setSelectedHistoryEventIndex(null);
713
- setSelectedConversationKey(null);
714
- setSelectedEventIndex(0);
715
- setDetailKey(null);
716
- }
717
- })
718
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
719
- children: [onRefresh && !isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
720
- onPress: onRefresh,
721
- style: styles.iconButton,
722
- testID: "buoy-storage-refresh",
723
- accessibilityLabel: "Refresh storage",
724
- accessibilityRole: "button",
725
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
726
- size: 14,
727
- color: _sharedUi.night.color.textSecondary
728
- })
729
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
730
- onPress: handleToggleFilters,
731
- testID: "buoy-storage-filters",
732
- accessibilityLabel: "Storage filters",
733
- accessibilityRole: "button",
734
- style: [styles.iconButton, (ignoredPatterns.size > 0 || enabledStorageTypes.size < 3) && styles.activeFilterButton],
735
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
736
- size: 14,
737
- color: ignoredPatterns.size > 0 || enabledStorageTypes.size < 3 ? _sharedUi.night.color.accent : _sharedUi.night.color.textSecondary
738
- })
739
- }), activeTab === "browser" && !isSearchActive && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
740
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
741
- onPress: () => setIsSearchActive(true),
742
- testID: "buoy-storage-search-open",
743
- accessibilityLabel: "Open search",
744
- accessibilityRole: "button",
745
- style: styles.iconButton,
746
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
747
- size: 14,
748
- color: _sharedUi.night.color.textSecondary
749
- })
750
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
751
- value: getStorageSnapshot,
752
- disabled: storageDataRef.current.length === 0,
753
- buttonStyle: styles.iconButton,
754
- testID: "buoy-storage-copy"
755
- })]
756
- }), activeTab === "events" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
757
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
758
- value: getEventsSnapshot,
759
- disabled: filteredEvents.length === 0,
760
- buttonStyle: styles.iconButton,
761
- testID: "buoy-storage-events-copy"
762
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
763
- isEnabled: isListening,
764
- onToggle: handleToggleListening,
765
- accessibilityLabel: "Toggle storage event monitoring",
766
- testID: "buoy-storage-power"
767
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarClearButton, {
768
- onPress: handleClearEvents,
769
- buttonStyle: styles.iconButton,
770
- testID: "buoy-storage-clear-events"
771
- })]
772
- })]
773
- })]
774
- });
775
-
776
- // The ONLY difference between the phone and the desktop dashboard is which
777
- // shell the header, body and footer go in. Everything above this is shared.
778
- if (inline) {
779
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TickProvider, {
780
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
781
- style: styles.inlineRoot,
782
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
783
- style: styles.inlineHeader,
784
- ..._sharedUi.windowDragProps,
785
- children: headerContent
786
- }), renderContent(), footerNode]
787
- })
788
- });
789
- }
790
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TickProvider, {
791
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.JsModal, {
792
- visible: visible,
793
- onClose: onClose,
794
- onBack: currentBackHandler,
795
- onMinimize: onMinimize,
796
- persistenceKey: persistenceKey,
797
- header: {
798
- showToggleButton: true,
799
- customContent: headerContent
800
- },
801
- onModeChange: handleModeChange,
802
- enablePersistence: true,
803
- initialMode: "bottomSheet",
804
- enableGlitchEffects: true,
805
- styles: {},
806
- footer: footerNode,
807
- footerHeight: footerNode ? 68 : 0,
808
- disableScrollWrapper: true,
809
- children: renderContent()
810
- })
811
- });
812
- }
813
-
814
- /**
815
- * The same storage tool as `StorageModalWithTabs`, rendered INLINE for hosts
816
- * that place tools in a panel rather than a modal. Exists so there is exactly
817
- * one implementation — the desktop dashboard used to ship its own copy of this
818
- * component's header, tabs and screen router.
819
- */
820
- function StorageToolView(props = {}) {
821
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(StorageModalWithTabs, {
822
- ...props,
823
- inline: true,
824
- visible: true,
825
- onClose: () => {}
826
- });
827
- }
828
- const styles = _reactNative.StyleSheet.create({
829
- inlineRoot: {
830
- flex: 1,
831
- minHeight: 0,
832
- // Transparent so the desktop workbench's hoisted sky (one field spanning
833
- // sidebar + panel) shows through — the shell paints its base beneath it.
834
- backgroundColor: "transparent"
835
- },
836
- inlineHeader: {
837
- paddingHorizontal: 12,
838
- paddingVertical: 8,
839
- borderBottomWidth: 1,
840
- borderBottomColor: _sharedUi.macOSColors.border.default,
841
- // Clear, like Bench — the host's hoisted sky shows through the header.
842
- backgroundColor: "transparent"
843
- },
844
- headerSearchContainer: {
845
- flex: 1
846
- },
847
- flexFill: {
848
- flex: 1
849
- },
850
- // Night chrome — the header is glass over the star field, so the action
851
- // chips sit on the Everlights dark-glass fill.
852
- iconButton: {
853
- width: 32,
854
- height: 32,
855
- borderRadius: 8,
856
- backgroundColor: _sharedUi.night.color.buttonSurface,
857
- borderWidth: 1,
858
- borderColor: _sharedUi.night.color.border,
859
- alignItems: "center",
860
- justifyContent: "center"
861
- },
862
- activeFilterButton: {
863
- backgroundColor: _sharedUi.night.color.accentSoft,
864
- borderColor: _sharedUi.night.color.accentBorderStrong
865
- },
866
- separator: {
867
- height: 8
868
- },
869
- listContent: {
870
- paddingVertical: 6
871
- },
872
- lockedBannerContainer: {
873
- marginTop: 8,
874
- marginHorizontal: 16
875
- },
876
- /** Gutter matching the event cards below it, so the pills line up with them. */
877
- eventFilterBar: {
878
- paddingHorizontal: 12,
879
- paddingTop: 8,
880
- paddingBottom: 4
881
- },
882
- emptyState: {
883
- flex: 1,
884
- justifyContent: "center",
885
- alignItems: "center",
886
- padding: 32
887
- },
888
- emptyTitle: {
889
- color: _sharedUi.night.color.text,
890
- fontSize: 16,
891
- fontWeight: "600",
892
- marginTop: 16,
893
- marginBottom: 8,
894
- fontFamily: _sharedUi.monoFont,
895
- letterSpacing: 0.5,
896
- textTransform: "uppercase"
897
- },
898
- emptySubtitle: {
899
- color: _sharedUi.night.color.textSecondary,
900
- fontSize: 14,
901
- textAlign: "center",
902
- fontFamily: _sharedUi.monoFont
903
- },
904
- eventNavigation: {
905
- flexDirection: "row",
906
- alignItems: "center",
907
- gap: 8,
908
- paddingHorizontal: 8
909
- },
910
- navButton: {
911
- padding: 4,
912
- borderRadius: 4
913
- },
914
- navButtonDisabled: {
915
- opacity: 0.3
916
- },
917
- eventCounter: {
918
- color: _sharedUi.night.color.text,
919
- fontSize: 12,
920
- fontWeight: "600",
921
- fontFamily: _sharedUi.monoFont,
922
- paddingHorizontal: 8
923
- },
924
- headerTopRow: {
925
- flexDirection: "row",
926
- alignItems: "center",
927
- gap: 8,
928
- marginBottom: 12
929
- },
930
- keyNameContainer: {
931
- flex: 1,
932
- flexDirection: "row",
933
- alignItems: "center",
934
- gap: 6,
935
- backgroundColor: _sharedUi.night.color.surfaceElevated,
936
- paddingHorizontal: 10,
937
- paddingVertical: 4,
938
- borderRadius: 6,
939
- borderWidth: 1,
940
- borderColor: _sharedUi.night.color.border,
941
- height: 28
942
- },
943
- keyNameText: {
944
- flex: 1,
945
- fontSize: 13,
946
- fontWeight: "600",
947
- color: _sharedUi.night.color.accent,
948
- fontFamily: _sharedUi.monoFont,
949
- letterSpacing: 0.5
950
- },
951
- contentWrapper: {
952
- flex: 1
953
- }
954
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.FREE_TIER_EVENT_LIMIT=void 0;exports.StorageModalWithTabs=StorageModalWithTabs;exports.StorageToolView=StorageToolView;var _react=require("react");var _reactNative=require("react-native");var _asyncStorage=_interopRequireDefault(require("@react-native-async-storage/async-storage"));var _sharedUi=require("@buoy-gg/shared-ui");var _StorageBrowserMode=require("./StorageBrowserMode");var _StorageTypeFilterBar=require("./StorageTypeFilterBar");var _clearAllStorage=require("../utils/clearAllStorage");var _useStorageEvents=require("../hooks/useStorageEvents");var _StorageEventDetailContent=require("./StorageEventDetailContent");var _StorageEventFilterView=require("./StorageEventFilterView");var _StorageEventCard=require("./StorageEventCard");var _license=require("@buoy-gg/license");var _jsxRuntime=require("react/jsx-runtime");function _interopRequireDefault(o){return o&&o.__esModule?o:{default:o}}const FREE_TIER_EVENT_LIMIT=exports.FREE_TIER_EVENT_LIMIT=_sharedUi.TIER_LIMITS["storage.events"].free;const ASYNC_STORAGE_BACKEND={getItem:o=>_asyncStorage.default.getItem(o),setItem:(o,p)=>_asyncStorage.default.setItem(o,p)};const TAB_PERSISTENCE={storage:ASYNC_STORAGE_BACKEND,parse:o=>o==="browser"||o==="events"?o:void 0,serialize:o=>o};const BOOLEAN_PERSISTENCE={storage:ASYNC_STORAGE_BACKEND,parse:o=>o==="true",serialize:o=>o.toString()};const STRING_SET_PERSISTENCE={storage:ASYNC_STORAGE_BACKEND,parse:o=>new Set(JSON.parse(o)),serialize:o=>JSON.stringify(Array.from(o))};const IGNORED_PATTERNS_PERSISTENCE={...STRING_SET_PERSISTENCE,parse:o=>{const p=JSON.parse(o);if(!p.includes("@react_buoy"))p.push("@react_buoy");return new Set(p)}};function StorageModalWithTabs({visible:o,onClose:p,onBack:F,onMinimize:oe,enableSharedModalDimensions:ne=false,requiredStorageKeys:ae=[],inline:se=false,onTabChange:O,onRefresh:z,onEventSelect:C}){const De=(0,_license.useIsPro)();const I=(0,_sharedUi.useLimit)("storage.events");const[m,le]=(0,_sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.activeTab(),"browser",TAB_PERSISTENCE);(0,_react.useEffect)(()=>{O?.(m)},[m,O]);const[L,q]=(0,_react.useState)("");const[B,k]=(0,_react.useState)(null);const[M,P]=(0,_react.useState)(false);const[W,ie]=(0,_sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.isMonitoring(),true,BOOLEAN_PERSISTENCE);const{events:T,clearEvents:$,isCapturing:ce}=(0,_useStorageEvents.useStorageEvents)({enabled:W});const[A,b]=(0,_react.useState)(null);const[S,D]=(0,_react.useState)(null);const[c,d]=(0,_react.useState)(null);const[j,h]=(0,_react.useState)(0);const[x,_]=(0,_react.useState)(false);const[u,G]=(0,_sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.eventFilters(),new Set(["@react_buoy","@RNAsyncStorage","redux-persist","persist:"]),IGNORED_PATTERNS_PERSISTENCE);const[f,J]=(0,_react.useState)("all");const[g,de]=(0,_react.useState)(new Set(["async","mmkv","secure"]));const[ue,ge]=(0,_sharedUi.usePersistedState)(_sharedUi.devToolsStorageKeys.storage.pinnedKeys(),new Set,STRING_SET_PERSISTENCE);const _e=(0,_react.useRef)(null);const Q=W&&ce;const[R,ye]=(0,_react.useState)("bottomSheet");const pe=(0,_react.useCallback)(e=>{ye(e)},[]);const Se=(0,_react.useCallback)(()=>{ie(e=>!e)},[]);const he=(0,_react.useCallback)(()=>{$();b(null);h(0)},[$]);const fe=(0,_react.useCallback)(e=>{D(e);d(null)},[]);const U=(0,_react.useCallback)(()=>{D(null);d(null)},[]);const Y=(0,_react.useCallback)(e=>{G(t=>{const r=new Set(t);if(r.has(e)){r.delete(e)}else{r.add(e)}return r})},[]);const X=(0,_react.useCallback)(e=>{G(t=>new Set([...t,e]))},[]);const ve=(0,_react.useCallback)(e=>{ge(t=>{const r=new Set(t);if(r.has(e)){r.delete(e)}else{r.add(e)}return r})},[]);const me=(0,_react.useCallback)(()=>{_(!x)},[x]);const Te=(0,_react.useCallback)(e=>{de(t=>{const r=new Set(t);if(r.has(e)){r.delete(e)}else{r.add(e)}return r})},[]);const H=(0,_react.useRef)([]);const be=(0,_react.useCallback)(()=>{const e=H.current;return{timestamp:new Date().toISOString(),totalKeys:e.length,asyncStorage:e.filter(t=>t.storageType==="async").reduce((t,r)=>{t[r.key]=(0,_sharedUi.truncatePayload)(r.value);return t},{}),mmkv:e.filter(t=>t.storageType==="mmkv").reduce((t,r)=>{const n=r.instanceId||"default";if(!t[n])t[n]={};t[n][r.key]=(0,_sharedUi.truncatePayload)(r.value);return t},{}),secure:e.filter(t=>t.storageType==="secure").reduce((t,r)=>{t[r.key]=(0,_sharedUi.truncatePayload)(r.value);return t},{})}},[]);const Re=(0,_react.useCallback)(async()=>{_reactNative.Alert.alert("Clear Storage","Choose what to clear:",[{text:"Cancel",style:"cancel"},{text:"Clear App Storage",onPress:async()=>{try{await(0,_clearAllStorage.clearAllAppStorage)()}catch(e){_reactNative.Alert.alert("Error","Failed to clear app storage")}}},{text:"Clear All (Including Dev Tools)",style:"destructive",onPress:async()=>{try{await(0,_clearAllStorage.clearAllStorageIncludingDevTools)()}catch(e){_reactNative.Alert.alert("Error","Failed to clear all storage")}}}])},[]);const w=e=>{const t=(0,_sharedUi.parseValue)(e);if(t===null)return"null";if(t===void 0)return"undefined";if(Array.isArray(t))return"array";if(typeof t==="boolean")return"boolean";if(typeof t==="number")return"number";if(typeof t==="string")return"string";if(typeof t==="object")return"object";return"undefined"};const[Z,xe]=(0,_react.useState)([]);(0,_react.useEffect)(()=>{const e=async()=>{try{const t=await _asyncStorage.default.getAllKeys();xe([...t].sort())}catch(t){}};if(o){e()}},[o]);const Ee=(0,_react.useMemo)(()=>{const e=new Set(Z);T.forEach(t=>{if(t.data?.key){e.add(t.data.key)}});return Array.from(e).sort()},[T,Z]);const l=(0,_react.useMemo)(()=>{const e=new Map;T.forEach(t=>{if(!t.data?.key)return;const r=t.data.key;if(!g.has(t.storageType))return;const n=Array.from(u).some(v=>r.includes(v));if(n)return;const a=e.get(r);if(!a){e.set(r,{key:r,lastEvent:t,events:[t],totalOperations:1,currentValue:t.data.value,valueType:w(t.data.value),storageTypes:new Set([t.storageType])})}else{a.events.push(t);a.totalOperations++;a.storageTypes.add(t.storageType);if(t.timestamp>a.lastEvent.timestamp){a.lastEvent=t;a.currentValue=t.data.value;a.valueType=w(t.data.value)}}});return Array.from(e.values()).sort((t,r)=>r.lastEvent.timestamp.getTime()-t.lastEvent.timestamp.getTime())},[T,u,g]);const s=(0,_react.useMemo)(()=>{if(!A)return null;return l.find(e=>e.key===A)||null},[A,l]);const Ce=(0,_react.useMemo)(()=>{const e={};l.forEach(t=>{e[t.key]=t.totalOperations});return e},[l]);const Ie=(0,_react.useMemo)(()=>{const e={};l.forEach(t=>{e[t.key]=t.lastEvent.timestamp.getTime()});return e},[l]);const y=(0,_react.useMemo)(()=>{if(!S)return null;return l.find(e=>e.key===S)||null},[S,l]);(0,_react.useEffect)(()=>{if(!C)return;const e=s?s:c!==null?y:null;const t=s?j:c;if(!e||t==null)return;const r=[...e.events].sort((v,V)=>v.timestamp.getTime()-V.timestamp.getTime());const n=r[t];const a=r[t-1];if(n)C(n);if(a)C(a)},[C,s,j,y,c]);const E=(0,_react.useMemo)(()=>{return T.filter(e=>{if(!e.data?.key)return false;if(!g.has(e.storageType))return false;const t=e.data.key;return!Array.from(u).some(r=>t.includes(r))})},[T,u,g]);const ke=(0,_react.useMemo)(()=>(0,_StorageTypeFilterBar.countByStorageType)(E),[E]);const i=(0,_react.useMemo)(()=>{if(f==="all")return E;return E.filter(e=>e.storageType===f)},[E,f]);const je=(0,_react.useCallback)(()=>{return i.map(e=>({key:e.data?.key,action:e.action,value:(0,_sharedUi.truncatePayload)(e.data?.value),storageType:e.storageType,timestamp:e.timestamp.toISOString()}))},[i]);const we=(0,_react.useMemo)(()=>i.slice(0,I),[i,I]);const K=(0,_react.useMemo)(()=>Math.max(0,i.length-I),[i.length,I]);const Fe=.8;const Be=(0,_react.useCallback)(e=>e.id,[]);const ee=(0,_react.useCallback)(e=>{const t=l.find(a=>a.key===e.data?.key);if(!t)return;const r=[...t.events].sort((a,v)=>a.timestamp.getTime()-v.timestamp.getTime());const n=r.findIndex(a=>a.timestamp.getTime()===e.timestamp.getTime());b(t.key);h(n>=0?n:0)},[l]);const Me=(0,_react.useCallback)(({item:e})=>{const t={key:e.data?.key||"",lastAction:e.action,totalOperations:1,lastEventTimestamp:e.timestamp,storageTypes:new Set([e.storageType]),valueType:w(e.data?.value)};return(0,_jsxRuntime.jsx)(_StorageEventCard.StorageEventCard,{data:t,onPress:()=>ee(e),testID:`buoy-storage-event-${e.id}`})},[ee]);if(!o)return null;const Pe=ne?_sharedUi.devToolsStorageKeys.modal.root():_sharedUi.devToolsStorageKeys.storage.modal();const te=()=>{if(x){return(0,_jsxRuntime.jsx)(_StorageEventFilterView.StorageEventFilterView,{ignoredPatterns:u,onTogglePattern:Y,onAddPattern:X,availableKeys:Ee,enabledStorageTypes:g,onToggleStorageType:Te})}if(m==="browser"){if(S&&y){if(c!==null){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.contentWrapper,children:(0,_jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailContent,{conversation:y,selectedEventIndex:c,onEventIndexChange:d,disableInternalFooter:true})})}const t=[...y.events].sort((n,a)=>n.timestamp.getTime()-a.timestamp.getTime());const r=[...t].reverse();return(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:"buoy-storage-history-list",style:styles.flexFill,data:r,keyExtractor:n=>n.id,renderItem:({item:n})=>{const a=t.findIndex(V=>V.timestamp.getTime()===n.timestamp.getTime());const v={key:n.data?.key||"",lastAction:n.action,totalOperations:1,lastEventTimestamp:n.timestamp,storageTypes:new Set([n.storageType]),valueType:w(n.data?.value)};return(0,_jsxRuntime.jsx)(_StorageEventCard.StorageEventCard,{data:v,onPress:()=>d(a>=0?a:0),testID:`buoy-storage-history-event-${n.id}`})},contentContainerStyle:styles.listContent,ItemSeparatorComponent:()=>(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.separator}),initialNumToRender:20})}return(0,_jsxRuntime.jsx)(_StorageBrowserMode.StorageBrowserMode,{requiredStorageKeys:ae,ignoredPatterns:u,onTogglePattern:Y,onAddPattern:X,searchQuery:L,storageDataRef:H,eventCountByKey:Ce,lastWriteByKey:Ie,onViewHistory:fe,enabledStorageTypes:g,pinnedKeys:ue,onTogglePin:ve,applySafeAreaInset:R==="bottomSheet",storageTypeFilter:f,onStorageTypeFilterChange:J,detailKey:B,onOpenKey:k,onCloseDetail:()=>k(null)})}if(s){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.contentWrapper,children:(0,_jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailContent,{conversation:s,selectedEventIndex:j,onEventIndexChange:h,disableInternalFooter:true})})}const e=(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.eventFilterBar,children:(0,_jsxRuntime.jsx)(_StorageTypeFilterBar.StorageTypeFilterBar,{active:f,onChange:J,counts:ke})});if(i.length===0){const t=f!=="all";return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.flexFill,children:[e,(0,_jsxRuntime.jsx)(_sharedUi.ToolEmptyState,{testID:"buoy-storage-events-empty",...!Q?{otto:"paused",title:"Event listener is paused",message:"Press play to start monitoring"}:t?{otto:"filtering",title:`No ${f} events`,message:"Other backends have events \u2014 pick All Types to see them"}:{otto:"listening",title:"No storage events yet",message:"Storage operations will appear here"}})]})}return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.flexFill,children:[e,(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:"buoy-storage-events-list",style:styles.flexFill,data:we,renderItem:Me,keyExtractor:Be,onEndReachedThreshold:Fe,contentContainerStyle:styles.listContent,ListFooterComponent:K>0?(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.lockedBannerContainer,children:(0,_jsxRuntime.jsx)(_sharedUi.ProFeatureBanner,{featureName:`${K} event${K>1?"s":""} locked`,description:"Upgrade to Pro to unlock full event history"})}):null,initialNumToRender:10,maxToRenderPerBatch:10,windowSize:10})]})};const N=s&&s.events.length>1?(0,_jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter,{conversation:s,selectedEventIndex:j,onEventIndexChange:h,applySafeAreaInset:R==="bottomSheet"}):y&&c!==null&&y.events.length>1?(0,_jsxRuntime.jsx)(_StorageEventDetailContent.StorageEventDetailFooter,{conversation:y,selectedEventIndex:c,onEventIndexChange:d,applySafeAreaInset:R==="bottomSheet"}):null;const Ae=x?()=>_(false):c!==null?()=>d(null):S?U:s?()=>{b(null);h(0)}:F;const re=x?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:()=>_(false)}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"Filters"})]}):S?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:c!==null?()=>d(null):U}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:`${S} History`,centered:true})]}):s?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:()=>{b(null);h(0)}}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:s.key})]}):B?(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:()=>k(null)}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:B.key,centered:true,testID:"buoy-storage-detail-title"})]}):(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader,{children:[F&&(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation,{onBack:F}),(0,_jsxRuntime.jsx)(_sharedUi.ModalHeader.Content,{title:"",children:M?(0,_jsxRuntime.jsx)(_sharedUi.SearchBar,{value:L,onChange:q,onClear:()=>{q("");P(false)},placeholder:"Search storage keys...",testID:"buoy-storage-search",autoFocus:true,onSubmitEditing:()=>P(false),returnKeyType:"search",containerStyle:styles.headerSearchContainer}):(0,_jsxRuntime.jsx)(_sharedUi.TabSelector,{tabs:[{key:"browser",label:"Storage"},{key:"events",label:`Events${i.length>0?` (${i.length})`:""}`}],activeTab:m,testIDPrefix:"buoy-storage-tab",onTabChange:e=>{le(e);D(null);d(null);b(null);h(0);k(null)}})}),(0,_jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions,{children:[z&&!M?(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:z,style:styles.iconButton,testID:"buoy-storage-refresh",accessibilityLabel:"Refresh storage",accessibilityRole:"button",children:(0,_jsxRuntime.jsx)(_sharedUi.RefreshCw,{size:14,color:_sharedUi.night.color.textSecondary})}):null,(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:me,testID:"buoy-storage-filters",accessibilityLabel:"Storage filters",accessibilityRole:"button",style:[styles.iconButton,(u.size>0||g.size<3)&&styles.activeFilterButton],children:(0,_jsxRuntime.jsx)(_sharedUi.Filter,{size:14,color:u.size>0||g.size<3?_sharedUi.night.color.accent:_sharedUi.night.color.textSecondary})}),m==="browser"&&!M&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>P(true),testID:"buoy-storage-search-open",accessibilityLabel:"Open search",accessibilityRole:"button",style:styles.iconButton,children:(0,_jsxRuntime.jsx)(_sharedUi.Search,{size:14,color:_sharedUi.night.color.textSecondary})}),(0,_jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton,{value:be,disabled:H.current.length===0,buttonStyle:styles.iconButton,testID:"buoy-storage-copy"})]}),m==="events"&&(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton,{value:je,disabled:i.length===0,buttonStyle:styles.iconButton,testID:"buoy-storage-events-copy"}),(0,_jsxRuntime.jsx)(_sharedUi.PowerToggleButton,{isEnabled:Q,onToggle:Se,accessibilityLabel:"Toggle storage event monitoring",testID:"buoy-storage-power"}),(0,_jsxRuntime.jsx)(_sharedUi.ToolbarClearButton,{onPress:he,buttonStyle:styles.iconButton,testID:"buoy-storage-clear-events"})]})]})]});if(se){return(0,_jsxRuntime.jsx)(_sharedUi.TickProvider,{children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.inlineRoot,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.inlineHeader,..._sharedUi.windowDragProps,children:re}),te(),N]})})}return(0,_jsxRuntime.jsx)(_sharedUi.TickProvider,{children:(0,_jsxRuntime.jsx)(_sharedUi.JsModal,{visible:o,onClose:p,onBack:Ae,onMinimize:oe,persistenceKey:Pe,header:{showToggleButton:true,customContent:re},onModeChange:pe,enablePersistence:true,initialMode:"bottomSheet",enableGlitchEffects:true,styles:{},footer:N,footerHeight:N?68:0,disableScrollWrapper:true,children:te()})})}function StorageToolView(o={}){return(0,_jsxRuntime.jsx)(StorageModalWithTabs,{...o,inline:true,visible:true,onClose:()=>{}})}const styles=_reactNative.StyleSheet.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:_sharedUi.macOSColors.border.default,backgroundColor:"transparent"},headerSearchContainer:{flex:1},flexFill:{flex:1},iconButton:{width:32,height:32,borderRadius:8,backgroundColor:_sharedUi.night.color.buttonSurface,borderWidth:1,borderColor:_sharedUi.night.color.border,alignItems:"center",justifyContent:"center"},activeFilterButton:{backgroundColor:_sharedUi.night.color.accentSoft,borderColor:_sharedUi.night.color.accentBorderStrong},separator:{height:8},listContent:{paddingVertical:6},lockedBannerContainer:{marginTop:8,marginHorizontal:16},eventFilterBar:{paddingHorizontal:12,paddingTop:8,paddingBottom:4},emptyState:{flex:1,justifyContent:"center",alignItems:"center",padding:32},emptyTitle:{color:_sharedUi.night.color.text,fontSize:16,fontWeight:"600",marginTop:16,marginBottom:8,fontFamily:_sharedUi.monoFont,letterSpacing:.5,textTransform:"uppercase"},emptySubtitle:{color:_sharedUi.night.color.textSecondary,fontSize:14,textAlign:"center",fontFamily:_sharedUi.monoFont},eventNavigation:{flexDirection:"row",alignItems:"center",gap:8,paddingHorizontal:8},navButton:{padding:4,borderRadius:4},navButtonDisabled:{opacity:.3},eventCounter:{color:_sharedUi.night.color.text,fontSize:12,fontWeight:"600",fontFamily:_sharedUi.monoFont,paddingHorizontal:8},headerTopRow:{flexDirection:"row",alignItems:"center",gap:8,marginBottom:12},keyNameContainer:{flex:1,flexDirection:"row",alignItems:"center",gap:6,backgroundColor:_sharedUi.night.color.surfaceElevated,paddingHorizontal:10,paddingVertical:4,borderRadius:6,borderWidth:1,borderColor:_sharedUi.night.color.border,height:28},keyNameText:{flex:1,fontSize:13,fontWeight:"600",color:_sharedUi.night.color.accent,fontFamily:_sharedUi.monoFont,letterSpacing:.5},contentWrapper:{flex:1}});