@buoy-gg/time-machine 7.0.41 → 7.0.42

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 (172) hide show
  1. package/lib/commonjs/components/HeaderActions.js +1 -91
  2. package/lib/commonjs/components/HeaderReloadButton.js +1 -58
  3. package/lib/commonjs/components/TimeMachineBody.js +1 -282
  4. package/lib/commonjs/components/TimeMachineModal.js +1 -74
  5. package/lib/commonjs/components/confirm.js +2 -38
  6. package/lib/commonjs/components/floating/NamePromptModal.js +1 -193
  7. package/lib/commonjs/components/floating/TimeMachineFloatingTool.js +1 -715
  8. package/lib/commonjs/components/floating/armedPoint.js +1 -92
  9. package/lib/commonjs/components/floating/barSettings.js +1 -66
  10. package/lib/commonjs/components/format.js +1 -24
  11. package/lib/commonjs/components/list/CaptureBar.js +1 -97
  12. package/lib/commonjs/components/list/RestoreOutcomeCard.js +1 -175
  13. package/lib/commonjs/components/list/SnapshotRow.js +1 -170
  14. package/lib/commonjs/components/list/SourcesStrip.js +1 -172
  15. package/lib/commonjs/components/list/UndoDeleteBar.js +1 -85
  16. package/lib/commonjs/components/list/WipeAllRow.js +1 -46
  17. package/lib/commonjs/components/preview/ItemDiffPanel.js +1 -79
  18. package/lib/commonjs/components/preview/PreviewFlow.js +1 -51
  19. package/lib/commonjs/components/preview/RestorePointNameRow.js +1 -83
  20. package/lib/commonjs/components/preview/RestorePreviewScreen.js +1 -1010
  21. package/lib/commonjs/components/preview/verdictMeta.js +1 -58
  22. package/lib/commonjs/components/sources.js +1 -83
  23. package/lib/commonjs/hooks/useTimeMachine.js +1 -24
  24. package/lib/commonjs/index.js +1 -161
  25. package/lib/commonjs/preset.js +1 -56
  26. package/lib/commonjs/store/routeRestore.js +1 -188
  27. package/lib/commonjs/store/snapshotVault.js +1 -186
  28. package/lib/commonjs/store/sourceIds.js +1 -9
  29. package/lib/commonjs/store/sourceSettings.js +1 -79
  30. package/lib/commonjs/store/timeMachineStore.js +1 -848
  31. package/lib/commonjs/sync/timeMachineSyncAdapter.js +1 -258
  32. package/lib/commonjs/ui/SwipeActionsRow.js +1 -178
  33. package/lib/commonjs/ui/primitives.js +1 -172
  34. package/lib/commonjs/ui/tmTheme.js +1 -118
  35. package/lib/module/components/HeaderActions.js +1 -85
  36. package/lib/module/components/HeaderReloadButton.js +1 -53
  37. package/lib/module/components/TimeMachineBody.js +1 -277
  38. package/lib/module/components/TimeMachineModal.js +1 -68
  39. package/lib/module/components/confirm.js +2 -33
  40. package/lib/module/components/floating/NamePromptModal.js +1 -188
  41. package/lib/module/components/floating/TimeMachineFloatingTool.js +1 -708
  42. package/lib/module/components/floating/armedPoint.js +1 -82
  43. package/lib/module/components/floating/barSettings.js +1 -57
  44. package/lib/module/components/format.js +1 -19
  45. package/lib/module/components/list/CaptureBar.js +1 -92
  46. package/lib/module/components/list/RestoreOutcomeCard.js +1 -169
  47. package/lib/module/components/list/SnapshotRow.js +1 -167
  48. package/lib/module/components/list/SourcesStrip.js +1 -169
  49. package/lib/module/components/list/UndoDeleteBar.js +1 -82
  50. package/lib/module/components/list/WipeAllRow.js +1 -41
  51. package/lib/module/components/preview/ItemDiffPanel.js +1 -74
  52. package/lib/module/components/preview/PreviewFlow.js +1 -46
  53. package/lib/module/components/preview/RestorePointNameRow.js +1 -78
  54. package/lib/module/components/preview/RestorePreviewScreen.js +1 -1005
  55. package/lib/module/components/preview/verdictMeta.js +1 -54
  56. package/lib/module/components/sources.js +1 -73
  57. package/lib/module/hooks/useTimeMachine.js +1 -19
  58. package/lib/module/index.js +1 -46
  59. package/lib/module/preset.js +1 -51
  60. package/lib/module/store/routeRestore.js +1 -176
  61. package/lib/module/store/snapshotVault.js +1 -170
  62. package/lib/module/store/sourceIds.js +1 -5
  63. package/lib/module/store/sourceSettings.js +1 -69
  64. package/lib/module/store/timeMachineStore.js +1 -839
  65. package/lib/module/sync/timeMachineSyncAdapter.js +1 -253
  66. package/lib/module/ui/SwipeActionsRow.js +1 -173
  67. package/lib/module/ui/primitives.js +1 -164
  68. package/lib/module/ui/tmTheme.js +1 -114
  69. package/lib/web/index.mjs +2 -3
  70. package/package.json +7 -7
  71. package/lib/commonjs/components/HeaderActions.js.map +0 -1
  72. package/lib/commonjs/components/HeaderReloadButton.js.map +0 -1
  73. package/lib/commonjs/components/TimeMachineBody.js.map +0 -1
  74. package/lib/commonjs/components/TimeMachineModal.js.map +0 -1
  75. package/lib/commonjs/components/confirm.js.map +0 -1
  76. package/lib/commonjs/components/floating/NamePromptModal.js.map +0 -1
  77. package/lib/commonjs/components/floating/TimeMachineFloatingTool.js.map +0 -1
  78. package/lib/commonjs/components/floating/armedPoint.js.map +0 -1
  79. package/lib/commonjs/components/floating/barSettings.js.map +0 -1
  80. package/lib/commonjs/components/format.js.map +0 -1
  81. package/lib/commonjs/components/list/CaptureBar.js.map +0 -1
  82. package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +0 -1
  83. package/lib/commonjs/components/list/SnapshotRow.js.map +0 -1
  84. package/lib/commonjs/components/list/SourcesStrip.js.map +0 -1
  85. package/lib/commonjs/components/list/UndoDeleteBar.js.map +0 -1
  86. package/lib/commonjs/components/list/WipeAllRow.js.map +0 -1
  87. package/lib/commonjs/components/preview/ItemDiffPanel.js.map +0 -1
  88. package/lib/commonjs/components/preview/PreviewFlow.js.map +0 -1
  89. package/lib/commonjs/components/preview/RestorePointNameRow.js.map +0 -1
  90. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +0 -1
  91. package/lib/commonjs/components/preview/verdictMeta.js.map +0 -1
  92. package/lib/commonjs/components/sources.js.map +0 -1
  93. package/lib/commonjs/hooks/useTimeMachine.js.map +0 -1
  94. package/lib/commonjs/index.js.map +0 -1
  95. package/lib/commonjs/preset.js.map +0 -1
  96. package/lib/commonjs/store/routeRestore.js.map +0 -1
  97. package/lib/commonjs/store/snapshotVault.js.map +0 -1
  98. package/lib/commonjs/store/sourceIds.js.map +0 -1
  99. package/lib/commonjs/store/sourceSettings.js.map +0 -1
  100. package/lib/commonjs/store/timeMachineStore.js.map +0 -1
  101. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +0 -1
  102. package/lib/commonjs/ui/SwipeActionsRow.js.map +0 -1
  103. package/lib/commonjs/ui/primitives.js.map +0 -1
  104. package/lib/commonjs/ui/tmTheme.js.map +0 -1
  105. package/lib/module/components/HeaderActions.js.map +0 -1
  106. package/lib/module/components/HeaderReloadButton.js.map +0 -1
  107. package/lib/module/components/TimeMachineBody.js.map +0 -1
  108. package/lib/module/components/TimeMachineModal.js.map +0 -1
  109. package/lib/module/components/confirm.js.map +0 -1
  110. package/lib/module/components/floating/NamePromptModal.js.map +0 -1
  111. package/lib/module/components/floating/TimeMachineFloatingTool.js.map +0 -1
  112. package/lib/module/components/floating/armedPoint.js.map +0 -1
  113. package/lib/module/components/floating/barSettings.js.map +0 -1
  114. package/lib/module/components/format.js.map +0 -1
  115. package/lib/module/components/list/CaptureBar.js.map +0 -1
  116. package/lib/module/components/list/RestoreOutcomeCard.js.map +0 -1
  117. package/lib/module/components/list/SnapshotRow.js.map +0 -1
  118. package/lib/module/components/list/SourcesStrip.js.map +0 -1
  119. package/lib/module/components/list/UndoDeleteBar.js.map +0 -1
  120. package/lib/module/components/list/WipeAllRow.js.map +0 -1
  121. package/lib/module/components/preview/ItemDiffPanel.js.map +0 -1
  122. package/lib/module/components/preview/PreviewFlow.js.map +0 -1
  123. package/lib/module/components/preview/RestorePointNameRow.js.map +0 -1
  124. package/lib/module/components/preview/RestorePreviewScreen.js.map +0 -1
  125. package/lib/module/components/preview/verdictMeta.js.map +0 -1
  126. package/lib/module/components/sources.js.map +0 -1
  127. package/lib/module/hooks/useTimeMachine.js.map +0 -1
  128. package/lib/module/index.js.map +0 -1
  129. package/lib/module/preset.js.map +0 -1
  130. package/lib/module/store/routeRestore.js.map +0 -1
  131. package/lib/module/store/snapshotVault.js.map +0 -1
  132. package/lib/module/store/sourceIds.js.map +0 -1
  133. package/lib/module/store/sourceSettings.js.map +0 -1
  134. package/lib/module/store/timeMachineStore.js.map +0 -1
  135. package/lib/module/sync/timeMachineSyncAdapter.js.map +0 -1
  136. package/lib/module/ui/SwipeActionsRow.js.map +0 -1
  137. package/lib/module/ui/primitives.js.map +0 -1
  138. package/lib/module/ui/tmTheme.js.map +0 -1
  139. package/lib/typescript/components/HeaderActions.d.ts.map +0 -1
  140. package/lib/typescript/components/HeaderReloadButton.d.ts.map +0 -1
  141. package/lib/typescript/components/TimeMachineBody.d.ts.map +0 -1
  142. package/lib/typescript/components/TimeMachineModal.d.ts.map +0 -1
  143. package/lib/typescript/components/confirm.d.ts.map +0 -1
  144. package/lib/typescript/components/floating/NamePromptModal.d.ts.map +0 -1
  145. package/lib/typescript/components/floating/TimeMachineFloatingTool.d.ts.map +0 -1
  146. package/lib/typescript/components/floating/armedPoint.d.ts.map +0 -1
  147. package/lib/typescript/components/floating/barSettings.d.ts.map +0 -1
  148. package/lib/typescript/components/format.d.ts.map +0 -1
  149. package/lib/typescript/components/list/CaptureBar.d.ts.map +0 -1
  150. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +0 -1
  151. package/lib/typescript/components/list/SnapshotRow.d.ts.map +0 -1
  152. package/lib/typescript/components/list/SourcesStrip.d.ts.map +0 -1
  153. package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +0 -1
  154. package/lib/typescript/components/list/WipeAllRow.d.ts.map +0 -1
  155. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +0 -1
  156. package/lib/typescript/components/preview/PreviewFlow.d.ts.map +0 -1
  157. package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +0 -1
  158. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +0 -1
  159. package/lib/typescript/components/preview/verdictMeta.d.ts.map +0 -1
  160. package/lib/typescript/components/sources.d.ts.map +0 -1
  161. package/lib/typescript/hooks/useTimeMachine.d.ts.map +0 -1
  162. package/lib/typescript/index.d.ts.map +0 -1
  163. package/lib/typescript/preset.d.ts.map +0 -1
  164. package/lib/typescript/store/routeRestore.d.ts.map +0 -1
  165. package/lib/typescript/store/snapshotVault.d.ts.map +0 -1
  166. package/lib/typescript/store/sourceIds.d.ts.map +0 -1
  167. package/lib/typescript/store/sourceSettings.d.ts.map +0 -1
  168. package/lib/typescript/store/timeMachineStore.d.ts.map +0 -1
  169. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +0 -1
  170. package/lib/typescript/ui/SwipeActionsRow.d.ts.map +0 -1
  171. package/lib/typescript/ui/primitives.d.ts.map +0 -1
  172. package/lib/typescript/ui/tmTheme.d.ts.map +0 -1
@@ -1,708 +1 @@
1
- "use strict";
2
-
3
- /**
4
- * The restore bar: a floating strip that stays over the app with one restore
5
- * point armed, so "go back to A" is one tap instead of dial → tool → RESTORE.
6
- *
7
- * Same shell and shape as Network's throttling strip. Left to right: a
8
- * "Restore" button, the armed point's name over a status line (the drag
9
- * surface), a chevron for the picker, a capture button, and ×. Close only
10
- * dismisses; an armed point is not a running effect, so there is nothing to
11
- * switch off.
12
- *
13
- * Safety: every restore from here first writes the rolling "Before last
14
- * restore" snapshot, and the status line offers Undo for ten seconds. A
15
- * control meant to be tapped without looking has to be safe to mis-tap.
16
- *
17
- * Feedback follows the list's rule: a clean restore gets no toast, because
18
- * the restored app is the confirmation. The button flashes a check so a tap
19
- * on an already-matching screen still visibly did something. Only a partial
20
- * restore, an error, or the tier cap opens the accessory panel under the
21
- * strip.
22
- */
23
- import { useEffect, useRef, useState, useSyncExternalStore } from "react";
24
- import { ActivityIndicator, ScrollView, StyleSheet, Text, TouchableOpacity, View, useWindowDimensions } from "react-native";
25
- import { Check, ChevronDown, ChevronUp, FloatingTool, NightSwitch, Plus, TimeMachineIcon, X, night, openFloatingTool, persistentStorage, registerPersistentFloatingTool, useLimit } from "@buoy-gg/shared-ui";
26
- import { useSnapshotProviders, useTimeMachineState } from "../../hooks/useTimeMachine";
27
- import { timeMachineStore, willRestoreRoute } from "../../store/timeMachineStore";
28
- import { captureCurrentRoute } from "../../store/routeRestore";
29
- import { isSourceEnabled } from "../../store/sourceSettings";
30
- import { restoreNeedsAttention } from "../list/RestoreOutcomeCard";
31
- import { timeAgo } from "../format";
32
- import { ensurePickedPointLoaded, getPickedPointId, resolveArmedPoint, setPickedPointId, subscribePickedPoint } from "./armedPoint";
33
- import { NamePromptModal } from "./NamePromptModal";
34
- import { ensureBarSettingsLoaded, getBarReloadMode, setBarReloadMode, subscribeBarSettings } from "./barSettings";
35
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
36
- export const TIME_MACHINE_FLOATING_TOOL_ID = "time-machine-restore";
37
-
38
- /** How long "just now" and Undo stay on offer. */
39
- const RECENT_MS = 10_000;
40
- const FLASH_MS = 900;
41
- const PICKER_ROWS = 6;
42
- const registration = {
43
- id: TIME_MACHINE_FLOATING_TOOL_ID,
44
- render: context => /*#__PURE__*/_jsx(TimeMachineFloatingTool, {
45
- ...context
46
- })
47
- };
48
-
49
- // Restore an open bar on the next boot. Dev bundles on a real RN runtime
50
- // only: a production build must never re-open a devtool at launch, and the
51
- // desktop dashboard and web mirrors host this module without a device.
52
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof navigator !== "undefined" && navigator.product === "ReactNative") {
53
- registerPersistentFloatingTool(registration, persistentStorage);
54
- }
55
-
56
- /** Open (or reveal) the restore bar, optionally arming a specific point. */
57
- export function openTimeMachineFloatingTool(armId) {
58
- if (armId) setPickedPointId(armId);
59
- openFloatingTool(registration);
60
- }
61
- /** "3 of 5 sources failed", counting the route leg when it was asked for. */
62
- function failureSummary(outcome) {
63
- const results = Object.values(outcome.results);
64
- const failed = results.filter(r => !r.ok || r.skipped.length > 0).length;
65
- const total = results.length;
66
- const route = outcome.route;
67
- const routeFailed = route?.status === "failed" || route?.status === "unavailable";
68
- if (failed === 0 && routeFailed) return "Route did not restore";
69
- return `${failed} of ${total} source${total === 1 ? "" : "s"} failed`;
70
- }
71
- function failureLines(outcome) {
72
- const lines = [];
73
- for (const [id, r] of Object.entries(outcome.results)) {
74
- if (r.ok && r.skipped.length === 0) continue;
75
- const reason = r.skipped[0]?.reason ?? r.warnings[0] ?? "did not apply";
76
- lines.push(`${id}: ${reason}`);
77
- }
78
- const route = outcome.route;
79
- if (route?.status === "failed" || route?.status === "unavailable") lines.push(`route: ${route.status}`);
80
- return lines;
81
- }
82
-
83
- /** A short-lived flag: set it, and it clears itself after `ms`. */
84
- function useFlash(ms) {
85
- const [token, setToken] = useState(0);
86
- useEffect(() => {
87
- if (!token) return;
88
- const timer = setTimeout(() => setToken(0), ms);
89
- return () => clearTimeout(timer);
90
- }, [token, ms]);
91
- return [token > 0, () => setToken(n => n + 1)];
92
- }
93
- export function TimeMachineFloatingTool({
94
- id,
95
- revealToken,
96
- dismiss
97
- }) {
98
- const {
99
- snapshots,
100
- hasLoaded,
101
- busy
102
- } = useTimeMachineState();
103
- const providers = useSnapshotProviders();
104
- const pickedId = useSyncExternalStore(subscribePickedPoint, getPickedPointId, getPickedPointId);
105
- const snapshotLimit = useLimit("timeMachine.snapshots");
106
- const reloadMode = useSyncExternalStore(subscribeBarSettings, getBarReloadMode, getBarReloadMode);
107
- const {
108
- width
109
- } = useWindowDimensions();
110
- const [panel, setPanel] = useState(null);
111
- const [failed, setFailed] = useState(null);
112
- const [error, setError] = useState();
113
- const [naming, setNaming] = useState(false);
114
- const [restoredFlash, flashRestored] = useFlash(FLASH_MS);
115
- const [capturedFlash, flashCaptured] = useFlash(FLASH_MS);
116
- // The way back after a bar restore: the safety snapshot's id, for a while.
117
- const [undo, setUndo] = useState(null);
118
- const [undoneAt, setUndoneAt] = useState(null);
119
- // Re-render on a slow clock so "just now" and "restored 5s ago" move on.
120
- const [, setTick] = useState(0);
121
- const mounted = useRef(true);
122
- useEffect(() => {
123
- mounted.current = true;
124
- void ensurePickedPointLoaded();
125
- void ensureBarSettingsLoaded();
126
- void timeMachineStore.ensureLoaded();
127
- return () => {
128
- mounted.current = false;
129
- };
130
- }, []);
131
- const armed = resolveArmedPoint(snapshots, pickedId);
132
- const armedId = armed?.id;
133
- const lastRestoredAt = armed?.lastRestoredAt;
134
- const createdAt = armed?.createdAt;
135
- useEffect(() => {
136
- if (lastRestoredAt === undefined && createdAt === undefined && !undo && undoneAt === null) return;
137
- const timer = setInterval(() => setTick(n => n + 1), 2_500);
138
- return () => clearInterval(timer);
139
- }, [lastRestoredAt, createdAt, undo, undoneAt]);
140
-
141
- // A failure belongs to the point it happened on; arming another clears it.
142
- useEffect(() => {
143
- if (failed && failed.snapshotId !== armedId) {
144
- setFailed(null);
145
- setPanel(p => p === "outcome" ? null : p);
146
- }
147
- }, [armedId, failed]);
148
- const now = Date.now();
149
- const undoAvailable = undo !== null && now - undo.at < RECENT_MS && snapshots.some(meta => meta.id === undo.id);
150
- // The tier cap counts the points you chose to keep, not the safety slot.
151
- const kept = snapshots.filter(meta => !meta.safety);
152
- const atCaptureLimit = kept.length >= snapshotLimit;
153
- const restoreDisabled = !armed || busy !== null;
154
- const captureDisabled = busy !== null;
155
- const runRestore = async (targetId, options) => {
156
- if (busy) return;
157
- setPanel(null);
158
- setError(undefined);
159
- try {
160
- const outcome = await timeMachineStore.restore(targetId,
161
- // Reload mode restores storage, parks the route for the next boot
162
- // and reloads: the only way to reach screens that read storage once.
163
- reloadMode && options.safetyCapture ? "reload" : "live", undefined, options);
164
- if (!mounted.current) return;
165
- if (restoreNeedsAttention(outcome)) {
166
- setFailed(outcome);
167
- setPanel("outcome");
168
- } else {
169
- setFailed(null);
170
- flashRestored();
171
- }
172
- setUndo(outcome.safetySnapshotId ? {
173
- id: outcome.safetySnapshotId,
174
- at: Date.now()
175
- } : null);
176
- if (outcome.safetySnapshotId) setUndoneAt(null);
177
- } catch (failure) {
178
- if (!mounted.current) return;
179
- setError(failure instanceof Error ? failure.message : String(failure));
180
- setPanel("error");
181
- }
182
- };
183
-
184
- /** Tap: the point's own route setting applies. Long-press: state only. */
185
- const restore = (stateOnly = false) => {
186
- if (!armed) return;
187
- void runRestore(armed.id, {
188
- safetyCapture: true,
189
- ...(stateOnly ? {
190
- restoreRoute: false
191
- } : {})
192
- });
193
- };
194
- const undoRestore = () => {
195
- if (!undo) return;
196
- // Back to where the app was, state only: you have not moved since.
197
- void runRestore(undo.id, {
198
- safetyCapture: false,
199
- restoreRoute: false
200
- }).then(() => {
201
- if (mounted.current) setUndoneAt(Date.now());
202
- });
203
- };
204
-
205
- // `+` asks for a name first, in a dialog over the app. An empty name
206
- // keeps the default (screen and clock).
207
- const askForName = () => {
208
- if (busy) return;
209
- setError(undefined);
210
- if (atCaptureLimit) {
211
- setPanel("cap");
212
- return;
213
- }
214
- setPanel(null);
215
- setNaming(true);
216
- };
217
- const capture = async typed => {
218
- setNaming(false);
219
- if (busy) return;
220
- try {
221
- const meta = await timeMachineStore.capture(typed);
222
- if (!mounted.current) return;
223
- // Captured from the bar means "loop back to here": arm it.
224
- setPickedPointId(meta.id);
225
- setUndo(null);
226
- flashCaptured();
227
- } catch (failure) {
228
- if (!mounted.current) return;
229
- setError(failure instanceof Error ? failure.message : String(failure));
230
- setPanel("error");
231
- }
232
- };
233
- const pick = meta => {
234
- setPickedPointId(meta.id);
235
- setPanel(null);
236
- };
237
- const name = armed?.name ?? (hasLoaded ? "No restore points" : "Loading…");
238
- let status;
239
- let statusTone = "normal";
240
- let idle = false;
241
- if (busy === "restore") status = "Restoring…";else if (busy === "capture") status = "Capturing…";else if (error) {
242
- status = error;
243
- statusTone = "danger";
244
- } else if (failed && armed && failed.snapshotId === armed.id) {
245
- status = failureSummary(failed);
246
- statusTone = "danger";
247
- } else if (!armed) status = hasLoaded ? "Capture one with +" : "";else {
248
- idle = true;
249
- const restoredAgo = armed.lastRestoredAt === undefined ? null : now - armed.lastRestoredAt;
250
- const capturedAgo = now - armed.createdAt;
251
- if (undoneAt !== null && now - undoneAt < RECENT_MS) status = "undone, state only";else if (restoredAgo !== null && restoredAgo < RECENT_MS) status = "restored just now";else if (capturedAgo < RECENT_MS && restoredAgo === null) status = "captured just now";else if (armed.lastRestoredAt !== undefined) status = `restored ${timeAgo(armed.lastRestoredAt)}`;else status = `${Object.keys(armed.sources).length} sources`;
252
- if (reloadMode) status += " · reloads";
253
- }
254
- // Say where a restore will take you before it does.
255
- const destination = idle && !undoAvailable && armed && armed.route && willRestoreRoute(armed) ? armed.route.pathname : null;
256
- const pickable = snapshots.filter(meta => !meta.baseline && !meta.safety);
257
- const captureSummary = (() => {
258
- const count = providers.filter(p => p.status().canCapture && isSourceEnabled(p.id)).length;
259
- const route = captureCurrentRoute();
260
- const parts = [`${count} source${count === 1 ? "" : "s"}`];
261
- if (route) parts.push(route.pathname);
262
- return parts.join(" · ");
263
- })();
264
- let accessory;
265
- if (panel === "picker") {
266
- accessory = /*#__PURE__*/_jsxs(View, {
267
- style: styles.menu,
268
- testID: "time-machine-bar-picker",
269
- children: [/*#__PURE__*/_jsxs(ScrollView, {
270
- style: styles.pickerList,
271
- bounces: false,
272
- children: [pickable.map(meta => {
273
- const isArmed = meta.id === armed?.id;
274
- return /*#__PURE__*/_jsxs(TouchableOpacity, {
275
- testID: `time-machine-bar-pick-${meta.id}`,
276
- accessibilityRole: "button",
277
- accessibilityState: {
278
- selected: isArmed
279
- },
280
- accessibilityLabel: `Arm ${meta.name}`,
281
- onPress: () => pick(meta),
282
- style: styles.pickRow,
283
- children: [/*#__PURE__*/_jsxs(View, {
284
- style: styles.pickText,
285
- children: [/*#__PURE__*/_jsx(Text, {
286
- numberOfLines: 1,
287
- style: styles.pickName,
288
- children: meta.name
289
- }), /*#__PURE__*/_jsxs(Text, {
290
- numberOfLines: 1,
291
- style: styles.pickDetail,
292
- children: [timeAgo(meta.createdAt), meta.route ? ` · ${meta.route.pathname}` : "", meta.lastRestoredAt !== undefined ? /*#__PURE__*/_jsx(Text, {
293
- style: styles.pickRestored,
294
- children: ` · restored ${timeAgo(meta.lastRestoredAt)}`
295
- }) : null]
296
- })]
297
- }), isArmed ? /*#__PURE__*/_jsx(Check, {
298
- size: 14,
299
- color: night.color.accent
300
- }) : null]
301
- }, meta.id);
302
- }), pickable.length === 0 ? /*#__PURE__*/_jsx(Text, {
303
- style: styles.menuText,
304
- children: "No restore points yet."
305
- }) : null]
306
- }), pickable.length > PICKER_ROWS ? /*#__PURE__*/_jsxs(Text, {
307
- style: styles.pickerMore,
308
- children: ["Scroll for ", pickable.length - PICKER_ROWS, " more"]
309
- }) : null, /*#__PURE__*/_jsxs(View, {
310
- style: styles.settingRow,
311
- testID: "time-machine-bar-reload-mode",
312
- children: [/*#__PURE__*/_jsxs(View, {
313
- style: styles.settingText,
314
- children: [/*#__PURE__*/_jsx(Text, {
315
- style: styles.settingLabel,
316
- children: "Reload the app after restoring"
317
- }), /*#__PURE__*/_jsx(Text, {
318
- style: styles.settingHint,
319
- children: "For screens that read their state from storage only when they open, like a cart kept in a React context."
320
- })]
321
- }), /*#__PURE__*/_jsx(NightSwitch, {
322
- value: reloadMode,
323
- onValueChange: setBarReloadMode
324
- })]
325
- })]
326
- });
327
- } else if (panel === "outcome" && failed) {
328
- accessory = /*#__PURE__*/_jsxs(View, {
329
- style: styles.menu,
330
- testID: "time-machine-bar-outcome",
331
- children: [failureLines(failed).map(line => /*#__PURE__*/_jsx(Text, {
332
- style: styles.menuDanger,
333
- children: line
334
- }, line)), /*#__PURE__*/_jsx(Text, {
335
- style: styles.menuText,
336
- children: "The full report is on Time Machine's outcome card."
337
- }), /*#__PURE__*/_jsx(DismissLink, {
338
- onPress: () => setPanel(null)
339
- })]
340
- });
341
- } else if (panel === "cap") {
342
- accessory = /*#__PURE__*/_jsxs(View, {
343
- style: styles.menu,
344
- testID: "time-machine-bar-cap",
345
- children: [/*#__PURE__*/_jsxs(Text, {
346
- style: styles.menuText,
347
- children: ["Free plan keeps ", snapshotLimit, " restore point", snapshotLimit === 1 ? "" : "s", ". Delete one in Time Machine, or upgrade to Pro for unlimited snapshots."]
348
- }), /*#__PURE__*/_jsx(DismissLink, {
349
- onPress: () => setPanel(null)
350
- })]
351
- });
352
- } else if (panel === "error" && error) {
353
- accessory = /*#__PURE__*/_jsxs(View, {
354
- style: styles.menu,
355
- testID: "time-machine-bar-error",
356
- children: [/*#__PURE__*/_jsx(Text, {
357
- accessibilityRole: "alert",
358
- style: styles.menuDanger,
359
- children: error
360
- }), /*#__PURE__*/_jsx(DismissLink, {
361
- onPress: () => {
362
- setPanel(null);
363
- setError(undefined);
364
- }
365
- })]
366
- });
367
- }
368
- return /*#__PURE__*/_jsxs(_Fragment, {
369
- children: [/*#__PURE__*/_jsx(NamePromptModal, {
370
- visible: naming,
371
- summary: captureSummary,
372
- onCapture: typed => void capture(typed),
373
- onCancel: () => setNaming(false)
374
- }), /*#__PURE__*/_jsx(FloatingTool, {
375
- id: id,
376
- revealToken: revealToken,
377
- variant: "strip",
378
- dragFromSurface: true,
379
- defaultPlacement: "bottom",
380
- accessibilityLabel: `Move or hide the action bar. ${armed ? `${armed.name} armed` : "Nothing armed"}`,
381
- accessory: accessory
382
- // The hidden tab keeps the tool's icon, so it is not one more
383
- // anonymous grip on the edge next to the minimized-tools dock.
384
- ,
385
- handleAdornment: /*#__PURE__*/_jsx(TimeMachineIcon, {
386
- size: 12,
387
- color: night.color.accent
388
- }),
389
- children: /*#__PURE__*/_jsxs(View, {
390
- style: [styles.row, {
391
- width: Math.max(260, Math.min(320, width - 64))
392
- }],
393
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
394
- testID: "time-machine-bar-restore",
395
- accessibilityRole: "button",
396
- accessibilityLabel: armed ? `Restore ${armed.name}` : "Restore, nothing armed",
397
- accessibilityHint: destination ? `Also returns to ${destination}. Long press to restore state only.` : undefined,
398
- accessibilityState: {
399
- disabled: restoreDisabled
400
- },
401
- disabled: restoreDisabled,
402
- onPress: () => restore(false),
403
- onLongPress: () => restore(true),
404
- style: [styles.restoreButton, restoreDisabled && styles.disabled],
405
- children: busy === "restore" ? /*#__PURE__*/_jsx(ActivityIndicator, {
406
- size: "small",
407
- color: night.color.accent
408
- }) : restoredFlash ? /*#__PURE__*/_jsx(Text, {
409
- style: styles.restoreText,
410
- testID: "time-machine-bar-restored",
411
- children: "Restored"
412
- }) : /*#__PURE__*/_jsx(Text, {
413
- style: styles.restoreText,
414
- children: "Restore"
415
- })
416
- }), /*#__PURE__*/_jsxs(View, {
417
- style: styles.identity,
418
- children: [/*#__PURE__*/_jsx(Text, {
419
- testID: "time-machine-bar-name",
420
- accessibilityLabel: `Armed restore point: ${name}`,
421
- numberOfLines: 1
422
- // Default names are "screen · clock"; the clock is what tells
423
- // two captures apart, so the end must survive truncation.
424
- ,
425
- ellipsizeMode: "middle",
426
- style: styles.name,
427
- children: name
428
- }), /*#__PURE__*/_jsxs(View, {
429
- style: styles.statusRow,
430
- children: [/*#__PURE__*/_jsxs(Text, {
431
- testID: "time-machine-bar-status",
432
- numberOfLines: 1,
433
- accessibilityLiveRegion: "polite",
434
- style: [styles.status, statusTone === "danger" && styles.danger],
435
- children: [status, destination ? /*#__PURE__*/_jsx(Text, {
436
- style: styles.destination,
437
- children: ` · → ${destination}`
438
- }) : null]
439
- }), undoAvailable && !busy ? /*#__PURE__*/_jsx(TouchableOpacity, {
440
- testID: "time-machine-bar-undo",
441
- accessibilityRole: "button",
442
- accessibilityLabel: "Undo the last restore",
443
- onPress: undoRestore,
444
- hitSlop: {
445
- top: 8,
446
- bottom: 8,
447
- left: 6,
448
- right: 6
449
- },
450
- children: /*#__PURE__*/_jsx(Text, {
451
- style: styles.undo,
452
- children: "Undo"
453
- })
454
- }) : null]
455
- })]
456
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
457
- testID: "time-machine-bar-pick",
458
- accessibilityRole: "button",
459
- accessibilityLabel: "Choose another restore point",
460
- accessibilityState: {
461
- expanded: panel === "picker"
462
- },
463
- onPress: () => setPanel(p => p === "picker" ? null : "picker"),
464
- style: styles.smallButton,
465
- children: panel === "picker" ? /*#__PURE__*/_jsx(ChevronUp, {
466
- size: 14,
467
- color: night.color.textSecondary
468
- }) : /*#__PURE__*/_jsx(ChevronDown, {
469
- size: 14,
470
- color: night.color.textSecondary
471
- })
472
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
473
- testID: "time-machine-bar-capture",
474
- accessibilityRole: "button",
475
- accessibilityLabel: "Capture a new restore point here and arm it",
476
- accessibilityState: {
477
- disabled: captureDisabled
478
- },
479
- disabled: captureDisabled,
480
- onPress: askForName,
481
- style: [styles.smallButton, captureDisabled && styles.disabled],
482
- children: busy === "capture" ? /*#__PURE__*/_jsx(ActivityIndicator, {
483
- size: "small",
484
- color: night.color.accent
485
- }) : capturedFlash ? /*#__PURE__*/_jsx(View, {
486
- testID: "time-machine-bar-captured",
487
- children: /*#__PURE__*/_jsx(Check, {
488
- size: 18,
489
- color: night.color.accent
490
- })
491
- }) : /*#__PURE__*/_jsx(Plus, {
492
- size: 18,
493
- color: atCaptureLimit ? night.color.textTertiary : night.color.text
494
- })
495
- }), /*#__PURE__*/_jsx(View, {
496
- style: styles.separator
497
- }), /*#__PURE__*/_jsx(TouchableOpacity, {
498
- testID: "time-machine-bar-close",
499
- accessibilityRole: "button",
500
- accessibilityLabel: "Close the action bar",
501
- onPress: dismiss,
502
- style: styles.closeButton,
503
- children: /*#__PURE__*/_jsx(X, {
504
- size: 16,
505
- color: night.color.textSecondary
506
- })
507
- })]
508
- })
509
- })]
510
- });
511
- }
512
- function DismissLink({
513
- onPress,
514
- label = "Dismiss"
515
- }) {
516
- return /*#__PURE__*/_jsx(TouchableOpacity, {
517
- testID: "time-machine-bar-dismiss",
518
- accessibilityRole: "button",
519
- onPress: onPress,
520
- style: styles.dismiss,
521
- children: /*#__PURE__*/_jsx(Text, {
522
- style: styles.dismissText,
523
- children: label
524
- })
525
- });
526
- }
527
- const styles = StyleSheet.create({
528
- row: {
529
- flexDirection: "row",
530
- alignItems: "center",
531
- gap: 4,
532
- paddingVertical: 4,
533
- minHeight: 48
534
- },
535
- // The primary action is a word, not a glyph: "Restore" needs no legend.
536
- // Same pill as Network's strip action: accent ink on a tinted, outlined
537
- // dark chip, so it belongs to the strip instead of shouting over it.
538
- restoreButton: {
539
- height: 32,
540
- minWidth: 72,
541
- paddingHorizontal: 12,
542
- borderRadius: 8,
543
- borderWidth: 1,
544
- borderColor: night.color.accentBorderStrong,
545
- backgroundColor: night.color.accentSoft,
546
- alignItems: "center",
547
- justifyContent: "center",
548
- marginLeft: 2
549
- },
550
- restoreText: {
551
- color: night.color.accent,
552
- fontSize: 12,
553
- fontWeight: "700"
554
- },
555
- smallButton: {
556
- width: 32,
557
- minHeight: 44,
558
- alignItems: "center",
559
- justifyContent: "center"
560
- },
561
- closeButton: {
562
- width: 28,
563
- minHeight: 44,
564
- alignItems: "center",
565
- justifyContent: "center"
566
- },
567
- identity: {
568
- flex: 1,
569
- minHeight: 44,
570
- justifyContent: "center",
571
- gap: 1
572
- },
573
- name: {
574
- color: night.color.text,
575
- fontSize: 13,
576
- fontWeight: "500"
577
- },
578
- statusRow: {
579
- flexDirection: "row",
580
- alignItems: "center",
581
- gap: 8
582
- },
583
- status: {
584
- color: night.color.textSecondary,
585
- fontSize: 10,
586
- lineHeight: 14,
587
- flexShrink: 1
588
- },
589
- destination: {
590
- color: night.color.textTertiary
591
- },
592
- danger: {
593
- color: night.color.danger
594
- },
595
- undo: {
596
- color: night.color.accent,
597
- fontSize: 10,
598
- lineHeight: 14,
599
- fontWeight: "700"
600
- },
601
- separator: {
602
- height: 20,
603
- width: 1,
604
- backgroundColor: night.color.separator,
605
- marginHorizontal: 2
606
- },
607
- disabled: {
608
- opacity: 0.4
609
- },
610
- menu: {
611
- marginVertical: 8,
612
- padding: 6,
613
- borderRadius: night.radius.card,
614
- borderWidth: 1,
615
- borderColor: night.color.separator,
616
- backgroundColor: night.color.surface,
617
- shadowColor: "#000",
618
- shadowOffset: {
619
- width: 0,
620
- height: 6
621
- },
622
- shadowOpacity: 0.25,
623
- shadowRadius: 16,
624
- elevation: 8
625
- },
626
- menuText: {
627
- color: night.color.textSecondary,
628
- fontSize: 12,
629
- lineHeight: 18,
630
- padding: 10
631
- },
632
- menuDanger: {
633
- color: night.color.danger,
634
- fontSize: 12,
635
- lineHeight: 18,
636
- paddingHorizontal: 10,
637
- paddingTop: 6
638
- },
639
- pickerList: {
640
- maxHeight: PICKER_ROWS * 44
641
- },
642
- pickerMore: {
643
- color: night.color.textTertiary,
644
- fontSize: 10,
645
- lineHeight: 14,
646
- textAlign: "center",
647
- paddingVertical: 4
648
- },
649
- pickRow: {
650
- flexDirection: "row",
651
- alignItems: "center",
652
- gap: 8,
653
- minHeight: 44,
654
- paddingHorizontal: 10,
655
- borderRadius: night.radius.row
656
- },
657
- pickText: {
658
- flex: 1
659
- },
660
- settingRow: {
661
- flexDirection: "row",
662
- alignItems: "center",
663
- gap: 10,
664
- paddingHorizontal: 10,
665
- paddingVertical: 8,
666
- borderTopWidth: StyleSheet.hairlineWidth,
667
- borderTopColor: night.color.separator,
668
- marginTop: 4
669
- },
670
- settingText: {
671
- flex: 1,
672
- gap: 2
673
- },
674
- settingLabel: {
675
- color: night.color.text,
676
- fontSize: 12,
677
- fontWeight: "500"
678
- },
679
- settingHint: {
680
- color: night.color.textTertiary,
681
- fontSize: 10,
682
- lineHeight: 13
683
- },
684
- pickName: {
685
- color: night.color.text,
686
- fontSize: 13,
687
- fontWeight: "500"
688
- },
689
- pickDetail: {
690
- color: night.color.textSecondary,
691
- fontSize: 10,
692
- lineHeight: 14
693
- },
694
- pickRestored: {
695
- color: night.color.accent
696
- },
697
- dismiss: {
698
- alignSelf: "flex-end",
699
- paddingHorizontal: 10,
700
- paddingVertical: 8
701
- },
702
- dismissText: {
703
- color: night.color.accent,
704
- fontSize: 12,
705
- fontWeight: "600"
706
- }
707
- });
708
- //# sourceMappingURL=TimeMachineFloatingTool.js.map
1
+ "use strict";import{useEffect as P,useRef as Le,useState as S,useSyncExternalStore as ne}from"react";import{ActivityIndicator as ie,ScrollView as Ce,StyleSheet as se,Text as s,TouchableOpacity as x,View as p,useWindowDimensions as Ae}from"react-native";import{Check as le,ChevronDown as ve,ChevronUp as ze,FloatingTool as Pe,NightSwitch as He,Plus as Me,TimeMachineIcon as _e,X as $e,night as r,openFloatingTool as Fe,persistentStorage as je,registerPersistentFloatingTool as Ne,useLimit as Oe}from"@buoy-gg/shared-ui";import{useSnapshotProviders as Ee,useTimeMachineState as We}from"../../hooks/useTimeMachine";import{timeMachineStore as E,willRestoreRoute as Be}from"../../store/timeMachineStore";import{captureCurrentRoute as Ve}from"../../store/routeRestore";import{isSourceEnabled as Ue}from"../../store/sourceSettings";import{restoreNeedsAttention as Ge}from"../list/RestoreOutcomeCard";import{timeAgo as W}from"../format";import{ensurePickedPointLoaded as Ke,getPickedPointId as ae,resolveArmedPoint as Xe,setPickedPointId as B,subscribePickedPoint as qe}from"./armedPoint";import{NamePromptModal as Je}from"./NamePromptModal";import{ensureBarSettingsLoaded as Qe,getBarReloadMode as ce,setBarReloadMode as Ye,subscribeBarSettings as Ze}from"./barSettings";import{jsx as o,jsxs as a,Fragment as et}from"react/jsx-runtime";export const TIME_MACHINE_FLOATING_TOOL_ID="time-machine-restore";const H=1e4;const de=900;const V=6;const ue={id:TIME_MACHINE_FLOATING_TOOL_ID,render:l=>o(TimeMachineFloatingTool,{...l})};if(typeof __DEV__!=="undefined"&&__DEV__&&typeof navigator!=="undefined"&&navigator.product==="ReactNative"){Ne(ue,je)}export function openTimeMachineFloatingTool(l){if(l)B(l);Fe(ue)}function tt(l){const c=Object.values(l.results);const g=c.filter(D=>!D.ok||D.skipped.length>0).length;const u=c.length;const y=l.route;const d=y?.status==="failed"||y?.status==="unavailable";if(g===0&&d)return"Route did not restore";return`${g} of ${u} source${u===1?"":"s"} failed`}function ot(l){const c=[];for(const[u,y]of Object.entries(l.results)){if(y.ok&&y.skipped.length===0)continue;const d=y.skipped[0]?.reason??y.warnings[0]??"did not apply";c.push(`${u}: ${d}`)}const g=l.route;if(g?.status==="failed"||g?.status==="unavailable")c.push(`route: ${g.status}`);return c}function he(l){const[c,g]=S(0);P(()=>{if(!c)return;const u=setTimeout(()=>g(0),l);return()=>clearTimeout(u)},[c,l]);return[c>0,()=>g(u=>u+1)]}export function TimeMachineFloatingTool({id:l,revealToken:c,dismiss:g}){const{snapshots:u,hasLoaded:y,busy:d}=We();const D=Ee();const me=ne(qe,ae,ae);const M=Oe("timeMachine.snapshots");const _=ne(Ze,ce,ce);const{width:fe}=Ae();const[T,h]=S(null);const[b,$]=S(null);const[L,w]=S();const[pe,F]=S(false);const[ge,ye]=he(de);const[be,Se]=he(de);const[R,G]=S(null);const[C,K]=S(null);const[,Re]=S(0);const k=Le(true);P(()=>{k.current=true;void Ke();void Qe();void E.ensureLoaded();return()=>{k.current=false}},[]);const n=Xe(u,me);const X=n?.id;const q=n?.lastRestoredAt;const J=n?.createdAt;P(()=>{if(q===void 0&&J===void 0&&!R&&C===null)return;const e=setInterval(()=>Re(i=>i+1),2500);return()=>clearInterval(e)},[q,J,R,C]);P(()=>{if(b&&b.snapshotId!==X){$(null);h(e=>e==="outcome"?null:e)}},[X,b]);const A=Date.now();const Q=R!==null&&A-R.at<H&&u.some(e=>e.id===R.id);const ke=u.filter(e=>!e.safety);const Y=ke.length>=M;const j=!n||d!==null;const N=d!==null;const Z=async(e,i)=>{if(d)return;h(null);w(void 0);try{const f=await E.restore(e,_&&i.safetyCapture?"reload":"live",void 0,i);if(!k.current)return;if(Ge(f)){$(f);h("outcome")}else{$(null);ye()}G(f.safetySnapshotId?{id:f.safetySnapshotId,at:Date.now()}:null);if(f.safetySnapshotId)K(null)}catch(f){if(!k.current)return;w(f instanceof Error?f.message:String(f));h("error")}};const ee=(e=false)=>{if(!n)return;void Z(n.id,{safetyCapture:true,...e?{restoreRoute:false}:{}})};const xe=()=>{if(!R)return;void Z(R.id,{safetyCapture:false,restoreRoute:false}).then(()=>{if(k.current)K(Date.now())})};const Te=()=>{if(d)return;w(void 0);if(Y){h("cap");return}h(null);F(true)};const we=async e=>{F(false);if(d)return;try{const i=await E.capture(e);if(!k.current)return;B(i.id);G(null);Se()}catch(i){if(!k.current)return;w(i instanceof Error?i.message:String(i));h("error")}};const Ie=e=>{B(e.id);h(null)};const te=n?.name??(y?"No restore points":"Loading\u2026");let m;let O="normal";let oe=false;if(d==="restore")m="Restoring\u2026";else if(d==="capture")m="Capturing\u2026";else if(L){m=L;O="danger"}else if(b&&n&&b.snapshotId===n.id){m=tt(b);O="danger"}else if(!n)m=y?"Capture one with +":"";else{oe=true;const e=n.lastRestoredAt===void 0?null:A-n.lastRestoredAt;const i=A-n.createdAt;if(C!==null&&A-C<H)m="undone, state only";else if(e!==null&&e<H)m="restored just now";else if(i<H&&e===null)m="captured just now";else if(n.lastRestoredAt!==void 0)m=`restored ${W(n.lastRestoredAt)}`;else m=`${Object.keys(n.sources).length} sources`;if(_)m+=" \xB7 reloads"}const v=oe&&!Q&&n&&n.route&&Be(n)?n.route.pathname:null;const z=u.filter(e=>!e.baseline&&!e.safety);const De=(()=>{const e=D.filter(re=>re.status().canCapture&&Ue(re.id)).length;const i=Ve();const f=[`${e} source${e===1?"":"s"}`];if(i)f.push(i.pathname);return f.join(" \xB7 ")})();let I;if(T==="picker"){I=a(p,{style:t.menu,testID:"time-machine-bar-picker",children:[a(Ce,{style:t.pickerList,bounces:false,children:[z.map(e=>{const i=e.id===n?.id;return a(x,{testID:`time-machine-bar-pick-${e.id}`,accessibilityRole:"button",accessibilityState:{selected:i},accessibilityLabel:`Arm ${e.name}`,onPress:()=>Ie(e),style:t.pickRow,children:[a(p,{style:t.pickText,children:[o(s,{numberOfLines:1,style:t.pickName,children:e.name}),a(s,{numberOfLines:1,style:t.pickDetail,children:[W(e.createdAt),e.route?` \xB7 ${e.route.pathname}`:"",e.lastRestoredAt!==void 0?o(s,{style:t.pickRestored,children:` \xB7 restored ${W(e.lastRestoredAt)}`}):null]})]}),i?o(le,{size:14,color:r.color.accent}):null]},e.id)}),z.length===0?o(s,{style:t.menuText,children:"No restore points yet."}):null]}),z.length>V?a(s,{style:t.pickerMore,children:["Scroll for ",z.length-V," more"]}):null,a(p,{style:t.settingRow,testID:"time-machine-bar-reload-mode",children:[a(p,{style:t.settingText,children:[o(s,{style:t.settingLabel,children:"Reload the app after restoring"}),o(s,{style:t.settingHint,children:"For screens that read their state from storage only when they open, like a cart kept in a React context."})]}),o(He,{value:_,onValueChange:Ye})]})]})}else if(T==="outcome"&&b){I=a(p,{style:t.menu,testID:"time-machine-bar-outcome",children:[ot(b).map(e=>o(s,{style:t.menuDanger,children:e},e)),o(s,{style:t.menuText,children:"The full report is on Time Machine's outcome card."}),o(U,{onPress:()=>h(null)})]})}else if(T==="cap"){I=a(p,{style:t.menu,testID:"time-machine-bar-cap",children:[a(s,{style:t.menuText,children:["Free plan keeps ",M," restore point",M===1?"":"s",". Delete one in Time Machine, or upgrade to Pro for unlimited snapshots."]}),o(U,{onPress:()=>h(null)})]})}else if(T==="error"&&L){I=a(p,{style:t.menu,testID:"time-machine-bar-error",children:[o(s,{accessibilityRole:"alert",style:t.menuDanger,children:L}),o(U,{onPress:()=>{h(null);w(void 0)}})]})}return a(et,{children:[o(Je,{visible:pe,summary:De,onCapture:e=>void we(e),onCancel:()=>F(false)}),o(Pe,{id:l,revealToken:c,variant:"strip",dragFromSurface:true,defaultPlacement:"bottom",accessibilityLabel:`Move or hide the action bar. ${n?`${n.name} armed`:"Nothing armed"}`,accessory:I,handleAdornment:o(_e,{size:12,color:r.color.accent}),children:a(p,{style:[t.row,{width:Math.max(260,Math.min(320,fe-64))}],children:[o(x,{testID:"time-machine-bar-restore",accessibilityRole:"button",accessibilityLabel:n?`Restore ${n.name}`:"Restore, nothing armed",accessibilityHint:v?`Also returns to ${v}. Long press to restore state only.`:void 0,accessibilityState:{disabled:j},disabled:j,onPress:()=>ee(false),onLongPress:()=>ee(true),style:[t.restoreButton,j&&t.disabled],children:d==="restore"?o(ie,{size:"small",color:r.color.accent}):ge?o(s,{style:t.restoreText,testID:"time-machine-bar-restored",children:"Restored"}):o(s,{style:t.restoreText,children:"Restore"})}),a(p,{style:t.identity,children:[o(s,{testID:"time-machine-bar-name",accessibilityLabel:`Armed restore point: ${te}`,numberOfLines:1,ellipsizeMode:"middle",style:t.name,children:te}),a(p,{style:t.statusRow,children:[a(s,{testID:"time-machine-bar-status",numberOfLines:1,accessibilityLiveRegion:"polite",style:[t.status,O==="danger"&&t.danger],children:[m,v?o(s,{style:t.destination,children:` \xB7 \u2192 ${v}`}):null]}),Q&&!d?o(x,{testID:"time-machine-bar-undo",accessibilityRole:"button",accessibilityLabel:"Undo the last restore",onPress:xe,hitSlop:{top:8,bottom:8,left:6,right:6},children:o(s,{style:t.undo,children:"Undo"})}):null]})]}),o(x,{testID:"time-machine-bar-pick",accessibilityRole:"button",accessibilityLabel:"Choose another restore point",accessibilityState:{expanded:T==="picker"},onPress:()=>h(e=>e==="picker"?null:"picker"),style:t.smallButton,children:T==="picker"?o(ze,{size:14,color:r.color.textSecondary}):o(ve,{size:14,color:r.color.textSecondary})}),o(x,{testID:"time-machine-bar-capture",accessibilityRole:"button",accessibilityLabel:"Capture a new restore point here and arm it",accessibilityState:{disabled:N},disabled:N,onPress:Te,style:[t.smallButton,N&&t.disabled],children:d==="capture"?o(ie,{size:"small",color:r.color.accent}):be?o(p,{testID:"time-machine-bar-captured",children:o(le,{size:18,color:r.color.accent})}):o(Me,{size:18,color:Y?r.color.textTertiary:r.color.text})}),o(p,{style:t.separator}),o(x,{testID:"time-machine-bar-close",accessibilityRole:"button",accessibilityLabel:"Close the action bar",onPress:g,style:t.closeButton,children:o($e,{size:16,color:r.color.textSecondary})})]})})]})}function U({onPress:l,label:c="Dismiss"}){return o(x,{testID:"time-machine-bar-dismiss",accessibilityRole:"button",onPress:l,style:t.dismiss,children:o(s,{style:t.dismissText,children:c})})}const t=se.create({row:{flexDirection:"row",alignItems:"center",gap:4,paddingVertical:4,minHeight:48},restoreButton:{height:32,minWidth:72,paddingHorizontal:12,borderRadius:8,borderWidth:1,borderColor:r.color.accentBorderStrong,backgroundColor:r.color.accentSoft,alignItems:"center",justifyContent:"center",marginLeft:2},restoreText:{color:r.color.accent,fontSize:12,fontWeight:"700"},smallButton:{width:32,minHeight:44,alignItems:"center",justifyContent:"center"},closeButton:{width:28,minHeight:44,alignItems:"center",justifyContent:"center"},identity:{flex:1,minHeight:44,justifyContent:"center",gap:1},name:{color:r.color.text,fontSize:13,fontWeight:"500"},statusRow:{flexDirection:"row",alignItems:"center",gap:8},status:{color:r.color.textSecondary,fontSize:10,lineHeight:14,flexShrink:1},destination:{color:r.color.textTertiary},danger:{color:r.color.danger},undo:{color:r.color.accent,fontSize:10,lineHeight:14,fontWeight:"700"},separator:{height:20,width:1,backgroundColor:r.color.separator,marginHorizontal:2},disabled:{opacity:.4},menu:{marginVertical:8,padding:6,borderRadius:r.radius.card,borderWidth:1,borderColor:r.color.separator,backgroundColor:r.color.surface,shadowColor:"#000",shadowOffset:{width:0,height:6},shadowOpacity:.25,shadowRadius:16,elevation:8},menuText:{color:r.color.textSecondary,fontSize:12,lineHeight:18,padding:10},menuDanger:{color:r.color.danger,fontSize:12,lineHeight:18,paddingHorizontal:10,paddingTop:6},pickerList:{maxHeight:V*44},pickerMore:{color:r.color.textTertiary,fontSize:10,lineHeight:14,textAlign:"center",paddingVertical:4},pickRow:{flexDirection:"row",alignItems:"center",gap:8,minHeight:44,paddingHorizontal:10,borderRadius:r.radius.row},pickText:{flex:1},settingRow:{flexDirection:"row",alignItems:"center",gap:10,paddingHorizontal:10,paddingVertical:8,borderTopWidth:se.hairlineWidth,borderTopColor:r.color.separator,marginTop:4},settingText:{flex:1,gap:2},settingLabel:{color:r.color.text,fontSize:12,fontWeight:"500"},settingHint:{color:r.color.textTertiary,fontSize:10,lineHeight:13},pickName:{color:r.color.text,fontSize:13,fontWeight:"500"},pickDetail:{color:r.color.textSecondary,fontSize:10,lineHeight:14},pickRestored:{color:r.color.accent},dismiss:{alignSelf:"flex-end",paddingHorizontal:10,paddingVertical:8},dismissText:{color:r.color.accent,fontSize:12,fontWeight:"600"}});