@buoy-gg/storage 7.0.30 → 7.0.32

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 (41) hide show
  1. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +92 -21
  2. package/lib/commonjs/storage/components/StorageBrowserMode.js +8 -2
  3. package/lib/commonjs/storage/components/StorageKeyDetailScreen.js +572 -0
  4. package/lib/commonjs/storage/components/StorageKeyRow.js +21 -2
  5. package/lib/commonjs/storage/components/StorageKeySection.js +44 -21
  6. package/lib/commonjs/storage/components/StorageModalWithTabs.js +25 -2
  7. package/lib/commonjs/storage/components/StorageSaveNotice.js +171 -0
  8. package/lib/commonjs/storage/components/StorageValueEditModal.js +11 -364
  9. package/lib/commonjs/storage/hooks/useKeyEditor.js +12 -85
  10. package/lib/commonjs/storage/hooks/useTreeDraft.js +7 -100
  11. package/lib/module/storage/components/GameUIStorageBrowser.js +94 -23
  12. package/lib/module/storage/components/StorageBrowserMode.js +8 -2
  13. package/lib/module/storage/components/StorageKeyDetailScreen.js +570 -0
  14. package/lib/module/storage/components/StorageKeyRow.js +20 -3
  15. package/lib/module/storage/components/StorageKeySection.js +43 -21
  16. package/lib/module/storage/components/StorageModalWithTabs.js +26 -3
  17. package/lib/module/storage/components/StorageSaveNotice.js +168 -0
  18. package/lib/module/storage/components/StorageValueEditModal.js +13 -366
  19. package/lib/module/storage/hooks/useKeyEditor.js +14 -87
  20. package/lib/module/storage/hooks/useTreeDraft.js +9 -102
  21. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +13 -2
  22. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  23. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +7 -2
  24. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
  25. package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts +46 -0
  26. package/lib/typescript/storage/components/StorageKeyDetailScreen.d.ts.map +1 -0
  27. package/lib/typescript/storage/components/StorageKeyRow.d.ts +9 -0
  28. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  29. package/lib/typescript/storage/components/StorageKeySection.d.ts +19 -1
  30. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
  31. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  32. package/lib/typescript/storage/components/StorageSaveNotice.d.ts +6 -0
  33. package/lib/typescript/storage/components/StorageSaveNotice.d.ts.map +1 -0
  34. package/lib/typescript/storage/components/StorageValueEditModal.d.ts +9 -31
  35. package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -1
  36. package/lib/typescript/storage/hooks/useKeyEditor.d.ts +7 -31
  37. package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -1
  38. package/lib/typescript/storage/hooks/useTreeDraft.d.ts +7 -66
  39. package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -1
  40. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +1 -1
  41. package/package.json +5 -5
@@ -0,0 +1,572 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.StorageKeyDetailScreen = StorageKeyDetailScreen;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
11
+ var _storageQueryUtils = require("../utils/storageQueryUtils");
12
+ var _valueType = require("../utils/valueType");
13
+ var _StorageKeyRow = require("./StorageKeyRow");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ /**
16
+ * One storage key, on a screen of its own — the React Query detail-screen
17
+ * shape (and the Zustand tool's twin), pointed at a stored value.
18
+ *
19
+ * Tapping a key card opens this instead of expanding in place. Top to bottom:
20
+ *
21
+ * 1. VALUE EDITOR — the shared LiveExplorer. No draft, no Save: an edit
22
+ * re-serializes the whole value and writes it as you make it. Typing
23
+ * commits on a short debounce (~300ms, flushed on blur) so a word is one
24
+ * storage write, not one per keystroke — storage writes hit disk and
25
+ * land in the events timeline, which is the one way this tool differs
26
+ * from RQ's unlogged cache writes. A scalar key is just the root leaf's
27
+ * input; a boolean key is a one-tap toggle.
28
+ * 2. KEY DETAILS — what the expanded card used to say: status, backend,
29
+ * MMKV instance, updates (with the road to history), schema diagnostics.
30
+ * 3. RAW VALUE — the serialized string exactly as the backend holds it.
31
+ * 4. The card's housekeeping actions: pin, hide, delete.
32
+ *
33
+ * THE ROUND-TRIP WRINKLE zustand doesn't have: a storage write is async —
34
+ * write, device event, snapshot, refreshed prop. Between an edit and its
35
+ * echo, `storageKey.value` is STALE, so basing the next edit on the prop
36
+ * would silently revert the previous one (edit qty, then name, and qty snaps
37
+ * back). `lastWrittenRef` holds the most recent written document and is the
38
+ * base for every edit until the prop catches up to it — last-write-wins
39
+ * against the app, exactly once, and never against the editor itself.
40
+ */
41
+
42
+ /** How long typed edits coalesce before the single write fires. */const TYPING_COMMIT_MS = 300;
43
+ const DETAIL_ID = "buoy-storage-detail";
44
+ function detailExplorerTestID(dataPath) {
45
+ const slug = part => part.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
46
+ return dataPath.length === 0 ? `${DETAIL_ID}-explorer-root` : `${DETAIL_ID}-explorer-${dataPath.map(slug).filter(Boolean).join("-")}`;
47
+ }
48
+
49
+ /** Same MMKV instance-color rule the card uses. */
50
+ const INSTANCE_COLORS = [_sharedUi.macOSColors.semantic.info, _sharedUi.macOSColors.semantic.success, _sharedUi.macOSColors.semantic.warning, _sharedUi.macOSColors.semantic.debug, "#FF6B9D", "#00D9FF"];
51
+ function getInstanceColor(instanceId) {
52
+ const hash = instanceId.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
53
+ return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
54
+ }
55
+ function StorageKeyDetailScreen({
56
+ storageKey,
57
+ editBlockedReason,
58
+ eventCount,
59
+ onSave,
60
+ onViewHistory,
61
+ onDeleteKey,
62
+ onHideKey,
63
+ isPinned = false,
64
+ onTogglePin,
65
+ applySafeAreaInset
66
+ }) {
67
+ const insets = (0, _sharedUi.useSafeAreaInsets)();
68
+ const bottomInset = applySafeAreaInset ? insets.bottom : 0;
69
+ const canEdit = editBlockedReason == null;
70
+ const config = (0, _StorageKeyRow.getStatusConfig)(storageKey.status);
71
+ const storageTypeLabel = (0, _storageQueryUtils.getStorageTypeLabel)(storageKey.storageType);
72
+ const [rawOpen, setRawOpen] = (0, _react.useState)(false);
73
+ // Force the explorer to re-sync focused inputs after a Raw-hatch apply.
74
+ const [rawVersion, setRawVersion] = (0, _react.useState)(0);
75
+
76
+ /**
77
+ * The most recent document THIS screen wrote, until the refreshed prop
78
+ * catches up to it — see the module docstring's round-trip wrinkle. Held
79
+ * as its serialized form so "caught up" is one string compare against the
80
+ * same serialization the write used.
81
+ */
82
+ const lastWrittenRef = (0, _react.useRef)(null);
83
+ const propParsed = (0, _react.useMemo)(() => (0, _valueType.parseStorageValue)(storageKey.value), [storageKey.value]);
84
+ const propRaw = typeof storageKey.value === "string" ? storageKey.value : JSON.stringify(storageKey.value);
85
+ if (lastWrittenRef.current && lastWrittenRef.current.raw === propRaw) {
86
+ // The device echoed our write back — the prop is current again.
87
+ lastWrittenRef.current = null;
88
+ }
89
+ const baseValue = lastWrittenRef.current ? lastWrittenRef.current.value : propParsed;
90
+ const write = (0, _react.useCallback)(next => {
91
+ const raw = JSON.stringify(next);
92
+ lastWrittenRef.current = {
93
+ raw,
94
+ value: next
95
+ };
96
+ onSave(raw).catch(e => {
97
+ // The write was refused (the Pro gate, a dead backend) — the base
98
+ // must fall back to what's actually stored, or the tree keeps
99
+ // showing an edit that never landed.
100
+ lastWrittenRef.current = null;
101
+ _reactNative.Alert.alert("Couldn't save", e instanceof Error ? e.message : String(e));
102
+ });
103
+ }, [onSave]);
104
+ const baseRef = (0, _react.useRef)(baseValue);
105
+ baseRef.current = baseValue;
106
+ const writer = (0, _react.useMemo)(() => {
107
+ if (!canEdit) return undefined;
108
+ return {
109
+ update: (path, value) => {
110
+ const next = path.length === 0 ? value : (0, _dataViewer.updateNestedDataByPath)(baseRef.current, path, value);
111
+ write(next);
112
+ },
113
+ remove: path => {
114
+ if (path.length === 0) return;
115
+ write((0, _dataViewer.deleteNestedDataByPath)(baseRef.current, path));
116
+ }
117
+ // Storage replaces the whole key on every write, so any node may go.
118
+ };
119
+ }, [canEdit, write]);
120
+ const showHousekeeping = onTogglePin || onHideKey || canEdit && onDeleteKey;
121
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
122
+ style: styles.screen,
123
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
124
+ testID: `${DETAIL_ID}-key`,
125
+ accessibilityLabel: storageKey.key
126
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
127
+ testID: `${DETAIL_ID}-scroll`,
128
+ style: styles.scroll,
129
+ contentContainerStyle: [styles.scrollContent, {
130
+ paddingBottom: 24 + bottomInset
131
+ }],
132
+ keyboardShouldPersistTaps: "handled",
133
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
134
+ style: styles.section,
135
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
136
+ style: styles.editorContainer,
137
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
138
+ style: styles.sectionHeaderRow,
139
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
140
+ testID: `${DETAIL_ID}-editor-header`,
141
+ style: styles.sectionHeaderAccentText,
142
+ children: "Value Editor"
143
+ }), canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
144
+ onPress: () => setRawOpen(true),
145
+ testID: `${DETAIL_ID}-raw`,
146
+ accessibilityRole: "button",
147
+ accessibilityLabel: "Edit raw JSON",
148
+ style: styles.rawButton,
149
+ hitSlop: {
150
+ top: 6,
151
+ bottom: 6,
152
+ left: 6,
153
+ right: 6
154
+ },
155
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
156
+ size: 13,
157
+ color: _sharedUi.night.color.accent
158
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
159
+ style: styles.rawButtonText,
160
+ children: "Raw"
161
+ })]
162
+ }) : null]
163
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
164
+ style: styles.sectionBody,
165
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.LiveExplorer, {
166
+ editable: canEdit,
167
+ label: "Value",
168
+ value: baseValue,
169
+ defaultExpanded: ["Value"],
170
+ writer: writer,
171
+ dataVersion: rawVersion,
172
+ testIDForPath: detailExplorerTestID,
173
+ debounceMs: TYPING_COMMIT_MS
174
+ }), canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
175
+ style: styles.editorHint,
176
+ children: "Edits write to the device as you make them \u2014 undo lives in the key's history."
177
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
178
+ style: styles.blockedText,
179
+ testID: `${DETAIL_ID}-edit-blocked`,
180
+ children: editBlockedReason
181
+ })]
182
+ })]
183
+ })
184
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
185
+ style: styles.section,
186
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
187
+ style: styles.infoContainer,
188
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
189
+ style: styles.sectionHeaderPlain,
190
+ children: "Key Details"
191
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
192
+ style: styles.infoBody,
193
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
194
+ style: styles.infoRow,
195
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
196
+ style: styles.infoLabel,
197
+ children: "Status:"
198
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
199
+ color: config.color,
200
+ testID: `${DETAIL_ID}-status`,
201
+ children: config.label
202
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
203
+ style: styles.infoDim,
204
+ children: config.sublabel
205
+ })]
206
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
207
+ style: styles.infoRow,
208
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
209
+ style: styles.infoLabel,
210
+ children: "Storage:"
211
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
212
+ color: (0, _StorageKeyRow.getStorageTypeColor)(storageKey.storageType),
213
+ children: storageTypeLabel
214
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
215
+ style: styles.infoDim,
216
+ children: (0, _valueType.getValueTypeLabel)(storageKey.value)
217
+ })]
218
+ }), storageKey.storageType === "mmkv" && storageKey.instanceId && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
219
+ style: styles.infoRow,
220
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
221
+ style: styles.infoLabel,
222
+ children: "Instance:"
223
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
224
+ color: getInstanceColor(storageKey.instanceId),
225
+ icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.HardDrive, {
226
+ size: 9,
227
+ color: getInstanceColor(storageKey.instanceId)
228
+ }),
229
+ children: storageKey.instanceId
230
+ })]
231
+ }), eventCount != null && eventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
232
+ style: styles.infoRow,
233
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
234
+ style: styles.infoLabel,
235
+ children: "Updates:"
236
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
237
+ color: _sharedUi.macOSColors.semantic.warning,
238
+ children: String(eventCount)
239
+ }), eventCount > 1 && onViewHistory && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
240
+ onPress: onViewHistory,
241
+ testID: "buoy-storage-key-history",
242
+ accessibilityLabel: `View history for ${storageKey.key}`,
243
+ accessibilityRole: "button",
244
+ style: styles.inlineLinkButton,
245
+ hitSlop: {
246
+ top: 6,
247
+ bottom: 6,
248
+ left: 6,
249
+ right: 6
250
+ },
251
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
252
+ style: styles.inlineLinkText,
253
+ children: "view history \u2192"
254
+ })
255
+ })]
256
+ }), storageKey.status === "required_wrong_type" && storageKey.expectedType && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
257
+ style: styles.infoRow,
258
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
259
+ style: styles.infoLabel,
260
+ children: "Expected:"
261
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TypeBadge, {
262
+ type: storageKey.expectedType
263
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
264
+ style: styles.infoDim,
265
+ children: ["got ", (0, _valueType.getValueTypeLabel)(storageKey.value)]
266
+ })]
267
+ }), storageKey.status === "required_wrong_value" && storageKey.expectedValue && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
268
+ style: styles.infoRow,
269
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
270
+ style: styles.infoLabel,
271
+ children: "Expected:"
272
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
273
+ style: styles.infoExpected,
274
+ children: String(storageKey.expectedValue)
275
+ })]
276
+ }), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
277
+ style: styles.infoRow,
278
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
279
+ style: styles.infoLabel,
280
+ children: "Info:"
281
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
282
+ style: styles.infoDescription,
283
+ children: storageKey.description
284
+ })]
285
+ }) : null]
286
+ })]
287
+ })
288
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
289
+ style: styles.section,
290
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
291
+ style: styles.infoContainer,
292
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
293
+ style: styles.sectionHeaderPlain,
294
+ children: "Value Explorer"
295
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
296
+ style: styles.viewerBody,
297
+ testID: `${DETAIL_ID}-viewer`,
298
+ children: baseValue !== null && typeof baseValue === "object" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
299
+ title: "",
300
+ data: baseValue,
301
+ showTypeFilter: true,
302
+ rawMode: true,
303
+ initialExpanded: false
304
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
305
+ style: styles.rawValueText,
306
+ selectable: true,
307
+ children: propRaw ?? "undefined"
308
+ })
309
+ })]
310
+ })
311
+ }), showHousekeeping ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
312
+ style: styles.actionRow,
313
+ children: [onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
314
+ style: [styles.actionChip, isPinned && styles.actionChipActive],
315
+ testID: "buoy-storage-key-pin",
316
+ accessibilityLabel: isPinned ? `Unpin ${storageKey.key}` : `Pin ${storageKey.key} to top`,
317
+ accessibilityRole: "button",
318
+ onPress: () => onTogglePin(storageKey.key),
319
+ hitSlop: {
320
+ top: 6,
321
+ bottom: 6,
322
+ left: 6,
323
+ right: 6
324
+ },
325
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Pin, {
326
+ size: 12,
327
+ active: isPinned,
328
+ color: _sharedUi.night.color.accent
329
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
330
+ style: [styles.actionChipText, {
331
+ color: _sharedUi.night.color.accent
332
+ }],
333
+ children: isPinned ? "Unpin" : "Pin to top"
334
+ })]
335
+ }) : null, onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
336
+ style: styles.actionChip,
337
+ testID: "buoy-storage-key-hide",
338
+ accessibilityLabel: `Hide ${storageKey.key} from list`,
339
+ accessibilityRole: "button",
340
+ onPress: () => onHideKey(storageKey),
341
+ hitSlop: {
342
+ top: 6,
343
+ bottom: 6,
344
+ left: 6,
345
+ right: 6
346
+ },
347
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EyeOff, {
348
+ size: 12,
349
+ color: _sharedUi.night.color.warning
350
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
351
+ style: [styles.actionChipText, {
352
+ color: _sharedUi.night.color.warning
353
+ }],
354
+ children: "Hide from list"
355
+ })]
356
+ }) : null, canEdit && onDeleteKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
357
+ style: [styles.actionChip, styles.dangerChip],
358
+ testID: "buoy-storage-key-delete",
359
+ accessibilityLabel: `Delete key ${storageKey.key}`,
360
+ accessibilityRole: "button",
361
+ onPress: () => onDeleteKey(storageKey),
362
+ hitSlop: {
363
+ top: 6,
364
+ bottom: 6,
365
+ left: 6,
366
+ right: 6
367
+ },
368
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
369
+ size: 12,
370
+ color: _sharedUi.night.color.danger
371
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
372
+ style: [styles.actionChipText, {
373
+ color: _sharedUi.night.color.danger
374
+ }],
375
+ children: "Delete key"
376
+ })]
377
+ }) : null]
378
+ }) : null]
379
+ }), rawOpen && canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.ValueEditModal, {
380
+ path: [],
381
+ value: baseValue,
382
+ onSave: next => {
383
+ write(next);
384
+ setRawVersion(v => v + 1);
385
+ setRawOpen(false);
386
+ },
387
+ onCancel: () => setRawOpen(false),
388
+ saveLabel: "Apply",
389
+ testIDPrefix: "buoy-storage-value"
390
+ }) : null]
391
+ });
392
+ }
393
+ const styles = _reactNative.StyleSheet.create({
394
+ screen: {
395
+ flex: 1,
396
+ minHeight: 0
397
+ // Transparent — this is the modal's body, and the modal paints its own
398
+ // background (the same contract as the Zustand detail screen).
399
+ },
400
+ scroll: {
401
+ flex: 1
402
+ },
403
+ scrollContent: {
404
+ paddingHorizontal: 8,
405
+ paddingTop: 8
406
+ },
407
+ section: {
408
+ marginBottom: 16
409
+ },
410
+ editorContainer: {
411
+ minWidth: 200,
412
+ backgroundColor: _sharedUi.night.color.surface,
413
+ borderRadius: 6,
414
+ borderWidth: 1,
415
+ borderColor: _sharedUi.night.color.accent + "4D",
416
+ overflow: "hidden",
417
+ shadowColor: _sharedUi.night.color.accent,
418
+ shadowOffset: {
419
+ width: 0,
420
+ height: 0
421
+ },
422
+ shadowOpacity: 0.1,
423
+ shadowRadius: 6
424
+ },
425
+ sectionHeaderRow: {
426
+ flexDirection: "row",
427
+ alignItems: "center",
428
+ justifyContent: "space-between",
429
+ backgroundColor: _sharedUi.night.color.accent + "1A",
430
+ paddingHorizontal: 12,
431
+ paddingVertical: 8,
432
+ borderBottomWidth: 1,
433
+ borderBottomColor: _sharedUi.night.color.accent + "33"
434
+ },
435
+ sectionHeaderAccentText: {
436
+ fontWeight: "600",
437
+ fontSize: 12,
438
+ color: _sharedUi.night.color.accent,
439
+ letterSpacing: 0.5,
440
+ textTransform: "uppercase",
441
+ fontFamily: "monospace"
442
+ },
443
+ rawButton: {
444
+ flexDirection: "row",
445
+ alignItems: "center",
446
+ gap: 4
447
+ },
448
+ rawButtonText: {
449
+ color: _sharedUi.night.color.accent,
450
+ fontSize: 11,
451
+ fontWeight: "700"
452
+ },
453
+ sectionBody: {
454
+ padding: 8
455
+ },
456
+ editorHint: {
457
+ color: _sharedUi.night.color.textTertiary,
458
+ fontSize: 10,
459
+ paddingTop: 8,
460
+ paddingHorizontal: 4,
461
+ lineHeight: 14
462
+ },
463
+ blockedText: {
464
+ color: _sharedUi.night.color.textSecondary,
465
+ fontSize: 12,
466
+ padding: 12,
467
+ lineHeight: 18
468
+ },
469
+ infoContainer: {
470
+ minWidth: 200,
471
+ backgroundColor: _sharedUi.night.color.surface,
472
+ borderRadius: 6,
473
+ borderWidth: 1,
474
+ borderColor: _sharedUi.night.color.border,
475
+ overflow: "hidden"
476
+ },
477
+ sectionHeaderPlain: {
478
+ backgroundColor: _sharedUi.night.color.surfaceElevated,
479
+ paddingHorizontal: 12,
480
+ paddingVertical: 8,
481
+ fontWeight: "600",
482
+ fontSize: 12,
483
+ color: _sharedUi.night.color.textSecondary,
484
+ borderBottomWidth: 1,
485
+ borderBottomColor: _sharedUi.night.color.border,
486
+ letterSpacing: 0.5,
487
+ textTransform: "uppercase",
488
+ fontFamily: "monospace"
489
+ },
490
+ infoBody: {
491
+ padding: 12,
492
+ gap: 10
493
+ },
494
+ infoRow: {
495
+ flexDirection: "row",
496
+ alignItems: "center",
497
+ gap: 8
498
+ },
499
+ infoLabel: {
500
+ fontSize: 10,
501
+ color: _sharedUi.night.color.textTertiary,
502
+ fontWeight: "600",
503
+ minWidth: 64,
504
+ fontFamily: "monospace"
505
+ },
506
+ infoDim: {
507
+ color: _sharedUi.night.color.textTertiary,
508
+ fontSize: 10,
509
+ fontFamily: "monospace"
510
+ },
511
+ infoExpected: {
512
+ fontSize: 11,
513
+ color: _sharedUi.macOSColors.semantic.warning,
514
+ fontFamily: "monospace",
515
+ flex: 1
516
+ },
517
+ infoDescription: {
518
+ fontSize: 11,
519
+ color: _sharedUi.night.color.textSecondary,
520
+ flex: 1
521
+ },
522
+ rawValueText: {
523
+ color: _sharedUi.night.color.textSecondary,
524
+ fontSize: 11,
525
+ fontFamily: "monospace",
526
+ lineHeight: 16,
527
+ padding: 12
528
+ },
529
+ viewerBody: {
530
+ backgroundColor: _sharedUi.night.color.bg,
531
+ minHeight: 48
532
+ },
533
+ inlineLinkButton: {
534
+ marginLeft: 4
535
+ },
536
+ inlineLinkText: {
537
+ fontSize: 10,
538
+ fontWeight: "600",
539
+ fontFamily: "monospace",
540
+ color: _sharedUi.night.color.accent
541
+ },
542
+ actionRow: {
543
+ flexDirection: "row",
544
+ alignItems: "center",
545
+ flexWrap: "wrap",
546
+ gap: 8,
547
+ paddingHorizontal: 4
548
+ },
549
+ actionChip: {
550
+ flexDirection: "row",
551
+ alignItems: "center",
552
+ gap: 6,
553
+ paddingVertical: 6,
554
+ paddingHorizontal: 10,
555
+ borderRadius: 6,
556
+ backgroundColor: _sharedUi.night.color.surface,
557
+ borderWidth: 1,
558
+ borderColor: _sharedUi.night.color.border
559
+ },
560
+ dangerChip: {
561
+ borderColor: _sharedUi.night.color.danger + "55"
562
+ },
563
+ actionChipActive: {
564
+ backgroundColor: _sharedUi.night.color.accentSoft,
565
+ borderColor: _sharedUi.night.color.accentBorderStrong
566
+ },
567
+ actionChipText: {
568
+ fontSize: 11,
569
+ fontWeight: "600",
570
+ fontFamily: "monospace"
571
+ }
572
+ });
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.StorageKeyRow = void 0;
6
+ exports.getStorageTypeColor = exports.getStatusConfig = exports.StorageKeyRow = void 0;
7
7
  var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
@@ -33,6 +33,8 @@ function getInstanceColor(instanceId) {
33
33
  const hash = instanceId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
34
34
  return INSTANCE_COLORS[hash % INSTANCE_COLORS.length];
35
35
  }
36
+ /** Exported for the key DETAIL screen, so card and detail can't disagree
37
+ * about what a status is called or what color it wears. */
36
38
  const getStatusConfig = status => {
37
39
  switch (status) {
38
40
  case "required_present":
@@ -65,8 +67,17 @@ const getStatusConfig = status => {
65
67
  color: _sharedUi.macOSColors.semantic.debug,
66
68
  sublabel: "Optional"
67
69
  };
70
+ // Runtime data can carry a status the union doesn't know (a stale
71
+ // snapshot, a newer adapter) — a devtool must render it, not redbox.
72
+ default:
73
+ return {
74
+ label: "Set",
75
+ color: _sharedUi.macOSColors.text.secondary,
76
+ sublabel: String(status)
77
+ };
68
78
  }
69
79
  };
80
+ exports.getStatusConfig = getStatusConfig;
70
81
  const formatValue = value => {
71
82
  if (value === undefined || value === null) {
72
83
  return "undefined";
@@ -377,7 +388,7 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
377
388
  testID: `buoy-storage-row-${storageKey.storageType}-${storageKey.key}`,
378
389
  accessibilityLabel: `${storageKey.storageType} ${storageKey.key}`
379
390
  }), eventCount != null && eventCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
380
- style: [styles.absCountBadge, {
391
+ style: [styles.absCountBadge, isExpanded && styles.absCountBadgeExpanded, {
381
392
  backgroundColor: _sharedUi.macOSColors.semantic.warning + "22",
382
393
  borderColor: _sharedUi.macOSColors.semantic.warning + "55"
383
394
  }],
@@ -392,6 +403,8 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
392
403
  })]
393
404
  });
394
405
  });
406
+
407
+ /** Exported for the key DETAIL screen — same reason as getStatusConfig. */
395
408
  const getStorageTypeColor = storageType => {
396
409
  switch (storageType) {
397
410
  case "mmkv":
@@ -404,6 +417,7 @@ const getStorageTypeColor = storageType => {
404
417
  return _sharedUi.macOSColors.text.secondary;
405
418
  }
406
419
  };
420
+ exports.getStorageTypeColor = getStorageTypeColor;
407
421
  const styles = _reactNative.StyleSheet.create({
408
422
  badgeRow: {
409
423
  flexDirection: "row",
@@ -478,6 +492,11 @@ const styles = _reactNative.StyleSheet.create({
478
492
  borderWidth: 1,
479
493
  zIndex: 1
480
494
  },
495
+ // The expanded CompactRow grows its wrapper margin from 3 to 12, so the
496
+ // badge has to drop with it or it floats above the card border.
497
+ absCountBadgeExpanded: {
498
+ top: 13
499
+ },
481
500
  absCountText: {
482
501
  fontSize: 9,
483
502
  fontWeight: "700",