@lmzhen/dsh-evolution-merge-ui 0.34.0 → 0.36.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 CHANGED
@@ -272,6 +272,144 @@ window.__ModuleLoader__.load({
272
272
  } catch {}
273
273
  }
274
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
294
+ //#region ../evolution-client-kit/src/notice.ts
295
+ /** One inline state notice: semantic tone plus text, no surface-specific styling. */
296
+ //#endregion
297
+ //#region ../evolution-client-kit/src/presentation/decision-frame.ts
298
+ /** One decision card: title, facts, at most one primary, secondary actions and details. */
299
+ function actionButton(action, role) {
300
+ return (0, react.createElement)("button", {
301
+ key: action.id,
302
+ type: "button",
303
+ "data-role": role === "primary" ? "decision-primary" : "decision-secondary",
304
+ "data-action": action.id,
305
+ "aria-busy": action.busy === true ? "true" : void 0,
306
+ ...action.attributes ?? {},
307
+ disabled: action.disabled === true || action.busy === true,
308
+ title: action.title ?? action.label,
309
+ onClick: () => {
310
+ if (action.disabled !== true && action.busy !== true) action.onSelect();
311
+ }
312
+ }, action.label);
313
+ }
314
+ /**
315
+ * Render the one-card decision surface. The model has a singular primary on purpose.
316
+ * @param props - decision model and optional surface class.
317
+ * @returns the decision frame.
318
+ */
319
+ function DecisionFrame({ decision, className }) {
320
+ const secondary = decision.secondary ?? [];
321
+ return (0, react.createElement)("section", {
322
+ "data-role": "decision-frame",
323
+ ...className === void 0 ? {} : { className }
324
+ }, (0, react.createElement)("h2", { "data-role": "decision-title" }, decision.title), decision.summary === void 0 || decision.summary === "" ? null : (0, react.createElement)("p", { "data-role": "decision-summary" }, decision.summary), decision.facts.length === 0 ? null : (0, react.createElement)("dl", { "data-role": "decision-facts" }, decision.facts.map((fact) => (0, react.createElement)("div", {
325
+ key: fact.label,
326
+ "data-role": "decision-fact"
327
+ }, (0, react.createElement)("dt", null, fact.label), (0, react.createElement)("dd", null, fact.value)))), decision.details === void 0 ? null : (0, react.createElement)("div", { "data-role": "decision-details" }, decision.details), decision.primary === void 0 && secondary.length === 0 ? null : (0, react.createElement)("div", { "data-role": "decision-actions" }, decision.primary === void 0 ? null : actionButton(decision.primary, "primary"), secondary.map((action) => actionButton(action, "secondary"))));
328
+ }
329
+ //#endregion
330
+ //#region ../evolution-client-kit/src/presentation/task-banner.ts
331
+ /** A read-only task hint; it deliberately has no primary action. */
332
+ /**
333
+ * Render a task count plus one sentence. The absence of an action prop is the contract.
334
+ * @param props - count, sentence and optional surface class.
335
+ * @returns the banner.
336
+ */
337
+ function TaskBanner({ count, text, className }) {
338
+ return (0, react.createElement)("div", {
339
+ "data-role": "task-banner",
340
+ "data-count": String(count),
341
+ role: "status",
342
+ ...className === void 0 ? {} : { className }
343
+ }, (0, react.createElement)("strong", { "data-role": "task-banner-count" }, String(count)), (0, react.createElement)("span", { "data-role": "task-banner-text" }, text));
344
+ }
345
+ //#endregion
346
+ //#region ../evolution-client-kit/src/presentation/chips.ts
347
+ /** Read-only status chips and toggleable filter chips. */
348
+ //#endregion
349
+ //#region ../evolution-client-kit/src/presentation/empty-state.ts
350
+ /** A compact completed/empty state with at most one primary next step. */
351
+ //#endregion
352
+ //#region ../evolution-client-kit/src/presentation/disclosure-group.ts
353
+ /** A native details group for normal/secondary facts. */
354
+ //#endregion
355
+ //#region ../evolution-client-kit/src/presentation/time-text.ts
356
+ /** The one local-time presentation used by reading surfaces. */
357
+ //#endregion
358
+ //#region ../evolution-client-kit/src/presentation/action-menu.ts
359
+ /**
360
+ * The one seam onto the platform's menu material, plus a small fallback for deployments
361
+ * without the primitive row. No page owns a second menu implementation.
362
+ */
363
+ const PLATFORM_UI_MODULE$1 = "@deepseek-ai/dsh-client-ui-primitives";
364
+ /**
365
+ * Render the platform menu directly. Kept exported for callers that already own the primitive row.
366
+ * @param primitives - platform MenuSurface/MenuItemButton pair.
367
+ * @param actions - rows in display order.
368
+ * @param label - accessible menu name.
369
+ * @param style - fixed position computed from the pointer.
370
+ * @param className - optional surface class.
371
+ * @returns the menu element.
372
+ */
373
+ function renderPlatformMenu(primitives, actions, label, style, className) {
374
+ return (0, react.createElement)(primitives.MenuSurface, {
375
+ "data-role": "action-menu",
376
+ "data-platform": "true",
377
+ role: "menu",
378
+ "aria-label": label,
379
+ ...className === void 0 ? {} : { className },
380
+ style
381
+ }, actions.map((action) => (0, react.createElement)(primitives.MenuItemButton, {
382
+ key: action.id,
383
+ children: action.label,
384
+ ...action.separatorBefore === true ? { separatorBefore: true } : {},
385
+ ...action.disabled === true ? { disabled: true } : {},
386
+ ...action.danger === true ? { danger: true } : {},
387
+ onSelect: action.onSelect
388
+ })));
389
+ }
390
+ /**
391
+ * Accept only the two component functions; a namespace row without them is not a menu.
392
+ * @param row - the module row's exports.
393
+ * @returns the two components, or undefined.
394
+ */
395
+ function menuPrimitivesFrom(row) {
396
+ const surface = row.MenuSurface;
397
+ const item = row.MenuItemButton;
398
+ if (typeof surface !== "function" || typeof item !== "function") return void 0;
399
+ return {
400
+ MenuSurface: surface,
401
+ MenuItemButton: item
402
+ };
403
+ }
404
+ /** The module-table row, when this deployment carries it. */
405
+ const PLATFORM_MENU = (() => {
406
+ try {
407
+ return menuPrimitivesFrom(require(PLATFORM_UI_MODULE$1));
408
+ } catch {
409
+ return;
410
+ }
411
+ })();
412
+ //#endregion
275
413
  //#region src/client/api.ts
276
414
  /** Read-only merge wire client. Classifies every failure and never writes. */
277
415
  const HOST_ROUTES = ROUTES.merge;
@@ -628,7 +766,7 @@ window.__ModuleLoader__.load({
628
766
  reason: item.reason,
629
767
  ...item.reasonCode === void 0 ? {} : { reasonCode: item.reasonCode },
630
768
  lastVersion: item.lastVersion,
631
- archivedAt: new Date(item.archivedAt).toISOString(),
769
+ archivedAt: item.archivedAt === null ? null : new Date(item.archivedAt).toISOString(),
632
770
  ...item.sourceRunId === void 0 ? {} : { sourceRunId: item.sourceRunId }
633
771
  })) }
634
772
  } : answer;
@@ -641,15 +779,18 @@ window.__ModuleLoader__.load({
641
779
  const NS = "evolution-merge";
642
780
  const zh = {
643
781
  "entry.label": "技能合并",
644
- "title": "技能合并",
782
+ "title": "合并建议",
783
+ "task.pending": "条合并建议待处理",
645
784
  "start.scan": "复制扫描命令",
646
785
  "start.pair": "复制配对命令模板",
647
786
  "degraded": "读模型降级:",
648
787
  "copied": "已复制:",
649
788
  "copyFailed": "复制失败:浏览器没有接受剪贴板写入。",
789
+ "copyRetry": "重试复制",
650
790
  "row.noSummary": "无摘要",
791
+ "row.summaryBoth": "双方摘要",
651
792
  "overlay.body": "正文",
652
- "hint": "手动触发;后台逐对判定。这里只读,不写技能。",
793
+ "hint": "先处理需要判断的合并,其他信息按需展开。",
653
794
  "history": "历史",
654
795
  "close": "关闭",
655
796
  "tab.verify": "待判断",
@@ -691,6 +832,9 @@ window.__ModuleLoader__.load({
691
832
  "row.failure": "失败",
692
833
  "row.noRetry": "不可重试",
693
834
  "row.copy": "复制命令",
835
+ "row.more": "更多命令",
836
+ "row.viewBoth": "查看双方",
837
+ "overlay.pair": "查看双方",
694
838
  "tab.expired": "已过期",
695
839
  "row.targetCreated": "新建",
696
840
  "row.verify": "判定状态",
@@ -730,6 +874,7 @@ window.__ModuleLoader__.load({
730
874
  "disabled.unavailable": "暂不可用",
731
875
  "history.empty": "没有历史。",
732
876
  "history.archivedAt": "归档于 {date}",
877
+ "history.archivedAtUnknown": "归档时间未知",
733
878
  "failureServer": "读取失败",
734
879
  "failureUnauthorized": "未登录",
735
880
  "failureForbidden": "无权访问",
@@ -808,6 +953,9 @@ window.__ModuleLoader__.load({
808
953
  "settings.capabilityDisabled": "当前部署不可用",
809
954
  "settings.readonly": "只读显示;修改请到平台设置页。",
810
955
  "run.title": "运行",
956
+ "run.progressUnknown": "进度未知",
957
+ "run.current": "当前运行",
958
+ "run.last": "上次完成",
811
959
  "run.id": "运行编号",
812
960
  "run.state": "状态",
813
961
  "run.stage": "阶段",
@@ -843,15 +991,18 @@ window.__ModuleLoader__.load({
843
991
  });
844
992
  const en = {
845
993
  "entry.label": "Skill merge",
846
- "title": "Skill merge",
994
+ "title": "Merge suggestions",
995
+ "task.pending": "merge suggestions need attention",
847
996
  "start.scan": "Copy scan command",
848
997
  "start.pair": "Copy pair command template",
849
998
  "degraded": "Degraded read model:",
850
999
  "copied": "Copied:",
851
1000
  "copyFailed": "Copy failed: the browser did not accept the clipboard write.",
1001
+ "copyRetry": "Retry copy",
852
1002
  "row.noSummary": "No summary",
1003
+ "row.summaryBoth": "Shared summary",
853
1004
  "overlay.body": "Body",
854
- "hint": "Manual only; pairs are judged in the background. Read-only here.",
1005
+ "hint": "Resolve the merges that need a decision first; everything else stays folded.",
855
1006
  "history": "History",
856
1007
  "close": "Close",
857
1008
  "tab.verify": "To judge",
@@ -893,6 +1044,9 @@ window.__ModuleLoader__.load({
893
1044
  "row.failure": "Failure",
894
1045
  "row.noRetry": "not retryable",
895
1046
  "row.copy": "Copy command",
1047
+ "row.more": "More commands",
1048
+ "row.viewBoth": "View both",
1049
+ "overlay.pair": "View both",
896
1050
  "tab.expired": "Expired",
897
1051
  "row.targetCreated": "created",
898
1052
  "row.verify": "Verify state",
@@ -932,6 +1086,7 @@ window.__ModuleLoader__.load({
932
1086
  "disabled.unavailable": "unavailable",
933
1087
  "history.empty": "No history.",
934
1088
  "history.archivedAt": "Archived {date}",
1089
+ "history.archivedAtUnknown": "Archive time unknown",
935
1090
  "failureServer": "Read failed",
936
1091
  "failureUnauthorized": "Not signed in",
937
1092
  "failureForbidden": "Forbidden",
@@ -1010,6 +1165,9 @@ window.__ModuleLoader__.load({
1010
1165
  "settings.capabilityDisabled": "Not available in this deployment",
1011
1166
  "settings.readonly": "Read-only here; edit in platform settings.",
1012
1167
  "run.title": "Run",
1168
+ "run.progressUnknown": "progress unknown",
1169
+ "run.current": "Current run",
1170
+ "run.last": "Last completed",
1013
1171
  "run.id": "Run ID",
1014
1172
  "run.state": "State",
1015
1173
  "run.stage": "Stage",
@@ -1091,7 +1249,32 @@ window.__ModuleLoader__.load({
1091
1249
  ".evolution-merge-panel [data-role=\"title\"] { margin:0; font-size:var(--evo-type-lg); font-weight:600; }",
1092
1250
  ".evolution-merge-panel [data-role=\"start\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
1093
1251
  ".evolution-merge-panel [data-role=\"degraded\"] { margin:0; color:var(--dsw-alias-state-warn-primary); font-size:var(--evo-type-xs); }",
1094
- ".evolution-merge-panel [data-role=\"expired-toggle\"][data-active=\"true\"] { background:var(--dsw-alias-brand-primary); color:var(--dsw-alias-bg-base); }",
1252
+ ".evolution-merge-panel [data-role=\"task-banner\"] { display:flex; align-items:baseline; gap:var(--evo-space-6); padding:var(--evo-space-10) var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); }",
1253
+ ".evolution-merge-panel [data-role=\"task-banner-count\"] { font-size:var(--evo-type-lg); font-variant-numeric:tabular-nums; }",
1254
+ ".evolution-merge-panel [data-role=\"task-banner-text\"] { color:var(--dsw-alias-label-secondary); font-size:var(--evo-type-sm); }",
1255
+ ".evolution-merge-panel .merge-decision { display:flex; flex-direction:column; gap:var(--evo-space-8); padding:var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); }",
1256
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-title\"] { margin:0; font-size:var(--evo-type-sm); font-weight:600; }",
1257
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-summary\"] { margin:0; color:var(--dsw-alias-label-secondary); font-size:var(--evo-type-xs); }",
1258
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-facts\"] { display:grid; grid-template-columns:max-content minmax(0,1fr); gap:var(--evo-space-4) var(--evo-space-12); margin:0; font-size:var(--evo-type-xs); }",
1259
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-fact\"] { display:contents; }",
1260
+ ".evolution-merge-panel .merge-decision dt { color:var(--dsw-alias-label-tertiary); }",
1261
+ ".evolution-merge-panel .merge-decision dd { margin:0; min-width:0; overflow-wrap:anywhere; }",
1262
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-actions\"] { display:flex; flex-wrap:wrap; align-items:center; gap:var(--evo-space-8); padding-top:var(--evo-space-8); }",
1263
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"], .evolution-merge-panel .merge-decision [data-role=\"decision-secondary\"], .evolution-merge-panel .merge-decision [data-role=\"view-both\"] { padding:var(--evo-space-6) var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); font-size:var(--evo-type-sm); cursor:pointer; }",
1264
+ ".evolution-merge-panel [data-role=\"pair-grid\"] { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--evo-space-12); }",
1265
+ ".evolution-merge-panel [data-role=\"body-a\"], .evolution-merge-panel [data-role=\"body-b\"] { min-width:0; }",
1266
+ ".evolution-merge-panel [data-role=\"body-a\"] pre, .evolution-merge-panel [data-role=\"body-b\"] pre { max-height:min(52vh,560px); overflow:auto; white-space:pre-wrap; }",
1267
+ "@media (max-width:900px){.evolution-merge-panel [data-role=\"pair-grid\"]{grid-template-columns:minmax(0,1fr)}}",
1268
+ ".evo-merge-modal [data-role=\"pair-grid\"]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--evo-space-12)}",
1269
+ ".evo-merge-modal [data-role=\"body-a\"],.evo-merge-modal [data-role=\"body-b\"]{min-width:0}",
1270
+ ".evo-merge-modal [data-role=\"body-a\"] pre,.evo-merge-modal [data-role=\"body-b\"] pre{max-height:min(52vh,560px);overflow:auto;white-space:pre-wrap}",
1271
+ "@media (max-width:900px){.evo-merge-modal [data-role=\"pair-grid\"]{grid-template-columns:minmax(0,1fr)}}",
1272
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"] { background:var(--dsw-alias-label-primary); border-color:transparent; color:var(--dsw-alias-label-primary-foreground); }",
1273
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"]:disabled, .evolution-merge-panel .merge-decision [data-role=\"decision-secondary\"]:disabled, .evolution-merge-panel .merge-decision [data-role=\"view-both\"]:disabled { color:var(--dsw-alias-label-dimmed); cursor:default; }",
1274
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-details\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); }",
1275
+ ".evolution-merge-panel [data-role=\"row-actions\"] { display:flex; align-items:center; justify-content:flex-end; gap:var(--evo-space-8); }",
1276
+ ".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; }",
1277
+ ".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; }",
1095
1278
  ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
1096
1279
  ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
1097
1280
  ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
@@ -1106,7 +1289,7 @@ window.__ModuleLoader__.load({
1106
1289
  ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1107
1290
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1108
1291
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
1109
- ".evolution-merge-panel [data-role=\"row\"] { display:grid; grid-template-columns:1fr auto; gap:var(--evo-space-8); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); cursor:grab; }",
1292
+ ".evolution-merge-panel [data-role=\"row\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); cursor:grab; }",
1110
1293
  ".evolution-merge-panel [data-role=\"row\"]:active { cursor:grabbing; }",
1111
1294
  ".evolution-merge-panel [data-role=\"row-label\"] { font-weight:600; }",
1112
1295
  ".evolution-merge-panel [data-role=\"row-meta\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
@@ -1120,7 +1303,9 @@ window.__ModuleLoader__.load({
1120
1303
  ".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); }",
1121
1304
  ".evolution-merge-panel [data-role=\"settings-title\"] { margin:0; font-size:var(--evo-type-sm); }",
1122
1305
  ".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); }",
1123
- ".evolution-merge-panel [data-role=\"settings-section\"] { display:grid; grid-template-columns:minmax(0,1fr) auto auto; align-items:center; column-gap:var(--evo-space-8); }",
1306
+ ".evolution-merge-panel [data-role=\"settings-chips\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-6); }",
1307
+ ".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); }",
1308
+ ".evolution-merge-panel [data-role=\"settings-label\"] { color:var(--dsw-alias-label-secondary); }",
1124
1309
  ".evolution-merge-panel [data-role=\"settings-section\"][data-disabled=\"true\"] { color:var(--dsw-alias-label-tertiary); }",
1125
1310
  ".evolution-merge-panel [data-role=\"settings-toggle\"] { margin:0; }",
1126
1311
  ".evolution-merge-panel [data-role=\"settings-value\"] { font-weight:600; min-width:2.5em; text-align:right; }",
@@ -1144,7 +1329,7 @@ window.__ModuleLoader__.load({
1144
1329
  ].join("\n");
1145
1330
  //#endregion
1146
1331
  //#region src/client/platform-modal.ts
1147
- const PLATFORM_UI_MODULE$1 = "@deepseek-ai/dsh-client-ui-primitives";
1332
+ const PLATFORM_UI_MODULE = "@deepseek-ai/dsh-client-ui-primitives";
1148
1333
  let cached;
1149
1334
  /** The platform Modal, or undefined in a non-browser/SSR render or a deployment without the row. */
1150
1335
  function platformModal() {
@@ -1154,7 +1339,7 @@ window.__ModuleLoader__.load({
1154
1339
  return;
1155
1340
  }
1156
1341
  try {
1157
- const row = require(PLATFORM_UI_MODULE$1);
1342
+ const row = require(PLATFORM_UI_MODULE);
1158
1343
  cached = typeof row.Modal === "function" ? row.Modal : null;
1159
1344
  } catch {
1160
1345
  cached = null;
@@ -1162,61 +1347,6 @@ window.__ModuleLoader__.load({
1162
1347
  return cached ?? void 0;
1163
1348
  }
1164
1349
  //#endregion
1165
- //#region src/client/platform-menu.ts
1166
- /**
1167
- * The one seam onto the platform's menu material and rows.
1168
- *
1169
- * The family bundle keeps `@deepseek-ai/*` external, so the platform components are reached through
1170
- * the loader-provided `require` (the same door the Markdown seam uses). A deployment without the
1171
- * row gets the panel's own menu fallback; the platform path is not a hard dependency.
1172
- * @module dsh-evolution-merge-ui/client
1173
- */
1174
- const PLATFORM_UI_MODULE = "@deepseek-ai/dsh-client-ui-primitives";
1175
- /**
1176
- * Render the coordinate menu through the platform primitives.
1177
- * @param primitives - the platform MenuSurface/MenuItemButton pair.
1178
- * @param actions - rows in display order.
1179
- * @param label - accessible menu name.
1180
- * @param style - fixed position computed from the pointer.
1181
- * @returns the menu element.
1182
- */
1183
- function renderPlatformMenu(primitives, actions, label, style) {
1184
- return (0, react.createElement)(primitives.MenuSurface, {
1185
- "data-role": "sort-menu",
1186
- "data-platform": "true",
1187
- role: "menu",
1188
- "aria-label": label,
1189
- style
1190
- }, actions.map((action) => (0, react.createElement)(primitives.MenuItemButton, {
1191
- key: action.id,
1192
- children: action.label,
1193
- ...action.separatorBefore === true ? { separatorBefore: true } : {},
1194
- onSelect: action.onSelect
1195
- })));
1196
- }
1197
- /**
1198
- * Accept only the two component functions; a namespace row without them is not a menu.
1199
- * @param row - the module row's exports.
1200
- * @returns the two components, or undefined.
1201
- */
1202
- function menuPrimitivesFrom(row) {
1203
- const surface = row.MenuSurface;
1204
- const item = row.MenuItemButton;
1205
- if (typeof surface !== "function" || typeof item !== "function") return void 0;
1206
- return {
1207
- MenuSurface: surface,
1208
- MenuItemButton: item
1209
- };
1210
- }
1211
- /** The module-table row, when this deployment carries it. */
1212
- const PLATFORM_MENU = (() => {
1213
- try {
1214
- return menuPrimitivesFrom(require(PLATFORM_UI_MODULE));
1215
- } catch {
1216
- return;
1217
- }
1218
- })();
1219
- //#endregion
1220
1350
  //#region src/client/sorting.ts
1221
1351
  /** Workspace scope for one tab. A missing panel workspace is the local scope. */
1222
1352
  function resolveWorkspaceId(workspaceId) {
@@ -1305,121 +1435,14 @@ window.__ModuleLoader__.load({
1305
1435
  return edge === "top" ? [id, ...rest] : [...rest, id];
1306
1436
  }
1307
1437
  //#endregion
1308
- //#region src/client/fallbacks.ts
1309
- /** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
1310
- const FALLBACK_COMMANDS = [
1311
- "/evolution merge scan",
1312
- "/evolution merge pair <a> <b>",
1313
- "/evolution merge pending",
1314
- "/evolution merge pending --expired",
1315
- "/evolution merge skipped",
1316
- "/evolution merge candidates",
1317
- "/evolution merge status",
1318
- "/evolution merge report",
1319
- "/evolution doctor"
1320
- ];
1321
- //#endregion
1322
- //#region src/client/Panel.ts
1323
- /** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
1324
- const QUEUES = [
1325
- "verify",
1326
- "pending",
1327
- "fuse",
1328
- "skipped"
1329
- ];
1330
- const DOCTOR_COMMAND = "/evolution doctor";
1331
- function problemKey(problem) {
1332
- switch (problem.code) {
1333
- case "unauthenticated": return "failureUnauthorized";
1334
- case "forbidden": return "failureForbidden";
1335
- case "unmounted": return "failureUnmounted";
1336
- case "capability": return "failureCapability";
1337
- case "body-ambiguous": return "failureBodyAmbiguous";
1338
- case "wire-version": return "failureWireVersion";
1339
- case "body-missing": return "failureBodyMissing";
1340
- case "bad-query": return "failureBadQuery";
1341
- case "refused": return "failureRefused";
1342
- case "server": return "failureServer";
1343
- }
1344
- }
1345
- function reasonText(t, reasonCode, fallback) {
1346
- if (reasonCode === void 0) return fallback;
1347
- const key = "history.reason." + reasonCode;
1348
- const text = t(key);
1349
- return text === key ? fallback : text;
1350
- }
1351
- function blockedReasonKey(failure) {
1352
- const code = failure.readiness.code ?? failure.code;
1353
- if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
1354
- if (code.endsWith("forward-only")) return "blocked.reason.forward-only";
1355
- if (code.endsWith("migration-blocked")) return "blocked.reason.migration-blocked";
1356
- return "blocked.reason.unknown";
1357
- }
1358
- function failureNode(t, failure, fallback, copy) {
1359
- if (isBlocked(failure)) {
1360
- const raw = failure.readiness.message;
1361
- return (0, react.createElement)("div", {
1362
- "data-role": "blocked",
1363
- role: "alert",
1364
- "data-block-code": failure.code,
1365
- "data-readiness-code": failure.readiness.code ?? ""
1366
- }, (0, react.createElement)("h3", { "data-role": "blocked-title" }, t("blocked.title")), (0, react.createElement)("p", {
1367
- "data-role": "blocked-reason",
1368
- title: raw ?? t(blockedReasonKey(failure))
1369
- }, 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", {
1370
- type: "button",
1371
- "data-role": "doctor",
1372
- onClick: () => {
1373
- copy(DOCTOR_COMMAND);
1374
- }
1375
- }, t("blocked.doctor")), (0, react.createElement)("button", {
1376
- type: "button",
1377
- "data-role": "refresh",
1378
- onClick: () => {
1379
- window.dispatchEvent(new Event("evolution-merge:refresh"));
1380
- }
1381
- }, t("refresh"))));
1382
- }
1383
- const capability = failure.code === "capability";
1384
- return (0, react.createElement)("div", {
1385
- "data-role": capability ? "capability-failure" : "failure",
1386
- "data-error-code": failure.code,
1387
- "data-status": String(failure.status)
1388
- }, (0, react.createElement)("p", {
1389
- "data-role": "failure-title",
1390
- title: failure.message
1391
- }, 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", {
1392
- key: command,
1393
- type: "button",
1394
- "data-role": "fallback-command",
1395
- onClick: () => {
1396
- copy(command);
1397
- }
1398
- }, command))));
1399
- }
1400
- function summaryNode(row, t) {
1401
- if (row.state !== "pending" || row.summary === void 0) return null;
1402
- return (0, react.createElement)("div", { "data-role": "pending-summary" }, [
1403
- ["row.summaryA", row.summary.a],
1404
- ["row.summaryB", row.summary.b],
1405
- ["row.rationale", row.summary.rationale]
1406
- ].map(([label, value]) => (0, react.createElement)("div", {
1407
- key: label,
1408
- "data-role": "pending-summary-slot"
1409
- }, (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))));
1410
- }
1411
- function stageNode(row, t) {
1412
- const stage = row.stageView;
1413
- if (stage === void 0) return null;
1414
- const tone = stage.state === "lost" ? "fail" : stage.state === "expired" || stage.state === "reserved" ? "warn" : "neutral";
1415
- const expiry = stage.expiresAt === void 0 ? "" : " · " + t("row.expires") + " " + new Date(stage.expiresAt).toLocaleString();
1416
- return (0, react.createElement)("span", {
1417
- "data-role": "stage-badge",
1418
- "data-stage": stage.state,
1419
- "data-tone": tone
1420
- }, t("stage.state." + stage.state) + expiry);
1421
- }
1422
- function runNode(view, t, copy) {
1438
+ //#region src/client/MergeRunStrip.ts
1439
+ /** The merge run strips: active/last identity, state, progress, report and details. */
1440
+ /**
1441
+ * Render active and last runs as two independently labelled strips.
1442
+ * @param props - view, translator and command copier.
1443
+ * @returns the strips, or null when neither run exists.
1444
+ */
1445
+ function MergeRunStrips({ view, t, copy }) {
1423
1446
  const active = view.activeRun;
1424
1447
  const last = view.lastRun;
1425
1448
  if (active === void 0 && last === void 0) return null;
@@ -1441,7 +1464,7 @@ window.__ModuleLoader__.load({
1441
1464
  run.endedAt === void 0 ? null : (0, react.createElement)("span", {
1442
1465
  key: "ended",
1443
1466
  "data-role": "run-ended"
1444
- }, t("run.endedAt") + " " + new Date(run.endedAt).toLocaleString()),
1467
+ }, t("run.endedAt") + " " + (formatLocalDateTime(run.endedAt) ?? "")),
1445
1468
  run.failure === void 0 ? null : (0, react.createElement)("span", {
1446
1469
  key: "failure",
1447
1470
  "data-role": "run-failure",
@@ -1465,7 +1488,10 @@ window.__ModuleLoader__.load({
1465
1488
  "data-role": "run-strip",
1466
1489
  "data-run-role": role,
1467
1490
  "data-run-state": run.state
1468
- }, (0, react.createElement)("span", { "data-role": "run-state" }, t("run.state") + " " + t("run." + run.state)), run.progress === null || run.progress === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-progress" }, t("run.progress") + " " + String(run.progress.done) + "/" + String(run.progress.total)), (0, react.createElement)("button", {
1491
+ }, (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", {
1492
+ "data-role": "run-progress",
1493
+ "data-progress": "unknown"
1494
+ }, 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", {
1469
1495
  type: "button",
1470
1496
  "data-role": "run-report",
1471
1497
  "data-action": "report",
@@ -1486,46 +1512,64 @@ window.__ModuleLoader__.load({
1486
1512
  }
1487
1513
  }, t("action." + action.id))), (0, react.createElement)("details", { "data-role": "run-details" }, (0, react.createElement)("summary", null, t("run.details")), ...internals));
1488
1514
  }
1489
- function ReadOnlySetting({ section, label, value, disabled, missing, note, link, t }) {
1490
- const text = missing ? t("settings.capabilityMissing") : disabled ? t("settings.capabilityDisabled") : value === true ? t("settings.on") : t("settings.off");
1491
- return (0, react.createElement)("div", {
1492
- "data-role": "settings-section",
1493
- "data-section": section,
1494
- "data-disabled": missing || disabled ? "true" : "false"
1495
- }, (0, react.createElement)("span", { "data-role": "settings-label" }, label), (0, react.createElement)("span", {
1496
- "data-role": "settings-value",
1497
- "data-value": missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off"
1498
- }, text), note === void 0 ? null : (0, react.createElement)("span", { "data-role": "settings-note" }, note), link === true ? (0, react.createElement)("span", { "data-role": "settings-link" }, t("settings.openPlatformPath")) : null);
1515
+ //#endregion
1516
+ //#region src/client/merge-presentation.ts
1517
+ /**
1518
+ * Compare the two source summaries after trimming. Equal non-empty text is one fact;
1519
+ * missing text stays missing rather than being invented.
1520
+ * @param a - source A summary, or null.
1521
+ * @param b - source B summary, or null.
1522
+ * @returns the presentation form.
1523
+ */
1524
+ function summarizePair(a, b) {
1525
+ const left = a?.trim() ?? "";
1526
+ const right = b?.trim() ?? "";
1527
+ if (left === "" && right === "") return { kind: "missing" };
1528
+ if (left !== "" && left === right) return {
1529
+ kind: "same",
1530
+ text: left
1531
+ };
1532
+ return {
1533
+ kind: "different",
1534
+ a: left === "" ? null : left,
1535
+ b: right === "" ? null : right
1536
+ };
1499
1537
  }
1500
- function SettingsCard({ view, t }) {
1501
- const platform = view.approval.platformWrite;
1502
- const platformCapability = platform?.capability === true;
1503
- const platformFallback = platform?.enabled ?? false;
1504
- const mergeFallback = view.approval.mergeApproval;
1505
- 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)("p", { "data-role": "settings-path" }, t("settings.openPlatformPath")), (0, react.createElement)(ReadOnlySetting, {
1506
- section: "merge-approval",
1507
- label: t("settings.mergeApproval"),
1508
- value: mergeFallback,
1509
- disabled: false,
1510
- missing: false,
1511
- t
1512
- }), (0, react.createElement)(ReadOnlySetting, {
1513
- section: "platform-write",
1514
- label: t("settings.platformWrite"),
1515
- value: platformFallback,
1516
- disabled: platformCapability === false,
1517
- missing: platform === void 0,
1518
- note: t("settings.platformWriteLinkHint"),
1519
- t
1520
- }), (0, react.createElement)("p", { "data-role": "settings-readonly" }, t("settings.readonly")));
1538
+ //#endregion
1539
+ //#region src/client/MergeRow.ts
1540
+ /** One merge row: summary, stage, facts, one primary decision and the copy/sort drawer. */
1541
+ function reasonText$1(t, reasonCode, fallback) {
1542
+ return reasonCode === void 0 ? fallback : t("history.reason." + reasonCode);
1521
1543
  }
1522
- function rowNode(row, t, copy, sort, openBody, onDecide, busy) {
1523
- const first = [
1524
- row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
1525
- row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
1526
- row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
1527
- row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText(t, row.reasonCode, row.reason ?? "")
1528
- ].filter((value) => value !== void 0).join(" · ");
1544
+ function summaryNode(row, t) {
1545
+ if (row.state !== "pending" || row.summary === void 0) return null;
1546
+ const pair = summarizePair(row.summary.a, row.summary.b);
1547
+ 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]];
1548
+ slots.push([t("row.rationale"), row.summary.rationale]);
1549
+ return (0, react.createElement)("div", { "data-role": "pending-summary" }, slots.map(([label, value]) => (0, react.createElement)("div", {
1550
+ key: label,
1551
+ "data-role": "pending-summary-slot"
1552
+ }, (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))));
1553
+ }
1554
+ function stageNode(row, t) {
1555
+ const stage = row.stageView;
1556
+ if (stage === void 0) return null;
1557
+ const tone = stage.state === "lost" ? "fail" : stage.state === "expired" || stage.state === "reserved" ? "warn" : "neutral";
1558
+ const expiry = stage.expiresAt === void 0 ? "" : " · " + t("row.expires") + " " + new Date(stage.expiresAt).toLocaleString();
1559
+ return (0, react.createElement)("span", {
1560
+ "data-role": "stage-badge",
1561
+ "data-stage": stage.state,
1562
+ "data-tone": tone
1563
+ }, t("stage.state." + stage.state) + expiry);
1564
+ }
1565
+ /**
1566
+ * Render one row as a decision card. The approve action is the only primary; A/B, reject and
1567
+ * auxiliary sort/copy commands cannot borrow that weight.
1568
+ * @param props - row, translator, copier, sort hooks, body opener and decision callback.
1569
+ * @returns the row with drag/keyboard wiring and a separate more drawer.
1570
+ */
1571
+ function MergeRow(props) {
1572
+ const { row, t, copy, sort, openBoth, onDecide, busy } = props;
1529
1573
  const details = [
1530
1574
  t("row.id") + " " + row.id,
1531
1575
  row.targetActual === void 0 ? void 0 : t("row.actualTarget") + " " + row.targetActual,
@@ -1534,35 +1578,83 @@ window.__ModuleLoader__.load({
1534
1578
  row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
1535
1579
  row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2)
1536
1580
  ].filter((value) => value !== void 0).join(" · ");
1537
- const actions = row.actions.flatMap((action) => {
1538
- const decision = (action.id === "approve" || action.id === "reject") && onDecide !== void 0;
1539
- const primary = (0, react.createElement)("button", {
1540
- key: action.id,
1541
- type: "button",
1542
- "data-role": decision ? "row-decision" : "row-action",
1543
- "data-action": action.id,
1544
- ...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
1545
- title: action.command,
1581
+ const approve = row.actions.find((action) => action.id === "approve");
1582
+ const reject = row.actions.find((action) => action.id === "reject");
1583
+ const bodyA = row.bodies?.find((body) => body.name === row.a);
1584
+ const bodyB = row.bodies?.find((body) => body.name === row.b);
1585
+ const primary = approve === void 0 ? void 0 : {
1586
+ id: "approve",
1587
+ label: t("decision.approve"),
1588
+ disabled: !approve.enabled || busy || onDecide === void 0,
1589
+ ...approve.disabledReason === void 0 ? {} : { attributes: { "data-disabled-reason": approve.disabledReason } },
1590
+ onSelect: () => {
1591
+ if (approve.enabled && !busy && onDecide !== void 0) onDecide(row.id, "approve");
1592
+ }
1593
+ };
1594
+ const secondary = [];
1595
+ if (approve !== void 0 && onDecide === void 0) secondary.push({
1596
+ id: "approve",
1597
+ label: t("action.approve"),
1598
+ disabled: !approve.enabled || busy,
1599
+ attributes: { "data-role": "row-action" },
1600
+ onSelect: () => {
1601
+ if (approve.enabled && !busy) copy(approve.command);
1602
+ }
1603
+ });
1604
+ if (reject !== void 0) secondary.push({
1605
+ id: "reject",
1606
+ label: t("decision.reject"),
1607
+ disabled: !reject.enabled || busy || onDecide === void 0,
1608
+ ...reject.disabledReason === void 0 ? {} : { attributes: { "data-disabled-reason": reject.disabledReason } },
1609
+ onSelect: () => {
1610
+ if (reject.enabled && !busy && onDecide !== void 0) onDecide(row.id, "reject");
1611
+ }
1612
+ });
1613
+ if (bodyA !== void 0 && bodyB !== void 0) secondary.push({
1614
+ id: "view-both",
1615
+ label: t("row.viewBoth"),
1616
+ attributes: { "data-role": "view-both" },
1617
+ onSelect: () => {
1618
+ openBoth({
1619
+ name: bodyA.name,
1620
+ side: bodyA.side
1621
+ }, {
1622
+ name: bodyB.name,
1623
+ side: bodyB.side
1624
+ }, bodyA.rowId);
1625
+ }
1626
+ });
1627
+ for (const action of row.actions) {
1628
+ if (action.id === "approve" || action.id === "reject" || action.id === "copy" || action.id === "discard") continue;
1629
+ secondary.push({
1630
+ id: action.id,
1631
+ label: t("action." + action.id),
1546
1632
  disabled: !action.enabled || busy,
1547
- onClick: () => {
1548
- if (!action.enabled || busy) return;
1549
- if (decision) onDecide(row.id, action.id);
1550
- else copy(action.command);
1551
- }
1552
- }, decision ? t("decision." + action.id) : t("action." + action.id));
1553
- if (!decision) return [primary];
1554
- return [primary, (0, react.createElement)("button", {
1555
- key: action.id + "-copy",
1556
- type: "button",
1557
- "data-role": "row-copy",
1558
- "data-action": action.id + "-copy",
1559
- title: action.command,
1560
- disabled: busy,
1561
- onClick: () => {
1562
- if (!busy) copy(action.command);
1633
+ attributes: { "data-role": "row-action" },
1634
+ onSelect: () => {
1635
+ if (action.enabled && !busy) copy(action.command);
1563
1636
  }
1564
- }, t("row.copy"))];
1565
- });
1637
+ });
1638
+ }
1639
+ const facts = [
1640
+ ...row.target === void 0 ? [] : [{
1641
+ label: t("row.target"),
1642
+ value: row.target
1643
+ }],
1644
+ ...row.failure === void 0 ? [] : [{
1645
+ label: t("row.failure"),
1646
+ value: row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry"))
1647
+ }],
1648
+ ...row.expired === void 0 ? [] : [{
1649
+ label: t("row.expired"),
1650
+ value: t("expired." + row.expired)
1651
+ }],
1652
+ ...row.reasonCode === void 0 && row.reason === void 0 ? [] : [{
1653
+ label: t("row.reason"),
1654
+ value: reasonText$1(t, row.reasonCode, row.reason ?? "")
1655
+ }]
1656
+ ];
1657
+ const decisionDetails = (0, react.createElement)("div", null, stageNode(row, t), summaryNode(row, t), bodyA === void 0 || bodyB === void 0 ? null : (0, react.createElement)("div", { "data-role": "body-names" }, "A · " + bodyA.name + " · B · " + bodyB.name), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details)));
1566
1658
  return (0, react.createElement)("div", {
1567
1659
  key: row.id,
1568
1660
  "data-role": "row",
@@ -1602,7 +1694,16 @@ window.__ModuleLoader__.load({
1602
1694
  event.preventDefault();
1603
1695
  sort.menu(row.id, event.clientX, event.clientY);
1604
1696
  }
1605
- }, (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", {
1697
+ }, (0, react.createElement)(DecisionFrame, {
1698
+ className: "merge-decision",
1699
+ decision: {
1700
+ title: row.label,
1701
+ facts,
1702
+ ...primary === void 0 ? {} : { primary },
1703
+ ...secondary.length === 0 ? {} : { secondary },
1704
+ details: decisionDetails
1705
+ }
1706
+ }), (0, react.createElement)("div", { "data-role": "row-actions" }, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("details", { "data-role": "row-more" }, (0, react.createElement)("summary", null, t("row.more")), (0, react.createElement)("button", {
1606
1707
  type: "button",
1607
1708
  "data-role": "row-sort",
1608
1709
  "aria-haspopup": "menu",
@@ -1611,29 +1712,151 @@ window.__ModuleLoader__.load({
1611
1712
  const rect = event.currentTarget.getBoundingClientRect();
1612
1713
  sort.menu(row.id, rect.left, rect.bottom);
1613
1714
  }
1614
- }, t("sort.menu")), (() => {
1615
- const bodyA = row.bodies?.find((body) => body.name === row.a);
1616
- const bodyB = row.bodies?.find((body) => body.name === row.b);
1617
- return [bodyA === void 0 ? null : (0, react.createElement)("button", {
1618
- key: "a",
1715
+ }, t("sort.menu")), ...row.actions.map((action) => (0, react.createElement)("button", {
1716
+ key: "copy-" + action.id,
1717
+ type: "button",
1718
+ "data-role": "row-copy",
1719
+ "data-action": action.id + "-copy",
1720
+ title: action.command,
1721
+ disabled: busy,
1722
+ onClick: () => {
1723
+ if (!busy) copy(action.command);
1724
+ }
1725
+ }, t("row.copy") + " " + t("action." + action.id))))));
1726
+ }
1727
+ //#endregion
1728
+ //#region src/client/MergeSettings.ts
1729
+ /** The merge settings summary: two read-only state chips and one settings path. */
1730
+ function SettingChip({ section, label, value, disabled, missing, t }) {
1731
+ const text = missing ? t("settings.capabilityMissing") : disabled ? t("settings.capabilityDisabled") : value === true ? t("settings.on") : t("settings.off");
1732
+ const state = missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off";
1733
+ return (0, react.createElement)("span", {
1734
+ "data-role": "settings-section",
1735
+ "data-section": section,
1736
+ "data-disabled": missing || disabled ? "true" : "false",
1737
+ "data-state": state
1738
+ }, (0, react.createElement)("span", { "data-role": "settings-label" }, label), (0, react.createElement)("span", {
1739
+ "data-role": "settings-value",
1740
+ "data-value": state
1741
+ }, text));
1742
+ }
1743
+ /**
1744
+ * Render the read-only approval state without the old large settings card.
1745
+ * @param props - the merge view and translator.
1746
+ * @returns two chips and the one current settings path.
1747
+ */
1748
+ function MergeSettings({ view, t }) {
1749
+ const platform = view.approval.platformWrite;
1750
+ const platformCapability = platform?.capability === true;
1751
+ const platformFallback = platform?.enabled ?? false;
1752
+ 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, {
1753
+ section: "merge-approval",
1754
+ label: t("settings.mergeApproval"),
1755
+ value: view.approval.mergeApproval,
1756
+ disabled: false,
1757
+ missing: false,
1758
+ t
1759
+ }), (0, react.createElement)(SettingChip, {
1760
+ section: "platform-write",
1761
+ label: t("settings.platformWrite"),
1762
+ value: platformFallback,
1763
+ disabled: platformCapability === false,
1764
+ missing: platform === void 0,
1765
+ t
1766
+ })));
1767
+ }
1768
+ //#endregion
1769
+ //#region src/client/fallbacks.ts
1770
+ /** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
1771
+ const FALLBACK_COMMANDS = [
1772
+ "/evolution merge scan",
1773
+ "/evolution merge pair <a> <b>",
1774
+ "/evolution merge pending",
1775
+ "/evolution merge pending --expired",
1776
+ "/evolution merge skipped",
1777
+ "/evolution merge candidates",
1778
+ "/evolution merge status",
1779
+ "/evolution merge report",
1780
+ "/evolution doctor"
1781
+ ];
1782
+ //#endregion
1783
+ //#region src/client/Panel.ts
1784
+ /** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
1785
+ const QUEUES = [
1786
+ "verify",
1787
+ "pending",
1788
+ "fuse",
1789
+ "skipped"
1790
+ ];
1791
+ const DOCTOR_COMMAND = "/evolution doctor";
1792
+ function problemKey(problem) {
1793
+ switch (problem.code) {
1794
+ case "unauthenticated": return "failureUnauthorized";
1795
+ case "forbidden": return "failureForbidden";
1796
+ case "unmounted": return "failureUnmounted";
1797
+ case "capability": return "failureCapability";
1798
+ case "body-ambiguous": return "failureBodyAmbiguous";
1799
+ case "wire-version": return "failureWireVersion";
1800
+ case "body-missing": return "failureBodyMissing";
1801
+ case "bad-query": return "failureBadQuery";
1802
+ case "refused": return "failureRefused";
1803
+ case "server": return "failureServer";
1804
+ }
1805
+ }
1806
+ function reasonText(t, reasonCode, fallback) {
1807
+ if (reasonCode === void 0) return fallback;
1808
+ const key = "history.reason." + reasonCode;
1809
+ const text = t(key);
1810
+ return text === key ? fallback : text;
1811
+ }
1812
+ function blockedReasonKey(failure) {
1813
+ const code = failure.readiness.code ?? failure.code;
1814
+ if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
1815
+ if (code.endsWith("forward-only")) return "blocked.reason.forward-only";
1816
+ if (code.endsWith("migration-blocked")) return "blocked.reason.migration-blocked";
1817
+ return "blocked.reason.unknown";
1818
+ }
1819
+ function failureNode(t, failure, fallback, copy) {
1820
+ if (isBlocked(failure)) {
1821
+ const raw = failure.readiness.message;
1822
+ return (0, react.createElement)("div", {
1823
+ "data-role": "blocked",
1824
+ role: "alert",
1825
+ "data-block-code": failure.code,
1826
+ "data-readiness-code": failure.readiness.code ?? ""
1827
+ }, (0, react.createElement)("h3", { "data-role": "blocked-title" }, t("blocked.title")), (0, react.createElement)("p", {
1828
+ "data-role": "blocked-reason",
1829
+ title: raw ?? t(blockedReasonKey(failure))
1830
+ }, 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", {
1619
1831
  type: "button",
1620
- "data-role": "body-a",
1621
- "aria-label": t("body." + bodyA.side) + " " + bodyA.name,
1622
- title: t("body." + bodyA.side) + " " + bodyA.name,
1832
+ "data-role": "doctor",
1623
1833
  onClick: () => {
1624
- openBody(bodyA.name, bodyA.side, bodyA.rowId);
1834
+ copy(DOCTOR_COMMAND);
1625
1835
  }
1626
- }, "A · " + bodyA.name), bodyB === void 0 ? null : (0, react.createElement)("button", {
1627
- key: "b",
1836
+ }, t("blocked.doctor")), (0, react.createElement)("button", {
1628
1837
  type: "button",
1629
- "data-role": "body-b",
1630
- "aria-label": t("body." + bodyB.side) + " " + bodyB.name,
1631
- title: t("body." + bodyB.side) + " " + bodyB.name,
1838
+ "data-role": "refresh",
1632
1839
  onClick: () => {
1633
- openBody(bodyB.name, bodyB.side, bodyB.rowId);
1840
+ window.dispatchEvent(new Event("evolution-merge:refresh"));
1634
1841
  }
1635
- }, "B · " + bodyB.name)];
1636
- })(), actions)));
1842
+ }, t("refresh"))));
1843
+ }
1844
+ const capability = failure.code === "capability";
1845
+ return (0, react.createElement)("div", {
1846
+ "data-role": capability ? "capability-failure" : "failure",
1847
+ "data-error-code": failure.code,
1848
+ "data-status": String(failure.status)
1849
+ }, (0, react.createElement)("p", {
1850
+ "data-role": "failure-title",
1851
+ title: failure.message
1852
+ }, 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", {
1853
+ key: command,
1854
+ type: "button",
1855
+ "data-role": "fallback-command",
1856
+ onClick: () => {
1857
+ copy(command);
1858
+ }
1859
+ }, command))));
1637
1860
  }
1638
1861
  const overlayKey = (event, onClose) => {
1639
1862
  if (event.key === "Escape") {
@@ -1683,6 +1906,39 @@ window.__ModuleLoader__.load({
1683
1906
  className: "evo-merge-modal"
1684
1907
  }, inner);
1685
1908
  }
1909
+ function pairNode(overlay, t, onClose) {
1910
+ const Modal = platformModal();
1911
+ const title = t("overlay.pair");
1912
+ const block = (target, body) => (0, react.createElement)("section", {
1913
+ "data-role": target === overlay.a ? "body-a" : "body-b",
1914
+ "data-side": target.side
1915
+ }, (0, react.createElement)("h4", { "data-role": "pair-name" }, target.name + " · " + t("body." + target.side)), body === void 0 ? null : body.truncated ? (0, react.createElement)("p", { "data-role": "body-truncated" }, t("body.truncated")) : null, body === void 0 ? null : body.text === "" ? (0, react.createElement)("p", { "data-role": "body-empty" }, t("body.empty")) : (0, react.createElement)("pre", { "data-role": "body-text" }, body.text));
1916
+ const inner = (0, react.createElement)("section", {
1917
+ "data-role": "overlay",
1918
+ "data-overlay": "pair",
1919
+ "aria-label": title,
1920
+ ...Modal === void 0 ? {
1921
+ role: "dialog",
1922
+ "aria-modal": "true",
1923
+ onKeyDown: (event) => {
1924
+ overlayKey(event, onClose);
1925
+ }
1926
+ } : { "data-platform-modal": "true" }
1927
+ }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, title), (0, react.createElement)("button", {
1928
+ type: "button",
1929
+ "data-role": "overlay-close",
1930
+ ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1931
+ onClick: onClose
1932
+ }, t("close"))), overlay.status === "loading" ? (0, react.createElement)("p", { "data-role": "overlay-loading" }, t("loading")) : null, overlay.status === "error" && overlay.failure !== void 0 ? failureNode(t, overlay.failure, [], () => {}) : null, overlay.status === "ready" ? (0, react.createElement)("div", { "data-role": "pair-grid" }, block(overlay.a, overlay.aBody), block(overlay.b, overlay.bBody)) : null);
1933
+ return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1934
+ open: true,
1935
+ onClose,
1936
+ title,
1937
+ closeLabel: t("close"),
1938
+ headless: true,
1939
+ className: "evo-merge-modal"
1940
+ }, inner);
1941
+ }
1686
1942
  function historyNode(overlay, t, onClose) {
1687
1943
  const Modal = platformModal();
1688
1944
  const title = t("history");
@@ -1712,7 +1968,7 @@ window.__ModuleLoader__.load({
1712
1968
  const kind = t("history.kind." + item.kind);
1713
1969
  const reason = reasonText(t, item.reasonCode, item.reason);
1714
1970
  return kind === reason ? kind : kind + " · " + reason;
1715
- })() + " · v" + String(item.lastVersion) + " · " + t("history.archivedAt", { date: new Date(item.archivedAt).toLocaleString() })))) : null);
1971
+ })() + " · v" + String(item.lastVersion) + " · " + (item.archivedAt === null ? t("history.archivedAtUnknown") : t("history.archivedAt", { date: formatLocalDateTime(item.archivedAt) ?? t("history.archivedAtUnknown") }))))) : null);
1716
1972
  return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1717
1973
  open: true,
1718
1974
  onClose,
@@ -1735,9 +1991,9 @@ window.__ModuleLoader__.load({
1735
1991
  const captureFocus = () => {
1736
1992
  returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1737
1993
  };
1738
- const openBody = (name, side, rowId) => {
1994
+ const openBoth = (a, b, rowId) => {
1739
1995
  captureFocus();
1740
- store.openBody(name, side, rowId);
1996
+ store.openPair(a, b, rowId);
1741
1997
  };
1742
1998
  const openHistory = () => {
1743
1999
  captureFocus();
@@ -1905,11 +2161,23 @@ window.__ModuleLoader__.load({
1905
2161
  "data-role": "decision-notice",
1906
2162
  "data-ok": decisionNotice.ok ? "true" : "false"
1907
2163
  }, decisionNotice.message);
2164
+ const taskBanner = view.counts.pending > 0 ? (0, react.createElement)(TaskBanner, {
2165
+ count: view.counts.pending,
2166
+ text: t("task.pending")
2167
+ }) : null;
1908
2168
  const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", {
1909
2169
  "data-role": "rows",
1910
2170
  id: "merge-rows",
1911
2171
  role: "tabpanel"
1912
- }, visibleRows.map((row) => rowNode(row, t, copy, sortHandlers, openBody, face.decide === void 0 ? void 0 : decideRow, deciding === row.id))) : null;
2172
+ }, visibleRows.map((row) => (0, react.createElement)(MergeRow, {
2173
+ row,
2174
+ t,
2175
+ copy,
2176
+ sort: sortHandlers,
2177
+ openBoth,
2178
+ onDecide: face.decide === void 0 ? void 0 : decideRow,
2179
+ busy: deciding === row.id
2180
+ }))) : null;
1913
2181
  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;
1914
2182
  const staleViewFailure = snapshot.viewState === "ready" ? snapshot.viewFailure : void 0;
1915
2183
  const activeFailure = viewFailure !== void 0 ? viewFailure : staleViewFailure !== void 0 ? staleViewFailure : queue.status === "error" ? queue.failure : void 0;
@@ -1975,38 +2243,48 @@ window.__ModuleLoader__.load({
1975
2243
  return (0, react.createElement)("section", {
1976
2244
  className: CSS_TAG_ID,
1977
2245
  "data-state": uiState
1978
- }, (0, react.createElement)("h2", { "data-role": "title" }, t("title")), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), blocked !== void 0 ? failureNode(t, blocked, fallback, copy) : (0, react.createElement)("div", { "data-role": "content" }, (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
1979
- type: "button",
1980
- "data-role": "start-scan",
1981
- onClick: () => {
1982
- copy(view.startCommands.scan);
1983
- }
1984
- }, t("start.scan")), (0, react.createElement)("button", {
1985
- type: "button",
1986
- "data-role": "start-pair",
1987
- onClick: () => {
1988
- copy(view.startCommands.pair);
1989
- }
1990
- }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(SettingsCard, {
1991
- view,
1992
- t
1993
- }), runNode(view, t, copy), tabs, expiredToggle, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
2246
+ }, (0, react.createElement)("h2", { "data-role": "title" }, t("title")), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), blocked !== void 0 ? failureNode(t, blocked, fallback, copy) : (0, react.createElement)("div", { "data-role": "content" }, taskBanner, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
1994
2247
  "data-role": "note",
1995
2248
  role: "status"
1996
2249
  }, t("refreshing")) : null, activeFailure === void 0 ? null : failureNode(t, activeFailure, fallback, copy), copyState.kind === "idle" ? null : copyState.kind === "copied" ? (0, react.createElement)("p", {
1997
2250
  "data-role": "copied",
1998
2251
  role: "status"
1999
- }, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("p", {
2252
+ }, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("div", {
2000
2253
  "data-role": "copy-failed",
2001
2254
  role: "alert"
2002
- }, t("copyFailed")), notice, list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2255
+ }, (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", {
2256
+ type: "button",
2257
+ "data-role": "copy-retry",
2258
+ onClick: () => {
2259
+ if (copyState.text !== void 0) copy(copyState.text);
2260
+ }
2261
+ }, t("copyRetry"))), notice, list, tabs, expiredToggle, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2003
2262
  type: "button",
2004
2263
  "data-role": "load-more",
2005
2264
  disabled: queue.status === "loading",
2006
2265
  onClick: () => {
2007
2266
  store.loadMore();
2008
2267
  }
2009
- }, t("loadMore")), (0, react.createElement)("button", {
2268
+ }, t("loadMore")), (0, react.createElement)(MergeSettings, {
2269
+ view,
2270
+ t
2271
+ }), (0, react.createElement)(MergeRunStrips, {
2272
+ view,
2273
+ t,
2274
+ copy
2275
+ }), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
2276
+ type: "button",
2277
+ "data-role": "start-scan",
2278
+ onClick: () => {
2279
+ copy(view.startCommands.scan);
2280
+ }
2281
+ }, t("start.scan")), (0, react.createElement)("button", {
2282
+ type: "button",
2283
+ "data-role": "start-pair",
2284
+ onClick: () => {
2285
+ copy(view.startCommands.pair);
2286
+ }
2287
+ }, t("start.pair"))), (0, react.createElement)("button", {
2010
2288
  type: "button",
2011
2289
  "data-role": "history",
2012
2290
  onClick: () => {
@@ -2018,7 +2296,7 @@ window.__ModuleLoader__.load({
2018
2296
  onClick: () => {
2019
2297
  copy(doctorCommand);
2020
2298
  }
2021
- }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), (0, react.createElement)("button", {
2299
+ }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : overlay.kind === "pair" ? pairNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), (0, react.createElement)("button", {
2022
2300
  type: "button",
2023
2301
  "data-role": "refresh",
2024
2302
  onClick: () => {
@@ -2331,6 +2609,54 @@ window.__ModuleLoader__.load({
2331
2609
  }
2332
2610
  });
2333
2611
  }
2612
+ async openPair(a, b, rowId) {
2613
+ const token = ++this.overlayToken;
2614
+ this.set({
2615
+ ...this.snapshot,
2616
+ overlay: {
2617
+ kind: "pair",
2618
+ a,
2619
+ b,
2620
+ status: "loading"
2621
+ }
2622
+ });
2623
+ const [left, right] = await Promise.all([this.source.body(a.name, a.side, rowId), this.source.body(b.name, b.side, rowId)]);
2624
+ if (this.disposed || token !== this.overlayToken) return;
2625
+ if (!left.ok || !right.ok) {
2626
+ const failure = !left.ok ? left.failure : !right.ok ? right.failure : void 0;
2627
+ if (failure?.kind === "blocked") {
2628
+ this.set({
2629
+ ...this.snapshot,
2630
+ viewState: "blocked",
2631
+ viewFailure: failure,
2632
+ overlay: void 0
2633
+ });
2634
+ return;
2635
+ }
2636
+ this.set({
2637
+ ...this.snapshot,
2638
+ overlay: {
2639
+ kind: "pair",
2640
+ a,
2641
+ b,
2642
+ status: "error",
2643
+ ...failure === void 0 ? {} : { failure }
2644
+ }
2645
+ });
2646
+ return;
2647
+ }
2648
+ this.set({
2649
+ ...this.snapshot,
2650
+ overlay: {
2651
+ kind: "pair",
2652
+ a,
2653
+ b,
2654
+ status: "ready",
2655
+ aBody: left.payload,
2656
+ bBody: right.payload
2657
+ }
2658
+ });
2659
+ }
2334
2660
  async openHistory() {
2335
2661
  const token = ++this.overlayToken;
2336
2662
  this.set({