@lmzhen/dsh-evolution-merge-ui 0.36.2 → 0.37.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
@@ -134,6 +134,22 @@ window.__ModuleLoader__.load({
134
134
  return EMPTY;
135
135
  }
136
136
  }
137
+ /**
138
+ * Classify one refused write from the exact route's response: the route answers 409 for a revision
139
+ * CAS loss and 400 with an "invalid config" error for a schema refusal, so the card can name the
140
+ * right cause instead of calling every refusal a concurrent edit.
141
+ * @param response - the non-ok response.
142
+ * @returns the refusal class; anything the route did not name stays transport.
143
+ */
144
+ async function refusalKind(response) {
145
+ if (response.status === 409) return "conflict";
146
+ try {
147
+ const body = await response.json();
148
+ return typeof body.error === "string" && body.error.startsWith("invalid config") ? "validation" : "transport";
149
+ } catch {
150
+ return "transport";
151
+ }
152
+ }
137
153
  async function write(namespace, ops, expectedRevision) {
138
154
  try {
139
155
  const response = await fetch(SETTINGS_MUTATE, {
@@ -148,14 +164,20 @@ window.__ModuleLoader__.load({
148
164
  ...expectedRevision === void 0 ? {} : { expectedRevision }
149
165
  })
150
166
  });
151
- if (!response.ok) return { ok: false };
167
+ if (!response.ok) return {
168
+ ok: false,
169
+ kind: await refusalKind(response)
170
+ };
152
171
  const body = await response.json();
153
172
  return {
154
173
  ok: body.ok === true,
155
174
  snapshot: snapshotFrom(body.data, namespace)
156
175
  };
157
176
  } catch {
158
- return { ok: false };
177
+ return {
178
+ ok: false,
179
+ kind: "transport"
180
+ };
159
181
  }
160
182
  }
161
183
  function createForm(namespace, ctx) {
@@ -219,6 +241,15 @@ window.__ModuleLoader__.load({
219
241
  op: "unset",
220
242
  path: [field]
221
243
  }]),
244
+ save: async (ops, expectedRevision) => {
245
+ const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
246
+ if (outcome.snapshot !== void 0) absorb(outcome.snapshot);
247
+ else await refresh();
248
+ return outcome.ok ? { written: true } : {
249
+ written: false,
250
+ kind: outcome.kind ?? "transport"
251
+ };
252
+ },
222
253
  mutate: async (ops, expectedRevision) => {
223
254
  const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
224
255
  if (outcome.snapshot !== void 0) absorb(outcome.snapshot);
@@ -296,7 +327,7 @@ window.__ModuleLoader__.load({
296
327
  //#endregion
297
328
  //#region ../evolution-client-kit/src/presentation/decision-frame.ts
298
329
  /** One decision card: title, facts, at most one primary, secondary actions and details. */
299
- function actionButton(action, role) {
330
+ function actionButton$1(action, role) {
300
331
  return (0, react.createElement)("button", {
301
332
  key: action.id,
302
333
  type: "button",
@@ -324,7 +355,7 @@ window.__ModuleLoader__.load({
324
355
  }, (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
356
  key: fact.label,
326
357
  "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"))));
358
+ }, (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$1(decision.primary, "primary"), secondary.map((action) => actionButton$1(action, "secondary"))));
328
359
  }
329
360
  //#endregion
330
361
  //#region ../evolution-client-kit/src/presentation/task-banner.ts
@@ -348,6 +379,30 @@ window.__ModuleLoader__.load({
348
379
  //#endregion
349
380
  //#region ../evolution-client-kit/src/presentation/empty-state.ts
350
381
  /** A compact completed/empty state with at most one primary next step. */
382
+ function actionButton(action, role) {
383
+ return (0, react.createElement)("button", {
384
+ key: action.id,
385
+ type: "button",
386
+ "data-role": role === "primary" ? "empty-primary" : "empty-secondary",
387
+ "data-action": action.id,
388
+ ...action.attributes ?? {},
389
+ disabled: action.disabled === true || action.busy === true,
390
+ title: action.title ?? action.label,
391
+ onClick: () => {
392
+ if (action.disabled !== true && action.busy !== true) action.onSelect();
393
+ }
394
+ }, action.label);
395
+ }
396
+ /** Render a quiet empty state; loading/failure remain the caller's separate states. */
397
+ function EmptyState({ title, description, tone = "neutral", primary, secondary, className, variant = "default" }) {
398
+ const actions = primary === void 0 && secondary === void 0 ? null : (0, react.createElement)("div", { "data-role": "empty-actions" }, primary === void 0 ? null : actionButton(primary, "primary"), secondary === void 0 ? null : actionButton(secondary, "secondary"));
399
+ return (0, react.createElement)("section", {
400
+ "data-role": "empty-state",
401
+ "data-tone": tone,
402
+ ...variant === "default" ? {} : { "data-variant": variant },
403
+ ...className === void 0 ? {} : { className }
404
+ }, (0, react.createElement)("h2", { "data-role": "empty-title" }, title), description === void 0 || description === "" ? null : (0, react.createElement)("p", { "data-role": "empty-description" }, description), actions);
405
+ }
351
406
  //#endregion
352
407
  //#region ../evolution-client-kit/src/presentation/disclosure-group.ts
353
408
  /** A native details group for normal/secondary facts. */
@@ -853,18 +908,18 @@ window.__ModuleLoader__.load({
853
908
  "skipKind.human-reject": "人工拒绝",
854
909
  "skipKind.over-limit": "超限",
855
910
  "skipKind.forced": "强制",
856
- "action.approve": "复制批准",
857
- "action.reject": "复制拒绝",
911
+ "action.approve": "复制批准命令",
912
+ "action.reject": "复制拒绝命令",
858
913
  "decision.approve": "批准",
859
914
  "decision.reject": "拒绝",
860
915
  "action.retry-candidates": "复制重试候选",
861
- "action.retry-resume": "重试续跑",
862
- "action.discard": "复制丢弃",
916
+ "action.retry-resume": "复制续跑命令",
917
+ "action.discard": "复制丢弃命令",
863
918
  "action.restore": "复制恢复命令",
864
919
  "action.pair": "复制配对",
865
920
  "action.status": "复制状态命令",
866
921
  "action.report": "复制报告命令",
867
- "action.cancel": "取消",
922
+ "action.cancel": "复制取消命令",
868
923
  "action.confirm": "确认复制",
869
924
  "disabled.expired": "已过期,不可用",
870
925
  "disabled.invalid": "状态不允许",
@@ -932,6 +987,7 @@ window.__ModuleLoader__.load({
932
987
  "history.reason.archived": "已归档",
933
988
  "history.reason.unknown": "原因未提供",
934
989
  "history.lastVersion": "最后版本",
990
+ "history.sourceRunId": "来源运行",
935
991
  "body.truncated": "正文过长,已截断。",
936
992
  "body.empty": "正文为空。",
937
993
  "body.totalBytes": "总字节",
@@ -1065,18 +1121,18 @@ window.__ModuleLoader__.load({
1065
1121
  "skipKind.human-reject": "human reject",
1066
1122
  "skipKind.over-limit": "over limit",
1067
1123
  "skipKind.forced": "forced",
1068
- "action.approve": "Copy approve",
1069
- "action.reject": "Copy reject",
1124
+ "action.approve": "Copy approve command",
1125
+ "action.reject": "Copy reject command",
1070
1126
  "decision.approve": "Approve",
1071
1127
  "decision.reject": "Reject",
1072
1128
  "action.retry-candidates": "Copy retry candidates",
1073
- "action.retry-resume": "Retry resume",
1074
- "action.discard": "Copy discard",
1129
+ "action.retry-resume": "Copy resume command",
1130
+ "action.discard": "Copy discard command",
1075
1131
  "action.restore": "Copy restore command",
1076
1132
  "action.pair": "Copy pair",
1077
1133
  "action.status": "Copy status command",
1078
1134
  "action.report": "Copy report command",
1079
- "action.cancel": "Cancel",
1135
+ "action.cancel": "Copy cancel command",
1080
1136
  "action.confirm": "Copy confirmation",
1081
1137
  "disabled.expired": "expired",
1082
1138
  "disabled.invalid": "invalid state",
@@ -1144,6 +1200,7 @@ window.__ModuleLoader__.load({
1144
1200
  "history.reason.archived": "archived",
1145
1201
  "history.reason.unknown": "reason not provided",
1146
1202
  "history.lastVersion": "Last version",
1203
+ "history.sourceRunId": "Source run",
1147
1204
  "body.truncated": "Body is truncated.",
1148
1205
  "body.empty": "Body is empty.",
1149
1206
  "body.totalBytes": "Total bytes",
@@ -1235,14 +1292,15 @@ window.__ModuleLoader__.load({
1235
1292
  //#region src/client/styles.ts
1236
1293
  const CSS_TAG_ID = "evolution-merge-panel";
1237
1294
  const CSS = [
1238
- ":where(.evolution-merge-panel) { --evo-type-lg:calc(1em + 2px);--evo-type-body:1em;--evo-type-sm:calc(1em - 1px);--evo-type-xs:calc(1em - 2px);--evo-space-2:2px;--evo-space-4:4px;--evo-space-6:6px;--evo-space-8:8px;--evo-space-10:10px;--evo-space-12:12px;--evo-space-14:14px;--evo-space-16:16px;--evo-space-20:20px;--evo-radius-chip:4px;--evo-radius-control:8px;--evo-radius-surface:12px;--evo-radius-pill:999px;--evo-hairline-width:.5px;--evo-hairline-marker:2px;--evo-measure-read:80ch;--evo-leading-base:1.5;--evo-tone-quiet:var(--dsw-alias-label-secondary);--evo-tone-primary:var(--dsw-alias-brand-primary);--evo-tone-danger:var(--dsw-alias-state-error-primary);--evo-tone-success:var(--dsw-alias-state-success-primary);--evo-focus-ring:2px solid var(--dsw-alias-label-primary);--evo-focus-offset:-2px;--evo-cap-aside:280px;--evo-cap-block:260px }",
1295
+ ":where(.evolution-merge-panel,.evo-merge-modal) { --evo-type-lg:calc(1em + 2px);--evo-type-body:1em;--evo-type-sm:calc(1em - 1px);--evo-type-xs:calc(1em - 2px);--evo-space-2:2px;--evo-space-4:4px;--evo-space-6:6px;--evo-space-8:8px;--evo-space-10:10px;--evo-space-12:12px;--evo-space-14:14px;--evo-space-16:16px;--evo-space-20:20px;--evo-radius-chip:4px;--evo-radius-control:8px;--evo-radius-surface:12px;--evo-radius-pill:999px;--evo-hairline-width:.5px;--evo-hairline-marker:2px;--evo-measure-read:80ch;--evo-leading-base:1.5;--evo-tone-quiet:var(--dsw-alias-label-secondary);--evo-tone-primary:var(--dsw-alias-brand-primary);--evo-tone-danger:var(--dsw-alias-state-error-primary);--evo-tone-success:var(--dsw-alias-state-success-primary);--evo-focus-ring:2px solid var(--dsw-alias-label-primary);--evo-focus-offset:-2px;--evo-cap-aside:280px;--evo-cap-block:260px }",
1239
1296
  ".evolution-merge-panel { display:flex; flex-direction:column; gap:var(--evo-space-12); padding:var(--evo-space-16); color:var(--dsw-alias-label-primary); font-size:var(--evo-type-sm); line-height:var(--evo-leading-base); }",
1240
1297
  ".evolution-merge-panel [data-role=\"hint\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
1241
1298
  ".evolution-merge-panel [data-role=\"stats\"] { display:flex; gap:var(--evo-space-8); flex-wrap:wrap; }",
1242
1299
  ".evolution-merge-panel [data-role=\"stat\"] { border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); padding:var(--evo-space-6) var(--evo-space-10); min-width:calc(var(--evo-space-16) * 4); }",
1243
1300
  ".evolution-merge-panel [data-role=\"stat-value\"] { font-size:var(--evo-type-lg); font-weight:600; }",
1244
1301
  ".evolution-merge-panel [data-role=\"stat-label\"] { font-size:var(--evo-type-xs); color:var(--dsw-alias-label-tertiary); }",
1245
- ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-6); border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
1302
+ ".evolution-merge-panel [data-role=\"queue-head\"] { display:flex; align-items:flex-end; justify-content:space-between; gap:var(--evo-space-8); border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
1303
+ ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-6); flex:1 1 auto; min-width:0; }",
1246
1304
  ".evolution-merge-panel [data-role=\"tab\"] { background:none; border:0; border-bottom:var(--evo-hairline-width) solid transparent; padding:var(--evo-space-6) var(--evo-space-8); cursor:pointer; color:inherit; }",
1247
1305
  ".evolution-merge-panel [data-role=\"tab\"][data-active=\"true\"] { border-bottom-color:var(--dsw-alias-brand-primary); }",
1248
1306
  ".evolution-merge-panel button:focus-visible { outline:2px solid currentColor !important; outline-offset:2px; box-shadow:0 0 0 2px currentColor !important; }",
@@ -1273,19 +1331,26 @@ window.__ModuleLoader__.load({
1273
1331
  ".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
1332
  ".evolution-merge-panel .merge-decision [data-role=\"decision-details\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); }",
1275
1333
  ".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; }",
1334
+ ".evolution-merge-panel [data-role=\"expired-toggle\"] { display:inline-flex; align-items:center; flex:0 0 auto; align-self:center; gap:var(--evo-space-4); margin-bottom:var(--evo-space-4); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); 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
1335
  ".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; }",
1278
1336
  ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
1279
1337
  ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
1280
1338
  ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
1281
1339
  ".evolution-merge-panel [data-role=\"overlay\"] { position:fixed; z-index:60; top:8vh; left:50%; transform:translateX(-50%); width:min(820px, calc(100vw - 64px)); max-height:78vh; overflow:auto; 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); box-shadow:0 0 var(--evo-space-8) var(--dsw-alias-border-l2); }",
1282
1340
  ".evo-merge-modal { width:min(820px, calc(100vw - 48px)); max-width:calc(100vw - 48px); max-height:min(78vh, 720px); }",
1283
- ".evo-merge-modal [data-role=\"overlay\"][data-platform-modal] { position:static; z-index:auto; top:auto; left:auto; transform:none; width:auto; box-sizing:border-box; max-height:none; overflow:auto; padding:var(--evo-space-20); border:0; border-radius:0; background:transparent; box-shadow:none; gap:var(--evo-space-8); }",
1341
+ ".evo-merge-modal [data-role=\"overlay\"][data-platform-modal] { position:static; z-index:auto; top:auto; left:auto; transform:none; width:auto; box-sizing:border-box; max-height:none; overflow:auto; padding:var(--evo-space-20); border:0; border-radius:0; box-shadow:none; background:var(--dsw-alias-bg-overlay); gap:var(--evo-space-8); }",
1284
1342
  ".evo-merge-modal [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1285
1343
  ".evo-merge-modal [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1286
1344
  ".evo-merge-modal [data-role=\"body-text\"] { margin:0; max-height:52vh; overflow:auto; white-space:pre-wrap; }",
1287
- ".evo-merge-modal [data-role=\"history-list\"] { margin:0; padding-left:var(--evo-space-20); }",
1288
- ".evo-merge-modal [data-role=\"history-item\"] { padding:var(--evo-space-4) 0; }",
1345
+ ".evo-merge-modal [data-role=\"history-list\"] { display:flex; flex-direction:column; gap:var(--evo-space-8); margin:0; padding:0; list-style:none; max-height:56vh; overflow:auto; }",
1346
+ ".evo-merge-modal [data-role=\"history-item\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); 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-2); }",
1347
+ ".evo-merge-modal [data-role=\"history-item-head\"] { display:flex; flex-wrap:wrap; align-items:center; gap:var(--evo-space-4) var(--evo-space-8); }",
1348
+ ".evo-merge-modal [data-role=\"history-item-name\"] { min-width:0; font-weight:600; overflow-wrap:anywhere; }",
1349
+ ".evo-merge-modal [data-role=\"history-item-kind\"] { flex:none; padding:var(--evo-space-2) var(--evo-space-6); border-radius:var(--evo-radius-pill); background:var(--dsw-alias-bg-layer-3); color:var(--dsw-alias-label-secondary); font-size:var(--evo-type-xs); }",
1350
+ ".evo-merge-modal [data-role=\"history-item-meta\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-4) var(--evo-space-8); color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
1351
+ ".evo-merge-modal [data-role=\"history-item-source\"], .evo-merge-modal [data-role=\"history-item-reason\"] { margin:0; color:var(--dsw-alias-label-secondary); overflow-wrap:anywhere; }",
1352
+ ".evo-merge-modal [data-role=\"overlay-close\"] { min-width:calc(var(--evo-space-20) + var(--evo-space-4)); min-height:calc(var(--evo-space-20) + var(--evo-space-4)); }",
1353
+ ".evolution-merge-panel [data-role=\"footer-tools\"] { display:flex; flex-wrap:wrap; align-items:center; gap:var(--evo-space-6) var(--evo-space-8); padding-top:var(--evo-space-8); border-top:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
1289
1354
  ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1290
1355
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1291
1356
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
@@ -1539,7 +1604,10 @@ window.__ModuleLoader__.load({
1539
1604
  //#region src/client/MergeRow.ts
1540
1605
  /** One merge row: summary, stage, facts, one primary decision and the copy/sort drawer. */
1541
1606
  function reasonText$1(t, reasonCode, fallback) {
1542
- return reasonCode === void 0 ? fallback : t("history.reason." + reasonCode);
1607
+ if (reasonCode === void 0) return fallback;
1608
+ const key = "history.reason." + reasonCode;
1609
+ const text = t(key);
1610
+ return text === key ? fallback : text;
1543
1611
  }
1544
1612
  function summaryNode(row, t) {
1545
1613
  if (row.state !== "pending" || row.summary === void 0) return null;
@@ -1722,7 +1790,7 @@ window.__ModuleLoader__.load({
1722
1790
  onClick: () => {
1723
1791
  if (!busy) copy(action.command);
1724
1792
  }
1725
- }, t("row.copy") + " " + t("action." + action.id))))));
1793
+ }, t("action." + action.id))))));
1726
1794
  }
1727
1795
  //#endregion
1728
1796
  //#region src/client/MergeSettings.ts
@@ -1766,6 +1834,27 @@ window.__ModuleLoader__.load({
1766
1834
  })));
1767
1835
  }
1768
1836
  //#endregion
1837
+ //#region src/client/HistoryItemRow.ts
1838
+ /** Structured rows for the read-only merge archive; the modal owns focus and scrolling. */
1839
+ function reasonText(t, item) {
1840
+ if (item.reasonCode !== void 0) {
1841
+ const key = "history.reason." + item.reasonCode;
1842
+ const text = t(key);
1843
+ if (text !== key) return text;
1844
+ }
1845
+ return item.reason;
1846
+ }
1847
+ /** One archive entry: identity and outcome, version/time, then source or reason. */
1848
+ function HistoryItemRow({ item, t }) {
1849
+ const kind = t("history.kind." + item.kind);
1850
+ const reason = reasonText(t, item);
1851
+ const archived = item.archivedAt === null ? t("history.archivedAtUnknown") : t("history.archivedAt", { date: formatLocalDateTime(item.archivedAt) ?? t("history.archivedAtUnknown") });
1852
+ return (0, react.createElement)("li", { "data-role": "history-item" }, (0, react.createElement)("div", { "data-role": "history-item-head" }, (0, react.createElement)("span", { "data-role": "history-item-name" }, item.name), (0, react.createElement)("span", {
1853
+ "data-role": "history-item-kind",
1854
+ "data-kind": item.kind
1855
+ }, kind)), (0, react.createElement)("div", { "data-role": "history-item-meta" }, (0, react.createElement)("span", { "data-role": "history-item-version" }, "v" + String(item.lastVersion)), (0, react.createElement)("span", { "data-role": "history-item-archived" }, archived)), item.sourceRunId === void 0 ? null : (0, react.createElement)("p", { "data-role": "history-item-source" }, t("history.sourceRunId") + " " + item.sourceRunId), reason === "" || reason === kind ? null : (0, react.createElement)("p", { "data-role": "history-item-reason" }, reason));
1856
+ }
1857
+ //#endregion
1769
1858
  //#region src/client/fallbacks.ts
1770
1859
  /** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
1771
1860
  const FALLBACK_COMMANDS = [
@@ -1803,12 +1892,6 @@ window.__ModuleLoader__.load({
1803
1892
  case "server": return "failureServer";
1804
1893
  }
1805
1894
  }
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
1895
  function blockedReasonKey(failure) {
1813
1896
  const code = failure.readiness.code ?? failure.code;
1814
1897
  if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
@@ -1961,14 +2044,14 @@ window.__ModuleLoader__.load({
1961
2044
  "data-role": "overlay-close",
1962
2045
  ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1963
2046
  onClick: onClose
1964
- }, 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" ? overlay.items === void 0 || overlay.items.length === 0 ? (0, react.createElement)("p", { "data-role": "history-empty" }, t("history.empty")) : (0, react.createElement)("ul", { "data-role": "history-list" }, overlay.items.map((item) => (0, react.createElement)("li", {
1965
- key: item.name + ":" + item.archivedAt,
1966
- "data-role": "history-item"
1967
- }, item.name + " · " + (() => {
1968
- const kind = t("history.kind." + item.kind);
1969
- const reason = reasonText(t, item.reasonCode, item.reason);
1970
- return kind === reason ? kind : kind + " · " + reason;
1971
- })() + " · v" + String(item.lastVersion) + " · " + (item.archivedAt === null ? t("history.archivedAtUnknown") : t("history.archivedAt", { date: formatLocalDateTime(item.archivedAt) ?? t("history.archivedAtUnknown") }))))) : null);
2047
+ }, 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" ? overlay.items === void 0 || overlay.items.length === 0 ? (0, react.createElement)(EmptyState, {
2048
+ title: t("history.empty"),
2049
+ variant: "compact"
2050
+ }) : (0, react.createElement)("ul", { "data-role": "history-list" }, overlay.items.map((item) => (0, react.createElement)(HistoryItemRow, {
2051
+ key: item.name + ":" + String(item.archivedAt),
2052
+ item,
2053
+ t
2054
+ }))) : null);
1972
2055
  return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1973
2056
  open: true,
1974
2057
  onClose,
@@ -2114,6 +2197,22 @@ window.__ModuleLoader__.load({
2114
2197
  saveOrder(pinRow(visibleRows.map((row) => row.id), menu.id, edge));
2115
2198
  setMenu(void 0);
2116
2199
  };
2200
+ (0, react.useEffect)(() => {
2201
+ if (menu === void 0 || PLATFORM_MENU !== void 0) return;
2202
+ const onKey = (event) => {
2203
+ if (event.key === "Escape") setMenu(void 0);
2204
+ };
2205
+ const onPointer = (event) => {
2206
+ const target = event.target;
2207
+ if (!(target instanceof Element) || target.closest("[data-role=\"sort-menu\"]") === null) setMenu(void 0);
2208
+ };
2209
+ window.addEventListener("keydown", onKey);
2210
+ window.addEventListener("pointerdown", onPointer, true);
2211
+ return () => {
2212
+ window.removeEventListener("keydown", onKey);
2213
+ window.removeEventListener("pointerdown", onPointer, true);
2214
+ };
2215
+ }, [menu]);
2117
2216
  const onTabKey = (event, current) => {
2118
2217
  const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
2119
2218
  const isEdge = event.key === "Home" || event.key === "End";
@@ -2187,8 +2286,8 @@ window.__ModuleLoader__.load({
2187
2286
  zIndex: 70,
2188
2287
  minWidth: "160px",
2189
2288
  maxWidth: "min(320px, calc(100vw - 16px))",
2190
- left: String(Math.min(menu.x, Math.max(0, (globalThis.innerWidth || 1440) - 180))) + "px",
2191
- top: String(Math.min(menu.y, Math.max(0, (globalThis.innerHeight || 800) - 110))) + "px"
2289
+ left: String(Math.min(menu.x, Math.max(0, (globalThis.innerWidth || 1440) - 320))) + "px",
2290
+ top: String(Math.min(menu.y, Math.max(0, (globalThis.innerHeight || 800) - 220))) + "px"
2192
2291
  };
2193
2292
  const menuNode = menu === void 0 ? null : PLATFORM_MENU === void 0 ? (0, react.createElement)("div", {
2194
2293
  "data-role": "sort-menu",
@@ -2258,7 +2357,7 @@ window.__ModuleLoader__.load({
2258
2357
  onClick: () => {
2259
2358
  if (copyState.text !== void 0) copy(copyState.text);
2260
2359
  }
2261
- }, t("copyRetry"))), notice, list, tabs, expiredToggle, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2360
+ }, t("copyRetry"))), notice, (0, react.createElement)("div", { "data-role": "queue-head" }, tabs, expiredToggle), list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2262
2361
  type: "button",
2263
2362
  "data-role": "load-more",
2264
2363
  disabled: queue.status === "loading",
@@ -2272,7 +2371,7 @@ window.__ModuleLoader__.load({
2272
2371
  view,
2273
2372
  t,
2274
2373
  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", {
2374
+ }), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)("div", { "data-role": "footer-tools" }, (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
2276
2375
  type: "button",
2277
2376
  "data-role": "start-scan",
2278
2377
  onClick: () => {
@@ -2296,13 +2395,13 @@ window.__ModuleLoader__.load({
2296
2395
  onClick: () => {
2297
2396
  copy(doctorCommand);
2298
2397
  }
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", {
2398
+ }, t("doctor.open")), (0, react.createElement)("button", {
2300
2399
  type: "button",
2301
2400
  "data-role": "refresh",
2302
2401
  onClick: () => {
2303
2402
  store.refresh();
2304
2403
  }
2305
- }, t("refresh")), menuNode));
2404
+ }, t("refresh"))), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : overlay.kind === "pair" ? pairNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), menuNode));
2306
2405
  }
2307
2406
  //#endregion
2308
2407
  //#region src/client/MergeStore.ts
@@ -0,0 +1,11 @@
1
+ /** Structured rows for the read-only merge archive; the modal owns focus and scrolling. */
2
+ import { type ReactNode } from 'react';
3
+ import type { MergeHistoryItem } from './api.ts';
4
+ type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
5
+ /** One archive entry: identity and outcome, version/time, then source or reason. */
6
+ export declare function HistoryItemRow({ item, t }: {
7
+ item: MergeHistoryItem;
8
+ t: Translate;
9
+ }): ReactNode;
10
+ export {};
11
+ //# sourceMappingURL=HistoryItemRow.d.ts.map
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.36.2",
14
+ "version": "0.37.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.36.2"
54
+ "@lmzhen/dsh-evolution-client-kit": "^0.37.0"
55
55
  },
56
56
  "engines": {
57
57
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"