@buoy-gg/storage 7.0.17 → 7.0.18

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 (38) hide show
  1. package/lib/commonjs/index.js +12 -0
  2. package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +5 -602
  3. package/lib/commonjs/storage/components/StorageModalWithTabs.js +17 -1
  4. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +13 -10
  5. package/lib/commonjs/storage/hooks/useMMKVKeys.js +28 -23
  6. package/lib/commonjs/storage/stores/storageEventStore.js +85 -1
  7. package/lib/commonjs/storage/sync/storageSyncAdapter.js +214 -6
  8. package/lib/commonjs/storage/utils/requiredStorageKeyScope.js +17 -0
  9. package/lib/commonjs/storage/utils/snapshotProvider.js +173 -5
  10. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +17 -3
  11. package/lib/module/index.js +6 -1
  12. package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +7 -603
  13. package/lib/module/storage/components/StorageModalWithTabs.js +17 -1
  14. package/lib/module/storage/hooks/useAsyncStorageKeys.js +13 -10
  15. package/lib/module/storage/hooks/useMMKVKeys.js +28 -23
  16. package/lib/module/storage/stores/storageEventStore.js +81 -0
  17. package/lib/module/storage/sync/storageSyncAdapter.js +215 -7
  18. package/lib/module/storage/utils/requiredStorageKeyScope.js +13 -0
  19. package/lib/module/storage/utils/snapshotProvider.js +173 -5
  20. package/lib/module/storage/utils/storageTimeTravelUtils.js +17 -3
  21. package/lib/typescript/index.d.ts +2 -1
  22. package/lib/typescript/index.d.ts.map +1 -1
  23. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -18
  24. package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
  25. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +8 -1
  26. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  27. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
  28. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
  29. package/lib/typescript/storage/stores/storageEventStore.d.ts +26 -0
  30. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
  31. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +69 -3
  32. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
  33. package/lib/typescript/storage/types.d.ts +9 -9
  34. package/lib/typescript/storage/types.d.ts.map +1 -1
  35. package/lib/typescript/storage/utils/requiredStorageKeyScope.d.ts +10 -0
  36. package/lib/typescript/storage/utils/requiredStorageKeyScope.d.ts.map +1 -0
  37. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
  38. package/package.json +5 -5
@@ -1,605 +1,9 @@
1
1
  "use strict";
2
2
 
3
- import { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { View, Text, FlatList, StyleSheet, TouchableOpacity } from "react-native";
5
- import { computeLineDiff, DiffType } from "../../../utils/lineDiff";
6
- import { DiffSummary } from "../components/DiffSummary";
7
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
- const DEFAULT_OPTIONS = {
9
- hideLineNumbers: false,
10
- disableWordDiff: false,
11
- showDiffOnly: false,
12
- compareMethod: "words",
13
- contextLines: 3,
14
- lineOffset: 0
15
- };
16
- export function ThemedSplitView({
17
- oldValue,
18
- newValue,
19
- theme,
20
- options = DEFAULT_OPTIONS,
21
- showThemeName = false,
22
- scrollEnabled = true
23
- }) {
24
- // Inline mode drops the fixed 400pt pane so the container grows with the
25
- // rows; the ancestor ScrollView is what scrolls them.
26
- const dynamicStyles = useMemo(() => createDynamicStyles(theme, scrollEnabled), [theme, scrollEnabled]);
27
- const diffComputeOptions = useMemo(() => ({
28
- compareMethod: options.compareMethod,
29
- disableWordDiff: options.disableWordDiff,
30
- showDiffOnly: options.showDiffOnly,
31
- contextLines: options.contextLines
32
- }), [options.compareMethod, options.disableWordDiff, options.showDiffOnly, options.contextLines]);
33
- const lineDiffs = useMemo(() => computeLineDiff(oldValue, newValue, diffComputeOptions), [oldValue, newValue, diffComputeOptions]);
34
-
35
- // One pass for the summary instead of three .filter() calls per render.
36
- const summary = useMemo(() => {
37
- let added = 0;
38
- let removed = 0;
39
- let modified = 0;
40
- for (const d of lineDiffs) {
41
- if (d.type === DiffType.ADDED) added++;else if (d.type === DiffType.REMOVED) removed++;else if (d.type === DiffType.MODIFIED) modified++;
42
- }
43
- return {
44
- added,
45
- removed,
46
- modified
47
- };
48
- }, [lineDiffs]);
49
-
50
- // Auto-collapse runs of unchanged lines that aren't near a change. Keeps
51
- // CONTEXT_LINES on either side of every change visible (GitHub-style), folds
52
- // the rest into a tappable header.
53
- const [expandedFolds, setExpandedFolds] = useState(() => new Set());
54
-
55
- // Reset fold expansion when the user actually switches comparisons. Parent
56
- // memoizes oldValue/newValue, so this only fires on a real change — not on
57
- // every re-render. First run is a no-op (initial state is already empty).
58
- const hasMountedRef = useRef(false);
59
- useEffect(() => {
60
- if (!hasMountedRef.current) {
61
- hasMountedRef.current = true;
62
- return;
63
- }
64
- setExpandedFolds(new Set());
65
- }, [oldValue, newValue]);
66
- const items = useMemo(() => buildSplitItems(lineDiffs, expandedFolds), [lineDiffs, expandedFolds]);
67
- const toggleFold = useCallback(foldId => {
68
- setExpandedFolds(prev => {
69
- const next = new Set(prev);
70
- if (next.has(foldId)) next.delete(foldId);else next.add(foldId);
71
- return next;
72
- });
73
- }, []);
74
- const keyExtractor = useCallback(item => item.kind === "row" ? `r:${item.diffIndex}` : `f:${item.foldId}`, []);
75
- const renderSplitItem = useCallback(item => {
76
- if (item.kind === "fold") {
77
- return /*#__PURE__*/_jsx(FoldHeader, {
78
- foldId: item.foldId,
79
- count: item.count,
80
- expanded: item.expanded,
81
- theme: theme,
82
- styles: dynamicStyles,
83
- onToggle: toggleFold
84
- });
85
- }
86
- return /*#__PURE__*/_jsx(DiffRow, {
87
- diff: item.diff,
88
- showSeparator: false,
89
- theme: theme,
90
- options: options,
91
- styles: dynamicStyles
92
- });
93
- }, [theme, options, dynamicStyles, toggleFold]);
94
- const renderItem = useCallback(({
95
- item
96
- }) => renderSplitItem(item), [renderSplitItem]);
97
- return /*#__PURE__*/_jsxs(View, {
98
- style: [dynamicStyles.container, theme.glowColor && {
99
- shadowColor: theme.glowColor,
100
- shadowOffset: {
101
- width: 0,
102
- height: 0
103
- },
104
- shadowOpacity: theme.neonIntensity && theme.neonIntensity > 0.7 ? 0.5 : theme.neonIntensity && theme.neonIntensity > 0.3 ? 0.3 : 0.1,
105
- shadowRadius: theme.neonIntensity && theme.neonIntensity > 0.7 ? 10 : theme.neonIntensity && theme.neonIntensity > 0.3 ? 5 : 2
106
- }],
107
- children: [showThemeName && /*#__PURE__*/_jsxs(View, {
108
- style: dynamicStyles.themeBadge,
109
- children: [/*#__PURE__*/_jsx(Text, {
110
- style: dynamicStyles.themeName,
111
- children: theme.name
112
- }), /*#__PURE__*/_jsx(Text, {
113
- style: dynamicStyles.themeDescription,
114
- children: theme.description
115
- })]
116
- }), /*#__PURE__*/_jsxs(View, {
117
- style: dynamicStyles.header,
118
- children: [/*#__PURE__*/_jsx(View, {
119
- style: dynamicStyles.headerLeft,
120
- children: /*#__PURE__*/_jsx(Text, {
121
- style: dynamicStyles.headerTitle,
122
- children: "PREV"
123
- })
124
- }), /*#__PURE__*/_jsx(View, {
125
- style: dynamicStyles.divider
126
- }), /*#__PURE__*/_jsx(View, {
127
- style: dynamicStyles.headerRight,
128
- children: /*#__PURE__*/_jsx(Text, {
129
- style: dynamicStyles.headerTitle,
130
- children: "CUR"
131
- })
132
- })]
133
- }), /*#__PURE__*/_jsx(DiffSummary, {
134
- added: summary.added,
135
- removed: summary.removed,
136
- modified: summary.modified,
137
- theme: theme
138
- }), lineDiffs.length === 0 ? /*#__PURE__*/_jsx(View, {
139
- style: dynamicStyles.emptyState,
140
- children: /*#__PURE__*/_jsx(Text, {
141
- style: dynamicStyles.emptyText,
142
- children: options.showDiffOnly ? "No differences found" : "No content to display"
143
- })
144
- }) : scrollEnabled ? /*#__PURE__*/_jsx(FlatList, {
145
- style: dynamicStyles.scrollView,
146
- contentContainerStyle: dynamicStyles.scrollContent,
147
- data: items,
148
- renderItem: renderItem,
149
- keyExtractor: keyExtractor,
150
- showsVerticalScrollIndicator: false,
151
- initialNumToRender: 20,
152
- maxToRenderPerBatch: 20,
153
- windowSize: 7,
154
- removeClippedSubviews: true
155
- }) :
156
- /*#__PURE__*/
157
- // A FlatList can't virtualize inside a page ScrollView — it has no
158
- // viewport of its own to measure against — so render the rows directly.
159
- // Folding already keeps this list short (unchanged runs collapse).
160
- _jsx(View, {
161
- style: dynamicStyles.scrollContent,
162
- children: items.map(item => /*#__PURE__*/_jsx(Fragment, {
163
- children: renderSplitItem(item)
164
- }, keyExtractor(item)))
165
- })]
166
- });
167
- }
168
-
169
- // Number of context lines kept visible on either side of every change.
170
- const CONTEXT_LINES = 3;
171
- // Don't bother folding tiny gaps — toggling them is more friction than scroll.
172
- const MIN_FOLD_SIZE = 4;
173
- function buildSplitItems(lineDiffs, expandedFolds) {
174
- const n = lineDiffs.length;
175
- if (n === 0) return [];
176
-
177
- // Mark every line that should always be visible: any change, plus
178
- // CONTEXT_LINES on either side of it.
179
- const visible = new Array(n).fill(false);
180
- for (let i = 0; i < n; i++) {
181
- if (lineDiffs[i].type !== DiffType.DEFAULT) {
182
- const start = Math.max(0, i - CONTEXT_LINES);
183
- const end = Math.min(n - 1, i + CONTEXT_LINES);
184
- for (let k = start; k <= end; k++) visible[k] = true;
185
- }
186
- }
187
- const items = [];
188
- let foldId = 0;
189
- let i = 0;
190
- while (i < n) {
191
- if (visible[i]) {
192
- items.push({
193
- kind: "row",
194
- diff: lineDiffs[i],
195
- diffIndex: i
196
- });
197
- i++;
198
- continue;
199
- }
200
- let j = i;
201
- while (j < n && !visible[j]) j++;
202
- const runLength = j - i;
203
- if (runLength < MIN_FOLD_SIZE) {
204
- for (let k = i; k < j; k++) {
205
- items.push({
206
- kind: "row",
207
- diff: lineDiffs[k],
208
- diffIndex: k
209
- });
210
- }
211
- } else {
212
- const id = foldId++;
213
- const expanded = expandedFolds.has(id);
214
- items.push({
215
- kind: "fold",
216
- foldId: id,
217
- count: runLength,
218
- expanded
219
- });
220
- if (expanded) {
221
- for (let k = i; k < j; k++) {
222
- items.push({
223
- kind: "row",
224
- diff: lineDiffs[k],
225
- diffIndex: k
226
- });
227
- }
228
- }
229
- }
230
- i = j;
231
- }
232
- return items;
233
- }
234
- const FoldHeader = /*#__PURE__*/memo(function FoldHeader({
235
- foldId,
236
- count,
237
- expanded,
238
- theme,
239
- styles,
240
- onToggle
241
- }) {
242
- return /*#__PURE__*/_jsx(TouchableOpacity, {
243
- style: [styles.foldHeader, {
244
- backgroundColor: theme.separatorBackground
245
- }],
246
- onPress: () => onToggle(foldId),
247
- activeOpacity: 0.7,
248
- children: /*#__PURE__*/_jsx(Text, {
249
- style: [styles.foldHeaderText, {
250
- color: theme.separatorText
251
- }],
252
- children: expanded ? `▼ ${count} unchanged line${count === 1 ? "" : "s"} — tap to hide` : `▶ ${count} unchanged line${count === 1 ? "" : "s"} — tap to expand`
253
- })
254
- });
255
- });
256
- const DiffRow = /*#__PURE__*/memo(function DiffRow({
257
- diff,
258
- showSeparator,
259
- theme,
260
- options,
261
- styles
262
- }) {
263
- const isRemoved = diff.type === DiffType.REMOVED;
264
- const isAdded = diff.type === DiffType.ADDED;
265
- const isModified = diff.type === DiffType.MODIFIED;
266
- const isDefault = diff.type === DiffType.DEFAULT;
267
- return /*#__PURE__*/_jsxs(Fragment, {
268
- children: [showSeparator && /*#__PURE__*/_jsx(View, {
269
- style: styles.separator,
270
- children: /*#__PURE__*/_jsx(Text, {
271
- style: styles.separatorText,
272
- children: "\u2022 \u2022 \u2022"
273
- })
274
- }), /*#__PURE__*/_jsxs(View, {
275
- style: styles.row,
276
- children: [/*#__PURE__*/_jsx(View, {
277
- style: styles.leftSide,
278
- children: isRemoved || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
279
- lineNumber: diff.leftLineNumber,
280
- content: diff.leftContent,
281
- type: isModified ? DiffType.REMOVED : diff.type,
282
- marker: isRemoved || isModified ? "-" : " ",
283
- theme: theme,
284
- options: options,
285
- styles: styles
286
- }) : /*#__PURE__*/_jsx(EmptyLineSide, {
287
- options: options,
288
- styles: styles
289
- })
290
- }), /*#__PURE__*/_jsx(View, {
291
- style: styles.centerDivider
292
- }), /*#__PURE__*/_jsx(View, {
293
- style: styles.rightSide,
294
- children: isAdded || isModified || isDefault ? /*#__PURE__*/_jsx(LineSide, {
295
- lineNumber: diff.rightLineNumber,
296
- content: diff.rightContent,
297
- type: isModified ? DiffType.ADDED : diff.type,
298
- marker: isAdded || isModified ? "+" : " ",
299
- theme: theme,
300
- options: options,
301
- styles: styles
302
- }) : /*#__PURE__*/_jsx(EmptyLineSide, {
303
- options: options,
304
- styles: styles
305
- })
306
- })]
307
- })]
308
- });
309
- });
310
- function LineSide({
311
- lineNumber,
312
- content,
313
- type,
314
- marker,
315
- theme,
316
- options,
317
- styles
318
- }) {
319
- const colors = getDiffColors(type, theme);
320
- return /*#__PURE__*/_jsxs(Fragment, {
321
- children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
322
- style: [styles.gutter, {
323
- backgroundColor: theme.lineNumberBackground
324
- }],
325
- children: /*#__PURE__*/_jsx(Text, {
326
- style: styles.lineNumber,
327
- children: lineNumber || " "
328
- })
329
- }), /*#__PURE__*/_jsx(View, {
330
- style: [styles.marker, {
331
- backgroundColor: colors.markerBg
332
- }],
333
- children: /*#__PURE__*/_jsx(Text, {
334
- style: [styles.markerText, {
335
- color: theme.markerText
336
- }],
337
- children: marker
338
- })
339
- }), /*#__PURE__*/_jsx(View, {
340
- style: [styles.contentCell, {
341
- backgroundColor: colors.background
342
- }],
343
- children: /*#__PURE__*/_jsx(Text, {
344
- style: [styles.content, {
345
- color: colors.text
346
- }],
347
- children: Array.isArray(content) ? content.map((word, idx) => {
348
- let backgroundColor = "transparent";
349
- if (word.type === DiffType.ADDED) {
350
- backgroundColor = theme.addedWordHighlight;
351
- } else if (word.type === DiffType.REMOVED) {
352
- backgroundColor = theme.removedWordHighlight;
353
- }
354
- return /*#__PURE__*/_jsx(Text, {
355
- style: [styles.wordDiff, {
356
- backgroundColor
357
- }],
358
- children: word.value
359
- }, idx);
360
- }) : content || " "
361
- })
362
- })]
363
- });
364
- }
365
- function EmptyLineSide({
366
- options,
367
- styles
368
- }) {
369
- return /*#__PURE__*/_jsxs(Fragment, {
370
- children: [!options.hideLineNumbers && /*#__PURE__*/_jsx(View, {
371
- style: [styles.gutter, styles.emptyGutter],
372
- children: /*#__PURE__*/_jsx(Text, {
373
- style: styles.lineNumber,
374
- children: " "
375
- })
376
- }), /*#__PURE__*/_jsx(View, {
377
- style: [styles.marker, styles.emptyMarker],
378
- children: /*#__PURE__*/_jsx(Text, {
379
- style: styles.markerText,
380
- children: " "
381
- })
382
- }), /*#__PURE__*/_jsx(View, {
383
- style: [styles.contentCell, styles.emptyContent],
384
- children: /*#__PURE__*/_jsx(Text, {
385
- style: styles.content,
386
- children: " "
387
- })
388
- })]
389
- });
390
- }
391
- function getDiffColors(type, theme) {
392
- switch (type) {
393
- case DiffType.ADDED:
394
- return {
395
- background: theme.addedBackground,
396
- text: theme.addedText,
397
- markerBg: theme.markerAddedBackground
398
- };
399
- case DiffType.REMOVED:
400
- return {
401
- background: theme.removedBackground,
402
- text: theme.removedText,
403
- markerBg: theme.markerRemovedBackground
404
- };
405
- case DiffType.DEFAULT:
406
- default:
407
- return {
408
- background: theme.unchangedBackground,
409
- text: theme.unchangedText,
410
- markerBg: "transparent"
411
- };
412
- }
413
- }
414
-
415
- // Create dynamic styles based on theme
416
- function createDynamicStyles(theme, scrollEnabled) {
417
- return StyleSheet.create({
418
- container: {
419
- // Fixed pane only when the rows scroll themselves; inline mode sizes to
420
- // content so the page scroll reveals it.
421
- ...(scrollEnabled ? {
422
- height: 400
423
- } : null),
424
- backgroundColor: theme.background,
425
- borderRadius: 8,
426
- overflow: "hidden",
427
- borderWidth: 1,
428
- borderColor: theme.borderColor
429
- },
430
- themeBadge: {
431
- backgroundColor: theme.panelBackground,
432
- paddingHorizontal: 12,
433
- paddingVertical: 8,
434
- borderBottomWidth: 1,
435
- borderBottomColor: theme.borderColor
436
- },
437
- themeName: {
438
- fontSize: 12,
439
- fontWeight: "700",
440
- color: theme.accentColor || theme.unchangedText,
441
- fontFamily: "monospace",
442
- letterSpacing: 0.5
443
- },
444
- themeDescription: {
445
- fontSize: 10,
446
- color: theme.unchangedText,
447
- fontFamily: "monospace",
448
- marginTop: 2,
449
- opacity: 0.8
450
- },
451
- header: {
452
- flexDirection: "row",
453
- backgroundColor: theme.headerBackground,
454
- borderBottomWidth: 1,
455
- borderBottomColor: theme.borderColor
456
- },
457
- headerLeft: {
458
- flex: 1,
459
- paddingVertical: 8,
460
- paddingHorizontal: 12
461
- },
462
- headerRight: {
463
- flex: 1,
464
- paddingVertical: 8,
465
- paddingHorizontal: 12
466
- },
467
- headerTitle: {
468
- fontSize: 10,
469
- fontWeight: "700",
470
- color: theme.accentColor || theme.unchangedText,
471
- fontFamily: "monospace",
472
- letterSpacing: 0.5,
473
- textTransform: "uppercase"
474
- },
475
- divider: {
476
- width: 1,
477
- backgroundColor: theme.dividerColor
478
- },
479
- scrollView: {
480
- flex: 1
481
- },
482
- scrollContent: {
483
- paddingBottom: 10
484
- },
485
- row: {
486
- flexDirection: "row",
487
- minHeight: 20
488
- },
489
- leftSide: {
490
- flex: 1,
491
- flexDirection: "row"
492
- },
493
- rightSide: {
494
- flex: 1,
495
- flexDirection: "row"
496
- },
497
- centerDivider: {
498
- width: 1,
499
- backgroundColor: theme.dividerColor
500
- },
501
- gutter: {
502
- width: 35,
503
- paddingHorizontal: 4,
504
- justifyContent: "center",
505
- alignItems: "flex-end",
506
- backgroundColor: theme.lineNumberBackground,
507
- borderRightWidth: 1,
508
- borderRightColor: theme.lineNumberBorder
509
- },
510
- emptyGutter: {
511
- backgroundColor: theme.contextBackground
512
- },
513
- lineNumber: {
514
- fontSize: 9,
515
- fontFamily: "monospace",
516
- color: theme.lineNumberText
517
- },
518
- marker: {
519
- width: 20,
520
- justifyContent: "center",
521
- alignItems: "center"
522
- },
523
- emptyMarker: {
524
- backgroundColor: theme.contextBackground
525
- },
526
- markerText: {
527
- fontSize: 10,
528
- fontFamily: "monospace",
529
- fontWeight: "600"
530
- },
531
- contentCell: {
532
- flex: 1,
533
- paddingHorizontal: 8,
534
- paddingVertical: 2
535
- },
536
- emptyContent: {
537
- backgroundColor: theme.contextBackground
538
- },
539
- content: {
540
- fontSize: 10,
541
- fontFamily: "monospace",
542
- lineHeight: 16
543
- },
544
- wordDiff: {
545
- fontSize: 10,
546
- fontFamily: "monospace"
547
- },
548
- summaryBar: {
549
- flexDirection: "row",
550
- alignItems: "center",
551
- justifyContent: "center",
552
- gap: 16,
553
- paddingVertical: 6,
554
- backgroundColor: theme.summaryBackground,
555
- borderTopWidth: 1,
556
- borderTopColor: theme.borderColor
557
- },
558
- summaryItem: {
559
- flexDirection: "row",
560
- alignItems: "center"
561
- },
562
- summaryText: {
563
- fontSize: 10,
564
- fontFamily: "monospace",
565
- fontWeight: "700"
566
- },
567
- separator: {
568
- height: 20,
569
- justifyContent: "center",
570
- alignItems: "center",
571
- backgroundColor: theme.separatorBackground
572
- },
573
- separatorText: {
574
- fontSize: 8,
575
- color: theme.separatorText,
576
- fontFamily: "monospace",
577
- letterSpacing: 2
578
- },
579
- foldHeader: {
580
- paddingVertical: 6,
581
- paddingHorizontal: 12,
582
- alignItems: "center",
583
- justifyContent: "center",
584
- borderTopWidth: 1,
585
- borderBottomWidth: 1,
586
- borderColor: theme.borderColor
587
- },
588
- foldHeaderText: {
589
- fontSize: 10,
590
- fontFamily: "monospace",
591
- letterSpacing: 0.5
592
- },
593
- emptyState: {
594
- padding: 40,
595
- alignItems: "center",
596
- justifyContent: "center"
597
- },
598
- emptyText: {
599
- fontSize: 11,
600
- color: theme.emptyStateText,
601
- fontStyle: "italic",
602
- fontFamily: "monospace"
603
- }
604
- });
605
- }
3
+ /**
4
+ * Re-export shim — ThemedSplitView now lives in @buoy-gg/shared-ui/dataViewer
5
+ * (promoted so redux/zustand/jotai/time-machine share one copy; this used to
6
+ * be the only fork with folding + virtualization, and fixes had to be made
7
+ * twice). Keep importing from here inside the storage package.
8
+ */
9
+ export { ThemedSplitView } from "@buoy-gg/shared-ui/dataViewer";
@@ -29,7 +29,8 @@ export function StorageModalWithTabs({
29
29
  requiredStorageKeys = [],
30
30
  inline = false,
31
31
  onTabChange,
32
- onRefresh
32
+ onRefresh,
33
+ onEventSelect
33
34
  }) {
34
35
  const isPro = useIsPro();
35
36
  const [activeTab, setActiveTab] = useState("browser");
@@ -457,6 +458,21 @@ export function StorageModalWithTabs({
457
458
  return conversations.find(c => c.key === selectedHistoryKey) || null;
458
459
  }, [selectedHistoryKey, conversations]);
459
460
 
461
+ // Wire-form snapshots omit oversized values. Tell a remote host which
462
+ // event(s) are on screen so it can fetch them — selected + previous, because
463
+ // the detail pane diffs those two.
464
+ useEffect(() => {
465
+ if (!onEventSelect) return;
466
+ const conversation = selectedConversation ? selectedConversation : selectedHistoryEventIndex !== null ? selectedHistoryConversation : null;
467
+ const index = selectedConversation ? selectedEventIndex : selectedHistoryEventIndex;
468
+ if (!conversation || index == null) return;
469
+ const sorted = [...conversation.events].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
470
+ const selected = sorted[index];
471
+ const previous = sorted[index - 1];
472
+ if (selected) onEventSelect(selected);
473
+ if (previous) onEventSelect(previous);
474
+ }, [onEventSelect, selectedConversation, selectedEventIndex, selectedHistoryConversation, selectedHistoryEventIndex]);
475
+
460
476
  /**
461
477
  * Events passing everything EXCEPT the backend pills.
462
478
  *