@buoy-gg/storage 1.7.7 → 2.1.1

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 (61) hide show
  1. package/lib/commonjs/index.js +219 -16
  2. package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  3. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +9 -23
  4. package/lib/commonjs/storage/components/SelectionActionBar.js +8 -22
  5. package/lib/commonjs/storage/components/StorageActionButtons.js +8 -22
  6. package/lib/commonjs/storage/components/StorageActions.js +8 -22
  7. package/lib/commonjs/storage/components/StorageEventActionButton.js +120 -0
  8. package/lib/commonjs/storage/components/StorageEventCard.js +112 -0
  9. package/lib/commonjs/storage/components/StorageEventDetailContent.js +331 -822
  10. package/lib/commonjs/storage/components/StorageModalWithTabs.js +43 -200
  11. package/lib/commonjs/storage/hooks/useStorageEvents.js +98 -0
  12. package/lib/commonjs/storage/index.js +111 -2
  13. package/lib/commonjs/storage/stores/storageEventStore.js +243 -0
  14. package/lib/commonjs/storage/utils/AsyncStorageListener.js +164 -35
  15. package/lib/commonjs/storage/utils/index.js +37 -0
  16. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +251 -0
  17. package/lib/module/index.js +74 -3
  18. package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  19. package/lib/module/storage/components/GameUIStorageBrowser.js +9 -23
  20. package/lib/module/storage/components/SelectionActionBar.js +9 -24
  21. package/lib/module/storage/components/StorageActionButtons.js +9 -24
  22. package/lib/module/storage/components/StorageActions.js +9 -24
  23. package/lib/module/storage/components/StorageEventActionButton.js +117 -0
  24. package/lib/module/storage/components/StorageEventCard.js +107 -0
  25. package/lib/module/storage/components/StorageEventDetailContent.js +332 -824
  26. package/lib/module/storage/components/StorageModalWithTabs.js +45 -202
  27. package/lib/module/storage/hooks/useStorageEvents.js +95 -0
  28. package/lib/module/storage/index.js +7 -1
  29. package/lib/module/storage/stores/storageEventStore.js +231 -0
  30. package/lib/module/storage/utils/AsyncStorageListener.js +159 -33
  31. package/lib/module/storage/utils/index.js +4 -1
  32. package/lib/module/storage/utils/storageTimeTravelUtils.js +245 -0
  33. package/lib/typescript/index.d.ts +36 -1
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts +1 -1
  36. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +1 -1
  37. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  38. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  39. package/lib/typescript/storage/components/StorageActionButtons.d.ts +0 -2
  40. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +1 -1
  41. package/lib/typescript/storage/components/StorageActions.d.ts.map +1 -1
  42. package/lib/typescript/storage/components/StorageEventActionButton.d.ts +37 -0
  43. package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +1 -0
  44. package/lib/typescript/storage/components/StorageEventCard.d.ts +40 -0
  45. package/lib/typescript/storage/components/StorageEventCard.d.ts.map +1 -0
  46. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +11 -3
  47. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
  48. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  49. package/lib/typescript/storage/hooks/useStorageEvents.d.ts +51 -0
  50. package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +1 -0
  51. package/lib/typescript/storage/index.d.ts +4 -0
  52. package/lib/typescript/storage/index.d.ts.map +1 -1
  53. package/lib/typescript/storage/stores/storageEventStore.d.ts +113 -0
  54. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -0
  55. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +38 -1
  56. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  57. package/lib/typescript/storage/utils/index.d.ts +2 -1
  58. package/lib/typescript/storage/utils/index.d.ts.map +1 -1
  59. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +35 -0
  60. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -0
  61. package/package.json +20 -4
@@ -9,377 +9,276 @@ var _reactNative = require("react-native");
9
9
  var _react = require("react");
10
10
  var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
11
11
  var _sharedUi = require("@buoy-gg/shared-ui");
12
+ var _license = require("@buoy-gg/license");
12
13
  var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
13
14
  var _ThemedSplitView = require("./DiffViewer/modes/ThemedSplitView");
14
15
  var _diffThemes = require("./DiffViewer/themes/diffThemes");
15
- var _lineDiff = require("../utils/lineDiff");
16
16
  var _TreeDiffViewer = require("./DiffViewer/TreeDiffViewer");
17
17
  var _storageActionHelpers = require("../utils/storageActionHelpers");
18
+ var _StorageEventActionButton = require("./StorageEventActionButton");
19
+ var _storageTimeTravelUtils = require("../utils/storageTimeTravelUtils");
18
20
  var _jsxRuntime = require("react/jsx-runtime");
19
21
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
20
- // Lazy load the license hooks to avoid circular dependencies
21
- let _useIsPro = null;
22
- let _licenseLoadAttempted = false;
23
- function loadLicenseModule() {
24
- if (_licenseLoadAttempted) return;
25
- _licenseLoadAttempted = true;
26
- try {
27
- const mod = require("@buoy-gg/license");
28
- if (mod) {
29
- _useIsPro = mod.useIsPro ?? null;
30
- }
31
- } catch {
32
- // License package not available
22
+ /**
23
+ * StorageEventDetailContent
24
+ *
25
+ * Detail view for storage events using the shared EventHistoryViewer component.
26
+ */
27
+
28
+ // Unified storage event type (supports both AsyncStorage and MMKV)
29
+
30
+ /**
31
+ * Get color for storage action
32
+ */
33
+ function getActionColor(action) {
34
+ switch (action) {
35
+ case "setItem":
36
+ case "multiSet":
37
+ return _sharedUi.macOSColors.semantic.success;
38
+ case "removeItem":
39
+ case "multiRemove":
40
+ case "clear":
41
+ return _sharedUi.macOSColors.semantic.error;
42
+ case "mergeItem":
43
+ case "multiMerge":
44
+ return _sharedUi.macOSColors.semantic.info;
45
+ default:
46
+ return _sharedUi.macOSColors.text.muted;
33
47
  }
34
48
  }
35
- function getUseIsPro() {
36
- loadLicenseModule();
37
- return _useIsPro ?? (() => false);
38
- }
39
49
 
40
- // Free tier limit for event history navigation
41
- const FREE_TIER_EVENT_HISTORY_LIMIT = 3;
42
-
43
- // Unified storage event type (supports both AsyncStorage and MMKV)
50
+ /**
51
+ * Get the type of a value
52
+ */
53
+ function getValueType(value) {
54
+ if (value === null) return "null";
55
+ if (value === undefined) return "undefined";
56
+ if (Array.isArray(value)) return "array";
57
+ return typeof value;
58
+ }
44
59
 
60
+ /**
61
+ * Format timestamp with milliseconds
62
+ */
63
+ function formatTimeWithMs(date) {
64
+ const h = String(date.getHours()).padStart(2, "0");
65
+ const m = String(date.getMinutes()).padStart(2, "0");
66
+ const s = String(date.getSeconds()).padStart(2, "0");
67
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
68
+ return `${h}:${m}:${s}.${ms}`;
69
+ }
45
70
  function StorageEventDetailContent({
46
71
  conversation,
47
72
  selectedEventIndex = 0,
48
73
  onEventIndexChange = () => {},
49
74
  disableInternalFooter = false
50
75
  }) {
51
- // Check Pro status internally
52
- const useIsPro = getUseIsPro();
53
- const isPro = useIsPro();
54
- // Internal view state - now managed internally instead of via props
55
- const [internalActiveView, setInternalActiveView] = (0, _react.useState)("current");
56
- // Compare-any-two state for Diff tab
76
+ // Pro feature gating
77
+ const isPro = (0, _license.useIsPro)();
78
+ const [showProModal, setShowProModal] = (0, _react.useState)(false);
79
+
80
+ // View state
81
+ const [activeView, setActiveView] = (0, _react.useState)("current");
82
+ const [diffMode, setDiffMode] = (0, _react.useState)("tree");
83
+
84
+ // Compare indices for diff view
57
85
  const [leftIndex, setLeftIndex] = (0, _react.useState)(Math.max(0, selectedEventIndex - 1));
58
86
  const [rightIndex, setRightIndex] = (0, _react.useState)(selectedEventIndex);
59
- const [isLeftPickerOpen, setIsLeftPickerOpen] = (0, _react.useState)(false);
60
- const [isRightPickerOpen, setIsRightPickerOpen] = (0, _react.useState)(false);
61
- const [diffViewerTab, setDiffViewerTab] = (0, _react.useState)("tree");
62
87
 
63
88
  // Track if preferences have been loaded
64
89
  const hasLoadedPreferences = (0, _react.useRef)(false);
65
90
 
91
+ // Get sorted events
92
+ const navigationItems = (0, _react.useMemo)(() => conversation.events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()), [conversation.events]);
93
+ const totalEvents = navigationItems.length;
94
+
66
95
  // Load saved preferences on mount
67
96
  (0, _react.useEffect)(() => {
68
97
  if (hasLoadedPreferences.current) return;
69
98
  const loadPreferences = async () => {
70
99
  try {
71
- // Load detail view preference (current/diff)
72
100
  const savedDetailView = await _asyncStorage.default.getItem(_sharedUi.devToolsStorageKeys.storage.detailView());
73
101
  if (savedDetailView === "current" || savedDetailView === "diff") {
74
- setInternalActiveView(savedDetailView);
102
+ setActiveView(savedDetailView);
75
103
  }
76
-
77
- // Load diff viewer mode preference (split/tree)
78
104
  const savedDiffMode = await _asyncStorage.default.getItem(_sharedUi.devToolsStorageKeys.storage.diffViewerMode());
79
105
  if (savedDiffMode === "split" || savedDiffMode === "tree") {
80
- setDiffViewerTab(savedDiffMode);
106
+ setDiffMode(savedDiffMode);
81
107
  }
82
108
  hasLoadedPreferences.current = true;
83
- } catch (error) {
84
- // Failed to load view preferences
109
+ } catch {
110
+ // Failed to load preferences
85
111
  }
86
112
  };
87
113
  loadPreferences();
88
114
  }, []);
89
115
 
90
- // Save detail view preference when changed
116
+ // Keep compare indices synced to selection
117
+ (0, _react.useEffect)(() => {
118
+ const newRight = Math.min(totalEvents - 1, Math.max(0, selectedEventIndex));
119
+ const newLeft = Math.max(0, Math.min(newRight - 1, selectedEventIndex - 1));
120
+ setLeftIndex(newLeft);
121
+ setRightIndex(newRight);
122
+ }, [selectedEventIndex, totalEvents]);
123
+
124
+ // Save view preference when changed
91
125
  const handleViewChange = (0, _react.useCallback)(async view => {
92
- setInternalActiveView(view);
126
+ setActiveView(view);
93
127
  try {
94
128
  await _asyncStorage.default.setItem(_sharedUi.devToolsStorageKeys.storage.detailView(), view);
95
- } catch (error) {
96
- // Failed to save detail view preference
129
+ } catch {
130
+ // Failed to save preference
97
131
  }
98
132
  }, []);
99
133
 
100
- // Save diff viewer mode preference when changed
134
+ // Save diff mode preference when changed
101
135
  const handleDiffModeChange = (0, _react.useCallback)(async mode => {
102
- setDiffViewerTab(mode);
136
+ setDiffMode(mode);
103
137
  try {
104
138
  await _asyncStorage.default.setItem(_sharedUi.devToolsStorageKeys.storage.diffViewerMode(), mode);
105
- } catch (error) {
106
- // Failed to save diff viewer mode preference
139
+ } catch {
140
+ // Failed to save preference
107
141
  }
108
142
  }, []);
109
- const getActionColor = action => {
110
- switch (action) {
111
- case "setItem":
112
- case "multiSet":
113
- return _sharedUi.macOSColors.semantic.success;
114
- case "removeItem":
115
- case "multiRemove":
116
- case "clear":
117
- return _sharedUi.macOSColors.semantic.error;
118
- case "mergeItem":
119
- case "multiMerge":
120
- return _sharedUi.macOSColors.semantic.info;
121
- default:
122
- return _sharedUi.macOSColors.text.muted;
143
+
144
+ // Get current event
145
+ const selectedEvent = navigationItems[selectedEventIndex];
146
+ const valueToShow = selectedEvent?.data?.value ?? conversation.currentValue;
147
+ const action = selectedEvent?.action;
148
+ const valueType = getValueType(valueToShow);
149
+ const actionColor = getActionColor(action || "");
150
+
151
+ // Get events for diff comparison
152
+ const leftEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, leftIndex))];
153
+ const rightEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, rightIndex))];
154
+
155
+ // Create event display info for compare bar
156
+ const leftEventInfo = (0, _react.useMemo)(() => ({
157
+ index: leftIndex,
158
+ label: `#${leftIndex + 1} / ${totalEvents}`,
159
+ timestamp: leftEvent ? formatTimeWithMs(leftEvent.timestamp) : "",
160
+ relativeTime: leftEvent ? (0, _sharedUi.formatRelativeTime)(leftEvent.timestamp) : "",
161
+ badge: leftEvent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
162
+ style: [styles.actionBadgeSmall, {
163
+ backgroundColor: `${getActionColor(leftEvent.action)}20`
164
+ }],
165
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
166
+ style: [styles.actionTextSmall, {
167
+ color: getActionColor(leftEvent.action)
168
+ }],
169
+ children: (0, _storageActionHelpers.translateStorageAction)(leftEvent.action)
170
+ })
171
+ }) : undefined
172
+ }), [leftIndex, leftEvent, totalEvents]);
173
+ const rightEventInfo = (0, _react.useMemo)(() => ({
174
+ index: rightIndex,
175
+ label: `#${rightIndex + 1} / ${totalEvents}`,
176
+ timestamp: rightEvent ? formatTimeWithMs(rightEvent.timestamp) : "",
177
+ relativeTime: rightEvent ? (0, _sharedUi.formatRelativeTime)(rightEvent.timestamp) : "",
178
+ badge: rightEvent ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
179
+ style: [styles.actionBadgeSmall, {
180
+ backgroundColor: `${getActionColor(rightEvent.action)}20`
181
+ }],
182
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
183
+ style: [styles.actionTextSmall, {
184
+ color: getActionColor(rightEvent.action)
185
+ }],
186
+ children: (0, _storageActionHelpers.translateStorageAction)(rightEvent.action)
187
+ })
188
+ }) : undefined
189
+ }), [rightIndex, rightEvent, totalEvents]);
190
+
191
+ // Diff mode tabs
192
+ const diffModeTabs = (0, _react.useMemo)(() => [{
193
+ key: "tree",
194
+ label: "TREE VIEW"
195
+ }, {
196
+ key: "split",
197
+ label: "SPLIT VIEW"
198
+ }], []);
199
+
200
+ // Navigation handlers for compare bar
201
+ const handleLeftPrevious = (0, _react.useCallback)(() => {
202
+ if (leftIndex > 0) {
203
+ setLeftIndex(leftIndex - 1);
204
+ }
205
+ }, [leftIndex]);
206
+ const handleLeftNext = (0, _react.useCallback)(() => {
207
+ if (leftIndex < rightIndex - 1) {
208
+ setLeftIndex(leftIndex + 1);
209
+ }
210
+ }, [leftIndex, rightIndex]);
211
+ const handleRightPrevious = (0, _react.useCallback)(() => {
212
+ if (rightIndex > leftIndex + 1) {
213
+ setRightIndex(rightIndex - 1);
123
214
  }
124
- };
125
- const renderValueContent = (value, label, action) => {
126
- const parsed = (0, _sharedUi.parseValue)(value);
127
- const type = parsed === null ? "null" : parsed === undefined ? "undefined" : Array.isArray(parsed) ? "array" : typeof parsed;
215
+ }, [rightIndex, leftIndex]);
216
+ const handleRightNext = (0, _react.useCallback)(() => {
217
+ if (rightIndex < totalEvents - 1) {
218
+ setRightIndex(rightIndex + 1);
219
+ }
220
+ }, [rightIndex, totalEvents]);
221
+
222
+ // Render current value view
223
+ const renderCurrentView = (0, _react.useCallback)(() => {
224
+ const parsed = (0, _sharedUi.parseValue)(valueToShow);
128
225
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
129
- style: styles.valueContent,
226
+ style: styles.contentCard,
130
227
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
131
228
  style: styles.valueHeader,
132
229
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
133
230
  style: styles.valueLabel,
134
- children: label
231
+ children: "CURRENT VALUE"
135
232
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
136
233
  style: styles.valueHeaderBadges,
137
234
  children: [action && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
138
235
  style: [styles.actionBadge, {
139
- backgroundColor: `${getActionColor(action)}20`
236
+ backgroundColor: `${actionColor}20`
140
237
  }],
141
238
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
142
239
  style: [styles.actionText, {
143
- color: getActionColor(action)
240
+ color: actionColor
144
241
  }],
145
242
  children: (0, _storageActionHelpers.translateStorageAction)(action)
146
243
  })
147
244
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
148
- style: [styles.typeBadge],
245
+ style: styles.typeBadge,
149
246
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
150
247
  style: styles.typeText,
151
- children: type.toUpperCase()
248
+ children: valueType.toUpperCase()
152
249
  })
153
250
  })]
154
251
  })]
155
252
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
156
253
  style: styles.valueBox,
157
- children: type === "object" || type === "array" ? parsed && (Array.isArray(parsed) && parsed.length > 0 || typeof parsed === "object" && Object.keys(parsed).length > 0) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
254
+ children: (valueType === "object" || valueType === "array") && parsed ? Array.isArray(parsed) && parsed.length > 0 || typeof parsed === "object" && Object.keys(parsed).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
158
255
  title: "",
159
256
  data: parsed,
160
257
  showTypeFilter: false
161
258
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
162
259
  style: styles.valueText,
163
- children: type === "array" ? "[]" : "{}"
260
+ children: valueType === "array" ? "[]" : "{}"
164
261
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
165
262
  style: styles.valueText,
166
- children: parsed === null ? "null" : parsed === undefined ? "undefined" : type === "string" ? `"${parsed}"` : String(parsed)
263
+ children: parsed === null ? "null" : parsed === undefined ? "undefined" : valueType === "string" ? `"${parsed}"` : String(parsed)
167
264
  })
168
265
  })]
169
266
  });
170
- };
171
-
172
- // Get all events sorted by time
173
- const navigationItems = conversation.events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
174
- const totalEvents = navigationItems.length;
175
-
176
- // Free tier limit - max event index accessible
177
- const maxAccessibleEventIndex = isPro ? totalEvents - 1 : Math.min(FREE_TIER_EVENT_HISTORY_LIMIT - 1, totalEvents - 1);
178
- const lockedEventCount = isPro ? 0 : Math.max(0, totalEvents - FREE_TIER_EVENT_HISTORY_LIMIT);
179
-
180
- // Keep compare indices synced to selection
181
- (0, _react.useEffect)(() => {
182
- const newRight = Math.min(totalEvents - 1, Math.max(0, selectedEventIndex));
183
- const newLeft = Math.max(0, Math.min(newRight - 1, selectedEventIndex - 1));
184
- setLeftIndex(newLeft);
185
- setRightIndex(newRight);
186
- }, [selectedEventIndex, totalEvents]);
187
-
188
- // Precise time HH:MM:SS.mmm
189
- const formatTimeWithMs = (0, _react.useCallback)(date => {
190
- const h = String(date.getHours()).padStart(2, "0");
191
- const m = String(date.getMinutes()).padStart(2, "0");
192
- const s = String(date.getSeconds()).padStart(2, "0");
193
- const ms = String(date.getMilliseconds()).padStart(3, "0");
194
- return `${h}:${m}:${s}.${ms}`;
195
- }, []);
196
- const bumpLeft = delta => {
197
- if (totalEvents < 2) return;
198
- const maxLeft = isPro ? totalEvents - 2 : Math.min(maxAccessibleEventIndex - 1, totalEvents - 2);
199
- let next = Math.max(0, Math.min(maxLeft, leftIndex + delta));
200
- if (next >= rightIndex) next = Math.max(0, rightIndex - 1);
201
- setLeftIndex(next);
202
- };
203
- const bumpRight = delta => {
204
- if (totalEvents < 2) return;
205
- const maxRight = isPro ? totalEvents - 1 : maxAccessibleEventIndex;
206
- let next = Math.max(1, Math.min(maxRight, rightIndex + delta));
207
- if (next <= leftIndex) next = Math.min(maxRight, leftIndex + 1);
208
- setRightIndex(next);
209
- };
210
-
211
- // Render current value tab
212
- const renderCurrentValue = () => {
213
- const selectedEvent = navigationItems[selectedEventIndex];
214
- const valueToShow = selectedEvent?.data?.value ?? conversation.currentValue;
215
- const action = selectedEvent?.action;
216
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
217
- style: styles.fullPageSection,
218
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
219
- style: styles.contentCard,
220
- children: renderValueContent(valueToShow, "CURRENT VALUE", action)
221
- })
222
- });
223
- };
267
+ }, [valueToShow, action, actionColor, valueType]);
224
268
 
225
- // Render diff tab
226
- const renderDiff = () => {
227
- if (navigationItems.length === 0) {
228
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
229
- style: styles.emptyState,
230
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertCircle, {
231
- size: 32,
232
- color: _sharedUi.macOSColors.text.muted
233
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
234
- style: styles.emptyText,
235
- children: "No changes to display"
236
- })]
237
- });
238
- }
239
- const leftEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, leftIndex))];
240
- const rightEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, rightIndex))];
241
- const previousValue = leftEvent?.data?.value ?? null;
242
- const currentValue = rightEvent?.data?.value;
243
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
244
- style: styles.fullPageSection,
245
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
246
- style: styles.diffViewerTabs,
247
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
248
- style: [styles.diffViewerTab, diffViewerTab === "split" && styles.diffViewerTabActive],
249
- onPress: () => handleDiffModeChange("split"),
250
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
251
- style: [styles.diffViewerTabText, diffViewerTab === "split" && styles.diffViewerTabTextActive],
252
- children: "SPLIT VIEW"
253
- })
254
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
255
- style: [styles.diffViewerTab, diffViewerTab === "tree" && styles.diffViewerTabActive],
256
- onPress: () => handleDiffModeChange("tree"),
257
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
258
- style: [styles.diffViewerTabText, diffViewerTab === "tree" && styles.diffViewerTabTextActive],
259
- children: "TREE VIEW"
260
- })
261
- })]
262
- }), totalEvents > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
263
- style: styles.compareBar,
264
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
265
- style: styles.compareSide,
266
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
267
- style: styles.compareLabelRow,
268
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
269
- style: [styles.compareLabel, {
270
- color: _sharedUi.macOSColors.semantic.debug
271
- }],
272
- children: "PREV"
273
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
274
- style: [styles.compareActionBadge, {
275
- backgroundColor: `${getActionColor(leftEvent.action)}20`
276
- }],
277
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
278
- style: [styles.compareActionText, {
279
- color: getActionColor(leftEvent.action)
280
- }],
281
- children: (0, _storageActionHelpers.translateStorageAction)(leftEvent.action)
282
- })
283
- })]
284
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
285
- style: styles.compareControls,
286
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
287
- onPress: () => bumpLeft(-1),
288
- disabled: leftIndex <= 0,
289
- style: [styles.compareBtn, leftIndex <= 0 && styles.compareBtnDisabled],
290
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronLeft, {
291
- size: 14,
292
- color: leftIndex <= 0 ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.secondary
293
- })
294
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
295
- style: styles.compareMeta,
296
- onPress: () => setIsLeftPickerOpen(true),
297
- activeOpacity: 0.8,
298
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
299
- style: styles.compareIndex,
300
- children: ["#", leftIndex + 1, " / ", totalEvents]
301
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
302
- style: styles.compareTime,
303
- children: formatTimeWithMs(leftEvent.timestamp)
304
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
305
- style: styles.compareRelative,
306
- children: ["(", (0, _sharedUi.formatRelativeTime)(leftEvent.timestamp), ")"]
307
- })]
308
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
309
- onPress: () => bumpLeft(1),
310
- disabled: leftIndex >= rightIndex - 1,
311
- style: [styles.compareBtn, leftIndex >= rightIndex - 1 && styles.compareBtnDisabled],
312
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
313
- size: 14,
314
- color: leftIndex >= rightIndex - 1 ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.secondary
315
- })
316
- })]
317
- })]
318
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
319
- style: styles.compareDivider
320
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
321
- style: styles.compareSide,
322
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
323
- style: styles.compareLabelRow,
324
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
325
- style: [styles.compareLabel, {
326
- color: _sharedUi.macOSColors.semantic.success
327
- }],
328
- children: "CUR"
329
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
330
- style: [styles.compareActionBadge, {
331
- backgroundColor: `${getActionColor(rightEvent.action)}20`
332
- }],
333
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
334
- style: [styles.compareActionText, {
335
- color: getActionColor(rightEvent.action)
336
- }],
337
- children: (0, _storageActionHelpers.translateStorageAction)(rightEvent.action)
338
- })
339
- })]
340
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
341
- style: styles.compareControls,
342
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
343
- onPress: () => bumpRight(-1),
344
- disabled: rightIndex <= leftIndex + 1,
345
- style: [styles.compareBtn, rightIndex <= leftIndex + 1 && styles.compareBtnDisabled],
346
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronLeft, {
347
- size: 14,
348
- color: rightIndex <= leftIndex + 1 ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.secondary
349
- })
350
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
351
- style: styles.compareMeta,
352
- onPress: () => setIsRightPickerOpen(true),
353
- activeOpacity: 0.8,
354
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
355
- style: styles.compareIndex,
356
- children: ["#", rightIndex + 1, " / ", totalEvents]
357
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
358
- style: styles.compareTime,
359
- children: formatTimeWithMs(rightEvent.timestamp)
360
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
361
- style: styles.compareRelative,
362
- children: ["(", (0, _sharedUi.formatRelativeTime)(rightEvent.timestamp), ")"]
363
- })]
364
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
365
- onPress: () => bumpRight(1),
366
- disabled: rightIndex >= totalEvents - 1,
367
- style: [styles.compareBtn, rightIndex >= totalEvents - 1 && styles.compareBtnDisabled],
368
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
369
- size: 14,
370
- color: rightIndex >= totalEvents - 1 ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.secondary
371
- })
372
- })]
373
- })]
374
- })]
375
- }), diffViewerTab === "split" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
269
+ // Render diff content
270
+ const renderDiffContent = (0, _react.useCallback)(() => {
271
+ const previousValue = (0, _sharedUi.parseValue)(leftEvent?.data?.value ?? null);
272
+ const currentValue = (0, _sharedUi.parseValue)(rightEvent?.data?.value);
273
+ if (diffMode === "split") {
274
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
376
275
  style: {
377
276
  flex: 1
378
277
  },
379
278
  showsVerticalScrollIndicator: true,
380
279
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ThemedSplitView.ThemedSplitView, {
381
- oldValue: (0, _sharedUi.parseValue)(previousValue),
382
- newValue: (0, _sharedUi.parseValue)(currentValue),
280
+ oldValue: previousValue,
281
+ newValue: currentValue,
383
282
  differences: [],
384
283
  theme: _diffThemes.diffThemes.devToolsDefault,
385
284
  options: {
@@ -392,245 +291,185 @@ function StorageEventDetailContent({
392
291
  },
393
292
  showThemeName: false
394
293
  })
395
- }), diffViewerTab === "tree" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_TreeDiffViewer.TreeDiffViewer, {
396
- oldValue: (0, _sharedUi.parseValue)(previousValue),
397
- newValue: (0, _sharedUi.parseValue)(currentValue)
398
- })]
294
+ });
295
+ }
296
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_TreeDiffViewer.TreeDiffViewer, {
297
+ oldValue: previousValue,
298
+ newValue: currentValue
399
299
  });
400
- };
300
+ }, [leftEvent, rightEvent, diffMode]);
301
+
302
+ // Footer navigation handlers
303
+ const handleFooterPrevious = (0, _react.useCallback)(() => {
304
+ onEventIndexChange(Math.max(0, selectedEventIndex - 1));
305
+ }, [selectedEventIndex, onEventIndexChange]);
306
+ const handleFooterNext = (0, _react.useCallback)(() => {
307
+ onEventIndexChange(Math.min(totalEvents - 1, selectedEventIndex + 1));
308
+ }, [selectedEventIndex, totalEvents, onEventIndexChange]);
309
+
310
+ // Time-travel action handlers
311
+ const handleUndo = (0, _react.useCallback)(async () => {
312
+ if (!isPro) {
313
+ setShowProModal(true);
314
+ return;
315
+ }
316
+ if (!selectedEvent || selectedEvent.storageType === "mmkv") return;
317
+ try {
318
+ await (0, _storageTimeTravelUtils.undoOperation)(selectedEvent);
319
+ } catch {
320
+ // Silent fail
321
+ }
322
+ }, [selectedEvent, isPro]);
323
+ const handleJump = (0, _react.useCallback)(async () => {
324
+ if (!isPro) {
325
+ setShowProModal(true);
326
+ return;
327
+ }
328
+ if (!selectedEvent || selectedEvent.storageType === "mmkv") return;
329
+ const asyncEvents = navigationItems.filter(e => e.storageType !== "mmkv");
330
+ const targetIndex = asyncEvents.findIndex(e => e.timestamp.getTime() === selectedEvent.timestamp.getTime());
331
+ if (targetIndex === -1) return;
332
+ try {
333
+ await (0, _storageTimeTravelUtils.jumpToState)(asyncEvents, targetIndex);
334
+ } catch {
335
+ // Silent fail
336
+ }
337
+ }, [selectedEvent, navigationItems, isPro]);
338
+ const handleCopy = (0, _react.useCallback)(async () => {
339
+ if (!selectedEvent) return;
340
+ const eventData = {
341
+ action: selectedEvent.action,
342
+ timestamp: selectedEvent.timestamp.toISOString(),
343
+ data: selectedEvent.data
344
+ };
345
+ await (0, _sharedUi.copyToClipboard)(eventData);
346
+ }, [selectedEvent]);
347
+
348
+ // Check if current event supports actions
349
+ const isAsyncStorageEvent = selectedEvent?.storageType !== "mmkv";
350
+ const canUndoEvent = isAsyncStorageEvent && selectedEvent && (0, _storageTimeTravelUtils.canUndo)(selectedEvent);
401
351
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
402
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
403
- style: [styles.contentOnly, {
404
- flex: 1,
405
- paddingBottom: !disableInternalFooter && totalEvents > 1 ? 80 : 0
406
- }],
407
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
408
- style: styles.viewToggleContainer,
409
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
410
- style: [styles.viewToggleCard, internalActiveView === "current" && styles.viewToggleCardActive],
411
- onPress: () => handleViewChange("current"),
412
- activeOpacity: 0.8,
413
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
414
- style: styles.viewToggleContent,
415
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Database, {
416
- size: 16,
417
- color: internalActiveView === "current" ? _sharedUi.macOSColors.semantic.info : _sharedUi.macOSColors.text.secondary
418
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
419
- style: [styles.viewToggleLabel, internalActiveView === "current" && styles.viewToggleLabelActive],
420
- children: "CURRENT VALUE"
421
- })]
422
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
423
- style: [styles.viewToggleDescription, internalActiveView === "current" && {
424
- color: _sharedUi.macOSColors.text.primary
425
- }],
426
- children: "View the current stored value"
427
- })]
428
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
429
- style: [styles.viewToggleCard, internalActiveView === "diff" && styles.viewToggleCardActive],
430
- onPress: () => handleViewChange("diff"),
431
- activeOpacity: 0.8,
432
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
433
- style: styles.viewToggleContent,
434
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.GitBranch, {
435
- size: 16,
436
- color: internalActiveView === "diff" ? _sharedUi.macOSColors.semantic.success : _sharedUi.macOSColors.text.secondary
437
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
438
- style: [styles.viewToggleLabel, internalActiveView === "diff" && styles.viewToggleLabelActive],
439
- children: "DIFF VIEW"
440
- })]
441
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
442
- style: [styles.viewToggleDescription, internalActiveView === "diff" && {
443
- color: _sharedUi.macOSColors.text.primary
444
- }],
445
- children: "Compare changes between versions"
446
- })]
447
- })]
448
- }), lockedEventCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
449
- style: styles.lockedEventsBannerGlobal,
450
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Lock, {
451
- size: 14,
452
- color: _sharedUi.macOSColors.semantic.warning
453
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
454
- style: styles.lockedEventsBannerText,
455
- children: [lockedEventCount, " older ", lockedEventCount === 1 ? 'event' : 'events', " locked"]
456
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
457
- style: styles.lockedEventsBannerSubtext,
458
- children: "Upgrade to Pro for full history"
459
- })]
460
- }), internalActiveView === "current" && renderCurrentValue(), internalActiveView === "diff" && renderDiff()]
461
- }), (isLeftPickerOpen || isRightPickerOpen) && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
462
- style: styles.pickerOverlay,
463
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
464
- style: styles.pickerBackdrop,
465
- activeOpacity: 1,
466
- onPress: () => {
467
- setIsLeftPickerOpen(false);
468
- setIsRightPickerOpen(false);
469
- }
470
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
471
- style: [styles.pickerCard, isLeftPickerOpen && styles.pickerCardLeft, isRightPickerOpen && styles.pickerCardRight],
472
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
473
- style: styles.pickerHeader,
474
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
475
- style: [styles.pickerTitle, isLeftPickerOpen && styles.pickerTitleLeft, isRightPickerOpen && styles.pickerTitleRight],
476
- children: ["Select ", isLeftPickerOpen ? "PREV" : "CUR", " Event"]
477
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
478
- onPress: () => {
479
- setIsLeftPickerOpen(false);
480
- setIsRightPickerOpen(false);
481
- },
482
- style: styles.pickerClose,
483
- accessibilityLabel: "Close event picker",
484
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
485
- size: 16,
486
- color: _sharedUi.macOSColors.text.secondary
487
- })
488
- })]
489
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
490
- style: styles.pickerDivider
491
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
492
- style: styles.pickerScroll,
493
- contentContainerStyle: styles.pickerList,
494
- showsVerticalScrollIndicator: true,
495
- nestedScrollEnabled: true,
496
- children: navigationItems.slice(0, maxAccessibleEventIndex + 1).map((item, idx) => {
497
- const disabled = isLeftPickerOpen ? idx >= rightIndex : idx <= leftIndex;
498
- const isSelected = isLeftPickerOpen ? idx === leftIndex : idx === rightIndex;
499
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
500
- disabled: disabled,
501
- onPress: () => {
502
- if (isLeftPickerOpen) {
503
- setLeftIndex(Math.min(idx, rightIndex - 1));
504
- setIsLeftPickerOpen(false);
505
- } else {
506
- setRightIndex(Math.max(idx, leftIndex + 1));
507
- setIsRightPickerOpen(false);
508
- }
509
- },
510
- style: [styles.pickerItem, isSelected && styles.pickerItemSelected, isSelected && isLeftPickerOpen && styles.pickerItemSelectedLeft, isSelected && isRightPickerOpen && styles.pickerItemSelectedRight, disabled && styles.pickerItemDisabled],
511
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
512
- style: styles.pickerIndex,
513
- children: ["#", idx + 1]
514
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
515
- style: styles.pickerTime,
516
- children: formatTimeWithMs(item.timestamp)
517
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
518
- style: styles.pickerRelative,
519
- children: ["(", (0, _sharedUi.formatRelativeTime)(item.timestamp), ")"]
520
- }), (() => {
521
- const targetOld = isLeftPickerOpen ? item : navigationItems[leftIndex];
522
- const targetNew = isLeftPickerOpen ? navigationItems[rightIndex] : item;
523
- const oldVal = (0, _sharedUi.parseValue)(targetOld.data?.value);
524
- const newVal = (0, _sharedUi.parseValue)(targetNew.data?.value);
525
- const diffs = (0, _lineDiff.computeLineDiff)(oldVal, newVal, {
526
- compareMethod: "words",
527
- disableWordDiff: false,
528
- showDiffOnly: false,
529
- contextLines: 0
530
- });
531
- const added = diffs.filter(d => d.type === _lineDiff.DiffType.ADDED).length;
532
- const removed = diffs.filter(d => d.type === _lineDiff.DiffType.REMOVED).length;
533
- const modified = diffs.filter(d => d.type === _lineDiff.DiffType.MODIFIED).length;
534
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
535
- style: styles.pickerCounts,
536
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
537
- style: [styles.pickerCountText, {
538
- color: _diffThemes.diffThemes.devToolsDefault.summaryAddedText
539
- }],
540
- children: ["+", added]
541
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
542
- style: [styles.pickerCountText, {
543
- color: _diffThemes.diffThemes.devToolsDefault.summaryRemovedText
544
- }],
545
- children: ["-", removed]
546
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
547
- style: [styles.pickerCountText, {
548
- color: _diffThemes.diffThemes.devToolsDefault.summaryModifiedText
549
- }],
550
- children: ["~", modified]
551
- })]
552
- });
553
- })()]
554
- }, idx);
555
- })
556
- })]
352
+ children: [isAsyncStorageEvent && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
353
+ style: styles.actionButtonsContainer,
354
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventActionButton.StorageEventActionButton, {
355
+ type: "undo",
356
+ text: "UNDO",
357
+ onPress: handleUndo,
358
+ disabled: !canUndoEvent,
359
+ locked: !isPro
360
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventActionButton.StorageEventActionButton, {
361
+ type: "jump",
362
+ text: "JUMP",
363
+ onPress: handleJump,
364
+ locked: !isPro
365
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StorageEventActionButton.StorageEventActionButton, {
366
+ type: "copy",
367
+ text: "COPY",
368
+ onPress: handleCopy
557
369
  })]
558
- }), !disableInternalFooter && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EventStepperFooter, {
559
- currentIndex: selectedEventIndex,
560
- totalItems: totalEvents,
561
- onPrevious: () => onEventIndexChange(Math.max(0, selectedEventIndex - 1)),
562
- onNext: () => onEventIndexChange(Math.min(totalEvents - 1, selectedEventIndex + 1)),
563
- itemLabel: "Event",
564
- subtitle: (0, _sharedUi.formatRelativeTime)(navigationItems[selectedEventIndex]?.timestamp),
565
- absolute: true
370
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EventHistoryViewer
371
+ // View Toggle
372
+ , {
373
+ activeView: activeView,
374
+ onViewChange: handleViewChange,
375
+ currentViewLabel: "CURRENT VALUE",
376
+ currentViewDescription: "View the current stored value",
377
+ currentViewIcon: _sharedUi.Database,
378
+ diffViewLabel: "DIFF VIEW",
379
+ diffViewDescription: "Compare changes between versions",
380
+ diffViewIcon: _sharedUi.GitBranch
381
+ // Current View
382
+ ,
383
+ renderCurrentView: renderCurrentView
384
+ // Diff View
385
+ ,
386
+ diffModeTabs: diffModeTabs,
387
+ activeDiffMode: diffMode,
388
+ onDiffModeChange: handleDiffModeChange,
389
+ renderDiffContent: renderDiffContent
390
+ // Compare Bar
391
+ ,
392
+ leftEvent: leftEventInfo,
393
+ rightEvent: rightEventInfo,
394
+ showCompareNavigation: true,
395
+ onLeftPrevious: handleLeftPrevious,
396
+ onLeftNext: handleLeftNext,
397
+ onRightPrevious: handleRightPrevious,
398
+ onRightNext: handleRightNext,
399
+ canLeftPrevious: leftIndex > 0,
400
+ canLeftNext: leftIndex < rightIndex - 1,
401
+ canRightPrevious: rightIndex > leftIndex + 1,
402
+ canRightNext: rightIndex < totalEvents - 1
403
+ // Footer
404
+ ,
405
+ disableInternalFooter: disableInternalFooter,
406
+ footerCurrentIndex: selectedEventIndex,
407
+ footerTotalItems: totalEvents,
408
+ footerItemLabel: "Event",
409
+ footerSubtitle: (0, _sharedUi.formatRelativeTime)(selectedEvent?.timestamp),
410
+ onFooterPrevious: handleFooterPrevious,
411
+ onFooterNext: handleFooterNext
412
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProUpgradeModal, {
413
+ visible: showProModal,
414
+ onClose: () => setShowProModal(false),
415
+ featureName: "Storage Time-Travel"
566
416
  })]
567
417
  });
568
418
  }
569
419
 
570
- // External footer component to be rendered by the modal outside the ScrollView
420
+ /**
421
+ * External footer component for modal use
422
+ */
571
423
  function StorageEventDetailFooter({
572
424
  conversation,
573
425
  selectedEventIndex = 0,
574
426
  onEventIndexChange = () => {}
575
427
  }) {
576
- // Check Pro status internally
577
- const useIsPro = getUseIsPro();
578
- const isPro = useIsPro();
579
428
  const navigationItems = conversation.events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
580
429
  const totalEvents = navigationItems.length;
581
-
582
- // Free tier limit for navigation
583
- const maxAccessibleIndex = isPro ? totalEvents - 1 : Math.min(FREE_TIER_EVENT_HISTORY_LIMIT - 1, totalEvents - 1);
584
- const lockedCount = isPro ? 0 : Math.max(0, totalEvents - FREE_TIER_EVENT_HISTORY_LIMIT);
585
-
586
- // Calculate display total (accessible events for free users)
587
- const displayTotal = isPro ? totalEvents : Math.min(totalEvents, FREE_TIER_EVENT_HISTORY_LIMIT);
588
430
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.EventStepperFooter, {
589
431
  currentIndex: selectedEventIndex,
590
- totalItems: displayTotal,
432
+ totalItems: totalEvents,
591
433
  onPrevious: () => onEventIndexChange(Math.max(0, selectedEventIndex - 1)),
592
- onNext: () => onEventIndexChange(Math.min(maxAccessibleIndex, selectedEventIndex + 1)),
434
+ onNext: () => onEventIndexChange(Math.min(totalEvents - 1, selectedEventIndex + 1)),
593
435
  itemLabel: "Event",
594
- subtitle: lockedCount > 0 ? `${(0, _sharedUi.formatRelativeTime)(navigationItems[selectedEventIndex]?.timestamp)} • ${lockedCount} locked` : (0, _sharedUi.formatRelativeTime)(navigationItems[selectedEventIndex]?.timestamp)
436
+ subtitle: (0, _sharedUi.formatRelativeTime)(navigationItems[selectedEventIndex]?.timestamp)
595
437
  });
596
438
  }
597
439
  const styles = _reactNative.StyleSheet.create({
598
- contentOnly: {
599
- flex: 1,
600
- backgroundColor: _sharedUi.macOSColors.background.base
601
- },
602
- fullPageSection: {
603
- flex: 1,
604
- paddingHorizontal: 14,
605
- paddingVertical: 10
606
- },
607
- emptyState: {
608
- flex: 1,
609
- alignItems: "center",
440
+ // Action Buttons
441
+ actionButtonsContainer: {
442
+ flexDirection: "row",
610
443
  justifyContent: "center",
611
- paddingVertical: 48
612
- },
613
- emptyText: {
614
- marginTop: 12,
615
- fontSize: 14,
616
- color: _sharedUi.macOSColors.text.secondary,
617
- fontFamily: "monospace"
444
+ alignItems: "center",
445
+ gap: 8,
446
+ paddingHorizontal: 12,
447
+ paddingVertical: 10,
448
+ backgroundColor: _sharedUi.buoyColors.card,
449
+ borderBottomWidth: 1,
450
+ borderBottomColor: _sharedUi.buoyColors.border
618
451
  },
619
- card: {
452
+ // Content Card
453
+ contentCard: {
620
454
  backgroundColor: _sharedUi.macOSColors.background.card,
621
455
  borderRadius: 14,
622
456
  padding: 14,
623
457
  borderWidth: 1,
624
- borderColor: _sharedUi.macOSColors.border.default
625
- },
626
- valueContent: {
627
- marginTop: 4
458
+ borderColor: _sharedUi.macOSColors.border.default,
459
+ shadowColor: _sharedUi.macOSColors.semantic.info,
460
+ shadowOffset: {
461
+ width: 0,
462
+ height: 2
463
+ },
464
+ shadowOpacity: 0.04,
465
+ shadowRadius: 16,
466
+ elevation: 2
628
467
  },
629
468
  valueHeader: {
630
469
  flexDirection: "row",
631
470
  alignItems: "center",
632
471
  justifyContent: "space-between",
633
- marginBottom: 4
472
+ marginBottom: 8
634
473
  },
635
474
  valueLabel: {
636
475
  fontSize: 10,
@@ -648,9 +487,7 @@ const styles = _reactNative.StyleSheet.create({
648
487
  actionBadge: {
649
488
  paddingHorizontal: 8,
650
489
  paddingVertical: 2,
651
- borderRadius: 4,
652
- borderWidth: 1,
653
- borderColor: "transparent"
490
+ borderRadius: 4
654
491
  },
655
492
  actionText: {
656
493
  fontSize: 9,
@@ -671,7 +508,7 @@ const styles = _reactNative.StyleSheet.create({
671
508
  fontFamily: "monospace"
672
509
  },
673
510
  valueBox: {
674
- backgroundColor: _sharedUi.macOSColors.background.card,
511
+ backgroundColor: _sharedUi.macOSColors.background.base,
675
512
  borderRadius: 6,
676
513
  borderWidth: 1,
677
514
  borderColor: _sharedUi.macOSColors.border.input,
@@ -683,343 +520,15 @@ const styles = _reactNative.StyleSheet.create({
683
520
  fontFamily: "monospace",
684
521
  lineHeight: 18
685
522
  },
686
- // Compare picker styles
687
- compareBar: {
688
- flexDirection: "row",
689
- alignItems: "center",
690
- justifyContent: "space-between",
691
- backgroundColor: _sharedUi.macOSColors.background.card,
692
- borderWidth: 1,
693
- borderColor: _sharedUi.macOSColors.border.default,
694
- borderRadius: 6,
695
- paddingHorizontal: 8,
696
- paddingVertical: 6,
697
- marginBottom: 8,
698
- gap: 8
699
- },
700
- compareSide: {
701
- flex: 1
702
- },
703
- compareLabelRow: {
704
- flexDirection: "row",
705
- alignItems: "center",
706
- gap: 6,
707
- marginBottom: 2
708
- },
709
- compareLabel: {
710
- fontSize: 10,
711
- fontFamily: "monospace",
712
- fontWeight: "700",
713
- letterSpacing: 0.5,
714
- textTransform: "uppercase"
715
- },
716
- compareActionBadge: {
523
+ // Action badges for compare bar
524
+ actionBadgeSmall: {
717
525
  paddingHorizontal: 6,
718
526
  paddingVertical: 1,
719
527
  borderRadius: 3
720
528
  },
721
- compareActionText: {
529
+ actionTextSmall: {
722
530
  fontSize: 8,
723
531
  fontWeight: "700",
724
- fontFamily: "monospace",
725
- letterSpacing: 0.3
726
- },
727
- compareControls: {
728
- flexDirection: "row",
729
- alignItems: "center",
730
- gap: 6
731
- },
732
- compareBtn: {
733
- width: 26,
734
- height: 26,
735
- borderRadius: 6,
736
- backgroundColor: _sharedUi.macOSColors.background.card,
737
- borderWidth: 1,
738
- borderColor: _sharedUi.macOSColors.border.default,
739
- alignItems: "center",
740
- justifyContent: "center"
741
- },
742
- compareBtnDisabled: {
743
- opacity: 0.4
744
- },
745
- compareMeta: {
746
- flex: 1
747
- },
748
- compareTime: {
749
- fontSize: 11,
750
- color: _sharedUi.macOSColors.text.primary,
751
- fontFamily: "monospace"
752
- },
753
- compareIndex: {
754
- fontSize: 10,
755
- color: _sharedUi.macOSColors.text.secondary,
756
- fontFamily: "monospace"
757
- },
758
- compareRelative: {
759
- fontSize: 10,
760
- color: _sharedUi.macOSColors.text.secondary,
761
- fontFamily: "monospace"
762
- },
763
- compareDivider: {
764
- width: 1,
765
- height: 34,
766
- backgroundColor: _sharedUi.macOSColors.background.input
767
- },
768
- pickerOverlay: {
769
- ..._reactNative.StyleSheet.absoluteFillObject,
770
- zIndex: 20,
771
- justifyContent: "center",
772
- alignItems: "center"
773
- },
774
- pickerBackdrop: {
775
- ..._reactNative.StyleSheet.absoluteFillObject,
776
- backgroundColor: "rgba(0,0,0,0.65)"
777
- },
778
- pickerCard: {
779
- width: "86%",
780
- maxHeight: 320,
781
- backgroundColor: _sharedUi.macOSColors.background.card,
782
- borderRadius: 16,
783
- borderWidth: 2,
784
- padding: 16,
785
- shadowOffset: {
786
- width: 0,
787
- height: 0
788
- },
789
- shadowOpacity: 0.5,
790
- shadowRadius: 40,
791
- elevation: 15
792
- },
793
- pickerCardLeft: {
794
- borderColor: _sharedUi.macOSColors.semantic.debug,
795
- shadowColor: _sharedUi.macOSColors.semantic.debug
796
- },
797
- pickerCardRight: {
798
- borderColor: _sharedUi.macOSColors.semantic.success,
799
- shadowColor: _sharedUi.macOSColors.semantic.success
800
- },
801
- pickerHeader: {
802
- flexDirection: "row",
803
- alignItems: "center",
804
- justifyContent: "space-between"
805
- },
806
- pickerClose: {
807
- padding: 6,
808
- borderRadius: 6,
809
- backgroundColor: _sharedUi.macOSColors.background.card,
810
- borderWidth: 1,
811
- borderColor: _sharedUi.macOSColors.border.default
812
- },
813
- pickerDivider: {
814
- height: 1,
815
- backgroundColor: _sharedUi.macOSColors.background.input,
816
- marginVertical: 8
817
- },
818
- pickerScroll: {
819
- maxHeight: 260
820
- },
821
- pickerTitle: {
822
- fontSize: 13,
823
- fontWeight: "700",
824
- fontFamily: "monospace",
825
- textTransform: "uppercase",
826
- marginBottom: 8,
827
- letterSpacing: 0.6
828
- },
829
- pickerTitleLeft: {
830
- color: _sharedUi.macOSColors.semantic.debug
831
- },
832
- pickerTitleRight: {
833
- color: _sharedUi.macOSColors.semantic.success
834
- },
835
- pickerList: {
836
- gap: 4
837
- },
838
- pickerItem: {
839
- paddingVertical: 10,
840
- paddingHorizontal: 12,
841
- borderRadius: 10,
842
- backgroundColor: _sharedUi.macOSColors.background.base,
843
- borderWidth: 1,
844
- borderColor: _sharedUi.macOSColors.border.default,
845
- flexDirection: "row",
846
- alignItems: "center",
847
- gap: 8,
848
- marginBottom: 6
849
- },
850
- pickerItemSelected: {
851
- borderWidth: 1.5,
852
- shadowOffset: {
853
- width: 0,
854
- height: 2
855
- },
856
- shadowOpacity: 0.25,
857
- shadowRadius: 12,
858
- elevation: 4
859
- },
860
- pickerItemSelectedLeft: {
861
- backgroundColor: _sharedUi.macOSColors.semantic.debug + "1A",
862
- borderColor: _sharedUi.macOSColors.semantic.debug,
863
- shadowColor: _sharedUi.macOSColors.semantic.debug
864
- },
865
- pickerItemSelectedRight: {
866
- backgroundColor: _sharedUi.macOSColors.semantic.successBackground + "30",
867
- borderColor: _sharedUi.macOSColors.semantic.success,
868
- shadowColor: _sharedUi.macOSColors.semantic.success
869
- },
870
- pickerItemDisabled: {
871
- opacity: 0.4
872
- },
873
- pickerIndex: {
874
- fontSize: 10,
875
- color: _sharedUi.macOSColors.text.secondary,
876
- fontFamily: "monospace",
877
- width: 40
878
- },
879
- pickerTime: {
880
- fontSize: 11,
881
- color: _sharedUi.macOSColors.text.primary,
882
- fontFamily: "monospace",
883
- flex: 1
884
- },
885
- pickerRelative: {
886
- fontSize: 10,
887
- color: _sharedUi.macOSColors.text.secondary,
888
532
  fontFamily: "monospace"
889
- },
890
- pickerCounts: {
891
- flexDirection: "row",
892
- alignItems: "center",
893
- gap: 8,
894
- marginLeft: "auto"
895
- },
896
- pickerCountText: {
897
- fontSize: 10,
898
- fontFamily: "monospace",
899
- fontWeight: "700"
900
- },
901
- diffViewerTabs: {
902
- flexDirection: "row",
903
- alignItems: "center",
904
- justifyContent: "space-around",
905
- backgroundColor: _sharedUi.macOSColors.background.card,
906
- borderWidth: 1,
907
- borderColor: _sharedUi.macOSColors.border.default,
908
- borderRadius: 6,
909
- paddingHorizontal: 4,
910
- paddingVertical: 4,
911
- marginBottom: 8,
912
- gap: 4
913
- },
914
- diffViewerTab: {
915
- flex: 1,
916
- paddingVertical: 8,
917
- paddingHorizontal: 12,
918
- borderRadius: 4,
919
- alignItems: "center",
920
- backgroundColor: "transparent"
921
- },
922
- diffViewerTabActive: {
923
- backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
924
- borderWidth: 1,
925
- borderColor: _sharedUi.macOSColors.semantic.info + "40"
926
- },
927
- diffViewerTabText: {
928
- fontSize: 11,
929
- fontFamily: "monospace",
930
- fontWeight: "600",
931
- color: _sharedUi.macOSColors.text.secondary,
932
- letterSpacing: 0.5
933
- },
934
- diffViewerTabTextActive: {
935
- color: _sharedUi.macOSColors.text.primary
936
- },
937
- contentCard: {
938
- backgroundColor: _sharedUi.macOSColors.background.card,
939
- borderRadius: 14,
940
- padding: 14,
941
- borderWidth: 1,
942
- borderColor: _sharedUi.macOSColors.border.default,
943
- shadowColor: _sharedUi.macOSColors.semantic.info,
944
- shadowOffset: {
945
- width: 0,
946
- height: 2
947
- },
948
- shadowOpacity: 0.04,
949
- shadowRadius: 16,
950
- elevation: 2
951
- },
952
- // View Toggle Cards
953
- viewToggleContainer: {
954
- flexDirection: "row",
955
- gap: 12,
956
- padding: 14,
957
- backgroundColor: _sharedUi.macOSColors.background.base
958
- },
959
- viewToggleCard: {
960
- flex: 1,
961
- backgroundColor: _sharedUi.macOSColors.background.card,
962
- borderRadius: 14,
963
- borderWidth: 1,
964
- borderColor: _sharedUi.macOSColors.border.default,
965
- padding: 14,
966
- gap: 8
967
- },
968
- viewToggleCardActive: {
969
- borderWidth: 1.5,
970
- borderColor: _sharedUi.macOSColors.semantic.info,
971
- backgroundColor: _sharedUi.macOSColors.semantic.infoBackground + "30",
972
- shadowColor: _sharedUi.macOSColors.semantic.info,
973
- shadowOffset: {
974
- width: 0,
975
- height: 2
976
- },
977
- shadowOpacity: 0.1,
978
- shadowRadius: 8,
979
- elevation: 3
980
- },
981
- viewToggleContent: {
982
- flexDirection: "row",
983
- alignItems: "center",
984
- gap: 8
985
- },
986
- viewToggleLabel: {
987
- fontSize: 12,
988
- fontWeight: "700",
989
- letterSpacing: 0.5,
990
- color: _sharedUi.macOSColors.text.secondary,
991
- textTransform: "uppercase"
992
- },
993
- viewToggleLabelActive: {
994
- color: _sharedUi.macOSColors.text.primary
995
- },
996
- viewToggleDescription: {
997
- fontSize: 11,
998
- color: _sharedUi.macOSColors.text.muted,
999
- lineHeight: 16
1000
- },
1001
- lockedEventsBannerGlobal: {
1002
- flexDirection: "row",
1003
- alignItems: "center",
1004
- gap: 8,
1005
- backgroundColor: _sharedUi.macOSColors.semantic.warning + "15",
1006
- borderWidth: 1,
1007
- borderColor: _sharedUi.macOSColors.semantic.warning + "30",
1008
- borderRadius: 10,
1009
- paddingVertical: 12,
1010
- paddingHorizontal: 14,
1011
- marginHorizontal: 14,
1012
- marginBottom: 4
1013
- },
1014
- lockedEventsBannerText: {
1015
- fontSize: 12,
1016
- fontWeight: "600",
1017
- color: _sharedUi.macOSColors.semantic.warning,
1018
- fontFamily: "monospace"
1019
- },
1020
- lockedEventsBannerSubtext: {
1021
- fontSize: 11,
1022
- color: _sharedUi.macOSColors.text.secondary,
1023
- marginLeft: "auto"
1024
533
  }
1025
534
  });