@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.
- package/README.md +11 -90
- package/lib/commonjs/index.js +1 -353
- package/lib/commonjs/preset.js +1 -85
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +1 -36
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -12
- package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +1 -24
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +3 -1259
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -257
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -419
- package/lib/commonjs/storage/components/SelectionActionBar.js +1 -207
- package/lib/commonjs/storage/components/StorageActionButtons.js +1 -242
- package/lib/commonjs/storage/components/StorageBrowserMode.js +1 -61
- package/lib/commonjs/storage/components/StorageEventActionButton.js +1 -121
- package/lib/commonjs/storage/components/StorageEventCard.js +1 -118
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +1 -494
- package/lib/commonjs/storage/components/StorageEventFilterView.js +1 -160
- package/lib/commonjs/storage/components/StorageEventsSection.js +1 -24
- package/lib/commonjs/storage/components/StorageFilterCards.js +1 -231
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -520
- package/lib/commonjs/storage/components/StorageKeyDetailScreen.js +1 -579
- package/lib/commonjs/storage/components/StorageKeyRow.js +1 -559
- package/lib/commonjs/storage/components/StorageKeySection.js +1 -160
- package/lib/commonjs/storage/components/StorageKeyStats.js +1 -345
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +1 -954
- package/lib/commonjs/storage/components/StorageSaveNotice.js +1 -171
- package/lib/commonjs/storage/components/StorageSection.js +1 -43
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +1 -144
- package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +1 -141
- package/lib/commonjs/storage/hooks/useMMKVInstances.js +1 -210
- package/lib/commonjs/storage/hooks/useMMKVKeys.js +1 -376
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +1 -202
- package/lib/commonjs/storage/hooks/useStorageEvents.js +1 -61
- package/lib/commonjs/storage/stores/storageEventStore.js +1 -312
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +1 -492
- package/lib/commonjs/storage/types.js +1 -5
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +1 -691
- package/lib/commonjs/storage/utils/AsyncStorageListener.web.js +1 -89
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +1 -203
- package/lib/commonjs/storage/utils/MMKVListener.js +1 -416
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +1 -200
- package/lib/commonjs/storage/utils/asyncStorageCompat.js +1 -89
- package/lib/commonjs/storage/utils/browserStorageLifecycle.js +1 -10
- package/lib/commonjs/storage/utils/browserStorageLifecycle.web.js +1 -70
- package/lib/commonjs/storage/utils/clearAllStorage.js +1 -48
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +1 -82
- package/lib/commonjs/storage/utils/lineDiff.js +1 -30
- package/lib/commonjs/storage/utils/mmkvAvailability.js +1 -62
- package/lib/commonjs/storage/utils/mmkvAvailability.web.js +1 -21
- package/lib/commonjs/storage/utils/mmkvTypeDetection.js +1 -154
- package/lib/commonjs/storage/utils/requiredStorageKeyScope.js +1 -17
- package/lib/commonjs/storage/utils/setStorageValue.js +1 -218
- package/lib/commonjs/storage/utils/snapshotProvider.js +1 -382
- package/lib/commonjs/storage/utils/storageActionHelpers.js +1 -46
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +1 -49
- package/lib/commonjs/storage/utils/storageLabels.js +1 -13
- package/lib/commonjs/storage/utils/storageLabels.web.js +1 -13
- package/lib/commonjs/storage/utils/storageOps.js +1 -53
- package/lib/commonjs/storage/utils/storageQueryUtils.js +1 -35
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +1 -288
- package/lib/commonjs/storage/utils/valueType.js +1 -83
- package/lib/module/index.js +1 -114
- package/lib/module/preset.js +1 -82
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +1 -32
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +1 -9
- package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +1 -10
- package/lib/module/storage/components/GameUIStorageBrowser.js +3 -1257
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -253
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -415
- package/lib/module/storage/components/SelectionActionBar.js +1 -204
- package/lib/module/storage/components/StorageActionButtons.js +1 -238
- package/lib/module/storage/components/StorageBrowserMode.js +1 -57
- package/lib/module/storage/components/StorageEventActionButton.js +1 -118
- package/lib/module/storage/components/StorageEventCard.js +1 -113
- package/lib/module/storage/components/StorageEventDetailContent.js +1 -490
- package/lib/module/storage/components/StorageEventFilterView.js +1 -156
- package/lib/module/storage/components/StorageEventsSection.js +1 -20
- package/lib/module/storage/components/StorageFilterCards.js +1 -227
- package/lib/module/storage/components/StorageKeyCard.js +1 -517
- package/lib/module/storage/components/StorageKeyDetailScreen.js +1 -577
- package/lib/module/storage/components/StorageKeyRow.js +1 -554
- package/lib/module/storage/components/StorageKeySection.js +1 -155
- package/lib/module/storage/components/StorageKeyStats.js +1 -342
- package/lib/module/storage/components/StorageModalWithTabs.js +1 -949
- package/lib/module/storage/components/StorageSaveNotice.js +1 -168
- package/lib/module/storage/components/StorageSection.js +1 -40
- package/lib/module/storage/components/StorageTypeFilterBar.js +1 -139
- package/lib/module/storage/hooks/useAsyncStorageKeys.js +1 -136
- package/lib/module/storage/hooks/useMMKVInstances.js +1 -204
- package/lib/module/storage/hooks/useMMKVKeys.js +1 -371
- package/lib/module/storage/hooks/useSecureStoreKeys.js +1 -199
- package/lib/module/storage/hooks/useStorageEvents.js +1 -58
- package/lib/module/storage/stores/storageEventStore.js +1 -301
- package/lib/module/storage/sync/storageSyncAdapter.js +1 -486
- package/lib/module/storage/types.js +1 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +1 -679
- package/lib/module/storage/utils/AsyncStorageListener.web.js +1 -76
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +1 -197
- package/lib/module/storage/utils/MMKVListener.js +1 -403
- package/lib/module/storage/utils/SecureStoreRegistry.js +1 -194
- package/lib/module/storage/utils/asyncStorageCompat.js +1 -81
- package/lib/module/storage/utils/browserStorageLifecycle.js +1 -6
- package/lib/module/storage/utils/browserStorageLifecycle.web.js +1 -66
- package/lib/module/storage/utils/clearAllStorage.js +1 -43
- package/lib/module/storage/utils/deleteStorageKeys.js +1 -78
- package/lib/module/storage/utils/lineDiff.js +1 -12
- package/lib/module/storage/utils/mmkvAvailability.js +1 -56
- package/lib/module/storage/utils/mmkvAvailability.web.js +1 -15
- package/lib/module/storage/utils/mmkvTypeDetection.js +1 -148
- package/lib/module/storage/utils/requiredStorageKeyScope.js +1 -13
- package/lib/module/storage/utils/setStorageValue.js +1 -209
- package/lib/module/storage/utils/snapshotProvider.js +1 -380
- package/lib/module/storage/utils/storageActionHelpers.js +1 -42
- package/lib/module/storage/utils/storageBrowserRefresh.js +1 -44
- package/lib/module/storage/utils/storageLabels.js +1 -9
- package/lib/module/storage/utils/storageLabels.web.js +1 -9
- package/lib/module/storage/utils/storageOps.js +1 -46
- package/lib/module/storage/utils/storageQueryUtils.js +1 -30
- package/lib/module/storage/utils/storageTimeTravelUtils.js +1 -281
- package/lib/module/storage/utils/valueType.js +1 -76
- package/lib/web/index.mjs +2 -3
- package/package.json +6 -10
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +0 -1
- package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +0 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +0 -1
- package/lib/typescript/storage/components/MMKVInstanceInfoPanel.d.ts.map +0 -1
- package/lib/typescript/storage/components/MMKVInstanceSelector.d.ts.map +0 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventCard.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventFilterView.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageEventsSection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyCard.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageSaveNotice.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageSection.d.ts.map +0 -1
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useMMKVInstances.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +0 -1
- package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +0 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +0 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/storage/types.d.ts.map +0 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +0 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +0 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +0 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +0 -1
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +0 -1
- package/lib/typescript/storage/utils/browserStorageLifecycle.d.ts.map +0 -1
- package/lib/typescript/storage/utils/browserStorageLifecycle.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +0 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +0 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvAvailability.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvAvailability.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +0 -1
- package/lib/typescript/storage/utils/requiredStorageKeyScope.d.ts.map +0 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +0 -1
- package/lib/typescript/storage/utils/snapshotProvider.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageActionHelpers.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageLabels.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageLabels.web.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageOps.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageQueryUtils.d.ts.map +0 -1
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +0 -1
- 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}});
|