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