@buoy-gg/time-machine 7.0.17 → 7.0.18

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 (69) hide show
  1. package/lib/commonjs/components/HeaderReloadButton.js +61 -0
  2. package/lib/commonjs/components/HeaderReloadButton.js.map +1 -0
  3. package/lib/commonjs/components/TimeMachineBody.js +343 -127
  4. package/lib/commonjs/components/TimeMachineBody.js.map +1 -1
  5. package/lib/commonjs/components/TimeMachineModal.js +1 -0
  6. package/lib/commonjs/components/TimeMachineModal.js.map +1 -1
  7. package/lib/commonjs/components/preview/ItemDetailScreen.js +189 -0
  8. package/lib/commonjs/components/preview/ItemDetailScreen.js.map +1 -0
  9. package/lib/commonjs/components/preview/RestorePreviewScreen.js +1056 -0
  10. package/lib/commonjs/components/preview/RestorePreviewScreen.js.map +1 -0
  11. package/lib/commonjs/components/preview/verdictMeta.js +58 -0
  12. package/lib/commonjs/components/preview/verdictMeta.js.map +1 -0
  13. package/lib/commonjs/preset.js +11 -2
  14. package/lib/commonjs/preset.js.map +1 -1
  15. package/lib/commonjs/store/snapshotVault.js +19 -0
  16. package/lib/commonjs/store/snapshotVault.js.map +1 -1
  17. package/lib/commonjs/store/timeMachineStore.js +298 -7
  18. package/lib/commonjs/store/timeMachineStore.js.map +1 -1
  19. package/lib/commonjs/sync/timeMachineSyncAdapter.js +62 -2
  20. package/lib/commonjs/sync/timeMachineSyncAdapter.js.map +1 -1
  21. package/lib/module/components/HeaderReloadButton.js +56 -0
  22. package/lib/module/components/HeaderReloadButton.js.map +1 -0
  23. package/lib/module/components/TimeMachineBody.js +345 -129
  24. package/lib/module/components/TimeMachineBody.js.map +1 -1
  25. package/lib/module/components/TimeMachineModal.js +1 -0
  26. package/lib/module/components/TimeMachineModal.js.map +1 -1
  27. package/lib/module/components/preview/ItemDetailScreen.js +184 -0
  28. package/lib/module/components/preview/ItemDetailScreen.js.map +1 -0
  29. package/lib/module/components/preview/RestorePreviewScreen.js +1051 -0
  30. package/lib/module/components/preview/RestorePreviewScreen.js.map +1 -0
  31. package/lib/module/components/preview/verdictMeta.js +53 -0
  32. package/lib/module/components/preview/verdictMeta.js.map +1 -0
  33. package/lib/module/preset.js +11 -2
  34. package/lib/module/preset.js.map +1 -1
  35. package/lib/module/store/snapshotVault.js +18 -0
  36. package/lib/module/store/snapshotVault.js.map +1 -1
  37. package/lib/module/store/timeMachineStore.js +299 -8
  38. package/lib/module/store/timeMachineStore.js.map +1 -1
  39. package/lib/module/sync/timeMachineSyncAdapter.js +62 -2
  40. package/lib/module/sync/timeMachineSyncAdapter.js.map +1 -1
  41. package/lib/typescript/components/HeaderReloadButton.d.ts +2 -0
  42. package/lib/typescript/components/HeaderReloadButton.d.ts.map +1 -0
  43. package/lib/typescript/components/TimeMachineBody.d.ts.map +1 -1
  44. package/lib/typescript/components/TimeMachineModal.d.ts.map +1 -1
  45. package/lib/typescript/components/preview/ItemDetailScreen.d.ts +11 -0
  46. package/lib/typescript/components/preview/ItemDetailScreen.d.ts.map +1 -0
  47. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts +15 -0
  48. package/lib/typescript/components/preview/RestorePreviewScreen.d.ts.map +1 -0
  49. package/lib/typescript/components/preview/verdictMeta.d.ts +10 -0
  50. package/lib/typescript/components/preview/verdictMeta.d.ts.map +1 -0
  51. package/lib/typescript/preset.d.ts +15 -3
  52. package/lib/typescript/preset.d.ts.map +1 -1
  53. package/lib/typescript/store/snapshotVault.d.ts +7 -0
  54. package/lib/typescript/store/snapshotVault.d.ts.map +1 -1
  55. package/lib/typescript/store/timeMachineStore.d.ts +49 -2
  56. package/lib/typescript/store/timeMachineStore.d.ts.map +1 -1
  57. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts +36 -0
  58. package/lib/typescript/sync/timeMachineSyncAdapter.d.ts.map +1 -1
  59. package/package.json +13 -6
  60. package/src/components/HeaderReloadButton.tsx +45 -0
  61. package/src/components/TimeMachineBody.tsx +416 -137
  62. package/src/components/TimeMachineModal.tsx +1 -0
  63. package/src/components/preview/ItemDetailScreen.tsx +172 -0
  64. package/src/components/preview/RestorePreviewScreen.tsx +1089 -0
  65. package/src/components/preview/verdictMeta.ts +40 -0
  66. package/src/preset.tsx +11 -2
  67. package/src/store/snapshotVault.ts +28 -0
  68. package/src/store/timeMachineStore.ts +330 -7
  69. package/src/sync/timeMachineSyncAdapter.ts +63 -3
@@ -0,0 +1,1056 @@
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 _floatingToolsCore = require("@buoy-gg/floating-tools-core");
11
+ var _core = require("@buoy-gg/core");
12
+ var _timeMachineStore = require("../../store/timeMachineStore");
13
+ var _verdictMeta = require("./verdictMeta");
14
+ var _jsxRuntime = require("react/jsx-runtime");
15
+ /**
16
+ * Restore Preview — "what will restoring this snapshot actually change?"
17
+ *
18
+ * ONE virtualized FlatList is the scrollable area (compare card, filters,
19
+ * source cards and item rows are all list rows) with a sticky Restore CTA
20
+ * footer — no nested scroll regions, thousands of storage keys stay cheap.
21
+ *
22
+ * Layout follows the Time Machine mockup: source sections are card
23
+ * dropdowns with tool-icon tiles, a "N keys · M changed" detection subtitle,
24
+ * ±≈ badges, a search field with an expand/collapse-all button, and a bottom
25
+ * "Restore State" pill. Only rows a restore would touch (or can't touch)
26
+ * render — unchanged items never do. Every applicable item keeps its
27
+ * INCLUDE checkbox (source headers toggle their whole section) — unchecked
28
+ * items are passed to restore as exclusions and left untouched.
29
+ */
30
+
31
+ const SOURCE_ICONS = {
32
+ storage: _floatingToolsCore.StorageIcon,
33
+ redux: _floatingToolsCore.ReduxIcon,
34
+ zustand: _floatingToolsCore.ZustandIcon,
35
+ jotai: _floatingToolsCore.JotaiIcon,
36
+ query: _floatingToolsCore.QueryIcon
37
+ };
38
+ const SOURCE_NOUNS = {
39
+ storage: "keys",
40
+ redux: "slices",
41
+ zustand: "stores",
42
+ jotai: "atoms",
43
+ query: "queries"
44
+ };
45
+ /** Compound exclusion key — what timeMachineStore.restore expects. */
46
+ function cKey(sourceId, itemKey) {
47
+ return `${sourceId}::${itemKey}`;
48
+ }
49
+ function shortPreview(value) {
50
+ if (value === undefined) return "∅";
51
+ try {
52
+ const s = typeof value === "string" ? `"${value}"` : JSON.stringify(value);
53
+ if (!s) return String(value);
54
+ return s.length > 46 ? `${s.slice(0, 43)}…` : s;
55
+ } catch {
56
+ return "[complex]";
57
+ }
58
+ }
59
+ function sideName(ref, snapshots) {
60
+ if (ref === "current") return "Current state";
61
+ return snapshots.find(s => s.id === ref.snapshotId)?.name ?? ref.snapshotId;
62
+ }
63
+ function timeAgo(ts) {
64
+ const s = Math.max(0, Math.round((Date.now() - ts) / 1000));
65
+ if (s < 60) return `${s}s`;
66
+ const m = Math.round(s / 60);
67
+ if (m < 60) return `${m}m`;
68
+ const h = Math.round(m / 60);
69
+ return h < 24 ? `${h}h` : `${Math.round(h / 24)}d`;
70
+ }
71
+ function Checkbox({
72
+ state
73
+ }) {
74
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
75
+ style: [styles.checkbox, state !== "none" && styles.checkboxChecked],
76
+ children: [state === "all" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
77
+ style: styles.checkboxGlyph,
78
+ children: "\u2713"
79
+ }), state === "some" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
80
+ style: styles.checkboxGlyph,
81
+ children: "\u2212"
82
+ })]
83
+ });
84
+ }
85
+ const ItemRow = /*#__PURE__*/(0, _react.memo)(function ItemRow({
86
+ row,
87
+ onOpen,
88
+ onToggle
89
+ }) {
90
+ const meta = _verdictMeta.VERDICT_META[row.item.verdict];
91
+ const dimmed = !meta.applies || row.excluded;
92
+ const openable = row.item.verdict === "changed" || row.item.verdict === "added" || row.item.verdict === "removed";
93
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
94
+ style: [styles.itemRow, dimmed && styles.dimmed],
95
+ children: [meta.applies && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
96
+ onPress: onToggle,
97
+ hitSlop: {
98
+ top: 10,
99
+ bottom: 10,
100
+ left: 8,
101
+ right: 8
102
+ },
103
+ testID: `tm-preview-toggle-${row.sourceId}-${row.item.key}`,
104
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Checkbox, {
105
+ state: row.excluded ? "none" : "all"
106
+ })
107
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
108
+ style: styles.itemBodyTouch,
109
+ onPress: () => openable && onOpen(),
110
+ disabled: !openable,
111
+ testID: `tm-preview-item-${row.sourceId}-${row.item.key}`,
112
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
113
+ style: [styles.itemGlyph, {
114
+ color: meta.color
115
+ }],
116
+ children: meta.glyph
117
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
118
+ style: styles.itemBody,
119
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
120
+ style: styles.itemLabel,
121
+ numberOfLines: 1,
122
+ children: row.item.label
123
+ }), row.item.verdict === "changed" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
124
+ style: styles.itemPreview,
125
+ numberOfLines: 1,
126
+ children: [shortPreview(row.item.oldValue), " \u2192", " ", shortPreview(row.item.newValue)]
127
+ }), row.item.verdict === "added" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
128
+ style: styles.itemPreview,
129
+ numberOfLines: 1,
130
+ children: shortPreview(row.item.newValue)
131
+ }), row.item.verdict === "removed" && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
132
+ style: [styles.itemPreview, styles.itemPreviewRemoved],
133
+ numberOfLines: 1,
134
+ children: shortPreview(row.item.oldValue)
135
+ }), row.excluded ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
136
+ style: styles.itemExcluded,
137
+ children: "excluded \u2014 restore leaves this untouched"
138
+ }) : row.item.reason && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
+ style: styles.itemReason,
140
+ numberOfLines: 2,
141
+ children: row.item.reason
142
+ })]
143
+ }), openable && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
144
+ style: styles.itemChevron,
145
+ children: "\u203A"
146
+ })]
147
+ })]
148
+ });
149
+ });
150
+ function RestorePreviewScreen({
151
+ preview,
152
+ snapshots,
153
+ busy,
154
+ onBack,
155
+ onOpenItem,
156
+ onRestore,
157
+ onPickSide,
158
+ onRefresh
159
+ }) {
160
+ const host = (0, _core.useToolHost)();
161
+ const hosted = host.instanceId !== undefined;
162
+ const [localSearch, setLocalSearch] = (0, _react.useState)("");
163
+ const [collapsed, setCollapsed] = (0, _react.useState)({});
164
+ const [allCollapsed, setAllCollapsed] = (0, _react.useState)(false);
165
+ const [excluded, setExcluded] = (0, _react.useState)(new Set());
166
+ const search = localSearch;
167
+ const rightMeta = preview.right === "current" ? undefined : snapshots.find(s => s.id === preview.right.snapshotId);
168
+ const isBaseline = rightMeta?.baseline === true;
169
+ const liveLeft = preview.left === "current";
170
+
171
+ // Master selection: "None → tick just what you want" is the fast path for
172
+ // targeted restores ("only my cart"), the inverse of the default all-in.
173
+ const selectAll = (0, _react.useCallback)(() => setExcluded(new Set()), []);
174
+ const selectNone = (0, _react.useCallback)(() => {
175
+ const next = new Set();
176
+ for (const src of preview.sources) {
177
+ for (const item of src.items) {
178
+ if (_verdictMeta.VERDICT_META[item.verdict].applies) next.add(cKey(src.id, item.key));
179
+ }
180
+ }
181
+ setExcluded(next);
182
+ }, [preview.sources]);
183
+
184
+ // A saved scope pre-selects the checkboxes: everything applicable that is
185
+ // OUTSIDE the scope starts excluded, so the preview shows exactly what a
186
+ // scoped restore will do (and stays adjustable).
187
+ const seededScopeFor = (0, _react.useRef)(null);
188
+ (0, _react.useEffect)(() => {
189
+ if (!rightMeta?.scope?.length || preview.building) return;
190
+ if (seededScopeFor.current === rightMeta.id) return;
191
+ seededScopeFor.current = rightMeta.id;
192
+ const scopeSet = new Set(rightMeta.scope);
193
+ const next = new Set();
194
+ for (const src of preview.sources) {
195
+ for (const item of src.items) {
196
+ const key = cKey(src.id, item.key);
197
+ if (_verdictMeta.VERDICT_META[item.verdict].applies && !scopeSet.has(key)) {
198
+ next.add(key);
199
+ }
200
+ }
201
+ }
202
+ setExcluded(next);
203
+ }, [rightMeta, preview.building, preview.sources]);
204
+
205
+ // Persist the current selection as this restore point's scope — from then
206
+ // on, RESTORE (home row, MCP, this screen) touches only these items.
207
+ // Selecting everything clears the scope back to a full restore.
208
+ const saveScope = (0, _react.useCallback)(() => {
209
+ if (!rightMeta) return;
210
+ const selected = [];
211
+ for (const src of preview.sources) {
212
+ for (const item of src.items) {
213
+ const key = cKey(src.id, item.key);
214
+ if (_verdictMeta.VERDICT_META[item.verdict].applies && !excluded.has(key)) {
215
+ selected.push(key);
216
+ }
217
+ }
218
+ }
219
+ const clearing = excluded.size === 0;
220
+ void _timeMachineStore.timeMachineStore.setScope(rightMeta.id, clearing ? null : selected);
221
+ host.showToast(clearing ? "Scope cleared — full restore" : `Scope saved — this restore point now restores only ${selected.length} item${selected.length === 1 ? "" : "s"}`, "success");
222
+ }, [rightMeta, preview.sources, excluded, host]);
223
+ const toggleItem = (0, _react.useCallback)(key => {
224
+ setExcluded(prev => {
225
+ const next = new Set(prev);
226
+ if (next.has(key)) next.delete(key);else next.add(key);
227
+ return next;
228
+ });
229
+ }, []);
230
+ const toggleSource = (0, _react.useCallback)((source, includeState) => {
231
+ setExcluded(prev => {
232
+ const next = new Set(prev);
233
+ for (const item of source.items) {
234
+ if (!_verdictMeta.VERDICT_META[item.verdict].applies) continue;
235
+ const key = cKey(source.id, item.key);
236
+ if (includeState === "all") next.add(key);else next.delete(key);
237
+ }
238
+ return next;
239
+ });
240
+ }, []);
241
+
242
+ // Takes the EFFECTIVE collapsed state — the map is sparse (defaults come
243
+ // from the ?? fallback), so `!map[id]` would no-op the first tap on a
244
+ // default-collapsed card.
245
+ const toggleCollapse = (0, _react.useCallback)((id, currentlyCollapsed) => {
246
+ setCollapsed(c => ({
247
+ ...c,
248
+ [id]: !currentlyCollapsed
249
+ }));
250
+ }, []);
251
+ const toggleAllCollapsed = (0, _react.useCallback)(() => {
252
+ setAllCollapsed(prev => {
253
+ const next = !prev;
254
+ setCollapsed(Object.fromEntries(preview.sources.map(s => [s.id, next])));
255
+ return next;
256
+ });
257
+ }, [preview.sources]);
258
+ const totals = (0, _react.useMemo)(() => {
259
+ let applies = 0;
260
+ let excludedCount = 0;
261
+ let wontApply = 0;
262
+ const perSource = [];
263
+ for (const src of preview.sources) {
264
+ let n = 0;
265
+ for (const item of src.items) {
266
+ const m = _verdictMeta.VERDICT_META[item.verdict];
267
+ if (m.applies) {
268
+ if (excluded.has(cKey(src.id, item.key))) excludedCount += 1;else {
269
+ applies += 1;
270
+ n += 1;
271
+ }
272
+ } else if (item.verdict === "wont-apply") wontApply += 1;
273
+ }
274
+ if (n > 0) perSource.push(`${n} ${src.label.toLowerCase()}`);
275
+ }
276
+ return {
277
+ applies,
278
+ excludedCount,
279
+ wontApply,
280
+ perSource
281
+ };
282
+ }, [preview.sources, excluded]);
283
+
284
+ // Hosted: back + identity in the shared modal header. Restore lives in the
285
+ // bottom CTA; the preset's labeled RELOAD APP action stays available.
286
+ (0, _core.useToolHeader)(hosted ? {
287
+ title: "Restore Preview",
288
+ subtitle: rightMeta?.name ?? "compare",
289
+ onBack,
290
+ searchable: false // the in-body "Filter keys…" field covers it
291
+ } : null,
292
+ // eslint-disable-next-line react-hooks/exhaustive-deps
293
+ [hosted, rightMeta?.name]);
294
+ const rows = (0, _react.useMemo)(() => {
295
+ const q = search.trim().toLowerCase();
296
+ const out = [];
297
+ for (const src of preview.sources) {
298
+ // Every detected source gets a card whose subtitle carries the
299
+ // detection proof ("60 keys · 7 changed" / "2 slices · no changes"),
300
+ // so clean sources need no expansion. Unchanged ITEMS never render.
301
+ const relevant = src.items.some(i => i.verdict !== "unchanged");
302
+ const expandable = relevant || src.notes.length > 0 || !!src.error;
303
+ const applicable = src.items.filter(i => _verdictMeta.VERDICT_META[i.verdict].applies);
304
+ const wontApplyCount = src.items.filter(i => i.verdict === "wont-apply").length;
305
+ const noun = src.items.length === 1 ? (SOURCE_NOUNS[src.id] ?? "items").replace(/ies$/, "y").replace(/s$/, "") : SOURCE_NOUNS[src.id] ?? "items";
306
+ const summary = `${src.items.length} ${noun} · ` + (applicable.length > 0 ? `${applicable.length} changed` : wontApplyCount > 0 ? `${wontApplyCount} won't apply` : "no changes");
307
+ const excludedInSource = applicable.filter(i => excluded.has(cKey(src.id, i.key))).length;
308
+ const includeState = applicable.length === 0 || excludedInSource === 0 ? "all" : excludedInSource === applicable.length ? "none" : "some";
309
+ const counts = {
310
+ added: 0,
311
+ removed: 0,
312
+ changed: 0
313
+ };
314
+ for (const i of src.items) {
315
+ if (excluded.has(cKey(src.id, i.key))) continue;
316
+ 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;
317
+ }
318
+ const visible = src.items.filter(item => {
319
+ // Unchanged items never render — the dropdowns show only what a
320
+ // restore would touch (changed) or can't touch (won't-apply).
321
+ if (item.verdict === "unchanged") return false;
322
+ if (q && !item.label.toLowerCase().includes(q)) return false;
323
+ return true;
324
+ });
325
+ const isCollapsed = !expandable ? true : collapsed[src.id] ?? (applicable.length === 0 && !src.error);
326
+ out.push({
327
+ kind: "source",
328
+ source: src,
329
+ collapsed: isCollapsed,
330
+ includeState,
331
+ applicableCount: applicable.length,
332
+ counts,
333
+ expandable,
334
+ summary
335
+ });
336
+ if (!isCollapsed) {
337
+ for (const note of src.notes) {
338
+ out.push({
339
+ kind: "note",
340
+ id: `${src.id}:${note}`,
341
+ text: note
342
+ });
343
+ }
344
+ if (src.error) {
345
+ out.push({
346
+ kind: "note",
347
+ id: `${src.id}:err`,
348
+ text: `⚠ ${src.error}`
349
+ });
350
+ }
351
+ for (const item of visible) {
352
+ out.push({
353
+ kind: "item",
354
+ sourceId: src.id,
355
+ item,
356
+ excluded: excluded.has(cKey(src.id, item.key))
357
+ });
358
+ }
359
+ }
360
+ }
361
+ return out;
362
+ }, [preview.sources, search, collapsed, excluded]);
363
+ const renderRow = (0, _react.useCallback)(({
364
+ item: row
365
+ }) => {
366
+ if (row.kind === "source") {
367
+ const src = row.source;
368
+ const {
369
+ counts
370
+ } = row;
371
+ const clean = counts.added + counts.removed + counts.changed === 0 && !src.error;
372
+ const IconComp = SOURCE_ICONS[src.id] ?? _floatingToolsCore.TimeMachineIcon;
373
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
374
+ style: styles.sourceCard,
375
+ children: [row.applicableCount > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
376
+ onPress: () => toggleSource(src, row.includeState),
377
+ hitSlop: {
378
+ top: 10,
379
+ bottom: 10,
380
+ left: 8,
381
+ right: 8
382
+ },
383
+ testID: `tm-preview-source-toggle-${src.id}`,
384
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Checkbox, {
385
+ state: row.includeState
386
+ })
387
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
388
+ style: styles.sourceCardTouch,
389
+ onPress: () => row.expandable && toggleCollapse(src.id, row.collapsed),
390
+ disabled: !row.expandable,
391
+ testID: `tm-preview-source-${src.id}`,
392
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
393
+ style: styles.sourceIconTile,
394
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(IconComp, {
395
+ size: 18
396
+ })
397
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
398
+ style: styles.sourceNameBlock,
399
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
400
+ style: styles.sourceName,
401
+ numberOfLines: 1,
402
+ children: src.label
403
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
404
+ style: styles.sourceSummary,
405
+ numberOfLines: 1,
406
+ children: row.includeState === "none" && row.applicableCount > 0 ? `${row.summary} · excluded` : row.summary
407
+ })]
408
+ }), !clean && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
409
+ style: styles.sourceBadges,
410
+ children: [counts.removed > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
411
+ style: [styles.sourceBadge, {
412
+ color: _sharedUi.buoyColors.error
413
+ }],
414
+ children: ["\u2212", counts.removed]
415
+ }), counts.added > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
416
+ style: [styles.sourceBadge, {
417
+ color: _sharedUi.buoyColors.success
418
+ }],
419
+ children: ["+", counts.added]
420
+ }), counts.changed > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
421
+ style: [styles.sourceBadge, {
422
+ color: _sharedUi.buoyColors.info
423
+ }],
424
+ children: ["\u2248", counts.changed]
425
+ })]
426
+ }), row.expandable ? row.collapsed ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
427
+ size: 16,
428
+ color: _sharedUi.buoyColors.textMuted
429
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronUp, {
430
+ size: 16,
431
+ color: _sharedUi.buoyColors.textMuted
432
+ }) : null]
433
+ })]
434
+ });
435
+ }
436
+ if (row.kind === "note") {
437
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
438
+ style: styles.noteRow,
439
+ children: row.text
440
+ });
441
+ }
442
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ItemRow, {
443
+ row: row,
444
+ onOpen: () => onOpenItem(row.sourceId, row.item),
445
+ onToggle: () => toggleItem(cKey(row.sourceId, row.item.key))
446
+ });
447
+ }, [onOpenItem, toggleCollapse, toggleItem, toggleSource]);
448
+ const keyExtractor = (0, _react.useCallback)(row => {
449
+ if (row.kind === "source") return `s:${row.source.id}`;
450
+ if (row.kind === "note") return `n:${row.id}`;
451
+ return `i:${row.sourceId}:${row.item.key}`;
452
+ }, []);
453
+ const header = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
454
+ children: [!hosted && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
455
+ style: styles.fallbackHeader,
456
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
457
+ onPress: onBack,
458
+ hitSlop: HIT,
459
+ testID: "tm-preview-back",
460
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
461
+ style: styles.backText,
462
+ children: "\u2039 Back"
463
+ })
464
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
465
+ style: styles.screenTitle,
466
+ numberOfLines: 1,
467
+ children: "Restore Preview"
468
+ })]
469
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
470
+ style: styles.compareCard,
471
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
472
+ style: styles.compareSide,
473
+ onPress: () => onPickSide("left"),
474
+ testID: "tm-preview-pick-left",
475
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
476
+ style: styles.compareTagRow,
477
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
478
+ style: [styles.stateDot, {
479
+ backgroundColor: _sharedUi.buoyColors.info
480
+ }]
481
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
482
+ style: styles.compareTag,
483
+ children: liveLeft ? "CURRENT STATE" : "SNAPSHOT"
484
+ })]
485
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
486
+ style: styles.compareName,
487
+ numberOfLines: 1,
488
+ children: sideName(preview.left, snapshots)
489
+ }), liveLeft ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
490
+ style: styles.compareMetaRow,
491
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
492
+ style: styles.compareMeta,
493
+ children: ["built ", timeAgo(preview.builtAt), " ago"]
494
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
495
+ onPress: onRefresh,
496
+ disabled: preview.building,
497
+ hitSlop: {
498
+ top: 8,
499
+ bottom: 8,
500
+ left: 6,
501
+ right: 8
502
+ },
503
+ accessibilityLabel: "Rebuild preview",
504
+ testID: "tm-preview-refresh",
505
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
506
+ size: 11,
507
+ color: preview.building ? _sharedUi.macOSColors.text.disabled : _sharedUi.buoyColors.textSecondary
508
+ })
509
+ })]
510
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
511
+ style: styles.compareMeta,
512
+ children: "snapshot"
513
+ })]
514
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
515
+ style: styles.swapBadge,
516
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
517
+ style: styles.swapGlyph,
518
+ children: "\u21C4"
519
+ })
520
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
521
+ style: [styles.compareSide, styles.compareSideRight],
522
+ onPress: () => onPickSide("right"),
523
+ testID: "tm-preview-pick-right",
524
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
525
+ style: styles.compareTagRow,
526
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
527
+ style: styles.compareTag,
528
+ children: isBaseline ? "BASELINE" : "SNAPSHOT"
529
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
530
+ style: [styles.stateDot, {
531
+ backgroundColor: _sharedUi.buoyColors.success
532
+ }]
533
+ })]
534
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
535
+ style: [styles.compareName, styles.compareNameRight],
536
+ numberOfLines: 1,
537
+ children: sideName(preview.right, snapshots)
538
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
539
+ style: [styles.compareMeta, styles.compareMetaRight],
540
+ children: rightMeta ? `${timeAgo(rightMeta.createdAt)} ago` : ""
541
+ })]
542
+ })]
543
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
544
+ style: styles.changesRow,
545
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
546
+ style: styles.changesTitleRow,
547
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
548
+ style: styles.changesTitle,
549
+ children: "Changes to restore"
550
+ }), totals.applies + totals.excludedCount > 1 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
551
+ style: styles.selectLinks,
552
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
553
+ onPress: selectAll,
554
+ hitSlop: {
555
+ top: 8,
556
+ bottom: 8,
557
+ left: 4,
558
+ right: 4
559
+ },
560
+ testID: "tm-preview-select-all",
561
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
562
+ style: [styles.selectLink, totals.excludedCount === 0 && styles.selectLinkActive],
563
+ children: "all"
564
+ })
565
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
566
+ style: styles.selectLinkSep,
567
+ children: "\xB7"
568
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
569
+ onPress: selectNone,
570
+ hitSlop: {
571
+ top: 8,
572
+ bottom: 8,
573
+ left: 4,
574
+ right: 4
575
+ },
576
+ testID: "tm-preview-select-none",
577
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
578
+ style: [styles.selectLink, totals.applies === 0 && totals.excludedCount > 0 && styles.selectLinkActive],
579
+ children: "none"
580
+ })
581
+ }), liveLeft && rightMeta && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
582
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
583
+ style: styles.selectLinkSep,
584
+ children: "\xB7"
585
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
586
+ onPress: saveScope,
587
+ hitSlop: {
588
+ top: 8,
589
+ bottom: 8,
590
+ left: 4,
591
+ right: 4
592
+ },
593
+ testID: "tm-preview-save-scope",
594
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
595
+ style: [styles.selectLink, !!rightMeta.scope?.length && styles.selectLinkActive],
596
+ children: "save scope"
597
+ })
598
+ })]
599
+ })]
600
+ })]
601
+ }), preview.building ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
602
+ size: "small",
603
+ color: _sharedUi.buoyColors.primary
604
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
605
+ style: styles.changesChip,
606
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
607
+ style: styles.changesChipText,
608
+ numberOfLines: 1,
609
+ children: [totals.applies === 0 ? totals.excludedCount > 0 ? "everything excluded" : "no changes" : totals.perSource.join(" · "), totals.excludedCount > 0 && totals.applies > 0 ? ` · ${totals.excludedCount} excluded` : ""]
610
+ })
611
+ })]
612
+ }), totals.wontApply > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
613
+ style: styles.wontApplyLine,
614
+ children: ["\u2298 ", totals.wontApply, " item", totals.wontApply === 1 ? "" : "s", " won't apply \u2014 shown inline with the reason"]
615
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
616
+ style: styles.searchRow,
617
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
618
+ style: styles.searchField,
619
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
620
+ size: 14,
621
+ color: _sharedUi.buoyColors.textMuted
622
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
623
+ style: styles.searchInput,
624
+ value: localSearch,
625
+ onChangeText: setLocalSearch,
626
+ placeholder: "Filter keys\u2026",
627
+ placeholderTextColor: _sharedUi.buoyColors.textMuted,
628
+ autoCorrect: false,
629
+ autoCapitalize: "none",
630
+ testID: "tm-preview-search"
631
+ })]
632
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
633
+ style: styles.collapseAllButton,
634
+ onPress: toggleAllCollapsed,
635
+ accessibilityLabel: allCollapsed ? "Expand all" : "Collapse all",
636
+ testID: "tm-preview-collapse-all",
637
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
638
+ size: 15,
639
+ color: _sharedUi.buoyColors.textSecondary
640
+ })
641
+ })]
642
+ })]
643
+ });
644
+ const ctaDisabled = busy || preview.building || totals.applies === 0;
645
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
646
+ style: styles.container,
647
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
648
+ data: rows,
649
+ renderItem: renderRow,
650
+ keyExtractor: keyExtractor,
651
+ ListHeaderComponent: header,
652
+ ListEmptyComponent: preview.building ? null : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
653
+ style: styles.emptyText,
654
+ children: search ? "Nothing matches this filter." : "No changes — restoring would change nothing."
655
+ }),
656
+ removeClippedSubviews: true,
657
+ initialNumToRender: 20,
658
+ windowSize: 7,
659
+ contentContainerStyle: styles.listContent
660
+ }), liveLeft && rightMeta && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
661
+ style: styles.footer,
662
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
663
+ style: [styles.cta, isBaseline && styles.ctaDanger, ctaDisabled && styles.ctaDisabled],
664
+ onPress: () => onRestore(isBaseline ? "reload" : "live", Array.from(excluded)),
665
+ disabled: isBaseline ? busy || preview.building : ctaDisabled,
666
+ testID: "tm-preview-restore",
667
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.RefreshCw, {
668
+ size: 16,
669
+ color: "#0A0A0A"
670
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
671
+ style: styles.ctaText,
672
+ children: isBaseline ? "Wipe & Reload" : "Restore State"
673
+ })]
674
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
675
+ style: styles.ctaCaption,
676
+ 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."
677
+ })]
678
+ })]
679
+ });
680
+ }
681
+ const HIT = {
682
+ top: 10,
683
+ bottom: 10,
684
+ left: 10,
685
+ right: 10
686
+ };
687
+ const styles = _reactNative.StyleSheet.create({
688
+ container: {
689
+ flex: 1
690
+ },
691
+ fallbackHeader: {
692
+ flexDirection: "row",
693
+ alignItems: "center",
694
+ paddingVertical: 8,
695
+ gap: 10
696
+ },
697
+ backText: {
698
+ color: _sharedUi.buoyColors.primary,
699
+ fontSize: 14
700
+ },
701
+ screenTitle: {
702
+ color: _sharedUi.buoyColors.text,
703
+ fontSize: 13,
704
+ fontWeight: "700",
705
+ flex: 1
706
+ },
707
+ toolbarButton: {
708
+ padding: 4,
709
+ justifyContent: "center",
710
+ alignItems: "center"
711
+ },
712
+ dimmed: {
713
+ opacity: 0.55
714
+ },
715
+ listContent: {
716
+ paddingHorizontal: 12,
717
+ paddingTop: 8,
718
+ paddingBottom: 12
719
+ },
720
+ // Compare card
721
+ compareCard: {
722
+ flexDirection: "row",
723
+ alignItems: "center",
724
+ backgroundColor: _sharedUi.buoyColors.card,
725
+ borderColor: _sharedUi.buoyColors.border,
726
+ borderWidth: 1,
727
+ borderRadius: 14,
728
+ padding: 14,
729
+ gap: 10
730
+ },
731
+ compareSide: {
732
+ flex: 1,
733
+ minWidth: 0,
734
+ gap: 3
735
+ },
736
+ compareSideRight: {
737
+ alignItems: "flex-end"
738
+ },
739
+ compareTagRow: {
740
+ flexDirection: "row",
741
+ alignItems: "center",
742
+ gap: 5
743
+ },
744
+ stateDot: {
745
+ width: 6,
746
+ height: 6,
747
+ borderRadius: 3
748
+ },
749
+ compareTag: {
750
+ color: _sharedUi.buoyColors.textSecondary,
751
+ fontSize: 10,
752
+ fontWeight: "700",
753
+ letterSpacing: 0.6
754
+ },
755
+ compareName: {
756
+ color: _sharedUi.buoyColors.text,
757
+ fontSize: 15,
758
+ fontWeight: "700"
759
+ },
760
+ compareNameRight: {
761
+ textAlign: "right"
762
+ },
763
+ compareMeta: {
764
+ color: _sharedUi.buoyColors.textMuted,
765
+ fontSize: 10
766
+ },
767
+ compareMetaRight: {
768
+ textAlign: "right"
769
+ },
770
+ compareMetaRow: {
771
+ flexDirection: "row",
772
+ alignItems: "center",
773
+ gap: 6
774
+ },
775
+ swapBadge: {
776
+ width: 34,
777
+ height: 34,
778
+ borderRadius: 17,
779
+ backgroundColor: _sharedUi.buoyColors.hover,
780
+ borderColor: _sharedUi.buoyColors.border,
781
+ borderWidth: 1,
782
+ alignItems: "center",
783
+ justifyContent: "center"
784
+ },
785
+ swapGlyph: {
786
+ color: _sharedUi.buoyColors.textSecondary,
787
+ fontSize: 14
788
+ },
789
+ // Changes row
790
+ changesRow: {
791
+ flexDirection: "row",
792
+ alignItems: "center",
793
+ justifyContent: "space-between",
794
+ marginTop: 14,
795
+ gap: 10
796
+ },
797
+ changesTitle: {
798
+ color: _sharedUi.buoyColors.text,
799
+ fontSize: 14,
800
+ fontWeight: "700"
801
+ },
802
+ changesTitleRow: {
803
+ flexDirection: "row",
804
+ alignItems: "baseline",
805
+ gap: 8
806
+ },
807
+ selectLinks: {
808
+ flexDirection: "row",
809
+ alignItems: "baseline",
810
+ gap: 4
811
+ },
812
+ selectLink: {
813
+ color: _sharedUi.buoyColors.textMuted,
814
+ fontSize: 12
815
+ },
816
+ selectLinkActive: {
817
+ color: _sharedUi.buoyColors.primary,
818
+ fontWeight: "600"
819
+ },
820
+ selectLinkSep: {
821
+ color: _sharedUi.buoyColors.textMuted,
822
+ fontSize: 12
823
+ },
824
+ changesChip: {
825
+ backgroundColor: _sharedUi.buoyColors.card,
826
+ borderColor: _sharedUi.buoyColors.border,
827
+ borderWidth: 1,
828
+ borderRadius: 8,
829
+ paddingHorizontal: 10,
830
+ paddingVertical: 5,
831
+ maxWidth: "60%"
832
+ },
833
+ changesChipText: {
834
+ color: _sharedUi.buoyColors.textSecondary,
835
+ fontSize: 11
836
+ },
837
+ wontApplyLine: {
838
+ color: _sharedUi.buoyColors.warning,
839
+ fontSize: 11,
840
+ marginTop: 6
841
+ },
842
+ // Search row
843
+ searchRow: {
844
+ flexDirection: "row",
845
+ alignItems: "center",
846
+ gap: 8,
847
+ marginTop: 10,
848
+ marginBottom: 6
849
+ },
850
+ searchField: {
851
+ flex: 1,
852
+ flexDirection: "row",
853
+ alignItems: "center",
854
+ gap: 8,
855
+ backgroundColor: _sharedUi.buoyColors.card,
856
+ borderColor: _sharedUi.buoyColors.border,
857
+ borderWidth: 1,
858
+ borderRadius: 10,
859
+ paddingHorizontal: 10
860
+ },
861
+ searchInput: {
862
+ flex: 1,
863
+ color: _sharedUi.buoyColors.text,
864
+ fontSize: 12,
865
+ paddingVertical: 8
866
+ },
867
+ collapseAllButton: {
868
+ width: 36,
869
+ height: 36,
870
+ borderRadius: 10,
871
+ backgroundColor: _sharedUi.buoyColors.card,
872
+ borderColor: _sharedUi.buoyColors.border,
873
+ borderWidth: 1,
874
+ alignItems: "center",
875
+ justifyContent: "center"
876
+ },
877
+ // Checkbox
878
+ checkbox: {
879
+ width: 18,
880
+ height: 18,
881
+ borderRadius: 4,
882
+ borderWidth: 1.5,
883
+ borderColor: _sharedUi.buoyColors.textMuted,
884
+ alignItems: "center",
885
+ justifyContent: "center"
886
+ },
887
+ checkboxChecked: {
888
+ borderColor: _sharedUi.buoyColors.primary,
889
+ backgroundColor: `${_sharedUi.buoyColors.primary}26`
890
+ },
891
+ checkboxGlyph: {
892
+ color: _sharedUi.buoyColors.primary,
893
+ fontSize: 11,
894
+ fontWeight: "700",
895
+ lineHeight: 13
896
+ },
897
+ // Source cards
898
+ sourceCard: {
899
+ flexDirection: "row",
900
+ alignItems: "center",
901
+ backgroundColor: _sharedUi.buoyColors.card,
902
+ borderColor: _sharedUi.buoyColors.border,
903
+ borderWidth: 1,
904
+ borderRadius: 12,
905
+ paddingHorizontal: 12,
906
+ paddingVertical: 10,
907
+ marginTop: 8,
908
+ gap: 10
909
+ },
910
+ sourceCardTouch: {
911
+ flex: 1,
912
+ flexDirection: "row",
913
+ alignItems: "center",
914
+ gap: 12,
915
+ minWidth: 0
916
+ },
917
+ sourceIconTile: {
918
+ width: 36,
919
+ height: 36,
920
+ borderRadius: 10,
921
+ backgroundColor: _sharedUi.buoyColors.hover,
922
+ alignItems: "center",
923
+ justifyContent: "center"
924
+ },
925
+ sourceNameBlock: {
926
+ flex: 1,
927
+ minWidth: 0,
928
+ gap: 2
929
+ },
930
+ sourceName: {
931
+ color: _sharedUi.buoyColors.text,
932
+ fontSize: 15,
933
+ fontWeight: "700"
934
+ },
935
+ sourceSummary: {
936
+ color: _sharedUi.buoyColors.textMuted,
937
+ fontSize: 11,
938
+ fontVariant: ["tabular-nums"]
939
+ },
940
+ sourceBadges: {
941
+ flexDirection: "row",
942
+ gap: 8
943
+ },
944
+ sourceBadge: {
945
+ fontSize: 13,
946
+ fontWeight: "700",
947
+ fontVariant: ["tabular-nums"]
948
+ },
949
+ noteRow: {
950
+ color: _sharedUi.buoyColors.textMuted,
951
+ fontSize: 10,
952
+ paddingLeft: 24,
953
+ paddingTop: 6
954
+ },
955
+ // Item rows (nested under a source card)
956
+ itemRow: {
957
+ flexDirection: "row",
958
+ alignItems: "center",
959
+ backgroundColor: _sharedUi.buoyColors.hover,
960
+ borderColor: _sharedUi.buoyColors.border,
961
+ borderWidth: 1,
962
+ borderRadius: 10,
963
+ paddingHorizontal: 10,
964
+ paddingVertical: 9,
965
+ marginTop: 6,
966
+ marginLeft: 14,
967
+ gap: 10
968
+ },
969
+ itemBodyTouch: {
970
+ flex: 1,
971
+ flexDirection: "row",
972
+ alignItems: "center",
973
+ gap: 8,
974
+ minWidth: 0
975
+ },
976
+ itemGlyph: {
977
+ fontSize: 13,
978
+ fontWeight: "700",
979
+ width: 14,
980
+ textAlign: "center"
981
+ },
982
+ itemBody: {
983
+ flex: 1,
984
+ minWidth: 0
985
+ },
986
+ itemLabel: {
987
+ color: _sharedUi.buoyColors.text,
988
+ fontSize: 12
989
+ },
990
+ itemPreview: {
991
+ color: _sharedUi.buoyColors.textMuted,
992
+ fontSize: 10,
993
+ marginTop: 2,
994
+ fontVariant: ["tabular-nums"]
995
+ },
996
+ itemPreviewRemoved: {
997
+ textDecorationLine: "line-through"
998
+ },
999
+ itemReason: {
1000
+ color: _sharedUi.buoyColors.warning,
1001
+ fontSize: 10,
1002
+ marginTop: 2
1003
+ },
1004
+ itemExcluded: {
1005
+ color: _sharedUi.buoyColors.textMuted,
1006
+ fontSize: 10,
1007
+ marginTop: 2,
1008
+ fontStyle: "italic"
1009
+ },
1010
+ itemChevron: {
1011
+ color: _sharedUi.buoyColors.textMuted,
1012
+ fontSize: 16
1013
+ },
1014
+ emptyText: {
1015
+ color: _sharedUi.buoyColors.textMuted,
1016
+ fontSize: 12,
1017
+ textAlign: "center",
1018
+ marginTop: 24
1019
+ },
1020
+ // Footer CTA
1021
+ footer: {
1022
+ paddingHorizontal: 12,
1023
+ paddingTop: 8,
1024
+ paddingBottom: 12,
1025
+ gap: 6,
1026
+ borderTopColor: _sharedUi.buoyColors.border,
1027
+ borderTopWidth: 1,
1028
+ backgroundColor: _sharedUi.buoyColors.base
1029
+ },
1030
+ cta: {
1031
+ flexDirection: "row",
1032
+ alignItems: "center",
1033
+ justifyContent: "center",
1034
+ gap: 8,
1035
+ backgroundColor: _sharedUi.buoyColors.primary,
1036
+ borderRadius: 24,
1037
+ paddingVertical: 13
1038
+ },
1039
+ ctaDanger: {
1040
+ backgroundColor: _sharedUi.buoyColors.error
1041
+ },
1042
+ ctaDisabled: {
1043
+ opacity: 0.4
1044
+ },
1045
+ ctaText: {
1046
+ color: "#0A0A0A",
1047
+ fontSize: 14,
1048
+ fontWeight: "700"
1049
+ },
1050
+ ctaCaption: {
1051
+ color: _sharedUi.buoyColors.textMuted,
1052
+ fontSize: 10,
1053
+ textAlign: "center"
1054
+ }
1055
+ });
1056
+ //# sourceMappingURL=RestorePreviewScreen.js.map