@narumitw/pi-sync 0.50.3 → 0.51.1

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 (218) hide show
  1. package/README.md +4 -1
  2. package/dist/chunks/{chunk-6LP3MCR2.ts → chunk-2JZFHNLM.ts} +3 -5
  3. package/dist/chunks/chunk-2JZFHNLM.ts.map +7 -0
  4. package/dist/chunks/{chunk-LCRI3ABW.ts → chunk-2RZSQWBH.ts} +8 -19
  5. package/dist/chunks/chunk-2RZSQWBH.ts.map +7 -0
  6. package/dist/chunks/{chunk-IJ6FJFA5.ts → chunk-4AIHXRKE.ts} +11 -17
  7. package/dist/chunks/chunk-4AIHXRKE.ts.map +7 -0
  8. package/dist/chunks/{chunk-C7XY5NF5.ts → chunk-4MPIH46N.ts} +29 -51
  9. package/dist/chunks/chunk-4MPIH46N.ts.map +7 -0
  10. package/dist/chunks/{chunk-5B3FXBLE.ts → chunk-6546TS54.ts} +16 -20
  11. package/dist/chunks/chunk-6546TS54.ts.map +7 -0
  12. package/dist/chunks/{chunk-Q2TUM5X6.ts → chunk-6NT3YFGY.ts} +2 -2
  13. package/dist/chunks/chunk-6NT3YFGY.ts.map +7 -0
  14. package/dist/chunks/{chunk-2B2ELUF5.ts → chunk-7D5SF7P3.ts} +1 -1
  15. package/dist/chunks/chunk-7D5SF7P3.ts.map +7 -0
  16. package/dist/chunks/{chunk-YDWUZVPN.ts → chunk-7KHKWL3Q.ts} +28 -47
  17. package/dist/chunks/chunk-7KHKWL3Q.ts.map +7 -0
  18. package/dist/chunks/{chunk-ZI5MXMEX.ts → chunk-BVVP33U5.ts} +2 -4
  19. package/dist/chunks/chunk-BVVP33U5.ts.map +7 -0
  20. package/dist/chunks/{chunk-SZWBGPID.ts → chunk-DM4X4FIA.ts} +8 -13
  21. package/dist/chunks/chunk-DM4X4FIA.ts.map +7 -0
  22. package/dist/chunks/{chunk-SL64RLXR.ts → chunk-DZUSTGQV.ts} +8 -16
  23. package/dist/chunks/chunk-DZUSTGQV.ts.map +7 -0
  24. package/dist/chunks/{chunk-YHEMENKV.ts → chunk-E42GXD6D.ts} +13 -19
  25. package/dist/chunks/chunk-E42GXD6D.ts.map +7 -0
  26. package/dist/chunks/{chunk-Z4DQF6L3.ts → chunk-ECYCNZOE.ts} +1 -1
  27. package/dist/chunks/chunk-ECYCNZOE.ts.map +7 -0
  28. package/dist/chunks/{chunk-OEX4ABY6.ts → chunk-EEJPYTWE.ts} +11 -21
  29. package/dist/chunks/chunk-EEJPYTWE.ts.map +7 -0
  30. package/dist/chunks/{chunk-CPEHGXJV.ts → chunk-G4RUXRWZ.ts} +3 -5
  31. package/dist/chunks/chunk-G4RUXRWZ.ts.map +7 -0
  32. package/dist/chunks/{chunk-T2B5IU74.ts → chunk-HGJH56KR.ts} +4 -4
  33. package/dist/chunks/chunk-HGJH56KR.ts.map +7 -0
  34. package/dist/chunks/{chunk-JAAKLL6M.ts → chunk-IVXEDPAD.ts} +10 -34
  35. package/dist/chunks/chunk-IVXEDPAD.ts.map +7 -0
  36. package/dist/chunks/chunk-K3RIEL6J.ts +20 -0
  37. package/dist/chunks/chunk-K3RIEL6J.ts.map +7 -0
  38. package/dist/chunks/{chunk-LTBWGSBV.ts → chunk-MSSW6F3R.ts} +1 -1
  39. package/dist/chunks/chunk-MSSW6F3R.ts.map +7 -0
  40. package/dist/chunks/{chunk-T3HH3UEJ.ts → chunk-OVDTF5CH.ts} +4 -10
  41. package/dist/chunks/chunk-OVDTF5CH.ts.map +7 -0
  42. package/dist/chunks/{chunk-NJFUIVYL.ts → chunk-PWMWOJP4.ts} +1 -1
  43. package/dist/chunks/chunk-PWMWOJP4.ts.map +7 -0
  44. package/dist/chunks/{chunk-HUDQATCL.ts → chunk-Q32CQWPM.ts} +6 -8
  45. package/dist/chunks/chunk-Q32CQWPM.ts.map +7 -0
  46. package/dist/chunks/{chunk-ETTRJDNP.ts → chunk-QLSN7AGX.ts} +6 -13
  47. package/dist/chunks/chunk-QLSN7AGX.ts.map +7 -0
  48. package/dist/chunks/{chunk-THMF4BVL.ts → chunk-SWUXA3MH.ts} +1 -1
  49. package/dist/chunks/chunk-SWUXA3MH.ts.map +7 -0
  50. package/dist/chunks/{chunk-TEN2ZBJV.ts → chunk-T2SJD3N7.ts} +2 -4
  51. package/dist/chunks/chunk-T2SJD3N7.ts.map +7 -0
  52. package/dist/chunks/{chunk-AUSDLD6F.ts → chunk-T56RNRZJ.ts} +10 -26
  53. package/dist/chunks/chunk-T56RNRZJ.ts.map +7 -0
  54. package/dist/chunks/{chunk-DHVFS4QK.ts → chunk-UA7RXOIT.ts} +37 -90
  55. package/dist/chunks/chunk-UA7RXOIT.ts.map +7 -0
  56. package/dist/chunks/{chunk-D5X27LCA.ts → chunk-UETBKTDI.ts} +1 -1
  57. package/dist/chunks/chunk-UETBKTDI.ts.map +7 -0
  58. package/dist/chunks/{chunk-CHH26D4F.ts → chunk-YFADF5ZQ.ts} +7 -6
  59. package/dist/chunks/chunk-YFADF5ZQ.ts.map +7 -0
  60. package/dist/chunks/{chunk-CCSN3F47.ts → chunk-YPXGU5OR.ts} +4 -4
  61. package/dist/chunks/chunk-YPXGU5OR.ts.map +7 -0
  62. package/dist/chunks/{file-selection-FTTKAOYX.ts → file-selection-RAVHRYFG.ts} +14 -26
  63. package/dist/chunks/file-selection-RAVHRYFG.ts.map +7 -0
  64. package/dist/chunks/{git-backend-GMVS4PD4.ts → git-backend-IAANDL3K.ts} +41 -104
  65. package/dist/chunks/git-backend-IAANDL3K.ts.map +7 -0
  66. package/dist/chunks/manager-result-dispatcher-OTUMQM26.ts +23 -0
  67. package/dist/chunks/{manager-ui-IPAOAWZA.ts → manager-ui-763NEKJY.ts} +124 -251
  68. package/dist/chunks/manager-ui-763NEKJY.ts.map +7 -0
  69. package/dist/chunks/remote-selection-ui-TZTJF32Y.ts +22 -0
  70. package/dist/chunks/{s3-backend-66O4FGBH.ts → s3-backend-XGXUOPUN.ts} +23 -54
  71. package/dist/chunks/s3-backend-XGXUOPUN.ts.map +7 -0
  72. package/dist/chunks/{setup-switch-IS2PEZ3J.ts → setup-switch-5G2VNJHL.ts} +9 -9
  73. package/dist/chunks/{setup-wizard-JUJZYNNM.ts → setup-wizard-YIEWGX32.ts} +12 -12
  74. package/dist/chunks/setup-wizard-YIEWGX32.ts.map +7 -0
  75. package/dist/chunks/{snapshot-HKEWCVK6.ts → snapshot-JL5DFSXI.ts} +5 -5
  76. package/dist/chunks/sync-inspection-P3ENJLNF.ts +20 -0
  77. package/dist/chunks/{sync-operations-LBX3GKCM.ts → sync-operations-AR76EB6C.ts} +85 -201
  78. package/dist/chunks/sync-operations-AR76EB6C.ts.map +7 -0
  79. package/dist/chunks/{sync-state-Q2FTXTW6.ts → sync-state-VHJUZ4YG.ts} +6 -6
  80. package/dist/chunks/{webdav-backend-ECIKD7SO.ts → webdav-backend-3EGUONAT.ts} +20 -51
  81. package/dist/chunks/webdav-backend-3EGUONAT.ts.map +7 -0
  82. package/dist/index.ts +49 -74
  83. package/dist/index.ts.map +3 -3
  84. package/docs/settings.md +9 -0
  85. package/package.json +61 -62
  86. package/src/backends/backend-factory.ts +15 -15
  87. package/src/backends/backend-identity.ts +31 -31
  88. package/src/backends/backend-types.ts +42 -42
  89. package/src/backends/git/git-backend.ts +714 -788
  90. package/src/backends/git/git-config.ts +104 -111
  91. package/src/backends/git/git-runner.ts +230 -247
  92. package/src/backends/git/git-storage.ts +228 -245
  93. package/src/backends/s3/s3-backend.ts +408 -441
  94. package/src/backends/s3/s3-client.ts +288 -333
  95. package/src/backends/sync-backend.ts +65 -65
  96. package/src/backends/webdav/webdav-backend.ts +542 -589
  97. package/src/backends/webdav/webdav-client.ts +292 -316
  98. package/src/backends/webdav/webdav-config.ts +57 -68
  99. package/src/commands/command-execution.ts +270 -289
  100. package/src/commands/command-handler.ts +160 -163
  101. package/src/commands/command-types.ts +13 -13
  102. package/src/commands/command.ts +190 -196
  103. package/src/commands/setup-completions.ts +1 -1
  104. package/src/paths.ts +47 -47
  105. package/src/settings/config-errors.ts +4 -5
  106. package/src/settings/config-file.ts +473 -506
  107. package/src/settings/config.ts +171 -174
  108. package/src/settings/settings-management.ts +191 -216
  109. package/src/settings/settings-store.ts +63 -77
  110. package/src/settings/settings-types.ts +78 -75
  111. package/src/settings/settings-validation.ts +328 -364
  112. package/src/snapshot/session-paths.ts +41 -55
  113. package/src/snapshot/snapshot-apply.ts +197 -229
  114. package/src/snapshot/snapshot-codec.ts +29 -31
  115. package/src/snapshot/snapshot-paths.ts +4 -6
  116. package/src/snapshot/snapshot-transaction.ts +161 -170
  117. package/src/snapshot/snapshot-types.ts +24 -24
  118. package/src/snapshot/snapshot.ts +310 -343
  119. package/src/state/json-file.ts +11 -11
  120. package/src/state/lock.ts +226 -242
  121. package/src/state/lockfile-fs.ts +20 -20
  122. package/src/state/operation-availability.ts +36 -41
  123. package/src/state/state-directory.ts +171 -173
  124. package/src/state/state-types.ts +15 -15
  125. package/src/state/sync-state-store.ts +23 -23
  126. package/src/sync/automatic-sync.ts +75 -80
  127. package/src/sync/remote-snapshot.ts +52 -60
  128. package/src/sync/setup-switch.ts +99 -114
  129. package/src/sync/signals.ts +3 -5
  130. package/src/sync/startup-check.ts +97 -111
  131. package/src/sync/sync-decision.ts +76 -80
  132. package/src/sync/sync-error-guidance.ts +41 -48
  133. package/src/sync/sync-errors.ts +10 -10
  134. package/src/sync/sync-inspection.ts +29 -33
  135. package/src/sync/sync-loaders.ts +28 -36
  136. package/src/sync/sync-mutations.ts +510 -622
  137. package/src/sync/sync-operations.ts +7 -7
  138. package/src/sync/sync-policy.ts +296 -326
  139. package/src/sync/sync-queries.ts +175 -194
  140. package/src/sync/sync-state.ts +74 -100
  141. package/src/sync-extension.ts +89 -101
  142. package/src/sync.ts +26 -26
  143. package/src/types.ts +37 -37
  144. package/src/ui/automatic-sync-summary.ts +1 -3
  145. package/src/ui/cancellable-operation.ts +89 -101
  146. package/src/ui/file-selection.ts +250 -274
  147. package/src/ui/manager-attention.ts +51 -63
  148. package/src/ui/manager-recovery.ts +48 -57
  149. package/src/ui/manager-result-dispatcher.ts +62 -76
  150. package/src/ui/manager-state.ts +200 -232
  151. package/src/ui/manager-ui.ts +264 -362
  152. package/src/ui/remote-selection-ui.ts +547 -588
  153. package/src/ui/secret-input.ts +197 -205
  154. package/src/ui/settings-ui.ts +175 -172
  155. package/src/ui/setup/git-ui.ts +232 -261
  156. package/src/ui/setup/s3-credentials-ui.ts +58 -65
  157. package/src/ui/setup/s3-ui.ts +171 -195
  158. package/src/ui/setup/setup-actions.ts +91 -101
  159. package/src/ui/setup/setup-location-ui.ts +121 -137
  160. package/src/ui/setup/setup-name-ui.ts +27 -27
  161. package/src/ui/setup/setup-prompts.ts +65 -65
  162. package/src/ui/setup/setup-review.ts +58 -59
  163. package/src/ui/setup/setup-switcher.ts +156 -160
  164. package/src/ui/setup/setup-wizard.ts +28 -28
  165. package/src/ui/setup/text-input.ts +54 -58
  166. package/src/ui/setup/webdav-ui.ts +246 -293
  167. package/src/ui/storage-connections-ui.ts +317 -369
  168. package/src/ui/sync-attention.ts +148 -164
  169. package/src/ui/sync-format.ts +110 -121
  170. package/src/ui/sync-resolution-ui.ts +147 -153
  171. package/src/ui/sync-setups-ui.ts +218 -238
  172. package/src/ui/sync-status.ts +17 -0
  173. package/src/ui/terminal-text.ts +2 -2
  174. package/dist/chunks/chunk-2B2ELUF5.ts.map +0 -7
  175. package/dist/chunks/chunk-5B3FXBLE.ts.map +0 -7
  176. package/dist/chunks/chunk-6LP3MCR2.ts.map +0 -7
  177. package/dist/chunks/chunk-AUSDLD6F.ts.map +0 -7
  178. package/dist/chunks/chunk-C7XY5NF5.ts.map +0 -7
  179. package/dist/chunks/chunk-CCSN3F47.ts.map +0 -7
  180. package/dist/chunks/chunk-CHH26D4F.ts.map +0 -7
  181. package/dist/chunks/chunk-CPEHGXJV.ts.map +0 -7
  182. package/dist/chunks/chunk-D5X27LCA.ts.map +0 -7
  183. package/dist/chunks/chunk-DHVFS4QK.ts.map +0 -7
  184. package/dist/chunks/chunk-ETTRJDNP.ts.map +0 -7
  185. package/dist/chunks/chunk-HUDQATCL.ts.map +0 -7
  186. package/dist/chunks/chunk-IJ6FJFA5.ts.map +0 -7
  187. package/dist/chunks/chunk-JAAKLL6M.ts.map +0 -7
  188. package/dist/chunks/chunk-LCRI3ABW.ts.map +0 -7
  189. package/dist/chunks/chunk-LTBWGSBV.ts.map +0 -7
  190. package/dist/chunks/chunk-NJFUIVYL.ts.map +0 -7
  191. package/dist/chunks/chunk-OEX4ABY6.ts.map +0 -7
  192. package/dist/chunks/chunk-Q2TUM5X6.ts.map +0 -7
  193. package/dist/chunks/chunk-SL64RLXR.ts.map +0 -7
  194. package/dist/chunks/chunk-SZWBGPID.ts.map +0 -7
  195. package/dist/chunks/chunk-T2B5IU74.ts.map +0 -7
  196. package/dist/chunks/chunk-T3HH3UEJ.ts.map +0 -7
  197. package/dist/chunks/chunk-TEN2ZBJV.ts.map +0 -7
  198. package/dist/chunks/chunk-THMF4BVL.ts.map +0 -7
  199. package/dist/chunks/chunk-YDWUZVPN.ts.map +0 -7
  200. package/dist/chunks/chunk-YHEMENKV.ts.map +0 -7
  201. package/dist/chunks/chunk-Z4DQF6L3.ts.map +0 -7
  202. package/dist/chunks/chunk-ZI5MXMEX.ts.map +0 -7
  203. package/dist/chunks/file-selection-FTTKAOYX.ts.map +0 -7
  204. package/dist/chunks/git-backend-GMVS4PD4.ts.map +0 -7
  205. package/dist/chunks/manager-result-dispatcher-ASKGOSW5.ts +0 -22
  206. package/dist/chunks/manager-ui-IPAOAWZA.ts.map +0 -7
  207. package/dist/chunks/remote-selection-ui-QDNR7WEI.ts +0 -21
  208. package/dist/chunks/s3-backend-66O4FGBH.ts.map +0 -7
  209. package/dist/chunks/setup-wizard-JUJZYNNM.ts.map +0 -7
  210. package/dist/chunks/sync-inspection-CST2KL65.ts +0 -20
  211. package/dist/chunks/sync-operations-LBX3GKCM.ts.map +0 -7
  212. package/dist/chunks/webdav-backend-ECIKD7SO.ts.map +0 -7
  213. /package/dist/chunks/{manager-result-dispatcher-ASKGOSW5.ts.map → manager-result-dispatcher-OTUMQM26.ts.map} +0 -0
  214. /package/dist/chunks/{remote-selection-ui-QDNR7WEI.ts.map → remote-selection-ui-TZTJF32Y.ts.map} +0 -0
  215. /package/dist/chunks/{setup-switch-IS2PEZ3J.ts.map → setup-switch-5G2VNJHL.ts.map} +0 -0
  216. /package/dist/chunks/{snapshot-HKEWCVK6.ts.map → snapshot-JL5DFSXI.ts.map} +0 -0
  217. /package/dist/chunks/{sync-inspection-CST2KL65.ts.map → sync-inspection-P3ENJLNF.ts.map} +0 -0
  218. /package/dist/chunks/{sync-state-Q2FTXTW6.ts.map → sync-state-VHJUZ4YG.ts.map} +0 -0
@@ -3,180 +3,174 @@ import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
3
3
  import { loadConfig, syncConfigReviewIdentity } from "../settings/config.js";
4
4
  import type { SyncDecision, SyncResolutionDirection } from "../sync/sync-decision.js";
5
5
  import { errorMessage } from "../sync/sync-errors.js";
6
- import {
7
- type RunRoute,
8
- type RunRouteResult,
9
- runCancellableOperation,
10
- } from "./cancellable-operation.js";
6
+ import { type RunRoute, type RunRouteResult, runCancellableOperation } from "./cancellable-operation.js";
11
7
  import { safeTerminalText } from "./terminal-text.js";
12
8
 
13
9
  export type SyncResolutionResult =
14
- | { kind: "resolved"; direction: SyncResolutionDirection }
15
- | {
16
- kind: "route-result";
17
- result: RunRouteResult;
18
- route: SyncResolutionDirection;
19
- }
20
- | { kind: "back" }
21
- | { kind: "closed" }
22
- | { kind: "stale" };
10
+ | { kind: "resolved"; direction: SyncResolutionDirection }
11
+ | {
12
+ kind: "route-result";
13
+ result: RunRouteResult;
14
+ route: SyncResolutionDirection;
15
+ }
16
+ | { kind: "back" }
17
+ | { kind: "closed" }
18
+ | { kind: "stale" };
23
19
 
24
20
  export async function showSyncResolution(
25
- ctx: ExtensionContext,
26
- initialDecision: SyncDecision,
27
- runRoute: RunRoute,
28
- sessionSignal?: AbortSignal,
21
+ ctx: ExtensionContext,
22
+ initialDecision: SyncDecision,
23
+ runRoute: RunRoute,
24
+ sessionSignal?: AbortSignal,
29
25
  ): Promise<SyncResolutionResult> {
30
- type Screen = "resolve" | "review";
31
- type Action = "push" | "pull" | "back";
32
- let currentDecision = initialDecision;
33
- let resolvedDirection: SyncResolutionDirection | undefined;
34
- let chainedResult: { result: RunRouteResult; route: SyncResolutionDirection } | undefined;
35
- const menu = defineMenu<SyncDecision, Screen, Action, ExtensionContext>({
36
- start: "resolve",
37
- screens: {
38
- resolve: ({ state }) => ({
39
- kind: "actions",
40
- title: resolutionTitle(state),
41
- lines: resolutionLines(state),
42
- items: [
43
- { id: "review", label: "Review differences (recommended)", to: "review" },
44
- ...(state.directions.includes("push")
45
- ? [
46
- {
47
- id: "push",
48
- label: pushLabel(state),
49
- description: "Review an exact push before replacing the remote version.",
50
- action: "push" as const,
51
- },
52
- ]
53
- : []),
54
- ...(state.directions.includes("pull")
55
- ? [
56
- {
57
- id: "pull",
58
- label: pullLabel(state),
59
- description: "Review exact local changes and create a backup before applying.",
60
- action: "pull" as const,
61
- },
62
- ]
63
- : []),
64
- { id: "back", label: "Back", action: "back" },
65
- ],
66
- hint: "back",
67
- }),
68
- review: ({ state }) => ({
69
- kind: "review",
70
- title: `Review differences · ${safeTerminalText(state.setupName)}`,
71
- content: state.review,
72
- format: { kind: "text" },
73
- viewportSize: "adaptive",
74
- hint: "back",
75
- }),
76
- },
77
- actions: {
78
- push: ({ state, signal }) => resolveDirection("push", state, signal),
79
- pull: ({ state, signal }) => resolveDirection("pull", state, signal),
80
- back: async () => ({ kind: "back" }),
81
- },
82
- });
26
+ type Screen = "resolve" | "review";
27
+ type Action = "push" | "pull" | "back";
28
+ let currentDecision = initialDecision;
29
+ let resolvedDirection: SyncResolutionDirection | undefined;
30
+ let chainedResult: { result: RunRouteResult; route: SyncResolutionDirection } | undefined;
31
+ const menu = defineMenu<SyncDecision, Screen, Action, ExtensionContext>({
32
+ start: "resolve",
33
+ screens: {
34
+ resolve: ({ state }) => ({
35
+ kind: "actions",
36
+ title: resolutionTitle(state),
37
+ lines: resolutionLines(state),
38
+ items: [
39
+ { id: "review", label: "Review differences (recommended)", to: "review" },
40
+ ...(state.directions.includes("push")
41
+ ? [
42
+ {
43
+ id: "push",
44
+ label: pushLabel(state),
45
+ description: "Review an exact push before replacing the remote version.",
46
+ action: "push" as const,
47
+ },
48
+ ]
49
+ : []),
50
+ ...(state.directions.includes("pull")
51
+ ? [
52
+ {
53
+ id: "pull",
54
+ label: pullLabel(state),
55
+ description: "Review exact local changes and create a backup before applying.",
56
+ action: "pull" as const,
57
+ },
58
+ ]
59
+ : []),
60
+ { id: "back", label: "Back", action: "back" },
61
+ ],
62
+ hint: "back",
63
+ }),
64
+ review: ({ state }) => ({
65
+ kind: "review",
66
+ title: `Review differences · ${safeTerminalText(state.setupName)}`,
67
+ content: state.review,
68
+ format: { kind: "text" },
69
+ viewportSize: "adaptive",
70
+ hint: "back",
71
+ }),
72
+ },
73
+ actions: {
74
+ push: ({ state, signal }) => resolveDirection("push", state, signal),
75
+ pull: ({ state, signal }) => resolveDirection("pull", state, signal),
76
+ back: async () => ({ kind: "back" }),
77
+ },
78
+ });
83
79
 
84
- async function resolveDirection(
85
- direction: SyncResolutionDirection,
86
- decision: SyncDecision,
87
- actionSignal: AbortSignal,
88
- ) {
89
- const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
90
- const config = await loadConfig(decision.setupName);
91
- if (signal.aborted) return { kind: "close" as const };
92
- if (syncConfigReviewIdentity(config) !== decision.configIdentity) {
93
- throw new Error(
94
- `Sync setup “${safeTerminalText(decision.setupName)}” changed while conflict resolution was open; return to the sync manager and retry.`,
95
- );
96
- }
97
- const result = await runCancellableOperation(
98
- ctx,
99
- direction === "push"
100
- ? "Preparing local-wins push preview…"
101
- : "Preparing remote-wins pull preview…",
102
- `${direction} --force`,
103
- runRoute,
104
- {
105
- commitAware: true,
106
- cancelledMessage:
107
- direction === "push"
108
- ? "Push preparation cancelled; no remote files were changed."
109
- : "Pull check cancelled; no local files were changed.",
110
- target: decision.setupName,
111
- signal,
112
- },
113
- );
114
- if (result.kind === "decision-required") {
115
- currentDecision = result.decision;
116
- return { kind: "stay" as const };
117
- }
118
- if (result.kind === "remote-selection-required") {
119
- chainedResult = { result, route: direction };
120
- return { kind: "close" as const };
121
- }
122
- if (result.kind === "completed") {
123
- if (result.outcome === "cancelled") return { kind: "stay" as const };
124
- resolvedDirection = direction;
125
- return { kind: "close" as const };
126
- }
127
- return result.kind === "closed" ? { kind: "close" as const } : { kind: "stay" as const };
128
- }
80
+ async function resolveDirection(
81
+ direction: SyncResolutionDirection,
82
+ decision: SyncDecision,
83
+ actionSignal: AbortSignal,
84
+ ) {
85
+ const signal = sessionSignal ? AbortSignal.any([sessionSignal, actionSignal]) : actionSignal;
86
+ const config = await loadConfig(decision.setupName);
87
+ if (signal.aborted) return { kind: "close" as const };
88
+ if (syncConfigReviewIdentity(config) !== decision.configIdentity) {
89
+ throw new Error(
90
+ `Sync setup “${safeTerminalText(decision.setupName)}” changed while conflict resolution was open; return to the sync manager and retry.`,
91
+ );
92
+ }
93
+ const result = await runCancellableOperation(
94
+ ctx,
95
+ direction === "push" ? "Preparing local-wins push preview…" : "Preparing remote-wins pull preview…",
96
+ `${direction} --force`,
97
+ runRoute,
98
+ {
99
+ commitAware: true,
100
+ cancelledMessage:
101
+ direction === "push"
102
+ ? "Push preparation cancelled; no remote files were changed."
103
+ : "Pull check cancelled; no local files were changed.",
104
+ target: decision.setupName,
105
+ signal,
106
+ },
107
+ );
108
+ if (result.kind === "decision-required") {
109
+ currentDecision = result.decision;
110
+ return { kind: "stay" as const };
111
+ }
112
+ if (result.kind === "remote-selection-required") {
113
+ chainedResult = { result, route: direction };
114
+ return { kind: "close" as const };
115
+ }
116
+ if (result.kind === "completed") {
117
+ if (result.outcome === "cancelled") return { kind: "stay" as const };
118
+ resolvedDirection = direction;
119
+ return { kind: "close" as const };
120
+ }
121
+ return result.kind === "closed" ? { kind: "close" as const } : { kind: "stay" as const };
122
+ }
129
123
 
130
- const result = await runMenu(ctx, menu, {
131
- getState: () => currentDecision,
132
- signal: sessionSignal,
133
- isCurrent: () => !sessionSignal?.aborted,
134
- onError: (_menuCtx, error) => ctx.ui.notify(errorMessage(error), "error"),
135
- });
136
- if (sessionSignal?.aborted || result.kind === "stale") return { kind: "stale" };
137
- if (chainedResult) return { kind: "route-result", ...chainedResult };
138
- if (resolvedDirection) return { kind: "resolved", direction: resolvedDirection };
139
- if (result.kind === "closed") {
140
- return result.reason === "back" ? { kind: "back" } : { kind: "closed" };
141
- }
142
- return { kind: "closed" };
124
+ const result = await runMenu(ctx, menu, {
125
+ getState: () => currentDecision,
126
+ signal: sessionSignal,
127
+ isCurrent: () => !sessionSignal?.aborted,
128
+ onError: (_menuCtx, error) => ctx.ui.notify(errorMessage(error), "error"),
129
+ });
130
+ if (sessionSignal?.aborted || result.kind === "stale") return { kind: "stale" };
131
+ if (chainedResult) return { kind: "route-result", ...chainedResult };
132
+ if (resolvedDirection) return { kind: "resolved", direction: resolvedDirection };
133
+ if (result.kind === "closed") {
134
+ return result.reason === "back" ? { kind: "back" } : { kind: "closed" };
135
+ }
136
+ return { kind: "closed" };
143
137
  }
144
138
 
145
139
  function resolutionTitle(decision: SyncDecision) {
146
- return decision.kind === "remote-empty" ? "Remote is empty" : "Resolve sync conflict";
140
+ return decision.kind === "remote-empty" ? "Remote is empty" : "Resolve sync conflict";
147
141
  }
148
142
 
149
143
  function resolutionLines(decision: SyncDecision) {
150
- return [
151
- `Sync setup: ${safeTerminalText(decision.setupName)}`,
152
- ...causeSummary(decision),
153
- "No files have been changed by this failed operation.",
154
- ];
144
+ return [
145
+ `Sync setup: ${safeTerminalText(decision.setupName)}`,
146
+ ...causeSummary(decision),
147
+ "No files have been changed by this failed operation.",
148
+ ];
155
149
  }
156
150
 
157
151
  function causeSummary(decision: SyncDecision) {
158
- if (decision.kind === "first-sync-settings-diverged") {
159
- return ["This machine and the remote have different Pi settings on first sync."];
160
- }
161
- if (decision.kind === "first-sync-sessions-diverged") {
162
- return ["Pi settings match, but local and remote sessions differ on first sync."];
163
- }
164
- if (decision.kind === "remote-empty") return ["The remote storage location has no snapshot."];
165
- return [
166
- ...(decision.causes.localChanged ? ["Local content changed since the last sync."] : []),
167
- ...(decision.causes.remoteChanged ? ["Remote content changed since the last sync."] : []),
168
- ...(decision.causes.policyChanged ? ["Included content changed since the last sync."] : []),
169
- ];
152
+ if (decision.kind === "first-sync-settings-diverged") {
153
+ return ["This machine and the remote have different Pi settings on first sync."];
154
+ }
155
+ if (decision.kind === "first-sync-sessions-diverged") {
156
+ return ["Pi settings match, but local and remote sessions differ on first sync."];
157
+ }
158
+ if (decision.kind === "remote-empty") return ["The remote storage location has no snapshot."];
159
+ return [
160
+ ...(decision.causes.localChanged ? ["Local content changed since the last sync."] : []),
161
+ ...(decision.causes.remoteChanged ? ["Remote content changed since the last sync."] : []),
162
+ ...(decision.causes.policyChanged ? ["Included content changed since the last sync."] : []),
163
+ ];
170
164
  }
171
165
 
172
166
  function pushLabel(decision: SyncDecision) {
173
- if (decision.kind === "remote-empty") return "Push local content…";
174
- if (decision.kind.startsWith("first-sync-")) return "Use local as initial source…";
175
- return "Keep local content and replace remote…";
167
+ if (decision.kind === "remote-empty") return "Push local content…";
168
+ if (decision.kind.startsWith("first-sync-")) return "Use local as initial source…";
169
+ return "Keep local content and replace remote…";
176
170
  }
177
171
 
178
172
  function pullLabel(decision: SyncDecision) {
179
- return decision.kind.startsWith("first-sync-")
180
- ? "Use remote as initial source…"
181
- : "Use remote content and replace local…";
173
+ return decision.kind.startsWith("first-sync-")
174
+ ? "Use remote as initial source…"
175
+ : "Use remote content and replace local…";
182
176
  }