@buoy-gg/storage 7.0.4 → 7.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/lib/commonjs/index.js +77 -1
  2. package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
  3. package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
  4. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
  5. package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
  6. package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
  7. package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
  8. package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
  9. package/lib/commonjs/storage/components/StorageActions.js +6 -6
  10. package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
  11. package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
  12. package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
  13. package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
  14. package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
  15. package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
  16. package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
  17. package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
  18. package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
  19. package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
  20. package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
  21. package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
  22. package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
  23. package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
  24. package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
  25. package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
  26. package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
  27. package/lib/commonjs/storage/index.js +29 -0
  28. package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
  29. package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
  30. package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
  31. package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
  32. package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
  33. package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
  34. package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
  35. package/lib/commonjs/storage/utils/index.js +13 -0
  36. package/lib/commonjs/storage/utils/lineDiff.js +21 -354
  37. package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
  38. package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
  39. package/lib/commonjs/storage/utils/storageOps.js +53 -0
  40. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
  41. package/lib/commonjs/storage/utils/valueType.js +24 -0
  42. package/lib/module/index.js +18 -1
  43. package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
  44. package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
  45. package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
  46. package/lib/module/storage/components/GameUIStorageStats.js +7 -7
  47. package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
  48. package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
  49. package/lib/module/storage/components/SelectionActionBar.js +2 -36
  50. package/lib/module/storage/components/StorageActions.js +7 -7
  51. package/lib/module/storage/components/StorageBrowserMode.js +16 -2
  52. package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
  53. package/lib/module/storage/components/StorageEventListener.js +14 -14
  54. package/lib/module/storage/components/StorageFilterCards.js +13 -135
  55. package/lib/module/storage/components/StorageKeyCard.js +1 -1
  56. package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
  57. package/lib/module/storage/components/StorageKeyRow.js +55 -17
  58. package/lib/module/storage/components/StorageKeySection.js +11 -5
  59. package/lib/module/storage/components/StorageKeyStats.js +11 -10
  60. package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
  61. package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
  62. package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
  63. package/lib/module/storage/components/StorageValueEditModal.js +357 -0
  64. package/lib/module/storage/hooks/useKeyEditor.js +100 -0
  65. package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
  66. package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
  67. package/lib/module/storage/hooks/useTreeDraft.js +103 -0
  68. package/lib/module/storage/index.js +3 -0
  69. package/lib/module/storage/stores/storageEventStore.js +4 -2
  70. package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
  71. package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
  72. package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
  73. package/lib/module/storage/utils/MMKVListener.js +40 -4
  74. package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
  75. package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
  76. package/lib/module/storage/utils/index.js +3 -0
  77. package/lib/module/storage/utils/lineDiff.js +7 -354
  78. package/lib/module/storage/utils/setStorageValue.js +209 -0
  79. package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
  80. package/lib/module/storage/utils/storageOps.js +46 -0
  81. package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
  82. package/lib/module/storage/utils/valueType.js +23 -0
  83. package/lib/typescript/index.d.ts +12 -1
  84. package/lib/typescript/index.d.ts.map +1 -1
  85. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
  86. package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
  87. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
  88. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
  89. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
  90. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  91. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  92. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
  93. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
  94. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
  95. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
  96. package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
  97. package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
  98. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
  99. package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
  100. package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
  101. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  102. package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
  103. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
  104. package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
  105. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
  106. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  107. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
  108. package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
  109. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
  110. package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
  111. package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
  112. package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
  113. package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
  114. package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
  115. package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
  116. package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
  117. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
  118. package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
  119. package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
  120. package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
  121. package/lib/typescript/storage/index.d.ts +2 -0
  122. package/lib/typescript/storage/index.d.ts.map +1 -1
  123. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
  124. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
  125. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
  126. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  127. package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
  128. package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
  129. package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
  130. package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
  131. package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
  132. package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
  133. package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
  134. package/lib/typescript/storage/utils/index.d.ts +1 -0
  135. package/lib/typescript/storage/utils/index.d.ts.map +1 -1
  136. package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
  137. package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
  138. package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
  139. package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
  140. package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
  141. package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
  142. package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
  143. package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
  144. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
  145. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
  146. package/lib/typescript/storage/utils/valueType.d.ts +13 -0
  147. package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
  148. package/package.json +5 -5
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Writing a storage value back, in one place.
5
+ *
6
+ * The counterpart to `deleteStorageKeys` — and the same reasoning applies: each
7
+ * backend writes differently, and a second copy of that fan-out is how one
8
+ * backend quietly starts corrupting values.
9
+ *
10
+ * The hard part is NOT the write, it's the types:
11
+ *
12
+ * - **AsyncStorage** stores strings only. A value that was stored as JSON must
13
+ * stay valid JSON, or an object silently becomes a quoted string and the app
14
+ * that reads it breaks in a way that looks nothing like a devtools edit.
15
+ * - **MMKV** is natively typed (string / number / boolean / buffer). Writing
16
+ * `"42"` over a number turns it into a string, so the original type is
17
+ * restored from `valueType`. Buffers can't round-trip at all — the snapshot
18
+ * only ever carried a `<ArrayBuffer n bytes>` placeholder.
19
+ * - **SecureStore** stores strings, and biometric-protected keys are never
20
+ * touched (writing needs the same auth prompt reading does).
21
+ *
22
+ * Pro gating and confirmation are deliberately NOT here; they belong to the UI
23
+ * that has the user's attention. This assumes the caller already asked.
24
+ */
25
+
26
+ import AsyncStorage from "@react-native-async-storage/async-storage";
27
+ import { secureStoreRegistry } from "./SecureStoreRegistry";
28
+ /**
29
+ * Why a key can't be edited, or null when it can. UI uses this both to hide the
30
+ * edit affordance and to explain itself, rather than failing at save time.
31
+ */
32
+ export function storageEditBlockedReason(storageKey, mmkvInstances = []) {
33
+ if (storageKey.storageType === "mmkv") {
34
+ if (storageKey.valueType === "buffer") {
35
+ return "Buffer values can't be edited — the tool only ever sees their byte length, not their contents.";
36
+ }
37
+ const instance = mmkvInstances.find(i => i.id === (storageKey.instanceId || "default"));
38
+ if (instance?.readOnly) {
39
+ return `MMKV instance "${instance.id}" is registered read-only.`;
40
+ }
41
+ }
42
+ if (storageKey.storageType === "secure") {
43
+ const entry = secureStoreRegistry.getAll().find(e => e.key === storageKey.key);
44
+ if (entry?.options.requireAuthentication) {
45
+ return "Biometric-protected keys can't be edited — writing one would fire an auth prompt.";
46
+ }
47
+ if (!secureStoreRegistry.getModule()) {
48
+ return "No SecureStore module is registered.";
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Validate `raw` for this key WITHOUT writing. Returns an error message, or
56
+ * null when the text is acceptable. The editor calls this as you type.
57
+ */
58
+ export function validateStorageValue(storageKey, raw) {
59
+ if (storageKey.storageType === "mmkv") {
60
+ const type = mmkvTargetType(storageKey);
61
+ if (type === "number") {
62
+ if (raw.trim() === "" || Number.isNaN(Number(raw))) {
63
+ return "Must be a number — this MMKV key is stored as a number.";
64
+ }
65
+ }
66
+ if (type === "boolean") {
67
+ const t = raw.trim().toLowerCase();
68
+ if (t !== "true" && t !== "false") {
69
+ return 'Must be "true" or "false" — this MMKV key is stored as a boolean.';
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+
75
+ // AsyncStorage + SecureStore are string-valued. Only guard the case where
76
+ // the CURRENT value is structured: silently flattening an object into a
77
+ // string is the corruption worth preventing. A plain string stays free-form.
78
+ if (isStructured(storageKey.value)) {
79
+ try {
80
+ JSON.parse(raw);
81
+ } catch (e) {
82
+ return `This key holds JSON — ${e instanceof Error ? e.message : "invalid JSON"}`;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
88
+ /**
89
+ * The text an editor should open with: JSON pretty-printed so it's actually
90
+ * editable, everything else verbatim.
91
+ */
92
+ export function storageValueToEditableText(value) {
93
+ if (value === undefined || value === null) return "";
94
+ if (typeof value === "string") {
95
+ // Values arrive already parsed when they were JSON, so a string here is a
96
+ // real string — except AsyncStorage hands back raw JSON text sometimes.
97
+ const trimmed = value.trim();
98
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
99
+ try {
100
+ return JSON.stringify(JSON.parse(value), null, 2);
101
+ } catch {
102
+ return value;
103
+ }
104
+ }
105
+ return value;
106
+ }
107
+ if (typeof value === "object") return JSON.stringify(value, null, 2);
108
+ return String(value);
109
+ }
110
+
111
+ /**
112
+ * The value as a STRUCTURE, for the tree editor to apply ops against.
113
+ *
114
+ * Backends disagree on how far they've already parsed: `useAsyncStorageKeys`
115
+ * hands back a parsed object, while MMKV reports its native type so a JSON blob
116
+ * arrives as the raw string. Both have to end up as the same thing before an op
117
+ * can address a path inside them.
118
+ *
119
+ * Returns `undefined` when the value isn't a container — scalars are edited
120
+ * through the plain value editor, not the tree.
121
+ */
122
+ export function structuredStorageValue(storageKey) {
123
+ const {
124
+ value
125
+ } = storageKey;
126
+ if (value !== null && typeof value === "object") return value;
127
+ if (typeof value === "string") {
128
+ const trimmed = value.trim();
129
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return undefined;
130
+ try {
131
+ const parsed = JSON.parse(value);
132
+ return parsed !== null && typeof parsed === "object" ? parsed : undefined;
133
+ } catch {
134
+ return undefined;
135
+ }
136
+ }
137
+ return undefined;
138
+ }
139
+
140
+ /**
141
+ * Write `raw` to whichever backend holds this key, restoring the value's
142
+ * original type. Rejects (rather than writing something lossy) when the key
143
+ * isn't editable or the text doesn't fit the type — callers surface that.
144
+ */
145
+ export async function setStorageValue(storageKey, raw, mmkvInstances = []) {
146
+ const blocked = storageEditBlockedReason(storageKey, mmkvInstances);
147
+ if (blocked) throw new Error(blocked);
148
+ const invalid = validateStorageValue(storageKey, raw);
149
+ if (invalid) throw new Error(invalid);
150
+ switch (storageKey.storageType) {
151
+ case "async":
152
+ {
153
+ await AsyncStorage.setItem(storageKey.key, normalizeJsonText(storageKey, raw));
154
+ return;
155
+ }
156
+ case "secure":
157
+ {
158
+ await secureStoreRegistry.setValue(storageKey.key, normalizeJsonText(storageKey, raw));
159
+ return;
160
+ }
161
+ case "mmkv":
162
+ {
163
+ const instanceId = storageKey.instanceId || "default";
164
+ const target = mmkvInstances.find(i => i.id === instanceId);
165
+ if (!target) {
166
+ throw new Error(`MMKV instance "${instanceId}" is not registered.`);
167
+ }
168
+ target.instance.set(storageKey.key, coerceMMKV(storageKey, raw));
169
+ return;
170
+ }
171
+ default:
172
+ {
173
+ throw new Error(`Unsupported storage type "${String(storageKey.storageType)}"`);
174
+ }
175
+ }
176
+ }
177
+
178
+ /** True when the value is an object/array (so its text must stay valid JSON). */
179
+ function isStructured(value) {
180
+ return value !== null && typeof value === "object";
181
+ }
182
+
183
+ /**
184
+ * Re-serialize JSON so the stored string is compact and canonical, matching
185
+ * what the app itself would have written with `JSON.stringify`. Free-form
186
+ * strings pass through untouched.
187
+ */
188
+ function normalizeJsonText(storageKey, raw) {
189
+ if (!isStructured(storageKey.value)) return raw;
190
+ return JSON.stringify(JSON.parse(raw));
191
+ }
192
+
193
+ /** The MMKV native type to write back, defaulting to the current JS type. */
194
+ function mmkvTargetType(storageKey) {
195
+ if (storageKey.valueType) return storageKey.valueType;
196
+ if (typeof storageKey.value === "number") return "number";
197
+ if (typeof storageKey.value === "boolean") return "boolean";
198
+ return "string";
199
+ }
200
+ function coerceMMKV(storageKey, raw) {
201
+ switch (mmkvTargetType(storageKey)) {
202
+ case "number":
203
+ return Number(raw);
204
+ case "boolean":
205
+ return raw.trim().toLowerCase() === "true";
206
+ default:
207
+ return raw;
208
+ }
209
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * A "the underlying storage changed, re-read it" signal for the storage
5
+ * browser.
6
+ *
7
+ * On device the browser refreshes itself from the AsyncStorage/MMKV listeners,
8
+ * but those never fire in remote-mirror mode (the desktop dashboard suppresses
9
+ * local capture and receives the device's events as snapshots instead). Without
10
+ * this seam the browse tab is a one-shot read at mount — values stay frozen
11
+ * until the panel remounts, which is exactly the "shows old data until I switch
12
+ * tools and come back" behaviour.
13
+ *
14
+ * The dashboard calls {@link requestStorageBrowserRefresh} whenever a fresh
15
+ * device snapshot lands; the browser re-reads all three backends (debounced).
16
+ * It is a no-op when nothing is listening.
17
+ */
18
+
19
+ const listeners = new Set();
20
+
21
+ /**
22
+ * Subscribe to external refresh requests. Returns an unsubscribe function.
23
+ * @internal — used by the storage browser.
24
+ */
25
+ export function onStorageBrowserRefresh(listener) {
26
+ listeners.add(listener);
27
+ return () => {
28
+ listeners.delete(listener);
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Ask any mounted storage browser to re-read AsyncStorage, MMKV and
34
+ * SecureStore. Safe to call at any rate — the browser debounces.
35
+ */
36
+ export function requestStorageBrowserRefresh() {
37
+ listeners.forEach(listener => {
38
+ try {
39
+ listener();
40
+ } catch {
41
+ // Listener errors are not ours
42
+ }
43
+ });
44
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * The five operations a storage event can be, and how to recognise one.
5
+ *
6
+ * AsyncStorage and MMKV each have their own action union, and neither maps
7
+ * cleanly onto what a reader wants to see — `multiRemove`, `removeItem` and
8
+ * `delete` are one idea wearing three names. These collapse both onto a single
9
+ * vocabulary so every surface labels, filters and colours events the same way.
10
+ *
11
+ * This is semantics, not presentation: it lives in `utils` rather than beside
12
+ * any one view, so a view can be deleted without taking the vocabulary with it.
13
+ */
14
+
15
+ import { macOSColors } from "@buoy-gg/shared-ui";
16
+ export const STORAGE_OPS = ["SET", "GET", "MERGE", "DELETE", "CLEAR"];
17
+ const OP_DOT = {
18
+ SET: macOSColors.semantic.info,
19
+ GET: macOSColors.text.muted,
20
+ MERGE: macOSColors.semantic.debug,
21
+ DELETE: macOSColors.semantic.error,
22
+ CLEAR: macOSColors.semantic.warning
23
+ };
24
+ export function storageOpDot(op) {
25
+ return OP_DOT[op];
26
+ }
27
+
28
+ /** Both backends' action unions, collapsed onto the five display operations. */
29
+ export function storageOpOf(event) {
30
+ const action = event.action;
31
+ if (action === "clear" || action === "clearAll") return "CLEAR";
32
+ if (action.startsWith("get.")) return "GET";
33
+ if (action === "removeItem" || action === "multiRemove" || action === "delete") return "DELETE";
34
+ if (action === "mergeItem" || action === "multiMerge") return "MERGE";
35
+ return "SET";
36
+ }
37
+
38
+ /**
39
+ * The store synthesises one event per existing key when it first subscribes, so
40
+ * the list isn't empty on open. Those are a SNAPSHOT, not a write: the key
41
+ * already held that value, so there is no "before" to diff against and showing
42
+ * one would invent a change that never happened.
43
+ */
44
+ export function isInitialScan(event) {
45
+ return Boolean(event.data?.initialScan);
46
+ }
@@ -6,15 +6,20 @@
6
6
  * Provides jump and undo functionality for AsyncStorage operations.
7
7
  */
8
8
 
9
+ import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
9
10
  import AsyncStorage from "@react-native-async-storage/async-storage";
10
- import { writeMany } from "./asyncStorageCompat";
11
- import { pauseCapture, resumeCapture } from "./AsyncStorageListener";
12
-
11
+ import { writeMany, removeMany } from "./asyncStorageCompat";
13
12
  /**
14
13
  * Undo a single storage operation
15
14
  *
16
15
  * Restores the previous value(s) before the operation occurred.
17
- * This pauses event capture to avoid creating new events during restoration.
16
+ *
17
+ * The restore is deliberately NOT capture-paused. Pausing it meant the write
18
+ * landed but emitted no event, so the modal's event list and current-value
19
+ * panel never changed — UNDO looked like a dead button even though it had
20
+ * worked. Letting the restore emit normally makes it visible and keeps the
21
+ * history honest about what the storage actually did. There is no feedback
22
+ * loop: a write emits an event, and an event never triggers a write.
18
23
  *
19
24
  * @param event - The storage event to undo
20
25
  */
@@ -23,10 +28,7 @@ export async function undoOperation(event) {
23
28
  action,
24
29
  data
25
30
  } = event;
26
-
27
- // Pause capture to avoid creating events during restoration
28
- pauseCapture();
29
- try {
31
+ {
30
32
  switch (action) {
31
33
  case "setItem":
32
34
  case "mergeItem":
@@ -81,20 +83,21 @@ export async function undoOperation(event) {
81
83
  default:
82
84
  throw new Error(`Cannot undo unknown action type: ${action}`);
83
85
  }
84
- } finally {
85
- // Always resume capture, even if an error occurred
86
- resumeCapture();
87
86
  }
88
87
  }
89
88
 
90
89
  /**
91
90
  * Jump to a specific point in storage history
92
91
  *
93
- * This reconstructs the storage state as it was after a specific event
94
- * by clearing storage and replaying all events up to that point.
92
+ * Reconstructs the state as of `targetEventIndex` by replaying `events` up to
93
+ * that point, then reconciling ONLY the keys that timeline governs.
95
94
  *
96
- * Note: This is a destructive operation - it will clear current storage
97
- * and replay events to reach the target state.
95
+ * This used to begin with `AsyncStorage.clear()`, which was badly out of
96
+ * proportion: the modal's JUMP button passes one key's history, so clearing
97
+ * wiped every unrelated app key — and Buoy's own `@react_buoy*` settings with
98
+ * them — to restore a single value. Now a jump touches exactly the keys that
99
+ * appear in the events it was given, and never Buoy's own keys. Like
100
+ * [undoOperation], the writes emit normally so the change is visible.
98
101
  *
99
102
  * @param events - All events in chronological order (oldest first)
100
103
  * @param targetEventIndex - The index of the event to jump to (0-based)
@@ -104,34 +107,52 @@ export async function jumpToState(events, targetEventIndex) {
104
107
  throw new Error(`Invalid event index: ${targetEventIndex}`);
105
108
  }
106
109
 
107
- // Pause capture to avoid creating events during replay
108
- pauseCapture();
109
- try {
110
- // Clear all storage first
111
- await AsyncStorage.clear();
112
-
113
- // Build up the state by applying each event up to and including the target
114
- // We use the final values from each event, not the operations themselves
115
- const stateMap = new Map();
116
- for (let i = 0; i <= targetEventIndex; i++) {
117
- const event = events[i];
118
- applyEventToStateMap(event, stateMap);
119
- }
110
+ // Build up the state by applying each event up to and including the target.
111
+ // We use the final values from each event, not the operations themselves.
112
+ const stateMap = new Map();
113
+ for (let i = 0; i <= targetEventIndex; i++) {
114
+ applyEventToStateMap(events[i], stateMap);
115
+ }
120
116
 
121
- // Apply the final state
122
- const pairsToSet = [];
123
- for (const [key, value] of stateMap) {
124
- if (value !== null) {
125
- pairsToSet.push([key, value]);
126
- }
127
- }
128
- if (pairsToSet.length > 0) {
129
- await writeMany(pairsToSet);
117
+ // Every key the WHOLE timeline touches — including events after the target,
118
+ // so a key created later is removed rather than left behind.
119
+ const governedKeys = new Set();
120
+ for (const event of events) {
121
+ collectEventKeys(event, governedKeys);
122
+ }
123
+ const pairsToSet = [];
124
+ const keysToRemove = [];
125
+ for (const key of governedKeys) {
126
+ // Buoy's own settings are never app state to travel through.
127
+ if (isDevToolsStorageKey(key)) continue;
128
+ const value = stateMap.get(key);
129
+ if (value === undefined || value === null) {
130
+ keysToRemove.push(key);
131
+ } else {
132
+ pairsToSet.push([key, value]);
130
133
  }
131
- } finally {
132
- // Always resume capture
133
- resumeCapture();
134
134
  }
135
+ if (keysToRemove.length > 0) {
136
+ await removeMany(keysToRemove);
137
+ }
138
+ if (pairsToSet.length > 0) {
139
+ await writeMany(pairsToSet);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Collect every key an event refers to, across all action shapes — including
145
+ * `prevPairs`, so keys a `clear` removed are still governed by the timeline.
146
+ */
147
+ function collectEventKeys(event, into) {
148
+ const {
149
+ data
150
+ } = event;
151
+ if (!data) return;
152
+ if (data.key) into.add(data.key);
153
+ if (data.keys) for (const key of data.keys) into.add(key);
154
+ if (data.pairs) for (const [key] of data.pairs) into.add(key);
155
+ if (data.prevPairs) for (const [key] of data.prevPairs) into.add(key);
135
156
  }
136
157
 
137
158
  /**
@@ -13,6 +13,29 @@ export function getValueTypeLabel(value) {
13
13
  return "unknown";
14
14
  }
15
15
 
16
+ /**
17
+ * A stored value as a tree can show it.
18
+ *
19
+ * AsyncStorage and SecureStore hold strings, so an object arrives as the text
20
+ * `{"a":1}` — displaying that as a string is technically true and useless. Any
21
+ * surface that renders or edits a value structurally has to do this same
22
+ * un-stringify first, so it lives here rather than being re-derived (slightly
23
+ * differently) in each one.
24
+ *
25
+ * A string that doesn't parse comes back untouched: it was just a string that
26
+ * happened to start with a brace.
27
+ */
28
+ export function parseStorageValue(value) {
29
+ if (typeof value !== "string") return value;
30
+ const trimmed = value.trim();
31
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
32
+ try {
33
+ return JSON.parse(value);
34
+ } catch {
35
+ return value;
36
+ }
37
+ }
38
+
16
39
  /** Longest value preview rendered inline next to the type label. */
17
40
  const MAX_INLINE_PREVIEW = 40;
18
41
 
@@ -10,12 +10,15 @@
10
10
  */
11
11
  export { storageToolPreset, createStorageTool } from "./preset";
12
12
  export { StorageSection } from "./storage/components/StorageSection";
13
- export { StorageModalWithTabs } from "./storage/components/StorageModalWithTabs";
13
+ export { StorageModalWithTabs, StorageToolView, } from "./storage/components/StorageModalWithTabs";
14
14
  export { StorageKeyCard } from "./storage/components/StorageKeyCard";
15
15
  export { StorageKeyStatsSection } from "./storage/components/StorageKeyStats";
16
16
  export { StorageKeySection } from "./storage/components/StorageKeySection";
17
17
  export { StorageBrowserMode } from "./storage/components/StorageBrowserMode";
18
+ export type { StorageFilterType, StorageTypeFilter, } from "./storage/components/StorageFilterCards";
19
+ export { StorageTypeFilterBar, countByStorageType, type StorageTypeCounts, } from "./storage/components/StorageTypeFilterBar";
18
20
  export { StorageEventsSection } from "./storage/components/StorageEventsSection";
21
+ export { STORAGE_OPS, storageOpOf, storageOpDot, isInitialScan, type StorageOp, } from "./storage/utils/storageOps";
19
22
  export { StorageEventCard, getValueType, type StorageEventCardData, type StorageValueType, } from "./storage/components/StorageEventCard";
20
23
  export { StorageEventDetailContent, StorageEventDetailFooter, } from "./storage/components/StorageEventDetailContent";
21
24
  export { MMKVInstanceSelector } from "./storage/components/MMKVInstanceSelector";
@@ -26,6 +29,7 @@ export { useMMKVInstances, useMMKVInstance, useMMKVInstanceExists, } from "./sto
26
29
  export { useStorageEvents, type UseStorageEventsOptions, type UseStorageEventsResult, } from "./storage/hooks/useStorageEvents";
27
30
  export type { StorageEvent, StorageEventListener, StorageEventCallback, } from "./storage/stores/storageEventStore";
28
31
  export { clearAllAppStorage } from "./storage/utils/clearAllStorage";
32
+ export { setStorageValue, storageEditBlockedReason, validateStorageValue, storageValueToEditableText, type EditableMMKVInstance, } from "./storage/utils/setStorageValue";
29
33
  export { isMMKVAvailable, getMMKVClass, getMMKVUnavailableMessage, } from "./storage/utils/mmkvAvailability";
30
34
  export { detectMMKVType, formatMMKVValue, isTypeMatch, type MMKVValueType, type MMKVValueInfo, } from "./storage/utils/mmkvTypeDetection";
31
35
  export { undoOperation, jumpToState, canUndo, } from "./storage/utils/storageTimeTravelUtils";
@@ -38,4 +42,11 @@ export type { SecureStoreLike, SecureStoreKeyDescriptor, SecureStoreOptionsLike,
38
42
  export { storageSyncAdapter } from "./storage/sync/storageSyncAdapter";
39
43
  /** @internal */
40
44
  export { storageEventStore } from "./storage/stores/storageEventStore";
45
+ /**
46
+ * Tell a mounted storage browser to re-read every backend. Used by the desktop
47
+ * dashboard, where local storage listeners are suppressed and device data
48
+ * arrives as snapshots instead.
49
+ * @internal
50
+ */
51
+ export { requestStorageBrowserRefresh } from "./storage/utils/storageBrowserRefresh";
41
52
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAKnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,kCAAkC,CAAC;AAK1C,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,GACR,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,SAAS,EACT,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAM9C,OAAO,EACL,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAKvE,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
@@ -1,7 +1,10 @@
1
1
  interface TreeDiffViewerProps {
2
2
  oldValue: unknown;
3
3
  newValue: unknown;
4
+ /** Forwarded to the shared viewer — false renders the tree inline so an
5
+ * ancestor ScrollView scrolls it as part of the page. */
6
+ scrollEnabled?: boolean;
4
7
  }
5
- export declare function TreeDiffViewer({ oldValue, newValue }: TreeDiffViewerProps): import("react").JSX.Element;
8
+ export declare function TreeDiffViewer({ oldValue, newValue, scrollEnabled, }: TreeDiffViewerProps): import("react").JSX.Element;
6
9
  export {};
7
10
  //# sourceMappingURL=TreeDiffViewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TreeDiffViewer.d.ts","sourceRoot":"","sources":["../../../../../src/storage/components/DiffViewer/TreeDiffViewer.tsx"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,mBAAmB,+BAUzE"}
1
+ {"version":3,"file":"TreeDiffViewer.d.ts","sourceRoot":"","sources":["../../../../../src/storage/components/DiffViewer/TreeDiffViewer.tsx"],"names":[],"mappings":"AAIA,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB;8DAC0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,QAAQ,EACR,aAAoB,GACrB,EAAE,mBAAmB,+BAWrB"}
@@ -7,7 +7,13 @@ interface ThemedSplitViewProps {
7
7
  theme: DiffTheme;
8
8
  options?: DiffOptions;
9
9
  showThemeName?: boolean;
10
+ /**
11
+ * Scroll the rows inside their own fixed-height pane (default). Set false to
12
+ * render every row inline at natural height and let an ancestor ScrollView
13
+ * move them — one page-level scroll instead of a nested scroll region.
14
+ */
15
+ scrollEnabled?: boolean;
10
16
  }
11
- export declare function ThemedSplitView({ oldValue, newValue, theme, options, showThemeName, }: ThemedSplitViewProps): import("react").JSX.Element;
17
+ export declare function ThemedSplitView({ oldValue, newValue, theme, options, showThemeName, scrollEnabled, }: ThemedSplitViewProps): import("react").JSX.Element;
12
18
  export {};
13
19
  //# sourceMappingURL=ThemedSplitView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ThemedSplitView.d.ts","sourceRoot":"","sources":["../../../../../../src/storage/components/DiffViewer/modes/ThemedSplitView.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,EAAE,CAAC;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAaD,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,OAAyB,EACzB,aAAqB,GACtB,EAAE,oBAAoB,+BAgLtB"}
1
+ {"version":3,"file":"ThemedSplitView.d.ts","sourceRoot":"","sources":["../../../../../../src/storage/components/DiffViewer/modes/ThemedSplitView.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,EAAE,CAAC;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAaD,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,OAAyB,EACzB,aAAqB,EACrB,aAAoB,GACrB,EAAE,oBAAoB,+BAmMtB"}
@@ -1,5 +1,6 @@
1
1
  import { MutableRefObject } from "react";
2
2
  import { RequiredStorageKey } from "../types";
3
+ import { type StorageFilterType, type StorageTypeFilter } from "./StorageFilterCards";
3
4
  /** Free tier limit for storage keys */
4
5
  export declare const FREE_TIER_KEY_LIMIT = 25;
5
6
  interface GameUIStorageBrowserProps {
@@ -11,13 +12,38 @@ interface GameUIStorageBrowserProps {
11
12
  searchQuery?: string;
12
13
  storageDataRef?: MutableRefObject<any[]>;
13
14
  eventCountByKey?: Record<string, number>;
15
+ /**
16
+ * Epoch ms of the most recent recorded write per key, for keys whose write
17
+ * we actually witnessed — a key can exist without us ever having seen it
18
+ * written. Currently unused by the card list; kept because the host computes
19
+ * it for the Events tab anyway.
20
+ */
21
+ lastWriteByKey?: Record<string, number>;
14
22
  onViewHistory?: (key: string) => void;
15
23
  enabledStorageTypes?: Set<'async' | 'mmkv' | 'secure'>;
24
+ /**
25
+ * Status filter (valid / missing / issues), lifted so a host that has its own
26
+ * toolbar can render the counters there — the desktop dashboard puts them
27
+ * beside the search field. Omit both to keep the filter internal.
28
+ */
29
+ statusFilter?: StorageFilterType;
30
+ onStatusFilterChange?: (filter: StorageFilterType) => void;
31
+ /** Reports the three status counts whenever they change, for that host toolbar. */
32
+ onStatusCountsChange?: (counts: {
33
+ validCount: number;
34
+ missingCount: number;
35
+ issuesCount: number;
36
+ }) => void;
16
37
  /** Keys pinned to the top of the list */
17
38
  pinnedKeys?: Set<string>;
18
39
  /** Toggle a key's pinned state */
19
40
  onTogglePin?: (key: string) => void;
41
+ /** Backend filter, lifted so the Events tab can share the same selection. */
42
+ storageTypeFilter?: StorageTypeFilter;
43
+ onStorageTypeFilterChange?: (type: StorageTypeFilter) => void;
44
+ /** Pad full-screen views past the home indicator (docked sheet only). */
45
+ applySafeAreaInset?: boolean;
20
46
  }
21
- export declare function GameUIStorageBrowser({ requiredStorageKeys, showFilters, ignoredPatterns, onTogglePattern, onAddPattern, searchQuery, storageDataRef, eventCountByKey, onViewHistory, enabledStorageTypes, pinnedKeys, onTogglePin, }: GameUIStorageBrowserProps): import("react").JSX.Element;
47
+ export declare function GameUIStorageBrowser({ requiredStorageKeys, showFilters, ignoredPatterns, onTogglePattern, onAddPattern, searchQuery, storageDataRef, eventCountByKey, lastWriteByKey, onViewHistory, enabledStorageTypes, statusFilter, onStatusFilterChange, onStatusCountsChange, pinnedKeys, onTogglePin, storageTypeFilter, onStorageTypeFilterChange, applySafeAreaInset, }: GameUIStorageBrowserProps): import("react").JSX.Element;
22
48
  export {};
23
49
  //# sourceMappingURL=GameUIStorageBrowser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,gBAAgB,EACjB,MAAM,OAAO,CAAC;AAUf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AA4C/E,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,mBAAmB,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IACvD,yCAAyC;IACzC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,kCAAkC;IAClC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,EACd,eAAe,EACf,aAAa,EACb,mBAAmB,EACnB,UAAU,EACV,WAAW,GACZ,EAAE,yBAAyB,+BAkzB3B"}
1
+ {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,gBAAgB,EACjB,MAAM,OAAO,CAAC;AAWf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAK/E,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AA2C9B,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,mBAAmB,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;IACvD;;;;OAIG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC3D,mFAAmF;IACnF,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE;QAC9B,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,IAAI,CAAC;IACX,yCAAyC;IACzC,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzB,kCAAkC;IAClC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC9D,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,kBAAkB,GACnB,EAAE,yBAAyB,+BA+9B3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionActionBar.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/SelectionActionBar.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAI1C,UAAU,uBAAuB;IAC/B,4BAA4B;IAC5B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,kCAAkC;IAClC,aAAa,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACpD,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gEAAgE;IAChE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC9C,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,uBAAuB,sCA+JzB"}
1
+ {"version":3,"file":"SelectionActionBar.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/SelectionActionBar.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,UAAU,uBAAuB;IAC/B,4BAA4B;IAC5B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,kCAAkC;IAClC,aAAa,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACpD,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,gEAAgE;IAChE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAC9C,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,uBAAuB,sCA6HzB"}