@lmzhen/dsh-evolution-merge-ui 0.36.2 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);
@@ -273,6 +304,20 @@ window.__ModuleLoader__.load({
273
304
  }
274
305
  //#endregion
275
306
  //#region ../evolution-client-kit/src/format.ts
307
+ /** Parse an ISO string, epoch ms, or null into a valid Date; null when absent/invalid. */
308
+ function toLocalDate(value) {
309
+ if (value === null || value === void 0) return null;
310
+ const ms = typeof value === "number" ? value : Date.parse(value);
311
+ if (!Number.isFinite(ms)) return null;
312
+ const date = new Date(ms);
313
+ return Number.isNaN(date.getTime()) ? null : date;
314
+ }
315
+ const pad = (part) => String(part).padStart(2, "0");
316
+ /** The local clock part, HH:MM[:SS]. */
317
+ function clockOf(date, options) {
318
+ const head = pad(date.getHours()) + ":" + pad(date.getMinutes());
319
+ return options.seconds === false ? head : head + ":" + pad(date.getSeconds());
320
+ }
276
321
  /**
277
322
  * Format an ISO string, epoch milliseconds, or null as local wall time.
278
323
  * Invalid values return null; callers decide how to say "unknown".
@@ -281,14 +326,9 @@ window.__ModuleLoader__.load({
281
326
  * @returns the formatted local time, or null when the value is absent/invalid.
282
327
  */
283
328
  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());
329
+ const date = toLocalDate(value);
330
+ if (date === null) return null;
331
+ return String(date.getFullYear()) + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate()) + " " + clockOf(date, options);
292
332
  }
293
333
  //#endregion
294
334
  //#region ../evolution-client-kit/src/notice.ts
@@ -296,7 +336,7 @@ window.__ModuleLoader__.load({
296
336
  //#endregion
297
337
  //#region ../evolution-client-kit/src/presentation/decision-frame.ts
298
338
  /** One decision card: title, facts, at most one primary, secondary actions and details. */
299
- function actionButton(action, role) {
339
+ function actionButton$1(action, role) {
300
340
  return (0, react.createElement)("button", {
301
341
  key: action.id,
302
342
  type: "button",
@@ -324,7 +364,7 @@ window.__ModuleLoader__.load({
324
364
  }, (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
365
  key: fact.label,
326
366
  "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"))));
367
+ }, (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
368
  }
329
369
  //#endregion
330
370
  //#region ../evolution-client-kit/src/presentation/task-banner.ts
@@ -348,6 +388,30 @@ window.__ModuleLoader__.load({
348
388
  //#endregion
349
389
  //#region ../evolution-client-kit/src/presentation/empty-state.ts
350
390
  /** A compact completed/empty state with at most one primary next step. */
391
+ function actionButton(action, role) {
392
+ return (0, react.createElement)("button", {
393
+ key: action.id,
394
+ type: "button",
395
+ "data-role": role === "primary" ? "empty-primary" : "empty-secondary",
396
+ "data-action": action.id,
397
+ ...action.attributes ?? {},
398
+ disabled: action.disabled === true || action.busy === true,
399
+ title: action.title ?? action.label,
400
+ onClick: () => {
401
+ if (action.disabled !== true && action.busy !== true) action.onSelect();
402
+ }
403
+ }, action.label);
404
+ }
405
+ /** Render a quiet empty state; loading/failure remain the caller's separate states. */
406
+ function EmptyState({ title, description, tone = "neutral", primary, secondary, className, variant = "default" }) {
407
+ 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"));
408
+ return (0, react.createElement)("section", {
409
+ "data-role": "empty-state",
410
+ "data-tone": tone,
411
+ ...variant === "default" ? {} : { "data-variant": variant },
412
+ ...className === void 0 ? {} : { className }
413
+ }, (0, react.createElement)("h2", { "data-role": "empty-title" }, title), description === void 0 || description === "" ? null : (0, react.createElement)("p", { "data-role": "empty-description" }, description), actions);
414
+ }
351
415
  //#endregion
352
416
  //#region ../evolution-client-kit/src/presentation/disclosure-group.ts
353
417
  /** A native details group for normal/secondary facts. */
@@ -779,11 +843,13 @@ window.__ModuleLoader__.load({
779
843
  const NS = "evolution-merge";
780
844
  const zh = {
781
845
  "entry.label": "技能合并",
846
+ "session.missing": "没有当前会话;请打开会话后重试",
847
+ "decide.failed": "审批请求失败",
782
848
  "title": "合并建议",
783
849
  "task.pending": "条合并建议待处理",
784
- "start.scan": "复制扫描命令",
785
- "start.pair": "复制配对命令模板",
786
- "degraded": "读模型降级:",
850
+ "start.scan": "复制查找重复技能的命令",
851
+ "start.pair": "复制配对命令",
852
+ "degraded": "读取结果不完整:",
787
853
  "copied": "已复制:",
788
854
  "copyFailed": "复制失败:浏览器没有接受剪贴板写入。",
789
855
  "copyRetry": "重试复制",
@@ -798,7 +864,7 @@ window.__ModuleLoader__.load({
798
864
  "tab.fuse": "待合并",
799
865
  "tab.skipped": "无需合并",
800
866
  "empty.verify": "没有待判断的对。",
801
- "empty.scope": "这里只做两轮合成;机械追加属于 skill 管理,不进入合并队列。",
867
+ "empty.scope": "这里只处理两轮合并;单纯追加内容属于技能管理,不会进入合并队列。",
802
868
  "empty.pending": "没有待批准的合并。",
803
869
  "empty.fuse": "没有待合并的对。",
804
870
  "empty.skipped": "没有标记为无需合并的对。",
@@ -810,10 +876,10 @@ window.__ModuleLoader__.load({
810
876
  "approval.off": "审批关:合并自动执行。",
811
877
  "failed": "读取失败,保留上次结果。",
812
878
  "blocked.title": "合并状态未就绪",
813
- "blocked.hint": "迁移/备份修复后刷新;面板不会自动降级。",
879
+ "blocked.hint": "修好合并数据后再刷新;面板不会自动退回旧数据。",
814
880
  "blocked.doctor": "复制诊断命令",
815
881
  "blocked.detail": "查看原始信息",
816
- "blocked.reason.migration-blocked": "合并状态需要迁移,面板已暂停读取。",
882
+ "blocked.reason.migration-blocked": "合并数据需要升级,面板已暂停读取。",
817
883
  "blocked.reason.needs-repair": "合并状态需要修复,面板已暂停读取。",
818
884
  "blocked.reason.forward-only": "合并状态版本过新,当前面板暂不支持。",
819
885
  "blocked.reason.unknown": "合并状态未就绪,面板已暂停读取。",
@@ -853,18 +919,18 @@ window.__ModuleLoader__.load({
853
919
  "skipKind.human-reject": "人工拒绝",
854
920
  "skipKind.over-limit": "超限",
855
921
  "skipKind.forced": "强制",
856
- "action.approve": "复制批准",
857
- "action.reject": "复制拒绝",
922
+ "action.approve": "复制批准命令",
923
+ "action.reject": "复制拒绝命令",
858
924
  "decision.approve": "批准",
859
925
  "decision.reject": "拒绝",
860
- "action.retry-candidates": "复制重试候选",
861
- "action.retry-resume": "重试续跑",
862
- "action.discard": "复制丢弃",
926
+ "action.retry-candidates": "复制重试命令",
927
+ "action.retry-resume": "复制继续运行命令",
928
+ "action.discard": "复制丢弃命令",
863
929
  "action.restore": "复制恢复命令",
864
930
  "action.pair": "复制配对",
865
931
  "action.status": "复制状态命令",
866
932
  "action.report": "复制报告命令",
867
- "action.cancel": "取消",
933
+ "action.cancel": "复制取消命令",
868
934
  "action.confirm": "确认复制",
869
935
  "disabled.expired": "已过期,不可用",
870
936
  "disabled.invalid": "状态不允许",
@@ -878,9 +944,9 @@ window.__ModuleLoader__.load({
878
944
  "failureServer": "读取失败",
879
945
  "failureUnauthorized": "未登录",
880
946
  "failureForbidden": "无权访问",
881
- "failureUnmounted": "宿主未挂载",
947
+ "failureUnmounted": "功能未启用",
882
948
  "failureCapability": "子代理通道不可用",
883
- "failureCapabilityHint": "先运行全局诊断,或复制下方命令继续处理。preset/host-only 形态没有面板时仍可用这些命令兜底。",
949
+ "failureCapabilityHint": "先运行全局诊断,或复制下方命令继续处理。没有面板的安装方式(preset / host-only)仍可用这些命令。",
884
950
  "failureBodyAmbiguous": "同名技能匹配到多行,无法确定读取哪一个;请用唯一名称或从具体行打开。",
885
951
  "fallbackHint": "可复制命令见下方",
886
952
  "body.missing": "正文不存在或不可读。",
@@ -932,6 +998,7 @@ window.__ModuleLoader__.load({
932
998
  "history.reason.archived": "已归档",
933
999
  "history.reason.unknown": "原因未提供",
934
1000
  "history.lastVersion": "最后版本",
1001
+ "history.sourceRunId": "来源运行",
935
1002
  "body.truncated": "正文过长,已截断。",
936
1003
  "body.empty": "正文为空。",
937
1004
  "body.totalBytes": "总字节",
@@ -991,11 +1058,13 @@ window.__ModuleLoader__.load({
991
1058
  });
992
1059
  const en = {
993
1060
  "entry.label": "Skill merge",
1061
+ "session.missing": "No current session; open a session and try again",
1062
+ "decide.failed": "The approval request failed",
994
1063
  "title": "Merge suggestions",
995
1064
  "task.pending": "merge suggestions need attention",
996
- "start.scan": "Copy scan command",
997
- "start.pair": "Copy pair command template",
998
- "degraded": "Degraded read model:",
1065
+ "start.scan": "Copy the find-duplicate-skills command",
1066
+ "start.pair": "Copy the pair command",
1067
+ "degraded": "Incomplete read:",
999
1068
  "copied": "Copied:",
1000
1069
  "copyFailed": "Copy failed: the browser did not accept the clipboard write.",
1001
1070
  "copyRetry": "Retry copy",
@@ -1010,7 +1079,7 @@ window.__ModuleLoader__.load({
1010
1079
  "tab.fuse": "Ready to merge",
1011
1080
  "tab.skipped": "Skipped",
1012
1081
  "empty.verify": "No pairs to judge.",
1013
- "empty.scope": "This panel only performs two-round synthesis; mechanical append belongs to skill management and does not enter the merge queue.",
1082
+ "empty.scope": "Only two-round merges are handled here; plain appends belong to skill management and never enter this queue.",
1014
1083
  "empty.pending": "No pending merges.",
1015
1084
  "empty.fuse": "No pairs ready to merge.",
1016
1085
  "empty.skipped": "No pairs marked as not worth merging.",
@@ -1022,10 +1091,10 @@ window.__ModuleLoader__.load({
1022
1091
  "approval.off": "Approval off: merges run automatically.",
1023
1092
  "failed": "Read failed; keeping the last result.",
1024
1093
  "blocked.title": "Merge state blocked",
1025
- "blocked.hint": "Fix the migration/backup, then refresh. The panel does not fall back automatically.",
1094
+ "blocked.hint": "Fix the merge data and refresh; the panel will not fall back to older data by itself.",
1026
1095
  "blocked.doctor": "Copy diagnostics command",
1027
1096
  "blocked.detail": "Show host message",
1028
- "blocked.reason.migration-blocked": "Merge state needs migration; the panel is paused.",
1097
+ "blocked.reason.migration-blocked": "The merge data needs an upgrade; reading is paused.",
1029
1098
  "blocked.reason.needs-repair": "Merge state needs repair; the panel is paused.",
1030
1099
  "blocked.reason.forward-only": "Merge state was written by a newer version; this panel cannot read it.",
1031
1100
  "blocked.reason.unknown": "Merge state is not ready; the panel is paused.",
@@ -1065,18 +1134,18 @@ window.__ModuleLoader__.load({
1065
1134
  "skipKind.human-reject": "human reject",
1066
1135
  "skipKind.over-limit": "over limit",
1067
1136
  "skipKind.forced": "forced",
1068
- "action.approve": "Copy approve",
1069
- "action.reject": "Copy reject",
1137
+ "action.approve": "Copy approve command",
1138
+ "action.reject": "Copy reject command",
1070
1139
  "decision.approve": "Approve",
1071
1140
  "decision.reject": "Reject",
1072
- "action.retry-candidates": "Copy retry candidates",
1073
- "action.retry-resume": "Retry resume",
1074
- "action.discard": "Copy discard",
1141
+ "action.retry-candidates": "Copy retry command",
1142
+ "action.retry-resume": "Copy resume command",
1143
+ "action.discard": "Copy discard command",
1075
1144
  "action.restore": "Copy restore command",
1076
1145
  "action.pair": "Copy pair",
1077
1146
  "action.status": "Copy status command",
1078
1147
  "action.report": "Copy report command",
1079
- "action.cancel": "Cancel",
1148
+ "action.cancel": "Copy cancel command",
1080
1149
  "action.confirm": "Copy confirmation",
1081
1150
  "disabled.expired": "expired",
1082
1151
  "disabled.invalid": "invalid state",
@@ -1090,9 +1159,9 @@ window.__ModuleLoader__.load({
1090
1159
  "failureServer": "Read failed",
1091
1160
  "failureUnauthorized": "Not signed in",
1092
1161
  "failureForbidden": "Forbidden",
1093
- "failureUnmounted": "Host not mounted",
1162
+ "failureUnmounted": "Feature not enabled",
1094
1163
  "failureCapability": "Subagent channel unavailable",
1095
- "failureCapabilityHint": "Run global diagnostics first, or copy a command below to continue. The command fallback also covers preset/host-only shapes without a panel.",
1164
+ "failureCapabilityHint": "Run the global diagnostics first, or copy a command below. Installs without the panel (preset / host-only) can still use these commands.",
1096
1165
  "failureBodyAmbiguous": "The skill name matches multiple rows, so the body is ambiguous. Use a unique name or open it from a specific row.",
1097
1166
  "fallbackHint": "Copy the command below",
1098
1167
  "body.missing": "Body missing or unreadable.",
@@ -1144,6 +1213,7 @@ window.__ModuleLoader__.load({
1144
1213
  "history.reason.archived": "archived",
1145
1214
  "history.reason.unknown": "reason not provided",
1146
1215
  "history.lastVersion": "Last version",
1216
+ "history.sourceRunId": "Source run",
1147
1217
  "body.truncated": "Body is truncated.",
1148
1218
  "body.empty": "Body is empty.",
1149
1219
  "body.totalBytes": "Total bytes",
@@ -1235,14 +1305,15 @@ window.__ModuleLoader__.load({
1235
1305
  //#region src/client/styles.ts
1236
1306
  const CSS_TAG_ID = "evolution-merge-panel";
1237
1307
  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 }",
1308
+ ":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
1309
  ".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
1310
  ".evolution-merge-panel [data-role=\"hint\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
1241
1311
  ".evolution-merge-panel [data-role=\"stats\"] { display:flex; gap:var(--evo-space-8); flex-wrap:wrap; }",
1242
1312
  ".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
1313
  ".evolution-merge-panel [data-role=\"stat-value\"] { font-size:var(--evo-type-lg); font-weight:600; }",
1244
1314
  ".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); }",
1315
+ ".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); }",
1316
+ ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; flex-wrap:wrap; gap:var(--evo-space-6); flex:1 1 auto; min-width:0; }",
1246
1317
  ".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
1318
  ".evolution-merge-panel [data-role=\"tab\"][data-active=\"true\"] { border-bottom-color:var(--dsw-alias-brand-primary); }",
1248
1319
  ".evolution-merge-panel button:focus-visible { outline:2px solid currentColor !important; outline-offset:2px; box-shadow:0 0 0 2px currentColor !important; }",
@@ -1273,19 +1344,26 @@ window.__ModuleLoader__.load({
1273
1344
  ".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
1345
  ".evolution-merge-panel .merge-decision [data-role=\"decision-details\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); }",
1275
1346
  ".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; }",
1347
+ ".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
1348
  ".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
1349
  ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
1279
1350
  ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
1280
1351
  ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
1281
1352
  ".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
1353
  ".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); }",
1354
+ ".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
1355
  ".evo-merge-modal [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1285
1356
  ".evo-merge-modal [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1286
1357
  ".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; }",
1358
+ ".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; }",
1359
+ ".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); }",
1360
+ ".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); }",
1361
+ ".evo-merge-modal [data-role=\"history-item-name\"] { min-width:0; font-weight:600; overflow-wrap:anywhere; }",
1362
+ ".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); }",
1363
+ ".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); }",
1364
+ ".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; }",
1365
+ ".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)); }",
1366
+ ".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
1367
  ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1290
1368
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1291
1369
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
@@ -1539,7 +1617,10 @@ window.__ModuleLoader__.load({
1539
1617
  //#region src/client/MergeRow.ts
1540
1618
  /** One merge row: summary, stage, facts, one primary decision and the copy/sort drawer. */
1541
1619
  function reasonText$1(t, reasonCode, fallback) {
1542
- return reasonCode === void 0 ? fallback : t("history.reason." + reasonCode);
1620
+ if (reasonCode === void 0) return fallback;
1621
+ const key = "history.reason." + reasonCode;
1622
+ const text = t(key);
1623
+ return text === key ? fallback : text;
1543
1624
  }
1544
1625
  function summaryNode(row, t) {
1545
1626
  if (row.state !== "pending" || row.summary === void 0) return null;
@@ -1722,7 +1803,7 @@ window.__ModuleLoader__.load({
1722
1803
  onClick: () => {
1723
1804
  if (!busy) copy(action.command);
1724
1805
  }
1725
- }, t("row.copy") + " " + t("action." + action.id))))));
1806
+ }, t("action." + action.id))))));
1726
1807
  }
1727
1808
  //#endregion
1728
1809
  //#region src/client/MergeSettings.ts
@@ -1766,6 +1847,27 @@ window.__ModuleLoader__.load({
1766
1847
  })));
1767
1848
  }
1768
1849
  //#endregion
1850
+ //#region src/client/HistoryItemRow.ts
1851
+ /** Structured rows for the read-only merge archive; the modal owns focus and scrolling. */
1852
+ function reasonText(t, item) {
1853
+ if (item.reasonCode !== void 0) {
1854
+ const key = "history.reason." + item.reasonCode;
1855
+ const text = t(key);
1856
+ if (text !== key) return text;
1857
+ }
1858
+ return item.reason;
1859
+ }
1860
+ /** One archive entry: identity and outcome, version/time, then source or reason. */
1861
+ function HistoryItemRow({ item, t }) {
1862
+ const kind = t("history.kind." + item.kind);
1863
+ const reason = reasonText(t, item);
1864
+ const archived = item.archivedAt === null ? t("history.archivedAtUnknown") : t("history.archivedAt", { date: formatLocalDateTime(item.archivedAt) ?? t("history.archivedAtUnknown") });
1865
+ 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", {
1866
+ "data-role": "history-item-kind",
1867
+ "data-kind": item.kind
1868
+ }, 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));
1869
+ }
1870
+ //#endregion
1769
1871
  //#region src/client/fallbacks.ts
1770
1872
  /** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
1771
1873
  const FALLBACK_COMMANDS = [
@@ -1803,12 +1905,6 @@ window.__ModuleLoader__.load({
1803
1905
  case "server": return "failureServer";
1804
1906
  }
1805
1907
  }
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
1908
  function blockedReasonKey(failure) {
1813
1909
  const code = failure.readiness.code ?? failure.code;
1814
1910
  if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
@@ -1961,14 +2057,14 @@ window.__ModuleLoader__.load({
1961
2057
  "data-role": "overlay-close",
1962
2058
  ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1963
2059
  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);
2060
+ }, 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, {
2061
+ title: t("history.empty"),
2062
+ variant: "compact"
2063
+ }) : (0, react.createElement)("ul", { "data-role": "history-list" }, overlay.items.map((item) => (0, react.createElement)(HistoryItemRow, {
2064
+ key: item.name + ":" + String(item.archivedAt),
2065
+ item,
2066
+ t
2067
+ }))) : null);
1972
2068
  return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1973
2069
  open: true,
1974
2070
  onClose,
@@ -2114,6 +2210,22 @@ window.__ModuleLoader__.load({
2114
2210
  saveOrder(pinRow(visibleRows.map((row) => row.id), menu.id, edge));
2115
2211
  setMenu(void 0);
2116
2212
  };
2213
+ (0, react.useEffect)(() => {
2214
+ if (menu === void 0 || PLATFORM_MENU !== void 0) return;
2215
+ const onKey = (event) => {
2216
+ if (event.key === "Escape") setMenu(void 0);
2217
+ };
2218
+ const onPointer = (event) => {
2219
+ const target = event.target;
2220
+ if (!(target instanceof Element) || target.closest("[data-role=\"sort-menu\"]") === null) setMenu(void 0);
2221
+ };
2222
+ window.addEventListener("keydown", onKey);
2223
+ window.addEventListener("pointerdown", onPointer, true);
2224
+ return () => {
2225
+ window.removeEventListener("keydown", onKey);
2226
+ window.removeEventListener("pointerdown", onPointer, true);
2227
+ };
2228
+ }, [menu]);
2117
2229
  const onTabKey = (event, current) => {
2118
2230
  const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
2119
2231
  const isEdge = event.key === "Home" || event.key === "End";
@@ -2187,8 +2299,8 @@ window.__ModuleLoader__.load({
2187
2299
  zIndex: 70,
2188
2300
  minWidth: "160px",
2189
2301
  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"
2302
+ left: String(Math.min(menu.x, Math.max(0, (globalThis.innerWidth || 1440) - 320))) + "px",
2303
+ top: String(Math.min(menu.y, Math.max(0, (globalThis.innerHeight || 800) - 220))) + "px"
2192
2304
  };
2193
2305
  const menuNode = menu === void 0 ? null : PLATFORM_MENU === void 0 ? (0, react.createElement)("div", {
2194
2306
  "data-role": "sort-menu",
@@ -2258,7 +2370,7 @@ window.__ModuleLoader__.load({
2258
2370
  onClick: () => {
2259
2371
  if (copyState.text !== void 0) copy(copyState.text);
2260
2372
  }
2261
- }, t("copyRetry"))), notice, list, tabs, expiredToggle, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2373
+ }, 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
2374
  type: "button",
2263
2375
  "data-role": "load-more",
2264
2376
  disabled: queue.status === "loading",
@@ -2272,7 +2384,7 @@ window.__ModuleLoader__.load({
2272
2384
  view,
2273
2385
  t,
2274
2386
  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", {
2387
+ }), 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
2388
  type: "button",
2277
2389
  "data-role": "start-scan",
2278
2390
  onClick: () => {
@@ -2296,13 +2408,13 @@ window.__ModuleLoader__.load({
2296
2408
  onClick: () => {
2297
2409
  copy(doctorCommand);
2298
2410
  }
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", {
2411
+ }, t("doctor.open")), (0, react.createElement)("button", {
2300
2412
  type: "button",
2301
2413
  "data-role": "refresh",
2302
2414
  onClick: () => {
2303
2415
  store.refresh();
2304
2416
  }
2305
- }, t("refresh")), menuNode));
2417
+ }, 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
2418
  }
2307
2419
  //#endregion
2308
2420
  //#region src/client/MergeStore.ts
@@ -2769,14 +2881,14 @@ window.__ModuleLoader__.load({
2769
2881
  const sessionId = currentSessionId(ctx);
2770
2882
  if (sessionId === void 0) return {
2771
2883
  ok: false,
2772
- message: "没有当前会话;请打开会话后重试"
2884
+ message: t("session.missing")
2773
2885
  };
2774
2886
  const result = await decide(rowId, verb, sessionId);
2775
2887
  if (result.ok) return result.payload;
2776
2888
  const failure = result.failure;
2777
2889
  return {
2778
2890
  ok: false,
2779
- message: "message" in failure && typeof failure.message === "string" ? failure.message : "审批请求失败"
2891
+ message: "message" in failure && typeof failure.message === "string" ? failure.message : t("decide.failed")
2780
2892
  };
2781
2893
  },
2782
2894
  ...workspaceId === void 0 ? {} : { workspaceId }
@@ -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.1",
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.1"
55
55
  },
56
56
  "engines": {
57
57
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"