@buoy-gg/time-machine 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.
- package/lib/commonjs/components/HeaderReloadButton.js +61 -0
- package/lib/commonjs/components/HeaderReloadButton.js.map +1 -0
- package/lib/commonjs/components/TimeMachineBody.js +343 -127
- package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
- package/lib/commonjs/components/TimeMachineModal.js +1 -0
- package/lib/commonjs/components/TimeMachineModal.js.map +1 -1
- package/lib/commonjs/components/preview/ItemDetailScreen.js +189 -0
- package/lib/commonjs/components/preview/ItemDetailScreen.js.map +1 -0
- package/lib/commonjs/components/preview/RestorePreviewScreen.js +1056 -0
- package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -0
- package/lib/commonjs/components/preview/verdictMeta.js +58 -0
- package/lib/commonjs/components/preview/verdictMeta.js.map +1 -0
- package/lib/commonjs/preset.js +11 -2
- package/lib/commonjs/preset.js.map +1 -1
- package/lib/commonjs/store/snapshotVault.js +19 -0
- package/lib/commonjs/store/snapshotVault.js.map +1 -1
- package/lib/commonjs/store/timeMachineStore.js +298 -7
- package/lib/commonjs/store/timeMachineStore.js.map +1 -1
- package/lib/commonjs/sync/timeMachineSyncAdapter.js +62 -2
- package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +1 -1
- package/lib/module/components/HeaderReloadButton.js +56 -0
- package/lib/module/components/HeaderReloadButton.js.map +1 -0
- package/lib/module/components/TimeMachineBody.js +345 -129
- package/lib/module/components/TimeMachineBody.js.map +1 -1
- package/lib/module/components/TimeMachineModal.js +1 -0
- package/lib/module/components/TimeMachineModal.js.map +1 -1
- package/lib/module/components/preview/ItemDetailScreen.js +184 -0
- package/lib/module/components/preview/ItemDetailScreen.js.map +1 -0
- package/lib/module/components/preview/RestorePreviewScreen.js +1051 -0
- package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -0
- package/lib/module/components/preview/verdictMeta.js +53 -0
- package/lib/module/components/preview/verdictMeta.js.map +1 -0
- package/lib/module/preset.js +11 -2
- package/lib/module/preset.js.map +1 -1
- package/lib/module/store/snapshotVault.js +18 -0
- package/lib/module/store/snapshotVault.js.map +1 -1
- package/lib/module/store/timeMachineStore.js +299 -8
- package/lib/module/store/timeMachineStore.js.map +1 -1
- package/lib/module/sync/timeMachineSyncAdapter.js +62 -2
- package/lib/module/sync/timeMachineSyncAdapter.js.map +1 -1
- package/lib/typescript/components/HeaderReloadButton.d.ts +2 -0
- package/lib/typescript/components/HeaderReloadButton.d.ts.map +1 -0
- package/lib/typescript/components/TimeMachineBody.d.ts.map +1 -1
- package/lib/typescript/components/TimeMachineModal.d.ts.map +1 -1
- package/lib/typescript/components/preview/ItemDetailScreen.d.ts +11 -0
- package/lib/typescript/components/preview/ItemDetailScreen.d.ts.map +1 -0
- package/lib/typescript/components/preview/RestorePreviewScreen.d.ts +15 -0
- package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -0
- package/lib/typescript/components/preview/verdictMeta.d.ts +10 -0
- package/lib/typescript/components/preview/verdictMeta.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +15 -3
- package/lib/typescript/preset.d.ts.map +1 -1
- package/lib/typescript/store/snapshotVault.d.ts +7 -0
- package/lib/typescript/store/snapshotVault.d.ts.map +1 -1
- package/lib/typescript/store/timeMachineStore.d.ts +49 -2
- package/lib/typescript/store/timeMachineStore.d.ts.map +1 -1
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +36 -0
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +1 -1
- package/package.json +13 -6
- package/src/components/HeaderReloadButton.tsx +45 -0
- package/src/components/TimeMachineBody.tsx +416 -137
- package/src/components/TimeMachineModal.tsx +1 -0
- package/src/components/preview/ItemDetailScreen.tsx +172 -0
- package/src/components/preview/RestorePreviewScreen.tsx +1089 -0
- package/src/components/preview/verdictMeta.ts +40 -0
- package/src/preset.tsx +11 -2
- package/src/store/snapshotVault.ts +28 -0
- package/src/store/timeMachineStore.ts +330 -7
- package/src/sync/timeMachineSyncAdapter.ts +63 -3
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Item detail — one preview item's before/after, rendered with the two
|
|
3
|
+
* familiar views (TREE / SPLIT, same as storage events).
|
|
4
|
+
*
|
|
5
|
+
* The render-layer viewers only handle plain JSON faithfully, so both sides
|
|
6
|
+
* pass through toDiffable (Dates→ISO, Maps→objects, BigInt→"1n", cycles→
|
|
7
|
+
* "[Circular]") and the pair is memoized ONCE per item — the storage tool's
|
|
8
|
+
* fix for the fresh-identity-every-render trap that redux/zustand still have.
|
|
9
|
+
*/
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
12
|
+
import {
|
|
13
|
+
DiffModeTabs,
|
|
14
|
+
buoyColors,
|
|
15
|
+
devToolsStorageKeys,
|
|
16
|
+
persistentStorage,
|
|
17
|
+
} from "@buoy-gg/shared-ui";
|
|
18
|
+
import { useToolHeader, useToolHost } from "@buoy-gg/core";
|
|
19
|
+
import type { StateSnapshotPreviewItem } from "@buoy-gg/shared-ui";
|
|
20
|
+
import {
|
|
21
|
+
ABSENT_VALUE,
|
|
22
|
+
ThemedSplitView,
|
|
23
|
+
TreeDiffViewer,
|
|
24
|
+
devToolsDefaultTheme,
|
|
25
|
+
toDiffable,
|
|
26
|
+
} from "@buoy-gg/shared-ui/dataViewer";
|
|
27
|
+
import { VERDICT_META } from "./verdictMeta";
|
|
28
|
+
|
|
29
|
+
const DIFF_MODE_KEY = devToolsStorageKeys.timeMachine.diffViewerMode();
|
|
30
|
+
const TABS = [
|
|
31
|
+
{ key: "tree", label: "TREE VIEW" },
|
|
32
|
+
{ key: "split", label: "SPLIT VIEW" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export interface ItemDetailScreenProps {
|
|
36
|
+
sourceLabel: string;
|
|
37
|
+
item: StateSnapshotPreviewItem;
|
|
38
|
+
/** "old → new" side labels, e.g. "CURRENT STATE" / snapshot name. */
|
|
39
|
+
leftLabel: string;
|
|
40
|
+
rightLabel: string;
|
|
41
|
+
onBack: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function ItemDetailScreen({
|
|
45
|
+
sourceLabel,
|
|
46
|
+
item,
|
|
47
|
+
leftLabel,
|
|
48
|
+
rightLabel,
|
|
49
|
+
onBack,
|
|
50
|
+
}: ItemDetailScreenProps) {
|
|
51
|
+
// Persisted view preference — storage's load-guard/validate/optimistic
|
|
52
|
+
// pattern, default TREE (structured state reads better as a tree).
|
|
53
|
+
const [diffMode, setDiffMode] = useState<string>("tree");
|
|
54
|
+
const hasLoadedMode = useRef(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (hasLoadedMode.current) return;
|
|
57
|
+
hasLoadedMode.current = true;
|
|
58
|
+
void persistentStorage.getItem(DIFF_MODE_KEY).then((saved) => {
|
|
59
|
+
if (saved === "tree" || saved === "split") setDiffMode(saved);
|
|
60
|
+
});
|
|
61
|
+
}, []);
|
|
62
|
+
const changeMode = (mode: string) => {
|
|
63
|
+
setDiffMode(mode);
|
|
64
|
+
void persistentStorage.setItem(DIFF_MODE_KEY, mode).catch(() => {});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const meta = VERDICT_META[item.verdict];
|
|
68
|
+
const host = useToolHost();
|
|
69
|
+
const hosted = host.instanceId !== undefined;
|
|
70
|
+
|
|
71
|
+
// Hosted: the shared modal header shows this item's identity + back.
|
|
72
|
+
useToolHeader(
|
|
73
|
+
hosted
|
|
74
|
+
? {
|
|
75
|
+
title: item.label,
|
|
76
|
+
subtitle: `${sourceLabel} · ${meta.label} · ${leftLabel} → ${rightLabel}`,
|
|
77
|
+
onBack,
|
|
78
|
+
searchable: false, // nothing searchable on a single item's diff
|
|
79
|
+
}
|
|
80
|
+
: null,
|
|
81
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
82
|
+
[hosted, item.key, sourceLabel, leftLabel, rightLabel]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Normalize ONCE per item; ABSENT_VALUE keeps added/removed reading as
|
|
86
|
+
// pure additions/removals instead of diffs against the literal "null".
|
|
87
|
+
const { oldValue, newValue } = useMemo(
|
|
88
|
+
() => ({
|
|
89
|
+
oldValue:
|
|
90
|
+
item.verdict === "added" ? ABSENT_VALUE : toDiffable(item.oldValue),
|
|
91
|
+
newValue:
|
|
92
|
+
item.verdict === "removed" ? ABSENT_VALUE : toDiffable(item.newValue),
|
|
93
|
+
}),
|
|
94
|
+
[item]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<View style={styles.container}>
|
|
99
|
+
{!hosted && (
|
|
100
|
+
<View style={styles.screenHeader}>
|
|
101
|
+
<TouchableOpacity onPress={onBack} hitSlop={HIT} testID="tm-item-back">
|
|
102
|
+
<Text style={styles.backText}>‹ Preview</Text>
|
|
103
|
+
</TouchableOpacity>
|
|
104
|
+
<View style={styles.titleBlock}>
|
|
105
|
+
<Text style={styles.title} numberOfLines={1}>
|
|
106
|
+
<Text style={{ color: meta.color }}>{meta.glyph} </Text>
|
|
107
|
+
{item.label}
|
|
108
|
+
</Text>
|
|
109
|
+
<Text style={styles.subtitle} numberOfLines={1}>
|
|
110
|
+
{sourceLabel} · {meta.label} · {leftLabel} → {rightLabel}
|
|
111
|
+
</Text>
|
|
112
|
+
</View>
|
|
113
|
+
</View>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
{item.reason && <Text style={styles.reason}>⚠ {item.reason}</Text>}
|
|
117
|
+
|
|
118
|
+
<View style={styles.tabs}>
|
|
119
|
+
<DiffModeTabs tabs={TABS} activeTab={diffMode} onTabChange={changeMode} />
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
{/* One page-level scroller; both viewers render inline at natural
|
|
123
|
+
height (scrollEnabled=false) — the storage matched-pair contract. */}
|
|
124
|
+
<ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContent}>
|
|
125
|
+
{diffMode === "tree" ? (
|
|
126
|
+
<TreeDiffViewer
|
|
127
|
+
oldValue={oldValue}
|
|
128
|
+
newValue={newValue}
|
|
129
|
+
theme="dark"
|
|
130
|
+
showUnchanged={false}
|
|
131
|
+
scrollEnabled={false}
|
|
132
|
+
/>
|
|
133
|
+
) : (
|
|
134
|
+
<ThemedSplitView
|
|
135
|
+
oldValue={oldValue}
|
|
136
|
+
newValue={newValue}
|
|
137
|
+
differences={EMPTY}
|
|
138
|
+
theme={devToolsDefaultTheme}
|
|
139
|
+
scrollEnabled={false}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
</ScrollView>
|
|
143
|
+
</View>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const EMPTY: unknown[] = [];
|
|
148
|
+
const HIT = { top: 10, bottom: 10, left: 10, right: 10 };
|
|
149
|
+
|
|
150
|
+
const styles = StyleSheet.create({
|
|
151
|
+
container: { flex: 1 },
|
|
152
|
+
screenHeader: {
|
|
153
|
+
flexDirection: "row",
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
paddingHorizontal: 12,
|
|
156
|
+
paddingVertical: 8,
|
|
157
|
+
gap: 10,
|
|
158
|
+
},
|
|
159
|
+
backText: { color: buoyColors.primary, fontSize: 14 },
|
|
160
|
+
titleBlock: { flex: 1, minWidth: 0 },
|
|
161
|
+
title: { color: buoyColors.text, fontSize: 13, fontWeight: "700" },
|
|
162
|
+
subtitle: { color: buoyColors.textMuted, fontSize: 10, marginTop: 2 },
|
|
163
|
+
reason: {
|
|
164
|
+
color: buoyColors.warning,
|
|
165
|
+
fontSize: 11,
|
|
166
|
+
paddingHorizontal: 12,
|
|
167
|
+
paddingBottom: 6,
|
|
168
|
+
},
|
|
169
|
+
tabs: { paddingHorizontal: 12, paddingBottom: 8 },
|
|
170
|
+
scroll: { flex: 1 },
|
|
171
|
+
scrollContent: { paddingHorizontal: 12, paddingBottom: 24 },
|
|
172
|
+
});
|