@buoy-gg/time-machine 7.0.22 → 7.0.24

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 (160) hide show
  1. package/lib/commonjs/components/TimeMachineBody.js +147 -677
  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/store/snapshotVault.js +93 -9
  34. package/lib/commonjs/store/snapshotVault.js.map +1 -1
  35. package/lib/commonjs/store/timeMachineStore.js +125 -6
  36. package/lib/commonjs/store/timeMachineStore.js.map +1 -1
  37. package/lib/commonjs/sync/timeMachineSyncAdapter.js +41 -0
  38. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +1 -1
  39. package/lib/commonjs/ui/SwipeActionsRow.js +167 -0
  40. package/lib/commonjs/ui/SwipeActionsRow.js.map +1 -0
  41. package/lib/commonjs/ui/primitives.js +164 -0
  42. package/lib/commonjs/ui/primitives.js.map +1 -0
  43. package/lib/commonjs/ui/tmTheme.js +103 -0
  44. package/lib/commonjs/ui/tmTheme.js.map +1 -0
  45. package/lib/module/components/TimeMachineBody.js +150 -680
  46. package/lib/module/components/TimeMachineBody.js.map +1 -1
  47. package/lib/module/components/TimeMachineModal.js +11 -12
  48. package/lib/module/components/TimeMachineModal.js.map +1 -1
  49. package/lib/module/components/confirm.js +34 -0
  50. package/lib/module/components/confirm.js.map +1 -0
  51. package/lib/module/components/format.js +19 -0
  52. package/lib/module/components/format.js.map +1 -0
  53. package/lib/module/components/list/CaptureBar.js +90 -0
  54. package/lib/module/components/list/CaptureBar.js.map +1 -0
  55. package/lib/module/components/list/RestoreOutcomeCard.js +156 -0
  56. package/lib/module/components/list/RestoreOutcomeCard.js.map +1 -0
  57. package/lib/module/components/list/SnapshotRow.js +138 -0
  58. package/lib/module/components/list/SnapshotRow.js.map +1 -0
  59. package/lib/module/components/list/SourcesStrip.js +121 -0
  60. package/lib/module/components/list/SourcesStrip.js.map +1 -0
  61. package/lib/module/components/list/UndoDeleteBar.js +78 -0
  62. package/lib/module/components/list/UndoDeleteBar.js.map +1 -0
  63. package/lib/module/components/list/WipeAllRow.js +41 -0
  64. package/lib/module/components/list/WipeAllRow.js.map +1 -0
  65. package/lib/module/components/preview/ItemDiffPanel.js +72 -0
  66. package/lib/module/components/preview/ItemDiffPanel.js.map +1 -0
  67. package/lib/module/components/preview/PreviewFlow.js +46 -0
  68. package/lib/module/components/preview/PreviewFlow.js.map +1 -0
  69. package/lib/module/components/preview/RestorePointNameRow.js +78 -0
  70. package/lib/module/components/preview/RestorePointNameRow.js.map +1 -0
  71. package/lib/module/components/preview/RestorePreviewScreen.js +552 -638
  72. package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -1
  73. package/lib/module/components/preview/verdictMeta.js +9 -8
  74. package/lib/module/components/preview/verdictMeta.js.map +1 -1
  75. package/lib/module/components/sources.js +66 -0
  76. package/lib/module/components/sources.js.map +1 -0
  77. package/lib/module/store/snapshotVault.js +87 -9
  78. package/lib/module/store/snapshotVault.js.map +1 -1
  79. package/lib/module/store/timeMachineStore.js +125 -7
  80. package/lib/module/store/timeMachineStore.js.map +1 -1
  81. package/lib/module/sync/timeMachineSyncAdapter.js +41 -0
  82. package/lib/module/sync/timeMachineSyncAdapter.js.map +1 -1
  83. package/lib/module/ui/SwipeActionsRow.js +162 -0
  84. package/lib/module/ui/SwipeActionsRow.js.map +1 -0
  85. package/lib/module/ui/primitives.js +156 -0
  86. package/lib/module/ui/primitives.js.map +1 -0
  87. package/lib/module/ui/tmTheme.js +99 -0
  88. package/lib/module/ui/tmTheme.js.map +1 -0
  89. package/lib/typescript/components/TimeMachineBody.d.ts.map +1 -1
  90. package/lib/typescript/components/TimeMachineModal.d.ts.map +1 -1
  91. package/lib/typescript/components/confirm.d.ts +2 -0
  92. package/lib/typescript/components/confirm.d.ts.map +1 -0
  93. package/lib/typescript/components/format.d.ts +4 -0
  94. package/lib/typescript/components/format.d.ts.map +1 -0
  95. package/lib/typescript/components/list/CaptureBar.d.ts +6 -0
  96. package/lib/typescript/components/list/CaptureBar.d.ts.map +1 -0
  97. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts +11 -0
  98. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +1 -0
  99. package/lib/typescript/components/list/SnapshotRow.d.ts +12 -0
  100. package/lib/typescript/components/list/SnapshotRow.d.ts.map +1 -0
  101. package/lib/typescript/components/list/SourcesStrip.d.ts +5 -0
  102. package/lib/typescript/components/list/SourcesStrip.d.ts.map +1 -0
  103. package/lib/typescript/components/list/UndoDeleteBar.d.ts +8 -0
  104. package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +1 -0
  105. package/lib/typescript/components/list/WipeAllRow.d.ts +5 -0
  106. package/lib/typescript/components/list/WipeAllRow.d.ts.map +1 -0
  107. package/lib/typescript/components/preview/ItemDiffPanel.d.ts +5 -0
  108. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +1 -0
  109. package/lib/typescript/components/preview/PreviewFlow.d.ts +9 -0
  110. package/lib/typescript/components/preview/PreviewFlow.d.ts.map +1 -0
  111. package/lib/typescript/components/preview/RestorePointNameRow.d.ts +5 -0
  112. package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +1 -0
  113. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts +1 -5
  114. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -1
  115. package/lib/typescript/components/preview/verdictMeta.d.ts +5 -0
  116. package/lib/typescript/components/preview/verdictMeta.d.ts.map +1 -1
  117. package/lib/typescript/components/sources.d.ts +36 -0
  118. package/lib/typescript/components/sources.d.ts.map +1 -0
  119. package/lib/typescript/store/snapshotVault.d.ts +48 -0
  120. package/lib/typescript/store/snapshotVault.d.ts.map +1 -1
  121. package/lib/typescript/store/timeMachineStore.d.ts +35 -1
  122. package/lib/typescript/store/timeMachineStore.d.ts.map +1 -1
  123. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +17 -0
  124. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +1 -1
  125. package/lib/typescript/ui/SwipeActionsRow.d.ts +32 -0
  126. package/lib/typescript/ui/SwipeActionsRow.d.ts.map +1 -0
  127. package/lib/typescript/ui/primitives.d.ts +57 -0
  128. package/lib/typescript/ui/primitives.d.ts.map +1 -0
  129. package/lib/typescript/ui/tmTheme.d.ts +67 -0
  130. package/lib/typescript/ui/tmTheme.d.ts.map +1 -0
  131. package/package.json +7 -7
  132. package/src/components/TimeMachineBody.tsx +183 -817
  133. package/src/components/TimeMachineModal.tsx +10 -11
  134. package/src/components/confirm.ts +35 -0
  135. package/src/components/format.ts +17 -0
  136. package/src/components/list/CaptureBar.tsx +100 -0
  137. package/src/components/list/RestoreOutcomeCard.tsx +159 -0
  138. package/src/components/list/SnapshotRow.tsx +155 -0
  139. package/src/components/list/SourcesStrip.tsx +124 -0
  140. package/src/components/list/UndoDeleteBar.tsx +76 -0
  141. package/src/components/list/WipeAllRow.tsx +42 -0
  142. package/src/components/preview/ItemDiffPanel.tsx +77 -0
  143. package/src/components/preview/PreviewFlow.tsx +70 -0
  144. package/src/components/preview/RestorePointNameRow.tsx +80 -0
  145. package/src/components/preview/RestorePreviewScreen.tsx +535 -535
  146. package/src/components/preview/verdictMeta.ts +8 -8
  147. package/src/components/sources.ts +92 -0
  148. package/src/store/snapshotVault.ts +131 -9
  149. package/src/store/timeMachineStore.ts +136 -5
  150. package/src/sync/timeMachineSyncAdapter.ts +30 -0
  151. package/src/ui/SwipeActionsRow.tsx +199 -0
  152. package/src/ui/primitives.tsx +157 -0
  153. package/src/ui/tmTheme.ts +107 -0
  154. package/lib/commonjs/components/preview/ItemDetailScreen.js +0 -189
  155. package/lib/commonjs/components/preview/ItemDetailScreen.js.map +0 -1
  156. package/lib/module/components/preview/ItemDetailScreen.js +0 -184
  157. package/lib/module/components/preview/ItemDetailScreen.js.map +0 -1
  158. package/lib/typescript/components/preview/ItemDetailScreen.d.ts +0 -11
  159. package/lib/typescript/components/preview/ItemDetailScreen.d.ts.map +0 -1
  160. package/src/components/preview/ItemDetailScreen.tsx +0 -172
@@ -1,726 +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, RoutesIcon, 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 { canNavigate, currentRoute } from "../store/routeRestore";
21
- import { RestorePreviewScreen } from "./preview/RestorePreviewScreen";
22
- import { ItemDetailScreen } from "./preview/ItemDetailScreen";
23
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
24
- const FREE_SNAPSHOT_LIMIT = 2;
25
- const SOURCE_ICONS = {
26
- storage: StorageIcon,
27
- redux: ReduxIcon,
28
- zustand: ZustandIcon,
29
- jotai: JotaiIcon,
30
- query: QueryIcon,
31
- route: RoutesIcon
32
- };
33
-
34
- /** Id of the route tile — a device capability, not a snapshot provider. */
35
- const ROUTE_SOURCE_ID = "route";
36
- function timeAgo(ts) {
37
- const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
38
- if (s < 60) return `${s}s ago`;
39
- const m = Math.round(s / 60);
40
- if (m < 60) return `${m}m ago`;
41
- const h = Math.round(m / 60);
42
- if (h < 24) return `${h}h ago`;
43
- return `${Math.round(h / 24)}d ago`;
44
- }
45
- function formatBytes(bytes) {
46
- if (bytes < 1024) return `${bytes} B`;
47
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
48
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
49
- }
50
- function warningCount(meta) {
51
- return Object.values(meta.sources).reduce((sum, s) => sum + s.warnings.length, 0);
52
- }
53
-
54
- /** RN-web's Alert renders no buttons — fall back to the browser dialog there. */
55
- function confirmDestructive(title, message, confirmLabel, onConfirm) {
56
- if (Platform.OS === "web") {
57
- const confirmFn = globalThis.confirm;
58
- if (!confirmFn || confirmFn(`${title}\n\n${message}`)) onConfirm();
59
- return;
60
- }
61
- Alert.alert(title, message, [{
62
- text: "Cancel",
63
- style: "cancel"
64
- }, {
65
- text: confirmLabel,
66
- style: "destructive",
67
- onPress: onConfirm
68
- }]);
69
- }
70
-
71
- /**
72
- * "Also go back to the screen this was captured on?"
73
- *
74
- * Asked BEFORE the restore runs, because a reload-mode restore has to park the
75
- * route in storage before the JS realm dies — there is no "after" to ask in.
76
- * `onChoose(withRoute)` runs the restore; dismissing cancels it entirely.
77
- *
78
- * RN-web's Alert renders no buttons, so the web (Buoy Desktop) path uses the
79
- * browser dialog: OK = state + route, Cancel = state only.
80
- */
81
- function confirmRestoreRoute(snapshotName, href, onChoose) {
82
- const message = `“${snapshotName}” was captured on ${href}. Go back to that screen too, or restore state only?`;
83
- if (Platform.OS === "web") {
84
- const confirmFn = globalThis.confirm;
85
- onChoose(confirmFn ? confirmFn(`Also return to ${href}?\n\n${message}`) : false);
86
- return;
87
- }
88
- Alert.alert("Restore route too?", message, [{
89
- text: "Cancel",
90
- style: "cancel"
91
- }, {
92
- text: "State only",
93
- onPress: () => onChoose(false)
94
- }, {
95
- text: `Go to ${href}`,
96
- onPress: () => onChoose(true)
97
- }]);
98
- }
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
+ };
99
51
 
100
- /**
101
- * Route leg of a restore: prompt when the snapshot has a captured route we
102
- * aren't already sitting on, otherwise restore straight away. On Buoy Desktop
103
- * the device's live route is unknown (no local router), so we always ask.
104
- */
105
- function restoreWithRoutePrompt(meta, run) {
106
- const route = meta.route;
107
- if (!route) {
108
- run(false);
109
- return;
110
- }
111
- if (currentRoute()?.href === route.href) {
112
- run(false); // already on that screen — nothing to ask
113
- return;
114
- }
115
- confirmRestoreRoute(meta.name, route.href, run);
116
- }
117
- function OutcomePanel({
118
- outcome
119
- }) {
120
- const entries = Object.entries(outcome.results);
121
- return /*#__PURE__*/_jsxs(View, {
122
- style: styles.outcomePanel,
123
- children: [/*#__PURE__*/_jsxs(View, {
124
- style: styles.outcomeHeaderRow,
125
- children: [/*#__PURE__*/_jsxs(Text, {
126
- style: styles.outcomeTitle,
127
- children: ["Restored \u201C", outcome.snapshotName, "\u201D", outcome.mode === "reload" || outcome.willReload ? " · reloading" : ""]
128
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
129
- onPress: () => timeMachineStore.clearLastOutcome(),
130
- hitSlop: {
131
- top: 8,
132
- bottom: 8,
133
- left: 8,
134
- right: 8
135
- },
136
- children: /*#__PURE__*/_jsx(Text, {
137
- style: styles.outcomeDismiss,
138
- children: "\u2715"
139
- })
140
- })]
141
- }), entries.length === 0 && /*#__PURE__*/_jsx(Text, {
142
- style: styles.outcomeLine,
143
- children: "no sources restored"
144
- }), outcome.route && /*#__PURE__*/_jsx(Text, {
145
- style: [styles.outcomeLine, (outcome.route.status === "failed" || outcome.route.status === "unavailable") && styles.outcomeLineBad],
146
- children: outcome.route.status === "navigated" ? `✓ route — ${outcome.route.href}` : outcome.route.status === "deferred" ? `↻ route — ${outcome.route.href} after reload` : `✕ route — ${outcome.route.error ?? outcome.route.status}`
147
- }), entries.map(([sourceId, r]) => /*#__PURE__*/_jsxs(View, {
148
- style: styles.outcomeSource,
149
- children: [/*#__PURE__*/_jsxs(Text, {
150
- style: [styles.outcomeLine, !r.ok && styles.outcomeLineBad],
151
- children: [r.ok ? "✓" : "✕", " ", sourceId, " \u2014 applied ", r.applied, r.skipped.length > 0 ? `, skipped ${r.skipped.length}` : ""]
152
- }), r.skipped.slice(0, 3).map(skip => /*#__PURE__*/_jsxs(Text, {
153
- style: styles.outcomeDetail,
154
- children: ["\xB7 ", skip.id, ": ", skip.reason]
155
- }, skip.id)), r.warnings.slice(0, 3).map(warning => /*#__PURE__*/_jsxs(Text, {
156
- style: styles.outcomeDetail,
157
- children: ["\u26A0 ", warning]
158
- }, warning))]
159
- }, 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
+ }
160
70
  });
161
71
  }
162
- export function TimeMachineBody() {
72
+ function RestorePointList({
73
+ sources,
74
+ onOpenPreview
75
+ }) {
163
76
  const {
164
77
  snapshots,
165
78
  hasLoaded,
166
79
  busy,
167
80
  lastOutcome,
168
- preview,
169
- remoteProviders,
170
- remoteRoute
81
+ recentlyDeleted
171
82
  } = useTimeMachineState();
172
- const localProviders = useSnapshotProviders();
173
83
  const isPro = useIsPro();
84
+ const snapshotLimit = useLimit("timeMachine.snapshots");
174
85
  const host = useToolHost();
175
- const [name, setName] = useState("");
176
- const [screen, setScreen] = useState({
177
- kind: "list"
86
+ const insets = useSafeAreaInsets({
87
+ minBottom: 16
178
88
  });
179
- const [picker, setPicker] = useState(null);
180
89
 
181
- // Header search (when hosted by the core shell) filters the snapshot list.
182
- const listQuery = host.search.trim().toLowerCase();
183
- 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;
184
93
 
185
- // On-device: live registry probes. Buoy Desktop: statuses mirrored from
186
- // the device's sync payload.
187
- const providers = remoteProviders ?? localProviders.map(p => ({
188
- id: p.id,
189
- label: p.label,
190
- ...p.status()
191
- }));
94
+ // Infinity for Pro, so this is simply false for them.
95
+ const atCaptureLimit = snapshots.length >= snapshotLimit;
96
+ const captureDisabled = busy !== null || atCaptureLimit;
192
97
 
193
- // Route sits in the same strip as the state sources so its availability is
194
- // visible before you ever open a restore point — but it's a device
195
- // capability, not a registered provider, so it's derived separately (from
196
- // the live router here, from the device's payload on Buoy Desktop).
197
- const routeAvailable = remoteProviders ? remoteRoute?.available === true : canNavigate();
198
- const routeSource = {
199
- id: ROUTE_SOURCE_ID,
200
- label: "Route",
201
- canCapture: routeAvailable,
202
- canRestore: routeAvailable,
203
- // No live route in the caption: this screen only re-renders on store
204
- // changes, so a pathname printed here goes stale the moment you navigate
205
- // with the tool still open. Each restore point shows the route it holds.
206
- detail: routeAvailable ? "recorded with every snapshot — restore can take you back" : "no Expo Router detected — snapshots record no route"
207
- };
208
- const sourceTiles = [...providers, routeSource];
209
- const atFreeLimit = !isPro && snapshots.length >= FREE_SNAPSHOT_LIMIT;
210
- const captureDisabled = busy !== null || atFreeLimit;
211
- const capture = () => {
212
- if (captureDisabled) return;
213
- void timeMachineStore.capture(name);
214
- setName("");
215
- };
216
- const restore = (meta, mode, excludeKeys) => {
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 => {
217
110
  if (busy) return;
218
111
  if (meta.baseline) {
219
112
  confirmDestructive("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void timeMachineStore.restore(meta.id, "reload"));
220
113
  return;
221
114
  }
222
- restoreWithRoutePrompt(meta, restoreRoute => {
223
- void timeMachineStore.restore(meta.id, mode, excludeKeys, {
224
- restoreRoute
225
- });
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();
226
119
  });
227
120
  };
228
- const remove = meta => {
229
- confirmDestructive("Delete snapshot?", `“${meta.name}” cannot be recovered.`, "Delete", () => void timeMachineStore.delete(meta.id));
230
- };
231
- const wipeAll = () => {
232
- if (busy) return;
233
- confirmDestructive("Wipe all app state?", "This deletes all app storage and reloads the app — like a fresh install.", "Wipe & reload", () => void timeMachineStore.wipeAll());
234
- };
235
- const openPreview = meta => {
236
- void timeMachineStore.buildPreview({
237
- snapshotId: meta.id
238
- });
239
- setScreen({
240
- kind: "preview"
241
- });
242
- };
243
- const closePreview = () => {
244
- timeMachineStore.clearPreview();
245
- setScreen({
246
- kind: "list"
247
- });
248
- };
249
-
250
- // ── Preview / item screens ──
251
- if (screen.kind === "item" && preview) {
252
- return /*#__PURE__*/_jsx(ItemDetailScreen, {
253
- sourceLabel: screen.sourceLabel,
254
- item: screen.item,
255
- leftLabel: preview.left === "current" ? "CURRENT" : snapshots.find(s => s.id === preview.left.snapshotId)?.name ?? "snapshot",
256
- rightLabel: preview.right === "current" ? "CURRENT" : snapshots.find(s => s.id === preview.right.snapshotId)?.name ?? "snapshot",
257
- onBack: () => setScreen({
258
- kind: "preview"
259
- })
260
- });
261
- }
262
- if (screen.kind === "preview" && preview) {
263
- const rightMeta = preview.right === "current" ? undefined : snapshots.find(s => s.id === preview.right.snapshotId);
264
- const pickerItems = [];
265
- if (picker) {
266
- const otherRef = picker === "left" ? preview.right : preview.left;
267
- const otherId = otherRef === "current" ? null : otherRef.snapshotId;
268
- if (picker === "left") {
269
- pickerItems.push({
270
- index: 0,
271
- label: "Current state",
272
- timestamp: "live",
273
- relativeTime: "now",
274
- selected: preview.left === "current"
275
- });
276
- }
277
- snapshots.forEach((s, i) => {
278
- const ref = {
279
- snapshotId: s.id
280
- };
281
- const current = picker === "left" ? preview.left : preview.right;
282
- pickerItems.push({
283
- index: picker === "left" ? i + 1 : i,
284
- label: s.baseline ? `⌂ ${s.name}` : s.name,
285
- timestamp: new Date(s.createdAt).toLocaleTimeString(),
286
- relativeTime: timeAgo(s.createdAt),
287
- disabled: s.id === otherId || picker === "right" && s.baseline,
288
- selected: current !== "current" && current.snapshotId === s.id
289
- });
290
- });
291
- }
292
- const onPickerSelect = index => {
293
- const side = picker;
294
- setPicker(null);
295
- if (!side) return;
296
- let ref;
297
- if (side === "left") {
298
- ref = index === 0 ? "current" : {
299
- snapshotId: snapshots[index - 1].id
300
- };
301
- void timeMachineStore.buildPreview(preview.right, ref);
302
- } else {
303
- ref = {
304
- snapshotId: snapshots[index].id
305
- };
306
- void timeMachineStore.buildPreview(ref, preview.left);
307
- }
308
- };
309
- return /*#__PURE__*/_jsxs(View, {
310
- style: styles.screenFill,
311
- children: [/*#__PURE__*/_jsx(RestorePreviewScreen, {
312
- preview: preview,
313
- snapshots: snapshots,
314
- busy: busy !== null,
315
- onBack: closePreview,
316
- onOpenItem: (sourceId, item) => {
317
- const source = preview.sources.find(s => s.id === sourceId);
318
- setScreen({
319
- kind: "item",
320
- sourceId,
321
- sourceLabel: source?.label ?? sourceId,
322
- item
323
- });
324
- },
325
- onRestore: (mode, excludeKeys) => {
326
- if (!rightMeta || busy) return;
327
- if (rightMeta.baseline) {
328
- confirmDestructive("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void timeMachineStore.restore(rightMeta.id, "reload"));
329
- return;
330
- }
331
- // Land back on the list, where the outcome panel reports the
332
- // results — the preview's diff is stale the moment restore runs.
333
- restoreWithRoutePrompt(rightMeta, restoreRoute => {
334
- void timeMachineStore.restore(rightMeta.id, mode, excludeKeys, {
335
- restoreRoute
336
- }).then(() => closePreview());
337
- });
338
- },
339
- onPickSide: setPicker,
340
- onRefresh: () => void timeMachineStore.buildPreview(preview.right, preview.left)
341
- }), picker && /*#__PURE__*/_jsx(EventPickerModal, {
342
- isOpen: true,
343
- onClose: () => setPicker(null),
344
- title: picker === "left" ? "Compare from" : "Compare to",
345
- position: picker,
346
- items: pickerItems,
347
- onSelect: onPickerSelect
348
- })]
349
- });
350
- }
351
-
352
- // ── List screen ──
353
- const unavailable = sourceTiles.filter(p => p.detail && (!p.canCapture || !p.canRestore));
354
121
  return /*#__PURE__*/_jsxs(ScrollView, {
355
- style: styles.screenFill,
356
- contentContainerStyle: styles.container,
357
- 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, {
358
127
  outcome: lastOutcome
359
- }), /*#__PURE__*/_jsxs(View, {
360
- style: styles.captureCard,
361
- children: [/*#__PURE__*/_jsxs(View, {
362
- style: styles.captureField,
363
- children: [/*#__PURE__*/_jsx(Plus, {
364
- size: 14,
365
- color: buoyColors.textMuted
366
- }), /*#__PURE__*/_jsx(TextInput, {
367
- style: styles.captureInput,
368
- value: name,
369
- onChangeText: setName,
370
- placeholder: "Name this restore point\u2026",
371
- placeholderTextColor: buoyColors.textMuted,
372
- editable: !captureDisabled,
373
- onSubmitEditing: capture,
374
- returnKeyType: "done",
375
- testID: "time-machine-name-input"
376
- })]
377
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
378
- style: [styles.captureButton, captureDisabled && styles.buttonDisabled],
379
- 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, {
380
135
  disabled: captureDisabled,
381
- testID: "time-machine-capture",
382
- children: /*#__PURE__*/_jsx(Text, {
383
- style: styles.captureButtonText,
384
- children: busy === "capture" ? "…" : "CAPTURE"
385
- })
386
- })]
387
- }), atFreeLimit && /*#__PURE__*/_jsxs(Text, {
388
- style: styles.freeLimitNote,
389
- children: ["Free plan: ", FREE_SNAPSHOT_LIMIT, " restore points. Upgrade to Pro for unlimited snapshots."]
390
- }), /*#__PURE__*/_jsxs(View, {
391
- style: styles.sectionRow,
392
- children: [/*#__PURE__*/_jsx(Text, {
393
- style: styles.sectionTitle,
394
- children: "Sources"
395
- }), /*#__PURE__*/_jsxs(View, {
396
- style: styles.sourceTiles,
397
- children: [sourceTiles.map(p => {
398
- const IconComp = SOURCE_ICONS[p.id] ?? TimeMachineIcon;
399
- const dotColor = !p.canCapture ? buoyColors.textMuted : p.canRestore ? buoyColors.success : buoyColors.warning;
400
- return /*#__PURE__*/_jsxs(View, {
401
- style: styles.sourceTile,
402
- testID: `time-machine-source-${p.id}`,
403
- accessibilityLabel: `${p.label}: ${p.canCapture ? p.canRestore ? "capture and restore" : "capture only" : "unavailable"}`,
404
- children: [/*#__PURE__*/_jsx(IconComp, {
405
- size: 16
406
- }), /*#__PURE__*/_jsx(View, {
407
- style: [styles.sourceTileDot, {
408
- backgroundColor: dotColor
409
- }]
410
- })]
411
- }, p.id);
412
- }), providers.length === 0 && !routeAvailable && /*#__PURE__*/_jsx(Text, {
413
- style: styles.emptyText,
414
- children: "none registered"
415
- })]
416
- })]
417
- }), unavailable.map(p => /*#__PURE__*/_jsxs(Text, {
418
- style: styles.providerDetail,
419
- children: [p.label, ": ", p.detail]
420
- }, p.id)), routeAvailable && /*#__PURE__*/_jsxs(Text, {
421
- style: styles.providerDetail,
422
- numberOfLines: 1,
423
- children: [routeSource.label, ": ", routeSource.detail]
424
- }), /*#__PURE__*/_jsx(Text, {
425
- style: [styles.sectionTitle, styles.sectionSpacing],
426
- children: "Restore points"
427
- }), hasLoaded && snapshots.length === 0 && /*#__PURE__*/_jsx(Text, {
428
- style: styles.emptyText,
429
- children: "No restore points yet. Set up the state you want to return to, then capture it."
430
- }), visibleSnapshots.map(meta => {
431
- const warnings = warningCount(meta);
432
- return /*#__PURE__*/_jsxs(TouchableOpacity, {
433
- style: styles.snapshotCard,
434
- onPress: () => openPreview(meta),
435
- testID: `time-machine-snap-${meta.id}`,
436
- children: [/*#__PURE__*/_jsx(View, {
437
- style: styles.snapshotIconTile,
438
- children: meta.baseline ? /*#__PURE__*/_jsx(Home, {
439
- size: 17,
440
- color: buoyColors.textSecondary
441
- }) : /*#__PURE__*/_jsx(TimeMachineIcon, {
442
- size: 18
443
- })
444
- }), /*#__PURE__*/_jsxs(View, {
445
- style: styles.snapshotInfo,
446
- children: [/*#__PURE__*/_jsx(Text, {
447
- style: styles.snapshotName,
448
- numberOfLines: 1,
449
- children: meta.name
450
- }), /*#__PURE__*/_jsxs(Text, {
451
- style: styles.snapshotMeta,
452
- numberOfLines: 1,
453
- 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}` : ""]
454
- }), !meta.baseline && meta.route && /*#__PURE__*/_jsxs(Text, {
455
- style: styles.snapshotRoute,
456
- numberOfLines: 1,
457
- children: ["\u2316 ", meta.route.href]
458
- })]
459
- }), !meta.baseline ? /*#__PURE__*/_jsx(TouchableOpacity, {
460
- style: [styles.rowAction, busy && styles.buttonDisabled],
461
- onPress: () => restore(meta, "live"),
462
- disabled: busy !== null,
463
- accessibilityLabel: "Restore",
464
- children: /*#__PURE__*/_jsx(Text, {
465
- style: styles.rowActionLabel,
466
- children: "RESTORE"
467
- })
468
- }) : /*#__PURE__*/_jsx(TouchableOpacity, {
469
- style: [styles.rowAction, busy && styles.buttonDisabled],
470
- onPress: () => restore(meta, "reload"),
471
- disabled: busy !== null,
472
- accessibilityLabel: "Wipe and reload",
473
- children: /*#__PURE__*/_jsx(Text, {
474
- style: styles.rowActionLabelDanger,
475
- children: "WIPE"
476
- })
477
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
478
- style: styles.rowAction,
479
- onPress: () => remove(meta),
480
- hitSlop: {
481
- top: 8,
482
- bottom: 8,
483
- left: 4,
484
- right: 8
485
- },
486
- accessibilityLabel: "Delete",
487
- children: /*#__PURE__*/_jsx(Trash2, {
488
- size: 14,
489
- color: buoyColors.error
490
- })
491
- })]
492
- }, meta.id);
493
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
494
- style: [styles.wipeAllButton, busy && styles.buttonDisabled],
495
- onPress: wipeAll,
496
- disabled: busy !== null,
497
- testID: "time-machine-wipe-all",
498
- children: [/*#__PURE__*/_jsx(Trash2, {
499
- size: 13,
500
- color: buoyColors.error
501
- }), /*#__PURE__*/_jsx(Text, {
502
- style: styles.wipeAllText,
503
- children: "Wipe all app state\u2026"
504
- })]
505
- }), /*#__PURE__*/_jsx(Text, {
506
- 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,
507
173
  children: "Deletes all app storage and reloads \u2014 like a fresh install."
508
174
  })]
509
175
  });
510
176
  }
511
177
  const styles = StyleSheet.create({
512
- screenFill: {
513
- flex: 1
514
- },
515
- container: {
516
- padding: 12,
517
- gap: 8
518
- },
519
- captureCard: {
520
- flexDirection: "row",
521
- alignItems: "center",
522
- gap: 8
523
- },
524
- captureField: {
178
+ fill: {
525
179
  flex: 1,
526
- flexDirection: "row",
527
- alignItems: "center",
528
- gap: 8,
529
- backgroundColor: buoyColors.card,
530
- borderColor: buoyColors.border,
531
- borderWidth: 1,
532
- borderRadius: 10,
533
- paddingHorizontal: 10
534
- },
535
- captureInput: {
536
- flex: 1,
537
- color: buoyColors.text,
538
- fontSize: 13,
539
- paddingVertical: 9
540
- },
541
- captureButton: {
542
- backgroundColor: buoyColors.primary,
543
- borderRadius: 10,
544
- paddingHorizontal: 16,
545
- paddingVertical: 10,
546
- justifyContent: "center"
547
- },
548
- captureButtonText: {
549
- color: "#0A0A0A",
550
- fontSize: 12,
551
- fontWeight: "700",
552
- letterSpacing: 0.5
553
- },
554
- buttonDisabled: {
555
- opacity: 0.4
556
- },
557
- freeLimitNote: {
558
- color: buoyColors.warning,
559
- fontSize: 11
560
- },
561
- sectionRow: {
562
- flexDirection: "row",
563
- alignItems: "center",
564
- justifyContent: "space-between",
565
- marginTop: 12,
566
- gap: 10
567
- },
568
- sectionTitle: {
569
- color: buoyColors.text,
570
- fontSize: 14,
571
- fontWeight: "700"
572
- },
573
- sectionSpacing: {
574
- marginTop: 14
575
- },
576
- sourceTiles: {
577
- flexDirection: "row",
578
- gap: 6,
579
- flexWrap: "wrap",
580
- justifyContent: "flex-end",
581
- flex: 1
582
- },
583
- sourceTile: {
584
- width: 32,
585
- height: 32,
586
- borderRadius: 9,
587
- backgroundColor: buoyColors.card,
588
- borderColor: buoyColors.border,
589
- borderWidth: 1,
590
- alignItems: "center",
591
- justifyContent: "center"
592
- },
593
- sourceTileDot: {
594
- position: "absolute",
595
- top: 3,
596
- right: 3,
597
- width: 6,
598
- height: 6,
599
- borderRadius: 3
600
- },
601
- providerDetail: {
602
- color: buoyColors.textMuted,
603
- fontSize: 10,
604
- marginTop: 4
605
- },
606
- emptyText: {
607
- color: buoyColors.textMuted,
608
- fontSize: 12,
609
- marginTop: 8
610
- },
611
- snapshotCard: {
612
- flexDirection: "row",
613
- alignItems: "center",
614
- backgroundColor: buoyColors.card,
615
- borderColor: buoyColors.border,
616
- borderWidth: 1,
617
- borderRadius: 12,
618
- paddingHorizontal: 12,
619
- paddingVertical: 10,
620
- marginTop: 8,
621
- gap: 10
622
- },
623
- snapshotIconTile: {
624
- width: 36,
625
- height: 36,
626
- borderRadius: 10,
627
- backgroundColor: buoyColors.hover,
628
- alignItems: "center",
629
- justifyContent: "center"
630
- },
631
- snapshotInfo: {
632
- flex: 1,
633
- minWidth: 0,
634
- gap: 2
635
- },
636
- snapshotName: {
637
- color: buoyColors.text,
638
- fontSize: 14,
639
- fontWeight: "700"
640
- },
641
- snapshotMeta: {
642
- color: buoyColors.textMuted,
643
- fontSize: 10,
644
- fontVariant: ["tabular-nums"]
645
- },
646
- snapshotRoute: {
647
- color: buoyColors.textSecondary,
648
- fontSize: 10
649
- },
650
- rowAction: {
651
- padding: 4,
652
- justifyContent: "center",
653
- alignItems: "center"
654
- },
655
- rowActionLabel: {
656
- color: buoyColors.primary,
657
- fontSize: 12,
658
- fontWeight: "600"
659
- },
660
- rowActionLabelDanger: {
661
- color: buoyColors.error,
662
- fontSize: 12,
663
- fontWeight: "600"
664
- },
665
- wipeAllButton: {
666
- flexDirection: "row",
667
- alignItems: "center",
668
- justifyContent: "center",
669
- gap: 7,
670
- borderColor: `${buoyColors.error}55`,
671
- borderWidth: 1,
672
- borderRadius: 10,
673
- paddingVertical: 10,
674
- marginTop: 18
675
- },
676
- wipeAllText: {
677
- color: buoyColors.error,
678
- fontSize: 12.5,
679
- fontWeight: "600"
680
- },
681
- wipeAllCaption: {
682
- color: buoyColors.textMuted,
683
- fontSize: 10,
684
- textAlign: "center",
685
- marginTop: 6
686
- },
687
- outcomePanel: {
688
- backgroundColor: buoyColors.card,
689
- borderColor: buoyColors.borderStrong,
690
- borderWidth: 1,
691
- borderRadius: 8,
692
- padding: 10,
693
- gap: 4
694
- },
695
- outcomeHeaderRow: {
696
- flexDirection: "row",
697
- justifyContent: "space-between",
698
- alignItems: "center"
699
- },
700
- outcomeTitle: {
701
- color: buoyColors.text,
702
- fontSize: 12,
703
- fontWeight: "700",
704
- flex: 1
705
- },
706
- outcomeDismiss: {
707
- color: buoyColors.textMuted,
708
- fontSize: 13
709
- },
710
- outcomeSource: {
711
- gap: 2
180
+ backgroundColor: tm.color.sheet
712
181
  },
713
- outcomeLine: {
714
- color: buoyColors.success,
715
- fontSize: 11
182
+ content: {
183
+ paddingTop: 14
716
184
  },
717
- outcomeLineBad: {
718
- 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
719
191
  },
720
- outcomeDetail: {
721
- color: buoyColors.textMuted,
722
- fontSize: 10,
723
- paddingLeft: 12
192
+ spacer: {
193
+ height: 24
724
194
  }
725
195
  });
726
196
  //# sourceMappingURL=TimeMachineBody.js.map