@narumitw/pi-sync 0.49.6 → 0.49.8

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.
package/src/manager-ui.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
2
  import { type ActionMenuItem, defineMenu, runMenu } from "@narumitw/pi-tui-kit";
3
- import {
4
- type CancellableOperationResult,
5
- type RunRoute,
6
- runCancellableOperation,
7
- } from "./cancellable-operation.js";
3
+ import { type RunRoute, runCancellableOperation } from "./cancellable-operation.js";
8
4
  import { setSyncSetupCompletions } from "./command.js";
9
5
  import {
10
6
  configuredSyncSetupNames,
@@ -18,6 +14,12 @@ import {
18
14
  } from "./config.js";
19
15
  import { showAddGitTarget, showEditGitTarget, showGitSetup } from "./git-ui.js";
20
16
  import { inspectLock, isStaleLock } from "./lock.js";
17
+ import {
18
+ attentionMainMenuItems,
19
+ blockedSyncMenuItem,
20
+ type SyncManagerAttentionOptions,
21
+ showManagerAttention,
22
+ } from "./manager-attention.js";
21
23
  import {
22
24
  errorMessage,
23
25
  ownRecord,
@@ -25,6 +27,7 @@ import {
25
27
  requiredInput,
26
28
  safeTerminalText,
27
29
  } from "./manager-helpers.js";
30
+ import { dispatchManagerResult } from "./manager-result-dispatcher.js";
28
31
  import { backendStorageDescription, describeManagerState } from "./manager-state.js";
29
32
  import { chooseS3Credentials } from "./s3-credentials-ui.js";
30
33
  import {
@@ -37,7 +40,6 @@ import { showSyncSettings } from "./settings-ui.js";
37
40
  import { useSyncSetup } from "./setup-switch.js";
38
41
  import { showAddStorageConnection, showStorageConnections } from "./storage-connections-ui.js";
39
42
  import { DEFAULT_SYNC_INCLUDE, syncIncludeSelection } from "./sync-policy.js";
40
- import { showSyncResolution } from "./sync-resolution-ui.js";
41
43
  import { showSyncSetups } from "./sync-setups-ui.js";
42
44
  import type { AnySyncConfig } from "./types.js";
43
45
  import { showAddWebDavTarget, showEditWebDavTarget, showWebDavSetup } from "./webdav-ui.js";
@@ -46,6 +48,7 @@ export async function showSyncManager(
46
48
  ctx: ExtensionCommandContext,
47
49
  runRoute: RunRoute,
48
50
  sessionSignal?: AbortSignal,
51
+ options: SyncManagerAttentionOptions = {},
49
52
  ): Promise<void> {
50
53
  if (!ctx.hasUI) {
51
54
  await runRoute("help");
@@ -53,6 +56,7 @@ export async function showSyncManager(
53
56
  }
54
57
  type Screen = "main" | "more" | "recovery";
55
58
  type Action =
59
+ | "review-attention"
56
60
  | "sync"
57
61
  | "switch"
58
62
  | "diff"
@@ -78,7 +82,12 @@ export async function showSyncManager(
78
82
  kind: "actions",
79
83
  title: "Manage sync",
80
84
  lines: state.manager.title.split("\n").slice(1),
81
- items: state.manager.actions.map(syncMainMenuItem),
85
+ items: [
86
+ ...attentionMainMenuItems(state.manager),
87
+ ...state.manager.actions.map(
88
+ (label) => blockedSyncMenuItem(label, state.manager) ?? syncMainMenuItem(label),
89
+ ),
90
+ ],
82
91
  hint: "close",
83
92
  }),
84
93
  more: () => ({
@@ -114,7 +123,28 @@ export async function showSyncManager(
114
123
  }),
115
124
  },
116
125
  actions: {
126
+ "review-attention": async () => {
127
+ const attention = options.getAttention?.();
128
+ if (!attention) return { kind: "stay" };
129
+ const disposition = await showManagerAttention(
130
+ ctx,
131
+ attention,
132
+ runRoute,
133
+ sessionSignal,
134
+ () => options.onSelectionResolved?.(attention),
135
+ );
136
+ return { kind: disposition };
137
+ },
117
138
  sync: async () => {
139
+ const pendingAttention = options.getAttention?.();
140
+ if (pendingAttention) {
141
+ const activeConfig = await loadConfig();
142
+ if (sessionSignal?.aborted) return { kind: "close" };
143
+ if (pendingAttention.decision.setupName === activeConfig.setupName) {
144
+ ctx.ui.notify("Review synced content before starting Sync now.", "warning");
145
+ return { kind: "stay" };
146
+ }
147
+ }
118
148
  const result = await runCancellableOperation(
119
149
  ctx,
120
150
  "Checking current sync setup…",
@@ -125,10 +155,14 @@ export async function showSyncManager(
125
155
  signal: sessionSignal,
126
156
  },
127
157
  );
128
- const resolution = await resolveManagerDecision(ctx, result, runRoute, sessionSignal);
129
- return result.kind === "closed" || resolution === "close"
130
- ? { kind: "close" }
131
- : { kind: "stay" };
158
+ const disposition = await dispatchManagerResult(
159
+ ctx,
160
+ result,
161
+ "sync",
162
+ runRoute,
163
+ sessionSignal,
164
+ );
165
+ return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
132
166
  },
133
167
  switch: async () => {
134
168
  const result = await showSetupSwitcher(ctx, runRoute, undefined, sessionSignal);
@@ -162,12 +196,14 @@ export async function showSyncManager(
162
196
  signal: sessionSignal,
163
197
  },
164
198
  );
165
- const resolution = await resolveManagerDecision(ctx, result, runRoute, sessionSignal);
166
- return result.kind === "closed" ||
167
- (result.kind === "completed" && result.outcome === "applied") ||
168
- resolution === "close"
169
- ? { kind: "close" }
170
- : { kind: "stay" };
199
+ const disposition = await dispatchManagerResult(
200
+ ctx,
201
+ result,
202
+ "pull",
203
+ runRoute,
204
+ sessionSignal,
205
+ );
206
+ return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
171
207
  },
172
208
  push: async () => {
173
209
  const result = await runCancellableOperation(
@@ -181,10 +217,14 @@ export async function showSyncManager(
181
217
  signal: sessionSignal,
182
218
  },
183
219
  );
184
- const resolution = await resolveManagerDecision(ctx, result, runRoute, sessionSignal);
185
- return result.kind === "closed" || resolution === "close"
186
- ? { kind: "close" }
187
- : { kind: "stay" };
220
+ const disposition = await dispatchManagerResult(
221
+ ctx,
222
+ result,
223
+ "push",
224
+ runRoute,
225
+ sessionSignal,
226
+ );
227
+ return disposition.kind === "close" ? { kind: "close" } : { kind: "stay" };
188
228
  },
189
229
  setups: async () => {
190
230
  const result = await showSyncSetupManager(ctx, runRoute, sessionSignal);
@@ -219,10 +259,14 @@ export async function showSyncManager(
219
259
  });
220
260
  await runMenu(ctx, menu, {
221
261
  getState: async () => {
262
+ const pendingAttention = options.getAttention?.();
222
263
  const [manager, lock] = await Promise.all([
223
- describeManagerState(sessionSignal),
264
+ describeManagerState(sessionSignal, pendingAttention),
224
265
  inspectLock(),
225
266
  ]);
267
+ if (pendingAttention && options.getAttention?.() === pendingAttention && !manager.attention) {
268
+ options.onSelectionResolved?.(pendingAttention);
269
+ }
226
270
  return {
227
271
  manager,
228
272
  canRecover:
@@ -234,21 +278,6 @@ export async function showSyncManager(
234
278
  });
235
279
  }
236
280
 
237
- async function resolveManagerDecision(
238
- ctx: ExtensionCommandContext,
239
- result: CancellableOperationResult,
240
- runRoute: RunRoute,
241
- signal?: AbortSignal,
242
- ) {
243
- if (result.kind !== "decision-required") return undefined;
244
- const resolution = await showSyncResolution(ctx, result.decision, runRoute, signal);
245
- return resolution.kind === "resolved" ||
246
- resolution.kind === "closed" ||
247
- resolution.kind === "stale"
248
- ? "close"
249
- : "stay";
250
- }
251
-
252
281
  function syncMainMenuItem(
253
282
  label: string,
254
283
  ): ActionMenuItem<
@@ -553,23 +582,11 @@ async function showSetupSwitcher(
553
582
  signal,
554
583
  },
555
584
  );
556
- if (pullResult.kind === "closed") {
557
- pullClosed = true;
558
- return undefined;
559
- }
560
- if (pullResult.kind === "decision-required") {
561
- const resolution = await showSyncResolution(ctx, pullResult.decision, runRoute, signal);
562
- if (
563
- resolution.kind === "resolved" ||
564
- resolution.kind === "closed" ||
565
- resolution.kind === "stale"
566
- ) {
567
- pullClosed = true;
568
- }
569
- return resolution.kind === "resolved" && resolution.direction === "pull"
570
- ? "applied"
571
- : undefined;
585
+ const disposition = await dispatchManagerResult(ctx, pullResult, "pull", runRoute, signal);
586
+ if (pullResult.kind === "closed" || pullResult.kind.endsWith("required")) {
587
+ pullClosed = disposition.kind === "close";
572
588
  }
589
+ if (disposition.appliedRoute === "pull") return "applied";
573
590
  if (pullResult.kind === "completed") return pullResult.outcome;
574
591
  return pullResult.kind === "cancelled" ? "cancelled" : undefined;
575
592
  },