@buoy-gg/time-machine 7.0.35 → 7.0.38
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/README.md +40 -35
- package/lib/commonjs/components/HeaderActions.js +91 -0
- package/lib/commonjs/components/HeaderReloadButton.js +22 -25
- package/lib/commonjs/components/TimeMachineBody.js +68 -13
- package/lib/commonjs/components/floating/NamePromptModal.js +193 -0
- package/lib/commonjs/components/floating/TimeMachineFloatingTool.js +715 -0
- package/lib/commonjs/components/floating/armedPoint.js +92 -0
- package/lib/commonjs/components/floating/barSettings.js +66 -0
- package/lib/commonjs/components/list/CaptureBar.js +2 -0
- package/lib/commonjs/components/list/RestoreOutcomeCard.js +2 -0
- package/lib/commonjs/components/list/SnapshotRow.js +37 -13
- package/lib/commonjs/components/list/SourcesStrip.js +127 -83
- package/lib/commonjs/components/list/UndoDeleteBar.js +3 -1
- package/lib/commonjs/components/preview/RestorePreviewScreen.js +7 -2
- package/lib/commonjs/components/sources.js +19 -8
- package/lib/commonjs/index.js +42 -0
- package/lib/commonjs/preset.js +6 -4
- package/lib/commonjs/store/sourceIds.js +9 -0
- package/lib/commonjs/store/sourceSettings.js +79 -0
- package/lib/commonjs/store/timeMachineStore.js +75 -10
- package/lib/commonjs/sync/timeMachineSyncAdapter.js +5 -1
- package/lib/commonjs/ui/SwipeActionsRow.js +14 -3
- package/lib/commonjs/ui/primitives.js +2 -0
- package/lib/commonjs/ui/tmTheme.js +18 -5
- package/lib/module/components/HeaderActions.js +85 -0
- package/lib/module/components/HeaderReloadButton.js +24 -27
- package/lib/module/components/TimeMachineBody.js +71 -16
- package/lib/module/components/floating/NamePromptModal.js +188 -0
- package/lib/module/components/floating/TimeMachineFloatingTool.js +708 -0
- package/lib/module/components/floating/armedPoint.js +82 -0
- package/lib/module/components/floating/barSettings.js +57 -0
- package/lib/module/components/list/CaptureBar.js +2 -0
- package/lib/module/components/list/RestoreOutcomeCard.js +2 -0
- package/lib/module/components/list/SnapshotRow.js +37 -13
- package/lib/module/components/list/SourcesStrip.js +130 -84
- package/lib/module/components/list/UndoDeleteBar.js +3 -1
- package/lib/module/components/preview/RestorePreviewScreen.js +7 -2
- package/lib/module/components/sources.js +13 -6
- package/lib/module/index.js +7 -1
- package/lib/module/preset.js +6 -4
- package/lib/module/store/sourceIds.js +5 -0
- package/lib/module/store/sourceSettings.js +69 -0
- package/lib/module/store/timeMachineStore.js +74 -9
- package/lib/module/sync/timeMachineSyncAdapter.js +5 -1
- package/lib/module/ui/SwipeActionsRow.js +14 -3
- package/lib/module/ui/primitives.js +2 -0
- package/lib/module/ui/tmTheme.js +18 -5
- package/lib/typescript/components/HeaderActions.d.ts +7 -0
- package/lib/typescript/components/floating/NamePromptModal.d.ts +9 -0
- package/lib/typescript/components/floating/TimeMachineFloatingTool.d.ts +6 -0
- package/lib/typescript/components/floating/armedPoint.d.ts +14 -0
- package/lib/typescript/components/floating/barSettings.d.ts +7 -0
- package/lib/typescript/components/list/SnapshotRow.d.ts +5 -1
- package/lib/typescript/components/list/SourcesStrip.d.ts +3 -1
- package/lib/typescript/components/sources.d.ts +7 -2
- package/lib/typescript/index.d.ts +3 -1
- package/lib/typescript/preset.d.ts +4 -2
- package/lib/typescript/store/snapshotVault.d.ts +6 -0
- package/lib/typescript/store/sourceIds.d.ts +3 -0
- package/lib/typescript/store/sourceSettings.d.ts +9 -0
- package/lib/typescript/store/timeMachineStore.d.ts +16 -1
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +4 -0
- package/lib/typescript/ui/tmTheme.d.ts +17 -4
- package/package.json +11 -8
- package/lib/commonjs/components/HeaderReloadButton.js.map +0 -1
- package/lib/commonjs/components/TimeMachineBody.js.map +0 -1
- package/lib/commonjs/components/TimeMachineModal.js.map +0 -1
- package/lib/commonjs/components/confirm.js.map +0 -1
- package/lib/commonjs/components/format.js.map +0 -1
- package/lib/commonjs/components/list/CaptureBar.js.map +0 -1
- package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +0 -1
- package/lib/commonjs/components/list/SnapshotRow.js.map +0 -1
- package/lib/commonjs/components/list/SourcesStrip.js.map +0 -1
- package/lib/commonjs/components/list/UndoDeleteBar.js.map +0 -1
- package/lib/commonjs/components/list/WipeAllRow.js.map +0 -1
- package/lib/commonjs/components/preview/ItemDiffPanel.js.map +0 -1
- package/lib/commonjs/components/preview/PreviewFlow.js.map +0 -1
- package/lib/commonjs/components/preview/RestorePointNameRow.js.map +0 -1
- package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +0 -1
- package/lib/commonjs/components/preview/verdictMeta.js.map +0 -1
- package/lib/commonjs/components/sources.js.map +0 -1
- package/lib/commonjs/hooks/useTimeMachine.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/store/routeRestore.js.map +0 -1
- package/lib/commonjs/store/snapshotVault.js.map +0 -1
- package/lib/commonjs/store/timeMachineStore.js.map +0 -1
- package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +0 -1
- package/lib/commonjs/ui/SwipeActionsRow.js.map +0 -1
- package/lib/commonjs/ui/primitives.js.map +0 -1
- package/lib/commonjs/ui/tmTheme.js.map +0 -1
- package/lib/module/components/HeaderReloadButton.js.map +0 -1
- package/lib/module/components/TimeMachineBody.js.map +0 -1
- package/lib/module/components/TimeMachineModal.js.map +0 -1
- package/lib/module/components/confirm.js.map +0 -1
- package/lib/module/components/format.js.map +0 -1
- package/lib/module/components/list/CaptureBar.js.map +0 -1
- package/lib/module/components/list/RestoreOutcomeCard.js.map +0 -1
- package/lib/module/components/list/SnapshotRow.js.map +0 -1
- package/lib/module/components/list/SourcesStrip.js.map +0 -1
- package/lib/module/components/list/UndoDeleteBar.js.map +0 -1
- package/lib/module/components/list/WipeAllRow.js.map +0 -1
- package/lib/module/components/preview/ItemDiffPanel.js.map +0 -1
- package/lib/module/components/preview/PreviewFlow.js.map +0 -1
- package/lib/module/components/preview/RestorePointNameRow.js.map +0 -1
- package/lib/module/components/preview/RestorePreviewScreen.js.map +0 -1
- package/lib/module/components/preview/verdictMeta.js.map +0 -1
- package/lib/module/components/sources.js.map +0 -1
- package/lib/module/hooks/useTimeMachine.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/store/routeRestore.js.map +0 -1
- package/lib/module/store/snapshotVault.js.map +0 -1
- package/lib/module/store/timeMachineStore.js.map +0 -1
- package/lib/module/sync/timeMachineSyncAdapter.js.map +0 -1
- package/lib/module/ui/SwipeActionsRow.js.map +0 -1
- package/lib/module/ui/primitives.js.map +0 -1
- package/lib/module/ui/tmTheme.js.map +0 -1
- package/lib/typescript/components/HeaderReloadButton.d.ts.map +0 -1
- package/lib/typescript/components/TimeMachineBody.d.ts.map +0 -1
- package/lib/typescript/components/TimeMachineModal.d.ts.map +0 -1
- package/lib/typescript/components/confirm.d.ts.map +0 -1
- package/lib/typescript/components/format.d.ts.map +0 -1
- package/lib/typescript/components/list/CaptureBar.d.ts.map +0 -1
- package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +0 -1
- package/lib/typescript/components/list/SnapshotRow.d.ts.map +0 -1
- package/lib/typescript/components/list/SourcesStrip.d.ts.map +0 -1
- package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +0 -1
- package/lib/typescript/components/list/WipeAllRow.d.ts.map +0 -1
- package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +0 -1
- package/lib/typescript/components/preview/PreviewFlow.d.ts.map +0 -1
- package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +0 -1
- package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +0 -1
- package/lib/typescript/components/preview/verdictMeta.d.ts.map +0 -1
- package/lib/typescript/components/sources.d.ts.map +0 -1
- package/lib/typescript/hooks/useTimeMachine.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/store/routeRestore.d.ts.map +0 -1
- package/lib/typescript/store/snapshotVault.d.ts.map +0 -1
- package/lib/typescript/store/timeMachineStore.d.ts.map +0 -1
- package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +0 -1
- package/lib/typescript/ui/SwipeActionsRow.d.ts.map +0 -1
- package/lib/typescript/ui/primitives.d.ts.map +0 -1
- package/lib/typescript/ui/tmTheme.d.ts.map +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which sources Time Machine is allowed to touch.
|
|
5
|
+
*
|
|
6
|
+
* A source switched off here is left out of every capture and every restore,
|
|
7
|
+
* from the tool, the bar, the desktop and MCP alike: "off" has to mean off
|
|
8
|
+
* everywhere, or the one path that forgets it is the one that breaks the app.
|
|
9
|
+
* The case this exists for is a store whose full replace the app cannot
|
|
10
|
+
* survive (a Zustand store holding live handles, say): leave it off and the
|
|
11
|
+
* rest of the state still round-trips.
|
|
12
|
+
*
|
|
13
|
+
* Persisted as the set of ids that are OFF, so a source that registers for
|
|
14
|
+
* the first time tomorrow starts on, and the default is "everything".
|
|
15
|
+
*/
|
|
16
|
+
import { definePersistedSetting, devToolsStorageKeys } from "@buoy-gg/shared-ui";
|
|
17
|
+
const setting = definePersistedSetting(devToolsStorageKeys.timeMachine.disabledSources(), [], {
|
|
18
|
+
sanitize: value => Array.isArray(value) ? value.filter(v => typeof v === "string") : []
|
|
19
|
+
});
|
|
20
|
+
let disabled = new Set();
|
|
21
|
+
let loaded = false;
|
|
22
|
+
let loadPromise = null;
|
|
23
|
+
const listeners = new Set();
|
|
24
|
+
const notify = () => listeners.forEach(listener => listener());
|
|
25
|
+
export function ensureSourceSettingsLoaded() {
|
|
26
|
+
if (loaded) return Promise.resolve();
|
|
27
|
+
if (!loadPromise) {
|
|
28
|
+
loadPromise = setting.load().then(ids => {
|
|
29
|
+
// A toggle made while the read was in flight wins over the disk copy.
|
|
30
|
+
if (disabled.size === 0) {
|
|
31
|
+
disabled = new Set(ids);
|
|
32
|
+
notify();
|
|
33
|
+
}
|
|
34
|
+
}).finally(() => {
|
|
35
|
+
loaded = true;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return loadPromise;
|
|
39
|
+
}
|
|
40
|
+
export function subscribeSourceSettings(listener) {
|
|
41
|
+
listeners.add(listener);
|
|
42
|
+
return () => {
|
|
43
|
+
listeners.delete(listener);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Stable across renders until something changes, for useSyncExternalStore. */
|
|
48
|
+
export function getDisabledSources() {
|
|
49
|
+
return disabled;
|
|
50
|
+
}
|
|
51
|
+
export function isSourceEnabled(id) {
|
|
52
|
+
return !disabled.has(id);
|
|
53
|
+
}
|
|
54
|
+
export function setSourceEnabled(id, enabled) {
|
|
55
|
+
if (enabled === !disabled.has(id)) return;
|
|
56
|
+
const next = new Set(disabled);
|
|
57
|
+
if (enabled) next.delete(id);else next.add(id);
|
|
58
|
+
disabled = next;
|
|
59
|
+
notify();
|
|
60
|
+
void setting.save([...next]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Test seam: forget everything without touching storage. */
|
|
64
|
+
export function resetSourceSettingsForTests() {
|
|
65
|
+
disabled = new Set();
|
|
66
|
+
loaded = false;
|
|
67
|
+
loadPromise = null;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=sourceSettings.js.map
|
|
@@ -20,6 +20,8 @@ import { getStateSnapshotProvider, getStateSnapshotProviders, scheduleReloadApp,
|
|
|
20
20
|
import { objectDiff } from "@buoy-gg/shared-ui/dataViewer";
|
|
21
21
|
import { deleteSnapshot, duplicateSnapshot, loadIndex, loadSnapshot, markSnapshotRestored, readSnapshot, renameSnapshot, restoreDeletedSnapshot, saveSnapshot, setSnapshotRestoreRoute, setSnapshotScope, setSnapshotExclusions } from "./snapshotVault";
|
|
22
22
|
import { captureCurrentRoute, navigateToRoute, savePendingRoute } from "./routeRestore";
|
|
23
|
+
import { ensureSourceSettingsLoaded, isSourceEnabled } from "./sourceSettings";
|
|
24
|
+
import { ROUTE_SOURCE_ID } from "./sourceIds";
|
|
23
25
|
export const RESTORE_ORDER = ["storage", "redux", "zustand", "jotai", "query"];
|
|
24
26
|
/**
|
|
25
27
|
* What to call a capture nobody named. The screen you were on plus the time
|
|
@@ -30,7 +32,22 @@ export const RESTORE_ORDER = ["storage", "redux", "zustand", "jotai", "query"];
|
|
|
30
32
|
function defaultSnapshotName(route) {
|
|
31
33
|
const now = new Date();
|
|
32
34
|
const clock = `${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
|
|
33
|
-
|
|
35
|
+
const screen = route ? route.pathname === "/" ? "Home" : route.pathname : null;
|
|
36
|
+
return screen ? `${screen} · ${clock}` : `Snapshot · ${clock}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Two captures in the same minute on the same screen would get the same
|
|
41
|
+
* default name and be told apart by nothing. Number the repeats.
|
|
42
|
+
*/
|
|
43
|
+
function dedupeName(name, existing) {
|
|
44
|
+
const taken = new Set(existing.map(m => m.name));
|
|
45
|
+
if (!taken.has(name)) return name;
|
|
46
|
+
for (let n = 2; n < 1000; n++) {
|
|
47
|
+
const candidate = `${name} (${n})`;
|
|
48
|
+
if (!taken.has(candidate)) return candidate;
|
|
49
|
+
}
|
|
50
|
+
return name;
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
/**
|
|
@@ -44,6 +61,8 @@ export function willRestoreRoute(meta) {
|
|
|
44
61
|
|
|
45
62
|
/** Extra knobs a restore accepts beyond mode + exclusions. */
|
|
46
63
|
|
|
64
|
+
export const SAFETY_SNAPSHOT_NAME = "Before last restore";
|
|
65
|
+
|
|
47
66
|
/** Provider capability row as mirrored to a remote dashboard. */
|
|
48
67
|
|
|
49
68
|
/** One side of a preview comparison. */
|
|
@@ -135,7 +154,7 @@ class TimeMachineStore {
|
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
/** Capture all capturable sources (or a subset) into a named snapshot. */
|
|
138
|
-
async capture(name, sourceIds) {
|
|
157
|
+
async capture(name, sourceIds, options) {
|
|
139
158
|
if (remoteHandler) {
|
|
140
159
|
const result = await remoteHandler("capture", {
|
|
141
160
|
name,
|
|
@@ -144,11 +163,12 @@ class TimeMachineStore {
|
|
|
144
163
|
return result.snapshot;
|
|
145
164
|
}
|
|
146
165
|
await this.ensureLoaded();
|
|
166
|
+
await ensureSourceSettingsLoaded();
|
|
147
167
|
this.setState({
|
|
148
168
|
busy: "capture"
|
|
149
169
|
});
|
|
150
170
|
try {
|
|
151
|
-
const providers = getStateSnapshotProviders().filter(p => p.status().canCapture && (!sourceIds || sourceIds.includes(p.id)));
|
|
171
|
+
const providers = getStateSnapshotProviders().filter(p => p.status().canCapture && isSourceEnabled(p.id) && (!sourceIds || sourceIds.includes(p.id)));
|
|
152
172
|
const data = {};
|
|
153
173
|
const sources = {};
|
|
154
174
|
for (const provider of providers) {
|
|
@@ -165,21 +185,31 @@ class TimeMachineStore {
|
|
|
165
185
|
}
|
|
166
186
|
}
|
|
167
187
|
// Where the app was when this state was true — restoring can return here.
|
|
168
|
-
const route = captureCurrentRoute();
|
|
188
|
+
const route = isSourceEnabled(ROUTE_SOURCE_ID) ? captureCurrentRoute() : null;
|
|
189
|
+
// The safety slot is rolling: the previous one goes before this one lands.
|
|
190
|
+
let current = this.state.snapshots;
|
|
191
|
+
if (options?.safety) {
|
|
192
|
+
for (const stale of current.filter(m => m.safety)) {
|
|
193
|
+
current = await deleteSnapshot(stale.id, current);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
169
196
|
const snapshot = {
|
|
170
197
|
meta: {
|
|
171
198
|
id: nextId(),
|
|
172
|
-
name: name.trim() || defaultSnapshotName(route),
|
|
199
|
+
name: options?.safety ? SAFETY_SNAPSHOT_NAME : dedupeName(name.trim() || defaultSnapshotName(route), current),
|
|
173
200
|
createdAt: Date.now(),
|
|
174
201
|
sizeBytes: 0,
|
|
175
202
|
...(route ? {
|
|
176
203
|
route
|
|
177
204
|
} : {}),
|
|
205
|
+
...(options?.safety ? {
|
|
206
|
+
safety: true
|
|
207
|
+
} : {}),
|
|
178
208
|
sources
|
|
179
209
|
},
|
|
180
210
|
data
|
|
181
211
|
};
|
|
182
|
-
const snapshots = await saveSnapshot(snapshot,
|
|
212
|
+
const snapshots = await saveSnapshot(snapshot, current);
|
|
183
213
|
this.setState({
|
|
184
214
|
snapshots
|
|
185
215
|
});
|
|
@@ -202,6 +232,7 @@ class TimeMachineStore {
|
|
|
202
232
|
return result.snapshot;
|
|
203
233
|
}
|
|
204
234
|
await this.ensureLoaded();
|
|
235
|
+
await ensureSourceSettingsLoaded();
|
|
205
236
|
const snapshot = {
|
|
206
237
|
meta: {
|
|
207
238
|
id: nextId(),
|
|
@@ -239,6 +270,16 @@ class TimeMachineStore {
|
|
|
239
270
|
return outcome;
|
|
240
271
|
}
|
|
241
272
|
await this.ensureLoaded();
|
|
273
|
+
await ensureSourceSettingsLoaded();
|
|
274
|
+
// The way back from a mis-tap: capture what is about to be replaced.
|
|
275
|
+
// Restoring the safety slot itself never rolls it, or Undo would eat
|
|
276
|
+
// its own target.
|
|
277
|
+
let safetySnapshotId;
|
|
278
|
+
if (options?.safetyCapture && !this.state.snapshots.find(m => m.id === id)?.safety) {
|
|
279
|
+
safetySnapshotId = (await this.capture(SAFETY_SNAPSHOT_NAME, undefined, {
|
|
280
|
+
safety: true
|
|
281
|
+
})).id;
|
|
282
|
+
}
|
|
242
283
|
this.setState({
|
|
243
284
|
busy: "restore"
|
|
244
285
|
});
|
|
@@ -251,7 +292,10 @@ class TimeMachineStore {
|
|
|
251
292
|
mode,
|
|
252
293
|
at: Date.now(),
|
|
253
294
|
willReload: false,
|
|
254
|
-
results: {}
|
|
295
|
+
results: {},
|
|
296
|
+
...(safetySnapshotId ? {
|
|
297
|
+
safetySnapshotId
|
|
298
|
+
} : {})
|
|
255
299
|
};
|
|
256
300
|
if (stored.meta.baseline) {
|
|
257
301
|
// Fresh install: wipe every clearable source, then reload so all
|
|
@@ -299,10 +343,10 @@ class TimeMachineStore {
|
|
|
299
343
|
// No prompt any more: the point carries its own answer, and having a
|
|
300
344
|
// route means wanting it back unless you said otherwise. An explicit
|
|
301
345
|
// option still wins, so MCP and the desktop can force either way.
|
|
302
|
-
const wantsRoute = options?.restoreRoute ?? willRestoreRoute(stored.meta);
|
|
346
|
+
const wantsRoute = isSourceEnabled(ROUTE_SOURCE_ID) && (options?.restoreRoute ?? willRestoreRoute(stored.meta));
|
|
303
347
|
if (mode === "reload") {
|
|
304
348
|
// Storage only; everything in-memory rebuilds after the reload.
|
|
305
|
-
if (stored.data.storage && (!includeBySource || includeBySource.has("storage"))) {
|
|
349
|
+
if (stored.data.storage && isSourceEnabled("storage") && (!includeBySource || includeBySource.has("storage"))) {
|
|
306
350
|
outcome.results.storage = await this.restoreSource("storage", stored.data.storage, excludeBySource.get("storage"), includeBySource?.get("storage"));
|
|
307
351
|
}
|
|
308
352
|
// Hand the route to the NEXT boot — this realm is about to die, so the
|
|
@@ -334,6 +378,17 @@ class TimeMachineStore {
|
|
|
334
378
|
for (const sid of sourceIds) {
|
|
335
379
|
// Sources entirely outside the scope are untouched and unreported.
|
|
336
380
|
if (includeBySource && !includeBySource.has(sid)) continue;
|
|
381
|
+
// A source switched off in Sources is left exactly as it is. Reported
|
|
382
|
+
// as a warning, not a failure: this is what you asked for.
|
|
383
|
+
if (!isSourceEnabled(sid)) {
|
|
384
|
+
outcome.results[sid] = {
|
|
385
|
+
ok: true,
|
|
386
|
+
applied: 0,
|
|
387
|
+
skipped: [],
|
|
388
|
+
warnings: ["Switched off in Sources; left as it is"]
|
|
389
|
+
};
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
337
392
|
outcome.results[sid] = await this.restoreSource(sid, stored.data[sid], excludeBySource.get(sid), includeBySource?.get(sid));
|
|
338
393
|
}
|
|
339
394
|
// Navigate LAST — the screen we land on should read the restored state.
|
|
@@ -400,6 +455,7 @@ class TimeMachineStore {
|
|
|
400
455
|
return;
|
|
401
456
|
}
|
|
402
457
|
await this.ensureLoaded();
|
|
458
|
+
await ensureSourceSettingsLoaded();
|
|
403
459
|
const stored = await readSnapshot(id);
|
|
404
460
|
const snapshots = await deleteSnapshot(id, this.state.snapshots);
|
|
405
461
|
this.setState({
|
|
@@ -439,6 +495,7 @@ class TimeMachineStore {
|
|
|
439
495
|
return;
|
|
440
496
|
}
|
|
441
497
|
await this.ensureLoaded();
|
|
498
|
+
await ensureSourceSettingsLoaded();
|
|
442
499
|
const snapshots = await setSnapshotRestoreRoute(id, restoreRoute, this.state.snapshots);
|
|
443
500
|
this.setState({
|
|
444
501
|
snapshots
|
|
@@ -454,6 +511,7 @@ class TimeMachineStore {
|
|
|
454
511
|
return result?.snapshot ?? null;
|
|
455
512
|
}
|
|
456
513
|
await this.ensureLoaded();
|
|
514
|
+
await ensureSourceSettingsLoaded();
|
|
457
515
|
const source = this.state.snapshots.find(s => s.id === id);
|
|
458
516
|
if (!source) return null;
|
|
459
517
|
const result = await duplicateSnapshot(id, `${source.name} copy`, this.state.snapshots);
|
|
@@ -472,6 +530,7 @@ class TimeMachineStore {
|
|
|
472
530
|
return;
|
|
473
531
|
}
|
|
474
532
|
await this.ensureLoaded();
|
|
533
|
+
await ensureSourceSettingsLoaded();
|
|
475
534
|
const snapshots = await renameSnapshot(id, name, this.state.snapshots);
|
|
476
535
|
this.setState({
|
|
477
536
|
snapshots
|
|
@@ -492,6 +551,7 @@ class TimeMachineStore {
|
|
|
492
551
|
return;
|
|
493
552
|
}
|
|
494
553
|
await this.ensureLoaded();
|
|
554
|
+
await ensureSourceSettingsLoaded();
|
|
495
555
|
const snapshots = await setSnapshotScope(id, scope, this.state.snapshots);
|
|
496
556
|
this.setState({
|
|
497
557
|
snapshots
|
|
@@ -512,6 +572,7 @@ class TimeMachineStore {
|
|
|
512
572
|
return;
|
|
513
573
|
}
|
|
514
574
|
await this.ensureLoaded();
|
|
575
|
+
await ensureSourceSettingsLoaded();
|
|
515
576
|
const snapshots = await setSnapshotExclusions(id, excludedKeys, this.state.snapshots);
|
|
516
577
|
this.setState({
|
|
517
578
|
snapshots
|
|
@@ -629,6 +690,7 @@ class TimeMachineStore {
|
|
|
629
690
|
return;
|
|
630
691
|
}
|
|
631
692
|
await this.ensureLoaded();
|
|
693
|
+
await ensureSourceSettingsLoaded();
|
|
632
694
|
const runId = ++this.previewRunId;
|
|
633
695
|
this.setState({
|
|
634
696
|
preview: {
|
|
@@ -660,6 +722,9 @@ class TimeMachineStore {
|
|
|
660
722
|
const sourceIds = isBaseline ? getStateSnapshotProviders().filter(p => p.clear).map(p => p.id) : [...RESTORE_ORDER.filter(sid => sid in rightData), ...Object.keys(rightData).filter(sid => !RESTORE_ORDER.includes(sid))];
|
|
661
723
|
const sources = [];
|
|
662
724
|
for (const sid of sourceIds) {
|
|
725
|
+
// Off in Sources: a restore will not touch it, so there is nothing
|
|
726
|
+
// to preview.
|
|
727
|
+
if (!isSourceEnabled(sid)) continue;
|
|
663
728
|
// Yield a frame between sources — verdict passes are bounded but a
|
|
664
729
|
// five-source build shouldn't run as one synchronous block.
|
|
665
730
|
await new Promise(r => setTimeout(r, 0));
|
|
@@ -18,6 +18,8 @@ import { jsonLikelyLargerThan } from "@buoy-gg/shared-ui/dataViewer";
|
|
|
18
18
|
import { timeMachineStore } from "../store/timeMachineStore";
|
|
19
19
|
import { loadSnapshot } from "../store/snapshotVault";
|
|
20
20
|
import { canNavigate, currentRoute } from "../store/routeRestore";
|
|
21
|
+
import { isSourceEnabled } from "../store/sourceSettings";
|
|
22
|
+
import { ROUTE_SOURCE_ID } from "../store/sourceIds";
|
|
21
23
|
function providerStatuses() {
|
|
22
24
|
return getStateSnapshotProviders().map(p => {
|
|
23
25
|
const s = p.status();
|
|
@@ -26,7 +28,8 @@ function providerStatuses() {
|
|
|
26
28
|
label: p.label,
|
|
27
29
|
canCapture: s.canCapture,
|
|
28
30
|
canRestore: s.canRestore,
|
|
29
|
-
detail: s.detail
|
|
31
|
+
detail: s.detail,
|
|
32
|
+
enabled: isSourceEnabled(p.id)
|
|
30
33
|
};
|
|
31
34
|
});
|
|
32
35
|
}
|
|
@@ -46,6 +49,7 @@ function metadata() {
|
|
|
46
49
|
// a dashboard has no router of its own to probe, so it's mirrored here.
|
|
47
50
|
route: {
|
|
48
51
|
available: canNavigate(),
|
|
52
|
+
enabled: isSourceEnabled(ROUTE_SOURCE_ID),
|
|
49
53
|
...(current ? {
|
|
50
54
|
current: current.href
|
|
51
55
|
} : {})
|
|
@@ -91,11 +91,20 @@ export function SwipeActionsRow({
|
|
|
91
91
|
useEffect(() => () => {
|
|
92
92
|
if (closeOpenRow === close) closeOpenRow = null;
|
|
93
93
|
}, [close]);
|
|
94
|
+
|
|
95
|
+
// The row is glass now, so the actions can't hide under it: they fade in
|
|
96
|
+
// with the first points of the drag instead of sitting there at rest.
|
|
97
|
+
const actionOpacity = translateX.interpolate({
|
|
98
|
+
inputRange: [-24, 0],
|
|
99
|
+
outputRange: [1, 0],
|
|
100
|
+
extrapolate: "clamp"
|
|
101
|
+
});
|
|
94
102
|
return /*#__PURE__*/_jsxs(View, {
|
|
95
103
|
style: styles.clip,
|
|
96
|
-
children: [/*#__PURE__*/_jsx(View, {
|
|
104
|
+
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
97
105
|
style: [styles.actionLayer, {
|
|
98
|
-
width: openWidth
|
|
106
|
+
width: openWidth,
|
|
107
|
+
opacity: actionOpacity
|
|
99
108
|
}],
|
|
100
109
|
children: actions.map(action => /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
101
110
|
style: [styles.action, action.background ? {
|
|
@@ -154,7 +163,9 @@ const styles = StyleSheet.create({
|
|
|
154
163
|
fontWeight: "600",
|
|
155
164
|
letterSpacing: tm.tracking.control
|
|
156
165
|
},
|
|
157
|
-
//
|
|
166
|
+
// Glass, like the card it sits in. The actions underneath are faded out
|
|
167
|
+
// at rest (see actionOpacity), which is what keeps them from bleeding
|
|
168
|
+
// through a translucent row.
|
|
158
169
|
content: {
|
|
159
170
|
backgroundColor: tm.color.group
|
|
160
171
|
}
|
package/lib/module/ui/tmTheme.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Time Machine design tokens — the "modern" surface language.
|
|
5
5
|
*
|
|
6
6
|
* A grouped-inset list system (iOS Settings shape): one dark sheet, rounded
|
|
7
|
-
*
|
|
7
|
+
* glass cards holding hairline-separated rows, uppercase section labels
|
|
8
8
|
* outside the cards, and explanatory footnotes under them.
|
|
9
9
|
*
|
|
10
10
|
* COLOR COMES FROM the shared `night` palette (ui/night/nightTheme.ts) — not
|
|
@@ -35,15 +35,28 @@ export const tm = {
|
|
|
35
35
|
* without the list reading through the CTA.
|
|
36
36
|
*/
|
|
37
37
|
sheet: "rgba(5, 5, 5, 0.92)",
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* A grouped card: night glass, so the star field behind the sheet shows
|
|
40
|
+
* through every card the way it does through the header chips. Cards
|
|
41
|
+
* take `groupBorder` as a hairline, or the glass reads as a smudge.
|
|
42
|
+
*/
|
|
43
|
+
group: night.color.glass,
|
|
44
|
+
groupBorder: night.color.glassBorder,
|
|
40
45
|
/** Row press / hover wash on top of a group. */
|
|
41
|
-
rowPressed: night.color.
|
|
46
|
+
rowPressed: night.color.glassElevated,
|
|
42
47
|
/** Hairline between rows, inset from the left like iOS. */
|
|
43
48
|
separator: night.color.separator,
|
|
44
49
|
/** Neutral control fill (icon discs, chips, round buttons). */
|
|
45
|
-
fill: night.color.
|
|
50
|
+
fill: night.color.glassElevated,
|
|
46
51
|
fillStrong: night.color.fillTertiary,
|
|
52
|
+
/** Opaque ring around a badge on a glass card (the sheet's own color). */
|
|
53
|
+
ring: night.color.bg,
|
|
54
|
+
/**
|
|
55
|
+
* Opaque disc behind a Buoy icon. The icons paint their knock-outs in
|
|
56
|
+
* the color they are handed, so a translucent fill turns a floppy disk
|
|
57
|
+
* into a yellow square. Stays solid on purpose.
|
|
58
|
+
*/
|
|
59
|
+
disc: night.color.surfaceElevated,
|
|
47
60
|
label: night.color.text,
|
|
48
61
|
labelSecondary: night.color.textSecondary,
|
|
49
62
|
labelTertiary: night.color.textTertiary,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function HeaderBarButton({ armId }: {
|
|
2
|
+
armId?: string;
|
|
3
|
+
}): import("react").JSX.Element;
|
|
4
|
+
export declare function TimeMachineHeaderActions({ armId }: {
|
|
5
|
+
armId?: string;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=HeaderActions.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function NamePromptModal({ visible, summary, onCapture, onCancel, }: {
|
|
2
|
+
visible: boolean;
|
|
3
|
+
/** What the capture will take, e.g. "5 sources · /shop/bag". */
|
|
4
|
+
summary?: string;
|
|
5
|
+
/** Called with the trimmed name; empty means "use the default". */
|
|
6
|
+
onCapture: (name: string) => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=NamePromptModal.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type FloatingToolRenderContext } from "@buoy-gg/shared-ui";
|
|
2
|
+
export declare const TIME_MACHINE_FLOATING_TOOL_ID = "time-machine-restore";
|
|
3
|
+
/** Open (or reveal) the restore bar, optionally arming a specific point. */
|
|
4
|
+
export declare function openTimeMachineFloatingTool(armId?: string): void;
|
|
5
|
+
export declare function TimeMachineFloatingTool({ id, revealToken, dismiss, }: FloatingToolRenderContext): import("react").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=TimeMachineFloatingTool.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SnapshotMeta } from "../../store/snapshotVault";
|
|
2
|
+
export declare function getPickedPointId(): string | null;
|
|
3
|
+
export declare function subscribePickedPoint(listener: () => void): () => void;
|
|
4
|
+
/** Read the saved pick once. Later calls return the same promise. */
|
|
5
|
+
export declare function ensurePickedPointLoaded(): Promise<void>;
|
|
6
|
+
export declare function setPickedPointId(id: string | null): void;
|
|
7
|
+
/** Test seam: forget the pick and the load, without touching storage. */
|
|
8
|
+
export declare function resetPickedPointForTests(): void;
|
|
9
|
+
/**
|
|
10
|
+
* The point the bar restores: the saved pick if it still exists, else the
|
|
11
|
+
* most recently restored point, else the newest capture. Never a baseline.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveArmedPoint(snapshots: readonly SnapshotMeta[], pickedId: string | null): SnapshotMeta | null;
|
|
14
|
+
//# sourceMappingURL=armedPoint.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function getBarReloadMode(): boolean;
|
|
2
|
+
export declare function subscribeBarSettings(listener: () => void): () => void;
|
|
3
|
+
export declare function ensureBarSettingsLoaded(): Promise<void>;
|
|
4
|
+
export declare function setBarReloadMode(on: boolean): void;
|
|
5
|
+
/** Test seam. */
|
|
6
|
+
export declare function resetBarSettingsForTests(): void;
|
|
7
|
+
//# sourceMappingURL=barSettings.d.ts.map
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { SnapshotMeta } from "../../store/snapshotVault";
|
|
2
|
-
export declare function SnapshotRow({ meta, busy, onOpen, onRestore, onDelete, onDuplicate, }: {
|
|
2
|
+
export declare function SnapshotRow({ meta, busy, armed, onOpen, onRestore, onDelete, onDuplicate, onArm, }: {
|
|
3
3
|
meta: SnapshotMeta;
|
|
4
4
|
busy: boolean;
|
|
5
|
+
/** This point is the one the floating restore bar restores right now. */
|
|
6
|
+
armed?: boolean;
|
|
5
7
|
/** Zero-arg: a data-taking `onPress` prop is invoked with the event by
|
|
6
8
|
* Buoy MCP's tap_element, which crashes the row. */
|
|
7
9
|
onOpen: () => void;
|
|
8
10
|
onRestore: () => void;
|
|
9
11
|
onDelete: () => void;
|
|
10
12
|
onDuplicate: () => void;
|
|
13
|
+
/** Put this point on the floating restore bar. */
|
|
14
|
+
onArm: () => void;
|
|
11
15
|
}): import("react").JSX.Element;
|
|
12
16
|
//# sourceMappingURL=SnapshotRow.d.ts.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type SourceStatus } from "../sources";
|
|
2
|
-
export declare function SourcesStrip({ sources }: {
|
|
2
|
+
export declare function SourcesStrip({ sources, onToggle, }: {
|
|
3
3
|
sources: SourceStatus[];
|
|
4
|
+
/** Flip a source on or off. Absent on the desktop mirror. */
|
|
5
|
+
onToggle?: (id: string, enabled: boolean) => void;
|
|
4
6
|
}): import("react").JSX.Element;
|
|
5
7
|
//# sourceMappingURL=SourcesStrip.d.ts.map
|
|
@@ -8,11 +8,18 @@
|
|
|
8
8
|
* same question as "will my Redux store come back".
|
|
9
9
|
*/
|
|
10
10
|
import type { ComponentType } from "react";
|
|
11
|
+
import { ROUTE_SOURCE_ID } from "../store/sourceIds";
|
|
12
|
+
export { ROUTE_SOURCE_ID };
|
|
11
13
|
export interface SourceStatus {
|
|
12
14
|
id: string;
|
|
13
15
|
label: string;
|
|
14
16
|
canCapture: boolean;
|
|
15
17
|
canRestore: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Switched on in the Sources strip. Off means every capture and restore
|
|
20
|
+
* leaves this source alone, whatever else it could do.
|
|
21
|
+
*/
|
|
22
|
+
enabled: boolean;
|
|
16
23
|
/** Why it is in this state — shown on the Sources screen. */
|
|
17
24
|
detail?: string;
|
|
18
25
|
}
|
|
@@ -28,8 +35,6 @@ export type SourceIcon = ComponentType<{
|
|
|
28
35
|
bgColor?: string;
|
|
29
36
|
}>;
|
|
30
37
|
export declare function sourceIcon(id: string): SourceIcon;
|
|
31
|
-
/** Id of the route tile — a device capability, not a snapshot provider. */
|
|
32
|
-
export declare const ROUTE_SOURCE_ID = "route";
|
|
33
38
|
export declare function useSourceStatuses(): SourceStatus[];
|
|
34
39
|
/** A source is "attached" when a snapshot would actually pick it up. */
|
|
35
40
|
export declare function isAttached(source: SourceStatus): boolean;
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export { timeMachineToolPreset, createTimeMachineTool } from "./preset";
|
|
13
13
|
export { TimeMachineModal, TimeMachineToolView, type TimeMachineModalProps, } from "./components/TimeMachineModal";
|
|
14
|
+
export { openTimeMachineFloatingTool, TIME_MACHINE_FLOATING_TOOL_ID, } from "./components/floating/TimeMachineFloatingTool";
|
|
14
15
|
export { timeMachineSyncAdapter } from "./sync/timeMachineSyncAdapter";
|
|
15
|
-
export { timeMachineStore, RESTORE_ORDER, type RestoreMode, type RestoreOptions, type RestoreOutcome, type TimeMachineState, type RemoteProviderStatus, type PreviewSource, } from "./store/timeMachineStore";
|
|
16
|
+
export { timeMachineStore, RESTORE_ORDER, SAFETY_SNAPSHOT_NAME, type RestoreMode, type RestoreOptions, type RestoreOutcome, type TimeMachineState, type RemoteProviderStatus, type PreviewSource, } from "./store/timeMachineStore";
|
|
16
17
|
export { armPendingRouteRestore, captureCurrentRoute, navigateToRoute, canNavigate as canRestoreRoute, type SnapshotRoute, type RouteRestoreResult, type RouteRestoreStatus, } from "./store/routeRestore";
|
|
18
|
+
export { isSourceEnabled, setSourceEnabled, subscribeSourceSettings, } from "./store/sourceSettings";
|
|
17
19
|
export { setRemoteTimeMachineHandler, replaceTimeMachineFromSnapshot, } from "./store/timeMachineStore";
|
|
18
20
|
export type { SnapshotMeta, SnapshotSourceMeta, StoredSnapshot, } from "./store/snapshotVault";
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -14,9 +14,10 @@ export declare const timeMachineToolPreset: {
|
|
|
14
14
|
name: string;
|
|
15
15
|
description: string;
|
|
16
16
|
slot: "both";
|
|
17
|
-
icon: ({ size, color }: {
|
|
17
|
+
icon: ({ size, color, slot }: {
|
|
18
18
|
size: number;
|
|
19
19
|
color?: string;
|
|
20
|
+
slot?: string;
|
|
20
21
|
}) => import("react").JSX.Element;
|
|
21
22
|
component: typeof TimeMachineBody;
|
|
22
23
|
modal: {
|
|
@@ -35,9 +36,10 @@ export declare function createTimeMachineTool(options?: {
|
|
|
35
36
|
description: string;
|
|
36
37
|
id: string;
|
|
37
38
|
slot: "both";
|
|
38
|
-
icon: ({ size, color }: {
|
|
39
|
+
icon: ({ size, color, slot }: {
|
|
39
40
|
size: number;
|
|
40
41
|
color?: string;
|
|
42
|
+
slot?: string;
|
|
41
43
|
}) => import("react").JSX.Element;
|
|
42
44
|
component: typeof TimeMachineBody;
|
|
43
45
|
modal: {
|
|
@@ -65,6 +65,12 @@ export interface SnapshotMeta {
|
|
|
65
65
|
restoreRoute?: boolean;
|
|
66
66
|
/** When this point was last restored, for the "restored 4m ago" marker. */
|
|
67
67
|
lastRestoredAt?: number;
|
|
68
|
+
/**
|
|
69
|
+
* The rolling "Before last restore" slot the restore bar writes before it
|
|
70
|
+
* restores, so a mis-tap has a way back. One exists at a time, it never
|
|
71
|
+
* counts against the tier cap, and the bar never arms it.
|
|
72
|
+
*/
|
|
73
|
+
safety?: boolean;
|
|
68
74
|
/** Source ids captured, with their capture warnings. */
|
|
69
75
|
sources: Record<string, SnapshotSourceMeta>;
|
|
70
76
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ensureSourceSettingsLoaded(): Promise<void>;
|
|
2
|
+
export declare function subscribeSourceSettings(listener: () => void): () => void;
|
|
3
|
+
/** Stable across renders until something changes, for useSyncExternalStore. */
|
|
4
|
+
export declare function getDisabledSources(): ReadonlySet<string>;
|
|
5
|
+
export declare function isSourceEnabled(id: string): boolean;
|
|
6
|
+
export declare function setSourceEnabled(id: string, enabled: boolean): void;
|
|
7
|
+
/** Test seam: forget everything without touching storage. */
|
|
8
|
+
export declare function resetSourceSettingsForTests(): void;
|
|
9
|
+
//# sourceMappingURL=sourceSettings.d.ts.map
|
|
@@ -32,6 +32,8 @@ export interface RestoreOutcome {
|
|
|
32
32
|
* "deferred" means the navigation is queued for the post-reload boot.
|
|
33
33
|
*/
|
|
34
34
|
route?: RouteRestoreResult;
|
|
35
|
+
/** Set when the restore wrote a safety snapshot first (see RestoreOptions). */
|
|
36
|
+
safetySnapshotId?: string;
|
|
35
37
|
}
|
|
36
38
|
/**
|
|
37
39
|
* Will restoring this point also navigate back to its route? Having a route
|
|
@@ -47,13 +49,22 @@ export interface RestoreOptions {
|
|
|
47
49
|
* app's own start route).
|
|
48
50
|
*/
|
|
49
51
|
restoreRoute?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Capture the app's current state into the rolling "Before last restore"
|
|
54
|
+
* slot first, so this restore can be undone. The outcome carries its id as
|
|
55
|
+
* `safetySnapshotId`. Local only: the desktop mirror has no bar.
|
|
56
|
+
*/
|
|
57
|
+
safetyCapture?: boolean;
|
|
50
58
|
}
|
|
59
|
+
export declare const SAFETY_SNAPSHOT_NAME = "Before last restore";
|
|
51
60
|
/** Provider capability row as mirrored to a remote dashboard. */
|
|
52
61
|
export interface RemoteProviderStatus {
|
|
53
62
|
id: string;
|
|
54
63
|
label: string;
|
|
55
64
|
canCapture: boolean;
|
|
56
65
|
canRestore: boolean;
|
|
66
|
+
/** False when the device has this source switched off in Sources. */
|
|
67
|
+
enabled?: boolean;
|
|
57
68
|
detail?: string;
|
|
58
69
|
}
|
|
59
70
|
/** One side of a preview comparison. */
|
|
@@ -104,6 +115,8 @@ export interface TimeMachineState {
|
|
|
104
115
|
/** Whether this device can capture and return to routes, and where it is now. */
|
|
105
116
|
export interface RouteToolStatus {
|
|
106
117
|
available: boolean;
|
|
118
|
+
/** False when the device has the route source switched off in Sources. */
|
|
119
|
+
enabled?: boolean;
|
|
107
120
|
/** The route the app is on right now, when one can be read. */
|
|
108
121
|
current?: string;
|
|
109
122
|
}
|
|
@@ -128,7 +141,9 @@ declare class TimeMachineStore {
|
|
|
128
141
|
route?: RouteToolStatus;
|
|
129
142
|
}): void;
|
|
130
143
|
/** Capture all capturable sources (or a subset) into a named snapshot. */
|
|
131
|
-
capture(name: string, sourceIds?: string[]
|
|
144
|
+
capture(name: string, sourceIds?: string[], options?: {
|
|
145
|
+
safety?: boolean;
|
|
146
|
+
}): Promise<SnapshotMeta>;
|
|
132
147
|
/**
|
|
133
148
|
* Capture the fresh-install baseline: an EMPTY snapshot whose restore
|
|
134
149
|
* wipes app state (clear + reload) — regardless of what state the app is
|