@lmzhen/dsh-evolution-merge-ui 0.32.4 → 0.32.6

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/README.md CHANGED
@@ -1,11 +1,7 @@
1
1
  # @lmzhen/dsh-evolution-merge-ui
2
2
 
3
- Browser-only sidebar panel for the merge queues. It reads the four host routes
4
- and never writes skills: row actions are copyable `/evolution merge ...`
5
- commands.
3
+ Skill-merge view inside the shared evolution workbench. It ships only in the `all` installation form.
6
4
 
7
- - Queues: pending / retry / skipped.
8
- - Row facts: target, confidence, failure, attempts, origin, staging expiry.
9
- - Run strip: latest run id/state/stage/progress.
10
- - Four states: loading / ready / empty / failed; view schemaVersion guard.
11
- - Mounted by the `all` bundle only.
5
+ The view is read-only: action buttons copy the appropriate `/evolution merge` or `/evolution doctor` command; they do not approve, execute, or undo a merge in the browser. Internal run fields (run id, stage, parent, result ref) live under the view's run-details disclosure so the queue and decision facts come first.
6
+
7
+ The host entry intentionally registers nothing. The Loader discovers the `dsh.client` manifest and serves the browser bundle, which registers itself into `evolution.workbench.view` and uses the shared client-kit lifecycle and the four merge read routes.
package/lib/client.js CHANGED
@@ -5,28 +5,24 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react = require("react");
8
- //#region ../evolution-client-kit/src/panel.ts
9
- function interpolate(text, values) {
10
- if (values === void 0) return text;
11
- return text.replace(/\{(\w+)\}/g, (_match, key) => String(values[key] ?? ""));
12
- }
8
+ //#region ../evolution-client-kit/src/workbench-view.ts
13
9
  function injectStyles(doc, css, tagId) {
14
10
  if (doc === void 0) return () => {};
15
11
  doc.querySelector("style[data-plugin-css=\"" + tagId + "\"]")?.remove();
16
12
  const tag = doc.createElement("style");
17
13
  tag.setAttribute("data-plugin-css", tagId);
18
14
  tag.textContent = css;
19
- doc.head.append(tag);
15
+ doc.head.appendChild(tag);
20
16
  return () => {
21
17
  tag.remove();
22
18
  };
23
19
  }
24
20
  /**
25
- * Register one sidebar row and its main body under one lifecycle.
26
- * @param ctx - the client context carrying the slots and locale seats.
27
- * @param spec - the feature-specific row/body descriptor.
21
+ * Register one view into `evolution.workbench.view`.
22
+ * @param ctx - client cordis context.
23
+ * @param spec - view identity, copy, styles, component and face factory.
28
24
  */
29
- function registerSidebarPanel(ctx, spec) {
25
+ function registerWorkbenchView(ctx, spec) {
30
26
  const seam = ctx;
31
27
  let registered = false;
32
28
  ctx.effect(() => {
@@ -35,12 +31,12 @@ window.__ModuleLoader__.load({
35
31
  registered = true;
36
32
  return dispose;
37
33
  } catch (error) {
38
- ctx.logger.warn(spec.namespace + ": the locale seat refused the dictionary (" + String(error) + "); the panel falls back to the bundle copy");
34
+ ctx.logger.warn(spec.namespace + ": locale refused (" + String(error) + "); the view falls back to the bundle copy");
39
35
  return () => {};
40
36
  }
41
37
  }, spec.namespace + ": locale");
42
38
  ctx.effect(() => injectStyles(typeof document === "undefined" ? void 0 : document, spec.styles.css, spec.styles.tagId), spec.namespace + ": styles");
43
- const fallback = spec.dictionaries[spec.fallback ?? "zh"];
39
+ const fallback = spec.dictionaries.zh;
44
40
  const lookup = (key) => {
45
41
  const own = fallback[key] ?? key;
46
42
  if (!registered) return own;
@@ -50,47 +46,36 @@ window.__ModuleLoader__.load({
50
46
  return own;
51
47
  }
52
48
  };
53
- const t = (key, values) => interpolate(lookup(key), values);
54
- const label = () => lookup("entry.label");
55
- seam.slots.inject("sidebar.panellist", function* () {
49
+ const t = (key, values) => {
50
+ const template = lookup(key);
51
+ if (values === void 0) return template;
52
+ return template.replace(/\{(\w+)\}/g, (match, slot) => values[slot] === void 0 ? match : String(values[slot]));
53
+ };
54
+ seam.slots.inject("evolution.workbench.view", function* () {
56
55
  yield seam.slots.register({
57
- name: "sidebar.panellist",
56
+ name: "evolution.workbench.view",
58
57
  id: spec.id,
59
58
  order: spec.order,
60
- label
61
- }, spec.icon);
62
- yield seam.slots.register({
63
- name: "main",
64
- key: spec.id,
65
- inject: () => spec.body(t)
59
+ label: () => t("entry.label"),
60
+ inject: () => spec.face(t)
66
61
  }, spec.component);
67
62
  });
68
63
  }
69
64
  //#endregion
70
- //#region ../evolution-client-kit/src/workspace.ts
65
+ //#region ../evolution-client-kit/src/generated/routes.generated.ts
71
66
  /**
72
- * Best-effort workspace id; a seat that is absent or refuses leaves the panel in local scope.
73
- * @param ctx - the client context.
74
- * @returns the current workspace id, or undefined.
67
+ * Every family host path, generated from evolution-core's HOST_ROUTES (the ONE declaration).
68
+ * A browser half cannot import the host package at runtime (client bundles keep @deepseek-ai/*
69
+ * external), so the table is mirrored here mechanically; a rename in core regenerates all three
70
+ * client copies, and the generator's --check makes a stale copy red.
75
71
  */
76
- function panelWorkspaceId(ctx) {
77
- const get = ctx.get;
78
- if (typeof get !== "function") return void 0;
79
- for (const name of ["workspace", "uiWorkspace"]) try {
80
- const seat = get.call(ctx, name);
81
- const id = seat?.workspaceId ?? seat?.currentWorkspaceId;
82
- if (typeof id === "string" && id.trim() !== "") return id;
83
- } catch {}
84
- }
85
- //#endregion
86
- //#region src/client/api.ts
87
- /** Read-only merge wire client. Classifies every failure and never writes. */
88
- const HOST_ROUTES = {
72
+ const ROUTES = {
89
73
  approval: {
90
74
  pending: "/api/dsh-evolution/approval/pending",
91
75
  preview: "/api/dsh-evolution/approval/preview",
92
76
  approve: "/api/dsh-evolution/approval/approve",
93
- reject: "/api/dsh-evolution/approval/reject"
77
+ reject: "/api/dsh-evolution/approval/reject",
78
+ release: "/api/dsh-evolution/approval/release"
94
79
  },
95
80
  "skill-history": {
96
81
  skills: "/api/dsh-evolution/skill-history/skills",
@@ -109,8 +94,166 @@ window.__ModuleLoader__.load({
109
94
  body: "/api/dsh-evolution/merge/body",
110
95
  history: "/api/dsh-evolution/merge/history"
111
96
  },
112
- diagnostics: { view: "/api/dsh-evolution/diagnostics" }
113
- }.merge;
97
+ diagnostics: { view: "/api/dsh-evolution/diagnostics" },
98
+ settings: {
99
+ describe: "/api/dsh-evolution/settings/describe",
100
+ mutate: "/api/dsh-evolution/settings/mutate"
101
+ }
102
+ };
103
+ //#endregion
104
+ //#region ../evolution-client-kit/src/settings-seat.ts
105
+ const SETTINGS_DESCRIBE = ROUTES.settings.describe;
106
+ const SETTINGS_MUTATE = ROUTES.settings.mutate;
107
+ const EMPTY = Object.freeze({
108
+ status: "unavailable",
109
+ value: void 0,
110
+ user: void 0,
111
+ writable: false,
112
+ base: void 0,
113
+ revision: void 0,
114
+ reason: "transport"
115
+ });
116
+ function snapshotFrom(value, ns) {
117
+ if (typeof value !== "object" || value === null) return EMPTY;
118
+ const list = value.namespaces;
119
+ if (!Array.isArray(list)) return EMPTY;
120
+ const item = list.find((candidate) => typeof candidate === "object" && candidate !== null && candidate.ns === ns);
121
+ return item === void 0 ? {
122
+ ...EMPTY,
123
+ reason: "not-served"
124
+ } : item;
125
+ }
126
+ async function describe(namespace) {
127
+ try {
128
+ const response = await fetch(SETTINGS_DESCRIBE, { headers: { accept: "application/json" } });
129
+ if (!response.ok) return EMPTY;
130
+ const body = await response.json();
131
+ if (body.ok !== true) return EMPTY;
132
+ return snapshotFrom(body.data, namespace);
133
+ } catch {
134
+ return EMPTY;
135
+ }
136
+ }
137
+ async function write(namespace, ops, expectedRevision) {
138
+ try {
139
+ const response = await fetch(SETTINGS_MUTATE, {
140
+ method: "POST",
141
+ headers: {
142
+ "content-type": "application/json",
143
+ accept: "application/json"
144
+ },
145
+ body: JSON.stringify({
146
+ ns: namespace,
147
+ ops,
148
+ ...expectedRevision === void 0 ? {} : { expectedRevision }
149
+ })
150
+ });
151
+ if (!response.ok) return { ok: false };
152
+ const body = await response.json();
153
+ return {
154
+ ok: body.ok === true,
155
+ snapshot: snapshotFrom(body.data, namespace)
156
+ };
157
+ } catch {
158
+ return { ok: false };
159
+ }
160
+ }
161
+ function createForm(namespace, ctx) {
162
+ let snapshot = {
163
+ ...EMPTY,
164
+ reason: "seat-missing"
165
+ };
166
+ const listeners = /* @__PURE__ */ new Set();
167
+ let started = false;
168
+ let listening = false;
169
+ const notify = () => {
170
+ for (const listener of listeners) listener();
171
+ };
172
+ const refresh = async () => {
173
+ snapshot = await describe(namespace);
174
+ notify();
175
+ };
176
+ const start = () => {
177
+ if (started) return;
178
+ started = true;
179
+ if (!listening) {
180
+ listening = true;
181
+ (ctx.get?.("remote"))?.$on?.("settings/document-updated", (changed) => {
182
+ if (changed === namespace) refresh();
183
+ });
184
+ }
185
+ refresh();
186
+ };
187
+ const form = {
188
+ getSnapshot: () => snapshot,
189
+ subscribe: (listener) => {
190
+ listeners.add(listener);
191
+ start();
192
+ return () => {
193
+ listeners.delete(listener);
194
+ };
195
+ },
196
+ set: async (field, value) => await form.mutate([{
197
+ op: "set",
198
+ path: [field],
199
+ value
200
+ }]),
201
+ unset: async (field) => await form.mutate([{
202
+ op: "unset",
203
+ path: [field]
204
+ }]),
205
+ mutate: async (ops, expectedRevision) => {
206
+ const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
207
+ if (outcome.snapshot !== void 0) {
208
+ snapshot = outcome.snapshot;
209
+ notify();
210
+ } else await refresh();
211
+ return outcome.ok;
212
+ }
213
+ };
214
+ return form;
215
+ }
216
+ /**
217
+ * Build one cached form per family namespace over the family settings routes.
218
+ * @param ctx - the client plugin context (remote subscription is optional).
219
+ * @returns the seat; each namespace is served by /api/dsh-evolution/settings/*.
220
+ */
221
+ const seats = /* @__PURE__ */ new WeakMap();
222
+ function createSettingsSeat(ctx) {
223
+ const cached = seats.get(ctx);
224
+ if (cached !== void 0) return cached;
225
+ const forms = /* @__PURE__ */ new Map();
226
+ const seat = { get(namespace) {
227
+ let form = forms.get(namespace);
228
+ if (form === void 0) {
229
+ form = createForm(namespace, ctx);
230
+ forms.set(namespace, form);
231
+ }
232
+ return form;
233
+ } };
234
+ seats.set(ctx, seat);
235
+ return seat;
236
+ }
237
+ //#endregion
238
+ //#region ../evolution-client-kit/src/workspace.ts
239
+ /**
240
+ * Best-effort workspace id; a seat that is absent or refuses leaves the panel in local scope.
241
+ * @param ctx - the client context.
242
+ * @returns the current workspace id, or undefined.
243
+ */
244
+ function panelWorkspaceId(ctx) {
245
+ const get = ctx.get;
246
+ if (typeof get !== "function") return void 0;
247
+ for (const name of ["workspace", "uiWorkspace"]) try {
248
+ const seat = get.call(ctx, name);
249
+ const id = seat?.workspaceId ?? seat?.currentWorkspaceId;
250
+ if (typeof id === "string" && id.trim() !== "") return id;
251
+ } catch {}
252
+ }
253
+ //#endregion
254
+ //#region src/client/api.ts
255
+ /** Read-only merge wire client. Classifies every failure and never writes. */
256
+ const HOST_ROUTES = ROUTES.merge;
114
257
  const isBlocked = (failure) => failure.kind === "blocked";
115
258
  const asReadiness = (value) => {
116
259
  const raw = typeof value === "object" && value !== null ? value : {};
@@ -348,7 +491,7 @@ window.__ModuleLoader__.load({
348
491
  startCommands: facts.startCommands,
349
492
  notes: [...facts.notes],
350
493
  degraded: facts.degraded,
351
- ...facts.last === null && facts.active === null ? {} : { latestRun: toRun(facts.last ?? facts.active) },
494
+ ...facts.active === null && facts.last === null ? {} : { latestRun: toRun(facts.active ?? facts.last) },
352
495
  approval: {
353
496
  mergeApproval: facts.approval.mergeApproval,
354
497
  ...platformWrite === void 0 ? {} : { platformWrite }
@@ -426,6 +569,7 @@ window.__ModuleLoader__.load({
426
569
  "start.pair": "复制配对命令模板",
427
570
  "degraded": "读模型降级:",
428
571
  "copied": "已复制:",
572
+ "copyFailed": "复制失败:浏览器没有接受剪贴板写入。",
429
573
  "row.noSummary": "无摘要",
430
574
  "overlay.body": "正文",
431
575
  "hint": "手动触发;后台逐对判定。这里只读,不写技能。",
@@ -441,6 +585,7 @@ window.__ModuleLoader__.load({
441
585
  "empty.fuse": "没有待合并的对。",
442
586
  "empty.skipped": "没有标记为无需合并的对。",
443
587
  "loading": "正在读取…",
588
+ "refreshing": "刷新中,下面仍是上次结果…",
444
589
  "refresh": "刷新",
445
590
  "loadMore": "加载更多",
446
591
  "approval.on": "审批开:合并前需人工批准。",
@@ -489,13 +634,13 @@ window.__ModuleLoader__.load({
489
634
  "skipKind.forced": "强制",
490
635
  "action.approve": "复制批准",
491
636
  "action.reject": "复制拒绝",
492
- "action.retry-candidates": "重试候选",
637
+ "action.retry-candidates": "复制重试候选",
493
638
  "action.retry-resume": "重试续跑",
494
639
  "action.discard": "复制丢弃",
495
- "action.restore": "恢复",
640
+ "action.restore": "复制恢复命令",
496
641
  "action.pair": "复制配对",
497
- "action.status": "状态",
498
- "action.report": "报告",
642
+ "action.status": "复制状态命令",
643
+ "action.report": "复制报告命令",
499
644
  "action.cancel": "取消",
500
645
  "action.confirm": "确认复制",
501
646
  "disabled.expired": "已过期,不可用",
@@ -505,6 +650,7 @@ window.__ModuleLoader__.load({
505
650
  "disabled.owned": "已有运行",
506
651
  "disabled.unavailable": "暂不可用",
507
652
  "history.empty": "没有历史。",
653
+ "history.archivedAt": "归档于 {date}",
508
654
  "failureServer": "读取失败",
509
655
  "failureUnauthorized": "未登录",
510
656
  "failureForbidden": "无权访问",
@@ -592,6 +738,7 @@ window.__ModuleLoader__.load({
592
738
  "run.currentPair": "当前对",
593
739
  "run.parent": "父运行",
594
740
  "run.report": "报告",
741
+ "run.details": "运行详情",
595
742
  "run.noReport": "暂无报告",
596
743
  "run.resultRef": "报告位置",
597
744
  "run.actions": "运行操作",
@@ -609,7 +756,7 @@ window.__ModuleLoader__.load({
609
756
  "error.title": "读取失败",
610
757
  "error.retry": "重试",
611
758
  "error.raw": "原始信息",
612
- "doctor.open": "打开全局诊断",
759
+ "doctor.open": "复制诊断命令",
613
760
  "sort.updatedAt": "按更新时间",
614
761
  "sort.score": "按分数",
615
762
  "pin": "置顶",
@@ -622,6 +769,7 @@ window.__ModuleLoader__.load({
622
769
  "start.pair": "Copy pair command template",
623
770
  "degraded": "Degraded read model:",
624
771
  "copied": "Copied:",
772
+ "copyFailed": "Copy failed: the browser did not accept the clipboard write.",
625
773
  "row.noSummary": "No summary",
626
774
  "overlay.body": "Body",
627
775
  "hint": "Manual only; pairs are judged in the background. Read-only here.",
@@ -637,6 +785,7 @@ window.__ModuleLoader__.load({
637
785
  "empty.fuse": "No pairs ready to merge.",
638
786
  "empty.skipped": "No pairs marked as not worth merging.",
639
787
  "loading": "Loading…",
788
+ "refreshing": "Refreshing; the rows below are the previous result…",
640
789
  "refresh": "Refresh",
641
790
  "loadMore": "Load more",
642
791
  "approval.on": "Approval on: merges wait for a human.",
@@ -685,13 +834,13 @@ window.__ModuleLoader__.load({
685
834
  "skipKind.forced": "forced",
686
835
  "action.approve": "Copy approve",
687
836
  "action.reject": "Copy reject",
688
- "action.retry-candidates": "Retry candidates",
837
+ "action.retry-candidates": "Copy retry candidates",
689
838
  "action.retry-resume": "Retry resume",
690
839
  "action.discard": "Copy discard",
691
- "action.restore": "Restore",
840
+ "action.restore": "Copy restore command",
692
841
  "action.pair": "Copy pair",
693
- "action.status": "Status",
694
- "action.report": "Report",
842
+ "action.status": "Copy status command",
843
+ "action.report": "Copy report command",
695
844
  "action.cancel": "Cancel",
696
845
  "action.confirm": "Copy confirmation",
697
846
  "disabled.expired": "expired",
@@ -701,6 +850,7 @@ window.__ModuleLoader__.load({
701
850
  "disabled.owned": "already running",
702
851
  "disabled.unavailable": "unavailable",
703
852
  "history.empty": "No history.",
853
+ "history.archivedAt": "Archived {date}",
704
854
  "failureServer": "Read failed",
705
855
  "failureUnauthorized": "Not signed in",
706
856
  "failureForbidden": "Forbidden",
@@ -788,6 +938,7 @@ window.__ModuleLoader__.load({
788
938
  "run.currentPair": "Current pair",
789
939
  "run.parent": "Parent run",
790
940
  "run.report": "Report",
941
+ "run.details": "Run details",
791
942
  "run.noReport": "No report yet",
792
943
  "run.resultRef": "Report location",
793
944
  "run.actions": "Run actions",
@@ -805,7 +956,7 @@ window.__ModuleLoader__.load({
805
956
  "error.title": "Read failed",
806
957
  "error.retry": "Retry",
807
958
  "error.raw": "Raw message",
808
- "doctor.open": "Open global diagnostics",
959
+ "doctor.open": "Copy diagnostics command",
809
960
  "sort.updatedAt": "Last updated",
810
961
  "sort.score": "Score",
811
962
  "pin": "Pin",
@@ -815,12 +966,14 @@ window.__ModuleLoader__.load({
815
966
  "settings.saved": "已保存",
816
967
  "settings.saveFailed": "保存失败",
817
968
  "settings.openPlatform": "打开平台设置",
969
+ "settings.openPlatformPath": "设置 → 内置插件 → 插件配置 → 技能自动合并",
818
970
  "settings.live": "可直接在此保存;platformWrite 能力来自宿主。"
819
971
  });
820
972
  Object.assign(en, {
821
973
  "settings.saved": "Saved",
822
974
  "settings.saveFailed": "Save failed",
823
975
  "settings.openPlatform": "Open platform settings",
976
+ "settings.openPlatformPath": "Settings → Built-in plugins → Plugin configuration → Skill auto-merge",
824
977
  "settings.live": "Save directly here; platformWrite capability comes from the host."
825
978
  });
826
979
  Object.assign(zh, { "settings.platformWriteLinkHint": "此开关属于平台设置,这里只读;请到平台设置修改。" });
@@ -850,7 +1003,7 @@ window.__ModuleLoader__.load({
850
1003
  ".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); }",
851
1004
  ".evolution-merge-panel [data-role=\"stat-value\"] { font-size:var(--evo-type-lg); font-weight:600; }",
852
1005
  ".evolution-merge-panel [data-role=\"stat-label\"] { font-size:var(--evo-type-xs); color:var(--dsw-alias-label-tertiary); }",
853
- ".evolution-merge-panel [data-role=\"tabs\"] { display:flex; gap:var(--evo-space-6); border-bottom:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); }",
1006
+ ".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); }",
854
1007
  ".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; }",
855
1008
  ".evolution-merge-panel [data-role=\"tab\"][data-active=\"true\"] { border-bottom-color:var(--dsw-alias-brand-primary); }",
856
1009
  ".evolution-merge-panel button:focus-visible { outline:2px solid currentColor !important; outline-offset:2px; box-shadow:0 0 0 2px currentColor !important; }",
@@ -1171,19 +1324,43 @@ window.__ModuleLoader__.load({
1171
1324
  if (run === void 0) return null;
1172
1325
  const report = run.actions.find((action) => action.id === "report");
1173
1326
  const reportEnabled = report?.enabled === true;
1327
+ const internals = [
1328
+ (0, react.createElement)("span", {
1329
+ key: "id",
1330
+ "data-role": "run-id",
1331
+ title: run.id
1332
+ }, t("run.id") + " " + run.id),
1333
+ run.stage === void 0 ? null : (0, react.createElement)("span", {
1334
+ key: "stage",
1335
+ "data-role": "run-stage"
1336
+ }, t("stage." + run.stage)),
1337
+ run.endedAt === void 0 ? null : (0, react.createElement)("span", {
1338
+ key: "ended",
1339
+ "data-role": "run-ended"
1340
+ }, t("run.endedAt") + " " + new Date(run.endedAt).toLocaleString()),
1341
+ run.failure === void 0 ? null : (0, react.createElement)("span", {
1342
+ key: "failure",
1343
+ "data-role": "run-failure",
1344
+ title: run.failure.detail
1345
+ }, t("run.failure") + " " + run.failure.code),
1346
+ run.currentPairKey === void 0 ? null : (0, react.createElement)("span", {
1347
+ key: "pair",
1348
+ "data-role": "run-current-pair"
1349
+ }, t("run.currentPair") + " " + run.currentPairKey),
1350
+ run.parentRunId === void 0 ? null : (0, react.createElement)("span", {
1351
+ key: "parent",
1352
+ "data-role": "run-parent"
1353
+ }, t("run.parent") + " " + run.parentRunId),
1354
+ run.resultRef === void 0 ? null : (0, react.createElement)("span", {
1355
+ key: "ref",
1356
+ "data-role": "run-result-ref",
1357
+ title: run.resultRef
1358
+ }, t("run.resultRef"))
1359
+ ];
1174
1360
  return (0, react.createElement)("div", {
1175
1361
  "data-role": "run-strip",
1176
1362
  "data-run-state": run.state
1177
- }, (0, react.createElement)("span", {
1178
- "data-role": "run-id",
1179
- title: run.id
1180
- }, t("run.id") + " " + run.id), (0, react.createElement)("span", { "data-role": "run-state" }, t("run.state") + " " + t("run." + run.state)), run.stage === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-stage" }, t("stage." + run.stage)), 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)), run.endedAt === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-ended" }, t("run.endedAt") + " " + new Date(run.endedAt).toLocaleString()), run.failure === void 0 ? null : (0, react.createElement)("span", {
1181
- "data-role": "run-failure",
1182
- title: run.failure.detail
1183
- }, t("run.failure") + " " + run.failure.code), run.currentPairKey === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-current-pair" }, t("run.currentPair") + " " + run.currentPairKey), run.parentRunId === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-parent" }, t("run.parent") + " " + run.parentRunId), run.resultRef === void 0 ? null : (0, react.createElement)("span", {
1184
- "data-role": "run-result-ref",
1185
- title: run.resultRef
1186
- }, t("run.resultRef")), (0, react.createElement)("button", {
1363
+ }, (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", {
1187
1364
  type: "button",
1188
1365
  "data-role": "run-report",
1189
1366
  "data-action": "report",
@@ -1202,7 +1379,7 @@ window.__ModuleLoader__.load({
1202
1379
  onClick: () => {
1203
1380
  if (action.enabled) copy(action.command);
1204
1381
  }
1205
- }, t("action." + action.id))));
1382
+ }, t("action." + action.id))), (0, react.createElement)("details", { "data-role": "run-details" }, (0, react.createElement)("summary", null, t("run.details")), ...internals));
1206
1383
  }
1207
1384
  function ReadOnlySetting({ section, label, value, disabled, missing, note, link, t }) {
1208
1385
  const text = missing ? t("settings.capabilityMissing") : disabled ? t("settings.capabilityDisabled") : value === true ? t("settings.on") : t("settings.off");
@@ -1213,19 +1390,7 @@ window.__ModuleLoader__.load({
1213
1390
  }, (0, react.createElement)("span", { "data-role": "settings-label" }, label), (0, react.createElement)("span", {
1214
1391
  "data-role": "settings-value",
1215
1392
  "data-value": missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off"
1216
- }, text), note === void 0 ? null : (0, react.createElement)("span", { "data-role": "settings-note" }, note), link === true ? (0, react.createElement)("a", {
1217
- "data-role": "settings-link",
1218
- href: "#/settings",
1219
- onClick: () => {
1220
- window.dispatchEvent(new KeyboardEvent("keydown", {
1221
- key: ",",
1222
- code: "Comma",
1223
- ctrlKey: true,
1224
- altKey: true,
1225
- bubbles: true
1226
- }));
1227
- }
1228
- }, t("settings.openPlatform")) : null);
1393
+ }, 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);
1229
1394
  }
1230
1395
  function EditableSetting({ form, field, label, fallback, capability, t }) {
1231
1396
  const subscribe = (0, react.useCallback)((listener) => form.subscribe(listener), [form]);
@@ -1258,7 +1423,12 @@ window.__ModuleLoader__.load({
1258
1423
  "data-role": "settings-section",
1259
1424
  "data-section": field === "mergeApproval" ? "merge-approval" : "platform-write",
1260
1425
  ...disabled ? { "data-disabled": "true" } : {}
1261
- }, (0, react.createElement)("label", { "data-role": "settings-label" }, label), (0, react.createElement)("input", {
1426
+ }, (0, react.createElement)("label", {
1427
+ "data-role": "settings-label",
1428
+ htmlFor: "merge-setting-" + field
1429
+ }, label), (0, react.createElement)("input", {
1430
+ id: "merge-setting-" + field,
1431
+ "aria-label": label,
1262
1432
  type: "checkbox",
1263
1433
  role: "switch",
1264
1434
  "data-role": "settings-toggle",
@@ -1303,12 +1473,9 @@ window.__ModuleLoader__.load({
1303
1473
  note: t("settings.platformWriteLinkHint"),
1304
1474
  link: true,
1305
1475
  t
1306
- }), settings?.merge === void 0 ? (0, react.createElement)("a", {
1307
- "data-role": "settings-link",
1308
- href: "#/settings"
1309
- }, t("settings.openPlatform")) : null, (0, react.createElement)("p", { "data-role": "settings-readonly" }, settings?.merge === void 0 ? t("settings.readonly") : t("settings.live")));
1476
+ }), (0, react.createElement)("p", { "data-role": "settings-readonly" }, settings?.merge === void 0 ? t("settings.readonly") : t("settings.live")));
1310
1477
  }
1311
- function rowNode(row, store, t, copy, sort) {
1478
+ function rowNode(row, t, copy, sort, openBody) {
1312
1479
  const facts = [
1313
1480
  row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
1314
1481
  row.targetActual === void 0 ? void 0 : t("row.actualTarget") + " " + row.targetActual,
@@ -1330,7 +1497,6 @@ window.__ModuleLoader__.load({
1330
1497
  disabled: !action.enabled,
1331
1498
  onClick: () => {
1332
1499
  if (!action.enabled) return;
1333
- if (action.confirm && globalThis.confirm !== void 0 && !globalThis.confirm(t("action.confirm") + " " + (t("action." + action.id) || action.id))) return;
1334
1500
  copy(action.command);
1335
1501
  }
1336
1502
  }, t("action." + action.id)));
@@ -1341,7 +1507,15 @@ window.__ModuleLoader__.load({
1341
1507
  "data-row-state": row.state,
1342
1508
  "data-row-queue": row.queue,
1343
1509
  draggable: true,
1510
+ tabIndex: 0,
1344
1511
  title: t("sort.drag"),
1512
+ "aria-haspopup": "menu",
1513
+ onKeyDown: (event) => {
1514
+ if (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10")) return;
1515
+ event.preventDefault();
1516
+ const box = event.currentTarget.getBoundingClientRect();
1517
+ sort.menu(row.id, box.left + 12, box.top + 12);
1518
+ },
1345
1519
  onDragStart: (event) => {
1346
1520
  if (event.dataTransfer != null) {
1347
1521
  event.dataTransfer.setData("text/plain", row.id);
@@ -1375,29 +1549,56 @@ window.__ModuleLoader__.load({
1375
1549
  key: "a",
1376
1550
  type: "button",
1377
1551
  "data-role": "body-a",
1552
+ "aria-label": t("body." + bodyA.side) + " " + bodyA.name,
1553
+ title: t("body." + bodyA.side) + " " + bodyA.name,
1378
1554
  onClick: () => {
1379
- store.openBody(bodyA.name, bodyA.side, bodyA.rowId);
1555
+ openBody(bodyA.name, bodyA.side, bodyA.rowId);
1380
1556
  }
1381
1557
  }, "A"), bodyB === void 0 ? null : (0, react.createElement)("button", {
1382
1558
  key: "b",
1383
1559
  type: "button",
1384
1560
  "data-role": "body-b",
1561
+ "aria-label": t("body." + bodyB.side) + " " + bodyB.name,
1562
+ title: t("body." + bodyB.side) + " " + bodyB.name,
1385
1563
  onClick: () => {
1386
- store.openBody(bodyB.name, bodyB.side, bodyB.rowId);
1564
+ openBody(bodyB.name, bodyB.side, bodyB.rowId);
1387
1565
  }
1388
1566
  }, "B")];
1389
1567
  })(), actions)));
1390
1568
  }
1569
+ const overlayKey = (event, onClose) => {
1570
+ if (event.key === "Escape") {
1571
+ event.preventDefault();
1572
+ onClose();
1573
+ return;
1574
+ }
1575
+ if (event.key !== "Tab") return;
1576
+ const nodes = [...event.currentTarget.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])")].filter((node) => !node.hasAttribute("disabled"));
1577
+ if (nodes.length === 0) return;
1578
+ const first = nodes[0];
1579
+ const last = nodes[nodes.length - 1];
1580
+ if (event.shiftKey && document.activeElement === first) {
1581
+ event.preventDefault();
1582
+ last.focus();
1583
+ } else if (!event.shiftKey && document.activeElement === last) {
1584
+ event.preventDefault();
1585
+ first.focus();
1586
+ }
1587
+ };
1391
1588
  function bodyNode(overlay, t, onClose) {
1392
1589
  return (0, react.createElement)("section", {
1393
1590
  "data-role": "overlay",
1394
1591
  "data-overlay": "body",
1395
1592
  role: "dialog",
1396
1593
  "aria-modal": "true",
1397
- "aria-label": t("overlay.body")
1594
+ "aria-label": t("overlay.body"),
1595
+ onKeyDown: (event) => {
1596
+ overlayKey(event, onClose);
1597
+ }
1398
1598
  }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, t("overlay.body") + " · " + overlay.name), (0, react.createElement)("button", {
1399
1599
  type: "button",
1400
1600
  "data-role": "overlay-close",
1601
+ autoFocus: true,
1401
1602
  onClick: onClose
1402
1603
  }, t("close"))), (0, react.createElement)("p", { "data-role": "overlay-meta" }, t("body." + overlay.side) + " · " + (overlay.status === "ready" ? t("body.totalBytes") + " " + String(overlay.body?.totalBytes ?? 0) : "")), 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.body !== void 0 ? (0, react.createElement)("div", null, overlay.body.truncated ? (0, react.createElement)("p", { "data-role": "body-truncated" }, t("body.truncated")) : null, overlay.body.text === "" ? (0, react.createElement)("p", { "data-role": "body-empty" }, t("body.empty")) : (0, react.createElement)("pre", { "data-role": "body-text" }, overlay.body.text)) : null);
1403
1604
  }
@@ -1407,10 +1608,17 @@ window.__ModuleLoader__.load({
1407
1608
  "data-overlay": "history",
1408
1609
  role: "dialog",
1409
1610
  "aria-modal": "true",
1410
- "aria-label": t("history")
1611
+ "aria-label": t("history"),
1612
+ onKeyDown: (event) => {
1613
+ if (event.key === "Escape") {
1614
+ event.preventDefault();
1615
+ onClose();
1616
+ }
1617
+ }
1411
1618
  }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, t("history")), (0, react.createElement)("button", {
1412
1619
  type: "button",
1413
1620
  "data-role": "overlay-close",
1621
+ autoFocus: true,
1414
1622
  onClick: onClose
1415
1623
  }, 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", {
1416
1624
  key: item.name + ":" + item.archivedAt,
@@ -1419,20 +1627,51 @@ window.__ModuleLoader__.load({
1419
1627
  const kind = t("history.kind." + item.kind);
1420
1628
  const reason = reasonText(t, item.reasonCode, item.reason);
1421
1629
  return kind === reason ? kind : kind + " · " + reason;
1422
- })() + " · v" + String(item.lastVersion)))) : null);
1630
+ })() + " · v" + String(item.lastVersion) + " · " + t("history.archivedAt", { date: new Date(item.archivedAt).toLocaleString() })))) : null);
1423
1631
  }
1424
1632
  function MergePanel(face) {
1425
1633
  const { store, t } = face;
1426
1634
  const snapshot = (0, react.useSyncExternalStore)(store.subscribe, store.read, store.read);
1427
- const [copied, setCopied] = (0, react.useState)(void 0);
1635
+ const [copyState, setCopyState] = (0, react.useState)({ kind: "idle" });
1428
1636
  const [orders, setOrders] = (0, react.useState)({});
1429
1637
  const [dragId, setDragId] = (0, react.useState)(void 0);
1430
1638
  const [menu, setMenu] = (0, react.useState)(void 0);
1639
+ const returnFocus = (0, react.useRef)(null);
1640
+ const captureFocus = () => {
1641
+ returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1642
+ };
1643
+ const openBody = (name, side, rowId) => {
1644
+ captureFocus();
1645
+ store.openBody(name, side, rowId);
1646
+ };
1647
+ const openHistory = () => {
1648
+ captureFocus();
1649
+ store.openHistory();
1650
+ };
1651
+ const closeOverlay = () => {
1652
+ store.closeOverlay();
1653
+ requestAnimationFrame(() => {
1654
+ returnFocus.current?.focus();
1655
+ });
1656
+ };
1431
1657
  const workspaceId = resolveWorkspaceId(face.workspaceId);
1432
1658
  const orderStore = (0, react.useMemo)(() => createRowOrderStore(), []);
1433
1659
  const copy = (command) => {
1434
- navigator.clipboard.writeText(command);
1435
- setCopied(command);
1660
+ (async () => {
1661
+ try {
1662
+ if (navigator.clipboard?.writeText === void 0) throw new Error("clipboard unavailable");
1663
+ await navigator.clipboard.writeText(command);
1664
+ setCopyState({
1665
+ kind: "copied",
1666
+ text: command
1667
+ });
1668
+ } catch {
1669
+ setCopyState({
1670
+ kind: "failed",
1671
+ text: command
1672
+ });
1673
+ }
1674
+ })();
1436
1675
  };
1437
1676
  const doctorCommand = face.doctorCommand ?? DOCTOR_COMMAND;
1438
1677
  (0, react.useEffect)(() => {
@@ -1503,19 +1742,37 @@ window.__ModuleLoader__.load({
1503
1742
  saveOrder(pinRow(visibleRows.map((row) => row.id), menu.id, edge));
1504
1743
  setMenu(void 0);
1505
1744
  };
1745
+ const onTabKey = (event, current) => {
1746
+ const delta = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
1747
+ const isEdge = event.key === "Home" || event.key === "End";
1748
+ if (delta === 0 && !isEdge) return;
1749
+ event.preventDefault();
1750
+ const index = QUEUES.indexOf(current);
1751
+ const next = event.key === "Home" ? QUEUES[0] : event.key === "End" ? QUEUES[QUEUES.length - 1] : QUEUES[(index + delta + QUEUES.length) % QUEUES.length];
1752
+ store.select(next);
1753
+ requestAnimationFrame(() => {
1754
+ document.getElementById("merge-tab-" + next)?.focus();
1755
+ });
1756
+ };
1506
1757
  const tabs = (0, react.createElement)("div", {
1507
1758
  "data-role": "tabs",
1508
1759
  role: "tablist"
1509
1760
  }, QUEUES.map((item) => (0, react.createElement)("button", {
1510
1761
  key: item,
1762
+ id: "merge-tab-" + item,
1511
1763
  type: "button",
1512
1764
  role: "tab",
1513
1765
  "aria-selected": snapshot.queue === item ? "true" : "false",
1766
+ "aria-controls": "merge-rows",
1767
+ tabIndex: snapshot.queue === item ? 0 : -1,
1514
1768
  "data-role": "tab",
1515
1769
  "data-active": snapshot.queue === item ? "true" : "false",
1516
1770
  onClick: () => {
1517
1771
  setMenu(void 0);
1518
1772
  store.select(item);
1773
+ },
1774
+ onKeyDown: (event) => {
1775
+ onTabKey(event, item);
1519
1776
  }
1520
1777
  }, t("tab." + item) + " " + String(view.counts[item]))));
1521
1778
  const expiredToggle = snapshot.queue === "pending" ? (0, react.createElement)("button", {
@@ -1528,14 +1785,19 @@ window.__ModuleLoader__.load({
1528
1785
  store.toggleExpired();
1529
1786
  }
1530
1787
  }, t("tab.expired") + " " + String(view.expired)) : null;
1531
- const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", { "data-role": "rows" }, visibleRows.map((row) => rowNode(row, store, t, copy, sortHandlers))) : null;
1788
+ const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", {
1789
+ "data-role": "rows",
1790
+ id: "merge-rows",
1791
+ role: "tabpanel"
1792
+ }, visibleRows.map((row) => rowNode(row, t, copy, sortHandlers, openBody))) : null;
1532
1793
  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;
1533
1794
  const activeFailure = viewFailure !== void 0 ? viewFailure : queue.status === "error" ? queue.failure : void 0;
1534
1795
  const overlay = snapshot.overlay;
1535
1796
  const menuStyle = menu === void 0 ? void 0 : {
1536
1797
  position: "fixed",
1537
1798
  zIndex: 70,
1538
- minWidth: "var(--evo-measure-read)",
1799
+ minWidth: "160px",
1800
+ maxWidth: "min(320px, calc(100vw - 16px))",
1539
1801
  left: String(Math.min(menu.x, Math.max(0, (globalThis.innerWidth || 1440) - 180))) + "px",
1540
1802
  top: String(Math.min(menu.y, Math.max(0, (globalThis.innerHeight || 800) - 110))) + "px"
1541
1803
  };
@@ -1608,10 +1870,16 @@ window.__ModuleLoader__.load({
1608
1870
  view,
1609
1871
  settings: face.settings,
1610
1872
  t
1611
- }), runNode(view, t, copy), tabs, expiredToggle, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, activeFailure === void 0 ? null : failureNode(t, activeFailure, fallback, copy), copied === void 0 ? null : (0, react.createElement)("p", {
1873
+ }), 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", {
1874
+ "data-role": "note",
1875
+ role: "status"
1876
+ }, t("refreshing")) : null, activeFailure === void 0 ? null : failureNode(t, activeFailure, fallback, copy), copyState.kind === "idle" ? null : copyState.kind === "copied" ? (0, react.createElement)("p", {
1612
1877
  "data-role": "copied",
1613
1878
  role: "status"
1614
- }, t("copied") + " " + copied), list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
1879
+ }, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("p", {
1880
+ "data-role": "copy-failed",
1881
+ role: "alert"
1882
+ }, t("copyFailed")), list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
1615
1883
  type: "button",
1616
1884
  "data-role": "load-more",
1617
1885
  disabled: queue.status === "loading",
@@ -1622,7 +1890,7 @@ window.__ModuleLoader__.load({
1622
1890
  type: "button",
1623
1891
  "data-role": "history",
1624
1892
  onClick: () => {
1625
- store.openHistory();
1893
+ openHistory();
1626
1894
  }
1627
1895
  }, t("history")), (0, react.createElement)("button", {
1628
1896
  type: "button",
@@ -1630,11 +1898,7 @@ window.__ModuleLoader__.load({
1630
1898
  onClick: () => {
1631
1899
  copy(doctorCommand);
1632
1900
  }
1633
- }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, () => {
1634
- store.closeOverlay();
1635
- }) : historyNode(overlay, t, () => {
1636
- store.closeOverlay();
1637
- }), (0, react.createElement)("button", {
1901
+ }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), (0, react.createElement)("button", {
1638
1902
  type: "button",
1639
1903
  "data-role": "refresh",
1640
1904
  onClick: () => {
@@ -1738,7 +2002,11 @@ window.__ModuleLoader__.load({
1738
2002
  }
1739
2003
  async loadView(showLoading = true) {
1740
2004
  const token = ++this.viewToken;
1741
- if (showLoading) this.set({
2005
+ if (showLoading) if (this.snapshot.viewState === "ready") this.set({
2006
+ ...this.snapshot,
2007
+ viewRefreshing: true
2008
+ });
2009
+ else this.set({
1742
2010
  ...this.snapshot,
1743
2011
  viewState: "loading",
1744
2012
  viewFailure: void 0
@@ -1765,7 +2033,8 @@ window.__ModuleLoader__.load({
1765
2033
  this.set({
1766
2034
  ...this.snapshot,
1767
2035
  viewState: "error",
1768
- viewFailure: outcome.failure
2036
+ viewFailure: outcome.failure,
2037
+ viewRefreshing: false
1769
2038
  });
1770
2039
  return;
1771
2040
  }
@@ -1774,7 +2043,8 @@ window.__ModuleLoader__.load({
1774
2043
  ...this.snapshot,
1775
2044
  view: outcome.payload,
1776
2045
  viewState: "ready",
1777
- viewFailure: void 0
2046
+ viewFailure: void 0,
2047
+ viewRefreshing: false
1778
2048
  });
1779
2049
  if (wasBlocked && this.snapshot.current.status === "idle") this.loadQueue(this.snapshot.queue, this.snapshot.expiredOnly);
1780
2050
  }
@@ -1783,7 +2053,13 @@ window.__ModuleLoader__.load({
1783
2053
  const token = (this.tokens.get(key) ?? 0) + 1;
1784
2054
  this.tokens.set(key, token);
1785
2055
  const previous = this.cache.get(key);
1786
- const next = cursor === void 0 ? {
2056
+ const keepRows = cursor === void 0 && (previous?.rows.length ?? 0) > 0;
2057
+ const next = cursor === void 0 ? keepRows ? {
2058
+ status: "ready",
2059
+ refreshing: true,
2060
+ rows: previous.rows,
2061
+ ...previous.nextCursor === void 0 ? {} : { nextCursor: previous.nextCursor }
2062
+ } : {
1787
2063
  status: "loading",
1788
2064
  rows: []
1789
2065
  } : {
@@ -1815,7 +2091,12 @@ window.__ModuleLoader__.load({
1815
2091
  });
1816
2092
  return;
1817
2093
  }
1818
- const failed = {
2094
+ const failed = keepRows ? {
2095
+ status: "ready",
2096
+ refreshing: false,
2097
+ rows: previous.rows,
2098
+ failure: outcome.failure
2099
+ } : {
1819
2100
  status: "error",
1820
2101
  rows: [],
1821
2102
  failure: outcome.failure
@@ -1830,6 +2111,7 @@ window.__ModuleLoader__.load({
1830
2111
  const items = cursor === void 0 ? outcome.payload.items : [...previous?.rows ?? [], ...outcome.payload.items];
1831
2112
  const ready = {
1832
2113
  status: items.length === 0 ? "empty" : "ready",
2114
+ refreshing: false,
1833
2115
  rows: items,
1834
2116
  ...outcome.payload.nextCursor === void 0 ? {} : { nextCursor: outcome.payload.nextCursor }
1835
2117
  };
@@ -1981,26 +2263,16 @@ window.__ModuleLoader__.load({
1981
2263
  //#endregion
1982
2264
  //#region src/client/index.ts
1983
2265
  const inject = ["slots", "locale"];
1984
- function PanelIcon(props = {}) {
1985
- const size = typeof props.size === "number" && props.size > 0 ? props.size : 16;
1986
- return (0, react.createElement)("svg", {
1987
- "aria-hidden": "true",
1988
- width: String(size),
1989
- height: String(size),
1990
- viewBox: "0 0 16 16",
1991
- fill: "none",
1992
- stroke: "currentColor",
1993
- strokeWidth: "1.5",
1994
- strokeLinecap: "round",
1995
- strokeLinejoin: "round"
1996
- }, (0, react.createElement)("path", { d: "M3 4h10M3 8h6M3 12h8" }));
2266
+ /** The view component receives the workbench's activeView marker alongside its face. */
2267
+ function MergeView(props) {
2268
+ return props.activeView === "merge" ? MergePanel(props) : null;
1997
2269
  }
1998
2270
  /**
1999
- * Register the merge row and its keyed main body through the shared panel lifecycle.
2271
+ * Register the merge view on the workbench page.
2000
2272
  * @param ctx - the client context.
2001
2273
  */
2002
2274
  function apply(ctx) {
2003
- registerSidebarPanel(ctx, {
2275
+ registerWorkbenchView(ctx, {
2004
2276
  id: PANEL_ID,
2005
2277
  order: 45,
2006
2278
  namespace: NS,
@@ -2012,30 +2284,24 @@ window.__ModuleLoader__.load({
2012
2284
  css: CSS,
2013
2285
  tagId: CSS_TAG_ID
2014
2286
  },
2015
- icon: PanelIcon,
2016
- component: MergePanel,
2017
- body: (t) => {
2287
+ component: MergeView,
2288
+ face: (t) => {
2018
2289
  const source = {
2019
2290
  view,
2020
2291
  rows,
2021
2292
  body,
2022
2293
  history
2023
2294
  };
2024
- let seat;
2025
- try {
2026
- seat = ctx.get?.("configForms");
2027
- } catch {
2028
- seat = void 0;
2029
- }
2295
+ const seat = createSettingsSeat(ctx);
2030
2296
  const workspaceId = panelWorkspaceId(ctx);
2031
2297
  return {
2032
2298
  store: new MergeStore(source),
2033
2299
  t,
2034
2300
  doctorCommand: "/evolution doctor",
2035
- settings: { ...seat === void 0 ? {} : {
2036
- merge: seat.get("evolution-merge"),
2037
- platform: seat.get("evolution-approval")
2038
- } },
2301
+ settings: {
2302
+ ...seat.get("evolution-merge") === void 0 ? {} : { merge: seat.get("evolution-merge") },
2303
+ ...seat.get("evolution-approval") === void 0 ? {} : { platform: seat.get("evolution-approval") }
2304
+ },
2039
2305
  ...workspaceId === void 0 ? {} : { workspaceId }
2040
2306
  };
2041
2307
  }
@@ -11,6 +11,8 @@ export interface MergeSource {
11
11
  export type QueueStatus = 'idle' | 'loading' | 'ready' | 'empty' | 'error';
12
12
  export interface QueueState {
13
13
  status: QueueStatus;
14
+ /** A background refresh is in flight; the old rows remain visible. */
15
+ refreshing?: boolean | undefined;
14
16
  rows: MergeRowView[];
15
17
  nextCursor?: string | undefined;
16
18
  failure?: MergeFailure | undefined;
@@ -31,6 +33,7 @@ export type OverlayState = {
31
33
  export interface StoreSnapshot {
32
34
  view: MergeViewPayload;
33
35
  viewState: 'loading' | 'ready' | 'error' | 'blocked';
36
+ viewRefreshing?: boolean | undefined;
34
37
  viewFailure?: MergeFailure | undefined;
35
38
  queue: MergeQueue;
36
39
  expiredOnly: boolean;
@@ -1,8 +1,8 @@
1
- /** Browser half: one sidebar row and the read-only Batch 4 panel. */
1
+ /** Browser half: one read-only merge view inside the shared evolution workbench. */
2
2
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
3
3
  export declare const inject: string[];
4
4
  /**
5
- * Register the merge row and its keyed main body through the shared panel lifecycle.
5
+ * Register the merge view on the workbench page.
6
6
  * @param ctx - the client context.
7
7
  */
8
8
  export declare function apply(ctx: ClientContext): void;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.32.4",
14
+ "version": "0.32.6",
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.32.4"
54
+ "@lmzhen/dsh-evolution-client-kit": "^0.32.6"
55
55
  },
56
56
  "engines": {
57
57
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"