@buoy-gg/time-machine 7.0.36 → 7.0.38

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 (145) hide show
  1. package/README.md +40 -35
  2. package/lib/commonjs/components/HeaderActions.js +91 -0
  3. package/lib/commonjs/components/HeaderReloadButton.js +22 -25
  4. package/lib/commonjs/components/TimeMachineBody.js +68 -13
  5. package/lib/commonjs/components/floating/NamePromptModal.js +193 -0
  6. package/lib/commonjs/components/floating/TimeMachineFloatingTool.js +715 -0
  7. package/lib/commonjs/components/floating/armedPoint.js +92 -0
  8. package/lib/commonjs/components/floating/barSettings.js +66 -0
  9. package/lib/commonjs/components/list/CaptureBar.js +2 -0
  10. package/lib/commonjs/components/list/RestoreOutcomeCard.js +2 -0
  11. package/lib/commonjs/components/list/SnapshotRow.js +37 -13
  12. package/lib/commonjs/components/list/SourcesStrip.js +127 -83
  13. package/lib/commonjs/components/list/UndoDeleteBar.js +3 -1
  14. package/lib/commonjs/components/preview/RestorePreviewScreen.js +7 -2
  15. package/lib/commonjs/components/sources.js +19 -8
  16. package/lib/commonjs/index.js +42 -0
  17. package/lib/commonjs/preset.js +6 -4
  18. package/lib/commonjs/store/sourceIds.js +9 -0
  19. package/lib/commonjs/store/sourceSettings.js +79 -0
  20. package/lib/commonjs/store/timeMachineStore.js +75 -10
  21. package/lib/commonjs/sync/timeMachineSyncAdapter.js +5 -1
  22. package/lib/commonjs/ui/SwipeActionsRow.js +14 -3
  23. package/lib/commonjs/ui/primitives.js +2 -0
  24. package/lib/commonjs/ui/tmTheme.js +18 -5
  25. package/lib/module/components/HeaderActions.js +85 -0
  26. package/lib/module/components/HeaderReloadButton.js +24 -27
  27. package/lib/module/components/TimeMachineBody.js +71 -16
  28. package/lib/module/components/floating/NamePromptModal.js +188 -0
  29. package/lib/module/components/floating/TimeMachineFloatingTool.js +708 -0
  30. package/lib/module/components/floating/armedPoint.js +82 -0
  31. package/lib/module/components/floating/barSettings.js +57 -0
  32. package/lib/module/components/list/CaptureBar.js +2 -0
  33. package/lib/module/components/list/RestoreOutcomeCard.js +2 -0
  34. package/lib/module/components/list/SnapshotRow.js +37 -13
  35. package/lib/module/components/list/SourcesStrip.js +130 -84
  36. package/lib/module/components/list/UndoDeleteBar.js +3 -1
  37. package/lib/module/components/preview/RestorePreviewScreen.js +7 -2
  38. package/lib/module/components/sources.js +13 -6
  39. package/lib/module/index.js +7 -1
  40. package/lib/module/preset.js +6 -4
  41. package/lib/module/store/sourceIds.js +5 -0
  42. package/lib/module/store/sourceSettings.js +69 -0
  43. package/lib/module/store/timeMachineStore.js +74 -9
  44. package/lib/module/sync/timeMachineSyncAdapter.js +5 -1
  45. package/lib/module/ui/SwipeActionsRow.js +14 -3
  46. package/lib/module/ui/primitives.js +2 -0
  47. package/lib/module/ui/tmTheme.js +18 -5
  48. package/lib/typescript/components/HeaderActions.d.ts +7 -0
  49. package/lib/typescript/components/floating/NamePromptModal.d.ts +9 -0
  50. package/lib/typescript/components/floating/TimeMachineFloatingTool.d.ts +6 -0
  51. package/lib/typescript/components/floating/armedPoint.d.ts +14 -0
  52. package/lib/typescript/components/floating/barSettings.d.ts +7 -0
  53. package/lib/typescript/components/list/SnapshotRow.d.ts +5 -1
  54. package/lib/typescript/components/list/SourcesStrip.d.ts +3 -1
  55. package/lib/typescript/components/sources.d.ts +7 -2
  56. package/lib/typescript/index.d.ts +3 -1
  57. package/lib/typescript/preset.d.ts +4 -2
  58. package/lib/typescript/store/snapshotVault.d.ts +6 -0
  59. package/lib/typescript/store/sourceIds.d.ts +3 -0
  60. package/lib/typescript/store/sourceSettings.d.ts +9 -0
  61. package/lib/typescript/store/timeMachineStore.d.ts +16 -1
  62. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +4 -0
  63. package/lib/typescript/ui/tmTheme.d.ts +17 -4
  64. package/package.json +10 -7
  65. package/lib/commonjs/components/HeaderReloadButton.js.map +0 -1
  66. package/lib/commonjs/components/TimeMachineBody.js.map +0 -1
  67. package/lib/commonjs/components/TimeMachineModal.js.map +0 -1
  68. package/lib/commonjs/components/confirm.js.map +0 -1
  69. package/lib/commonjs/components/format.js.map +0 -1
  70. package/lib/commonjs/components/list/CaptureBar.js.map +0 -1
  71. package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +0 -1
  72. package/lib/commonjs/components/list/SnapshotRow.js.map +0 -1
  73. package/lib/commonjs/components/list/SourcesStrip.js.map +0 -1
  74. package/lib/commonjs/components/list/UndoDeleteBar.js.map +0 -1
  75. package/lib/commonjs/components/list/WipeAllRow.js.map +0 -1
  76. package/lib/commonjs/components/preview/ItemDiffPanel.js.map +0 -1
  77. package/lib/commonjs/components/preview/PreviewFlow.js.map +0 -1
  78. package/lib/commonjs/components/preview/RestorePointNameRow.js.map +0 -1
  79. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +0 -1
  80. package/lib/commonjs/components/preview/verdictMeta.js.map +0 -1
  81. package/lib/commonjs/components/sources.js.map +0 -1
  82. package/lib/commonjs/hooks/useTimeMachine.js.map +0 -1
  83. package/lib/commonjs/index.js.map +0 -1
  84. package/lib/commonjs/preset.js.map +0 -1
  85. package/lib/commonjs/store/routeRestore.js.map +0 -1
  86. package/lib/commonjs/store/snapshotVault.js.map +0 -1
  87. package/lib/commonjs/store/timeMachineStore.js.map +0 -1
  88. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +0 -1
  89. package/lib/commonjs/ui/SwipeActionsRow.js.map +0 -1
  90. package/lib/commonjs/ui/primitives.js.map +0 -1
  91. package/lib/commonjs/ui/tmTheme.js.map +0 -1
  92. package/lib/module/components/HeaderReloadButton.js.map +0 -1
  93. package/lib/module/components/TimeMachineBody.js.map +0 -1
  94. package/lib/module/components/TimeMachineModal.js.map +0 -1
  95. package/lib/module/components/confirm.js.map +0 -1
  96. package/lib/module/components/format.js.map +0 -1
  97. package/lib/module/components/list/CaptureBar.js.map +0 -1
  98. package/lib/module/components/list/RestoreOutcomeCard.js.map +0 -1
  99. package/lib/module/components/list/SnapshotRow.js.map +0 -1
  100. package/lib/module/components/list/SourcesStrip.js.map +0 -1
  101. package/lib/module/components/list/UndoDeleteBar.js.map +0 -1
  102. package/lib/module/components/list/WipeAllRow.js.map +0 -1
  103. package/lib/module/components/preview/ItemDiffPanel.js.map +0 -1
  104. package/lib/module/components/preview/PreviewFlow.js.map +0 -1
  105. package/lib/module/components/preview/RestorePointNameRow.js.map +0 -1
  106. package/lib/module/components/preview/RestorePreviewScreen.js.map +0 -1
  107. package/lib/module/components/preview/verdictMeta.js.map +0 -1
  108. package/lib/module/components/sources.js.map +0 -1
  109. package/lib/module/hooks/useTimeMachine.js.map +0 -1
  110. package/lib/module/index.js.map +0 -1
  111. package/lib/module/preset.js.map +0 -1
  112. package/lib/module/store/routeRestore.js.map +0 -1
  113. package/lib/module/store/snapshotVault.js.map +0 -1
  114. package/lib/module/store/timeMachineStore.js.map +0 -1
  115. package/lib/module/sync/timeMachineSyncAdapter.js.map +0 -1
  116. package/lib/module/ui/SwipeActionsRow.js.map +0 -1
  117. package/lib/module/ui/primitives.js.map +0 -1
  118. package/lib/module/ui/tmTheme.js.map +0 -1
  119. package/lib/typescript/components/HeaderReloadButton.d.ts.map +0 -1
  120. package/lib/typescript/components/TimeMachineBody.d.ts.map +0 -1
  121. package/lib/typescript/components/TimeMachineModal.d.ts.map +0 -1
  122. package/lib/typescript/components/confirm.d.ts.map +0 -1
  123. package/lib/typescript/components/format.d.ts.map +0 -1
  124. package/lib/typescript/components/list/CaptureBar.d.ts.map +0 -1
  125. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +0 -1
  126. package/lib/typescript/components/list/SnapshotRow.d.ts.map +0 -1
  127. package/lib/typescript/components/list/SourcesStrip.d.ts.map +0 -1
  128. package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +0 -1
  129. package/lib/typescript/components/list/WipeAllRow.d.ts.map +0 -1
  130. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +0 -1
  131. package/lib/typescript/components/preview/PreviewFlow.d.ts.map +0 -1
  132. package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +0 -1
  133. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +0 -1
  134. package/lib/typescript/components/preview/verdictMeta.d.ts.map +0 -1
  135. package/lib/typescript/components/sources.d.ts.map +0 -1
  136. package/lib/typescript/hooks/useTimeMachine.d.ts.map +0 -1
  137. package/lib/typescript/index.d.ts.map +0 -1
  138. package/lib/typescript/preset.d.ts.map +0 -1
  139. package/lib/typescript/store/routeRestore.d.ts.map +0 -1
  140. package/lib/typescript/store/snapshotVault.d.ts.map +0 -1
  141. package/lib/typescript/store/timeMachineStore.d.ts.map +0 -1
  142. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +0 -1
  143. package/lib/typescript/ui/SwipeActionsRow.d.ts.map +0 -1
  144. package/lib/typescript/ui/primitives.d.ts.map +0 -1
  145. package/lib/typescript/ui/tmTheme.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,55 +1,60 @@
1
1
  # @buoy-gg/time-machine
2
2
 
3
- ## Description
3
+ Save and restore supported client state while testing a React Native app. A restore point can include registered storage, Redux, Zustand, Jotai, and the React Query cache. Review the restore preview before applying changes.
4
4
 
5
- time-machine package for the monorepo.
5
+ ## Install
6
6
 
7
- ## Installation
8
-
9
- This package is part of the monorepo and is automatically available to other packages and the example app.
7
+ ```bash
8
+ npm install @buoy-gg/core @buoy-gg/time-machine
9
+ ```
10
10
 
11
- ## Usage
11
+ ## Account setup
12
12
 
13
- ```typescript
14
- import { TimeMachineComponent } from '@buoy-gg/time-machine';
13
+ Start in a development build with a Free or Pro Buoy account key. From the app directory:
15
14
 
16
- // Use in your component
17
- <TimeMachineComponent title="Hello World" />
15
+ ```bash
16
+ npx --package=@buoy-gg/core buoy login
18
17
  ```
19
18
 
20
- ## Development
19
+ For Expo, initialize Buoy with the key written to `.env.local`:
21
20
 
22
- ### Building
21
+ ```tsx
22
+ import { Buoy } from "@buoy-gg/core";
23
23
 
24
- ```bash
25
- pnpm build
24
+ Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
26
25
  ```
27
26
 
28
- ### Type Checking
27
+ For React Native CLI, pass the key through your app's environment configuration; `.env.local` is not loaded automatically. Keep the menu inside the providers used by your app. Restart the development server after installing packages. See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the complete root component.
29
28
 
30
- ```bash
31
- pnpm typecheck
32
- ```
29
+ ## Use a restore point
33
30
 
34
- ### Clean Build
31
+ Mount `FloatingDevTools` in your app. Buoy discovers this package. Install and configure the source tools for the state you want to capture: Storage, Redux, Zustand, Jotai, or React Query.
35
32
 
36
- ```bash
37
- pnpm clean
38
- ```
33
+ 1. Put the app into a recognizable test state and capture a named restore point.
34
+ 2. Change a disposable value through the app.
35
+ 3. Open the restore preview. Check additions, removals, overwrites, skipped items, and selected sources.
36
+ 4. Restore, then read the value from the app and inspect the per-source results.
39
37
 
40
- ## Structure
38
+ To repeat one restore many times, tap **Action bar** in the tool header or swipe a restore point and choose **Bar**. The tool minimizes and a floating strip stays over the app with one restore point armed: tap to restore it (long press for state only, without changing screens), `+` to name, capture and arm a new point, the chevron beside the name picks another. Each restore from the strip saves a "Before last restore" copy first and offers Undo for ten seconds. The strip's picker has a "Reload the app after restoring" switch for screens that read their state from storage only on mount (a cart kept in a React context, for example): a live restore cannot reach `useState`, a reload can. The strip returns after a reload and can be dragged or hidden at the screen edge.
41
39
 
42
- ```
43
- time-machine/
44
- ├── src/
45
- │ └── index.ts # Main export file
46
- ├── lib/ # Built output (git ignored)
47
- ├── package.json
48
- ├── tsconfig.json
49
- └── README.md
50
- ```
40
+ Live restore applies supported state in place. Restore + reload writes persisted storage and reloads JavaScript so the app can initialize from that storage. Optional Expo Router URL restoration does not recreate the entire navigation back stack.
41
+
42
+ Each tile in the Sources strip is a switch. A source switched off is left out of every capture and restore, so a store the app cannot survive having replaced (a Zustand store holding live handles, for example) can stay untouched while the rest of the state round-trips.
43
+
44
+ ## Restore limits
45
+
46
+ Restoring can remove keys absent from the snapshot. Exclude values you need to preserve in the preview. Read-only or unsupported values are skipped and reported. SecureStore and MMKV require the same registered keys and instances as the Storage tool.
47
+
48
+ Backend changes, component-local useState, and in-flight requests are not restored. A restored cart or session remains subject to backend validation. Redux needs a supported full-state restore integration; writable Jotai atoms can be restored, while read-only atoms recompute. React Query mutation history is not replayed.
49
+
50
+ ## API
51
+
52
+ For explicit registration, use the exported `createTimeMachineTool` or `timeMachineToolPreset`. Advanced integrations can use `timeMachineStore`, `RestoreOptions`, and `RestoreOutcome`.
53
+
54
+ See the [Time Machine guide](https://buoy.gg/buoy/latest/docs/tools/time-machine) for source-specific behavior, persistent exclusions, and restore results.
55
+
56
+ ## Desktop and MCP
51
57
 
52
- ## Dependencies
58
+ React Native connections require `@buoy-gg/external-sync`. Follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop), sign in to Desktop separately, and confirm the selected device. [MCP](https://buoy.gg/buoy/latest/docs/mcp) also needs a process account and Pro access.
53
59
 
54
- - Uses `@buoy-gg/shared-ui` for common components and utilities
55
- - React and React Native as peer dependencies
60
+ Device data can travel over your LAN to the configured broker. Account validation makes network requests. See [Telemetry](https://buoy.gg/buoy/latest/docs/telemetry) for data-flow details.
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.HeaderBarButton = HeaderBarButton;
7
+ exports.TimeMachineHeaderActions = TimeMachineHeaderActions;
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _core = require("@buoy-gg/core");
11
+ var _HeaderReloadButton = require("./HeaderReloadButton");
12
+ var _TimeMachineFloatingTool = require("./floating/TimeMachineFloatingTool");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ /**
15
+ * The tool's header actions, in Network's header language: labeled
16
+ * dark-glass chips. "Action bar" is named for the thing it opens, the
17
+ * floating action bar over the app, and takes the accent ink as the one
18
+ * action a newcomer has to find. Reload is the plain JS reload.
19
+ *
20
+ * Action bar puts a restore point on the floating bar and gets the modal out
21
+ * of the way. The preview screen passes the point it is showing as `armId`,
22
+ * so opening it from there arms that point; the list leaves it to the
23
+ * default rule.
24
+ */
25
+
26
+ function HeaderBarButton({
27
+ armId
28
+ }) {
29
+ const host = (0, _core.useToolHost)();
30
+ const onPress = () => {
31
+ (0, _TimeMachineFloatingTool.openTimeMachineFloatingTool)(armId);
32
+ // Same move as Network: the bar is for using the app, so the modal
33
+ // minimizes to the dock instead of covering the screen the bar controls.
34
+ host.minimize();
35
+ };
36
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
37
+ style: styles.labeledChip,
38
+ onPress: onPress,
39
+ accessibilityLabel: "Open the floating action bar over the app",
40
+ accessibilityRole: "button",
41
+ testID: "time-machine-header-bar",
42
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
43
+ style: styles.glyph
44
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
45
+ style: styles.label,
46
+ children: "Action bar"
47
+ })]
48
+ });
49
+ }
50
+ function TimeMachineHeaderActions({
51
+ armId
52
+ }) {
53
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
54
+ style: styles.row,
55
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(HeaderBarButton, {
56
+ armId: armId
57
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_HeaderReloadButton.HeaderReloadButton, {})]
58
+ });
59
+ }
60
+ const styles = _reactNative.StyleSheet.create({
61
+ row: {
62
+ flexDirection: "row",
63
+ alignItems: "center",
64
+ gap: 8
65
+ },
66
+ labeledChip: {
67
+ height: 32,
68
+ flexDirection: "row",
69
+ alignItems: "center",
70
+ gap: 6,
71
+ paddingHorizontal: 10,
72
+ borderRadius: 8,
73
+ borderWidth: 1,
74
+ borderColor: _sharedUi.night.color.border,
75
+ backgroundColor: _sharedUi.night.color.buttonSurface
76
+ },
77
+ // A small strip outline: the thing the button opens.
78
+ glyph: {
79
+ width: 14,
80
+ height: 8,
81
+ borderRadius: 3,
82
+ borderWidth: 1.5,
83
+ borderColor: _sharedUi.night.color.accent
84
+ },
85
+ label: {
86
+ fontSize: 11,
87
+ fontWeight: "700",
88
+ color: _sharedUi.night.color.accent
89
+ }
90
+ });
91
+ //# sourceMappingURL=HeaderActions.js.map
@@ -8,10 +8,11 @@ var _reactNative = require("react-native");
8
8
  var _sharedUi = require("@buoy-gg/shared-ui");
9
9
  var _jsxRuntime = require("react/jsx-runtime");
10
10
  /**
11
- * Labeled "RELOAD APP" header action — the one reload affordance in the tool.
12
- * Plain JS-bundle reload (no restore): in-memory state rebuilds from whatever
13
- * is persisted. Registered as the preset's default headerActions, so it shows
14
- * on every screen unless a screen overrides the header actions.
11
+ * The reload header action: a plain JS-bundle reload (no restore), so
12
+ * in-memory state rebuilds from whatever is persisted.
13
+ *
14
+ * A labeled dark-glass chip, the same shape as the Pin chip beside it and
15
+ * Network's "Override": the words are the whole explanation.
15
16
  */
16
17
 
17
18
  function HeaderReloadButton() {
@@ -24,38 +25,34 @@ function HeaderReloadButton() {
24
25
  // No reload mechanism available (bare production build) — nothing to do.
25
26
  }
26
27
  };
27
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
28
- style: styles.button,
28
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
29
+ style: styles.chip,
29
30
  onPress: onPress,
30
- hitSlop: {
31
- top: 8,
32
- bottom: 8,
33
- left: 8,
34
- right: 8
35
- },
36
31
  accessibilityLabel: "Reload app",
32
+ accessibilityHint: "Reloads the JavaScript bundle without restoring anything",
37
33
  accessibilityRole: "button",
38
34
  testID: "time-machine-reload-app",
39
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
40
- size: 12,
41
- color: _sharedUi.night.color.textSecondary
42
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
35
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
43
36
  style: styles.label,
44
- children: "RELOAD APP"
45
- })]
37
+ children: "Reload app"
38
+ })
46
39
  });
47
40
  }
48
41
  const styles = _reactNative.StyleSheet.create({
49
- button: {
50
- flexDirection: "row",
42
+ chip: {
43
+ height: 32,
44
+ paddingHorizontal: 10,
45
+ borderRadius: 8,
46
+ borderWidth: 1,
47
+ borderColor: _sharedUi.night.color.border,
48
+ backgroundColor: _sharedUi.night.color.buttonSurface,
51
49
  alignItems: "center",
52
- gap: 6,
53
- padding: 4
50
+ justifyContent: "center"
54
51
  },
55
52
  label: {
56
- color: _sharedUi.night.color.textSecondary,
57
- fontSize: 12,
58
- fontWeight: "600"
53
+ fontSize: 11,
54
+ fontWeight: "700",
55
+ color: _sharedUi.night.color.textSecondary
59
56
  }
60
57
  });
61
58
  //# sourceMappingURL=HeaderReloadButton.js.map
@@ -8,7 +8,6 @@ var _react = require("react");
8
8
  var _reactNative = require("react-native");
9
9
  var _sharedUi = require("@buoy-gg/shared-ui");
10
10
  var _core = require("@buoy-gg/core");
11
- var _license = require("@buoy-gg/license");
12
11
  var _useTimeMachine = require("../hooks/useTimeMachine");
13
12
  var _timeMachineStore = require("../store/timeMachineStore");
14
13
  var _primitives = require("../ui/primitives");
@@ -22,6 +21,9 @@ var _UndoDeleteBar = require("./list/UndoDeleteBar");
22
21
  var _SourcesStrip = require("./list/SourcesStrip");
23
22
  var _WipeAllRow = require("./list/WipeAllRow");
24
23
  var _PreviewFlow = require("./preview/PreviewFlow");
24
+ var _TimeMachineFloatingTool = require("./floating/TimeMachineFloatingTool");
25
+ var _armedPoint = require("./floating/armedPoint");
26
+ var _sourceSettings = require("../store/sourceSettings");
25
27
  var _jsxRuntime = require("react/jsx-runtime");
26
28
  /**
27
29
  * Time Machine body — the screen router plus the restore-point list.
@@ -37,6 +39,7 @@ var _jsxRuntime = require("react/jsx-runtime");
37
39
  * app-wide wipe kept last and understated.
38
40
  */
39
41
 
42
+ const isBarOpen = () => (0, _sharedUi.getFloatingTools)().some(tool => tool.id === _TimeMachineFloatingTool.TIME_MACHINE_FLOATING_TOOL_ID);
40
43
  function TimeMachineBody() {
41
44
  const [screen, setScreen] = (0, _react.useState)({
42
45
  kind: "list"
@@ -83,21 +86,35 @@ function RestorePointList({
83
86
  hasLoaded,
84
87
  busy,
85
88
  lastOutcome,
86
- recentlyDeleted
89
+ recentlyDeleted,
90
+ remoteProviders
87
91
  } = (0, _useTimeMachine.useTimeMachineState)();
88
- const isPro = (0, _license.useIsPro)();
89
92
  const snapshotLimit = (0, _sharedUi.useLimit)("timeMachine.snapshots");
90
93
  const host = (0, _core.useToolHost)();
91
94
  const insets = (0, _sharedUi.useSafeAreaInsets)({
92
95
  minBottom: 16
93
96
  });
97
+ // Which point the floating restore bar has armed, while the bar is open.
98
+ const barOpen = (0, _react.useSyncExternalStore)(_sharedUi.subscribeFloatingTools, isBarOpen, isBarOpen);
99
+ const pickedId = (0, _react.useSyncExternalStore)(_armedPoint.subscribePickedPoint, _armedPoint.getPickedPointId, _armedPoint.getPickedPointId);
100
+ const armedId = barOpen ? (0, _armedPoint.resolveArmedPoint)(snapshots, pickedId)?.id : undefined;
101
+ // Restoring the same point from this list again and again is what the bar
102
+ // is for; say so once, at the moment it is true.
103
+ const listRestores = (0, _react.useRef)({});
104
+ const [barHintFor, setBarHintFor] = (0, _react.useState)(null);
105
+ const putOnBar = id => {
106
+ setBarHintFor(null);
107
+ (0, _TimeMachineFloatingTool.openTimeMachineFloatingTool)(id);
108
+ host.minimize();
109
+ };
94
110
 
95
111
  // Header search (when hosted by the core shell) filters the list.
96
112
  const query = host.search.trim().toLowerCase();
97
113
  const visible = query ? snapshots.filter(s => s.name.toLowerCase().includes(query)) : snapshots;
98
114
 
99
- // Infinity for Pro, so this is simply false for them.
100
- const atCaptureLimit = snapshots.length >= snapshotLimit;
115
+ // Infinity for Pro, so this is simply false for them. The bar's safety
116
+ // slot is not a point you chose to keep, so it does not count.
117
+ const atCaptureLimit = snapshots.filter(s => !s.safety).length >= snapshotLimit;
101
118
  const captureDisabled = busy !== null || atCaptureLimit;
102
119
 
103
120
  /**
@@ -117,12 +134,16 @@ function RestorePointList({
117
134
  (0, _confirm.confirmDestructive)("Restore fresh install?", "This wipes all app storage and reloads the app.", "Wipe & reload", () => void _timeMachineStore.timeMachineStore.restore(meta.id, "reload"));
118
135
  return;
119
136
  }
137
+ const count = (listRestores.current[meta.id] ?? 0) + 1;
138
+ listRestores.current[meta.id] = count;
139
+ if (count >= 2 && !barOpen) setBarHintFor(meta.id);
120
140
  void _timeMachineStore.timeMachineStore.restore(meta.id, "live").then(outcome => {
121
141
  const clean = Object.values(outcome.results).every(r => r.ok);
122
142
  if (!clean || outcome.willReload) return;
123
143
  host.minimize();
124
144
  });
125
145
  };
146
+ const barHint = barHintFor && !barOpen ? snapshots.find(s => s.id === barHintFor) ?? null : null;
126
147
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
127
148
  style: styles.fill,
128
149
  contentContainerStyle: [styles.content, {
@@ -152,24 +173,47 @@ function RestorePointList({
152
173
  }), atCaptureLimit ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Footnote, {
153
174
  tone: "warning",
154
175
  testID: "time-machine-limit",
155
- children: ["Free plan keeps ", snapshotLimit, " restore points. Upgrade to Pro for unlimited snapshots."]
176
+ children: ["Free plan keeps ", snapshotLimit, " restore point", snapshotLimit === 1 ? "" : "s", ". Upgrade to Pro for unlimited snapshots."]
156
177
  }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
157
178
  children: "Sources"
158
179
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_SourcesStrip.SourcesStrip, {
159
- sources: sources
160
- }), sources.filter(s => s.detail && !(s.canCapture && s.canRestore)).map(s => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Footnote, {
161
- testID: `time-machine-source-${s.id}-detail`,
162
- children: [s.label, ": ", s.detail]
163
- }, s.id)), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
180
+ sources: sources,
181
+ onToggle: remoteProviders ? undefined : _sourceSettings.setSourceEnabled
182
+ }), remoteProviders ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Footnote, {
183
+ testID: "time-machine-sources-hint",
184
+ children: "Switch a source off to leave it out of every capture and restore."
185
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
164
186
  children: "Restore Points"
165
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Group, {
187
+ }), barHint ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
188
+ style: styles.barHint,
189
+ testID: "time-machine-bar-hint",
190
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Footnote, {
191
+ children: ["Restoring \"", barHint.name, "\" a lot? Put it on the floating action bar and restore it with one tap."]
192
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
193
+ onPress: () => putOnBar(barHint.id),
194
+ accessibilityRole: "button",
195
+ testID: "time-machine-bar-hint-pin",
196
+ hitSlop: {
197
+ top: 6,
198
+ bottom: 6,
199
+ left: 6,
200
+ right: 6
201
+ },
202
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
203
+ style: styles.barHintAction,
204
+ children: "Open the action bar"
205
+ })
206
+ })]
207
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Group, {
166
208
  children: [visible.map(meta => /*#__PURE__*/(0, _jsxRuntime.jsx)(_SnapshotRow.SnapshotRow, {
167
209
  meta: meta,
168
210
  busy: busy !== null,
211
+ armed: meta.id === armedId,
169
212
  onOpen: () => onOpenPreview(meta),
170
213
  onRestore: () => restore(meta),
171
214
  onDelete: () => void _timeMachineStore.timeMachineStore.delete(meta.id),
172
- onDuplicate: () => void _timeMachineStore.timeMachineStore.duplicate(meta.id)
215
+ onDuplicate: () => void _timeMachineStore.timeMachineStore.duplicate(meta.id),
216
+ onArm: () => putOnBar(meta.id)
173
217
  }, meta.id)), visible.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
174
218
  testID: "time-machine-empty",
175
219
  ...(!hasLoaded ? {
@@ -205,6 +249,17 @@ function RestorePointList({
205
249
  });
206
250
  }
207
251
  const styles = _reactNative.StyleSheet.create({
252
+ barHint: {
253
+ marginHorizontal: _tmTheme.tm.gutter,
254
+ marginBottom: 8
255
+ },
256
+ barHintAction: {
257
+ color: _tmTheme.tm.color.accent,
258
+ fontSize: _tmTheme.tm.font.footnote,
259
+ fontWeight: "600",
260
+ paddingHorizontal: _tmTheme.tm.textInset,
261
+ paddingTop: 2
262
+ },
208
263
  fill: {
209
264
  flex: 1,
210
265
  // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NamePromptModal = NamePromptModal;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _jsxRuntime = require("react/jsx-runtime");
11
+ /**
12
+ * "Name this restore point": the dialog `+` on the restore bar opens.
13
+ *
14
+ * A real Modal rather than a panel under the strip, so it sits centered over
15
+ * the app, above the keyboard, wherever the strip happens to be. An empty
16
+ * name is allowed: the store then names the point after the screen and the
17
+ * time, the same default the tool's capture bar uses.
18
+ */
19
+
20
+ function NamePromptModal({
21
+ visible,
22
+ summary,
23
+ onCapture,
24
+ onCancel
25
+ }) {
26
+ const [name, setName] = (0, _react.useState)("");
27
+ // Every opening starts blank, so a cancelled name never comes back.
28
+ (0, _react.useEffect)(() => {
29
+ if (visible) setName("");
30
+ }, [visible]);
31
+ const submit = () => onCapture(name.trim());
32
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
33
+ visible: visible,
34
+ transparent: true,
35
+ animationType: "fade",
36
+ onRequestClose: onCancel,
37
+ statusBarTranslucent: true,
38
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.KeyboardAvoidingView, {
39
+ behavior: _reactNative.Platform.OS === "ios" ? "padding" : undefined,
40
+ style: styles.fill,
41
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
42
+ style: styles.backdrop,
43
+ onPress: onCancel,
44
+ accessibilityLabel: "Cancel",
45
+ testID: "time-machine-bar-name-backdrop",
46
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
47
+ style: styles.card,
48
+ onPress: event => event.stopPropagation(),
49
+ testID: "time-machine-bar-name-panel"
50
+ // A Pressable is one accessibility element by default, which
51
+ // hid the field and both buttons from VoiceOver and from tests.
52
+ ,
53
+ accessible: false,
54
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
55
+ style: styles.title,
56
+ children: "Name this restore point"
57
+ }), summary ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
58
+ style: styles.summary,
59
+ testID: "time-machine-bar-name-summary",
60
+ children: summary
61
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
62
+ testID: "time-machine-bar-name-input",
63
+ accessibilityLabel: "Restore point name",
64
+ value: name,
65
+ onChangeText: setName,
66
+ onSubmitEditing: submit,
67
+ placeholder: "Screen and time, if left empty",
68
+ placeholderTextColor: _sharedUi.night.color.placeholder,
69
+ autoFocus: true,
70
+ autoCorrect: false
71
+ // Names are labels, not sentences: "bag" should stay "bag".
72
+ ,
73
+ autoCapitalize: "none",
74
+ returnKeyType: "done",
75
+ style: styles.input
76
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
77
+ style: styles.hint,
78
+ children: "Captures the state the app is in right now and arms it on the bar."
79
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
80
+ style: styles.actions,
81
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
82
+ testID: "time-machine-bar-name-cancel",
83
+ accessibilityRole: "button",
84
+ onPress: onCancel,
85
+ style: styles.secondary,
86
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
87
+ style: styles.secondaryText,
88
+ children: "Cancel"
89
+ })
90
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
91
+ testID: "time-machine-bar-name-capture",
92
+ accessibilityRole: "button",
93
+ accessibilityLabel: "Capture and arm this restore point",
94
+ onPress: submit,
95
+ style: styles.primary,
96
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
97
+ style: styles.primaryText,
98
+ children: "Capture"
99
+ })
100
+ })]
101
+ })]
102
+ })
103
+ })
104
+ })
105
+ });
106
+ }
107
+ const styles = _reactNative.StyleSheet.create({
108
+ fill: {
109
+ flex: 1
110
+ },
111
+ backdrop: {
112
+ flex: 1,
113
+ backgroundColor: "rgba(0, 0, 0, 0.55)",
114
+ alignItems: "center",
115
+ justifyContent: "center",
116
+ padding: 24
117
+ },
118
+ card: {
119
+ width: "100%",
120
+ maxWidth: 360,
121
+ borderRadius: _sharedUi.night.radius.sheet,
122
+ borderWidth: 1,
123
+ borderColor: _sharedUi.night.color.separator,
124
+ backgroundColor: _sharedUi.night.color.surface,
125
+ padding: 18,
126
+ gap: 12,
127
+ shadowColor: "#000",
128
+ shadowOffset: {
129
+ width: 0,
130
+ height: 12
131
+ },
132
+ shadowOpacity: 0.4,
133
+ shadowRadius: 24,
134
+ elevation: 12
135
+ },
136
+ title: {
137
+ color: _sharedUi.night.color.text,
138
+ fontSize: _sharedUi.night.font.title,
139
+ fontWeight: "600"
140
+ },
141
+ input: {
142
+ minHeight: 44,
143
+ borderRadius: _sharedUi.night.radius.input,
144
+ borderWidth: 1,
145
+ borderColor: _sharedUi.night.color.accentBorder,
146
+ backgroundColor: _sharedUi.night.color.surfaceElevated,
147
+ color: _sharedUi.night.color.text,
148
+ fontSize: _sharedUi.night.font.row,
149
+ paddingHorizontal: 12
150
+ },
151
+ summary: {
152
+ color: _sharedUi.night.color.textTertiary,
153
+ fontSize: _sharedUi.night.font.caption,
154
+ marginTop: -6
155
+ },
156
+ hint: {
157
+ color: _sharedUi.night.color.textSecondary,
158
+ fontSize: _sharedUi.night.font.caption,
159
+ lineHeight: 18
160
+ },
161
+ actions: {
162
+ flexDirection: "row",
163
+ justifyContent: "flex-end",
164
+ gap: 10,
165
+ marginTop: 4
166
+ },
167
+ secondary: {
168
+ minHeight: 40,
169
+ paddingHorizontal: 14,
170
+ borderRadius: _sharedUi.night.radius.input,
171
+ alignItems: "center",
172
+ justifyContent: "center"
173
+ },
174
+ secondaryText: {
175
+ color: _sharedUi.night.color.textSecondary,
176
+ fontSize: 14,
177
+ fontWeight: "600"
178
+ },
179
+ primary: {
180
+ minHeight: 40,
181
+ paddingHorizontal: 18,
182
+ borderRadius: _sharedUi.night.radius.input,
183
+ backgroundColor: _sharedUi.night.color.accent,
184
+ alignItems: "center",
185
+ justifyContent: "center"
186
+ },
187
+ primaryText: {
188
+ color: _sharedUi.night.color.onAccent,
189
+ fontSize: 14,
190
+ fontWeight: "700"
191
+ }
192
+ });
193
+ //# sourceMappingURL=NamePromptModal.js.map