@buoy-gg/time-machine 7.0.21 → 7.0.23

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 (174) hide show
  1. package/lib/commonjs/components/TimeMachineBody.js +148 -583
  2. package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
  3. package/lib/commonjs/components/TimeMachineModal.js +10 -11
  4. package/lib/commonjs/components/TimeMachineModal.js.map +1 -1
  5. package/lib/commonjs/components/confirm.js +39 -0
  6. package/lib/commonjs/components/confirm.js.map +1 -0
  7. package/lib/commonjs/components/format.js +24 -0
  8. package/lib/commonjs/components/format.js.map +1 -0
  9. package/lib/commonjs/components/list/CaptureBar.js +95 -0
  10. package/lib/commonjs/components/list/CaptureBar.js.map +1 -0
  11. package/lib/commonjs/components/list/RestoreOutcomeCard.js +162 -0
  12. package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +1 -0
  13. package/lib/commonjs/components/list/SnapshotRow.js +141 -0
  14. package/lib/commonjs/components/list/SnapshotRow.js.map +1 -0
  15. package/lib/commonjs/components/list/SourcesStrip.js +126 -0
  16. package/lib/commonjs/components/list/SourcesStrip.js.map +1 -0
  17. package/lib/commonjs/components/list/UndoDeleteBar.js +81 -0
  18. package/lib/commonjs/components/list/UndoDeleteBar.js.map +1 -0
  19. package/lib/commonjs/components/list/WipeAllRow.js +46 -0
  20. package/lib/commonjs/components/list/WipeAllRow.js.map +1 -0
  21. package/lib/commonjs/components/preview/ItemDiffPanel.js +77 -0
  22. package/lib/commonjs/components/preview/ItemDiffPanel.js.map +1 -0
  23. package/lib/commonjs/components/preview/PreviewFlow.js +51 -0
  24. package/lib/commonjs/components/preview/PreviewFlow.js.map +1 -0
  25. package/lib/commonjs/components/preview/RestorePointNameRow.js +83 -0
  26. package/lib/commonjs/components/preview/RestorePointNameRow.js.map +1 -0
  27. package/lib/commonjs/components/preview/RestorePreviewScreen.js +549 -635
  28. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -1
  29. package/lib/commonjs/components/preview/verdictMeta.js +8 -8
  30. package/lib/commonjs/components/preview/verdictMeta.js.map +1 -1
  31. package/lib/commonjs/components/sources.js +72 -0
  32. package/lib/commonjs/components/sources.js.map +1 -0
  33. package/lib/commonjs/index.js +55 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/store/routeRestore.js +188 -0
  36. package/lib/commonjs/store/routeRestore.js.map +1 -0
  37. package/lib/commonjs/store/snapshotVault.js +93 -9
  38. package/lib/commonjs/store/snapshotVault.js.map +1 -1
  39. package/lib/commonjs/store/timeMachineStore.js +164 -7
  40. package/lib/commonjs/store/timeMachineStore.js.map +1 -1
  41. package/lib/commonjs/sync/timeMachineSyncAdapter.js +59 -4
  42. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +1 -1
  43. package/lib/commonjs/ui/SwipeActionsRow.js +167 -0
  44. package/lib/commonjs/ui/SwipeActionsRow.js.map +1 -0
  45. package/lib/commonjs/ui/primitives.js +164 -0
  46. package/lib/commonjs/ui/primitives.js.map +1 -0
  47. package/lib/commonjs/ui/tmTheme.js +103 -0
  48. package/lib/commonjs/ui/tmTheme.js.map +1 -0
  49. package/lib/module/components/TimeMachineBody.js +151 -586
  50. package/lib/module/components/TimeMachineBody.js.map +1 -1
  51. package/lib/module/components/TimeMachineModal.js +11 -12
  52. package/lib/module/components/TimeMachineModal.js.map +1 -1
  53. package/lib/module/components/confirm.js +34 -0
  54. package/lib/module/components/confirm.js.map +1 -0
  55. package/lib/module/components/format.js +19 -0
  56. package/lib/module/components/format.js.map +1 -0
  57. package/lib/module/components/list/CaptureBar.js +90 -0
  58. package/lib/module/components/list/CaptureBar.js.map +1 -0
  59. package/lib/module/components/list/RestoreOutcomeCard.js +156 -0
  60. package/lib/module/components/list/RestoreOutcomeCard.js.map +1 -0
  61. package/lib/module/components/list/SnapshotRow.js +138 -0
  62. package/lib/module/components/list/SnapshotRow.js.map +1 -0
  63. package/lib/module/components/list/SourcesStrip.js +121 -0
  64. package/lib/module/components/list/SourcesStrip.js.map +1 -0
  65. package/lib/module/components/list/UndoDeleteBar.js +78 -0
  66. package/lib/module/components/list/UndoDeleteBar.js.map +1 -0
  67. package/lib/module/components/list/WipeAllRow.js +41 -0
  68. package/lib/module/components/list/WipeAllRow.js.map +1 -0
  69. package/lib/module/components/preview/ItemDiffPanel.js +72 -0
  70. package/lib/module/components/preview/ItemDiffPanel.js.map +1 -0
  71. package/lib/module/components/preview/PreviewFlow.js +46 -0
  72. package/lib/module/components/preview/PreviewFlow.js.map +1 -0
  73. package/lib/module/components/preview/RestorePointNameRow.js +78 -0
  74. package/lib/module/components/preview/RestorePointNameRow.js.map +1 -0
  75. package/lib/module/components/preview/RestorePreviewScreen.js +552 -638
  76. package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -1
  77. package/lib/module/components/preview/verdictMeta.js +9 -8
  78. package/lib/module/components/preview/verdictMeta.js.map +1 -1
  79. package/lib/module/components/sources.js +66 -0
  80. package/lib/module/components/sources.js.map +1 -0
  81. package/lib/module/index.js +11 -0
  82. package/lib/module/index.js.map +1 -1
  83. package/lib/module/store/routeRestore.js +176 -0
  84. package/lib/module/store/routeRestore.js.map +1 -0
  85. package/lib/module/store/snapshotVault.js +87 -9
  86. package/lib/module/store/snapshotVault.js.map +1 -1
  87. package/lib/module/store/timeMachineStore.js +164 -8
  88. package/lib/module/store/timeMachineStore.js.map +1 -1
  89. package/lib/module/sync/timeMachineSyncAdapter.js +59 -4
  90. package/lib/module/sync/timeMachineSyncAdapter.js.map +1 -1
  91. package/lib/module/ui/SwipeActionsRow.js +162 -0
  92. package/lib/module/ui/SwipeActionsRow.js.map +1 -0
  93. package/lib/module/ui/primitives.js +156 -0
  94. package/lib/module/ui/primitives.js.map +1 -0
  95. package/lib/module/ui/tmTheme.js +99 -0
  96. package/lib/module/ui/tmTheme.js.map +1 -0
  97. package/lib/typescript/components/TimeMachineBody.d.ts.map +1 -1
  98. package/lib/typescript/components/TimeMachineModal.d.ts.map +1 -1
  99. package/lib/typescript/components/confirm.d.ts +2 -0
  100. package/lib/typescript/components/confirm.d.ts.map +1 -0
  101. package/lib/typescript/components/format.d.ts +4 -0
  102. package/lib/typescript/components/format.d.ts.map +1 -0
  103. package/lib/typescript/components/list/CaptureBar.d.ts +6 -0
  104. package/lib/typescript/components/list/CaptureBar.d.ts.map +1 -0
  105. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts +11 -0
  106. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +1 -0
  107. package/lib/typescript/components/list/SnapshotRow.d.ts +12 -0
  108. package/lib/typescript/components/list/SnapshotRow.d.ts.map +1 -0
  109. package/lib/typescript/components/list/SourcesStrip.d.ts +5 -0
  110. package/lib/typescript/components/list/SourcesStrip.d.ts.map +1 -0
  111. package/lib/typescript/components/list/UndoDeleteBar.d.ts +8 -0
  112. package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +1 -0
  113. package/lib/typescript/components/list/WipeAllRow.d.ts +5 -0
  114. package/lib/typescript/components/list/WipeAllRow.d.ts.map +1 -0
  115. package/lib/typescript/components/preview/ItemDiffPanel.d.ts +5 -0
  116. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +1 -0
  117. package/lib/typescript/components/preview/PreviewFlow.d.ts +9 -0
  118. package/lib/typescript/components/preview/PreviewFlow.d.ts.map +1 -0
  119. package/lib/typescript/components/preview/RestorePointNameRow.d.ts +5 -0
  120. package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +1 -0
  121. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts +1 -5
  122. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -1
  123. package/lib/typescript/components/preview/verdictMeta.d.ts +5 -0
  124. package/lib/typescript/components/preview/verdictMeta.d.ts.map +1 -1
  125. package/lib/typescript/components/sources.d.ts +36 -0
  126. package/lib/typescript/components/sources.d.ts.map +1 -0
  127. package/lib/typescript/index.d.ts +2 -1
  128. package/lib/typescript/index.d.ts.map +1 -1
  129. package/lib/typescript/store/routeRestore.d.ts +51 -0
  130. package/lib/typescript/store/routeRestore.d.ts.map +1 -0
  131. package/lib/typescript/store/snapshotVault.d.ts +55 -0
  132. package/lib/typescript/store/snapshotVault.d.ts.map +1 -1
  133. package/lib/typescript/store/timeMachineStore.d.ts +64 -2
  134. package/lib/typescript/store/timeMachineStore.d.ts.map +1 -1
  135. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +25 -0
  136. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +1 -1
  137. package/lib/typescript/ui/SwipeActionsRow.d.ts +32 -0
  138. package/lib/typescript/ui/SwipeActionsRow.d.ts.map +1 -0
  139. package/lib/typescript/ui/primitives.d.ts +57 -0
  140. package/lib/typescript/ui/primitives.d.ts.map +1 -0
  141. package/lib/typescript/ui/tmTheme.d.ts +67 -0
  142. package/lib/typescript/ui/tmTheme.d.ts.map +1 -0
  143. package/package.json +12 -8
  144. package/src/components/TimeMachineBody.tsx +184 -682
  145. package/src/components/TimeMachineModal.tsx +10 -11
  146. package/src/components/confirm.ts +35 -0
  147. package/src/components/format.ts +17 -0
  148. package/src/components/list/CaptureBar.tsx +100 -0
  149. package/src/components/list/RestoreOutcomeCard.tsx +159 -0
  150. package/src/components/list/SnapshotRow.tsx +155 -0
  151. package/src/components/list/SourcesStrip.tsx +124 -0
  152. package/src/components/list/UndoDeleteBar.tsx +76 -0
  153. package/src/components/list/WipeAllRow.tsx +42 -0
  154. package/src/components/preview/ItemDiffPanel.tsx +77 -0
  155. package/src/components/preview/PreviewFlow.tsx +70 -0
  156. package/src/components/preview/RestorePointNameRow.tsx +80 -0
  157. package/src/components/preview/RestorePreviewScreen.tsx +535 -535
  158. package/src/components/preview/verdictMeta.ts +8 -8
  159. package/src/components/sources.ts +92 -0
  160. package/src/index.tsx +22 -0
  161. package/src/store/routeRestore.ts +194 -0
  162. package/src/store/snapshotVault.ts +138 -9
  163. package/src/store/timeMachineStore.ts +195 -5
  164. package/src/sync/timeMachineSyncAdapter.ts +57 -4
  165. package/src/ui/SwipeActionsRow.tsx +199 -0
  166. package/src/ui/primitives.tsx +157 -0
  167. package/src/ui/tmTheme.ts +107 -0
  168. package/lib/commonjs/components/preview/ItemDetailScreen.js +0 -189
  169. package/lib/commonjs/components/preview/ItemDetailScreen.js.map +0 -1
  170. package/lib/module/components/preview/ItemDetailScreen.js +0 -184
  171. package/lib/module/components/preview/ItemDetailScreen.js.map +0 -1
  172. package/lib/typescript/components/preview/ItemDetailScreen.d.ts +0 -11
  173. package/lib/typescript/components/preview/ItemDetailScreen.d.ts.map +0 -1
  174. package/src/components/preview/ItemDetailScreen.tsx +0 -172
@@ -1,631 +1,196 @@
1
1
  "use strict";
2
2
 
3
3
  /**
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).
4
+ * Time Machine body — the screen router plus the restore-point list.
7
5
  *
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).
6
+ * Screens: list → preview. Each is its own component that mounts and
7
+ * unmounts whole (no branching inside a single tree), and each owns its own
8
+ * scrolling — the host must NOT wrap this in a scroll view, or the preview's
9
+ * virtualized FlatList is defeated. Item diffs expand inline in that list
10
+ * rather than being a third screen.
11
+ *
12
+ * The list follows the grouped-inset surface in ../ui: capture bar, the
13
+ * Sources strip, one card of restore points with swipe-to-delete, and the
14
+ * app-wide wipe kept last and understated.
11
15
  */
12
16
  import { useState } from "react";
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";
17
+ import { ScrollView, StyleSheet, Text, View } from "react-native";
18
+ import { useSafeAreaInsets, useLimit } from "@buoy-gg/shared-ui";
16
19
  import { useToolHost } from "@buoy-gg/core";
17
20
  import { useIsPro } from "@buoy-gg/license";
18
- import { useSnapshotProviders, useTimeMachineState } from "../hooks/useTimeMachine";
21
+ import { useTimeMachineState } from "../hooks/useTimeMachine";
19
22
  import { timeMachineStore } from "../store/timeMachineStore";
20
- import { RestorePreviewScreen } from "./preview/RestorePreviewScreen";
21
- import { ItemDetailScreen } from "./preview/ItemDetailScreen";
22
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
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
- };
31
- function timeAgo(ts) {
32
- const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
33
- if (s < 60) return `${s}s ago`;
34
- const m = Math.round(s / 60);
35
- if (m < 60) return `${m}m ago`;
36
- const h = Math.round(m / 60);
37
- if (h < 24) return `${h}h ago`;
38
- return `${Math.round(h / 24)}d ago`;
39
- }
40
- function formatBytes(bytes) {
41
- if (bytes < 1024) return `${bytes} B`;
42
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
43
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
44
- }
45
- function warningCount(meta) {
46
- return Object.values(meta.sources).reduce((sum, s) => sum + s.warnings.length, 0);
47
- }
23
+ import { Footnote, Group, SectionLabel } from "../ui/primitives";
24
+ import { tm } from "../ui/tmTheme";
25
+ import { useSourceStatuses } from "./sources";
26
+ import { confirmDestructive } from "./confirm";
27
+ import { CaptureBar } from "./list/CaptureBar";
28
+ import { RestoreOutcomeCard, restoreNeedsAttention } from "./list/RestoreOutcomeCard";
29
+ import { SnapshotRow } from "./list/SnapshotRow";
30
+ import { UndoDeleteBar } from "./list/UndoDeleteBar";
31
+ import { SourcesStrip } from "./list/SourcesStrip";
32
+ import { WipeAllRow } from "./list/WipeAllRow";
33
+ import { PreviewScreen } from "./preview/PreviewFlow";
34
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
35
+ export function TimeMachineBody() {
36
+ const [screen, setScreen] = useState({
37
+ kind: "list"
38
+ });
39
+ const {
40
+ snapshots,
41
+ busy,
42
+ preview
43
+ } = useTimeMachineState();
44
+ const sources = useSourceStatuses();
45
+ const backToList = () => {
46
+ timeMachineStore.clearPreview();
47
+ setScreen({
48
+ kind: "list"
49
+ });
50
+ };
48
51
 
49
- /** RN-web's Alert renders no buttons — fall back to the browser dialog there. */
50
- function confirmDestructive(title, message, confirmLabel, onConfirm) {
51
- if (Platform.OS === "web") {
52
- const confirmFn = globalThis.confirm;
53
- if (!confirmFn || confirmFn(`${title}\n\n${message}`)) onConfirm();
54
- return;
55
- }
56
- Alert.alert(title, message, [{
57
- text: "Cancel",
58
- style: "cancel"
59
- }, {
60
- text: confirmLabel,
61
- style: "destructive",
62
- onPress: onConfirm
63
- }]);
64
- }
65
- function OutcomePanel({
66
- outcome
67
- }) {
68
- const entries = Object.entries(outcome.results);
69
- return /*#__PURE__*/_jsxs(View, {
70
- style: styles.outcomePanel,
71
- children: [/*#__PURE__*/_jsxs(View, {
72
- style: styles.outcomeHeaderRow,
73
- children: [/*#__PURE__*/_jsxs(Text, {
74
- style: styles.outcomeTitle,
75
- children: ["Restored \u201C", outcome.snapshotName, "\u201D", outcome.mode === "reload" || outcome.willReload ? " · reloading" : ""]
76
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
77
- onPress: () => timeMachineStore.clearLastOutcome(),
78
- hitSlop: {
79
- top: 8,
80
- bottom: 8,
81
- left: 8,
82
- right: 8
83
- },
84
- children: /*#__PURE__*/_jsx(Text, {
85
- style: styles.outcomeDismiss,
86
- children: "\u2715"
87
- })
88
- })]
89
- }), entries.length === 0 && /*#__PURE__*/_jsx(Text, {
90
- style: styles.outcomeLine,
91
- children: "no sources restored"
92
- }), entries.map(([sourceId, r]) => /*#__PURE__*/_jsxs(View, {
93
- style: styles.outcomeSource,
94
- children: [/*#__PURE__*/_jsxs(Text, {
95
- style: [styles.outcomeLine, !r.ok && styles.outcomeLineBad],
96
- children: [r.ok ? "✓" : "✕", " ", sourceId, " \u2014 applied ", r.applied, r.skipped.length > 0 ? `, skipped ${r.skipped.length}` : ""]
97
- }), r.skipped.slice(0, 3).map(skip => /*#__PURE__*/_jsxs(Text, {
98
- style: styles.outcomeDetail,
99
- children: ["\xB7 ", skip.id, ": ", skip.reason]
100
- }, skip.id)), r.warnings.slice(0, 3).map(warning => /*#__PURE__*/_jsxs(Text, {
101
- style: styles.outcomeDetail,
102
- children: ["\u26A0 ", warning]
103
- }, warning))]
104
- }, sourceId))]
52
+ // The preview can't render without a built diff (clearPreview races a
53
+ // back-navigation) — fall through to the list rather than an empty shell.
54
+ if (screen.kind === "preview" && preview) return /*#__PURE__*/_jsx(PreviewScreen, {
55
+ preview: preview,
56
+ snapshots: snapshots,
57
+ busy: busy !== null,
58
+ onClose: backToList
59
+ });
60
+ return /*#__PURE__*/_jsx(RestorePointList, {
61
+ sources: sources,
62
+ onOpenPreview: meta => {
63
+ void timeMachineStore.buildPreview({
64
+ snapshotId: meta.id
65
+ });
66
+ setScreen({
67
+ kind: "preview"
68
+ });
69
+ }
105
70
  });
106
71
  }
107
- export function TimeMachineBody() {
72
+ function RestorePointList({
73
+ sources,
74
+ onOpenPreview
75
+ }) {
108
76
  const {
109
77
  snapshots,
110
78
  hasLoaded,
111
79
  busy,
112
80
  lastOutcome,
113
- preview,
114
- remoteProviders
81
+ recentlyDeleted
115
82
  } = useTimeMachineState();
116
- const localProviders = useSnapshotProviders();
117
83
  const isPro = useIsPro();
84
+ const snapshotLimit = useLimit("timeMachine.snapshots");
118
85
  const host = useToolHost();
119
- const [name, setName] = useState("");
120
- const [screen, setScreen] = useState({
121
- kind: "list"
86
+ const insets = useSafeAreaInsets({
87
+ minBottom: 16
122
88
  });
123
- const [picker, setPicker] = useState(null);
124
89
 
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;
90
+ // Header search (when hosted by the core shell) filters the list.
91
+ const query = host.search.trim().toLowerCase();
92
+ const visible = query ? snapshots.filter(s => s.name.toLowerCase().includes(query)) : snapshots;
128
93
 
129
- // On-device: live registry probes. Buoy Desktop: statuses mirrored from
130
- // the device's sync payload.
131
- const providers = remoteProviders ?? localProviders.map(p => ({
132
- id: p.id,
133
- label: p.label,
134
- ...p.status()
135
- }));
136
- const atFreeLimit = !isPro && snapshots.length >= FREE_SNAPSHOT_LIMIT;
137
- const captureDisabled = busy !== null || atFreeLimit;
138
- const capture = () => {
139
- if (captureDisabled) return;
140
- void timeMachineStore.capture(name);
141
- setName("");
142
- };
143
- const restore = (meta, mode, excludeKeys) => {
94
+ // Infinity for Pro, so this is simply false for them.
95
+ const atCaptureLimit = snapshots.length >= snapshotLimit;
96
+ const captureDisabled = busy !== null || atCaptureLimit;
97
+
98
+ /**
99
+ * Restoring is the last thing anyone wants from this screen, so the tool
100
+ * gets out of the way the moment it worked — with no toast and no banner.
101
+ * The restored app IS the confirmation, and anything announcing it is one
102
+ * more thing to dismiss on a flow people run dozens of times an hour.
103
+ *
104
+ * It MINIMIZES rather than closes: the list, the search and the screen you
105
+ * were on all survive, and coming back is one tap on the dock. A partial
106
+ * restore stays open instead, because the outcome card is the only place
107
+ * the per-source failures are reported.
108
+ */
109
+ const restore = meta => {
144
110
  if (busy) return;
145
111
  if (meta.baseline) {
146
112
  confirmDestructive("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void timeMachineStore.restore(meta.id, "reload"));
147
113
  return;
148
114
  }
149
- void timeMachineStore.restore(meta.id, mode, excludeKeys);
150
- };
151
- const remove = meta => {
152
- confirmDestructive("Delete snapshot?", `“${meta.name}” cannot be recovered.`, "Delete", () => void timeMachineStore.delete(meta.id));
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"
115
+ void timeMachineStore.restore(meta.id, "live").then(outcome => {
116
+ const clean = Object.values(outcome.results).every(r => r.ok);
117
+ if (!clean || outcome.willReload) return;
118
+ host.minimize();
164
119
  });
165
120
  };
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 ──
272
- const unavailable = providers.filter(p => p.detail && (!p.canCapture || !p.canRestore));
273
121
  return /*#__PURE__*/_jsxs(ScrollView, {
274
- style: styles.screenFill,
275
- contentContainerStyle: styles.container,
276
- children: [lastOutcome && /*#__PURE__*/_jsx(OutcomePanel, {
122
+ style: styles.fill,
123
+ contentContainerStyle: [styles.content, {
124
+ paddingBottom: insets.bottom
125
+ }],
126
+ children: [lastOutcome && restoreNeedsAttention(lastOutcome) ? /*#__PURE__*/_jsx(RestoreOutcomeCard, {
277
127
  outcome: lastOutcome
278
- }), /*#__PURE__*/_jsxs(View, {
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
- })]
296
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
297
- style: [styles.captureButton, captureDisabled && styles.buttonDisabled],
298
- onPress: capture,
128
+ }) : null, recentlyDeleted ? /*#__PURE__*/_jsx(UndoDeleteBar, {
129
+ name: recentlyDeleted.stored.meta.name,
130
+ at: recentlyDeleted.at,
131
+ onUndo: () => void timeMachineStore.undoDelete(),
132
+ onExpire: () => timeMachineStore.clearRecentlyDeleted()
133
+ }) : null, /*#__PURE__*/_jsx(View, {
134
+ children: /*#__PURE__*/_jsx(CaptureBar, {
299
135
  disabled: captureDisabled,
300
- testID: "time-machine-capture",
301
- children: /*#__PURE__*/_jsx(Text, {
302
- style: styles.captureButtonText,
303
- children: busy === "capture" ? "…" : "CAPTURE"
304
- })
305
- })]
306
- }), atFreeLimit && /*#__PURE__*/_jsxs(Text, {
307
- style: styles.freeLimitNote,
308
- children: ["Free plan: ", FREE_SNAPSHOT_LIMIT, " restore points. Upgrade to Pro for unlimited snapshots."]
309
- }), /*#__PURE__*/_jsxs(View, {
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
- })]
333
- })]
334
- }), unavailable.map(p => /*#__PURE__*/_jsxs(Text, {
335
- style: styles.providerDetail,
336
- children: [p.label, ": ", p.detail]
337
- }, p.id)), /*#__PURE__*/_jsx(Text, {
338
- style: [styles.sectionTitle, styles.sectionSpacing],
339
- children: "Restore points"
340
- }), hasLoaded && snapshots.length === 0 && /*#__PURE__*/_jsx(Text, {
341
- style: styles.emptyText,
342
- children: "No restore points yet. Set up the state you want to return to, then capture it."
343
- }), visibleSnapshots.map(meta => {
344
- const warnings = warningCount(meta);
345
- return /*#__PURE__*/_jsxs(TouchableOpacity, {
346
- style: styles.snapshotCard,
347
- onPress: () => openPreview(meta),
348
- testID: `time-machine-snap-${meta.id}`,
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, {
358
- style: styles.snapshotInfo,
359
- children: [/*#__PURE__*/_jsx(Text, {
360
- style: styles.snapshotName,
361
- numberOfLines: 1,
362
- children: meta.name
363
- }), /*#__PURE__*/_jsxs(Text, {
364
- style: styles.snapshotMeta,
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}` : ""]
367
- })]
368
- }), !meta.baseline ? /*#__PURE__*/_jsx(TouchableOpacity, {
369
- style: [styles.rowAction, busy && styles.buttonDisabled],
370
- onPress: () => restore(meta, "live"),
371
- disabled: busy !== null,
372
- accessibilityLabel: "Restore",
373
- children: /*#__PURE__*/_jsx(Text, {
374
- style: styles.rowActionLabel,
375
- children: "RESTORE"
376
- })
377
- }) : /*#__PURE__*/_jsx(TouchableOpacity, {
378
- style: [styles.rowAction, busy && styles.buttonDisabled],
379
- onPress: () => restore(meta, "reload"),
380
- disabled: busy !== null,
381
- accessibilityLabel: "Wipe and reload",
382
- children: /*#__PURE__*/_jsx(Text, {
383
- style: styles.rowActionLabelDanger,
384
- children: "WIPE"
385
- })
386
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
387
- style: styles.rowAction,
388
- onPress: () => remove(meta),
389
- hitSlop: {
390
- top: 8,
391
- bottom: 8,
392
- left: 4,
393
- right: 8
394
- },
395
- accessibilityLabel: "Delete",
396
- children: /*#__PURE__*/_jsx(Trash2, {
397
- size: 14,
398
- color: buoyColors.error
399
- })
400
- })]
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,
136
+ busy: busy === "capture",
137
+ onCapture: name => void timeMachineStore.capture(name)
138
+ })
139
+ }), atCaptureLimit ? /*#__PURE__*/_jsxs(Footnote, {
140
+ tone: "warning",
141
+ children: ["Free plan keeps ", snapshotLimit, " restore points. Upgrade to Pro for unlimited snapshots."]
142
+ }) : null, /*#__PURE__*/_jsx(SectionLabel, {
143
+ children: "Sources"
144
+ }), /*#__PURE__*/_jsx(SourcesStrip, {
145
+ sources: sources
146
+ }), sources.filter(s => s.detail && !(s.canCapture && s.canRestore)).map(s => /*#__PURE__*/_jsxs(Footnote, {
147
+ children: [s.label, ": ", s.detail]
148
+ }, s.id)), /*#__PURE__*/_jsx(SectionLabel, {
149
+ children: "Restore Points"
150
+ }), /*#__PURE__*/_jsxs(Group, {
151
+ children: [visible.map(meta => /*#__PURE__*/_jsx(SnapshotRow, {
152
+ meta: meta,
153
+ busy: busy !== null,
154
+ onOpen: () => onOpenPreview(meta),
155
+ onRestore: () => restore(meta),
156
+ onDelete: () => void timeMachineStore.delete(meta.id),
157
+ onDuplicate: () => void timeMachineStore.duplicate(meta.id)
158
+ }, meta.id)), visible.length === 0 ? /*#__PURE__*/_jsx(Text, {
159
+ style: styles.empty,
160
+ children: !hasLoaded ? "Loading restore points…" : snapshots.length > 0 ? "No restore point matches that name." : "Set up the state you want to come back to, then capture it."
161
+ }) : null]
162
+ }), visible.length > 0 ? /*#__PURE__*/_jsx(Footnote, {
163
+ children: "Swipe a restore point to rename, copy or delete it."
164
+ }) : null, /*#__PURE__*/_jsx(View, {
165
+ style: styles.spacer
166
+ }), /*#__PURE__*/_jsx(Group, {
167
+ children: /*#__PURE__*/_jsx(WipeAllRow, {
168
+ disabled: busy !== null,
169
+ onWipe: () => confirmDestructive("Wipe all app state?", "This deletes all app storage and reloads the app — like a fresh install.", "Wipe & reload", () => void timeMachineStore.wipeAll())
170
+ })
171
+ }), /*#__PURE__*/_jsx(Footnote, {
172
+ centered: true,
416
173
  children: "Deletes all app storage and reloads \u2014 like a fresh install."
417
174
  })]
418
175
  });
419
176
  }
420
177
  const styles = StyleSheet.create({
421
- screenFill: {
422
- flex: 1
423
- },
424
- container: {
425
- padding: 12,
426
- gap: 8
427
- },
428
- captureCard: {
429
- flexDirection: "row",
430
- alignItems: "center",
431
- gap: 8
432
- },
433
- captureField: {
434
- flex: 1,
435
- flexDirection: "row",
436
- alignItems: "center",
437
- gap: 8,
438
- backgroundColor: buoyColors.card,
439
- borderColor: buoyColors.border,
440
- borderWidth: 1,
441
- borderRadius: 10,
442
- paddingHorizontal: 10
443
- },
444
- captureInput: {
178
+ fill: {
445
179
  flex: 1,
446
- color: buoyColors.text,
447
- fontSize: 13,
448
- paddingVertical: 9
449
- },
450
- captureButton: {
451
- backgroundColor: buoyColors.primary,
452
- borderRadius: 10,
453
- paddingHorizontal: 16,
454
- paddingVertical: 10,
455
- justifyContent: "center"
456
- },
457
- captureButtonText: {
458
- color: "#0A0A0A",
459
- fontSize: 12,
460
- fontWeight: "700",
461
- letterSpacing: 0.5
462
- },
463
- buttonDisabled: {
464
- opacity: 0.4
465
- },
466
- freeLimitNote: {
467
- color: buoyColors.warning,
468
- fontSize: 11
469
- },
470
- sectionRow: {
471
- flexDirection: "row",
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
484
- },
485
- sourceTiles: {
486
- flexDirection: "row",
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,
496
- backgroundColor: buoyColors.card,
497
- borderColor: buoyColors.border,
498
- borderWidth: 1,
499
- alignItems: "center",
500
- justifyContent: "center"
501
- },
502
- sourceTileDot: {
503
- position: "absolute",
504
- top: 3,
505
- right: 3,
506
- width: 6,
507
- height: 6,
508
- borderRadius: 3
509
- },
510
- providerDetail: {
511
- color: buoyColors.textMuted,
512
- fontSize: 10,
513
- marginTop: 4
514
- },
515
- emptyText: {
516
- color: buoyColors.textMuted,
517
- fontSize: 12,
518
- marginTop: 8
519
- },
520
- snapshotCard: {
521
- flexDirection: "row",
522
- alignItems: "center",
523
- backgroundColor: buoyColors.card,
524
- borderColor: buoyColors.border,
525
- borderWidth: 1,
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"
539
- },
540
- snapshotInfo: {
541
- flex: 1,
542
- minWidth: 0,
543
- gap: 2
544
- },
545
- snapshotName: {
546
- color: buoyColors.text,
547
- fontSize: 14,
548
- fontWeight: "700"
549
- },
550
- snapshotMeta: {
551
- color: buoyColors.textMuted,
552
- fontSize: 10,
553
- fontVariant: ["tabular-nums"]
554
- },
555
- rowAction: {
556
- padding: 4,
557
- justifyContent: "center",
558
- alignItems: "center"
559
- },
560
- rowActionLabel: {
561
- color: buoyColors.primary,
562
- fontSize: 12,
563
- fontWeight: "600"
564
- },
565
- rowActionLabelDanger: {
566
- color: buoyColors.error,
567
- fontSize: 12,
568
- fontWeight: "600"
569
- },
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: {
587
- color: buoyColors.textMuted,
588
- fontSize: 10,
589
- textAlign: "center",
590
- marginTop: 6
591
- },
592
- outcomePanel: {
593
- backgroundColor: buoyColors.card,
594
- borderColor: buoyColors.borderStrong,
595
- borderWidth: 1,
596
- borderRadius: 8,
597
- padding: 10,
598
- gap: 4
599
- },
600
- outcomeHeaderRow: {
601
- flexDirection: "row",
602
- justifyContent: "space-between",
603
- alignItems: "center"
604
- },
605
- outcomeTitle: {
606
- color: buoyColors.text,
607
- fontSize: 12,
608
- fontWeight: "700",
609
- flex: 1
610
- },
611
- outcomeDismiss: {
612
- color: buoyColors.textMuted,
613
- fontSize: 13
614
- },
615
- outcomeSource: {
616
- gap: 2
180
+ backgroundColor: tm.color.sheet
617
181
  },
618
- outcomeLine: {
619
- color: buoyColors.success,
620
- fontSize: 11
182
+ content: {
183
+ paddingTop: 14
621
184
  },
622
- outcomeLineBad: {
623
- color: buoyColors.warning
185
+ empty: {
186
+ color: tm.color.labelSecondary,
187
+ fontSize: tm.font.footnote,
188
+ lineHeight: tm.font.footnote * 1.4,
189
+ paddingHorizontal: tm.textInset,
190
+ paddingVertical: 16
624
191
  },
625
- outcomeDetail: {
626
- color: buoyColors.textMuted,
627
- fontSize: 10,
628
- paddingLeft: 12
192
+ spacer: {
193
+ height: 24
629
194
  }
630
195
  });
631
196
  //# sourceMappingURL=TimeMachineBody.js.map