@buoy-gg/time-machine 7.0.29 → 7.0.31
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/package.json +10 -11
- package/src/components/HeaderReloadButton.tsx +0 -45
- package/src/components/TimeMachineBody.tsx +0 -246
- package/src/components/TimeMachineModal.tsx +0 -75
- package/src/components/confirm.ts +0 -35
- package/src/components/format.ts +0 -17
- package/src/components/list/CaptureBar.tsx +0 -100
- package/src/components/list/RestoreOutcomeCard.tsx +0 -181
- package/src/components/list/SnapshotRow.tsx +0 -164
- package/src/components/list/SourcesStrip.tsx +0 -127
- package/src/components/list/UndoDeleteBar.tsx +0 -76
- package/src/components/list/WipeAllRow.tsx +0 -42
- package/src/components/preview/ItemDiffPanel.tsx +0 -83
- package/src/components/preview/PreviewFlow.tsx +0 -70
- package/src/components/preview/RestorePointNameRow.tsx +0 -80
- package/src/components/preview/RestorePreviewScreen.tsx +0 -1149
- package/src/components/preview/verdictMeta.ts +0 -40
- package/src/components/sources.ts +0 -92
- package/src/hooks/useTimeMachine.ts +0 -26
- package/src/index.tsx +0 -68
- package/src/preset.tsx +0 -47
- package/src/store/routeRestore.ts +0 -194
- package/src/store/snapshotVault.ts +0 -274
- package/src/store/timeMachineStore.ts +0 -881
- package/src/sync/timeMachineSyncAdapter.ts +0 -241
- package/src/ui/SwipeActionsRow.tsx +0 -199
- package/src/ui/primitives.tsx +0 -174
- package/src/ui/tmTheme.ts +0 -109
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Snapshot persistence for Time Machine.
|
|
3
|
-
*
|
|
4
|
-
* Snapshots live ON-DEVICE in persistentStorage (FileSystem → AsyncStorage →
|
|
5
|
-
* localStorage → Memory fallback): no size caps, survives restarts, and never
|
|
6
|
-
* crosses the 2MB external-sync snapshot budget — only metadata does.
|
|
7
|
-
*
|
|
8
|
-
* Layout: ONE storage key per snapshot (persistentStorage's FileSystem
|
|
9
|
-
* backend writes one file per key, and its multiGet is sequential — a single
|
|
10
|
-
* big key beats many small ones), plus one index key holding the metadata
|
|
11
|
-
* list for cheap listing.
|
|
12
|
-
*
|
|
13
|
-
* Keys use the `@react_buoy` family so every tool's isDevToolsStorageKey
|
|
14
|
-
* filter (including our own storage provider) excludes the vault itself from
|
|
15
|
-
* snapshots — a snapshot must never contain snapshots.
|
|
16
|
-
*/
|
|
17
|
-
import {
|
|
18
|
-
persistentStorage,
|
|
19
|
-
type SuperJSONResult,
|
|
20
|
-
} from "@buoy-gg/shared-ui";
|
|
21
|
-
import type { SnapshotRoute } from "./routeRestore";
|
|
22
|
-
|
|
23
|
-
const INDEX_KEY = "@react_buoy_time_machine:index";
|
|
24
|
-
const SNAP_PREFIX = "@react_buoy_time_machine:snap:";
|
|
25
|
-
|
|
26
|
-
export interface SnapshotSourceMeta {
|
|
27
|
-
warnings: string[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface SnapshotMeta {
|
|
31
|
-
id: string;
|
|
32
|
-
name: string;
|
|
33
|
-
createdAt: number;
|
|
34
|
-
/** Serialized size of the whole stored snapshot. */
|
|
35
|
-
sizeBytes: number;
|
|
36
|
-
/**
|
|
37
|
-
* A baseline snapshot has NO captured data — restoring it wipes to
|
|
38
|
-
* fresh-install state (clear + reload).
|
|
39
|
-
*/
|
|
40
|
-
baseline?: boolean;
|
|
41
|
-
/**
|
|
42
|
-
* User-saved restore scope: compound "sourceId::itemKey" strings. When set,
|
|
43
|
-
* restoring this snapshot touches ONLY these items — "this restore point
|
|
44
|
-
* means just my cart". Absent = full restore.
|
|
45
|
-
*
|
|
46
|
-
* Set through the MCP/adapter `setScope` action. The tool's own UI writes
|
|
47
|
-
* `excludedKeys` instead — see there for why the two exist.
|
|
48
|
-
*/
|
|
49
|
-
scope?: string[];
|
|
50
|
-
/**
|
|
51
|
-
* Items the user switched OFF in the restore preview and saved: compound
|
|
52
|
-
* "sourceId::itemKey" strings that every later restore of this snapshot
|
|
53
|
-
* leaves untouched, including the one-tap RESTORE on the list.
|
|
54
|
-
*
|
|
55
|
-
* The inverse of `scope`, and the right default for "I unchecked one of
|
|
56
|
-
* these ten keys": naming what to SKIP lets everything else through, so a
|
|
57
|
-
* key that only starts differing tomorrow is still restored. An include
|
|
58
|
-
* list built from today's diff would silently freeze the restore point to
|
|
59
|
-
* whatever happened to differ when it was saved.
|
|
60
|
-
*/
|
|
61
|
-
excludedKeys?: string[];
|
|
62
|
-
/**
|
|
63
|
-
* The route the app was on when this snapshot was captured. Absent when the
|
|
64
|
-
* app has no expo-router (nothing to return to). A restore can navigate back
|
|
65
|
-
* to it — see routeRestore.ts.
|
|
66
|
-
*/
|
|
67
|
-
route?: SnapshotRoute;
|
|
68
|
-
/**
|
|
69
|
-
* Whether restoring this point should also navigate back to `route`.
|
|
70
|
-
* Undefined means "yes, if there is a route" — the useful default, and the
|
|
71
|
-
* reason there is no longer a prompt on every restore.
|
|
72
|
-
*/
|
|
73
|
-
restoreRoute?: boolean;
|
|
74
|
-
/** When this point was last restored, for the "restored 4m ago" marker. */
|
|
75
|
-
lastRestoredAt?: number;
|
|
76
|
-
/** Source ids captured, with their capture warnings. */
|
|
77
|
-
sources: Record<string, SnapshotSourceMeta>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface StoredSnapshot {
|
|
81
|
-
meta: SnapshotMeta;
|
|
82
|
-
/** Per-source superjson payloads (absent for baseline snapshots). */
|
|
83
|
-
data: Record<string, SuperJSONResult>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function loadIndex(): Promise<SnapshotMeta[]> {
|
|
87
|
-
try {
|
|
88
|
-
const raw = await persistentStorage.getItem(INDEX_KEY);
|
|
89
|
-
if (!raw) return [];
|
|
90
|
-
const parsed = JSON.parse(raw) as SnapshotMeta[];
|
|
91
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
92
|
-
} catch {
|
|
93
|
-
return [];
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function saveIndex(metas: SnapshotMeta[]): Promise<void> {
|
|
98
|
-
await persistentStorage.setItem(INDEX_KEY, JSON.stringify(metas));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Persists the snapshot, stamps meta.sizeBytes, and updates the index. */
|
|
102
|
-
export async function saveSnapshot(
|
|
103
|
-
snapshot: StoredSnapshot,
|
|
104
|
-
metas: SnapshotMeta[]
|
|
105
|
-
): Promise<SnapshotMeta[]> {
|
|
106
|
-
const serialized = JSON.stringify(snapshot);
|
|
107
|
-
snapshot.meta.sizeBytes = serialized.length;
|
|
108
|
-
await persistentStorage.setItem(
|
|
109
|
-
SNAP_PREFIX + snapshot.meta.id,
|
|
110
|
-
// Re-stringify so the persisted copy carries the final sizeBytes.
|
|
111
|
-
JSON.stringify(snapshot)
|
|
112
|
-
);
|
|
113
|
-
const next = [
|
|
114
|
-
snapshot.meta,
|
|
115
|
-
...metas.filter((m) => m.id !== snapshot.meta.id),
|
|
116
|
-
];
|
|
117
|
-
await saveIndex(next);
|
|
118
|
-
return next;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function loadSnapshot(id: string): Promise<StoredSnapshot | null> {
|
|
122
|
-
try {
|
|
123
|
-
const raw = await persistentStorage.getItem(SNAP_PREFIX + id);
|
|
124
|
-
return raw ? (JSON.parse(raw) as StoredSnapshot) : null;
|
|
125
|
-
} catch {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export async function deleteSnapshot(
|
|
131
|
-
id: string,
|
|
132
|
-
metas: SnapshotMeta[]
|
|
133
|
-
): Promise<SnapshotMeta[]> {
|
|
134
|
-
await persistentStorage.removeItem(SNAP_PREFIX + id);
|
|
135
|
-
const next = metas.filter((m) => m.id !== id);
|
|
136
|
-
await saveIndex(next);
|
|
137
|
-
return next;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Rewrite one snapshot's metadata in BOTH places it lives — the index and the
|
|
142
|
-
* snapshot's own record — so a reload can't resurrect the old value from
|
|
143
|
-
* whichever one was missed.
|
|
144
|
-
*/
|
|
145
|
-
async function patchMeta(
|
|
146
|
-
id: string,
|
|
147
|
-
patch: (meta: SnapshotMeta) => SnapshotMeta,
|
|
148
|
-
metas: SnapshotMeta[]
|
|
149
|
-
): Promise<SnapshotMeta[]> {
|
|
150
|
-
const apply = (m: SnapshotMeta): SnapshotMeta => (m.id === id ? patch(m) : m);
|
|
151
|
-
const next = metas.map(apply);
|
|
152
|
-
await persistentStorage.setItem(INDEX_KEY, JSON.stringify(next));
|
|
153
|
-
const stored = await loadSnapshot(id);
|
|
154
|
-
if (stored) {
|
|
155
|
-
stored.meta = apply(stored.meta);
|
|
156
|
-
await persistentStorage.setItem(SNAP_PREFIX + id, JSON.stringify(stored));
|
|
157
|
-
}
|
|
158
|
-
return next;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Set (or clear with null/empty) the "only these items" restore scope. */
|
|
162
|
-
export function setSnapshotScope(
|
|
163
|
-
id: string,
|
|
164
|
-
scope: string[] | null,
|
|
165
|
-
metas: SnapshotMeta[]
|
|
166
|
-
): Promise<SnapshotMeta[]> {
|
|
167
|
-
return patchMeta(
|
|
168
|
-
id,
|
|
169
|
-
(m) => {
|
|
170
|
-
const next = { ...m };
|
|
171
|
-
if (scope && scope.length > 0) next.scope = scope;
|
|
172
|
-
else delete next.scope;
|
|
173
|
-
return next;
|
|
174
|
-
},
|
|
175
|
-
metas
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Set (or clear with null/empty) the items a restore of this snapshot must
|
|
181
|
-
* leave alone. Saving exclusions also clears any legacy `scope`: the two
|
|
182
|
-
* answer the same question in opposite directions, and the one the user just
|
|
183
|
-
* chose in the preview wins.
|
|
184
|
-
*/
|
|
185
|
-
export function setSnapshotExclusions(
|
|
186
|
-
id: string,
|
|
187
|
-
excludedKeys: string[] | null,
|
|
188
|
-
metas: SnapshotMeta[]
|
|
189
|
-
): Promise<SnapshotMeta[]> {
|
|
190
|
-
return patchMeta(
|
|
191
|
-
id,
|
|
192
|
-
(m) => {
|
|
193
|
-
const next = { ...m };
|
|
194
|
-
delete next.scope;
|
|
195
|
-
if (excludedKeys && excludedKeys.length > 0)
|
|
196
|
-
next.excludedKeys = excludedKeys;
|
|
197
|
-
else delete next.excludedKeys;
|
|
198
|
-
return next;
|
|
199
|
-
},
|
|
200
|
-
metas
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** Turn this point's route restore on or off — the row's route toggle. */
|
|
205
|
-
export function setSnapshotRestoreRoute(
|
|
206
|
-
id: string,
|
|
207
|
-
restoreRoute: boolean,
|
|
208
|
-
metas: SnapshotMeta[]
|
|
209
|
-
): Promise<SnapshotMeta[]> {
|
|
210
|
-
return patchMeta(id, (m) => ({ ...m, restoreRoute }), metas);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/** Stamp a restore so the list can say which point you are currently in. */
|
|
214
|
-
export function markSnapshotRestored(
|
|
215
|
-
id: string,
|
|
216
|
-
at: number,
|
|
217
|
-
metas: SnapshotMeta[]
|
|
218
|
-
): Promise<SnapshotMeta[]> {
|
|
219
|
-
return patchMeta(id, (m) => ({ ...m, lastRestoredAt: at }), metas);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Copy a stored snapshot under a new id and name. The payload is copied by
|
|
224
|
-
* reference into a fresh record — the vault serializes it on write, so the
|
|
225
|
-
* two never share mutable state.
|
|
226
|
-
*/
|
|
227
|
-
export async function duplicateSnapshot(
|
|
228
|
-
id: string,
|
|
229
|
-
name: string,
|
|
230
|
-
metas: SnapshotMeta[]
|
|
231
|
-
): Promise<{ metas: SnapshotMeta[]; meta: SnapshotMeta } | null> {
|
|
232
|
-
const stored = await loadSnapshot(id);
|
|
233
|
-
if (!stored) return null;
|
|
234
|
-
const copy: StoredSnapshot = {
|
|
235
|
-
meta: {
|
|
236
|
-
...stored.meta,
|
|
237
|
-
id: `${stored.meta.id}_copy_${Date.now().toString(36)}`,
|
|
238
|
-
name,
|
|
239
|
-
createdAt: Date.now(),
|
|
240
|
-
lastRestoredAt: undefined,
|
|
241
|
-
},
|
|
242
|
-
data: stored.data,
|
|
243
|
-
};
|
|
244
|
-
delete copy.meta.lastRestoredAt;
|
|
245
|
-
return { metas: await saveSnapshot(copy, metas), meta: copy.meta };
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/** Read a whole record so a delete can be held in memory and undone. */
|
|
249
|
-
export async function readSnapshot(id: string): Promise<StoredSnapshot | null> {
|
|
250
|
-
return loadSnapshot(id);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/** Put a held record back exactly as it was. */
|
|
254
|
-
export async function restoreDeletedSnapshot(
|
|
255
|
-
stored: StoredSnapshot,
|
|
256
|
-
metas: SnapshotMeta[]
|
|
257
|
-
): Promise<SnapshotMeta[]> {
|
|
258
|
-
return saveSnapshot(stored, metas);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export async function renameSnapshot(
|
|
262
|
-
id: string,
|
|
263
|
-
name: string,
|
|
264
|
-
metas: SnapshotMeta[]
|
|
265
|
-
): Promise<SnapshotMeta[]> {
|
|
266
|
-
const next = metas.map((m) => (m.id === id ? { ...m, name } : m));
|
|
267
|
-
await saveIndex(next);
|
|
268
|
-
const stored = await loadSnapshot(id);
|
|
269
|
-
if (stored) {
|
|
270
|
-
stored.meta.name = name;
|
|
271
|
-
await persistentStorage.setItem(SNAP_PREFIX + id, JSON.stringify(stored));
|
|
272
|
-
}
|
|
273
|
-
return next;
|
|
274
|
-
}
|