@buoy-gg/time-machine 7.0.22 → 7.0.23

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