@buoy-gg/time-machine 7.0.40 → 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 (173) hide show
  1. package/README.md +4 -0
  2. package/lib/commonjs/components/HeaderActions.js +1 -91
  3. package/lib/commonjs/components/HeaderReloadButton.js +1 -58
  4. package/lib/commonjs/components/TimeMachineBody.js +1 -282
  5. package/lib/commonjs/components/TimeMachineModal.js +1 -74
  6. package/lib/commonjs/components/confirm.js +2 -38
  7. package/lib/commonjs/components/floating/NamePromptModal.js +1 -193
  8. package/lib/commonjs/components/floating/TimeMachineFloatingTool.js +1 -715
  9. package/lib/commonjs/components/floating/armedPoint.js +1 -92
  10. package/lib/commonjs/components/floating/barSettings.js +1 -66
  11. package/lib/commonjs/components/format.js +1 -24
  12. package/lib/commonjs/components/list/CaptureBar.js +1 -97
  13. package/lib/commonjs/components/list/RestoreOutcomeCard.js +1 -175
  14. package/lib/commonjs/components/list/SnapshotRow.js +1 -170
  15. package/lib/commonjs/components/list/SourcesStrip.js +1 -172
  16. package/lib/commonjs/components/list/UndoDeleteBar.js +1 -85
  17. package/lib/commonjs/components/list/WipeAllRow.js +1 -46
  18. package/lib/commonjs/components/preview/ItemDiffPanel.js +1 -79
  19. package/lib/commonjs/components/preview/PreviewFlow.js +1 -51
  20. package/lib/commonjs/components/preview/RestorePointNameRow.js +1 -83
  21. package/lib/commonjs/components/preview/RestorePreviewScreen.js +1 -1010
  22. package/lib/commonjs/components/preview/verdictMeta.js +1 -58
  23. package/lib/commonjs/components/sources.js +1 -83
  24. package/lib/commonjs/hooks/useTimeMachine.js +1 -24
  25. package/lib/commonjs/index.js +1 -161
  26. package/lib/commonjs/preset.js +1 -56
  27. package/lib/commonjs/store/routeRestore.js +1 -188
  28. package/lib/commonjs/store/snapshotVault.js +1 -186
  29. package/lib/commonjs/store/sourceIds.js +1 -9
  30. package/lib/commonjs/store/sourceSettings.js +1 -79
  31. package/lib/commonjs/store/timeMachineStore.js +1 -848
  32. package/lib/commonjs/sync/timeMachineSyncAdapter.js +1 -258
  33. package/lib/commonjs/ui/SwipeActionsRow.js +1 -178
  34. package/lib/commonjs/ui/primitives.js +1 -172
  35. package/lib/commonjs/ui/tmTheme.js +1 -118
  36. package/lib/module/components/HeaderActions.js +1 -85
  37. package/lib/module/components/HeaderReloadButton.js +1 -53
  38. package/lib/module/components/TimeMachineBody.js +1 -277
  39. package/lib/module/components/TimeMachineModal.js +1 -68
  40. package/lib/module/components/confirm.js +2 -33
  41. package/lib/module/components/floating/NamePromptModal.js +1 -188
  42. package/lib/module/components/floating/TimeMachineFloatingTool.js +1 -708
  43. package/lib/module/components/floating/armedPoint.js +1 -82
  44. package/lib/module/components/floating/barSettings.js +1 -57
  45. package/lib/module/components/format.js +1 -19
  46. package/lib/module/components/list/CaptureBar.js +1 -92
  47. package/lib/module/components/list/RestoreOutcomeCard.js +1 -169
  48. package/lib/module/components/list/SnapshotRow.js +1 -167
  49. package/lib/module/components/list/SourcesStrip.js +1 -169
  50. package/lib/module/components/list/UndoDeleteBar.js +1 -82
  51. package/lib/module/components/list/WipeAllRow.js +1 -41
  52. package/lib/module/components/preview/ItemDiffPanel.js +1 -74
  53. package/lib/module/components/preview/PreviewFlow.js +1 -46
  54. package/lib/module/components/preview/RestorePointNameRow.js +1 -78
  55. package/lib/module/components/preview/RestorePreviewScreen.js +1 -1005
  56. package/lib/module/components/preview/verdictMeta.js +1 -54
  57. package/lib/module/components/sources.js +1 -73
  58. package/lib/module/hooks/useTimeMachine.js +1 -19
  59. package/lib/module/index.js +1 -46
  60. package/lib/module/preset.js +1 -51
  61. package/lib/module/store/routeRestore.js +1 -176
  62. package/lib/module/store/snapshotVault.js +1 -170
  63. package/lib/module/store/sourceIds.js +1 -5
  64. package/lib/module/store/sourceSettings.js +1 -69
  65. package/lib/module/store/timeMachineStore.js +1 -839
  66. package/lib/module/sync/timeMachineSyncAdapter.js +1 -253
  67. package/lib/module/ui/SwipeActionsRow.js +1 -173
  68. package/lib/module/ui/primitives.js +1 -164
  69. package/lib/module/ui/tmTheme.js +1 -114
  70. package/lib/web/index.mjs +3 -0
  71. package/package.json +43 -10
  72. package/lib/commonjs/components/HeaderActions.js.map +0 -1
  73. package/lib/commonjs/components/HeaderReloadButton.js.map +0 -1
  74. package/lib/commonjs/components/TimeMachineBody.js.map +0 -1
  75. package/lib/commonjs/components/TimeMachineModal.js.map +0 -1
  76. package/lib/commonjs/components/confirm.js.map +0 -1
  77. package/lib/commonjs/components/floating/NamePromptModal.js.map +0 -1
  78. package/lib/commonjs/components/floating/TimeMachineFloatingTool.js.map +0 -1
  79. package/lib/commonjs/components/floating/armedPoint.js.map +0 -1
  80. package/lib/commonjs/components/floating/barSettings.js.map +0 -1
  81. package/lib/commonjs/components/format.js.map +0 -1
  82. package/lib/commonjs/components/list/CaptureBar.js.map +0 -1
  83. package/lib/commonjs/components/list/RestoreOutcomeCard.js.map +0 -1
  84. package/lib/commonjs/components/list/SnapshotRow.js.map +0 -1
  85. package/lib/commonjs/components/list/SourcesStrip.js.map +0 -1
  86. package/lib/commonjs/components/list/UndoDeleteBar.js.map +0 -1
  87. package/lib/commonjs/components/list/WipeAllRow.js.map +0 -1
  88. package/lib/commonjs/components/preview/ItemDiffPanel.js.map +0 -1
  89. package/lib/commonjs/components/preview/PreviewFlow.js.map +0 -1
  90. package/lib/commonjs/components/preview/RestorePointNameRow.js.map +0 -1
  91. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +0 -1
  92. package/lib/commonjs/components/preview/verdictMeta.js.map +0 -1
  93. package/lib/commonjs/components/sources.js.map +0 -1
  94. package/lib/commonjs/hooks/useTimeMachine.js.map +0 -1
  95. package/lib/commonjs/index.js.map +0 -1
  96. package/lib/commonjs/preset.js.map +0 -1
  97. package/lib/commonjs/store/routeRestore.js.map +0 -1
  98. package/lib/commonjs/store/snapshotVault.js.map +0 -1
  99. package/lib/commonjs/store/sourceIds.js.map +0 -1
  100. package/lib/commonjs/store/sourceSettings.js.map +0 -1
  101. package/lib/commonjs/store/timeMachineStore.js.map +0 -1
  102. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +0 -1
  103. package/lib/commonjs/ui/SwipeActionsRow.js.map +0 -1
  104. package/lib/commonjs/ui/primitives.js.map +0 -1
  105. package/lib/commonjs/ui/tmTheme.js.map +0 -1
  106. package/lib/module/components/HeaderActions.js.map +0 -1
  107. package/lib/module/components/HeaderReloadButton.js.map +0 -1
  108. package/lib/module/components/TimeMachineBody.js.map +0 -1
  109. package/lib/module/components/TimeMachineModal.js.map +0 -1
  110. package/lib/module/components/confirm.js.map +0 -1
  111. package/lib/module/components/floating/NamePromptModal.js.map +0 -1
  112. package/lib/module/components/floating/TimeMachineFloatingTool.js.map +0 -1
  113. package/lib/module/components/floating/armedPoint.js.map +0 -1
  114. package/lib/module/components/floating/barSettings.js.map +0 -1
  115. package/lib/module/components/format.js.map +0 -1
  116. package/lib/module/components/list/CaptureBar.js.map +0 -1
  117. package/lib/module/components/list/RestoreOutcomeCard.js.map +0 -1
  118. package/lib/module/components/list/SnapshotRow.js.map +0 -1
  119. package/lib/module/components/list/SourcesStrip.js.map +0 -1
  120. package/lib/module/components/list/UndoDeleteBar.js.map +0 -1
  121. package/lib/module/components/list/WipeAllRow.js.map +0 -1
  122. package/lib/module/components/preview/ItemDiffPanel.js.map +0 -1
  123. package/lib/module/components/preview/PreviewFlow.js.map +0 -1
  124. package/lib/module/components/preview/RestorePointNameRow.js.map +0 -1
  125. package/lib/module/components/preview/RestorePreviewScreen.js.map +0 -1
  126. package/lib/module/components/preview/verdictMeta.js.map +0 -1
  127. package/lib/module/components/sources.js.map +0 -1
  128. package/lib/module/hooks/useTimeMachine.js.map +0 -1
  129. package/lib/module/index.js.map +0 -1
  130. package/lib/module/preset.js.map +0 -1
  131. package/lib/module/store/routeRestore.js.map +0 -1
  132. package/lib/module/store/snapshotVault.js.map +0 -1
  133. package/lib/module/store/sourceIds.js.map +0 -1
  134. package/lib/module/store/sourceSettings.js.map +0 -1
  135. package/lib/module/store/timeMachineStore.js.map +0 -1
  136. package/lib/module/sync/timeMachineSyncAdapter.js.map +0 -1
  137. package/lib/module/ui/SwipeActionsRow.js.map +0 -1
  138. package/lib/module/ui/primitives.js.map +0 -1
  139. package/lib/module/ui/tmTheme.js.map +0 -1
  140. package/lib/typescript/components/HeaderActions.d.ts.map +0 -1
  141. package/lib/typescript/components/HeaderReloadButton.d.ts.map +0 -1
  142. package/lib/typescript/components/TimeMachineBody.d.ts.map +0 -1
  143. package/lib/typescript/components/TimeMachineModal.d.ts.map +0 -1
  144. package/lib/typescript/components/confirm.d.ts.map +0 -1
  145. package/lib/typescript/components/floating/NamePromptModal.d.ts.map +0 -1
  146. package/lib/typescript/components/floating/TimeMachineFloatingTool.d.ts.map +0 -1
  147. package/lib/typescript/components/floating/armedPoint.d.ts.map +0 -1
  148. package/lib/typescript/components/floating/barSettings.d.ts.map +0 -1
  149. package/lib/typescript/components/format.d.ts.map +0 -1
  150. package/lib/typescript/components/list/CaptureBar.d.ts.map +0 -1
  151. package/lib/typescript/components/list/RestoreOutcomeCard.d.ts.map +0 -1
  152. package/lib/typescript/components/list/SnapshotRow.d.ts.map +0 -1
  153. package/lib/typescript/components/list/SourcesStrip.d.ts.map +0 -1
  154. package/lib/typescript/components/list/UndoDeleteBar.d.ts.map +0 -1
  155. package/lib/typescript/components/list/WipeAllRow.d.ts.map +0 -1
  156. package/lib/typescript/components/preview/ItemDiffPanel.d.ts.map +0 -1
  157. package/lib/typescript/components/preview/PreviewFlow.d.ts.map +0 -1
  158. package/lib/typescript/components/preview/RestorePointNameRow.d.ts.map +0 -1
  159. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +0 -1
  160. package/lib/typescript/components/preview/verdictMeta.d.ts.map +0 -1
  161. package/lib/typescript/components/sources.d.ts.map +0 -1
  162. package/lib/typescript/hooks/useTimeMachine.d.ts.map +0 -1
  163. package/lib/typescript/index.d.ts.map +0 -1
  164. package/lib/typescript/preset.d.ts.map +0 -1
  165. package/lib/typescript/store/routeRestore.d.ts.map +0 -1
  166. package/lib/typescript/store/snapshotVault.d.ts.map +0 -1
  167. package/lib/typescript/store/sourceIds.d.ts.map +0 -1
  168. package/lib/typescript/store/sourceSettings.d.ts.map +0 -1
  169. package/lib/typescript/store/timeMachineStore.d.ts.map +0 -1
  170. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +0 -1
  171. package/lib/typescript/ui/SwipeActionsRow.d.ts.map +0 -1
  172. package/lib/typescript/ui/primitives.d.ts.map +0 -1
  173. package/lib/typescript/ui/tmTheme.d.ts.map +0 -1
@@ -1,1010 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.RestorePreviewScreen = RestorePreviewScreen;
7
- var _react = require("react");
8
- var _reactNative = require("react-native");
9
- var _sharedUi = require("@buoy-gg/shared-ui");
10
- var _core = require("@buoy-gg/core");
11
- var _HeaderActions = require("../HeaderActions");
12
- var _primitives = require("../../ui/primitives");
13
- var _tmTheme = require("../../ui/tmTheme");
14
- var _timeMachineStore = require("../../store/timeMachineStore");
15
- var _ItemDiffPanel = require("./ItemDiffPanel");
16
- var _RestorePointNameRow = require("./RestorePointNameRow");
17
- var _verdictMeta = require("./verdictMeta");
18
- var _jsxRuntime = require("react/jsx-runtime");
19
- /**
20
- * Restore Preview — "what will restoring this snapshot actually change?"
21
- *
22
- * ONE virtualized FlatList is the scrollable area (the section header, the
23
- * source rows, their item rows and the inline diffs are all list rows) with
24
- * a sticky Restore CTA footer — no nested scroll regions, so thousands of
25
- * storage keys stay cheap.
26
- *
27
- * Layout follows the Time Machine mockup: source sections are card
28
- * dropdowns with tool-icon tiles, a "N keys · M changed" detection subtitle,
29
- * ±≈ badges, a search field with an expand/collapse-all button, and a bottom
30
- * "Restore State" pill. Only rows a restore would touch (or can't touch)
31
- * render — unchanged items never do. Every applicable item keeps its
32
- * INCLUDE checkbox (source headers toggle their whole section) — unchecked
33
- * items are passed to restore as exclusions and left untouched.
34
- */
35
-
36
- const SOURCE_NOUNS = {
37
- storage: "keys",
38
- redux: "slices",
39
- zustand: "stores",
40
- jotai: "atoms",
41
- query: "queries"
42
- };
43
-
44
- /**
45
- * Where a row sits in the one card that holds the whole list. The list has
46
- * to stay flat to stay virtualized, so each row paints its own share of the
47
- * card: the first row rounds its top, the last rounds its bottom, and every
48
- * other row draws the hairline above it.
49
- */
50
-
51
- /**
52
- * The route leg, as a row in the same card as the sources. It is not a
53
- * source — nothing diffs it — but "will this also take me back to
54
- * /checkout?" is a decision about THIS restore, so it belongs beside the
55
- * other things the restore is about to do rather than on the list screen.
56
- */
57
-
58
- /** Shared empty selection — a stable identity for "nothing is excluded". */
59
- const EMPTY_SELECTION = new Set();
60
- function sameSelection(a, b) {
61
- if (a.size !== b.size) return false;
62
- for (const key of a) if (!b.has(key)) return false;
63
- return true;
64
- }
65
-
66
- /** Compound exclusion key — what timeMachineStore.restore expects. */
67
- function cKey(sourceId, itemKey) {
68
- return `${sourceId}::${itemKey}`;
69
- }
70
- function shortPreview(value) {
71
- if (value === undefined) return "∅";
72
- try {
73
- const s = typeof value === "string" ? `"${value}"` : JSON.stringify(value);
74
- if (!s) return String(value);
75
- return s.length > 46 ? `${s.slice(0, 43)}…` : s;
76
- } catch {
77
- return "[complex]";
78
- }
79
- }
80
-
81
- /**
82
- * The grouped-card shell one row paints. Rounds the ends, insets the card
83
- * from the sheet, and draws the hairline that separates it from the row
84
- * above — the same shape the home list's <Group> makes, rebuilt per row so
85
- * the list stays virtualized.
86
- */
87
- function CardRow({
88
- position,
89
- children
90
- }) {
91
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
92
- style: [styles.cardRow, position.first ? styles.cardRowFirst : null, position.last ? styles.cardRowLast : null],
93
- children: [position.first ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
94
- style: styles.cardSeparator
95
- }), children]
96
- });
97
- }
98
- function CountPill({
99
- glyph,
100
- count,
101
- color,
102
- testID
103
- }) {
104
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
105
- style: [styles.countPill, {
106
- backgroundColor: `${color}26`
107
- }],
108
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
109
- style: [styles.countPillText, {
110
- color
111
- }],
112
- testID: testID,
113
- children: [glyph, count]
114
- })
115
- });
116
- }
117
- function Checkbox({
118
- state
119
- }) {
120
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
121
- style: [styles.checkbox, state !== "none" && styles.checkboxChecked],
122
- children: state === "none" ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
123
- style: styles.checkboxGlyph,
124
- children: state === "all" ? "✓" : "−"
125
- })
126
- });
127
- }
128
- const ItemRow = /*#__PURE__*/(0, _react.memo)(function ItemRow({
129
- row,
130
- expanded,
131
- onOpen,
132
- onToggle
133
- }) {
134
- const meta = _verdictMeta.VERDICT_META[row.item.verdict];
135
- const dimmed = !meta.applies || row.excluded;
136
- const openable = row.item.verdict === "changed" || row.item.verdict === "added" || row.item.verdict === "removed";
137
- // One stem for the row and every part of it, so a test that found the row
138
- // can read its label, its before/after and its exclusion note without
139
- // rebuilding the compound key three times.
140
- const id = `tm-preview-item-${row.sourceId}-${row.item.key}`;
141
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardRow, {
142
- position: row,
143
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
144
- style: [styles.itemRow, dimmed && styles.dimmed],
145
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
146
- onPress: onToggle,
147
- disabled: !meta.applies,
148
- hitSlop: {
149
- top: 10,
150
- bottom: 10,
151
- left: 8,
152
- right: 8
153
- },
154
- testID: `tm-preview-toggle-${row.sourceId}-${row.item.key}`,
155
- accessibilityRole: "checkbox",
156
- accessibilityState: {
157
- checked: !(!meta.applies || row.excluded)
158
- },
159
- accessibilityLabel: `Include ${row.item.label}`,
160
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Checkbox, {
161
- state: !meta.applies || row.excluded ? "none" : "all"
162
- })
163
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
164
- style: styles.itemBodyTouch,
165
- onPress: () => openable && onOpen(),
166
- disabled: !openable,
167
- testID: id,
168
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
169
- style: [styles.itemGlyph, {
170
- color: meta.color
171
- }],
172
- children: meta.glyph
173
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
174
- style: styles.itemBody,
175
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
176
- style: styles.itemLabel,
177
- numberOfLines: 1,
178
- testID: `${id}-label`,
179
- children: row.item.label
180
- }), row.item.verdict === "changed" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
181
- style: styles.itemPreview,
182
- numberOfLines: 1,
183
- testID: `${id}-values`,
184
- children: [shortPreview(row.item.oldValue), " \u2192", " ", shortPreview(row.item.newValue)]
185
- }), row.item.verdict === "added" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
186
- style: styles.itemPreview,
187
- numberOfLines: 1,
188
- testID: `${id}-values`,
189
- children: shortPreview(row.item.newValue)
190
- }), row.item.verdict === "removed" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
191
- style: [styles.itemPreview, styles.itemPreviewRemoved],
192
- numberOfLines: 1,
193
- testID: `${id}-values`,
194
- children: shortPreview(row.item.oldValue)
195
- }), row.excluded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
196
- style: styles.itemExcluded,
197
- testID: `${id}-excluded`,
198
- children: "excluded \u2014 restore leaves this untouched"
199
- }) : row.item.reason && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
200
- style: styles.itemReason,
201
- numberOfLines: 2,
202
- testID: `${id}-reason`,
203
- children: row.item.reason
204
- })]
205
- }), openable ? expanded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronUp, {
206
- size: 16,
207
- color: _tmTheme.tm.color.labelTertiary
208
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.DisclosureChevron, {}) : null]
209
- })]
210
- })
211
- });
212
- });
213
- function RestorePreviewScreen({
214
- preview,
215
- snapshots,
216
- busy,
217
- onBack,
218
- onRestore
219
- }) {
220
- const host = (0, _core.useToolHost)();
221
- const hosted = host.instanceId !== undefined;
222
- // `minBottom` is a floor, not a preference: the dimension map can't know
223
- // about a device that ships next year, and an unmapped one falls through to
224
- // `bottom: 0`. This keeps the CTA off the home indicator either way.
225
- const insets = (0, _sharedUi.useSafeAreaInsets)({
226
- minBottom: 16
227
- });
228
- const [collapsed, setCollapsed] = (0, _react.useState)({});
229
- const [excluded, setExcluded] = (0, _react.useState)(new Set());
230
- /** Set by every checkbox interaction — see the auto-save effect below. */
231
- const userEditedRef = (0, _react.useRef)(false);
232
- /** Item rows whose diff is expanded inline, by compound key. */
233
- const [openDiffs, setOpenDiffs] = (0, _react.useState)(EMPTY_SELECTION);
234
- const toggleDiff = (0, _react.useCallback)(key => {
235
- setOpenDiffs(prev => {
236
- const next = new Set(prev);
237
- if (next.has(key)) next.delete(key);else next.add(key);
238
- return next;
239
- });
240
- }, []);
241
- // Filtering lives in the shared modal header (the magnifier), same as the
242
- // restore-point list and every other Buoy tool — one place to look for it.
243
- const search = host.search;
244
- const rightMeta = preview.right === "current" ? undefined : snapshots.find(s => s.id === preview.right.snapshotId);
245
- // Latest-ref: `renderRow` is memoized on its handlers, so this one has to
246
- // keep a stable identity while still reading the current snapshot.
247
- const rightMetaRef = (0, _react.useRef)(rightMeta);
248
- rightMetaRef.current = rightMeta;
249
-
250
- /** Flip whether this restore also navigates back to the captured route. */
251
- const toggleRoute = (0, _react.useCallback)(() => {
252
- if (!rightMetaRef.current) return;
253
- const meta = rightMetaRef.current;
254
- void _timeMachineStore.timeMachineStore.setRestoreRoute(meta.id, !(0, _timeMachineStore.willRestoreRoute)(meta));
255
- }, []);
256
- const isBaseline = rightMeta?.baseline === true;
257
- const liveLeft = preview.left === "current";
258
-
259
- /**
260
- * The selection this restore point already remembers, as exclusions. Null
261
- * until there is a snapshot on the right-hand side to have one.
262
- */
263
- const savedExcluded = (0, _react.useMemo)(() => {
264
- if (!rightMeta) return null;
265
- return rightMeta.excludedKeys?.length ? new Set(rightMeta.excludedKeys) : EMPTY_SELECTION;
266
- }, [rightMeta]);
267
-
268
- /**
269
- * A snapshot still carrying the older `scope` include-list ("only ever
270
- * touch these", set via MCP). It seeds the checkboxes and is converted to
271
- * exclusions on read — a restore of it would otherwise still apply include
272
- * semantics, and the two forms would disagree about what one tap does.
273
- */
274
- const legacyScoped = !!rightMeta?.scope?.length && !rightMeta?.excludedKeys?.length;
275
- const legacySeed = (0, _react.useMemo)(() => {
276
- if (!legacyScoped || !rightMeta?.scope) return EMPTY_SELECTION;
277
- const scopeSet = new Set(rightMeta.scope);
278
- const next = new Set();
279
- for (const src of preview.sources) {
280
- for (const item of src.items) {
281
- const key = cKey(src.id, item.key);
282
- if (_verdictMeta.VERDICT_META[item.verdict].applies && !scopeSet.has(key)) {
283
- next.add(key);
284
- }
285
- }
286
- }
287
- return next;
288
- }, [legacyScoped, rightMeta, preview.sources]);
289
-
290
- // Open with the saved selection already applied — once per snapshot, and
291
- // only once there is a diff to apply it to.
292
- const seededFor = (0, _react.useRef)(null);
293
- (0, _react.useEffect)(() => {
294
- if (!rightMeta || preview.building || !savedExcluded) return;
295
- if (seededFor.current === rightMeta.id) return;
296
- seededFor.current = rightMeta.id;
297
- userEditedRef.current = false;
298
- const seed = savedExcluded.size > 0 ? savedExcluded : legacySeed;
299
- if (seed.size > 0) setExcluded(new Set(seed));
300
- // Convert an older include-list scope the moment it's read, so the two
301
- // forms never disagree about what a restore will do.
302
- if (legacyScoped && seed.size > 0) {
303
- void _timeMachineStore.timeMachineStore.setExclusions(rightMeta.id, Array.from(seed));
304
- }
305
- }, [rightMeta, preview.building, savedExcluded, legacySeed, legacyScoped]);
306
-
307
- /**
308
- * Auto-save. The checkboxes ARE the restore point's setting, so there is
309
- * nothing to confirm — every edit persists as EXCLUSIONS (the items to
310
- * leave alone), and every later restore of this point honors them, one-tap
311
- * RESTORE and MCP included.
312
- *
313
- * Never an include list: unchecking one of ten keys must keep restoring the
314
- * other nine AND anything that only starts differing tomorrow, which a list
315
- * built from today's diff cannot do.
316
- *
317
- * Gated on a ref rather than on comparing to what's stored, because on the
318
- * frames before the seed lands `excluded` is legitimately empty — writing
319
- * that would erase the saved selection we are about to load.
320
- */
321
- (0, _react.useEffect)(() => {
322
- if (!rightMeta || !savedExcluded || !userEditedRef.current) return;
323
- if (sameSelection(excluded, savedExcluded)) return;
324
- const keys = Array.from(excluded);
325
- void _timeMachineStore.timeMachineStore.setExclusions(rightMeta.id, keys.length ? keys : null);
326
- }, [excluded, rightMeta, savedExcluded]);
327
- const toggleItem = (0, _react.useCallback)(key => {
328
- userEditedRef.current = true;
329
- setExcluded(prev => {
330
- const next = new Set(prev);
331
- if (next.has(key)) next.delete(key);else next.add(key);
332
- return next;
333
- });
334
- }, []);
335
- const toggleSource = (0, _react.useCallback)((source, includeState) => {
336
- userEditedRef.current = true;
337
- setExcluded(prev => {
338
- const next = new Set(prev);
339
- for (const item of source.items) {
340
- if (!_verdictMeta.VERDICT_META[item.verdict].applies) continue;
341
- const key = cKey(source.id, item.key);
342
- if (includeState === "all") next.add(key);else next.delete(key);
343
- }
344
- return next;
345
- });
346
- }, []);
347
-
348
- // Takes the EFFECTIVE collapsed state — the map is sparse (defaults come
349
- // from the ?? fallback), so `!map[id]` would no-op the first tap on a
350
- // default-collapsed card.
351
- const toggleCollapse = (0, _react.useCallback)((id, currentlyCollapsed) => {
352
- setCollapsed(c => ({
353
- ...c,
354
- [id]: !currentlyCollapsed
355
- }));
356
- }, []);
357
- const totals = (0, _react.useMemo)(() => {
358
- let applies = 0;
359
- let excludedCount = 0;
360
- let wontApply = 0;
361
- const perSource = [];
362
- for (const src of preview.sources) {
363
- let n = 0;
364
- for (const item of src.items) {
365
- const m = _verdictMeta.VERDICT_META[item.verdict];
366
- if (m.applies) {
367
- if (excluded.has(cKey(src.id, item.key))) excludedCount += 1;else {
368
- applies += 1;
369
- n += 1;
370
- }
371
- } else if (item.verdict === "wont-apply") wontApply += 1;
372
- }
373
- if (n > 0) perSource.push(`${n} ${src.label.toLowerCase()}`);
374
- }
375
- return {
376
- applies,
377
- excludedCount,
378
- wontApply,
379
- perSource
380
- };
381
- }, [preview.sources, excluded]);
382
-
383
- // Hosted: back + identity in the shared modal header. Restore lives in the
384
- // bottom CTA; the preset's labeled RELOAD APP action stays available.
385
- (0, _core.useToolHeader)(hosted ? {
386
- title: "Restore Preview",
387
- subtitle: rightMeta?.name ?? "preview",
388
- onBack,
389
- // BAR from here arms the point being previewed.
390
- actions: /*#__PURE__*/(0, _jsxRuntime.jsx)(_HeaderActions.TimeMachineHeaderActions, {
391
- armId: rightMeta?.id
392
- })
393
- } : null,
394
- // eslint-disable-next-line react-hooks/exhaustive-deps
395
- [hosted, rightMeta?.name, rightMeta?.id]);
396
- const {
397
- rows,
398
- blockedSources
399
- } = (0, _react.useMemo)(() => {
400
- const q = search.trim().toLowerCase();
401
- const out = [];
402
- // Sources the restore would not touch at all get no card: "no changes"
403
- // rows are noise on a screen about what WILL change.
404
- /** Sources holding a won't-apply item, so the note can name where to look. */
405
- const blocked = [];
406
- for (const src of preview.sources) {
407
- // Every detected source gets a card whose subtitle carries the
408
- // detection proof ("60 keys · 7 changed" / "2 slices · no changes"),
409
- // so clean sources need no expansion. Unchanged ITEMS never render.
410
- const relevant = src.items.some(i => i.verdict !== "unchanged");
411
- if (!relevant && !src.error) continue;
412
- const expandable = relevant || src.notes.length > 0 || !!src.error;
413
- const applicable = src.items.filter(i => _verdictMeta.VERDICT_META[i.verdict].applies);
414
- const wontApplyCount = src.items.filter(i => i.verdict === "wont-apply").length;
415
- if (wontApplyCount > 0) blocked.push(src.label);
416
- const noun = src.items.length === 1 ? (SOURCE_NOUNS[src.id] ?? "items").replace(/ies$/, "y").replace(/s$/, "") : SOURCE_NOUNS[src.id] ?? "items";
417
- const summary = `${src.items.length} ${noun} · ` + (applicable.length > 0 ? `${applicable.length} changed` : wontApplyCount > 0 ? `${wontApplyCount} won't apply` : "no changes");
418
- const excludedInSource = applicable.filter(i => excluded.has(cKey(src.id, i.key))).length;
419
- const includeState = applicable.length === 0 || excludedInSource === 0 ? "all" : excludedInSource === applicable.length ? "none" : "some";
420
- const counts = {
421
- added: 0,
422
- removed: 0,
423
- changed: 0
424
- };
425
- for (const i of src.items) {
426
- if (excluded.has(cKey(src.id, i.key))) continue;
427
- if (i.verdict === "added") counts.added += 1;else if (i.verdict === "removed") counts.removed += 1;else if (i.verdict === "changed" || i.verdict === "too-large") counts.changed += 1;
428
- }
429
- const visible = src.items.filter(item => {
430
- // Unchanged items never render — the dropdowns show only what a
431
- // restore would touch (changed) or can't touch (won't-apply).
432
- if (item.verdict === "unchanged") return false;
433
- if (q && !item.label.toLowerCase().includes(q)) return false;
434
- return true;
435
- });
436
- // Everything starts collapsed. The screen's job on open is "which
437
- // sources would this touch, and how much" — expanding a source with
438
- // 58 keys before you asked buries that under its own contents.
439
- const isCollapsed = expandable ? collapsed[src.id] ?? true : true;
440
- out.push({
441
- kind: "source",
442
- source: src,
443
- collapsed: isCollapsed,
444
- includeState,
445
- applicableCount: applicable.length,
446
- counts,
447
- expandable,
448
- summary,
449
- first: false,
450
- last: false
451
- });
452
- if (!isCollapsed) {
453
- for (const note of src.notes) {
454
- out.push({
455
- kind: "note",
456
- id: `${src.id}:${note}`,
457
- text: note,
458
- first: false,
459
- last: false
460
- });
461
- }
462
- if (src.error) {
463
- out.push({
464
- kind: "note",
465
- id: `${src.id}:err`,
466
- text: `⚠ ${src.error}`,
467
- first: false,
468
- last: false
469
- });
470
- }
471
- for (const item of visible) {
472
- const key = cKey(src.id, item.key);
473
- out.push({
474
- kind: "item",
475
- sourceId: src.id,
476
- item,
477
- excluded: excluded.has(key),
478
- first: false,
479
- last: false
480
- });
481
- if (openDiffs.has(key)) {
482
- out.push({
483
- kind: "diff",
484
- sourceId: src.id,
485
- item,
486
- first: false,
487
- last: false
488
- });
489
- }
490
- }
491
- }
492
- }
493
- if (rightMeta?.route) {
494
- out.push({
495
- kind: "route",
496
- href: rightMeta.route.href,
497
- on: (0, _timeMachineStore.willRestoreRoute)(rightMeta),
498
- first: false,
499
- last: false
500
- });
501
- }
502
- // ONE card holds every source — the ends of the LIST round, and every
503
- // other boundary is just a hairline. Per-source cards would read as
504
- // separate groups, which they aren't: this is one list of what a restore
505
- // would touch.
506
- if (out.length > 0) {
507
- out[0].first = true;
508
- out[out.length - 1].last = true;
509
- }
510
- return {
511
- rows: out,
512
- blockedSources: blocked
513
- };
514
- }, [preview.sources, search, collapsed, excluded, openDiffs, rightMeta]);
515
- const renderRow = (0, _react.useCallback)(({
516
- item: row
517
- }) => {
518
- if (row.kind === "source") {
519
- const src = row.source;
520
- const {
521
- counts
522
- } = row;
523
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardRow, {
524
- position: row,
525
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
526
- style: styles.sourceCard,
527
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
528
- onPress: () => toggleSource(src, row.includeState),
529
- disabled: row.applicableCount === 0,
530
- hitSlop: {
531
- top: 10,
532
- bottom: 10,
533
- left: 8,
534
- right: 8
535
- },
536
- testID: `tm-preview-source-toggle-${src.id}`,
537
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Checkbox, {
538
- state: row.applicableCount === 0 ? "none" : row.includeState
539
- })
540
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
541
- style: styles.sourceCardTouch,
542
- onPress: () => row.expandable && toggleCollapse(src.id, row.collapsed),
543
- disabled: !row.expandable,
544
- testID: `tm-preview-source-${src.id}`,
545
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
546
- style: styles.sourceNameBlock,
547
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
548
- style: styles.sourceName,
549
- numberOfLines: 1,
550
- testID: `tm-preview-source-${src.id}-name`,
551
- children: src.label
552
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
553
- style: styles.sourceSummary,
554
- numberOfLines: 1,
555
- testID: `tm-preview-source-${src.id}-summary`,
556
- children: row.includeState === "none" && row.applicableCount > 0 ? `${row.summary} · excluded` : row.summary
557
- })]
558
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
559
- style: styles.sourceBadges,
560
- children: [counts.removed > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CountPill, {
561
- glyph: "\u2212",
562
- count: counts.removed,
563
- color: _tmTheme.tm.color.danger,
564
- testID: `tm-preview-source-${src.id}-removed`
565
- }) : null, counts.added > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CountPill, {
566
- glyph: "+",
567
- count: counts.added,
568
- color: _tmTheme.tm.color.accent,
569
- testID: `tm-preview-source-${src.id}-added`
570
- }) : null, counts.changed > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CountPill, {
571
- glyph: "\u2248",
572
- count: counts.changed,
573
- color: _tmTheme.tm.color.info,
574
- testID: `tm-preview-source-${src.id}-changed`
575
- }) : null]
576
- }), row.expandable ? row.collapsed ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.DisclosureChevron, {}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronUp, {
577
- size: 16,
578
- color: _tmTheme.tm.color.labelTertiary
579
- }) : null]
580
- })]
581
- })
582
- });
583
- }
584
- if (row.kind === "note") {
585
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardRow, {
586
- position: row,
587
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
588
- style: styles.noteRow,
589
- testID: `tm-preview-note-${row.id}`,
590
- children: row.text
591
- })
592
- });
593
- }
594
- if (row.kind === "route") {
595
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardRow, {
596
- position: row,
597
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
598
- style: styles.routeRow,
599
- onPress: toggleRoute,
600
- accessibilityRole: "switch",
601
- accessibilityState: {
602
- checked: row.on
603
- },
604
- accessibilityLabel: `Also return to ${row.href}`,
605
- testID: "tm-preview-route",
606
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Checkbox, {
607
- state: row.on ? "all" : "none"
608
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
609
- style: styles.routeText,
610
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
611
- style: styles.routeTitle,
612
- children: "Return to this screen"
613
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
614
- style: styles.routeHref,
615
- numberOfLines: 1,
616
- testID: "tm-preview-route-href",
617
- children: row.href
618
- })]
619
- })]
620
- })
621
- });
622
- }
623
- if (row.kind === "diff") {
624
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardRow, {
625
- position: row,
626
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ItemDiffPanel.ItemDiffPanel, {
627
- item: row.item,
628
- testID: `tm-preview-diff-${row.sourceId}-${row.item.key}`
629
- })
630
- });
631
- }
632
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(ItemRow, {
633
- row: row,
634
- expanded: openDiffs.has(cKey(row.sourceId, row.item.key)),
635
- onOpen: () => toggleDiff(cKey(row.sourceId, row.item.key)),
636
- onToggle: () => toggleItem(cKey(row.sourceId, row.item.key))
637
- });
638
- }, [openDiffs, toggleDiff, toggleCollapse, toggleItem, toggleSource, toggleRoute]);
639
- const keyExtractor = (0, _react.useCallback)(row => {
640
- if (row.kind === "source") return `s:${row.source.id}`;
641
- if (row.kind === "note") return `n:${row.id}`;
642
- if (row.kind === "route") return "route";
643
- if (row.kind === "diff") return `d:${row.sourceId}:${row.item.key}`;
644
- return `i:${row.sourceId}:${row.item.key}`;
645
- }, []);
646
- const header = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
647
- children: [!hosted && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
648
- style: styles.fallbackHeader,
649
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
650
- onPress: onBack,
651
- hitSlop: HIT,
652
- testID: "tm-preview-back",
653
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
654
- style: styles.backText,
655
- children: "\u2039 Back"
656
- })
657
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
658
- style: styles.screenTitle,
659
- numberOfLines: 1,
660
- children: "Restore Preview"
661
- })]
662
- }), rightMeta ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
663
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
664
- children: "Restore Point"
665
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.Group, {
666
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_RestorePointNameRow.RestorePointNameRow, {
667
- name: rightMeta.name,
668
- onRename: next => void _timeMachineStore.timeMachineStore.rename(rightMeta.id, next)
669
- })
670
- })]
671
- }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_primitives.SectionLabel, {
672
- children: "Changes to Restore"
673
- })]
674
- });
675
- const ctaDisabled = busy || preview.building || totals.applies === 0;
676
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
677
- style: styles.container,
678
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
679
- testID: "tm-preview-list",
680
- data: rows,
681
- renderItem: renderRow,
682
- keyExtractor: keyExtractor,
683
- ListHeaderComponent: header,
684
- ListEmptyComponent: preview.building ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
685
- style: styles.buildingSpinner,
686
- size: "small",
687
- color: _tmTheme.tm.color.accent
688
- }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
689
- style: styles.emptyText,
690
- testID: "tm-preview-empty",
691
- children: search ? "Nothing matches this filter." : "No changes — restoring would change nothing."
692
- }),
693
- ListFooterComponent: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
694
- children: totals.wontApply > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
695
- style: styles.warnNote,
696
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
697
- size: 12,
698
- color: _tmTheme.tm.color.warning
699
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
700
- style: styles.warnNoteText,
701
- testID: "tm-preview-wont-apply",
702
- children: [totals.wontApply, " item", totals.wontApply === 1 ? "" : "s", " ", "won't apply \u2014 expand", " ", blockedSources.length > 0 ? blockedSources.join(" and ") : "the source", " ", "to see the reason."]
703
- })]
704
- }) : null
705
- })
706
- // The name row is a TextInput inside this list: without "handled",
707
- // the first tap after renaming only dismisses the keyboard, so the
708
- // Restore CTA and every checkbox silently swallow one press.
709
- ,
710
- keyboardShouldPersistTaps: "handled",
711
- removeClippedSubviews: true,
712
- initialNumToRender: 20,
713
- windowSize: 7,
714
- contentContainerStyle: styles.listContent
715
- }), liveLeft && rightMeta && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
716
- style: [styles.footer, {
717
- paddingBottom: insets.bottom
718
- }],
719
- children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
720
- style: [styles.cta, isBaseline && styles.ctaDanger, ctaDisabled && styles.ctaDisabled],
721
- onPress: () => onRestore(isBaseline ? "reload" : "live", Array.from(excluded)),
722
- disabled: isBaseline ? busy || preview.building : ctaDisabled,
723
- testID: "tm-preview-restore",
724
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
725
- size: 16,
726
- color: isBaseline ? _tmTheme.tm.color.onDanger : _tmTheme.tm.color.onAccent
727
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
728
- style: [styles.ctaText, isBaseline ? styles.ctaTextDanger : null],
729
- children: isBaseline ? "Wipe & Reload" : "Restore State"
730
- })]
731
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
732
- style: styles.ctaCaption,
733
- testID: "tm-preview-cta-caption",
734
- children: isBaseline ? "This wipes all app storage and reloads the app." : totals.excludedCount > 0 ? `Restores ${totals.applies} of ${totals.applies + totals.excludedCount} change${totals.applies + totals.excludedCount === 1 ? "" : "s"} — everything else stays as it is.` : "This will replace the current state with the snapshot."
735
- })]
736
- })]
737
- });
738
- }
739
- const HIT = {
740
- top: 10,
741
- bottom: 10,
742
- left: 10,
743
- right: 10
744
- };
745
- const styles = _reactNative.StyleSheet.create({
746
- // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
747
- container: {
748
- flex: 1,
749
- backgroundColor: "transparent"
750
- },
751
- fallbackHeader: {
752
- flexDirection: "row",
753
- alignItems: "center",
754
- paddingHorizontal: _tmTheme.tm.gutter,
755
- paddingVertical: 10,
756
- gap: 10
757
- },
758
- backText: {
759
- color: _tmTheme.tm.color.accent,
760
- fontSize: _tmTheme.tm.font.control
761
- },
762
- screenTitle: {
763
- color: _tmTheme.tm.color.label,
764
- fontSize: 20,
765
- fontWeight: "700",
766
- letterSpacing: _tmTheme.tm.tracking.row,
767
- flex: 1
768
- },
769
- dimmed: {
770
- opacity: 0.5
771
- },
772
- listContent: {
773
- paddingTop: 14,
774
- paddingBottom: 12
775
- },
776
- // ── The grouped card each list row paints its own share of ──────────────
777
- cardRow: {
778
- marginHorizontal: _tmTheme.tm.gutter,
779
- backgroundColor: _tmTheme.tm.color.group
780
- },
781
- cardRowFirst: {
782
- borderTopLeftRadius: _tmTheme.tm.radius.group,
783
- borderTopRightRadius: _tmTheme.tm.radius.group
784
- },
785
- cardRowLast: {
786
- borderBottomLeftRadius: _tmTheme.tm.radius.group,
787
- borderBottomRightRadius: _tmTheme.tm.radius.group,
788
- marginBottom: 10
789
- },
790
- cardSeparator: {
791
- height: _reactNative.StyleSheet.hairlineWidth,
792
- marginLeft: 51,
793
- backgroundColor: _tmTheme.tm.color.separator
794
- },
795
- // ── Selection card ──────────────────────────────────────────────────────
796
-
797
- // ── The "won't apply" heads-up under the card ───────────────────────────
798
- warnNote: {
799
- flexDirection: "row",
800
- alignItems: "flex-start",
801
- gap: 6,
802
- paddingHorizontal: _tmTheme.tm.gutter + _tmTheme.tm.textInset,
803
- paddingTop: 8
804
- },
805
- warnNoteText: {
806
- flex: 1,
807
- color: _tmTheme.tm.color.labelSecondary,
808
- fontSize: _tmTheme.tm.font.footnote,
809
- lineHeight: _tmTheme.tm.font.footnote * 1.35
810
- },
811
- // ── Count pills on a source row ─────────────────────────────────────────
812
- countPill: {
813
- borderRadius: _tmTheme.tm.radius.pill,
814
- paddingHorizontal: 9,
815
- paddingVertical: 3
816
- },
817
- countPillText: {
818
- fontFamily: _tmTheme.TM_MONO,
819
- fontSize: _tmTheme.tm.font.footnote,
820
- fontWeight: "600"
821
- },
822
- // ── Include checkbox ────────────────────────────────────────────────────
823
- checkbox: {
824
- width: 23,
825
- height: 23,
826
- borderRadius: _tmTheme.tm.radius.pill,
827
- borderWidth: 1.7,
828
- borderColor: _tmTheme.tm.color.labelTertiary,
829
- alignItems: "center",
830
- justifyContent: "center"
831
- },
832
- checkboxChecked: {
833
- borderColor: _tmTheme.tm.color.accent,
834
- backgroundColor: _tmTheme.tm.color.accent
835
- },
836
- checkboxGlyph: {
837
- color: _tmTheme.tm.color.onAccent,
838
- fontSize: 13,
839
- fontWeight: "700",
840
- lineHeight: 15
841
- },
842
- // ── Route row ───────────────────────────────────────────────────────────
843
- routeRow: {
844
- flexDirection: "row",
845
- alignItems: "center",
846
- minHeight: 58,
847
- paddingHorizontal: _tmTheme.tm.textInset,
848
- paddingVertical: 9,
849
- gap: 12
850
- },
851
- routeText: {
852
- flex: 1,
853
- minWidth: 0,
854
- gap: 2
855
- },
856
- routeTitle: {
857
- color: _tmTheme.tm.color.label,
858
- fontSize: _tmTheme.tm.font.row,
859
- fontWeight: "600",
860
- letterSpacing: _tmTheme.tm.tracking.row
861
- },
862
- routeHref: {
863
- color: _tmTheme.tm.color.labelSecondary,
864
- fontFamily: _tmTheme.TM_MONO,
865
- fontSize: _tmTheme.tm.font.mono
866
- },
867
- // ── Source header row (first row of its card) ───────────────────────────
868
- sourceCard: {
869
- flexDirection: "row",
870
- alignItems: "center",
871
- minHeight: 58,
872
- paddingHorizontal: _tmTheme.tm.textInset,
873
- paddingVertical: 9,
874
- gap: 12
875
- },
876
- sourceCardTouch: {
877
- flex: 1,
878
- flexDirection: "row",
879
- alignItems: "center",
880
- gap: 12,
881
- minWidth: 0
882
- },
883
- sourceNameBlock: {
884
- flex: 1,
885
- minWidth: 0,
886
- gap: 2
887
- },
888
- sourceName: {
889
- color: _tmTheme.tm.color.label,
890
- fontSize: _tmTheme.tm.font.row,
891
- fontWeight: "600",
892
- letterSpacing: _tmTheme.tm.tracking.row
893
- },
894
- sourceSummary: {
895
- color: _tmTheme.tm.color.labelSecondary,
896
- fontSize: _tmTheme.tm.font.footnote,
897
- fontVariant: ["tabular-nums"]
898
- },
899
- sourceBadges: {
900
- flexDirection: "row",
901
- gap: 6
902
- },
903
- noteRow: {
904
- color: _tmTheme.tm.color.labelTertiary,
905
- fontSize: _tmTheme.tm.font.footnote,
906
- lineHeight: _tmTheme.tm.font.footnote * 1.35,
907
- paddingHorizontal: _tmTheme.tm.textInset,
908
- paddingVertical: 9
909
- },
910
- // ── Item rows (further rows of the same card) ───────────────────────────
911
- itemRow: {
912
- flexDirection: "row",
913
- alignItems: "center",
914
- paddingLeft: _tmTheme.tm.textInset,
915
- paddingRight: _tmTheme.tm.textInset,
916
- paddingVertical: 10,
917
- gap: 12
918
- },
919
- itemBodyTouch: {
920
- flex: 1,
921
- flexDirection: "row",
922
- alignItems: "center",
923
- gap: 10,
924
- minWidth: 0
925
- },
926
- itemGlyph: {
927
- fontSize: 14,
928
- fontWeight: "700",
929
- width: 14,
930
- textAlign: "center"
931
- },
932
- itemBody: {
933
- flex: 1,
934
- minWidth: 0
935
- },
936
- itemLabel: {
937
- color: _tmTheme.tm.color.label,
938
- fontSize: _tmTheme.tm.font.control,
939
- letterSpacing: _tmTheme.tm.tracking.control
940
- },
941
- itemPreview: {
942
- color: _tmTheme.tm.color.labelSecondary,
943
- fontFamily: _tmTheme.TM_MONO,
944
- fontSize: _tmTheme.tm.font.mono,
945
- marginTop: 3
946
- },
947
- itemPreviewRemoved: {
948
- textDecorationLine: "line-through"
949
- },
950
- itemReason: {
951
- color: _tmTheme.tm.color.warning,
952
- fontSize: _tmTheme.tm.font.mono,
953
- marginTop: 3
954
- },
955
- itemExcluded: {
956
- color: _tmTheme.tm.color.labelTertiary,
957
- fontSize: _tmTheme.tm.font.mono,
958
- marginTop: 3,
959
- fontStyle: "italic"
960
- },
961
- buildingSpinner: {
962
- marginTop: 28
963
- },
964
- emptyText: {
965
- color: _tmTheme.tm.color.labelSecondary,
966
- fontSize: _tmTheme.tm.font.footnote,
967
- textAlign: "center",
968
- marginTop: 28
969
- },
970
- // ── Sticky restore CTA ──────────────────────────────────────────────────
971
- footer: {
972
- paddingHorizontal: _tmTheme.tm.gutter,
973
- paddingTop: 10,
974
- gap: 8,
975
- borderTopColor: _tmTheme.tm.color.separator,
976
- borderTopWidth: _reactNative.StyleSheet.hairlineWidth,
977
- backgroundColor: _tmTheme.tm.color.sheet
978
- },
979
- cta: {
980
- flexDirection: "row",
981
- alignItems: "center",
982
- justifyContent: "center",
983
- gap: 8,
984
- height: 50,
985
- backgroundColor: _tmTheme.tm.color.accent,
986
- borderRadius: _tmTheme.tm.radius.group
987
- },
988
- ctaDanger: {
989
- backgroundColor: _tmTheme.tm.color.danger
990
- },
991
- ctaTextDanger: {
992
- color: _tmTheme.tm.color.onDanger
993
- },
994
- ctaDisabled: {
995
- opacity: 0.4
996
- },
997
- ctaText: {
998
- color: _tmTheme.tm.color.onAccent,
999
- fontSize: _tmTheme.tm.font.row,
1000
- fontWeight: "600",
1001
- letterSpacing: _tmTheme.tm.tracking.row
1002
- },
1003
- ctaCaption: {
1004
- color: _tmTheme.tm.color.labelSecondary,
1005
- fontSize: _tmTheme.tm.font.footnote,
1006
- lineHeight: _tmTheme.tm.font.footnote * 1.35,
1007
- textAlign: "center"
1008
- }
1009
- });
1010
- //# sourceMappingURL=RestorePreviewScreen.js.map
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.RestorePreviewScreen=RestorePreviewScreen;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _core=require("@buoy-gg/core");var _HeaderActions=require("../HeaderActions");var _primitives=require("../../ui/primitives");var _tmTheme=require("../../ui/tmTheme");var _timeMachineStore=require("../../store/timeMachineStore");var _ItemDiffPanel=require("./ItemDiffPanel");var _RestorePointNameRow=require("./RestorePointNameRow");var _verdictMeta=require("./verdictMeta");var _jsxRuntime=require("react/jsx-runtime");const SOURCE_NOUNS={storage:"keys",redux:"slices",zustand:"stores",jotai:"atoms",query:"queries"};const EMPTY_SELECTION=new Set;function sameSelection(o,t){if(o.size!==t.size)return false;for(const f of o)if(!t.has(f))return false;return true}function cKey(o,t){return`${o}::${t}`}function shortPreview(o){if(o===void 0)return"\u2205";try{const t=typeof o==="string"?`"${o}"`:JSON.stringify(o);if(!t)return String(o);return t.length>46?`${t.slice(0,43)}\u2026`:t}catch{return"[complex]"}}function CardRow({position:o,children:t}){return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.cardRow,o.first?styles.cardRowFirst:null,o.last?styles.cardRowLast:null],children:[o.first?null:(0,_jsxRuntime.jsx)(_reactNative.View,{style:styles.cardSeparator}),t]})}function CountPill({glyph:o,count:t,color:f,testID:y}){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.countPill,{backgroundColor:`${f}26`}],children:(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:[styles.countPillText,{color:f}],testID:y,children:[o,t]})})}function Checkbox({state:o}){return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[styles.checkbox,o!=="none"&&styles.checkboxChecked],children:o==="none"?null:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.checkboxGlyph,children:o==="all"?"\u2713":"\u2212"})})}const ItemRow=(0,_react.memo)(function o({row:t,expanded:f,onOpen:y,onToggle:R}){const h=_verdictMeta.VERDICT_META[t.item.verdict];const b=!h.applies||t.excluded;const S=t.item.verdict==="changed"||t.item.verdict==="added"||t.item.verdict==="removed";const a=`tm-preview-item-${t.sourceId}-${t.item.key}`;return(0,_jsxRuntime.jsx)(CardRow,{position:t,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.itemRow,b&&styles.dimmed],children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:R,disabled:!h.applies,hitSlop:{top:10,bottom:10,left:8,right:8},testID:`tm-preview-toggle-${t.sourceId}-${t.item.key}`,accessibilityRole:"checkbox",accessibilityState:{checked:!(!h.applies||t.excluded)},accessibilityLabel:`Include ${t.item.label}`,children:(0,_jsxRuntime.jsx)(Checkbox,{state:!h.applies||t.excluded?"none":"all"})}),(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.itemBodyTouch,onPress:()=>S&&y(),disabled:!S,testID:a,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.itemGlyph,{color:h.color}],children:h.glyph}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.itemBody,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.itemLabel,numberOfLines:1,testID:`${a}-label`,children:t.item.label}),t.item.verdict==="changed"&&(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.itemPreview,numberOfLines:1,testID:`${a}-values`,children:[shortPreview(t.item.oldValue)," \u2192"," ",shortPreview(t.item.newValue)]}),t.item.verdict==="added"&&(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.itemPreview,numberOfLines:1,testID:`${a}-values`,children:shortPreview(t.item.newValue)}),t.item.verdict==="removed"&&(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.itemPreview,styles.itemPreviewRemoved],numberOfLines:1,testID:`${a}-values`,children:shortPreview(t.item.oldValue)}),t.excluded?(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.itemExcluded,testID:`${a}-excluded`,children:"excluded \u2014 restore leaves this untouched"}):t.item.reason&&(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.itemReason,numberOfLines:2,testID:`${a}-reason`,children:t.item.reason})]}),S?f?(0,_jsxRuntime.jsx)(_sharedUi.ChevronUp,{size:16,color:_tmTheme.tm.color.labelTertiary}):(0,_jsxRuntime.jsx)(_primitives.DisclosureChevron,{}):null]})]})})});function RestorePreviewScreen({preview:o,snapshots:t,busy:f,onBack:y,onRestore:R}){const h=(0,_core.useToolHost)();const b=h.instanceId!==void 0;const S=(0,_sharedUi.useSafeAreaInsets)({minBottom:16});const[a,W]=(0,_react.useState)({});const[d,C]=(0,_react.useState)(new Set);const v=(0,_react.useRef)(false);const[j,q]=(0,_react.useState)(EMPTY_SELECTION);const w=(0,_react.useCallback)(e=>{q(i=>{const s=new Set(i);if(s.has(e))s.delete(e);else s.add(e);return s})},[]);const D=h.search;const r=o.right==="current"?void 0:t.find(e=>e.id===o.right.snapshotId);const $=(0,_react.useRef)(r);$.current=r;const z=(0,_react.useCallback)(()=>{if(!$.current)return;const e=$.current;void _timeMachineStore.timeMachineStore.setRestoreRoute(e.id,!(0,_timeMachineStore.willRestoreRoute)(e))},[]);const g=r?.baseline===true;const B=o.left==="current";const x=(0,_react.useMemo)(()=>{if(!r)return null;return r.excludedKeys?.length?new Set(r.excludedKeys):EMPTY_SELECTION},[r]);const k=!!r?.scope?.length&&!r?.excludedKeys?.length;const P=(0,_react.useMemo)(()=>{if(!k||!r?.scope)return EMPTY_SELECTION;const e=new Set(r.scope);const i=new Set;for(const s of o.sources){for(const n of s.items){const c=cKey(s.id,n.key);if(_verdictMeta.VERDICT_META[n.verdict].applies&&!e.has(c)){i.add(c)}}}return i},[k,r,o.sources]);const V=(0,_react.useRef)(null);(0,_react.useEffect)(()=>{if(!r||o.building||!x)return;if(V.current===r.id)return;V.current=r.id;v.current=false;const e=x.size>0?x:P;if(e.size>0)C(new Set(e));if(k&&e.size>0){void _timeMachineStore.timeMachineStore.setExclusions(r.id,Array.from(e))}},[r,o.building,x,P,k]);(0,_react.useEffect)(()=>{if(!r||!x||!v.current)return;if(sameSelection(d,x))return;const e=Array.from(d);void _timeMachineStore.timeMachineStore.setExclusions(r.id,e.length?e:null)},[d,r,x]);const L=(0,_react.useCallback)(e=>{v.current=true;C(i=>{const s=new Set(i);if(s.has(e))s.delete(e);else s.add(e);return s})},[]);const O=(0,_react.useCallback)((e,i)=>{v.current=true;C(s=>{const n=new Set(s);for(const c of e.items){if(!_verdictMeta.VERDICT_META[c.verdict].applies)continue;const p=cKey(e.id,c.key);if(i==="all")n.add(p);else n.delete(p)}return n})},[]);const M=(0,_react.useCallback)((e,i)=>{W(s=>({...s,[e]:!i}))},[]);const u=(0,_react.useMemo)(()=>{let e=0;let i=0;let s=0;const n=[];for(const c of o.sources){let p=0;for(const m of c.items){const T=_verdictMeta.VERDICT_META[m.verdict];if(T.applies){if(d.has(cKey(c.id,m.key)))i+=1;else{e+=1;p+=1}}else if(m.verdict==="wont-apply")s+=1}if(p>0)n.push(`${p} ${c.label.toLowerCase()}`)}return{applies:e,excludedCount:i,wontApply:s,perSource:n}},[o.sources,d]);(0,_core.useToolHeader)(b?{title:"Restore Preview",subtitle:r?.name??"preview",onBack:y,actions:(0,_jsxRuntime.jsx)(_HeaderActions.TimeMachineHeaderActions,{armId:r?.id})}:null,[b,r?.name,r?.id]);const{rows:F,blockedSources:A}=(0,_react.useMemo)(()=>{const e=D.trim().toLowerCase();const i=[];const s=[];for(const n of o.sources){const c=n.items.some(l=>l.verdict!=="unchanged");if(!c&&!n.error)continue;const p=c||n.notes.length>0||!!n.error;const m=n.items.filter(l=>_verdictMeta.VERDICT_META[l.verdict].applies);const T=n.items.filter(l=>l.verdict==="wont-apply").length;if(T>0)s.push(n.label);const J=n.items.length===1?(SOURCE_NOUNS[n.id]??"items").replace(/ies$/,"y").replace(/s$/,""):SOURCE_NOUNS[n.id]??"items";const Y=`${n.items.length} ${J} \xB7 `+(m.length>0?`${m.length} changed`:T>0?`${T} won't apply`:"no changes");const H=m.filter(l=>d.has(cKey(n.id,l.key))).length;const Q=m.length===0||H===0?"all":H===m.length?"none":"some";const I={added:0,removed:0,changed:0};for(const l of n.items){if(d.has(cKey(n.id,l.key)))continue;if(l.verdict==="added")I.added+=1;else if(l.verdict==="removed")I.removed+=1;else if(l.verdict==="changed"||l.verdict==="too-large")I.changed+=1}const X=n.items.filter(l=>{if(l.verdict==="unchanged")return false;if(e&&!l.label.toLowerCase().includes(e))return false;return true});const _=p?a[n.id]??true:true;i.push({kind:"source",source:n,collapsed:_,includeState:Q,applicableCount:m.length,counts:I,expandable:p,summary:Y,first:false,last:false});if(!_){for(const l of n.notes){i.push({kind:"note",id:`${n.id}:${l}`,text:l,first:false,last:false})}if(n.error){i.push({kind:"note",id:`${n.id}:err`,text:`\u26A0 ${n.error}`,first:false,last:false})}for(const l of X){const N=cKey(n.id,l.key);i.push({kind:"item",sourceId:n.id,item:l,excluded:d.has(N),first:false,last:false});if(j.has(N)){i.push({kind:"diff",sourceId:n.id,item:l,first:false,last:false})}}}}if(r?.route){i.push({kind:"route",href:r.route.href,on:(0,_timeMachineStore.willRestoreRoute)(r),first:false,last:false})}if(i.length>0){i[0].first=true;i[i.length-1].last=true}return{rows:i,blockedSources:s}},[o.sources,D,a,d,j,r]);const G=(0,_react.useCallback)(({item:e})=>{if(e.kind==="source"){const i=e.source;const{counts:s}=e;return(0,_jsxRuntime.jsx)(CardRow,{position:e,children:(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sourceCard,children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:()=>O(i,e.includeState),disabled:e.applicableCount===0,hitSlop:{top:10,bottom:10,left:8,right:8},testID:`tm-preview-source-toggle-${i.id}`,children:(0,_jsxRuntime.jsx)(Checkbox,{state:e.applicableCount===0?"none":e.includeState})}),(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.sourceCardTouch,onPress:()=>e.expandable&&M(i.id,e.collapsed),disabled:!e.expandable,testID:`tm-preview-source-${i.id}`,children:[(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sourceNameBlock,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sourceName,numberOfLines:1,testID:`tm-preview-source-${i.id}-name`,children:i.label}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.sourceSummary,numberOfLines:1,testID:`tm-preview-source-${i.id}-summary`,children:e.includeState==="none"&&e.applicableCount>0?`${e.summary} \xB7 excluded`:e.summary})]}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.sourceBadges,children:[s.removed>0?(0,_jsxRuntime.jsx)(CountPill,{glyph:"\u2212",count:s.removed,color:_tmTheme.tm.color.danger,testID:`tm-preview-source-${i.id}-removed`}):null,s.added>0?(0,_jsxRuntime.jsx)(CountPill,{glyph:"+",count:s.added,color:_tmTheme.tm.color.accent,testID:`tm-preview-source-${i.id}-added`}):null,s.changed>0?(0,_jsxRuntime.jsx)(CountPill,{glyph:"\u2248",count:s.changed,color:_tmTheme.tm.color.info,testID:`tm-preview-source-${i.id}-changed`}):null]}),e.expandable?e.collapsed?(0,_jsxRuntime.jsx)(_primitives.DisclosureChevron,{}):(0,_jsxRuntime.jsx)(_sharedUi.ChevronUp,{size:16,color:_tmTheme.tm.color.labelTertiary}):null]})]})})}if(e.kind==="note"){return(0,_jsxRuntime.jsx)(CardRow,{position:e,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.noteRow,testID:`tm-preview-note-${e.id}`,children:e.text})})}if(e.kind==="route"){return(0,_jsxRuntime.jsx)(CardRow,{position:e,children:(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:styles.routeRow,onPress:z,accessibilityRole:"switch",accessibilityState:{checked:e.on},accessibilityLabel:`Also return to ${e.href}`,testID:"tm-preview-route",children:[(0,_jsxRuntime.jsx)(Checkbox,{state:e.on?"all":"none"}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.routeText,children:[(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.routeTitle,children:"Return to this screen"}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.routeHref,numberOfLines:1,testID:"tm-preview-route-href",children:e.href})]})]})})}if(e.kind==="diff"){return(0,_jsxRuntime.jsx)(CardRow,{position:e,children:(0,_jsxRuntime.jsx)(_ItemDiffPanel.ItemDiffPanel,{item:e.item,testID:`tm-preview-diff-${e.sourceId}-${e.item.key}`})})}return(0,_jsxRuntime.jsx)(ItemRow,{row:e,expanded:j.has(cKey(e.sourceId,e.item.key)),onOpen:()=>w(cKey(e.sourceId,e.item.key)),onToggle:()=>L(cKey(e.sourceId,e.item.key))})},[j,w,M,L,O,z]);const U=(0,_react.useCallback)(e=>{if(e.kind==="source")return`s:${e.source.id}`;if(e.kind==="note")return`n:${e.id}`;if(e.kind==="route")return"route";if(e.kind==="diff")return`d:${e.sourceId}:${e.item.key}`;return`i:${e.sourceId}:${e.item.key}`},[]);const K=(0,_jsxRuntime.jsxs)(_reactNative.View,{children:[!b&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.fallbackHeader,children:[(0,_jsxRuntime.jsx)(_reactNative.TouchableOpacity,{onPress:y,hitSlop:HIT,testID:"tm-preview-back",children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.backText,children:"\u2039 Back"})}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.screenTitle,numberOfLines:1,children:"Restore Preview"})]}),r?(0,_jsxRuntime.jsxs)(_jsxRuntime.Fragment,{children:[(0,_jsxRuntime.jsx)(_primitives.SectionLabel,{children:"Restore Point"}),(0,_jsxRuntime.jsx)(_primitives.Group,{children:(0,_jsxRuntime.jsx)(_RestorePointNameRow.RestorePointNameRow,{name:r.name,onRename:e=>void _timeMachineStore.timeMachineStore.rename(r.id,e)})})]}):null,(0,_jsxRuntime.jsx)(_primitives.SectionLabel,{children:"Changes to Restore"})]});const E=f||o.building||u.applies===0;return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.container,children:[(0,_jsxRuntime.jsx)(_reactNative.FlatList,{testID:"tm-preview-list",data:F,renderItem:G,keyExtractor:U,ListHeaderComponent:K,ListEmptyComponent:o.building?(0,_jsxRuntime.jsx)(_reactNative.ActivityIndicator,{style:styles.buildingSpinner,size:"small",color:_tmTheme.tm.color.accent}):(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.emptyText,testID:"tm-preview-empty",children:D?"Nothing matches this filter.":"No changes \u2014 restoring would change nothing."}),ListFooterComponent:(0,_jsxRuntime.jsx)(_reactNative.View,{children:u.wontApply>0?(0,_jsxRuntime.jsxs)(_reactNative.View,{style:styles.warnNote,children:[(0,_jsxRuntime.jsx)(_sharedUi.AlertTriangle,{size:12,color:_tmTheme.tm.color.warning}),(0,_jsxRuntime.jsxs)(_reactNative.Text,{style:styles.warnNoteText,testID:"tm-preview-wont-apply",children:[u.wontApply," item",u.wontApply===1?"":"s"," ","won't apply \u2014 expand"," ",A.length>0?A.join(" and "):"the source"," ","to see the reason."]})]}):null}),keyboardShouldPersistTaps:"handled",removeClippedSubviews:true,initialNumToRender:20,windowSize:7,contentContainerStyle:styles.listContent}),B&&r&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[styles.footer,{paddingBottom:S.bottom}],children:[(0,_jsxRuntime.jsxs)(_reactNative.TouchableOpacity,{style:[styles.cta,g&&styles.ctaDanger,E&&styles.ctaDisabled],onPress:()=>R(g?"reload":"live",Array.from(d)),disabled:g?f||o.building:E,testID:"tm-preview-restore",children:[(0,_jsxRuntime.jsx)(_sharedUi.RefreshCw,{size:16,color:g?_tmTheme.tm.color.onDanger:_tmTheme.tm.color.onAccent}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:[styles.ctaText,g?styles.ctaTextDanger:null],children:g?"Wipe & Reload":"Restore State"})]}),(0,_jsxRuntime.jsx)(_reactNative.Text,{style:styles.ctaCaption,testID:"tm-preview-cta-caption",children:g?"This wipes all app storage and reloads the app.":u.excludedCount>0?`Restores ${u.applies} of ${u.applies+u.excludedCount} change${u.applies+u.excludedCount===1?"":"s"} \u2014 everything else stays as it is.`:"This will replace the current state with the snapshot."})]})]})}const HIT={top:10,bottom:10,left:10,right:10};const styles=_reactNative.StyleSheet.create({container:{flex:1,backgroundColor:"transparent"},fallbackHeader:{flexDirection:"row",alignItems:"center",paddingHorizontal:_tmTheme.tm.gutter,paddingVertical:10,gap:10},backText:{color:_tmTheme.tm.color.accent,fontSize:_tmTheme.tm.font.control},screenTitle:{color:_tmTheme.tm.color.label,fontSize:20,fontWeight:"700",letterSpacing:_tmTheme.tm.tracking.row,flex:1},dimmed:{opacity:.5},listContent:{paddingTop:14,paddingBottom:12},cardRow:{marginHorizontal:_tmTheme.tm.gutter,backgroundColor:_tmTheme.tm.color.group},cardRowFirst:{borderTopLeftRadius:_tmTheme.tm.radius.group,borderTopRightRadius:_tmTheme.tm.radius.group},cardRowLast:{borderBottomLeftRadius:_tmTheme.tm.radius.group,borderBottomRightRadius:_tmTheme.tm.radius.group,marginBottom:10},cardSeparator:{height:_reactNative.StyleSheet.hairlineWidth,marginLeft:51,backgroundColor:_tmTheme.tm.color.separator},warnNote:{flexDirection:"row",alignItems:"flex-start",gap:6,paddingHorizontal:_tmTheme.tm.gutter+_tmTheme.tm.textInset,paddingTop:8},warnNoteText:{flex:1,color:_tmTheme.tm.color.labelSecondary,fontSize:_tmTheme.tm.font.footnote,lineHeight:_tmTheme.tm.font.footnote*1.35},countPill:{borderRadius:_tmTheme.tm.radius.pill,paddingHorizontal:9,paddingVertical:3},countPillText:{fontFamily:_tmTheme.TM_MONO,fontSize:_tmTheme.tm.font.footnote,fontWeight:"600"},checkbox:{width:23,height:23,borderRadius:_tmTheme.tm.radius.pill,borderWidth:1.7,borderColor:_tmTheme.tm.color.labelTertiary,alignItems:"center",justifyContent:"center"},checkboxChecked:{borderColor:_tmTheme.tm.color.accent,backgroundColor:_tmTheme.tm.color.accent},checkboxGlyph:{color:_tmTheme.tm.color.onAccent,fontSize:13,fontWeight:"700",lineHeight:15},routeRow:{flexDirection:"row",alignItems:"center",minHeight:58,paddingHorizontal:_tmTheme.tm.textInset,paddingVertical:9,gap:12},routeText:{flex:1,minWidth:0,gap:2},routeTitle:{color:_tmTheme.tm.color.label,fontSize:_tmTheme.tm.font.row,fontWeight:"600",letterSpacing:_tmTheme.tm.tracking.row},routeHref:{color:_tmTheme.tm.color.labelSecondary,fontFamily:_tmTheme.TM_MONO,fontSize:_tmTheme.tm.font.mono},sourceCard:{flexDirection:"row",alignItems:"center",minHeight:58,paddingHorizontal:_tmTheme.tm.textInset,paddingVertical:9,gap:12},sourceCardTouch:{flex:1,flexDirection:"row",alignItems:"center",gap:12,minWidth:0},sourceNameBlock:{flex:1,minWidth:0,gap:2},sourceName:{color:_tmTheme.tm.color.label,fontSize:_tmTheme.tm.font.row,fontWeight:"600",letterSpacing:_tmTheme.tm.tracking.row},sourceSummary:{color:_tmTheme.tm.color.labelSecondary,fontSize:_tmTheme.tm.font.footnote,fontVariant:["tabular-nums"]},sourceBadges:{flexDirection:"row",gap:6},noteRow:{color:_tmTheme.tm.color.labelTertiary,fontSize:_tmTheme.tm.font.footnote,lineHeight:_tmTheme.tm.font.footnote*1.35,paddingHorizontal:_tmTheme.tm.textInset,paddingVertical:9},itemRow:{flexDirection:"row",alignItems:"center",paddingLeft:_tmTheme.tm.textInset,paddingRight:_tmTheme.tm.textInset,paddingVertical:10,gap:12},itemBodyTouch:{flex:1,flexDirection:"row",alignItems:"center",gap:10,minWidth:0},itemGlyph:{fontSize:14,fontWeight:"700",width:14,textAlign:"center"},itemBody:{flex:1,minWidth:0},itemLabel:{color:_tmTheme.tm.color.label,fontSize:_tmTheme.tm.font.control,letterSpacing:_tmTheme.tm.tracking.control},itemPreview:{color:_tmTheme.tm.color.labelSecondary,fontFamily:_tmTheme.TM_MONO,fontSize:_tmTheme.tm.font.mono,marginTop:3},itemPreviewRemoved:{textDecorationLine:"line-through"},itemReason:{color:_tmTheme.tm.color.warning,fontSize:_tmTheme.tm.font.mono,marginTop:3},itemExcluded:{color:_tmTheme.tm.color.labelTertiary,fontSize:_tmTheme.tm.font.mono,marginTop:3,fontStyle:"italic"},buildingSpinner:{marginTop:28},emptyText:{color:_tmTheme.tm.color.labelSecondary,fontSize:_tmTheme.tm.font.footnote,textAlign:"center",marginTop:28},footer:{paddingHorizontal:_tmTheme.tm.gutter,paddingTop:10,gap:8,borderTopColor:_tmTheme.tm.color.separator,borderTopWidth:_reactNative.StyleSheet.hairlineWidth,backgroundColor:_tmTheme.tm.color.sheet},cta:{flexDirection:"row",alignItems:"center",justifyContent:"center",gap:8,height:50,backgroundColor:_tmTheme.tm.color.accent,borderRadius:_tmTheme.tm.radius.group},ctaDanger:{backgroundColor:_tmTheme.tm.color.danger},ctaTextDanger:{color:_tmTheme.tm.color.onDanger},ctaDisabled:{opacity:.4},ctaText:{color:_tmTheme.tm.color.onAccent,fontSize:_tmTheme.tm.font.row,fontWeight:"600",letterSpacing:_tmTheme.tm.tracking.row},ctaCaption:{color:_tmTheme.tm.color.labelSecondary,fontSize:_tmTheme.tm.font.footnote,lineHeight:_tmTheme.tm.font.footnote*1.35,textAlign:"center"}});