@buoy-gg/time-machine 7.0.17 → 7.0.19

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 (69) hide show
  1. package/lib/commonjs/components/HeaderReloadButton.js +61 -0
  2. package/lib/commonjs/components/HeaderReloadButton.js.map +1 -0
  3. package/lib/commonjs/components/TimeMachineBody.js +343 -127
  4. package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
  5. package/lib/commonjs/components/TimeMachineModal.js +1 -0
  6. package/lib/commonjs/components/TimeMachineModal.js.map +1 -1
  7. package/lib/commonjs/components/preview/ItemDetailScreen.js +189 -0
  8. package/lib/commonjs/components/preview/ItemDetailScreen.js.map +1 -0
  9. package/lib/commonjs/components/preview/RestorePreviewScreen.js +1056 -0
  10. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -0
  11. package/lib/commonjs/components/preview/verdictMeta.js +58 -0
  12. package/lib/commonjs/components/preview/verdictMeta.js.map +1 -0
  13. package/lib/commonjs/preset.js +11 -2
  14. package/lib/commonjs/preset.js.map +1 -1
  15. package/lib/commonjs/store/snapshotVault.js +19 -0
  16. package/lib/commonjs/store/snapshotVault.js.map +1 -1
  17. package/lib/commonjs/store/timeMachineStore.js +298 -7
  18. package/lib/commonjs/store/timeMachineStore.js.map +1 -1
  19. package/lib/commonjs/sync/timeMachineSyncAdapter.js +62 -2
  20. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +1 -1
  21. package/lib/module/components/HeaderReloadButton.js +56 -0
  22. package/lib/module/components/HeaderReloadButton.js.map +1 -0
  23. package/lib/module/components/TimeMachineBody.js +345 -129
  24. package/lib/module/components/TimeMachineBody.js.map +1 -1
  25. package/lib/module/components/TimeMachineModal.js +1 -0
  26. package/lib/module/components/TimeMachineModal.js.map +1 -1
  27. package/lib/module/components/preview/ItemDetailScreen.js +184 -0
  28. package/lib/module/components/preview/ItemDetailScreen.js.map +1 -0
  29. package/lib/module/components/preview/RestorePreviewScreen.js +1051 -0
  30. package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -0
  31. package/lib/module/components/preview/verdictMeta.js +53 -0
  32. package/lib/module/components/preview/verdictMeta.js.map +1 -0
  33. package/lib/module/preset.js +11 -2
  34. package/lib/module/preset.js.map +1 -1
  35. package/lib/module/store/snapshotVault.js +18 -0
  36. package/lib/module/store/snapshotVault.js.map +1 -1
  37. package/lib/module/store/timeMachineStore.js +299 -8
  38. package/lib/module/store/timeMachineStore.js.map +1 -1
  39. package/lib/module/sync/timeMachineSyncAdapter.js +62 -2
  40. package/lib/module/sync/timeMachineSyncAdapter.js.map +1 -1
  41. package/lib/typescript/components/HeaderReloadButton.d.ts +2 -0
  42. package/lib/typescript/components/HeaderReloadButton.d.ts.map +1 -0
  43. package/lib/typescript/components/TimeMachineBody.d.ts.map +1 -1
  44. package/lib/typescript/components/TimeMachineModal.d.ts.map +1 -1
  45. package/lib/typescript/components/preview/ItemDetailScreen.d.ts +11 -0
  46. package/lib/typescript/components/preview/ItemDetailScreen.d.ts.map +1 -0
  47. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts +15 -0
  48. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -0
  49. package/lib/typescript/components/preview/verdictMeta.d.ts +10 -0
  50. package/lib/typescript/components/preview/verdictMeta.d.ts.map +1 -0
  51. package/lib/typescript/preset.d.ts +15 -3
  52. package/lib/typescript/preset.d.ts.map +1 -1
  53. package/lib/typescript/store/snapshotVault.d.ts +7 -0
  54. package/lib/typescript/store/snapshotVault.d.ts.map +1 -1
  55. package/lib/typescript/store/timeMachineStore.d.ts +49 -2
  56. package/lib/typescript/store/timeMachineStore.d.ts.map +1 -1
  57. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +36 -0
  58. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +1 -1
  59. package/package.json +19 -7
  60. package/src/components/HeaderReloadButton.tsx +45 -0
  61. package/src/components/TimeMachineBody.tsx +416 -137
  62. package/src/components/TimeMachineModal.tsx +1 -0
  63. package/src/components/preview/ItemDetailScreen.tsx +172 -0
  64. package/src/components/preview/RestorePreviewScreen.tsx +1089 -0
  65. package/src/components/preview/verdictMeta.ts +40 -0
  66. package/src/preset.tsx +11 -2
  67. package/src/store/snapshotVault.ts +28 -0
  68. package/src/store/timeMachineStore.ts +330 -7
  69. package/src/sync/timeMachineSyncAdapter.ts +63 -3
@@ -1,18 +1,33 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Time Machine body — capture bar, provider status strip, snapshot list and
5
- * the last-restore results panel. Shared by the RN modal and the inline
6
- * (desktop) tool view.
4
+ * Time Machine body — snapshot list, capture bar, provider status strip and
5
+ * the Restore Preview flow (tap a snapshot row → item-level diff of what a
6
+ * restore would change; tap an item → tree/split detail).
7
+ *
8
+ * Shared by the RN modal and the inline (desktop) tool view. The host must
9
+ * NOT wrap this in a scroll view — each screen owns its scrolling (the
10
+ * preview is a virtualized FlatList; wrapping it would defeat that).
7
11
  */
8
12
  import { useState } from "react";
9
- import { Alert, Platform, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
10
- import { buoyColors } from "@buoy-gg/shared-ui";
13
+ import { Alert, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native";
14
+ import { Plus, EventPickerModal, Home, Trash2, buoyColors } from "@buoy-gg/shared-ui";
15
+ import { JotaiIcon, QueryIcon, ReduxIcon, StorageIcon, TimeMachineIcon, ZustandIcon } from "@buoy-gg/floating-tools-core";
16
+ import { useToolHost } from "@buoy-gg/core";
11
17
  import { useIsPro } from "@buoy-gg/license";
12
18
  import { useSnapshotProviders, useTimeMachineState } from "../hooks/useTimeMachine";
13
19
  import { timeMachineStore } from "../store/timeMachineStore";
20
+ import { RestorePreviewScreen } from "./preview/RestorePreviewScreen";
21
+ import { ItemDetailScreen } from "./preview/ItemDetailScreen";
14
22
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
15
23
  const FREE_SNAPSHOT_LIMIT = 2;
24
+ const SOURCE_ICONS = {
25
+ storage: StorageIcon,
26
+ redux: ReduxIcon,
27
+ zustand: ZustandIcon,
28
+ jotai: JotaiIcon,
29
+ query: QueryIcon
30
+ };
16
31
  function timeAgo(ts) {
17
32
  const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
18
33
  if (s < 60) return `${s}s ago`;
@@ -95,11 +110,21 @@ export function TimeMachineBody() {
95
110
  hasLoaded,
96
111
  busy,
97
112
  lastOutcome,
113
+ preview,
98
114
  remoteProviders
99
115
  } = useTimeMachineState();
100
116
  const localProviders = useSnapshotProviders();
101
117
  const isPro = useIsPro();
118
+ const host = useToolHost();
102
119
  const [name, setName] = useState("");
120
+ const [screen, setScreen] = useState({
121
+ kind: "list"
122
+ });
123
+ const [picker, setPicker] = useState(null);
124
+
125
+ // Header search (when hosted by the core shell) filters the snapshot list.
126
+ const listQuery = host.search.trim().toLowerCase();
127
+ const visibleSnapshots = listQuery ? snapshots.filter(s => s.name.toLowerCase().includes(listQuery)) : snapshots;
103
128
 
104
129
  // On-device: live registry probes. Buoy Desktop: statuses mirrored from
105
130
  // the device's sync payload.
@@ -115,38 +140,159 @@ export function TimeMachineBody() {
115
140
  void timeMachineStore.capture(name);
116
141
  setName("");
117
142
  };
118
- const captureBaseline = () => {
119
- if (captureDisabled) return;
120
- void timeMachineStore.captureBaseline();
121
- };
122
- const restore = (meta, mode) => {
143
+ const restore = (meta, mode, excludeKeys) => {
123
144
  if (busy) return;
124
145
  if (meta.baseline) {
125
146
  confirmDestructive("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void timeMachineStore.restore(meta.id, "reload"));
126
147
  return;
127
148
  }
128
- void timeMachineStore.restore(meta.id, mode);
149
+ void timeMachineStore.restore(meta.id, mode, excludeKeys);
129
150
  };
130
151
  const remove = meta => {
131
152
  confirmDestructive("Delete snapshot?", `“${meta.name}” cannot be recovered.`, "Delete", () => void timeMachineStore.delete(meta.id));
132
153
  };
154
+ const wipeAll = () => {
155
+ if (busy) return;
156
+ confirmDestructive("Wipe all app state?", "This deletes all app storage and reloads the app — like a fresh install.", "Wipe & reload", () => void timeMachineStore.wipeAll());
157
+ };
158
+ const openPreview = meta => {
159
+ void timeMachineStore.buildPreview({
160
+ snapshotId: meta.id
161
+ });
162
+ setScreen({
163
+ kind: "preview"
164
+ });
165
+ };
166
+ const closePreview = () => {
167
+ timeMachineStore.clearPreview();
168
+ setScreen({
169
+ kind: "list"
170
+ });
171
+ };
172
+
173
+ // ── Preview / item screens ──
174
+ if (screen.kind === "item" && preview) {
175
+ return /*#__PURE__*/_jsx(ItemDetailScreen, {
176
+ sourceLabel: screen.sourceLabel,
177
+ item: screen.item,
178
+ leftLabel: preview.left === "current" ? "CURRENT" : snapshots.find(s => s.id === preview.left.snapshotId)?.name ?? "snapshot",
179
+ rightLabel: preview.right === "current" ? "CURRENT" : snapshots.find(s => s.id === preview.right.snapshotId)?.name ?? "snapshot",
180
+ onBack: () => setScreen({
181
+ kind: "preview"
182
+ })
183
+ });
184
+ }
185
+ if (screen.kind === "preview" && preview) {
186
+ const rightMeta = preview.right === "current" ? undefined : snapshots.find(s => s.id === preview.right.snapshotId);
187
+ const pickerItems = [];
188
+ if (picker) {
189
+ const otherRef = picker === "left" ? preview.right : preview.left;
190
+ const otherId = otherRef === "current" ? null : otherRef.snapshotId;
191
+ if (picker === "left") {
192
+ pickerItems.push({
193
+ index: 0,
194
+ label: "Current state",
195
+ timestamp: "live",
196
+ relativeTime: "now",
197
+ selected: preview.left === "current"
198
+ });
199
+ }
200
+ snapshots.forEach((s, i) => {
201
+ const ref = {
202
+ snapshotId: s.id
203
+ };
204
+ const current = picker === "left" ? preview.left : preview.right;
205
+ pickerItems.push({
206
+ index: picker === "left" ? i + 1 : i,
207
+ label: s.baseline ? `⌂ ${s.name}` : s.name,
208
+ timestamp: new Date(s.createdAt).toLocaleTimeString(),
209
+ relativeTime: timeAgo(s.createdAt),
210
+ disabled: s.id === otherId || picker === "right" && s.baseline,
211
+ selected: current !== "current" && current.snapshotId === s.id
212
+ });
213
+ });
214
+ }
215
+ const onPickerSelect = index => {
216
+ const side = picker;
217
+ setPicker(null);
218
+ if (!side) return;
219
+ let ref;
220
+ if (side === "left") {
221
+ ref = index === 0 ? "current" : {
222
+ snapshotId: snapshots[index - 1].id
223
+ };
224
+ void timeMachineStore.buildPreview(preview.right, ref);
225
+ } else {
226
+ ref = {
227
+ snapshotId: snapshots[index].id
228
+ };
229
+ void timeMachineStore.buildPreview(ref, preview.left);
230
+ }
231
+ };
232
+ return /*#__PURE__*/_jsxs(View, {
233
+ style: styles.screenFill,
234
+ children: [/*#__PURE__*/_jsx(RestorePreviewScreen, {
235
+ preview: preview,
236
+ snapshots: snapshots,
237
+ busy: busy !== null,
238
+ onBack: closePreview,
239
+ onOpenItem: (sourceId, item) => {
240
+ const source = preview.sources.find(s => s.id === sourceId);
241
+ setScreen({
242
+ kind: "item",
243
+ sourceId,
244
+ sourceLabel: source?.label ?? sourceId,
245
+ item
246
+ });
247
+ },
248
+ onRestore: (mode, excludeKeys) => {
249
+ if (!rightMeta || busy) return;
250
+ if (rightMeta.baseline) {
251
+ confirmDestructive("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void timeMachineStore.restore(rightMeta.id, "reload"));
252
+ return;
253
+ }
254
+ // Land back on the list, where the outcome panel reports the
255
+ // results — the preview's diff is stale the moment restore runs.
256
+ void timeMachineStore.restore(rightMeta.id, mode, excludeKeys).then(() => closePreview());
257
+ },
258
+ onPickSide: setPicker,
259
+ onRefresh: () => void timeMachineStore.buildPreview(preview.right, preview.left)
260
+ }), picker && /*#__PURE__*/_jsx(EventPickerModal, {
261
+ isOpen: true,
262
+ onClose: () => setPicker(null),
263
+ title: picker === "left" ? "Compare from" : "Compare to",
264
+ position: picker,
265
+ items: pickerItems,
266
+ onSelect: onPickerSelect
267
+ })]
268
+ });
269
+ }
270
+
271
+ // ── List screen ──
133
272
  const unavailable = providers.filter(p => p.detail && (!p.canCapture || !p.canRestore));
134
- return /*#__PURE__*/_jsxs(View, {
135
- style: styles.container,
273
+ return /*#__PURE__*/_jsxs(ScrollView, {
274
+ style: styles.screenFill,
275
+ contentContainerStyle: styles.container,
136
276
  children: [lastOutcome && /*#__PURE__*/_jsx(OutcomePanel, {
137
277
  outcome: lastOutcome
138
278
  }), /*#__PURE__*/_jsxs(View, {
139
- style: styles.captureRow,
140
- children: [/*#__PURE__*/_jsx(TextInput, {
141
- style: styles.nameInput,
142
- value: name,
143
- onChangeText: setName,
144
- placeholder: "Snapshot name\u2026",
145
- placeholderTextColor: buoyColors.textMuted,
146
- editable: !captureDisabled,
147
- onSubmitEditing: capture,
148
- returnKeyType: "done",
149
- testID: "time-machine-name-input"
279
+ style: styles.captureCard,
280
+ children: [/*#__PURE__*/_jsxs(View, {
281
+ style: styles.captureField,
282
+ children: [/*#__PURE__*/_jsx(Plus, {
283
+ size: 14,
284
+ color: buoyColors.textMuted
285
+ }), /*#__PURE__*/_jsx(TextInput, {
286
+ style: styles.captureInput,
287
+ value: name,
288
+ onChangeText: setName,
289
+ placeholder: "Name this restore point\u2026",
290
+ placeholderTextColor: buoyColors.textMuted,
291
+ editable: !captureDisabled,
292
+ onSubmitEditing: capture,
293
+ returnKeyType: "done",
294
+ testID: "time-machine-name-input"
295
+ })]
150
296
  }), /*#__PURE__*/_jsx(TouchableOpacity, {
151
297
  style: [styles.captureButton, captureDisabled && styles.buttonDisabled],
152
298
  onPress: capture,
@@ -157,76 +303,88 @@ export function TimeMachineBody() {
157
303
  children: busy === "capture" ? "…" : "CAPTURE"
158
304
  })
159
305
  })]
160
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
161
- style: [styles.baselineButton, captureDisabled && styles.buttonDisabled],
162
- onPress: captureBaseline,
163
- disabled: captureDisabled,
164
- testID: "time-machine-capture-baseline",
165
- children: /*#__PURE__*/_jsx(Text, {
166
- style: styles.baselineButtonText,
167
- children: "\u2302 CAPTURE FRESH-INSTALL BASELINE"
168
- })
169
306
  }), atFreeLimit && /*#__PURE__*/_jsxs(Text, {
170
307
  style: styles.freeLimitNote,
171
308
  children: ["Free plan: ", FREE_SNAPSHOT_LIMIT, " restore points. Upgrade to Pro for unlimited snapshots."]
172
309
  }), /*#__PURE__*/_jsxs(View, {
173
- style: styles.providerStrip,
174
- children: [providers.map(p => {
175
- const dotColor = !p.canCapture ? buoyColors.textMuted : p.canRestore ? buoyColors.success : buoyColors.warning;
176
- return /*#__PURE__*/_jsxs(View, {
177
- style: styles.providerChip,
178
- children: [/*#__PURE__*/_jsx(View, {
179
- style: [styles.providerDot, {
180
- backgroundColor: dotColor
181
- }]
182
- }), /*#__PURE__*/_jsx(Text, {
183
- style: styles.providerLabel,
184
- children: p.label
185
- })]
186
- }, p.id);
187
- }), providers.length === 0 && /*#__PURE__*/_jsx(Text, {
188
- style: styles.emptyText,
189
- children: "No snapshot providers registered"
310
+ style: styles.sectionRow,
311
+ children: [/*#__PURE__*/_jsx(Text, {
312
+ style: styles.sectionTitle,
313
+ children: "Sources"
314
+ }), /*#__PURE__*/_jsxs(View, {
315
+ style: styles.sourceTiles,
316
+ children: [providers.map(p => {
317
+ const IconComp = SOURCE_ICONS[p.id] ?? TimeMachineIcon;
318
+ const dotColor = !p.canCapture ? buoyColors.textMuted : p.canRestore ? buoyColors.success : buoyColors.warning;
319
+ return /*#__PURE__*/_jsxs(View, {
320
+ style: styles.sourceTile,
321
+ children: [/*#__PURE__*/_jsx(IconComp, {
322
+ size: 16
323
+ }), /*#__PURE__*/_jsx(View, {
324
+ style: [styles.sourceTileDot, {
325
+ backgroundColor: dotColor
326
+ }]
327
+ })]
328
+ }, p.id);
329
+ }), providers.length === 0 && /*#__PURE__*/_jsx(Text, {
330
+ style: styles.emptyText,
331
+ children: "none registered"
332
+ })]
190
333
  })]
191
334
  }), unavailable.map(p => /*#__PURE__*/_jsxs(Text, {
192
335
  style: styles.providerDetail,
193
336
  children: [p.label, ": ", p.detail]
194
- }, p.id)), hasLoaded && snapshots.length === 0 && /*#__PURE__*/_jsx(Text, {
337
+ }, p.id)), /*#__PURE__*/_jsx(Text, {
338
+ style: [styles.sectionTitle, styles.sectionSpacing],
339
+ children: "Restore points"
340
+ }), hasLoaded && snapshots.length === 0 && /*#__PURE__*/_jsx(Text, {
195
341
  style: styles.emptyText,
196
- children: "No snapshots yet. Capture one, test your flow, then restore to jump right back."
197
- }), snapshots.map(meta => {
342
+ children: "No restore points yet. Set up the state you want to return to, then capture it."
343
+ }), visibleSnapshots.map(meta => {
198
344
  const warnings = warningCount(meta);
199
- return /*#__PURE__*/_jsxs(View, {
200
- style: styles.snapshotRow,
345
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
346
+ style: styles.snapshotCard,
347
+ onPress: () => openPreview(meta),
201
348
  testID: `time-machine-snap-${meta.id}`,
202
- children: [/*#__PURE__*/_jsxs(View, {
349
+ children: [/*#__PURE__*/_jsx(View, {
350
+ style: styles.snapshotIconTile,
351
+ children: meta.baseline ? /*#__PURE__*/_jsx(Home, {
352
+ size: 17,
353
+ color: buoyColors.textSecondary
354
+ }) : /*#__PURE__*/_jsx(TimeMachineIcon, {
355
+ size: 18
356
+ })
357
+ }), /*#__PURE__*/_jsxs(View, {
203
358
  style: styles.snapshotInfo,
204
- children: [/*#__PURE__*/_jsxs(Text, {
359
+ children: [/*#__PURE__*/_jsx(Text, {
205
360
  style: styles.snapshotName,
206
361
  numberOfLines: 1,
207
- children: [meta.baseline ? "⌂ " : "", meta.name]
362
+ children: meta.name
208
363
  }), /*#__PURE__*/_jsxs(Text, {
209
364
  style: styles.snapshotMeta,
210
- children: [timeAgo(meta.createdAt), meta.baseline ? " · wipes to fresh install" : ` · ${formatBytes(meta.sizeBytes)} · ${Object.keys(meta.sources).join(", ") || "empty"}`, warnings > 0 ? ` · ⚠ ${warnings}` : ""]
365
+ numberOfLines: 1,
366
+ children: [meta.baseline ? "wipes to fresh install" : `${timeAgo(meta.createdAt)} · ${formatBytes(meta.sizeBytes)} · ${Object.keys(meta.sources).length} sources`, meta.scope?.length ? ` · scoped (${meta.scope.length})` : "", warnings > 0 ? ` · ⚠ ${warnings}` : ""]
211
367
  })]
212
- }), !meta.baseline && /*#__PURE__*/_jsx(TouchableOpacity, {
213
- style: [styles.rowButton, busy && styles.buttonDisabled],
368
+ }), !meta.baseline ? /*#__PURE__*/_jsx(TouchableOpacity, {
369
+ style: [styles.rowAction, busy && styles.buttonDisabled],
214
370
  onPress: () => restore(meta, "live"),
215
371
  disabled: busy !== null,
372
+ accessibilityLabel: "Restore",
216
373
  children: /*#__PURE__*/_jsx(Text, {
217
- style: styles.rowButtonText,
374
+ style: styles.rowActionLabel,
218
375
  children: "RESTORE"
219
376
  })
220
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
221
- style: [styles.rowButton, busy && styles.buttonDisabled],
377
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
378
+ style: [styles.rowAction, busy && styles.buttonDisabled],
222
379
  onPress: () => restore(meta, "reload"),
223
380
  disabled: busy !== null,
381
+ accessibilityLabel: "Wipe and reload",
224
382
  children: /*#__PURE__*/_jsx(Text, {
225
- style: styles.rowButtonText,
226
- children: meta.baseline ? "RESTORE" : "↻"
383
+ style: styles.rowActionLabelDanger,
384
+ children: "WIPE"
227
385
  })
228
386
  }), /*#__PURE__*/_jsx(TouchableOpacity, {
229
- style: styles.deleteButton,
387
+ style: styles.rowAction,
230
388
  onPress: () => remove(meta),
231
389
  hitSlop: {
232
390
  top: 8,
@@ -234,39 +392,66 @@ export function TimeMachineBody() {
234
392
  left: 4,
235
393
  right: 8
236
394
  },
237
- children: /*#__PURE__*/_jsx(Text, {
238
- style: styles.deleteButtonText,
239
- children: "\u2715"
395
+ accessibilityLabel: "Delete",
396
+ children: /*#__PURE__*/_jsx(Trash2, {
397
+ size: 14,
398
+ color: buoyColors.error
240
399
  })
241
400
  })]
242
401
  }, meta.id);
402
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
403
+ style: [styles.wipeAllButton, busy && styles.buttonDisabled],
404
+ onPress: wipeAll,
405
+ disabled: busy !== null,
406
+ testID: "time-machine-wipe-all",
407
+ children: [/*#__PURE__*/_jsx(Trash2, {
408
+ size: 13,
409
+ color: buoyColors.error
410
+ }), /*#__PURE__*/_jsx(Text, {
411
+ style: styles.wipeAllText,
412
+ children: "Wipe all app state\u2026"
413
+ })]
414
+ }), /*#__PURE__*/_jsx(Text, {
415
+ style: styles.wipeAllCaption,
416
+ children: "Deletes all app storage and reloads \u2014 like a fresh install."
243
417
  })]
244
418
  });
245
419
  }
246
420
  const styles = StyleSheet.create({
421
+ screenFill: {
422
+ flex: 1
423
+ },
247
424
  container: {
248
425
  padding: 12,
249
426
  gap: 8
250
427
  },
251
- captureRow: {
428
+ captureCard: {
252
429
  flexDirection: "row",
430
+ alignItems: "center",
253
431
  gap: 8
254
432
  },
255
- nameInput: {
433
+ captureField: {
256
434
  flex: 1,
257
- backgroundColor: buoyColors.input,
435
+ flexDirection: "row",
436
+ alignItems: "center",
437
+ gap: 8,
438
+ backgroundColor: buoyColors.card,
258
439
  borderColor: buoyColors.border,
259
440
  borderWidth: 1,
260
- borderRadius: 6,
441
+ borderRadius: 10,
442
+ paddingHorizontal: 10
443
+ },
444
+ captureInput: {
445
+ flex: 1,
261
446
  color: buoyColors.text,
262
- paddingHorizontal: 10,
263
- paddingVertical: 8,
264
- fontSize: 13
447
+ fontSize: 13,
448
+ paddingVertical: 9
265
449
  },
266
450
  captureButton: {
267
451
  backgroundColor: buoyColors.primary,
268
- borderRadius: 6,
269
- paddingHorizontal: 14,
452
+ borderRadius: 10,
453
+ paddingHorizontal: 16,
454
+ paddingVertical: 10,
270
455
  justifyContent: "center"
271
456
  },
272
457
  captureButtonText: {
@@ -275,19 +460,6 @@ const styles = StyleSheet.create({
275
460
  fontWeight: "700",
276
461
  letterSpacing: 0.5
277
462
  },
278
- baselineButton: {
279
- borderColor: buoyColors.border,
280
- borderWidth: 1,
281
- borderRadius: 6,
282
- paddingVertical: 8,
283
- alignItems: "center"
284
- },
285
- baselineButtonText: {
286
- color: buoyColors.textSecondary,
287
- fontSize: 11,
288
- fontWeight: "600",
289
- letterSpacing: 0.5
290
- },
291
463
  buttonDisabled: {
292
464
  opacity: 0.4
293
465
  },
@@ -295,83 +467,127 @@ const styles = StyleSheet.create({
295
467
  color: buoyColors.warning,
296
468
  fontSize: 11
297
469
  },
298
- providerStrip: {
470
+ sectionRow: {
299
471
  flexDirection: "row",
300
- flexWrap: "wrap",
301
- gap: 6,
302
- marginTop: 4
472
+ alignItems: "center",
473
+ justifyContent: "space-between",
474
+ marginTop: 12,
475
+ gap: 10
476
+ },
477
+ sectionTitle: {
478
+ color: buoyColors.text,
479
+ fontSize: 14,
480
+ fontWeight: "700"
481
+ },
482
+ sectionSpacing: {
483
+ marginTop: 14
303
484
  },
304
- providerChip: {
485
+ sourceTiles: {
305
486
  flexDirection: "row",
306
- alignItems: "center",
307
- gap: 5,
487
+ gap: 6,
488
+ flexWrap: "wrap",
489
+ justifyContent: "flex-end",
490
+ flex: 1
491
+ },
492
+ sourceTile: {
493
+ width: 32,
494
+ height: 32,
495
+ borderRadius: 9,
308
496
  backgroundColor: buoyColors.card,
309
497
  borderColor: buoyColors.border,
310
498
  borderWidth: 1,
311
- borderRadius: 12,
312
- paddingHorizontal: 8,
313
- paddingVertical: 3
499
+ alignItems: "center",
500
+ justifyContent: "center"
314
501
  },
315
- providerDot: {
502
+ sourceTileDot: {
503
+ position: "absolute",
504
+ top: 3,
505
+ right: 3,
316
506
  width: 6,
317
507
  height: 6,
318
508
  borderRadius: 3
319
509
  },
320
- providerLabel: {
321
- color: buoyColors.textSecondary,
322
- fontSize: 11
323
- },
324
510
  providerDetail: {
325
511
  color: buoyColors.textMuted,
326
- fontSize: 10
512
+ fontSize: 10,
513
+ marginTop: 4
327
514
  },
328
515
  emptyText: {
329
516
  color: buoyColors.textMuted,
330
517
  fontSize: 12,
331
518
  marginTop: 8
332
519
  },
333
- snapshotRow: {
520
+ snapshotCard: {
334
521
  flexDirection: "row",
335
522
  alignItems: "center",
336
523
  backgroundColor: buoyColors.card,
337
524
  borderColor: buoyColors.border,
338
525
  borderWidth: 1,
339
- borderRadius: 8,
340
- padding: 10,
341
- gap: 8
526
+ borderRadius: 12,
527
+ paddingHorizontal: 12,
528
+ paddingVertical: 10,
529
+ marginTop: 8,
530
+ gap: 10
531
+ },
532
+ snapshotIconTile: {
533
+ width: 36,
534
+ height: 36,
535
+ borderRadius: 10,
536
+ backgroundColor: buoyColors.hover,
537
+ alignItems: "center",
538
+ justifyContent: "center"
342
539
  },
343
540
  snapshotInfo: {
344
541
  flex: 1,
345
- minWidth: 0
542
+ minWidth: 0,
543
+ gap: 2
346
544
  },
347
545
  snapshotName: {
348
546
  color: buoyColors.text,
349
- fontSize: 13,
350
- fontWeight: "600"
547
+ fontSize: 14,
548
+ fontWeight: "700"
351
549
  },
352
550
  snapshotMeta: {
353
551
  color: buoyColors.textMuted,
354
552
  fontSize: 10,
355
- marginTop: 2
553
+ fontVariant: ["tabular-nums"]
356
554
  },
357
- rowButton: {
358
- borderColor: buoyColors.primary,
359
- borderWidth: 1,
360
- borderRadius: 6,
361
- paddingHorizontal: 8,
362
- paddingVertical: 6
555
+ rowAction: {
556
+ padding: 4,
557
+ justifyContent: "center",
558
+ alignItems: "center"
363
559
  },
364
- rowButtonText: {
560
+ rowActionLabel: {
365
561
  color: buoyColors.primary,
366
- fontSize: 10,
367
- fontWeight: "700"
562
+ fontSize: 12,
563
+ fontWeight: "600"
368
564
  },
369
- deleteButton: {
370
- paddingHorizontal: 2
565
+ rowActionLabelDanger: {
566
+ color: buoyColors.error,
567
+ fontSize: 12,
568
+ fontWeight: "600"
371
569
  },
372
- deleteButtonText: {
570
+ wipeAllButton: {
571
+ flexDirection: "row",
572
+ alignItems: "center",
573
+ justifyContent: "center",
574
+ gap: 7,
575
+ borderColor: `${buoyColors.error}55`,
576
+ borderWidth: 1,
577
+ borderRadius: 10,
578
+ paddingVertical: 10,
579
+ marginTop: 18
580
+ },
581
+ wipeAllText: {
582
+ color: buoyColors.error,
583
+ fontSize: 12.5,
584
+ fontWeight: "600"
585
+ },
586
+ wipeAllCaption: {
373
587
  color: buoyColors.textMuted,
374
- fontSize: 13
588
+ fontSize: 10,
589
+ textAlign: "center",
590
+ marginTop: 6
375
591
  },
376
592
  outcomePanel: {
377
593
  backgroundColor: buoyColors.card,