@buoy-gg/time-machine 7.0.35 → 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 +11 -8
  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
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ensurePickedPointLoaded = ensurePickedPointLoaded;
7
+ exports.getPickedPointId = getPickedPointId;
8
+ exports.resetPickedPointForTests = resetPickedPointForTests;
9
+ exports.resolveArmedPoint = resolveArmedPoint;
10
+ exports.setPickedPointId = setPickedPointId;
11
+ exports.subscribePickedPoint = subscribePickedPoint;
12
+ var _sharedUi = require("@buoy-gg/shared-ui");
13
+ /**
14
+ * Which restore point the floating restore bar restores on tap.
15
+ *
16
+ * The bar needs an answer before the user picks anything, or its first tap is
17
+ * a picker instead of a restore. So the armed point is resolved, not just
18
+ * stored: an explicit pick wins, then the point restored most recently, then
19
+ * the newest capture. Baselines never resolve; restoring one wipes storage
20
+ * and reloads behind a confirm dialog, which is not a one-tap action. The
21
+ * safety slot never resolves either: Undo is its way in.
22
+ *
23
+ * The explicit pick is persisted so it survives the reloads the loop itself
24
+ * causes (RELOAD APP, reload-mode restores).
25
+ */
26
+
27
+ const KEY = _sharedUi.devToolsStorageKeys.timeMachine.armedPoint();
28
+ let pickedId = null;
29
+ let loaded = false;
30
+ let loadPromise = null;
31
+ const listeners = new Set();
32
+ const notify = () => listeners.forEach(listener => listener());
33
+ function getPickedPointId() {
34
+ return pickedId;
35
+ }
36
+ function subscribePickedPoint(listener) {
37
+ listeners.add(listener);
38
+ return () => {
39
+ listeners.delete(listener);
40
+ };
41
+ }
42
+
43
+ /** Read the saved pick once. Later calls return the same promise. */
44
+ function ensurePickedPointLoaded() {
45
+ if (loaded) return Promise.resolve();
46
+ if (!loadPromise) {
47
+ loadPromise = _sharedUi.persistentStorage.getItem(KEY).then(saved => {
48
+ // A pick made while the read was in flight wins over the disk copy.
49
+ if (pickedId === null && saved) {
50
+ pickedId = saved;
51
+ notify();
52
+ }
53
+ }).catch(() => {}).finally(() => {
54
+ loaded = true;
55
+ });
56
+ }
57
+ return loadPromise;
58
+ }
59
+ function setPickedPointId(id) {
60
+ if (pickedId === id) return;
61
+ pickedId = id;
62
+ notify();
63
+ void (id === null ? _sharedUi.persistentStorage.removeItem(KEY) : _sharedUi.persistentStorage.setItem(KEY, id)).catch(() => {});
64
+ }
65
+
66
+ /** Test seam: forget the pick and the load, without touching storage. */
67
+ function resetPickedPointForTests() {
68
+ pickedId = null;
69
+ loaded = false;
70
+ loadPromise = null;
71
+ }
72
+
73
+ /**
74
+ * The point the bar restores: the saved pick if it still exists, else the
75
+ * most recently restored point, else the newest capture. Never a baseline.
76
+ */
77
+ function resolveArmedPoint(snapshots, pickedId) {
78
+ const candidates = snapshots.filter(meta => !meta.baseline && !meta.safety);
79
+ if (candidates.length === 0) return null;
80
+ if (pickedId) {
81
+ const picked = candidates.find(meta => meta.id === pickedId);
82
+ if (picked) return picked;
83
+ }
84
+ let best = null;
85
+ for (const meta of candidates) {
86
+ if (meta.lastRestoredAt === undefined) continue;
87
+ if (!best || meta.lastRestoredAt > (best.lastRestoredAt ?? 0)) best = meta;
88
+ }
89
+ if (best) return best;
90
+ return candidates.reduce((newest, meta) => meta.createdAt > newest.createdAt ? meta : newest);
91
+ }
92
+ //# sourceMappingURL=armedPoint.js.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ensureBarSettingsLoaded = ensureBarSettingsLoaded;
7
+ exports.getBarReloadMode = getBarReloadMode;
8
+ exports.resetBarSettingsForTests = resetBarSettingsForTests;
9
+ exports.setBarReloadMode = setBarReloadMode;
10
+ exports.subscribeBarSettings = subscribeBarSettings;
11
+ var _sharedUi = require("@buoy-gg/shared-ui");
12
+ /**
13
+ * The action bar's one setting: restore live, or restore and reload.
14
+ *
15
+ * Live restore replaces stores in place and is right for state that lives in
16
+ * Redux, Zustand, Jotai or the query cache. It cannot reach a React context
17
+ * or `useState` that hydrates itself from storage once at mount: the
18
+ * storage key comes back, the screen keeps showing what it already had, and
19
+ * "restore did nothing" is the report. A restore that reloads the app makes
20
+ * those screens rebuild from the restored storage. Persisted, because the
21
+ * reload itself is what brings the bar back.
22
+ */
23
+
24
+ const KEY = _sharedUi.devToolsStorageKeys.timeMachine.barReloadMode();
25
+ let reloadMode = false;
26
+ let loaded = false;
27
+ let loadPromise = null;
28
+ const listeners = new Set();
29
+ const notify = () => listeners.forEach(listener => listener());
30
+ function getBarReloadMode() {
31
+ return reloadMode;
32
+ }
33
+ function subscribeBarSettings(listener) {
34
+ listeners.add(listener);
35
+ return () => {
36
+ listeners.delete(listener);
37
+ };
38
+ }
39
+ function ensureBarSettingsLoaded() {
40
+ if (loaded) return Promise.resolve();
41
+ if (!loadPromise) {
42
+ loadPromise = _sharedUi.persistentStorage.getItem(KEY).then(saved => {
43
+ if (!reloadMode && saved === "1") {
44
+ reloadMode = true;
45
+ notify();
46
+ }
47
+ }).catch(() => {}).finally(() => {
48
+ loaded = true;
49
+ });
50
+ }
51
+ return loadPromise;
52
+ }
53
+ function setBarReloadMode(on) {
54
+ if (on === reloadMode) return;
55
+ reloadMode = on;
56
+ notify();
57
+ void _sharedUi.persistentStorage.setItem(KEY, on ? "1" : "0").catch(() => {});
58
+ }
59
+
60
+ /** Test seam. */
61
+ function resetBarSettingsForTests() {
62
+ reloadMode = false;
63
+ loaded = false;
64
+ loadPromise = null;
65
+ }
66
+ //# sourceMappingURL=barSettings.js.map
@@ -61,6 +61,8 @@ const styles = _reactNative.StyleSheet.create({
61
61
  marginHorizontal: _tmTheme.tm.gutter,
62
62
  backgroundColor: _tmTheme.tm.color.group,
63
63
  borderRadius: _tmTheme.tm.radius.group,
64
+ borderWidth: 1,
65
+ borderColor: _tmTheme.tm.color.groupBorder,
64
66
  paddingLeft: _tmTheme.tm.textInset,
65
67
  padding: 6
66
68
  },
@@ -136,6 +136,8 @@ const styles = _reactNative.StyleSheet.create({
136
136
  marginTop: 12,
137
137
  backgroundColor: _tmTheme.tm.color.group,
138
138
  borderRadius: _tmTheme.tm.radius.group,
139
+ borderWidth: 1,
140
+ borderColor: _tmTheme.tm.color.groupBorder,
139
141
  padding: 14,
140
142
  gap: 4
141
143
  },
@@ -32,20 +32,29 @@ var _jsxRuntime = require("react/jsx-runtime");
32
32
  // A saved selection is worth stating on the row — this restore point no
33
33
  // longer does the obvious thing, and one tap on RESTORE honors it.
34
34
  if (meta.excludedKeys?.length) parts.push(`skips ${meta.excludedKeys.length}`);else if (meta.scope?.length) parts.push(`scoped (${meta.scope.length})`);
35
+ // The bar's own undo slot: kept for one restore, not a point you chose.
36
+ if (meta.safety) parts.unshift("safety copy");
35
37
  return parts.join(" · ");
36
38
  }
37
39
  function SnapshotRow({
38
40
  meta,
39
41
  busy,
42
+ armed = false,
40
43
  onOpen,
41
44
  onRestore,
42
45
  onDelete,
43
- onDuplicate
46
+ onDuplicate,
47
+ onArm
44
48
  }) {
45
49
  const testID = `time-machine-snap-${meta.id}`;
46
50
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_SwipeActionsRow.SwipeActionsRow, {
47
51
  testID: testID,
48
52
  actions: [
53
+ // "Bar": put this point on the floating action bar.
54
+ {
55
+ label: "Bar",
56
+ onPress: onArm
57
+ },
49
58
  // "Dup" over "Copy": on a row full of text, "Copy" reads as
50
59
  // copy-to-clipboard.
51
60
  {
@@ -57,21 +66,26 @@ function SnapshotRow({
57
66
  background: _tmTheme.tm.color.danger,
58
67
  color: _tmTheme.tm.color.onDanger
59
68
  }],
60
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
69
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
61
70
  style: styles.row,
62
- onPress: onOpen,
63
- activeOpacity: 0.7,
64
- accessibilityRole: "button",
65
- accessibilityLabel: `${meta.name}. ${describe(meta)}. Open restore preview`,
66
- testID: testID,
67
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
71
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
68
72
  style: styles.text,
73
+ onPress: onOpen,
74
+ activeOpacity: 0.7,
75
+ accessibilityRole: "button",
76
+ accessibilityLabel: `${meta.name}. ${describe(meta)}. Open restore preview`,
77
+ testID: testID,
69
78
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
70
79
  style: styles.name,
71
80
  numberOfLines: 1,
72
81
  testID: `${testID}-name`,
73
82
  children: meta.name
74
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
83
+ }), armed ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
84
+ style: styles.armed,
85
+ numberOfLines: 1,
86
+ testID: `${testID}-armed`,
87
+ children: "On the action bar"
88
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
75
89
  style: styles.meta,
76
90
  numberOfLines: 1,
77
91
  testID: `${testID}-meta`,
@@ -90,7 +104,7 @@ function SnapshotRow({
90
104
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
91
105
  onPress: onRestore,
92
106
  disabled: busy,
93
- style: busy ? styles.actionDisabled : null,
107
+ style: [styles.restore, busy ? styles.actionDisabled : null],
94
108
  accessibilityRole: "button",
95
109
  accessibilityLabel: meta.baseline ? "Wipe and reload" : "Restore",
96
110
  testID: `${testID}-restore`,
@@ -109,12 +123,17 @@ const styles = _reactNative.StyleSheet.create({
109
123
  flexDirection: "row",
110
124
  alignItems: "center",
111
125
  gap: 10,
112
- paddingHorizontal: _tmTheme.tm.textInset,
113
- paddingVertical: 11
126
+ paddingHorizontal: _tmTheme.tm.textInset
114
127
  },
128
+ // The text block is the tallest child, so its padding sets the row height
129
+ // and the open target spans the full row.
115
130
  text: {
116
131
  flex: 1,
117
- minWidth: 0
132
+ minWidth: 0,
133
+ paddingVertical: 11
134
+ },
135
+ restore: {
136
+ justifyContent: "center"
118
137
  },
119
138
  name: {
120
139
  color: _tmTheme.tm.color.label,
@@ -133,6 +152,11 @@ const styles = _reactNative.StyleSheet.create({
133
152
  fontSize: _tmTheme.tm.font.footnote,
134
153
  marginTop: 2
135
154
  },
155
+ armed: {
156
+ color: _tmTheme.tm.color.accent,
157
+ fontSize: _tmTheme.tm.font.footnote,
158
+ marginTop: 2
159
+ },
136
160
  route: {
137
161
  color: _tmTheme.tm.color.labelTertiary,
138
162
  fontFamily: _tmTheme.TM_MONO,
@@ -5,124 +5,168 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.SourcesStrip = SourcesStrip;
7
7
  var _reactNative = require("react-native");
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
9
+ var _primitives = require("../../ui/primitives");
8
10
  var _tmTheme = require("../../ui/tmTheme");
9
11
  var _sources = require("../sources");
10
12
  var _jsxRuntime = require("react/jsx-runtime");
11
13
  /**
12
- * Sources — one horizontally scrollable line of what a capture will pick up.
14
+ * Sources — one settings row per source, each with a switch.
13
15
  *
14
- * Each source is its own tile (icon over name) rather than a count behind a
15
- * disclosure: "is Redux wired?" is a glance question, and the answer should
16
- * not cost a screen. A source that can't fully participate dims and carries a
17
- * status dot, with the reason spelled out underneath the strip.
16
+ * The iOS Settings shape the rest of the tool uses, because that is what a
17
+ * list of things you can turn on and off looks like: icon, name, a line of
18
+ * status, and a switch at the trailing edge. Off means every capture and
19
+ * restore leaves that source alone, for the store whose full replace the
20
+ * app cannot survive.
21
+ *
22
+ * Only sources the app has wired are listed; a library the app does not use
23
+ * is not a choice to make. Icons keep full brightness in every state. Under
24
+ * each name is what the source holds, in plain words; a short state line
25
+ * appears only when something is off or capture-only.
18
26
  */
19
27
 
20
- function statusDotColor(source) {
21
- if (!source.canCapture) return _tmTheme.tm.color.labelTertiary; // not detected
22
- if (!source.canRestore) return _tmTheme.tm.color.warning; // capture only
23
- return null; // ready — nothing to flag
28
+ /**
29
+ * What each source holds, in words for someone who has never heard of the
30
+ * library. The library name is the row title; this line is the meaning.
31
+ */const DESCRIPTIONS = {
32
+ storage: "Settings, sign-in and other things saved on the device",
33
+ redux: "Data the whole app shares between screens",
34
+ zustand: "Data the whole app shares between screens",
35
+ jotai: "Small pieces of data screens share",
36
+ query: "Data the app loaded from the server",
37
+ route: "Which screen you were on"
38
+ };
39
+ function describe(source) {
40
+ return DESCRIPTIONS[source.id] ?? "App data";
24
41
  }
25
- function SourceTile({
26
- source
42
+
43
+ /**
44
+ * The short state under the description, when there is one worth saying.
45
+ * "Working" is the default and needs no line.
46
+ */
47
+ function stateLine(source) {
48
+ if (!source.enabled) return {
49
+ text: "Off · left as it is",
50
+ tone: "off"
51
+ };
52
+ if (!source.canRestore) return {
53
+ text: "Can be saved but not put back",
54
+ tone: "warn"
55
+ };
56
+ return null;
57
+ }
58
+ function SourceRow({
59
+ source,
60
+ onToggle
27
61
  }) {
28
62
  const Icon = (0, _sources.sourceIcon)(source.id);
29
- const dot = statusDotColor(source);
63
+ const description = describe(source);
64
+ const state = stateLine(source);
30
65
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
31
- style: styles.tile,
66
+ style: styles.row,
32
67
  testID: `time-machine-source-${source.id}`,
33
- accessibilityLabel: `${source.label}: ${source.canCapture ? source.canRestore ? "capture and restore" : "capture only" : "not detected"}`,
34
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
35
- style: [styles.disc, !source.canCapture ? styles.discOff : null],
36
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Icon, {
37
- size: 26,
38
- bgColor: _tmTheme.tm.color.fill
39
- }), dot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
40
- style: [styles.dot, {
41
- backgroundColor: dot
42
- }]
68
+ accessibilityLabel: `${source.label}: ${description}${state ? `. ${state.text}` : ""}`,
69
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
70
+ style: styles.disc,
71
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Icon, {
72
+ size: 22,
73
+ bgColor: _tmTheme.tm.color.disc
74
+ })
75
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
76
+ style: styles.text,
77
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
78
+ style: styles.label,
79
+ numberOfLines: 1,
80
+ testID: `time-machine-source-${source.id}-label`,
81
+ children: source.label
82
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
83
+ style: styles.description,
84
+ numberOfLines: 2,
85
+ testID: `time-machine-source-${source.id}-description`,
86
+ children: description
87
+ }), state ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
88
+ style: [styles.state, styles[`state_${state.tone}`]],
89
+ numberOfLines: 1,
90
+ testID: state.tone === "off" ? `time-machine-source-${source.id}-off` : `time-machine-source-${source.id}-detail`,
91
+ children: state.text
43
92
  }) : null]
44
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
45
- style: [styles.label, !source.canCapture ? styles.labelOff : null],
46
- numberOfLines: 1,
47
- testID: `time-machine-source-${source.id}-label`,
48
- children: source.label
93
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
94
+ testID: `time-machine-source-${source.id}-switch`,
95
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.NightSwitch, {
96
+ value: source.enabled
97
+ // The desktop mirror shows the device's setting and cannot flip it.
98
+ ,
99
+ disabled: !onToggle,
100
+ onValueChange: next => onToggle?.(source.id, next)
101
+ })
49
102
  })]
50
103
  });
51
104
  }
52
105
  function SourcesStrip({
53
- sources
106
+ sources,
107
+ onToggle
54
108
  }) {
55
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
56
- style: styles.card,
57
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
58
- horizontal: true,
59
- showsHorizontalScrollIndicator: false,
60
- contentContainerStyle: styles.strip,
61
- testID: "time-machine-sources",
62
- children: [sources.map(source => /*#__PURE__*/(0, _jsxRuntime.jsx)(SourceTile, {
63
- source: source
64
- }, source.id)), sources.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
65
- style: styles.empty,
66
- testID: "time-machine-sources-empty",
67
- children: "No state tools installed."
68
- }) : null]
69
- })
109
+ // A source the app has not wired (no Redux Provider, no Zustand stores)
110
+ // is not listed: there is nothing to capture and nothing to switch off.
111
+ const wired = sources.filter(source => source.canCapture);
112
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.Group, {
113
+ children: [wired.map(source => /*#__PURE__*/(0, _jsxRuntime.jsx)(SourceRow, {
114
+ source: source,
115
+ onToggle: onToggle
116
+ }, source.id)), wired.length === 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
117
+ style: styles.row,
118
+ testID: "time-machine-sources-empty",
119
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
120
+ style: styles.description,
121
+ children: "No state tools are set up in this app."
122
+ })
123
+ }) : null]
70
124
  });
71
125
  }
72
126
  const styles = _reactNative.StyleSheet.create({
73
- card: {
74
- marginHorizontal: _tmTheme.tm.gutter,
75
- backgroundColor: _tmTheme.tm.color.group,
76
- borderRadius: _tmTheme.tm.radius.group,
77
- paddingVertical: 12
78
- },
79
- strip: {
127
+ row: {
80
128
  flexDirection: "row",
81
- alignItems: "flex-start",
82
- paddingHorizontal: _tmTheme.tm.textInset,
83
- gap: 18
84
- },
85
- tile: {
86
129
  alignItems: "center",
87
- gap: 7,
88
- // Fixed width so the row reads as a grid, and wide enough for the
89
- // longest source name on ONE line: a second line would be blank for
90
- // every other tile while still setting the card's height.
91
- width: 78
130
+ gap: 12,
131
+ minHeight: _tmTheme.tm.rowMinHeight,
132
+ paddingHorizontal: _tmTheme.tm.textInset,
133
+ paddingVertical: 10
92
134
  },
93
135
  disc: {
94
- width: 50,
95
- height: 50,
136
+ width: 36,
137
+ height: 36,
96
138
  borderRadius: _tmTheme.tm.radius.pill,
97
- backgroundColor: _tmTheme.tm.color.fill,
139
+ backgroundColor: _tmTheme.tm.color.disc,
98
140
  alignItems: "center",
99
141
  justifyContent: "center"
100
142
  },
101
- discOff: {
102
- opacity: 0.35
103
- },
104
- dot: {
105
- position: "absolute",
106
- top: 2,
107
- right: 2,
108
- width: 10,
109
- height: 10,
110
- borderRadius: 5,
111
- // Ring it in the card color so it reads as a badge, not a smudge.
112
- borderWidth: 2,
113
- borderColor: _tmTheme.tm.color.group
143
+ text: {
144
+ flex: 1,
145
+ minWidth: 0,
146
+ gap: 2
114
147
  },
115
148
  label: {
116
- color: _tmTheme.tm.color.labelSecondary,
149
+ color: _tmTheme.tm.color.label,
150
+ fontSize: _tmTheme.tm.font.row,
151
+ fontWeight: "500",
152
+ letterSpacing: _tmTheme.tm.tracking.row
153
+ },
154
+ description: {
155
+ fontSize: _tmTheme.tm.font.footnote,
156
+ lineHeight: _tmTheme.tm.font.footnote * 1.3,
157
+ color: _tmTheme.tm.color.labelSecondary
158
+ },
159
+ state: {
117
160
  fontSize: _tmTheme.tm.font.mono,
118
- textAlign: "center"
161
+ lineHeight: _tmTheme.tm.font.mono * 1.3,
162
+ fontWeight: "600",
163
+ marginTop: 1
119
164
  },
120
- labelOff: {
165
+ state_off: {
121
166
  color: _tmTheme.tm.color.labelTertiary
122
167
  },
123
- empty: {
124
- color: _tmTheme.tm.color.labelSecondary,
125
- fontSize: _tmTheme.tm.font.footnote
168
+ state_warn: {
169
+ color: _tmTheme.tm.color.warning
126
170
  }
127
171
  });
128
172
  //# sourceMappingURL=SourcesStrip.js.map
@@ -65,7 +65,9 @@ const styles = _reactNative.StyleSheet.create({
65
65
  minHeight: 46,
66
66
  paddingHorizontal: _tmTheme.tm.textInset,
67
67
  backgroundColor: _tmTheme.tm.color.group,
68
- borderRadius: _tmTheme.tm.radius.group
68
+ borderRadius: _tmTheme.tm.radius.group,
69
+ borderWidth: 1,
70
+ borderColor: _tmTheme.tm.color.groupBorder
69
71
  },
70
72
  text: {
71
73
  flex: 1,
@@ -8,6 +8,7 @@ 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 _HeaderActions = require("../HeaderActions");
11
12
  var _primitives = require("../../ui/primitives");
12
13
  var _tmTheme = require("../../ui/tmTheme");
13
14
  var _timeMachineStore = require("../../store/timeMachineStore");
@@ -384,10 +385,14 @@ function RestorePreviewScreen({
384
385
  (0, _core.useToolHeader)(hosted ? {
385
386
  title: "Restore Preview",
386
387
  subtitle: rightMeta?.name ?? "preview",
387
- onBack
388
+ onBack,
389
+ // BAR from here arms the point being previewed.
390
+ actions: /*#__PURE__*/(0, _jsxRuntime.jsx)(_HeaderActions.TimeMachineHeaderActions, {
391
+ armId: rightMeta?.id
392
+ })
388
393
  } : null,
389
394
  // eslint-disable-next-line react-hooks/exhaustive-deps
390
- [hosted, rightMeta?.name]);
395
+ [hosted, rightMeta?.name, rightMeta?.id]);
391
396
  const {
392
397
  rows,
393
398
  blockedSources
@@ -3,13 +3,21 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.ROUTE_SOURCE_ID = void 0;
6
+ Object.defineProperty(exports, "ROUTE_SOURCE_ID", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _sourceIds.ROUTE_SOURCE_ID;
10
+ }
11
+ });
7
12
  exports.isAttached = isAttached;
8
13
  exports.sourceIcon = sourceIcon;
9
14
  exports.useSourceStatuses = useSourceStatuses;
10
15
  var _floatingToolsCore = require("@buoy-gg/floating-tools-core");
16
+ var _react = require("react");
11
17
  var _useTimeMachine = require("../hooks/useTimeMachine");
12
18
  var _routeRestore = require("../store/routeRestore");
19
+ var _sourceIds = require("../store/sourceIds");
20
+ var _sourceSettings = require("../store/sourceSettings");
13
21
  /**
14
22
  * What Time Machine can capture from, and how healthy each source is.
15
23
  *
@@ -38,26 +46,29 @@ const ICONS = {
38
46
  function sourceIcon(id) {
39
47
  return ICONS[id] ?? _floatingToolsCore.TimeMachineIcon;
40
48
  }
41
-
42
- /** Id of the route tile — a device capability, not a snapshot provider. */
43
- const ROUTE_SOURCE_ID = exports.ROUTE_SOURCE_ID = "route";
44
49
  function useSourceStatuses() {
45
50
  const {
46
51
  remoteProviders,
47
52
  remoteRoute
48
53
  } = (0, _useTimeMachine.useTimeMachineState)();
49
54
  const localProviders = (0, _useTimeMachine.useSnapshotProviders)();
50
- const providers = remoteProviders ?? localProviders.map(p => ({
55
+ const disabled = (0, _react.useSyncExternalStore)(_sourceSettings.subscribeSourceSettings, _sourceSettings.getDisabledSources, _sourceSettings.getDisabledSources);
56
+ const providers = remoteProviders ? remoteProviders.map(p => ({
57
+ ...p,
58
+ enabled: p.enabled !== false
59
+ })) : localProviders.map(p => ({
51
60
  id: p.id,
52
61
  label: p.label,
53
- ...p.status()
62
+ ...p.status(),
63
+ enabled: !disabled.has(p.id)
54
64
  }));
55
65
  const routeAvailable = remoteProviders ? remoteRoute?.available === true : (0, _routeRestore.canNavigate)();
56
66
  return [...providers, {
57
- id: ROUTE_SOURCE_ID,
67
+ id: _sourceIds.ROUTE_SOURCE_ID,
58
68
  label: "Route",
59
69
  canCapture: routeAvailable,
60
70
  canRestore: routeAvailable,
71
+ enabled: remoteProviders ? remoteRoute?.enabled !== false : !disabled.has(_sourceIds.ROUTE_SOURCE_ID),
61
72
  // No live pathname here: these screens only re-render on store changes,
62
73
  // so a route printed as status goes stale the moment you navigate with
63
74
  // the tool still open. Each restore point shows the route it holds.
@@ -67,6 +78,6 @@ function useSourceStatuses() {
67
78
 
68
79
  /** A source is "attached" when a snapshot would actually pick it up. */
69
80
  function isAttached(source) {
70
- return source.canCapture;
81
+ return source.canCapture && source.enabled;
71
82
  }
72
83
  //# sourceMappingURL=sources.js.map