@buoy-gg/storage 7.0.4 → 7.0.6

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
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.StorageFilterCards = StorageFilterCards;
7
7
  var _reactNative = require("react-native");
8
+ var _StorageTypeFilterBar = require("./StorageTypeFilterBar");
8
9
  var _sharedUi = require("@buoy-gg/shared-ui");
9
10
  var _mmkvAvailability = require("../utils/mmkvAvailability");
10
11
  var _jsxRuntime = require("react/jsx-runtime");
@@ -22,7 +23,8 @@ function StorageFilterCards({
22
23
  activeFilter = "all",
23
24
  onFilterChange,
24
25
  activeStorageType = "all",
25
- onStorageTypeChange
26
+ onStorageTypeChange,
27
+ showStatus = true
26
28
  }) {
27
29
  // Use tabStats if provided (for filtered counts), otherwise fall back to stats
28
30
  const displayStats = tabStats || {
@@ -36,7 +38,7 @@ function StorageFilterCards({
36
38
  };
37
39
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
38
40
  style: styles.container,
39
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
41
+ children: [showStatus ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
40
42
  style: styles.filtersRow,
41
43
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
42
44
  style: [styles.filterChip, activeFilter === "all" && [styles.filterChipActive, {
@@ -90,101 +92,15 @@ function StorageFilterCards({
90
92
  children: "Issues"
91
93
  })]
92
94
  })]
93
- }), onStorageTypeChange && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
94
- style: styles.typesRow,
95
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
96
- style: [styles.typePill, {
97
- borderColor: _sharedUi.macOSColors.border.default
98
- }, activeStorageType === "all" && [styles.typePillActive, {
99
- backgroundColor: _sharedUi.macOSColors.background.hover,
100
- borderColor: _sharedUi.macOSColors.border.hover
101
- }]],
102
- onPress: () => onStorageTypeChange?.("all"),
103
- activeOpacity: 0.8,
104
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
105
- style: [styles.typePillLabel, activeStorageType === "all" && {
106
- color: _sharedUi.macOSColors.text.primary,
107
- fontWeight: "600"
108
- }],
109
- children: "All Types"
110
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
111
- style: [styles.typePillValue, activeStorageType === "all" && {
112
- color: _sharedUi.macOSColors.text.primary
113
- }, activeStorageType !== "all" && {
114
- color: _sharedUi.macOSColors.text.muted
115
- }],
116
- children: displayStats.totalCount
117
- })]
118
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
119
- style: [styles.typePill, {
120
- borderColor: _sharedUi.macOSColors.border.default
121
- }, activeStorageType === "async" && [styles.typePillActive, {
122
- backgroundColor: _sharedUi.macOSColors.semantic.warning + "15",
123
- borderColor: _sharedUi.macOSColors.semantic.warning + "40"
124
- }]],
125
- onPress: () => onStorageTypeChange?.("async"),
126
- activeOpacity: 0.8,
127
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
128
- style: [styles.typePillLabel, activeStorageType === "async" && {
129
- color: _sharedUi.macOSColors.semantic.warning,
130
- fontWeight: "600"
131
- }],
132
- children: "Async"
133
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
134
- style: [styles.typePillValue, activeStorageType === "async" ? {
135
- color: _sharedUi.macOSColors.semantic.warning
136
- } : {
137
- color: _sharedUi.macOSColors.text.muted
138
- }],
139
- children: displayStats.asyncCount
140
- })]
141
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
142
- style: [styles.typePill, {
143
- borderColor: _sharedUi.macOSColors.border.default
144
- }, activeStorageType === "mmkv" && [styles.typePillActive, {
145
- backgroundColor: _sharedUi.macOSColors.semantic.info + "15",
146
- borderColor: _sharedUi.macOSColors.semantic.info + "40"
147
- }]],
148
- onPress: () => onStorageTypeChange?.("mmkv"),
149
- activeOpacity: 0.8,
150
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
151
- style: [styles.typePillLabel, activeStorageType === "mmkv" && {
152
- color: _sharedUi.macOSColors.semantic.info,
153
- fontWeight: "600"
154
- }],
155
- children: "MMKV"
156
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
157
- style: [styles.typePillValue, activeStorageType === "mmkv" ? {
158
- color: _sharedUi.macOSColors.semantic.info
159
- } : {
160
- color: _sharedUi.macOSColors.text.muted
161
- }],
162
- children: displayStats.mmkvCount
163
- })]
164
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
165
- style: [styles.typePill, {
166
- borderColor: _sharedUi.macOSColors.border.default
167
- }, activeStorageType === "secure" && [styles.typePillActive, {
168
- backgroundColor: _sharedUi.macOSColors.semantic.success + "15",
169
- borderColor: _sharedUi.macOSColors.semantic.success + "40"
170
- }]],
171
- onPress: () => onStorageTypeChange?.("secure"),
172
- activeOpacity: 0.8,
173
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
174
- style: [styles.typePillLabel, activeStorageType === "secure" && {
175
- color: _sharedUi.macOSColors.semantic.success,
176
- fontWeight: "600"
177
- }],
178
- children: "Secure"
179
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
180
- style: [styles.typePillValue, activeStorageType === "secure" ? {
181
- color: _sharedUi.macOSColors.semantic.success
182
- } : {
183
- color: _sharedUi.macOSColors.text.muted
184
- }],
185
- children: displayStats.secureCount
186
- })]
187
- })]
95
+ }) : null, onStorageTypeChange && /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageTypeFilterBar.StorageTypeFilterBar, {
96
+ active: activeStorageType,
97
+ onChange: onStorageTypeChange,
98
+ counts: {
99
+ all: displayStats.totalCount,
100
+ async: displayStats.asyncCount,
101
+ mmkv: displayStats.mmkvCount,
102
+ secure: displayStats.secureCount
103
+ }
188
104
  })]
189
105
  });
190
106
  }
@@ -302,43 +218,5 @@ const styles = _reactNative.StyleSheet.create({
302
218
  fontWeight: "600",
303
219
  fontFamily: "monospace",
304
220
  lineHeight: 20
305
- },
306
- // Storage type pills
307
- typesRow: {
308
- flexDirection: "row",
309
- gap: 8,
310
- marginTop: 2
311
- },
312
- typePill: {
313
- flex: 1,
314
- backgroundColor: _sharedUi.macOSColors.background.input + "80",
315
- borderRadius: 8,
316
- borderWidth: 1,
317
- borderColor: _sharedUi.macOSColors.border.default + "30",
318
- paddingVertical: 6,
319
- paddingHorizontal: 8,
320
- alignItems: "center",
321
- justifyContent: "center",
322
- minHeight: 32
323
- },
324
- typePillActive: {
325
- borderWidth: 1,
326
- transform: [{
327
- scale: 1.005
328
- }]
329
- },
330
- typePillLabel: {
331
- fontSize: 9,
332
- color: _sharedUi.macOSColors.text.secondary,
333
- textTransform: "uppercase",
334
- letterSpacing: 0.4,
335
- fontWeight: "500",
336
- marginBottom: 1
337
- },
338
- typePillValue: {
339
- fontSize: 12,
340
- fontWeight: "600",
341
- fontFamily: "monospace",
342
- lineHeight: 14
343
221
  }
344
222
  });
@@ -347,7 +347,7 @@ const styles = _reactNative.StyleSheet.create({
347
347
  gap: 4
348
348
  },
349
349
  storageKeyText: {
350
- color: "#FFFFFF",
350
+ color: _sharedUi.macOSColors.text.primary,
351
351
  fontWeight: "500",
352
352
  fontSize: 12,
353
353
  flexWrap: "wrap",
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.StorageKeyEditorScreen = StorageKeyEditorScreen;
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 _useKeyEditor = require("../hooks/useKeyEditor");
12
+ var _StorageValueEditModal = require("./StorageValueEditModal");
13
+ var _StorageScalarKeyEditor = require("./StorageScalarKeyEditor");
14
+ var _storageQueryUtils = require("../utils/storageQueryUtils");
15
+ var _valueType = require("../utils/valueType");
16
+ var _jsxRuntime = require("react/jsx-runtime");
17
+ /**
18
+ * Editing a key, on a screen of its own.
19
+ *
20
+ * Editing used to happen inside the expanded card in the key list: a tree a few
21
+ * rows tall, an action bar wrapped onto three lines, and the rest of the list
22
+ * still scrolling underneath. Every control was competing for the same ~200px.
23
+ *
24
+ * So the card now offers ONE edit button and the actual work happens here, with
25
+ * the whole viewport: the tree gets the height, the six structural actions get
26
+ * thumb-sized tiles docked at the bottom where they're always in the same
27
+ * place, and manual JSON editing — the escape hatch, not the main path — moves
28
+ * to a single button in the top-right corner.
29
+ *
30
+ * Typing a value happens in a modal pinned to the top of the screen, over the
31
+ * tree rather than instead of it — see {@link StorageValueEditModal}.
32
+ */
33
+
34
+ /** Props once the value has been un-stringified — see `parseStorageValue`. */
35
+
36
+ /**
37
+ * A container and a scalar are different editors, not one editor with a flag.
38
+ *
39
+ * A tree of one row is not a tree, so a scalar key skips the whole structural
40
+ * editor and gets just the value modal. Splitting here keeps each editor's
41
+ * state to what it actually needs — see {@link StorageScalarKeyEditor}.
42
+ */
43
+ function StorageKeyEditorScreen(props) {
44
+ // Memo justified: this is a JSON.parse of the whole stored value, and the
45
+ // browser underneath re-renders on every storage event the app emits.
46
+ const raw = props.storageKey.value;
47
+ const value = (0, _react.useMemo)(() => (0, _valueType.parseStorageValue)(raw), [raw]);
48
+ return value !== null && typeof value === "object" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StorageTreeEditor, {
49
+ ...props,
50
+ value: value
51
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageScalarKeyEditor.StorageScalarKeyEditor, {
52
+ storageKey: props.storageKey,
53
+ value: value,
54
+ onSave: props.onSave,
55
+ onClose: props.onClose
56
+ });
57
+ }
58
+
59
+ /* ------------------------------------------------------------------ *
60
+ * Containers — tree + structural actions, buffered into a draft.
61
+ * ------------------------------------------------------------------ */
62
+
63
+ function StorageTreeEditor({
64
+ storageKey,
65
+ value,
66
+ onSave,
67
+ onClose,
68
+ applySafeAreaInset
69
+ }) {
70
+ const insets = (0, _sharedUi.useSafeAreaInsets)();
71
+ const bottomInset = applySafeAreaInset ? insets.bottom : 0;
72
+ const editor = (0, _useKeyEditor.useKeyEditor)({
73
+ value,
74
+ save: onSave,
75
+ onClose,
76
+ confirmDiscard: choices => _reactNative.Alert.alert("Unsaved changes", `"${storageKey.key}" has edits that haven't been written to the device.`, [{
77
+ text: "Keep editing",
78
+ style: "cancel"
79
+ }, {
80
+ text: "Discard",
81
+ style: "destructive",
82
+ onPress: choices.discard
83
+ }, {
84
+ text: "Save",
85
+ onPress: choices.save
86
+ }])
87
+ });
88
+ const {
89
+ draft,
90
+ selection,
91
+ valuePath
92
+ } = editor;
93
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
94
+ style: styles.screen,
95
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(EditorHeader, {
96
+ storageKey: storageKey,
97
+ state: draft.isSaving ? "Saving…" : draft.isDirty ? "Draft · unsaved" : "In sync with device",
98
+ dirty: draft.isDirty,
99
+ onClose: editor.requestClose
100
+ // Manual editing of the WHOLE value — everything else on this screen is
101
+ // structural, so raw JSON gets a corner rather than a tile.
102
+ ,
103
+ onRaw: editor.openRawEditor
104
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
105
+ style: styles.metaRow,
106
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PillBadge, {
107
+ color: storageTypeColor(storageKey.storageType),
108
+ children: (0, _storageQueryUtils.getStorageTypeLabel)(storageKey.storageType)
109
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
110
+ style: styles.metaText,
111
+ children: summarize(draft.value)
112
+ })]
113
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
114
+ style: styles.treeScroll,
115
+ contentContainerStyle: styles.treeContent,
116
+ keyboardShouldPersistTaps: "handled",
117
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
118
+ style: styles.treeCard,
119
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
120
+ data: draft.value,
121
+ title: "",
122
+ rawMode: true,
123
+ initialExpanded: true
124
+ // Type filtering is a READING aid: the filtered view is a
125
+ // projection whose row paths don't address the real document, so
126
+ // the explorer stops reporting selection while one is active. An
127
+ // editor whose actions silently go dead isn't worth the filter.
128
+ ,
129
+ showTypeFilter: false,
130
+ onSelectionChange: editor.setSelection,
131
+ selectPath: draft.selectPath,
132
+ onRequestInlineEdit: sel => editor.openValueEditor(sel.path)
133
+ })
134
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
135
+ style: styles.treeHint,
136
+ children: "Tap a row to select it \xB7 double-tap to edit its value"
137
+ })]
138
+ }), draft.error ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
+ style: styles.error,
140
+ numberOfLines: 3,
141
+ children: draft.error
142
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
143
+ style: [styles.footer, {
144
+ paddingBottom: bottomInset
145
+ }],
146
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataTreeActionDock, {
147
+ root: draft.value,
148
+ selection: selection,
149
+ onEdit: draft.apply,
150
+ onSelectPath: draft.selectAt
151
+ // Booleans never reach this — the dock applies their flip directly.
152
+ ,
153
+ onEditValue: () => editor.openValueEditor(selection?.path ?? [])
154
+ }), draft.isDirty ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
155
+ style: styles.commitRow,
156
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
157
+ onPress: draft.discard,
158
+ disabled: draft.isSaving,
159
+ accessibilityLabel: "Discard changes",
160
+ style: styles.discardButton,
161
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
162
+ style: styles.discardText,
163
+ children: "Discard"
164
+ })
165
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
166
+ onPress: editor.saveAndClose,
167
+ disabled: draft.isSaving,
168
+ accessibilityLabel: "Save changes",
169
+ style: [styles.saveButton, draft.isSaving && styles.saveDisabled],
170
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
171
+ style: styles.saveText,
172
+ children: draft.isSaving ? "Saving…" : "Save changes"
173
+ })
174
+ })]
175
+ }) : null]
176
+ }), valuePath ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageValueEditModal.StorageValueEditModal, {
177
+ path: valuePath,
178
+ value: (0, _dataViewer.getAtPath)(draft.value, valuePath),
179
+ onSave: editor.applyValue,
180
+ onCancel: editor.closeValueEditor
181
+ // It only reaches the draft here — the single write happens on Save.
182
+ ,
183
+ saveLabel: "Apply"
184
+ }) : null]
185
+ });
186
+ }
187
+
188
+ /* ------------------------------------------------------------------ *
189
+ * Shared pieces
190
+ * ------------------------------------------------------------------ */
191
+
192
+ function EditorHeader({
193
+ storageKey,
194
+ state,
195
+ dirty,
196
+ onClose,
197
+ onRaw
198
+ }) {
199
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
200
+ style: styles.header,
201
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
202
+ onPress: onClose,
203
+ accessibilityLabel: "Close editor",
204
+ style: styles.headerButton,
205
+ hitSlop: {
206
+ top: 8,
207
+ bottom: 8,
208
+ left: 8,
209
+ right: 8
210
+ },
211
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
212
+ size: 16,
213
+ color: _sharedUi.macOSColors.text.secondary
214
+ })
215
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
216
+ style: styles.headerTitle,
217
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
218
+ style: styles.headerKey,
219
+ numberOfLines: 1,
220
+ children: storageKey.key
221
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
222
+ style: [styles.headerState, dirty && styles.headerDirty],
223
+ children: state
224
+ })]
225
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
226
+ onPress: onRaw,
227
+ accessibilityLabel: "Edit raw JSON",
228
+ style: styles.headerButton,
229
+ hitSlop: {
230
+ top: 8,
231
+ bottom: 8,
232
+ left: 8,
233
+ right: 8
234
+ },
235
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
236
+ size: 16,
237
+ color: _sharedUi.macOSColors.semantic.info
238
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
239
+ style: styles.headerRawText,
240
+ children: "Raw"
241
+ })]
242
+ })]
243
+ });
244
+ }
245
+
246
+ /** `array · 3 items` — what the header says the draft currently is. */
247
+ function summarize(value) {
248
+ if (Array.isArray(value)) {
249
+ return `array · ${value.length} item${value.length === 1 ? "" : "s"}`;
250
+ }
251
+ if (value !== null && typeof value === "object") {
252
+ const n = Object.keys(value).length;
253
+ return `object · ${n} key${n === 1 ? "" : "s"}`;
254
+ }
255
+ return typeof value;
256
+ }
257
+ function storageTypeColor(storageType) {
258
+ switch (storageType) {
259
+ case "mmkv":
260
+ return _sharedUi.macOSColors.semantic.info;
261
+ case "async":
262
+ return _sharedUi.macOSColors.semantic.warning;
263
+ case "secure":
264
+ return _sharedUi.macOSColors.semantic.success;
265
+ default:
266
+ return _sharedUi.macOSColors.text.secondary;
267
+ }
268
+ }
269
+ const styles = _reactNative.StyleSheet.create({
270
+ screen: {
271
+ flex: 1,
272
+ backgroundColor: _sharedUi.macOSColors.background.base
273
+ },
274
+ header: {
275
+ flexDirection: "row",
276
+ alignItems: "center",
277
+ gap: 8,
278
+ paddingHorizontal: 10,
279
+ paddingVertical: 8,
280
+ borderBottomWidth: 1,
281
+ borderBottomColor: _sharedUi.macOSColors.border.default
282
+ },
283
+ headerButton: {
284
+ flexDirection: "row",
285
+ alignItems: "center",
286
+ gap: 4,
287
+ paddingVertical: 6,
288
+ paddingHorizontal: 4
289
+ },
290
+ headerRawText: {
291
+ color: _sharedUi.macOSColors.semantic.info,
292
+ fontSize: 12,
293
+ fontWeight: "700"
294
+ },
295
+ headerTitle: {
296
+ flex: 1,
297
+ alignItems: "center",
298
+ gap: 2
299
+ },
300
+ headerKey: {
301
+ color: _sharedUi.macOSColors.text.primary,
302
+ fontSize: 13,
303
+ fontWeight: "600",
304
+ fontFamily: "monospace"
305
+ },
306
+ headerState: {
307
+ color: _sharedUi.macOSColors.text.muted,
308
+ fontSize: 10
309
+ },
310
+ headerDirty: {
311
+ color: _sharedUi.macOSColors.semantic.warning
312
+ },
313
+ metaRow: {
314
+ flexDirection: "row",
315
+ alignItems: "center",
316
+ gap: 8,
317
+ paddingHorizontal: 12,
318
+ paddingVertical: 8
319
+ },
320
+ metaText: {
321
+ color: _sharedUi.macOSColors.text.muted,
322
+ fontSize: 11,
323
+ fontFamily: "monospace"
324
+ },
325
+ treeScroll: {
326
+ flex: 1
327
+ },
328
+ treeContent: {
329
+ paddingHorizontal: 12,
330
+ paddingBottom: 16
331
+ },
332
+ treeCard: {
333
+ backgroundColor: _sharedUi.macOSColors.background.card,
334
+ borderRadius: 10,
335
+ borderWidth: 1,
336
+ borderColor: _sharedUi.macOSColors.border.default,
337
+ padding: 6
338
+ },
339
+ treeHint: {
340
+ color: _sharedUi.macOSColors.text.muted,
341
+ fontSize: 10,
342
+ paddingTop: 8,
343
+ paddingHorizontal: 4
344
+ },
345
+ error: {
346
+ color: _sharedUi.macOSColors.semantic.error,
347
+ fontSize: 11,
348
+ fontFamily: "monospace",
349
+ paddingHorizontal: 12,
350
+ paddingBottom: 6
351
+ },
352
+ footer: {
353
+ backgroundColor: _sharedUi.macOSColors.background.card
354
+ },
355
+ commitRow: {
356
+ flexDirection: "row",
357
+ gap: 8,
358
+ paddingHorizontal: 10,
359
+ paddingBottom: 12,
360
+ paddingTop: 2
361
+ },
362
+ discardButton: {
363
+ flex: 1,
364
+ minHeight: 46,
365
+ alignItems: "center",
366
+ justifyContent: "center",
367
+ borderRadius: 12,
368
+ borderWidth: 1,
369
+ borderColor: _sharedUi.macOSColors.border.default
370
+ },
371
+ discardText: {
372
+ color: _sharedUi.macOSColors.text.secondary,
373
+ fontSize: 14,
374
+ fontWeight: "600"
375
+ },
376
+ saveButton: {
377
+ flex: 1.6,
378
+ minHeight: 46,
379
+ alignItems: "center",
380
+ justifyContent: "center",
381
+ borderRadius: 12,
382
+ backgroundColor: _sharedUi.macOSColors.semantic.success
383
+ },
384
+ saveDisabled: {
385
+ opacity: 0.5
386
+ },
387
+ saveText: {
388
+ color: _sharedUi.macOSColors.background.base,
389
+ fontSize: 15,
390
+ fontWeight: "700"
391
+ }
392
+ });
@@ -89,8 +89,12 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
89
89
  onViewHistory,
90
90
  onHideKey,
91
91
  isPinned = false,
92
- onTogglePin
92
+ onTogglePin,
93
+ onEditKey,
94
+ onToggleKey,
95
+ editBlockedReason
93
96
  }) {
97
+ const canEdit = onEditKey != null && editBlockedReason == null;
94
98
  const config = getStatusConfig(storageKey.status);
95
99
  const hasValue = storageKey.value !== undefined && storageKey.value !== null;
96
100
 
@@ -104,18 +108,12 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
104
108
  // Show storage type as secondary text
105
109
  const storageTypeLabel = (0, _storageQueryUtils.getStorageTypeLabel)(storageKey.storageType);
106
110
 
107
- // Check if value is JSON object/array for DataViewer
108
- const isJsonData = storageKey.value && (typeof storageKey.value === "object" || typeof storageKey.value === "string" && (storageKey.value.startsWith("{") || storageKey.value.startsWith("[")));
111
+ // Shared with the editor screen, so the card and the thing it opens can't
112
+ // disagree about whether a key holds a container.
113
+ const parsedValue = (0, _valueType.parseStorageValue)(storageKey.value);
109
114
 
110
- // Parse JSON string if needed
111
- let parsedValue = storageKey.value;
112
- if (typeof storageKey.value === "string" && isJsonData) {
113
- try {
114
- parsedValue = JSON.parse(storageKey.value);
115
- } catch {
116
- // Keep original if parse fails
117
- }
118
- }
115
+ /** The tree only renders for containers. */
116
+ const showsTree = parsedValue !== null && typeof parsedValue === "object";
119
117
 
120
118
  // Create expanded content for value and storage details
121
119
  const expandedContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -155,7 +153,7 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
155
153
  children: "view history \u2192"
156
154
  })
157
155
  })]
158
- }), isJsonData && typeof parsedValue === "object" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
156
+ }), showsTree ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
159
157
  style: styles.dataViewerContainer,
160
158
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
161
159
  data: parsedValue,
@@ -210,9 +208,34 @@ const StorageKeyRow = exports.StorageKeyRow = /*#__PURE__*/(0, _react.memo)(func
210
208
  style: styles.expandedDescription,
211
209
  children: storageKey.description
212
210
  })]
213
- }) : null, onTogglePin || onHideKey ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
211
+ }) : null, editBlockedReason ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
212
+ style: styles.expandedRow,
213
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
214
+ style: styles.expandedLabel,
215
+ children: "Edit:"
216
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
217
+ style: styles.expandedDescription,
218
+ children: editBlockedReason
219
+ })]
220
+ }) : null, onTogglePin || onHideKey || canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
214
221
  style: styles.actionRow,
215
- children: [onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
222
+ children: [canEdit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
223
+ style: styles.editButton,
224
+ onPress: () => isBoolean && onToggleKey ? onToggleKey(storageKey) : onEditKey?.(storageKey),
225
+ hitSlop: {
226
+ top: 6,
227
+ bottom: 6,
228
+ left: 6,
229
+ right: 6
230
+ },
231
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Edit3, {
232
+ size: 13,
233
+ color: _sharedUi.macOSColors.background.base
234
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
235
+ style: styles.editButtonText,
236
+ children: isBoolean && onToggleKey ? "Toggle" : "Edit"
237
+ })]
238
+ }) : null, onTogglePin ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
216
239
  style: [styles.actionChip, isPinned && styles.actionChipActive],
217
240
  onPress: () => onTogglePin(storageKey.key),
218
241
  hitSlop: {
@@ -434,6 +457,21 @@ const styles = _reactNative.StyleSheet.create({
434
457
  backgroundColor: _sharedUi.macOSColors.semantic.info + "15",
435
458
  borderColor: _sharedUi.macOSColors.semantic.info + "44"
436
459
  },
460
+ /** Filled, and first — the one action in this row that changes anything. */
461
+ editButton: {
462
+ flexDirection: "row",
463
+ alignItems: "center",
464
+ gap: 6,
465
+ paddingVertical: 8,
466
+ paddingHorizontal: 14,
467
+ borderRadius: 8,
468
+ backgroundColor: _sharedUi.macOSColors.semantic.success
469
+ },
470
+ editButtonText: {
471
+ color: _sharedUi.macOSColors.background.base,
472
+ fontSize: 12,
473
+ fontWeight: "700"
474
+ },
437
475
  actionChipText: {
438
476
  fontSize: 11,
439
477
  fontWeight: "600",