@lmzhen/dsh-evolution-merge-ui 0.33.0 → 0.35.0
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/lib/client.js +497 -233
- package/lib/types/client/MergeRow.d.ts +27 -0
- package/lib/types/client/MergeRunStrip.d.ts +16 -0
- package/lib/types/client/MergeSettings.d.ts +15 -0
- package/lib/types/client/Panel.d.ts +5 -1
- package/lib/types/client/api.d.ts +10 -2
- package/lib/types/client/merge-presentation.d.ts +20 -0
- package/lib/types/client/session.d.ts +2 -0
- package/lib/types/client/sorting.d.ts +4 -4
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -163,34 +163,51 @@ window.__ModuleLoader__.load({
|
|
|
163
163
|
...EMPTY,
|
|
164
164
|
reason: "seat-missing"
|
|
165
165
|
};
|
|
166
|
+
let lastReady;
|
|
167
|
+
let generation = 0;
|
|
166
168
|
const listeners = /* @__PURE__ */ new Set();
|
|
167
|
-
let
|
|
168
|
-
let listening = false;
|
|
169
|
+
let remoteOff;
|
|
169
170
|
const notify = () => {
|
|
170
171
|
for (const listener of listeners) listener();
|
|
171
172
|
};
|
|
172
|
-
const
|
|
173
|
-
|
|
173
|
+
const absorb = (next) => {
|
|
174
|
+
if (next.status === "ready") {
|
|
175
|
+
lastReady = next;
|
|
176
|
+
snapshot = next;
|
|
177
|
+
} else if (lastReady !== void 0) snapshot = {
|
|
178
|
+
...lastReady,
|
|
179
|
+
stale: true,
|
|
180
|
+
reason: next.reason ?? "readback-failed"
|
|
181
|
+
};
|
|
182
|
+
else snapshot = next;
|
|
174
183
|
notify();
|
|
175
184
|
};
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
const refresh = async () => {
|
|
186
|
+
const ticket = ++generation;
|
|
187
|
+
const next = await describe(namespace);
|
|
188
|
+
if (ticket !== generation) return;
|
|
189
|
+
absorb(next);
|
|
190
|
+
};
|
|
191
|
+
const remote = () => ctx.get?.("remote");
|
|
192
|
+
const stopRemote = () => {
|
|
193
|
+
remoteOff?.();
|
|
194
|
+
remoteOff = void 0;
|
|
195
|
+
};
|
|
196
|
+
const startRemote = () => {
|
|
197
|
+
if (remoteOff !== void 0) return;
|
|
198
|
+
remoteOff = remote()?.$on?.("settings/document-updated", (changed) => {
|
|
199
|
+
if (changed === namespace) refresh();
|
|
200
|
+
});
|
|
186
201
|
};
|
|
187
202
|
const form = {
|
|
188
203
|
getSnapshot: () => snapshot,
|
|
189
204
|
subscribe: (listener) => {
|
|
190
205
|
listeners.add(listener);
|
|
191
|
-
|
|
206
|
+
startRemote();
|
|
207
|
+
refresh();
|
|
192
208
|
return () => {
|
|
193
209
|
listeners.delete(listener);
|
|
210
|
+
if (listeners.size === 0) stopRemote();
|
|
194
211
|
};
|
|
195
212
|
},
|
|
196
213
|
set: async (field, value) => await form.mutate([{
|
|
@@ -204,35 +221,39 @@ window.__ModuleLoader__.load({
|
|
|
204
221
|
}]),
|
|
205
222
|
mutate: async (ops, expectedRevision) => {
|
|
206
223
|
const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
|
|
207
|
-
if (outcome.snapshot !== void 0)
|
|
208
|
-
|
|
209
|
-
notify();
|
|
210
|
-
} else await refresh();
|
|
224
|
+
if (outcome.snapshot !== void 0) absorb(outcome.snapshot);
|
|
225
|
+
else await refresh();
|
|
211
226
|
return outcome.ok;
|
|
227
|
+
},
|
|
228
|
+
refresh,
|
|
229
|
+
dispose: () => {
|
|
230
|
+
stopRemote();
|
|
231
|
+
listeners.clear();
|
|
212
232
|
}
|
|
213
233
|
};
|
|
214
234
|
return form;
|
|
215
235
|
}
|
|
216
236
|
/**
|
|
217
|
-
* Build one
|
|
218
|
-
*
|
|
219
|
-
*
|
|
237
|
+
* Build one seat per client registration; every form's remote listener is released by
|
|
238
|
+
* the caller's effect. No module-level WeakMap: a replaced registration must not share
|
|
239
|
+
* a dead seat with the previous one.
|
|
220
240
|
*/
|
|
221
|
-
const seats = /* @__PURE__ */ new WeakMap();
|
|
222
241
|
function createSettingsSeat(ctx) {
|
|
223
|
-
const cached = seats.get(ctx);
|
|
224
|
-
if (cached !== void 0) return cached;
|
|
225
242
|
const forms = /* @__PURE__ */ new Map();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
form
|
|
230
|
-
|
|
243
|
+
return {
|
|
244
|
+
get(namespace) {
|
|
245
|
+
let form = forms.get(namespace);
|
|
246
|
+
if (form === void 0) {
|
|
247
|
+
form = createForm(namespace, ctx);
|
|
248
|
+
forms.set(namespace, form);
|
|
249
|
+
}
|
|
250
|
+
return form;
|
|
251
|
+
},
|
|
252
|
+
dispose: () => {
|
|
253
|
+
for (const form of forms.values()) form.dispose?.();
|
|
254
|
+
forms.clear();
|
|
231
255
|
}
|
|
232
|
-
|
|
233
|
-
} };
|
|
234
|
-
seats.set(ctx, seat);
|
|
235
|
-
return seat;
|
|
256
|
+
};
|
|
236
257
|
}
|
|
237
258
|
//#endregion
|
|
238
259
|
//#region ../evolution-client-kit/src/workspace.ts
|
|
@@ -251,6 +272,25 @@ window.__ModuleLoader__.load({
|
|
|
251
272
|
} catch {}
|
|
252
273
|
}
|
|
253
274
|
//#endregion
|
|
275
|
+
//#region ../evolution-client-kit/src/format.ts
|
|
276
|
+
/**
|
|
277
|
+
* Format an ISO string, epoch milliseconds, or null as local wall time.
|
|
278
|
+
* Invalid values return null; callers decide how to say "unknown".
|
|
279
|
+
* @param value - ISO string, epoch ms, or null.
|
|
280
|
+
* @param options - seconds on/off; defaults to on.
|
|
281
|
+
* @returns the formatted local time, or null when the value is absent/invalid.
|
|
282
|
+
*/
|
|
283
|
+
function formatLocalDateTime(value, options = {}) {
|
|
284
|
+
if (value === null || value === void 0) return null;
|
|
285
|
+
const ms = typeof value === "number" ? value : Date.parse(value);
|
|
286
|
+
if (!Number.isFinite(ms)) return null;
|
|
287
|
+
const date = new Date(ms);
|
|
288
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
289
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
290
|
+
const head = String(date.getFullYear()) + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes());
|
|
291
|
+
return options.seconds === false ? head : head + ":" + pad(date.getSeconds());
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
254
294
|
//#region src/client/api.ts
|
|
255
295
|
/** Read-only merge wire client. Classifies every failure and never writes. */
|
|
256
296
|
const HOST_ROUTES = ROUTES.merge;
|
|
@@ -491,7 +531,8 @@ window.__ModuleLoader__.load({
|
|
|
491
531
|
startCommands: facts.startCommands,
|
|
492
532
|
notes: [...facts.notes],
|
|
493
533
|
degraded: facts.degraded,
|
|
494
|
-
...facts.active === null
|
|
534
|
+
...facts.active === null ? {} : { activeRun: toRun(facts.active) },
|
|
535
|
+
...facts.last === null ? {} : { lastRun: toRun(facts.last) },
|
|
495
536
|
approval: {
|
|
496
537
|
mergeApproval: facts.approval.mergeApproval,
|
|
497
538
|
...platformWrite === void 0 ? {} : { platformWrite }
|
|
@@ -518,9 +559,14 @@ window.__ModuleLoader__.load({
|
|
|
518
559
|
};
|
|
519
560
|
}
|
|
520
561
|
async function body(name, side, rowId) {
|
|
521
|
-
const
|
|
562
|
+
const query = new URLSearchParams({
|
|
563
|
+
name,
|
|
564
|
+
side
|
|
565
|
+
});
|
|
566
|
+
if (rowId !== void 0) query.set("id", rowId);
|
|
567
|
+
const answer = await requestWire(HOST_ROUTES.body + "?" + query.toString());
|
|
522
568
|
if (!answer.ok) {
|
|
523
|
-
if (answer.failure.kind === "problem" && answer.failure.code === "bad-query" && (answer.failure.message ?? "").includes("
|
|
569
|
+
if (answer.failure.kind === "problem" && answer.failure.code === "bad-query" && (answer.failure.message ?? "").includes("ambiguous")) return {
|
|
524
570
|
ok: false,
|
|
525
571
|
failure: {
|
|
526
572
|
...answer.failure,
|
|
@@ -541,6 +587,56 @@ window.__ModuleLoader__.load({
|
|
|
541
587
|
}
|
|
542
588
|
};
|
|
543
589
|
}
|
|
590
|
+
async function decide(rowId, verb, sessionId) {
|
|
591
|
+
const path = HOST_ROUTES.body.replace(/body$/, "decision");
|
|
592
|
+
let response;
|
|
593
|
+
try {
|
|
594
|
+
response = await fetch(path, {
|
|
595
|
+
method: "POST",
|
|
596
|
+
headers: {
|
|
597
|
+
accept: "application/json",
|
|
598
|
+
"content-type": "application/json"
|
|
599
|
+
},
|
|
600
|
+
body: JSON.stringify({
|
|
601
|
+
rowId,
|
|
602
|
+
verb,
|
|
603
|
+
sessionId
|
|
604
|
+
})
|
|
605
|
+
});
|
|
606
|
+
} catch {
|
|
607
|
+
return {
|
|
608
|
+
ok: false,
|
|
609
|
+
failure: problem("server", 0)
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
let body;
|
|
613
|
+
try {
|
|
614
|
+
body = await response.json();
|
|
615
|
+
} catch {
|
|
616
|
+
return {
|
|
617
|
+
ok: false,
|
|
618
|
+
failure: problem("server", response.status)
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
if (typeof body !== "object" || body === null) return {
|
|
622
|
+
ok: false,
|
|
623
|
+
failure: problem("server", response.status)
|
|
624
|
+
};
|
|
625
|
+
const record = body;
|
|
626
|
+
if (record.wireVersion !== 1 || typeof record.ok !== "boolean" || typeof record.message !== "string") return {
|
|
627
|
+
ok: false,
|
|
628
|
+
failure: problem("wire-version", response.status)
|
|
629
|
+
};
|
|
630
|
+
return {
|
|
631
|
+
ok: true,
|
|
632
|
+
payload: {
|
|
633
|
+
ok: record.ok,
|
|
634
|
+
message: record.message,
|
|
635
|
+
...typeof record.code === "string" ? { code: record.code } : {},
|
|
636
|
+
...typeof record.runId === "string" ? { runId: record.runId } : {}
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}
|
|
544
640
|
async function history() {
|
|
545
641
|
const answer = await requestWire(HOST_ROUTES.history);
|
|
546
642
|
return answer.ok ? {
|
|
@@ -551,7 +647,7 @@ window.__ModuleLoader__.load({
|
|
|
551
647
|
reason: item.reason,
|
|
552
648
|
...item.reasonCode === void 0 ? {} : { reasonCode: item.reasonCode },
|
|
553
649
|
lastVersion: item.lastVersion,
|
|
554
|
-
archivedAt: new Date(item.archivedAt).toISOString(),
|
|
650
|
+
archivedAt: item.archivedAt === null ? null : new Date(item.archivedAt).toISOString(),
|
|
555
651
|
...item.sourceRunId === void 0 ? {} : { sourceRunId: item.sourceRunId }
|
|
556
652
|
})) }
|
|
557
653
|
} : answer;
|
|
@@ -570,7 +666,9 @@ window.__ModuleLoader__.load({
|
|
|
570
666
|
"degraded": "读模型降级:",
|
|
571
667
|
"copied": "已复制:",
|
|
572
668
|
"copyFailed": "复制失败:浏览器没有接受剪贴板写入。",
|
|
669
|
+
"copyRetry": "重试复制",
|
|
573
670
|
"row.noSummary": "无摘要",
|
|
671
|
+
"row.summaryBoth": "双方摘要",
|
|
574
672
|
"overlay.body": "正文",
|
|
575
673
|
"hint": "手动触发;后台逐对判定。这里只读,不写技能。",
|
|
576
674
|
"history": "历史",
|
|
@@ -614,6 +712,7 @@ window.__ModuleLoader__.load({
|
|
|
614
712
|
"row.failure": "失败",
|
|
615
713
|
"row.noRetry": "不可重试",
|
|
616
714
|
"row.copy": "复制命令",
|
|
715
|
+
"row.more": "更多命令",
|
|
617
716
|
"tab.expired": "已过期",
|
|
618
717
|
"row.targetCreated": "新建",
|
|
619
718
|
"row.verify": "判定状态",
|
|
@@ -634,6 +733,8 @@ window.__ModuleLoader__.load({
|
|
|
634
733
|
"skipKind.forced": "强制",
|
|
635
734
|
"action.approve": "复制批准",
|
|
636
735
|
"action.reject": "复制拒绝",
|
|
736
|
+
"decision.approve": "批准",
|
|
737
|
+
"decision.reject": "拒绝",
|
|
637
738
|
"action.retry-candidates": "复制重试候选",
|
|
638
739
|
"action.retry-resume": "重试续跑",
|
|
639
740
|
"action.discard": "复制丢弃",
|
|
@@ -651,6 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
651
752
|
"disabled.unavailable": "暂不可用",
|
|
652
753
|
"history.empty": "没有历史。",
|
|
653
754
|
"history.archivedAt": "归档于 {date}",
|
|
755
|
+
"history.archivedAtUnknown": "归档时间未知",
|
|
654
756
|
"failureServer": "读取失败",
|
|
655
757
|
"failureUnauthorized": "未登录",
|
|
656
758
|
"failureForbidden": "无权访问",
|
|
@@ -708,7 +810,6 @@ window.__ModuleLoader__.load({
|
|
|
708
810
|
"history.reason.archived": "已归档",
|
|
709
811
|
"history.reason.unknown": "原因未提供",
|
|
710
812
|
"history.lastVersion": "最后版本",
|
|
711
|
-
"history.archivedAt": "归档时间",
|
|
712
813
|
"body.truncated": "正文过长,已截断。",
|
|
713
814
|
"body.empty": "正文为空。",
|
|
714
815
|
"body.totalBytes": "总字节",
|
|
@@ -729,8 +830,10 @@ window.__ModuleLoader__.load({
|
|
|
729
830
|
"settings.capabilityMissing": "宿主未提供该能力",
|
|
730
831
|
"settings.capabilityDisabled": "当前部署不可用",
|
|
731
832
|
"settings.readonly": "只读显示;修改请到平台设置页。",
|
|
732
|
-
"settings.mergeApprovalPath": "设置 → 插件 → 自进化 → 技能自动合并",
|
|
733
833
|
"run.title": "运行",
|
|
834
|
+
"run.progressUnknown": "进度未知",
|
|
835
|
+
"run.current": "当前运行",
|
|
836
|
+
"run.last": "上次完成",
|
|
734
837
|
"run.id": "运行编号",
|
|
735
838
|
"run.state": "状态",
|
|
736
839
|
"run.stage": "阶段",
|
|
@@ -772,7 +875,9 @@ window.__ModuleLoader__.load({
|
|
|
772
875
|
"degraded": "Degraded read model:",
|
|
773
876
|
"copied": "Copied:",
|
|
774
877
|
"copyFailed": "Copy failed: the browser did not accept the clipboard write.",
|
|
878
|
+
"copyRetry": "Retry copy",
|
|
775
879
|
"row.noSummary": "No summary",
|
|
880
|
+
"row.summaryBoth": "Shared summary",
|
|
776
881
|
"overlay.body": "Body",
|
|
777
882
|
"hint": "Manual only; pairs are judged in the background. Read-only here.",
|
|
778
883
|
"history": "History",
|
|
@@ -816,6 +921,7 @@ window.__ModuleLoader__.load({
|
|
|
816
921
|
"row.failure": "Failure",
|
|
817
922
|
"row.noRetry": "not retryable",
|
|
818
923
|
"row.copy": "Copy command",
|
|
924
|
+
"row.more": "More commands",
|
|
819
925
|
"tab.expired": "Expired",
|
|
820
926
|
"row.targetCreated": "created",
|
|
821
927
|
"row.verify": "Verify state",
|
|
@@ -836,6 +942,8 @@ window.__ModuleLoader__.load({
|
|
|
836
942
|
"skipKind.forced": "forced",
|
|
837
943
|
"action.approve": "Copy approve",
|
|
838
944
|
"action.reject": "Copy reject",
|
|
945
|
+
"decision.approve": "Approve",
|
|
946
|
+
"decision.reject": "Reject",
|
|
839
947
|
"action.retry-candidates": "Copy retry candidates",
|
|
840
948
|
"action.retry-resume": "Retry resume",
|
|
841
949
|
"action.discard": "Copy discard",
|
|
@@ -853,6 +961,7 @@ window.__ModuleLoader__.load({
|
|
|
853
961
|
"disabled.unavailable": "unavailable",
|
|
854
962
|
"history.empty": "No history.",
|
|
855
963
|
"history.archivedAt": "Archived {date}",
|
|
964
|
+
"history.archivedAtUnknown": "Archive time unknown",
|
|
856
965
|
"failureServer": "Read failed",
|
|
857
966
|
"failureUnauthorized": "Not signed in",
|
|
858
967
|
"failureForbidden": "Forbidden",
|
|
@@ -910,7 +1019,6 @@ window.__ModuleLoader__.load({
|
|
|
910
1019
|
"history.reason.archived": "archived",
|
|
911
1020
|
"history.reason.unknown": "reason not provided",
|
|
912
1021
|
"history.lastVersion": "Last version",
|
|
913
|
-
"history.archivedAt": "Archived at",
|
|
914
1022
|
"body.truncated": "Body is truncated.",
|
|
915
1023
|
"body.empty": "Body is empty.",
|
|
916
1024
|
"body.totalBytes": "Total bytes",
|
|
@@ -931,8 +1039,10 @@ window.__ModuleLoader__.load({
|
|
|
931
1039
|
"settings.capabilityMissing": "Host did not provide this capability",
|
|
932
1040
|
"settings.capabilityDisabled": "Not available in this deployment",
|
|
933
1041
|
"settings.readonly": "Read-only here; edit in platform settings.",
|
|
934
|
-
"settings.mergeApprovalPath": "Settings → Plugins → Evolution → Skill auto-merge",
|
|
935
1042
|
"run.title": "Run",
|
|
1043
|
+
"run.progressUnknown": "progress unknown",
|
|
1044
|
+
"run.current": "Current run",
|
|
1045
|
+
"run.last": "Last completed",
|
|
936
1046
|
"run.id": "Run ID",
|
|
937
1047
|
"run.state": "State",
|
|
938
1048
|
"run.stage": "Stage",
|
|
@@ -970,14 +1080,14 @@ window.__ModuleLoader__.load({
|
|
|
970
1080
|
"settings.saved": "已保存",
|
|
971
1081
|
"settings.saveFailed": "保存失败",
|
|
972
1082
|
"settings.openPlatform": "打开平台设置",
|
|
973
|
-
"settings.openPlatformPath": "设置 → 内置插件 →
|
|
1083
|
+
"settings.openPlatformPath": "设置 → 内置插件 → 自进化 → 技能自动合并",
|
|
974
1084
|
"settings.live": "可直接在此保存;platformWrite 能力来自宿主。"
|
|
975
1085
|
});
|
|
976
1086
|
Object.assign(en, {
|
|
977
1087
|
"settings.saved": "Saved",
|
|
978
1088
|
"settings.saveFailed": "Save failed",
|
|
979
1089
|
"settings.openPlatform": "Open platform settings",
|
|
980
|
-
"settings.openPlatformPath": "Settings → Built-in plugins →
|
|
1090
|
+
"settings.openPlatformPath": "Settings → Built-in plugins → Evolution → Skill auto-merge",
|
|
981
1091
|
"settings.live": "Save directly here; platformWrite capability comes from the host."
|
|
982
1092
|
});
|
|
983
1093
|
Object.assign(zh, { "settings.platformWriteLinkHint": "此开关属于平台设置,这里只读;请到平台设置修改。" });
|
|
@@ -1014,7 +1124,8 @@ window.__ModuleLoader__.load({
|
|
|
1014
1124
|
".evolution-merge-panel [data-role=\"title\"] { margin:0; font-size:var(--evo-type-lg); font-weight:600; }",
|
|
1015
1125
|
".evolution-merge-panel [data-role=\"start\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
|
|
1016
1126
|
".evolution-merge-panel [data-role=\"degraded\"] { margin:0; color:var(--dsw-alias-state-warn-primary); font-size:var(--evo-type-xs); }",
|
|
1017
|
-
".evolution-merge-panel [data-role=\"expired-toggle\"]
|
|
1127
|
+
".evolution-merge-panel [data-role=\"expired-toggle\"] { display:inline-flex; align-items:center; gap:var(--evo-space-4); border:var(--evo-hairline-width) solid var(--dsw-alias-border-secondary); border-radius:var(--evo-radius-control); background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:var(--evo-type-xs); padding:var(--evo-space-4) var(--evo-space-8); cursor:pointer; }",
|
|
1128
|
+
".evolution-merge-panel [data-role=\"expired-toggle\"][data-active=\"true\"] { color:var(--dsw-alias-state-warn-primary); border-color:var(--dsw-alias-state-warn-primary); background:transparent; }",
|
|
1018
1129
|
".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
|
|
1019
1130
|
".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
|
|
1020
1131
|
".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
|
|
@@ -1043,7 +1154,9 @@ window.__ModuleLoader__.load({
|
|
|
1043
1154
|
".evolution-merge-panel [data-role=\"settings-card\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); padding:var(--evo-space-10); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); }",
|
|
1044
1155
|
".evolution-merge-panel [data-role=\"settings-title\"] { margin:0; font-size:var(--evo-type-sm); }",
|
|
1045
1156
|
".evolution-merge-panel [data-role=\"settings-hint\"], .evolution-merge-panel [data-role=\"settings-readonly\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
|
|
1046
|
-
".evolution-merge-panel [data-role=\"settings-
|
|
1157
|
+
".evolution-merge-panel [data-role=\"settings-chips\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-6); }",
|
|
1158
|
+
".evolution-merge-panel [data-role=\"settings-section\"] { display:inline-flex; align-items:center; gap:var(--evo-space-6); padding:var(--evo-space-2) var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); font-size:var(--evo-type-xs); }",
|
|
1159
|
+
".evolution-merge-panel [data-role=\"settings-label\"] { color:var(--dsw-alias-label-secondary); }",
|
|
1047
1160
|
".evolution-merge-panel [data-role=\"settings-section\"][data-disabled=\"true\"] { color:var(--dsw-alias-label-tertiary); }",
|
|
1048
1161
|
".evolution-merge-panel [data-role=\"settings-toggle\"] { margin:0; }",
|
|
1049
1162
|
".evolution-merge-panel [data-role=\"settings-value\"] { font-weight:600; min-width:2.5em; text-align:right; }",
|
|
@@ -1146,9 +1259,9 @@ window.__ModuleLoader__.load({
|
|
|
1146
1259
|
const value = workspaceId?.trim() ?? "";
|
|
1147
1260
|
return value === "" ? "local" : value;
|
|
1148
1261
|
}
|
|
1149
|
-
/** Stable localStorage key for one workspace and
|
|
1150
|
-
function rowOrderKey(workspaceId, queue) {
|
|
1151
|
-
return "dsh-evolution-merge.row-order.v1:" + encodeURIComponent(resolveWorkspaceId(workspaceId)) + ":" + queue;
|
|
1262
|
+
/** Stable localStorage key for one workspace, queue and expired filter. */
|
|
1263
|
+
function rowOrderKey(workspaceId, queue, expiredOnly = false) {
|
|
1264
|
+
return "dsh-evolution-merge.row-order.v1:" + encodeURIComponent(resolveWorkspaceId(workspaceId)) + ":" + queue + (expiredOnly ? ":expired" : ":live");
|
|
1152
1265
|
}
|
|
1153
1266
|
function defaultStorage() {
|
|
1154
1267
|
try {
|
|
@@ -1177,18 +1290,18 @@ window.__ModuleLoader__.load({
|
|
|
1177
1290
|
/** One tab's persisted order; failures degrade to an empty (wire) order. */
|
|
1178
1291
|
function createRowOrderStore(storage = defaultStorage()) {
|
|
1179
1292
|
return {
|
|
1180
|
-
read(workspaceId, queue) {
|
|
1293
|
+
read(workspaceId, queue, expiredOnly = false) {
|
|
1181
1294
|
if (storage === void 0) return [];
|
|
1182
1295
|
try {
|
|
1183
|
-
return normalize(storage.getItem(rowOrderKey(workspaceId, queue)));
|
|
1296
|
+
return normalize(storage.getItem(rowOrderKey(workspaceId, queue, expiredOnly)));
|
|
1184
1297
|
} catch {
|
|
1185
1298
|
return [];
|
|
1186
1299
|
}
|
|
1187
1300
|
},
|
|
1188
|
-
write(workspaceId, queue, ids) {
|
|
1301
|
+
write(workspaceId, queue, ids, expiredOnly = false) {
|
|
1189
1302
|
if (storage === void 0) return;
|
|
1190
1303
|
try {
|
|
1191
|
-
storage.setItem(rowOrderKey(workspaceId, queue), JSON.stringify(ids));
|
|
1304
|
+
storage.setItem(rowOrderKey(workspaceId, queue, expiredOnly), JSON.stringify(ids));
|
|
1192
1305
|
} catch {}
|
|
1193
1306
|
}
|
|
1194
1307
|
};
|
|
@@ -1228,123 +1341,20 @@ window.__ModuleLoader__.load({
|
|
|
1228
1341
|
return edge === "top" ? [id, ...rest] : [...rest, id];
|
|
1229
1342
|
}
|
|
1230
1343
|
//#endregion
|
|
1231
|
-
//#region src/client/
|
|
1232
|
-
/**
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
"
|
|
1243
|
-
];
|
|
1244
|
-
//#endregion
|
|
1245
|
-
//#region src/client/Panel.ts
|
|
1246
|
-
/** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
|
|
1247
|
-
const QUEUES = [
|
|
1248
|
-
"verify",
|
|
1249
|
-
"pending",
|
|
1250
|
-
"fuse",
|
|
1251
|
-
"skipped"
|
|
1252
|
-
];
|
|
1253
|
-
const DOCTOR_COMMAND = "/evolution doctor";
|
|
1254
|
-
function problemKey(problem) {
|
|
1255
|
-
switch (problem.code) {
|
|
1256
|
-
case "unauthenticated": return "failureUnauthorized";
|
|
1257
|
-
case "forbidden": return "failureForbidden";
|
|
1258
|
-
case "unmounted": return "failureUnmounted";
|
|
1259
|
-
case "capability": return "failureCapability";
|
|
1260
|
-
case "body-ambiguous": return "failureBodyAmbiguous";
|
|
1261
|
-
case "wire-version": return "failureWireVersion";
|
|
1262
|
-
case "body-missing": return "failureBodyMissing";
|
|
1263
|
-
case "bad-query": return "failureBadQuery";
|
|
1264
|
-
case "refused": return "failureRefused";
|
|
1265
|
-
case "server": return "failureServer";
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
function reasonText(t, reasonCode, fallback) {
|
|
1269
|
-
if (reasonCode === void 0) return fallback;
|
|
1270
|
-
const key = "history.reason." + reasonCode;
|
|
1271
|
-
const text = t(key);
|
|
1272
|
-
return text === key ? fallback : text;
|
|
1273
|
-
}
|
|
1274
|
-
function blockedReasonKey(failure) {
|
|
1275
|
-
const code = failure.readiness.code ?? failure.code;
|
|
1276
|
-
if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
|
|
1277
|
-
if (code.endsWith("forward-only")) return "blocked.reason.forward-only";
|
|
1278
|
-
if (code.endsWith("migration-blocked")) return "blocked.reason.migration-blocked";
|
|
1279
|
-
return "blocked.reason.unknown";
|
|
1280
|
-
}
|
|
1281
|
-
function failureNode(t, failure, fallback, copy) {
|
|
1282
|
-
if (isBlocked(failure)) {
|
|
1283
|
-
const raw = failure.readiness.message;
|
|
1284
|
-
return (0, react.createElement)("div", {
|
|
1285
|
-
"data-role": "blocked",
|
|
1286
|
-
role: "alert",
|
|
1287
|
-
"data-block-code": failure.code,
|
|
1288
|
-
"data-readiness-code": failure.readiness.code ?? ""
|
|
1289
|
-
}, (0, react.createElement)("h3", { "data-role": "blocked-title" }, t("blocked.title")), (0, react.createElement)("p", {
|
|
1290
|
-
"data-role": "blocked-reason",
|
|
1291
|
-
title: raw ?? t(blockedReasonKey(failure))
|
|
1292
|
-
}, t(blockedReasonKey(failure))), (0, react.createElement)("p", { "data-role": "blocked-hint" }, t("blocked.hint")), raw === void 0 ? null : (0, react.createElement)("details", { "data-role": "blocked-detail" }, (0, react.createElement)("summary", null, t("blocked.detail")), (0, react.createElement)("p", { "data-role": "blocked-raw" }, raw)), (0, react.createElement)("div", { "data-role": "blocked-actions" }, (0, react.createElement)("button", {
|
|
1293
|
-
type: "button",
|
|
1294
|
-
"data-role": "doctor",
|
|
1295
|
-
onClick: () => {
|
|
1296
|
-
copy(DOCTOR_COMMAND);
|
|
1297
|
-
}
|
|
1298
|
-
}, t("blocked.doctor")), (0, react.createElement)("button", {
|
|
1299
|
-
type: "button",
|
|
1300
|
-
"data-role": "refresh",
|
|
1301
|
-
onClick: () => {
|
|
1302
|
-
window.dispatchEvent(new Event("evolution-merge:refresh"));
|
|
1303
|
-
}
|
|
1304
|
-
}, t("refresh"))));
|
|
1305
|
-
}
|
|
1306
|
-
const capability = failure.code === "capability";
|
|
1307
|
-
return (0, react.createElement)("div", {
|
|
1308
|
-
"data-role": capability ? "capability-failure" : "failure",
|
|
1309
|
-
"data-error-code": failure.code,
|
|
1310
|
-
"data-status": String(failure.status)
|
|
1311
|
-
}, (0, react.createElement)("p", {
|
|
1312
|
-
"data-role": "failure-title",
|
|
1313
|
-
title: failure.message
|
|
1314
|
-
}, t(problemKey(failure))), capability ? (0, react.createElement)("p", { "data-role": "capability-hint" }, t("failureCapabilityHint")) : null, failure.message === void 0 ? null : (0, react.createElement)("details", { "data-role": "failure-detail" }, (0, react.createElement)("summary", null, t("failureDetails")), (0, react.createElement)("p", { "data-role": "failure-raw" }, failure.message)), fallback.length === 0 ? null : (0, react.createElement)("div", { "data-role": "fallback" }, fallback.map((command) => (0, react.createElement)("button", {
|
|
1315
|
-
key: command,
|
|
1316
|
-
type: "button",
|
|
1317
|
-
"data-role": "fallback-command",
|
|
1318
|
-
onClick: () => {
|
|
1319
|
-
copy(command);
|
|
1320
|
-
}
|
|
1321
|
-
}, command))));
|
|
1322
|
-
}
|
|
1323
|
-
function summaryNode(row, t) {
|
|
1324
|
-
if (row.state !== "pending" || row.summary === void 0) return null;
|
|
1325
|
-
return (0, react.createElement)("div", { "data-role": "pending-summary" }, [
|
|
1326
|
-
["row.summaryA", row.summary.a],
|
|
1327
|
-
["row.summaryB", row.summary.b],
|
|
1328
|
-
["row.rationale", row.summary.rationale]
|
|
1329
|
-
].map(([label, value]) => (0, react.createElement)("div", {
|
|
1330
|
-
key: label,
|
|
1331
|
-
"data-role": "pending-summary-slot"
|
|
1332
|
-
}, (0, react.createElement)("span", { "data-role": "pending-summary-label" }, t(label)), (0, react.createElement)("span", { "data-role": "pending-summary-value" }, value === null || value === "" ? t("row.noSummary") : value))));
|
|
1333
|
-
}
|
|
1334
|
-
function stageNode(row, t) {
|
|
1335
|
-
const stage = row.stageView;
|
|
1336
|
-
if (stage === void 0) return null;
|
|
1337
|
-
const tone = stage.state === "lost" ? "fail" : stage.state === "expired" || stage.state === "reserved" ? "warn" : "neutral";
|
|
1338
|
-
const expiry = stage.expiresAt === void 0 ? "" : " · " + t("row.expires") + " " + new Date(stage.expiresAt).toLocaleString();
|
|
1339
|
-
return (0, react.createElement)("span", {
|
|
1340
|
-
"data-role": "stage-badge",
|
|
1341
|
-
"data-stage": stage.state,
|
|
1342
|
-
"data-tone": tone
|
|
1343
|
-
}, t("stage.state." + stage.state) + expiry);
|
|
1344
|
+
//#region src/client/MergeRunStrip.ts
|
|
1345
|
+
/** The merge run strips: active/last identity, state, progress, report and details. */
|
|
1346
|
+
/**
|
|
1347
|
+
* Render active and last runs as two independently labelled strips.
|
|
1348
|
+
* @param props - view, translator and command copier.
|
|
1349
|
+
* @returns the strips, or null when neither run exists.
|
|
1350
|
+
*/
|
|
1351
|
+
function MergeRunStrips({ view, t, copy }) {
|
|
1352
|
+
const active = view.activeRun;
|
|
1353
|
+
const last = view.lastRun;
|
|
1354
|
+
if (active === void 0 && last === void 0) return null;
|
|
1355
|
+
return (0, react.createElement)("div", { "data-role": "run-strips" }, active === void 0 ? null : runStrip(active, t, copy, "active"), last === void 0 ? null : runStrip(last, t, copy, "last"));
|
|
1344
1356
|
}
|
|
1345
|
-
function
|
|
1346
|
-
const run = view.latestRun;
|
|
1347
|
-
if (run === void 0) return null;
|
|
1357
|
+
function runStrip(run, t, copy, role) {
|
|
1348
1358
|
const report = run.actions.find((action) => action.id === "report");
|
|
1349
1359
|
const reportEnabled = report?.enabled === true;
|
|
1350
1360
|
const internals = [
|
|
@@ -1360,7 +1370,7 @@ window.__ModuleLoader__.load({
|
|
|
1360
1370
|
run.endedAt === void 0 ? null : (0, react.createElement)("span", {
|
|
1361
1371
|
key: "ended",
|
|
1362
1372
|
"data-role": "run-ended"
|
|
1363
|
-
}, t("run.endedAt") + " " +
|
|
1373
|
+
}, t("run.endedAt") + " " + (formatLocalDateTime(run.endedAt) ?? "")),
|
|
1364
1374
|
run.failure === void 0 ? null : (0, react.createElement)("span", {
|
|
1365
1375
|
key: "failure",
|
|
1366
1376
|
"data-role": "run-failure",
|
|
@@ -1382,8 +1392,12 @@ window.__ModuleLoader__.load({
|
|
|
1382
1392
|
];
|
|
1383
1393
|
return (0, react.createElement)("div", {
|
|
1384
1394
|
"data-role": "run-strip",
|
|
1395
|
+
"data-run-role": role,
|
|
1385
1396
|
"data-run-state": run.state
|
|
1386
|
-
}, (0, react.createElement)("span", { "data-role": "run-
|
|
1397
|
+
}, (0, react.createElement)("span", { "data-role": "run-identity" }, t(role === "active" ? "run.current" : "run.last")), (0, react.createElement)("span", { "data-role": "run-state" }, t("run.state") + " " + t("run." + run.state)), run.progress === null || run.progress === void 0 ? run.state === "succeeded" ? (0, react.createElement)("span", {
|
|
1398
|
+
"data-role": "run-progress",
|
|
1399
|
+
"data-progress": "unknown"
|
|
1400
|
+
}, t("run.progressUnknown")) : null : (0, react.createElement)("span", { "data-role": "run-progress" }, t("run.progress") + " " + String(run.progress.done) + "/" + String(run.progress.total)), (0, react.createElement)("button", {
|
|
1387
1401
|
type: "button",
|
|
1388
1402
|
"data-role": "run-report",
|
|
1389
1403
|
"data-action": "report",
|
|
@@ -1404,48 +1418,68 @@ window.__ModuleLoader__.load({
|
|
|
1404
1418
|
}
|
|
1405
1419
|
}, t("action." + action.id))), (0, react.createElement)("details", { "data-role": "run-details" }, (0, react.createElement)("summary", null, t("run.details")), ...internals));
|
|
1406
1420
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region src/client/merge-presentation.ts
|
|
1423
|
+
/**
|
|
1424
|
+
* Compare the two source summaries after trimming. Equal non-empty text is one fact;
|
|
1425
|
+
* missing text stays missing rather than being invented.
|
|
1426
|
+
* @param a - source A summary, or null.
|
|
1427
|
+
* @param b - source B summary, or null.
|
|
1428
|
+
* @returns the presentation form.
|
|
1429
|
+
*/
|
|
1430
|
+
function summarizePair(a, b) {
|
|
1431
|
+
const left = a?.trim() ?? "";
|
|
1432
|
+
const right = b?.trim() ?? "";
|
|
1433
|
+
if (left === "" && right === "") return { kind: "missing" };
|
|
1434
|
+
if (left !== "" && left === right) return {
|
|
1435
|
+
kind: "same",
|
|
1436
|
+
text: left
|
|
1437
|
+
};
|
|
1438
|
+
return {
|
|
1439
|
+
kind: "different",
|
|
1440
|
+
a: left === "" ? null : left,
|
|
1441
|
+
b: right === "" ? null : right
|
|
1442
|
+
};
|
|
1417
1443
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
return (0, react.createElement)("div", { "data-role": "settings-card" }, (0, react.createElement)("h3", { "data-role": "settings-title" }, t("settings.title")), (0, react.createElement)("p", { "data-role": "settings-hint" }, t("settings.hint")), (0, react.createElement)(ReadOnlySetting, {
|
|
1424
|
-
section: "merge-approval",
|
|
1425
|
-
label: t("settings.mergeApproval"),
|
|
1426
|
-
value: mergeFallback,
|
|
1427
|
-
disabled: false,
|
|
1428
|
-
missing: false,
|
|
1429
|
-
note: t("settings.mergeApprovalPath"),
|
|
1430
|
-
link: true,
|
|
1431
|
-
t
|
|
1432
|
-
}), (0, react.createElement)(ReadOnlySetting, {
|
|
1433
|
-
section: "platform-write",
|
|
1434
|
-
label: t("settings.platformWrite"),
|
|
1435
|
-
value: platformFallback,
|
|
1436
|
-
disabled: platformCapability === false,
|
|
1437
|
-
missing: platform === void 0,
|
|
1438
|
-
note: t("settings.platformWriteLinkHint"),
|
|
1439
|
-
link: true,
|
|
1440
|
-
t
|
|
1441
|
-
}), (0, react.createElement)("p", { "data-role": "settings-readonly" }, t("settings.readonly")));
|
|
1444
|
+
//#endregion
|
|
1445
|
+
//#region src/client/MergeRow.ts
|
|
1446
|
+
/** One merge row: summary, stage, facts, primary decisions and the copy drawer. */
|
|
1447
|
+
function reasonText$1(t, reasonCode, fallback) {
|
|
1448
|
+
return reasonCode === void 0 ? fallback : t("history.reason." + reasonCode);
|
|
1442
1449
|
}
|
|
1443
|
-
function
|
|
1450
|
+
function summaryNode(row, t) {
|
|
1451
|
+
if (row.state !== "pending" || row.summary === void 0) return null;
|
|
1452
|
+
const pair = summarizePair(row.summary.a, row.summary.b);
|
|
1453
|
+
const slots = pair.kind === "same" ? [[t("row.summaryBoth"), pair.text]] : pair.kind === "missing" ? [[t("row.summaryBoth"), t("row.noSummary")]] : [[t("row.summaryA"), pair.a], [t("row.summaryB"), pair.b]];
|
|
1454
|
+
slots.push([t("row.rationale"), row.summary.rationale]);
|
|
1455
|
+
return (0, react.createElement)("div", { "data-role": "pending-summary" }, slots.map(([label, value]) => (0, react.createElement)("div", {
|
|
1456
|
+
key: label,
|
|
1457
|
+
"data-role": "pending-summary-slot"
|
|
1458
|
+
}, (0, react.createElement)("span", { "data-role": "pending-summary-label" }, label), (0, react.createElement)("span", { "data-role": "pending-summary-value" }, value === null || value === "" ? t("row.noSummary") : value))));
|
|
1459
|
+
}
|
|
1460
|
+
function stageNode(row, t) {
|
|
1461
|
+
const stage = row.stageView;
|
|
1462
|
+
if (stage === void 0) return null;
|
|
1463
|
+
const tone = stage.state === "lost" ? "fail" : stage.state === "expired" || stage.state === "reserved" ? "warn" : "neutral";
|
|
1464
|
+
const expiry = stage.expiresAt === void 0 ? "" : " · " + t("row.expires") + " " + new Date(stage.expiresAt).toLocaleString();
|
|
1465
|
+
return (0, react.createElement)("span", {
|
|
1466
|
+
"data-role": "stage-badge",
|
|
1467
|
+
"data-stage": stage.state,
|
|
1468
|
+
"data-tone": tone
|
|
1469
|
+
}, t("stage.state." + stage.state) + expiry);
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Render one row. Primary decisions stay visible; copy commands live in one details drawer.
|
|
1473
|
+
* @param props - row, translator, copier, sort hooks, body opener and decision callback.
|
|
1474
|
+
* @returns the row element.
|
|
1475
|
+
*/
|
|
1476
|
+
function MergeRow(props) {
|
|
1477
|
+
const { row, t, copy, sort, openBody, onDecide, busy } = props;
|
|
1444
1478
|
const first = [
|
|
1445
1479
|
row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
|
|
1446
1480
|
row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
|
|
1447
1481
|
row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
|
|
1448
|
-
row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText(t, row.reasonCode, row.reason ?? "")
|
|
1482
|
+
row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText$1(t, row.reasonCode, row.reason ?? "")
|
|
1449
1483
|
].filter((value) => value !== void 0).join(" · ");
|
|
1450
1484
|
const details = [
|
|
1451
1485
|
t("row.id") + " " + row.id,
|
|
@@ -1455,19 +1489,23 @@ window.__ModuleLoader__.load({
|
|
|
1455
1489
|
row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
|
|
1456
1490
|
row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2)
|
|
1457
1491
|
].filter((value) => value !== void 0).join(" · ");
|
|
1458
|
-
const actions = row.actions.
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1492
|
+
const actions = row.actions.filter((action) => action.id !== "copy" && action.id !== "discard").map((action) => {
|
|
1493
|
+
const decision = (action.id === "approve" || action.id === "reject") && onDecide !== void 0;
|
|
1494
|
+
return (0, react.createElement)("button", {
|
|
1495
|
+
key: action.id,
|
|
1496
|
+
type: "button",
|
|
1497
|
+
"data-role": decision ? "row-decision" : "row-action",
|
|
1498
|
+
"data-action": action.id,
|
|
1499
|
+
...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
|
|
1500
|
+
title: action.command,
|
|
1501
|
+
disabled: !action.enabled || busy,
|
|
1502
|
+
onClick: () => {
|
|
1503
|
+
if (!action.enabled || busy) return;
|
|
1504
|
+
if (decision) onDecide(row.id, action.id);
|
|
1505
|
+
else copy(action.command);
|
|
1506
|
+
}
|
|
1507
|
+
}, decision ? t("decision." + action.id) : t("action." + action.id));
|
|
1508
|
+
});
|
|
1471
1509
|
return (0, react.createElement)("div", {
|
|
1472
1510
|
key: row.id,
|
|
1473
1511
|
"data-role": "row",
|
|
@@ -1507,7 +1545,16 @@ window.__ModuleLoader__.load({
|
|
|
1507
1545
|
event.preventDefault();
|
|
1508
1546
|
sort.menu(row.id, event.clientX, event.clientY);
|
|
1509
1547
|
}
|
|
1510
|
-
}, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), stageNode(row, t), summaryNode(row, t), first === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, first), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details))), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, ((
|
|
1548
|
+
}, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), stageNode(row, t), summaryNode(row, t), first === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, first), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details))), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, (0, react.createElement)("button", {
|
|
1549
|
+
type: "button",
|
|
1550
|
+
"data-role": "row-sort",
|
|
1551
|
+
"aria-haspopup": "menu",
|
|
1552
|
+
title: t("sort.menu"),
|
|
1553
|
+
onClick: (event) => {
|
|
1554
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
1555
|
+
sort.menu(row.id, rect.left, rect.bottom);
|
|
1556
|
+
}
|
|
1557
|
+
}, t("sort.menu")), (() => {
|
|
1511
1558
|
const bodyA = row.bodies?.find((body) => body.name === row.a);
|
|
1512
1559
|
const bodyB = row.bodies?.find((body) => body.name === row.b);
|
|
1513
1560
|
return [bodyA === void 0 ? null : (0, react.createElement)("button", {
|
|
@@ -1529,7 +1576,151 @@ window.__ModuleLoader__.load({
|
|
|
1529
1576
|
openBody(bodyB.name, bodyB.side, bodyB.rowId);
|
|
1530
1577
|
}
|
|
1531
1578
|
}, "B · " + bodyB.name)];
|
|
1532
|
-
})(), actions)))
|
|
1579
|
+
})(), actions), (0, react.createElement)("details", { "data-role": "row-more" }, (0, react.createElement)("summary", null, t("row.more")), ...row.actions.map((action) => (0, react.createElement)("button", {
|
|
1580
|
+
key: "copy-" + action.id,
|
|
1581
|
+
type: "button",
|
|
1582
|
+
"data-role": "row-copy",
|
|
1583
|
+
"data-action": action.id + "-copy",
|
|
1584
|
+
title: action.command,
|
|
1585
|
+
disabled: busy,
|
|
1586
|
+
onClick: () => {
|
|
1587
|
+
if (!busy) copy(action.command);
|
|
1588
|
+
}
|
|
1589
|
+
}, t("row.copy") + " " + t("action." + action.id))))));
|
|
1590
|
+
}
|
|
1591
|
+
//#endregion
|
|
1592
|
+
//#region src/client/MergeSettings.ts
|
|
1593
|
+
/** The merge settings summary: two read-only state chips and one settings path. */
|
|
1594
|
+
function SettingChip({ section, label, value, disabled, missing, t }) {
|
|
1595
|
+
const text = missing ? t("settings.capabilityMissing") : disabled ? t("settings.capabilityDisabled") : value === true ? t("settings.on") : t("settings.off");
|
|
1596
|
+
const state = missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off";
|
|
1597
|
+
return (0, react.createElement)("span", {
|
|
1598
|
+
"data-role": "settings-section",
|
|
1599
|
+
"data-section": section,
|
|
1600
|
+
"data-disabled": missing || disabled ? "true" : "false",
|
|
1601
|
+
"data-state": state
|
|
1602
|
+
}, (0, react.createElement)("span", { "data-role": "settings-label" }, label), (0, react.createElement)("span", {
|
|
1603
|
+
"data-role": "settings-value",
|
|
1604
|
+
"data-value": state
|
|
1605
|
+
}, text));
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* Render the read-only approval state without the old large settings card.
|
|
1609
|
+
* @param props - the merge view and translator.
|
|
1610
|
+
* @returns two chips and the one current settings path.
|
|
1611
|
+
*/
|
|
1612
|
+
function MergeSettings({ view, t }) {
|
|
1613
|
+
const platform = view.approval.platformWrite;
|
|
1614
|
+
const platformCapability = platform?.capability === true;
|
|
1615
|
+
const platformFallback = platform?.enabled ?? false;
|
|
1616
|
+
return (0, react.createElement)("div", { "data-role": "settings-card" }, (0, react.createElement)("h3", { "data-role": "settings-title" }, t("settings.title")), (0, react.createElement)("p", { "data-role": "settings-path" }, t("settings.openPlatformPath")), (0, react.createElement)("div", { "data-role": "settings-chips" }, (0, react.createElement)(SettingChip, {
|
|
1617
|
+
section: "merge-approval",
|
|
1618
|
+
label: t("settings.mergeApproval"),
|
|
1619
|
+
value: view.approval.mergeApproval,
|
|
1620
|
+
disabled: false,
|
|
1621
|
+
missing: false,
|
|
1622
|
+
t
|
|
1623
|
+
}), (0, react.createElement)(SettingChip, {
|
|
1624
|
+
section: "platform-write",
|
|
1625
|
+
label: t("settings.platformWrite"),
|
|
1626
|
+
value: platformFallback,
|
|
1627
|
+
disabled: platformCapability === false,
|
|
1628
|
+
missing: platform === void 0,
|
|
1629
|
+
t
|
|
1630
|
+
})));
|
|
1631
|
+
}
|
|
1632
|
+
//#endregion
|
|
1633
|
+
//#region src/client/fallbacks.ts
|
|
1634
|
+
/** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
|
|
1635
|
+
const FALLBACK_COMMANDS = [
|
|
1636
|
+
"/evolution merge scan",
|
|
1637
|
+
"/evolution merge pair <a> <b>",
|
|
1638
|
+
"/evolution merge pending",
|
|
1639
|
+
"/evolution merge pending --expired",
|
|
1640
|
+
"/evolution merge skipped",
|
|
1641
|
+
"/evolution merge candidates",
|
|
1642
|
+
"/evolution merge status",
|
|
1643
|
+
"/evolution merge report",
|
|
1644
|
+
"/evolution doctor"
|
|
1645
|
+
];
|
|
1646
|
+
//#endregion
|
|
1647
|
+
//#region src/client/Panel.ts
|
|
1648
|
+
/** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
|
|
1649
|
+
const QUEUES = [
|
|
1650
|
+
"verify",
|
|
1651
|
+
"pending",
|
|
1652
|
+
"fuse",
|
|
1653
|
+
"skipped"
|
|
1654
|
+
];
|
|
1655
|
+
const DOCTOR_COMMAND = "/evolution doctor";
|
|
1656
|
+
function problemKey(problem) {
|
|
1657
|
+
switch (problem.code) {
|
|
1658
|
+
case "unauthenticated": return "failureUnauthorized";
|
|
1659
|
+
case "forbidden": return "failureForbidden";
|
|
1660
|
+
case "unmounted": return "failureUnmounted";
|
|
1661
|
+
case "capability": return "failureCapability";
|
|
1662
|
+
case "body-ambiguous": return "failureBodyAmbiguous";
|
|
1663
|
+
case "wire-version": return "failureWireVersion";
|
|
1664
|
+
case "body-missing": return "failureBodyMissing";
|
|
1665
|
+
case "bad-query": return "failureBadQuery";
|
|
1666
|
+
case "refused": return "failureRefused";
|
|
1667
|
+
case "server": return "failureServer";
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
function reasonText(t, reasonCode, fallback) {
|
|
1671
|
+
if (reasonCode === void 0) return fallback;
|
|
1672
|
+
const key = "history.reason." + reasonCode;
|
|
1673
|
+
const text = t(key);
|
|
1674
|
+
return text === key ? fallback : text;
|
|
1675
|
+
}
|
|
1676
|
+
function blockedReasonKey(failure) {
|
|
1677
|
+
const code = failure.readiness.code ?? failure.code;
|
|
1678
|
+
if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
|
|
1679
|
+
if (code.endsWith("forward-only")) return "blocked.reason.forward-only";
|
|
1680
|
+
if (code.endsWith("migration-blocked")) return "blocked.reason.migration-blocked";
|
|
1681
|
+
return "blocked.reason.unknown";
|
|
1682
|
+
}
|
|
1683
|
+
function failureNode(t, failure, fallback, copy) {
|
|
1684
|
+
if (isBlocked(failure)) {
|
|
1685
|
+
const raw = failure.readiness.message;
|
|
1686
|
+
return (0, react.createElement)("div", {
|
|
1687
|
+
"data-role": "blocked",
|
|
1688
|
+
role: "alert",
|
|
1689
|
+
"data-block-code": failure.code,
|
|
1690
|
+
"data-readiness-code": failure.readiness.code ?? ""
|
|
1691
|
+
}, (0, react.createElement)("h3", { "data-role": "blocked-title" }, t("blocked.title")), (0, react.createElement)("p", {
|
|
1692
|
+
"data-role": "blocked-reason",
|
|
1693
|
+
title: raw ?? t(blockedReasonKey(failure))
|
|
1694
|
+
}, t(blockedReasonKey(failure))), (0, react.createElement)("p", { "data-role": "blocked-hint" }, t("blocked.hint")), raw === void 0 ? null : (0, react.createElement)("details", { "data-role": "blocked-detail" }, (0, react.createElement)("summary", null, t("blocked.detail")), (0, react.createElement)("p", { "data-role": "blocked-raw" }, raw)), (0, react.createElement)("div", { "data-role": "blocked-actions" }, (0, react.createElement)("button", {
|
|
1695
|
+
type: "button",
|
|
1696
|
+
"data-role": "doctor",
|
|
1697
|
+
onClick: () => {
|
|
1698
|
+
copy(DOCTOR_COMMAND);
|
|
1699
|
+
}
|
|
1700
|
+
}, t("blocked.doctor")), (0, react.createElement)("button", {
|
|
1701
|
+
type: "button",
|
|
1702
|
+
"data-role": "refresh",
|
|
1703
|
+
onClick: () => {
|
|
1704
|
+
window.dispatchEvent(new Event("evolution-merge:refresh"));
|
|
1705
|
+
}
|
|
1706
|
+
}, t("refresh"))));
|
|
1707
|
+
}
|
|
1708
|
+
const capability = failure.code === "capability";
|
|
1709
|
+
return (0, react.createElement)("div", {
|
|
1710
|
+
"data-role": capability ? "capability-failure" : "failure",
|
|
1711
|
+
"data-error-code": failure.code,
|
|
1712
|
+
"data-status": String(failure.status)
|
|
1713
|
+
}, (0, react.createElement)("p", {
|
|
1714
|
+
"data-role": "failure-title",
|
|
1715
|
+
title: failure.message
|
|
1716
|
+
}, t(problemKey(failure))), capability ? (0, react.createElement)("p", { "data-role": "capability-hint" }, t("failureCapabilityHint")) : null, failure.message === void 0 ? null : (0, react.createElement)("details", { "data-role": "failure-detail" }, (0, react.createElement)("summary", null, t("failureDetails")), (0, react.createElement)("p", { "data-role": "failure-raw" }, failure.message)), fallback.length === 0 ? null : (0, react.createElement)("div", { "data-role": "fallback" }, fallback.map((command) => (0, react.createElement)("button", {
|
|
1717
|
+
key: command,
|
|
1718
|
+
type: "button",
|
|
1719
|
+
"data-role": "fallback-command",
|
|
1720
|
+
onClick: () => {
|
|
1721
|
+
copy(command);
|
|
1722
|
+
}
|
|
1723
|
+
}, command))));
|
|
1533
1724
|
}
|
|
1534
1725
|
const overlayKey = (event, onClose) => {
|
|
1535
1726
|
if (event.key === "Escape") {
|
|
@@ -1608,7 +1799,7 @@ window.__ModuleLoader__.load({
|
|
|
1608
1799
|
const kind = t("history.kind." + item.kind);
|
|
1609
1800
|
const reason = reasonText(t, item.reasonCode, item.reason);
|
|
1610
1801
|
return kind === reason ? kind : kind + " · " + reason;
|
|
1611
|
-
})() + " · v" + String(item.lastVersion) + " · " + t("history.archivedAt", { date:
|
|
1802
|
+
})() + " · v" + String(item.lastVersion) + " · " + (item.archivedAt === null ? t("history.archivedAtUnknown") : t("history.archivedAt", { date: formatLocalDateTime(item.archivedAt) ?? t("history.archivedAtUnknown") }))))) : null);
|
|
1612
1803
|
return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
|
|
1613
1804
|
open: true,
|
|
1614
1805
|
onClose,
|
|
@@ -1624,6 +1815,8 @@ window.__ModuleLoader__.load({
|
|
|
1624
1815
|
const [copyState, setCopyState] = (0, react.useState)({ kind: "idle" });
|
|
1625
1816
|
const [orders, setOrders] = (0, react.useState)({});
|
|
1626
1817
|
const [dragId, setDragId] = (0, react.useState)(void 0);
|
|
1818
|
+
const [deciding, setDeciding] = (0, react.useState)(void 0);
|
|
1819
|
+
const [decisionNotice, setDecisionNotice] = (0, react.useState)(void 0);
|
|
1627
1820
|
const [menu, setMenu] = (0, react.useState)(void 0);
|
|
1628
1821
|
const returnFocus = (0, react.useRef)(null);
|
|
1629
1822
|
const captureFocus = () => {
|
|
@@ -1645,6 +1838,27 @@ window.__ModuleLoader__.load({
|
|
|
1645
1838
|
};
|
|
1646
1839
|
const workspaceId = resolveWorkspaceId(face.workspaceId);
|
|
1647
1840
|
const orderStore = (0, react.useMemo)(() => createRowOrderStore(), []);
|
|
1841
|
+
const decideRow = (rowId, verb) => {
|
|
1842
|
+
if (face.decide === void 0) return;
|
|
1843
|
+
setDeciding(rowId);
|
|
1844
|
+
(async () => {
|
|
1845
|
+
try {
|
|
1846
|
+
setDecisionNotice({
|
|
1847
|
+
rowId,
|
|
1848
|
+
...await face.decide(rowId, verb)
|
|
1849
|
+
});
|
|
1850
|
+
store.refresh();
|
|
1851
|
+
} catch (error) {
|
|
1852
|
+
setDecisionNotice({
|
|
1853
|
+
rowId,
|
|
1854
|
+
ok: false,
|
|
1855
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1856
|
+
});
|
|
1857
|
+
} finally {
|
|
1858
|
+
setDeciding(void 0);
|
|
1859
|
+
}
|
|
1860
|
+
})();
|
|
1861
|
+
};
|
|
1648
1862
|
const copy = (command) => {
|
|
1649
1863
|
(async () => {
|
|
1650
1864
|
try {
|
|
@@ -1694,15 +1908,15 @@ window.__ModuleLoader__.load({
|
|
|
1694
1908
|
const viewFailure = snapshot.viewState === "error" ? snapshot.viewFailure : void 0;
|
|
1695
1909
|
const queue = snapshot.current;
|
|
1696
1910
|
const uiState = blocked !== void 0 ? "blocked" : viewFailure !== void 0 ? "error" : snapshot.viewState === "loading" ? "loading" : queue.status === "error" ? "error" : queue.status === "loading" ? "loading" : queue.status === "empty" ? "empty" : "ready";
|
|
1697
|
-
const orderKey = rowOrderKey(workspaceId, snapshot.queue);
|
|
1698
|
-
const visibleOrder = orders[orderKey] ?? orderStore.read(workspaceId, snapshot.queue);
|
|
1911
|
+
const orderKey = rowOrderKey(workspaceId, snapshot.queue, snapshot.expiredOnly);
|
|
1912
|
+
const visibleOrder = orders[orderKey] ?? orderStore.read(workspaceId, snapshot.queue, snapshot.expiredOnly);
|
|
1699
1913
|
const visibleRows = orderRows(queue.rows, visibleOrder);
|
|
1700
1914
|
const saveOrder = (ids) => {
|
|
1701
1915
|
setOrders((previous) => ({
|
|
1702
1916
|
...previous,
|
|
1703
1917
|
[orderKey]: ids
|
|
1704
1918
|
}));
|
|
1705
|
-
orderStore.write(workspaceId, snapshot.queue, ids);
|
|
1919
|
+
orderStore.write(workspaceId, snapshot.queue, ids, snapshot.expiredOnly);
|
|
1706
1920
|
};
|
|
1707
1921
|
const sortHandlers = {
|
|
1708
1922
|
start: (id) => {
|
|
@@ -1774,11 +1988,23 @@ window.__ModuleLoader__.load({
|
|
|
1774
1988
|
store.toggleExpired();
|
|
1775
1989
|
}
|
|
1776
1990
|
}, t("tab.expired") + " " + String(view.expired)) : null;
|
|
1991
|
+
const notice = decisionNotice === void 0 ? null : (0, react.createElement)("p", {
|
|
1992
|
+
"data-role": "decision-notice",
|
|
1993
|
+
"data-ok": decisionNotice.ok ? "true" : "false"
|
|
1994
|
+
}, decisionNotice.message);
|
|
1777
1995
|
const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", {
|
|
1778
1996
|
"data-role": "rows",
|
|
1779
1997
|
id: "merge-rows",
|
|
1780
1998
|
role: "tabpanel"
|
|
1781
|
-
}, visibleRows.map((row) =>
|
|
1999
|
+
}, visibleRows.map((row) => (0, react.createElement)(MergeRow, {
|
|
2000
|
+
row,
|
|
2001
|
+
t,
|
|
2002
|
+
copy,
|
|
2003
|
+
sort: sortHandlers,
|
|
2004
|
+
openBody,
|
|
2005
|
+
onDecide: face.decide === void 0 ? void 0 : decideRow,
|
|
2006
|
+
busy: deciding === row.id
|
|
2007
|
+
}))) : null;
|
|
1782
2008
|
const empty = uiState === "empty" ? (0, react.createElement)("div", null, (0, react.createElement)("p", { "data-role": "note" }, t("empty." + snapshot.queue)), (0, react.createElement)("p", { "data-role": "empty-scope" }, t("empty.scope"))) : null;
|
|
1783
2009
|
const staleViewFailure = snapshot.viewState === "ready" ? snapshot.viewFailure : void 0;
|
|
1784
2010
|
const activeFailure = viewFailure !== void 0 ? viewFailure : staleViewFailure !== void 0 ? staleViewFailure : queue.status === "error" ? queue.failure : void 0;
|
|
@@ -1856,19 +2082,29 @@ window.__ModuleLoader__.load({
|
|
|
1856
2082
|
onClick: () => {
|
|
1857
2083
|
copy(view.startCommands.pair);
|
|
1858
2084
|
}
|
|
1859
|
-
}, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(
|
|
2085
|
+
}, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(MergeSettings, {
|
|
1860
2086
|
view,
|
|
1861
2087
|
t
|
|
1862
|
-
}),
|
|
2088
|
+
}), (0, react.createElement)(MergeRunStrips, {
|
|
2089
|
+
view,
|
|
2090
|
+
t,
|
|
2091
|
+
copy
|
|
2092
|
+
}), tabs, expiredToggle, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
|
|
1863
2093
|
"data-role": "note",
|
|
1864
2094
|
role: "status"
|
|
1865
2095
|
}, t("refreshing")) : null, activeFailure === void 0 ? null : failureNode(t, activeFailure, fallback, copy), copyState.kind === "idle" ? null : copyState.kind === "copied" ? (0, react.createElement)("p", {
|
|
1866
2096
|
"data-role": "copied",
|
|
1867
2097
|
role: "status"
|
|
1868
|
-
}, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("
|
|
2098
|
+
}, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("div", {
|
|
1869
2099
|
"data-role": "copy-failed",
|
|
1870
2100
|
role: "alert"
|
|
1871
|
-
}, t("copyFailed")),
|
|
2101
|
+
}, (0, react.createElement)("p", null, t("copyFailed")), copyState.text === void 0 ? null : (0, react.createElement)("code", { "data-role": "copy-failed-command" }, copyState.text), (0, react.createElement)("button", {
|
|
2102
|
+
type: "button",
|
|
2103
|
+
"data-role": "copy-retry",
|
|
2104
|
+
onClick: () => {
|
|
2105
|
+
if (copyState.text !== void 0) copy(copyState.text);
|
|
2106
|
+
}
|
|
2107
|
+
}, t("copyRetry"))), notice, list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
|
|
1872
2108
|
type: "button",
|
|
1873
2109
|
"data-role": "load-more",
|
|
1874
2110
|
disabled: queue.status === "loading",
|
|
@@ -2253,8 +2489,19 @@ window.__ModuleLoader__.load({
|
|
|
2253
2489
|
}
|
|
2254
2490
|
};
|
|
2255
2491
|
//#endregion
|
|
2492
|
+
//#region src/client/session.ts
|
|
2493
|
+
function currentSessionId(ctx) {
|
|
2494
|
+
const byId = ctx.sessions?.list?.getSnapshot?.().byId;
|
|
2495
|
+
if (byId === void 0) return void 0;
|
|
2496
|
+
return Object.values(byId).find((session) => (session.retainedBy?.mainView ?? 0) > 0)?.id;
|
|
2497
|
+
}
|
|
2498
|
+
//#endregion
|
|
2256
2499
|
//#region src/client/index.ts
|
|
2257
|
-
const inject = [
|
|
2500
|
+
const inject = [
|
|
2501
|
+
"slots",
|
|
2502
|
+
"sessions",
|
|
2503
|
+
"locale"
|
|
2504
|
+
];
|
|
2258
2505
|
/** The view component receives the workbench's activeView marker alongside its face. */
|
|
2259
2506
|
function MergeView(props) {
|
|
2260
2507
|
return props.activeView === "merge" ? MergePanel(props) : null;
|
|
@@ -2264,6 +2511,10 @@ window.__ModuleLoader__.load({
|
|
|
2264
2511
|
* @param ctx - the client context.
|
|
2265
2512
|
*/
|
|
2266
2513
|
function apply(ctx) {
|
|
2514
|
+
const seat = createSettingsSeat(ctx);
|
|
2515
|
+
ctx.effect(() => () => {
|
|
2516
|
+
seat.dispose?.();
|
|
2517
|
+
}, "evolution-merge-ui: settings seat");
|
|
2267
2518
|
registerWorkbenchView(ctx, {
|
|
2268
2519
|
id: PANEL_ID,
|
|
2269
2520
|
order: 45,
|
|
@@ -2284,7 +2535,6 @@ window.__ModuleLoader__.load({
|
|
|
2284
2535
|
body,
|
|
2285
2536
|
history
|
|
2286
2537
|
};
|
|
2287
|
-
const seat = createSettingsSeat(ctx);
|
|
2288
2538
|
const workspaceId = panelWorkspaceId(ctx);
|
|
2289
2539
|
return {
|
|
2290
2540
|
store: new MergeStore(source),
|
|
@@ -2294,6 +2544,20 @@ window.__ModuleLoader__.load({
|
|
|
2294
2544
|
...seat.get("evolution-merge") === void 0 ? {} : { merge: seat.get("evolution-merge") },
|
|
2295
2545
|
...seat.get("evolution-approval") === void 0 ? {} : { platform: seat.get("evolution-approval") }
|
|
2296
2546
|
},
|
|
2547
|
+
decide: async (rowId, verb) => {
|
|
2548
|
+
const sessionId = currentSessionId(ctx);
|
|
2549
|
+
if (sessionId === void 0) return {
|
|
2550
|
+
ok: false,
|
|
2551
|
+
message: "没有当前会话;请打开会话后重试"
|
|
2552
|
+
};
|
|
2553
|
+
const result = await decide(rowId, verb, sessionId);
|
|
2554
|
+
if (result.ok) return result.payload;
|
|
2555
|
+
const failure = result.failure;
|
|
2556
|
+
return {
|
|
2557
|
+
ok: false,
|
|
2558
|
+
message: "message" in failure && typeof failure.message === "string" ? failure.message : "审批请求失败"
|
|
2559
|
+
};
|
|
2560
|
+
},
|
|
2297
2561
|
...workspaceId === void 0 ? {} : { workspaceId }
|
|
2298
2562
|
};
|
|
2299
2563
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** One merge row: summary, stage, facts, primary decisions and the copy drawer. */
|
|
2
|
+
import { type DragEvent, type ReactNode } from 'react';
|
|
3
|
+
import type { MergeRowView } from './api.ts';
|
|
4
|
+
type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
|
|
5
|
+
export interface RowSortHandlers {
|
|
6
|
+
start(id: string): void;
|
|
7
|
+
over(event: DragEvent<HTMLDivElement>): void;
|
|
8
|
+
drop(id: string): void;
|
|
9
|
+
end(): void;
|
|
10
|
+
menu(id: string, x: number, y: number): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render one row. Primary decisions stay visible; copy commands live in one details drawer.
|
|
14
|
+
* @param props - row, translator, copier, sort hooks, body opener and decision callback.
|
|
15
|
+
* @returns the row element.
|
|
16
|
+
*/
|
|
17
|
+
export declare function MergeRow(props: {
|
|
18
|
+
row: MergeRowView;
|
|
19
|
+
t: Translate;
|
|
20
|
+
copy: (command: string) => void;
|
|
21
|
+
sort: RowSortHandlers;
|
|
22
|
+
openBody: (name: string, side: 'live' | 'archive', rowId: string | undefined) => void;
|
|
23
|
+
onDecide: ((rowId: string, verb: 'approve' | 'reject') => void) | undefined;
|
|
24
|
+
busy: boolean;
|
|
25
|
+
}): ReactNode;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=MergeRow.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The merge run strips: active/last identity, state, progress, report and details. */
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { MergeViewPayload } from './api.ts';
|
|
4
|
+
type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Render active and last runs as two independently labelled strips.
|
|
7
|
+
* @param props - view, translator and command copier.
|
|
8
|
+
* @returns the strips, or null when neither run exists.
|
|
9
|
+
*/
|
|
10
|
+
export declare function MergeRunStrips({ view, t, copy }: {
|
|
11
|
+
view: MergeViewPayload;
|
|
12
|
+
t: Translate;
|
|
13
|
+
copy: (command: string) => void;
|
|
14
|
+
}): ReactNode;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=MergeRunStrip.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The merge settings summary: two read-only state chips and one settings path. */
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
3
|
+
import type { MergeViewPayload } from './api.ts';
|
|
4
|
+
type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Render the read-only approval state without the old large settings card.
|
|
7
|
+
* @param props - the merge view and translator.
|
|
8
|
+
* @returns two chips and the one current settings path.
|
|
9
|
+
*/
|
|
10
|
+
export declare function MergeSettings({ view, t }: {
|
|
11
|
+
view: MergeViewPayload;
|
|
12
|
+
t: Translate;
|
|
13
|
+
}): ReactNode;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=MergeSettings.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import type { MergeStore } from './MergeStore.ts';
|
|
4
|
-
import type
|
|
4
|
+
import { type SettingsFace } from '@lmzhen/dsh-evolution-client-kit';
|
|
5
5
|
export type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
|
|
6
6
|
export interface MergeFace {
|
|
7
7
|
store: MergeStore;
|
|
@@ -9,6 +9,10 @@ export interface MergeFace {
|
|
|
9
9
|
doctorCommand?: string;
|
|
10
10
|
settings?: SettingsFace;
|
|
11
11
|
workspaceId?: string;
|
|
12
|
+
decide?: (rowId: string, verb: 'approve' | 'reject') => Promise<{
|
|
13
|
+
ok: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}>;
|
|
12
16
|
}
|
|
13
17
|
export declare function MergePanel(face: MergeFace): ReactNode;
|
|
14
18
|
//# sourceMappingURL=Panel.d.ts.map
|
|
@@ -65,7 +65,8 @@ export interface MergeViewPayload {
|
|
|
65
65
|
};
|
|
66
66
|
notes: string[];
|
|
67
67
|
degraded: boolean;
|
|
68
|
-
|
|
68
|
+
activeRun?: MergeRunView;
|
|
69
|
+
lastRun?: MergeRunView;
|
|
69
70
|
approval: MergeApproval;
|
|
70
71
|
}
|
|
71
72
|
export interface RowActionView {
|
|
@@ -143,7 +144,7 @@ export interface MergeHistoryItem {
|
|
|
143
144
|
reason: string;
|
|
144
145
|
reasonCode?: string;
|
|
145
146
|
lastVersion: number;
|
|
146
|
-
archivedAt: string;
|
|
147
|
+
archivedAt: string | null;
|
|
147
148
|
sourceRunId?: string;
|
|
148
149
|
}
|
|
149
150
|
export interface MergeStateReadiness {
|
|
@@ -177,6 +178,13 @@ export declare const isBlocked: (failure: MergeFailure) => failure is MergeBlock
|
|
|
177
178
|
export declare function view(): Promise<MergeOutcome<MergeViewPayload>>;
|
|
178
179
|
export declare function rows(queue: MergeQueue, cursor?: string, expiredOnly?: boolean): Promise<MergeOutcome<QueuePage>>;
|
|
179
180
|
export declare function body(name: string, side: 'live' | 'archive', rowId?: string): Promise<MergeOutcome<MergeBody>>;
|
|
181
|
+
export interface MergeDecisionOutcome {
|
|
182
|
+
readonly ok: boolean;
|
|
183
|
+
readonly message: string;
|
|
184
|
+
readonly code?: string;
|
|
185
|
+
readonly runId?: string;
|
|
186
|
+
}
|
|
187
|
+
export declare function decide(rowId: string, verb: 'approve' | 'reject', sessionId: string): Promise<MergeOutcome<MergeDecisionOutcome>>;
|
|
180
188
|
export declare function history(): Promise<MergeOutcome<{
|
|
181
189
|
items: MergeHistoryItem[];
|
|
182
190
|
}>>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Pure presentation decisions for one merge row; no network or store state. */
|
|
2
|
+
export type PairSummary = {
|
|
3
|
+
readonly kind: 'same';
|
|
4
|
+
readonly text: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: 'different';
|
|
7
|
+
readonly a: string | null;
|
|
8
|
+
readonly b: string | null;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'missing';
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Compare the two source summaries after trimming. Equal non-empty text is one fact;
|
|
14
|
+
* missing text stays missing rather than being invented.
|
|
15
|
+
* @param a - source A summary, or null.
|
|
16
|
+
* @param b - source B summary, or null.
|
|
17
|
+
* @returns the presentation form.
|
|
18
|
+
*/
|
|
19
|
+
export declare function summarizePair(a: string | null | undefined, b: string | null | undefined): PairSummary;
|
|
20
|
+
//# sourceMappingURL=merge-presentation.d.ts.map
|
|
@@ -7,12 +7,12 @@ export interface StorageLike {
|
|
|
7
7
|
export type RowOrderEdge = 'top' | 'bottom';
|
|
8
8
|
/** Workspace scope for one tab. A missing panel workspace is the local scope. */
|
|
9
9
|
export declare function resolveWorkspaceId(workspaceId: string | null | undefined): string;
|
|
10
|
-
/** Stable localStorage key for one workspace and
|
|
11
|
-
export declare function rowOrderKey(workspaceId: string | null | undefined, queue: MergeQueue): string;
|
|
10
|
+
/** Stable localStorage key for one workspace, queue and expired filter. */
|
|
11
|
+
export declare function rowOrderKey(workspaceId: string | null | undefined, queue: MergeQueue, expiredOnly?: boolean): string;
|
|
12
12
|
/** One tab's persisted order; failures degrade to an empty (wire) order. */
|
|
13
13
|
export declare function createRowOrderStore(storage?: StorageLike | undefined): {
|
|
14
|
-
read(workspaceId: string | null | undefined, queue: MergeQueue): readonly string[];
|
|
15
|
-
write(workspaceId: string | null | undefined, queue: MergeQueue, ids: readonly string[]): void;
|
|
14
|
+
read(workspaceId: string | null | undefined, queue: MergeQueue, expiredOnly?: boolean): readonly string[];
|
|
15
|
+
write(workspaceId: string | null | undefined, queue: MergeQueue, ids: readonly string[], expiredOnly?: boolean): void;
|
|
16
16
|
};
|
|
17
17
|
/** Apply persisted ids first, then append unknown rows in their wire order. */
|
|
18
18
|
export declare function orderRows<T extends {
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
]
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"version": "0.
|
|
14
|
+
"version": "0.35.0",
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react-dom": "^18.2.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@lmzhen/dsh-evolution-client-kit": "^0.
|
|
54
|
+
"@lmzhen/dsh-evolution-client-kit": "^0.35.0"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
57
|
"dsh": ">=0.2.1-rc.2 <0.2.2-0"
|