@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
@@ -3,7 +3,7 @@
3
3
  import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
4
4
  import { useState, useCallback, useEffect } from "react";
5
5
  import { clearAllStorageIncludingDevTools } from "../utils/clearAllStorage";
6
- import { RefreshCw, Trash2, CopyButton, ProUpgradeModal } from "@buoy-gg/shared-ui";
6
+ import { RefreshCw, Trash2, CopyButton, ProUpgradeModal, buoyColors } from "@buoy-gg/shared-ui";
7
7
  import { useIsPro } from "@buoy-gg/license";
8
8
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
9
9
  export function StorageActions({
@@ -89,7 +89,7 @@ export function StorageActions({
89
89
  accessibilityLabel: "Refresh storage",
90
90
  children: /*#__PURE__*/_jsx(RefreshCw, {
91
91
  size: 16,
92
- color: isRefreshing ? "#10B981" : "#9CA3AF"
92
+ color: isRefreshing ? "#10B981" : buoyColors.textSecondary
93
93
  })
94
94
  }), /*#__PURE__*/_jsx(CopyButton, {
95
95
  value: storageKeys,
@@ -125,11 +125,11 @@ const styles = StyleSheet.create({
125
125
  justifyContent: "space-between",
126
126
  paddingHorizontal: 12,
127
127
  paddingVertical: 8,
128
- backgroundColor: "rgba(255, 255, 255, 0.03)",
128
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
129
129
  borderRadius: 8,
130
130
  marginBottom: 12,
131
131
  borderWidth: 1,
132
- borderColor: "rgba(255, 255, 255, 0.08)"
132
+ borderColor: "rgba(128, 128, 128, 0.16)"
133
133
  },
134
134
  leftSection: {
135
135
  flexDirection: "row",
@@ -137,7 +137,7 @@ const styles = StyleSheet.create({
137
137
  gap: 8
138
138
  },
139
139
  keyCount: {
140
- color: "#9CA3AF",
140
+ color: buoyColors.textSecondary,
141
141
  fontSize: 12,
142
142
  fontWeight: "500"
143
143
  },
@@ -161,9 +161,9 @@ const styles = StyleSheet.create({
161
161
  width: 32,
162
162
  height: 32,
163
163
  borderRadius: 6,
164
- backgroundColor: "rgba(255, 255, 255, 0.03)",
164
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
165
165
  borderWidth: 1,
166
- borderColor: "rgba(255, 255, 255, 0.08)",
166
+ borderColor: "rgba(128, 128, 128, 0.16)",
167
167
  alignItems: "center",
168
168
  justifyContent: "center"
169
169
  },
@@ -15,10 +15,17 @@ export function StorageBrowserMode({
15
15
  searchQuery = "",
16
16
  storageDataRef,
17
17
  eventCountByKey,
18
+ lastWriteByKey,
18
19
  onViewHistory,
19
20
  enabledStorageTypes,
21
+ statusFilter,
22
+ onStatusFilterChange,
23
+ onStatusCountsChange,
20
24
  pinnedKeys,
21
- onTogglePin
25
+ onTogglePin,
26
+ applySafeAreaInset,
27
+ storageTypeFilter,
28
+ onStorageTypeFilterChange
22
29
  }) {
23
30
  return /*#__PURE__*/_jsx(GameUIStorageBrowser, {
24
31
  requiredStorageKeys: requiredStorageKeys,
@@ -29,9 +36,16 @@ export function StorageBrowserMode({
29
36
  searchQuery: searchQuery,
30
37
  storageDataRef: storageDataRef,
31
38
  eventCountByKey: eventCountByKey,
39
+ lastWriteByKey: lastWriteByKey,
32
40
  onViewHistory: onViewHistory,
33
41
  enabledStorageTypes: enabledStorageTypes,
42
+ statusFilter: statusFilter,
43
+ onStatusFilterChange: onStatusFilterChange,
44
+ onStatusCountsChange: onStatusCountsChange,
34
45
  pinnedKeys: pinnedKeys,
35
- onTogglePin: onTogglePin
46
+ onTogglePin: onTogglePin,
47
+ applySafeAreaInset: applySafeAreaInset,
48
+ storageTypeFilter: storageTypeFilter,
49
+ onStorageTypeFilterChange: onStorageTypeFilterChange
36
50
  });
37
51
  }
@@ -9,9 +9,9 @@
9
9
  import { View, Text, StyleSheet } from "react-native";
10
10
  import { useState, useCallback, useMemo, useEffect, useRef } from "react";
11
11
  import AsyncStorage from "@react-native-async-storage/async-storage";
12
- import { formatRelativeTime, macOSColors, buoyColors, parseValue, devToolsStorageKeys, Database, GitBranch, EventStepperFooter, EventHistoryViewer, copyToClipboard, ProUpgradeModal } from "@buoy-gg/shared-ui";
12
+ import { formatRelativeTime, macOSColors, buoyColors, parseValue, devToolsStorageKeys, GitBranch, EventStepperFooter, EventHistoryViewer, copyToClipboard, ProUpgradeModal } from "@buoy-gg/shared-ui";
13
13
  import { useIsPro } from "@buoy-gg/license";
14
- import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
14
+ import { ABSENT_VALUE } from "../utils/lineDiff";
15
15
  import { ThemedSplitView } from "./DiffViewer/modes/ThemedSplitView";
16
16
  import { diffThemes } from "./DiffViewer/themes/diffThemes";
17
17
  import { TreeDiffViewer } from "./DiffViewer/TreeDiffViewer";
@@ -19,8 +19,40 @@ import { translateStorageAction } from "../utils/storageActionHelpers";
19
19
  import { StorageEventActionButton } from "./StorageEventActionButton";
20
20
  import { undoOperation, jumpToState, canUndo } from "../utils/storageTimeTravelUtils";
21
21
 
22
- // Unified storage event type (supports both AsyncStorage and MMKV)
22
+ /**
23
+ * Compare-bar index for "the state before the oldest event".
24
+ *
25
+ * Not an event — it's reconstructed from that event's own `prevValue`, which
26
+ * every write records. Without it a key's first event had nothing to diff
27
+ * against and both panes showed the same value.
28
+ */
23
29
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
30
+ const ORIGIN_INDEX = -1;
31
+
32
+ /** Compare-bar copy for each origin state. Kept in sync with the Flutter port. */
33
+ const ORIGIN_LABELS = {
34
+ prior: {
35
+ label: "BEFORE #1",
36
+ timestamp: "value before first event",
37
+ relativeTime: "not captured",
38
+ badge: "PRIOR"
39
+ },
40
+ absent: {
41
+ label: "NO PRIOR VALUE",
42
+ timestamp: "key did not exist",
43
+ relativeTime: "created here",
44
+ badge: "ABSENT"
45
+ },
46
+ scanned: {
47
+ label: "ALREADY PRESENT",
48
+ timestamp: "value found at startup",
49
+ relativeTime: "not a write",
50
+ badge: "SCANNED"
51
+ }
52
+ };
53
+
54
+ // Unified storage event type (supports both AsyncStorage and MMKV)
55
+
24
56
  /**
25
57
  * Get color for storage action
26
58
  */
@@ -80,12 +112,16 @@ export function StorageEventDetailContent({
80
112
  const isPro = useIsPro();
81
113
  const [showProModal, setShowProModal] = useState(false);
82
114
 
83
- // View state
84
- const [activeView, setActiveView] = useState("current");
85
- const [diffMode, setDiffMode] = useState("tree");
115
+ // There is only the diff view now. The current stored value is what the Keys
116
+ // table shows — repeating it behind a toggle here meant two places to look for
117
+ // one fact, and the toggle was the first thing between you and the diff you
118
+ // opened this screen for.
119
+ const [diffMode, setDiffMode] = useState("split");
86
120
 
87
121
  // Compare indices for diff view
88
- const [leftIndex, setLeftIndex] = useState(Math.max(0, selectedEventIndex - 1));
122
+ // Matches what the sync effect below computes, so the first frame doesn't
123
+ // briefly pair the oldest event with itself.
124
+ const [leftIndex, setLeftIndex] = useState(Math.max(ORIGIN_INDEX, selectedEventIndex - 1));
89
125
  const [rightIndex, setRightIndex] = useState(selectedEventIndex);
90
126
 
91
127
  // Track if preferences have been loaded
@@ -100,10 +136,6 @@ export function StorageEventDetailContent({
100
136
  if (hasLoadedPreferences.current) return;
101
137
  const loadPreferences = async () => {
102
138
  try {
103
- const savedDetailView = await AsyncStorage.getItem(devToolsStorageKeys.storage.detailView());
104
- if (savedDetailView === "current" || savedDetailView === "diff") {
105
- setActiveView(savedDetailView);
106
- }
107
139
  const savedDiffMode = await AsyncStorage.getItem(devToolsStorageKeys.storage.diffViewerMode());
108
140
  if (savedDiffMode === "split" || savedDiffMode === "tree") {
109
141
  setDiffMode(savedDiffMode);
@@ -116,24 +148,15 @@ export function StorageEventDetailContent({
116
148
  loadPreferences();
117
149
  }, []);
118
150
 
119
- // Keep compare indices synced to selection
151
+ // Keep compare indices synced to selection. Left may land on ORIGIN_INDEX,
152
+ // which is not an event — it's the state the oldest event overwrote.
120
153
  useEffect(() => {
121
154
  const newRight = Math.min(totalEvents - 1, Math.max(0, selectedEventIndex));
122
- const newLeft = Math.max(0, Math.min(newRight - 1, selectedEventIndex - 1));
155
+ const newLeft = Math.max(ORIGIN_INDEX, Math.min(newRight - 1, selectedEventIndex - 1));
123
156
  setLeftIndex(newLeft);
124
157
  setRightIndex(newRight);
125
158
  }, [selectedEventIndex, totalEvents]);
126
159
 
127
- // Save view preference when changed
128
- const handleViewChange = useCallback(async view => {
129
- setActiveView(view);
130
- try {
131
- await AsyncStorage.setItem(devToolsStorageKeys.storage.detailView(), view);
132
- } catch {
133
- // Failed to save preference
134
- }
135
- }, []);
136
-
137
160
  // Save diff mode preference when changed
138
161
  const handleDiffModeChange = useCallback(async mode => {
139
162
  setDiffMode(mode);
@@ -146,18 +169,53 @@ export function StorageEventDetailContent({
146
169
 
147
170
  // Get current event
148
171
  const selectedEvent = navigationItems[selectedEventIndex];
149
- const valueToShow = selectedEvent?.data?.value ?? conversation.currentValue;
150
172
  const action = selectedEvent?.action;
151
173
  // Compute type from the parsed value so it matches what we actually render
152
- const valueType = getValueType(parseValue(valueToShow));
153
- const actionColor = getActionColor(action || "");
154
174
 
155
- // Get events for diff comparison
156
- const leftEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, leftIndex))];
175
+ // Get events for diff comparison. leftIndex === ORIGIN_INDEX means "the state
176
+ // before the oldest event", which has no event object of its own.
177
+ const isOriginLeft = leftIndex === ORIGIN_INDEX;
178
+ const leftEvent = isOriginLeft ? undefined : navigationItems[Math.max(0, Math.min(totalEvents - 1, leftIndex))];
157
179
  const rightEvent = navigationItems[Math.max(0, Math.min(totalEvents - 1, rightIndex))];
158
180
 
181
+ // Every write records the value it overwrote (for time travel), so even the
182
+ // very first event can be diffed against what was actually there. Three
183
+ // distinct origins, and conflating them would misinform:
184
+ // PRIOR — the write replaced a real value (including `[]`, `""`, `null`)
185
+ // ABSENT — the write created the key; there was nothing before it
186
+ // SCANNED — not a write at all, just the startup snapshot of a key that
187
+ // already existed, so nothing changed at this event
188
+ const originKind = useMemo(() => {
189
+ const data = rightEvent?.data;
190
+ if (data?.initialScan) return "scanned";
191
+ return data?.prevValue === null || data?.prevValue === undefined ? "absent" : "prior";
192
+ }, [rightEvent]);
193
+ const originValue = useMemo(() => {
194
+ if (originKind === "absent") return ABSENT_VALUE;
195
+ // A scanned key was already holding the event's own value, so diffing
196
+ // against it correctly reports no change.
197
+ if (originKind === "scanned") return parseValue(rightEvent?.data?.value);
198
+ return parseValue(rightEvent?.data?.prevValue);
199
+ }, [originKind, rightEvent]);
200
+
159
201
  // Create event display info for compare bar
160
- const leftEventInfo = useMemo(() => ({
202
+ const leftEventInfo = useMemo(() => isOriginLeft ? {
203
+ index: ORIGIN_INDEX,
204
+ label: ORIGIN_LABELS[originKind].label,
205
+ timestamp: ORIGIN_LABELS[originKind].timestamp,
206
+ relativeTime: ORIGIN_LABELS[originKind].relativeTime,
207
+ badge: /*#__PURE__*/_jsx(View, {
208
+ style: [styles.actionBadgeSmall, {
209
+ backgroundColor: `${macOSColors.text.muted}20`
210
+ }],
211
+ children: /*#__PURE__*/_jsx(Text, {
212
+ style: [styles.actionTextSmall, {
213
+ color: macOSColors.text.muted
214
+ }],
215
+ children: ORIGIN_LABELS[originKind].badge
216
+ })
217
+ })
218
+ } : {
161
219
  index: leftIndex,
162
220
  label: `#${leftIndex + 1} / ${totalEvents}`,
163
221
  timestamp: leftEvent ? formatTimeWithMs(leftEvent.timestamp) : "",
@@ -173,7 +231,7 @@ export function StorageEventDetailContent({
173
231
  children: translateStorageAction(leftEvent.action)
174
232
  })
175
233
  }) : undefined
176
- }), [leftIndex, leftEvent, totalEvents]);
234
+ }, [leftIndex, leftEvent, totalEvents, isOriginLeft, originKind]);
177
235
  const rightEventInfo = useMemo(() => ({
178
236
  index: rightIndex,
179
237
  label: `#${rightIndex + 1} / ${totalEvents}`,
@@ -203,7 +261,8 @@ export function StorageEventDetailContent({
203
261
 
204
262
  // Navigation handlers for compare bar
205
263
  const handleLeftPrevious = useCallback(() => {
206
- if (leftIndex > 0) {
264
+ // Stops at ORIGIN_INDEX — one step past event #1 is the state it overwrote.
265
+ if (leftIndex > ORIGIN_INDEX) {
207
266
  setLeftIndex(leftIndex - 1);
208
267
  }
209
268
  }, [leftIndex]);
@@ -223,54 +282,11 @@ export function StorageEventDetailContent({
223
282
  }
224
283
  }, [rightIndex, totalEvents]);
225
284
 
226
- // Render current value view
227
- const renderCurrentView = useCallback(() => {
228
- const parsed = parseValue(valueToShow);
229
- return /*#__PURE__*/_jsxs(View, {
230
- style: styles.contentCard,
231
- children: [/*#__PURE__*/_jsxs(View, {
232
- style: styles.valueHeader,
233
- children: [/*#__PURE__*/_jsx(Text, {
234
- style: styles.valueLabel,
235
- children: "CURRENT VALUE"
236
- }), /*#__PURE__*/_jsxs(View, {
237
- style: styles.valueHeaderBadges,
238
- children: [action ? /*#__PURE__*/_jsx(View, {
239
- style: [styles.actionBadge, {
240
- backgroundColor: `${actionColor}20`
241
- }],
242
- children: /*#__PURE__*/_jsx(Text, {
243
- style: [styles.actionText, {
244
- color: actionColor
245
- }],
246
- children: translateStorageAction(action)
247
- })
248
- }) : null, /*#__PURE__*/_jsx(View, {
249
- style: styles.typeBadge,
250
- children: /*#__PURE__*/_jsx(Text, {
251
- style: styles.typeText,
252
- children: valueType.toUpperCase()
253
- })
254
- })]
255
- })]
256
- }), /*#__PURE__*/_jsx(View, {
257
- style: styles.dataViewerContainer,
258
- children: /*#__PURE__*/_jsx(DataViewer, {
259
- title: "",
260
- data: parsed,
261
- showTypeFilter: true,
262
- rawMode: true,
263
- initialExpanded: true
264
- })
265
- })]
266
- });
267
- }, [valueToShow, action, actionColor, valueType]);
268
-
269
285
  // Memoized so the diff viewers don't see new oldValue/newValue refs on every
270
286
  // re-render. parseValue runs JSON.parse, which would otherwise produce fresh
271
287
  // object identities every time and force expensive recomputations downstream
272
288
  // (line diff, tree diff, expansion-state effects, etc.).
273
- const previousValue = useMemo(() => parseValue(leftEvent?.data?.value ?? null), [leftEvent]);
289
+ const previousValue = useMemo(() => isOriginLeft ? originValue : parseValue(leftEvent?.data?.value ?? null), [isOriginLeft, originValue, leftEvent]);
274
290
  const currentValue = useMemo(() => parseValue(rightEvent?.data?.value), [rightEvent]);
275
291
 
276
292
  // Render diff content
@@ -282,12 +298,17 @@ export function StorageEventDetailContent({
282
298
  differences: SPLIT_VIEW_DIFFERENCES,
283
299
  theme: diffThemes.devToolsDefault,
284
300
  options: SPLIT_VIEW_OPTIONS,
285
- showThemeName: false
301
+ showThemeName: false,
302
+ scrollEnabled: false
286
303
  });
287
304
  }
305
+
306
+ // Inline: the whole diff section scrolls as one page (see
307
+ // `scrollDiffSection` below), so the tree must not scroll itself.
288
308
  return /*#__PURE__*/_jsx(TreeDiffViewer, {
289
309
  oldValue: previousValue,
290
- newValue: currentValue
310
+ newValue: currentValue,
311
+ scrollEnabled: false
291
312
  });
292
313
  }, [previousValue, currentValue, diffMode]);
293
314
 
@@ -360,25 +381,23 @@ export function StorageEventDetailContent({
360
381
  onPress: handleCopy
361
382
  })]
362
383
  }), /*#__PURE__*/_jsx(EventHistoryViewer
363
- // View Toggle
384
+ // Diff only: no `renderCurrentView`, so the viewer drops the toggle
385
+ // cards entirely rather than showing a one-option switch.
364
386
  , {
365
- activeView: activeView,
366
- onViewChange: handleViewChange,
367
- currentViewLabel: "CURRENT VALUE",
368
- currentViewDescription: "View the current stored value",
369
- currentViewIcon: Database,
387
+ activeView: "diff",
370
388
  diffViewLabel: "DIFF VIEW",
371
389
  diffViewDescription: "Compare changes between versions",
372
390
  diffViewIcon: GitBranch
373
- // Current View
374
- ,
375
- renderCurrentView: renderCurrentView
376
391
  // Diff View
377
392
  ,
378
393
  diffModeTabs: diffModeTabs,
379
394
  activeDiffMode: diffMode,
380
395
  onDiffModeChange: handleDiffModeChange,
381
396
  renderDiffContent: renderDiffContent
397
+ // Both diff modes render inline now, so the whole section scrolls as
398
+ // one page rather than confining the rows to a nested pane.
399
+ ,
400
+ scrollDiffSection: true
382
401
  // Compare Bar
383
402
  ,
384
403
  leftEvent: leftEventInfo,
@@ -388,7 +407,7 @@ export function StorageEventDetailContent({
388
407
  onLeftNext: handleLeftNext,
389
408
  onRightPrevious: handleRightPrevious,
390
409
  onRightNext: handleRightNext,
391
- canLeftPrevious: leftIndex > 0,
410
+ canLeftPrevious: leftIndex > ORIGIN_INDEX,
392
411
  canLeftNext: leftIndex < rightIndex - 1,
393
412
  canRightPrevious: rightIndex > leftIndex + 1,
394
413
  canRightNext: rightIndex < totalEvents - 1
@@ -415,7 +434,8 @@ export function StorageEventDetailContent({
415
434
  export function StorageEventDetailFooter({
416
435
  conversation,
417
436
  selectedEventIndex = 0,
418
- onEventIndexChange = () => {}
437
+ onEventIndexChange = () => {},
438
+ applySafeAreaInset = true
419
439
  }) {
420
440
  const navigationItems = conversation.events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
421
441
  const totalEvents = navigationItems.length;
@@ -425,7 +445,8 @@ export function StorageEventDetailFooter({
425
445
  onPrevious: () => onEventIndexChange(Math.max(0, selectedEventIndex - 1)),
426
446
  onNext: () => onEventIndexChange(Math.min(totalEvents - 1, selectedEventIndex + 1)),
427
447
  itemLabel: "Event",
428
- subtitle: formatRelativeTime(navigationItems[selectedEventIndex]?.timestamp)
448
+ subtitle: formatRelativeTime(navigationItems[selectedEventIndex]?.timestamp),
449
+ applySafeAreaInset: applySafeAreaInset
429
450
  });
430
451
  }
431
452
  const styles = StyleSheet.create({
@@ -442,63 +463,6 @@ const styles = StyleSheet.create({
442
463
  borderBottomColor: buoyColors.border
443
464
  },
444
465
  // Content Card
445
- contentCard: {
446
- backgroundColor: macOSColors.background.card,
447
- borderRadius: 14,
448
- padding: 14,
449
- borderWidth: 1,
450
- borderColor: macOSColors.border.default,
451
- shadowColor: macOSColors.semantic.info,
452
- shadowOffset: {
453
- width: 0,
454
- height: 2
455
- },
456
- shadowOpacity: 0.04,
457
- shadowRadius: 16,
458
- elevation: 2
459
- },
460
- valueHeader: {
461
- flexDirection: "row",
462
- alignItems: "center",
463
- justifyContent: "space-between",
464
- marginBottom: 8
465
- },
466
- valueLabel: {
467
- fontSize: 10,
468
- color: macOSColors.text.secondary,
469
- fontFamily: "monospace",
470
- letterSpacing: 0.5,
471
- fontWeight: "600",
472
- textTransform: "uppercase"
473
- },
474
- valueHeaderBadges: {
475
- flexDirection: "row",
476
- alignItems: "center",
477
- gap: 6
478
- },
479
- actionBadge: {
480
- paddingHorizontal: 8,
481
- paddingVertical: 2,
482
- borderRadius: 4
483
- },
484
- actionText: {
485
- fontSize: 9,
486
- fontWeight: "700",
487
- fontFamily: "monospace",
488
- letterSpacing: 0.3
489
- },
490
- typeBadge: {
491
- paddingHorizontal: 8,
492
- paddingVertical: 2,
493
- borderRadius: 4,
494
- backgroundColor: macOSColors.background.input
495
- },
496
- typeText: {
497
- fontSize: 9,
498
- fontWeight: "600",
499
- color: macOSColors.text.muted,
500
- fontFamily: "monospace"
501
- },
502
466
  valueBox: {
503
467
  backgroundColor: macOSColors.background.base,
504
468
  borderRadius: 6,
@@ -522,10 +486,5 @@ const styles = StyleSheet.create({
522
486
  fontSize: 8,
523
487
  fontWeight: "700",
524
488
  fontFamily: "monospace"
525
- },
526
- dataViewerContainer: {
527
- marginTop: -12,
528
- marginHorizontal: -12,
529
- marginBottom: -12
530
489
  }
531
490
  });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState, useCallback } from "react";
4
4
  import { View, Text, StyleSheet, TouchableOpacity, ScrollView } from "react-native";
5
- import { Play, Pause, Trash2 } from "@buoy-gg/shared-ui";
5
+ import { Play, Pause, Trash2, macOSColors, buoyColors } from "@buoy-gg/shared-ui";
6
6
  import { startListening, stopListening, addListener, isListening as checkIsListening } from "../utils/AsyncStorageListener";
7
7
  import { translateStorageAction } from "../utils/storageActionHelpers";
8
8
  import { isMMKVAvailable } from "../utils/mmkvAvailability";
@@ -149,7 +149,7 @@ export function StorageEventListener() {
149
149
  return "#3B82F6";
150
150
  // Blue for merge
151
151
  default:
152
- return "#6B7280";
152
+ return macOSColors.text.icon;
153
153
  }
154
154
  };
155
155
  if (!isAsyncStorageAvailable) {
@@ -195,7 +195,7 @@ export function StorageEventListener() {
195
195
  disabled: events.length === 0,
196
196
  children: /*#__PURE__*/_jsx(Trash2, {
197
197
  size: 14,
198
- color: events.length > 0 ? "#6B7280" : "#374151"
198
+ color: events.length > 0 ? macOSColors.text.icon : macOSColors.border.toggle
199
199
  })
200
200
  })]
201
201
  })]
@@ -238,10 +238,10 @@ export function StorageEventListener() {
238
238
  }
239
239
  const styles = StyleSheet.create({
240
240
  container: {
241
- backgroundColor: "rgba(255, 255, 255, 0.03)",
241
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
242
242
  borderRadius: 8,
243
243
  borderWidth: 1,
244
- borderColor: "rgba(255, 255, 255, 0.08)",
244
+ borderColor: "rgba(128, 128, 128, 0.16)",
245
245
  marginBottom: 12,
246
246
  maxHeight: 200
247
247
  },
@@ -252,7 +252,7 @@ const styles = StyleSheet.create({
252
252
  paddingHorizontal: 12,
253
253
  paddingVertical: 8,
254
254
  borderBottomWidth: 1,
255
- borderBottomColor: "rgba(255, 255, 255, 0.06)"
255
+ borderBottomColor: "rgba(128, 128, 128, 0.12)"
256
256
  },
257
257
  headerLeft: {
258
258
  flexDirection: "row",
@@ -262,7 +262,7 @@ const styles = StyleSheet.create({
262
262
  title: {
263
263
  fontSize: 12,
264
264
  fontWeight: "500",
265
- color: "#9CA3AF",
265
+ color: buoyColors.textSecondary,
266
266
  textTransform: "uppercase",
267
267
  letterSpacing: 0.5
268
268
  },
@@ -277,7 +277,7 @@ const styles = StyleSheet.create({
277
277
  },
278
278
  statsText: {
279
279
  fontSize: 11,
280
- color: "#6B7280",
280
+ color: macOSColors.text.icon,
281
281
  fontWeight: "500"
282
282
  },
283
283
  listeningIndicator: {
@@ -295,9 +295,9 @@ const styles = StyleSheet.create({
295
295
  width: 28,
296
296
  height: 28,
297
297
  borderRadius: 6,
298
- backgroundColor: "rgba(255, 255, 255, 0.03)",
298
+ backgroundColor: "rgba(128, 128, 128, 0.06)",
299
299
  borderWidth: 1,
300
- borderColor: "rgba(255, 255, 255, 0.08)",
300
+ borderColor: "rgba(128, 128, 128, 0.16)",
301
301
  alignItems: "center",
302
302
  justifyContent: "center"
303
303
  },
@@ -318,7 +318,7 @@ const styles = StyleSheet.create({
318
318
  },
319
319
  emptyText: {
320
320
  fontSize: 11,
321
- color: "#6B7280",
321
+ color: macOSColors.text.icon,
322
322
  fontStyle: "italic"
323
323
  },
324
324
  eventItem: {
@@ -328,7 +328,7 @@ const styles = StyleSheet.create({
328
328
  paddingHorizontal: 12,
329
329
  paddingVertical: 6,
330
330
  borderBottomWidth: 1,
331
- borderBottomColor: "rgba(255, 255, 255, 0.03)"
331
+ borderBottomColor: "rgba(128, 128, 128, 0.06)"
332
332
  },
333
333
  eventLeft: {
334
334
  flex: 1,
@@ -344,11 +344,11 @@ const styles = StyleSheet.create({
344
344
  },
345
345
  eventData: {
346
346
  fontSize: 11,
347
- color: "#9CA3AF",
347
+ color: buoyColors.textSecondary,
348
348
  flex: 1
349
349
  },
350
350
  eventTime: {
351
351
  fontSize: 10,
352
- color: "#6B7280"
352
+ color: macOSColors.text.icon
353
353
  }
354
354
  });