@lmzhen/dsh-evolution-merge-ui 0.32.6 → 0.34.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
@@ -163,34 +163,51 @@ window.__ModuleLoader__.load({
163
163
  ...EMPTY,
164
164
  reason: "seat-missing"
165
165
  };
166
+ let lastReady;
167
+ let generation = 0;
166
168
  const listeners = /* @__PURE__ */ new Set();
167
- let started = false;
168
- let listening = false;
169
+ let remoteOff;
169
170
  const notify = () => {
170
171
  for (const listener of listeners) listener();
171
172
  };
172
- const refresh = async () => {
173
- snapshot = await describe(namespace);
173
+ const absorb = (next) => {
174
+ if (next.status === "ready") {
175
+ lastReady = next;
176
+ snapshot = next;
177
+ } else if (lastReady !== void 0) snapshot = {
178
+ ...lastReady,
179
+ stale: true,
180
+ reason: next.reason ?? "readback-failed"
181
+ };
182
+ else snapshot = next;
174
183
  notify();
175
184
  };
176
- const 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();
185
+ const refresh = async () => {
186
+ const ticket = ++generation;
187
+ const next = await describe(namespace);
188
+ if (ticket !== generation) return;
189
+ absorb(next);
190
+ };
191
+ const remote = () => ctx.get?.("remote");
192
+ const stopRemote = () => {
193
+ remoteOff?.();
194
+ remoteOff = void 0;
195
+ };
196
+ const startRemote = () => {
197
+ if (remoteOff !== void 0) return;
198
+ remoteOff = remote()?.$on?.("settings/document-updated", (changed) => {
199
+ if (changed === namespace) refresh();
200
+ });
186
201
  };
187
202
  const form = {
188
203
  getSnapshot: () => snapshot,
189
204
  subscribe: (listener) => {
190
205
  listeners.add(listener);
191
- start();
206
+ startRemote();
207
+ refresh();
192
208
  return () => {
193
209
  listeners.delete(listener);
210
+ if (listeners.size === 0) stopRemote();
194
211
  };
195
212
  },
196
213
  set: async (field, value) => await form.mutate([{
@@ -204,35 +221,39 @@ window.__ModuleLoader__.load({
204
221
  }]),
205
222
  mutate: async (ops, expectedRevision) => {
206
223
  const outcome = await write(namespace, ops, expectedRevision ?? snapshot.revision);
207
- if (outcome.snapshot !== void 0) {
208
- snapshot = outcome.snapshot;
209
- notify();
210
- } else await refresh();
224
+ if (outcome.snapshot !== void 0) absorb(outcome.snapshot);
225
+ else await refresh();
211
226
  return outcome.ok;
227
+ },
228
+ refresh,
229
+ dispose: () => {
230
+ stopRemote();
231
+ listeners.clear();
212
232
  }
213
233
  };
214
234
  return form;
215
235
  }
216
236
  /**
217
- * Build one 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/*.
237
+ * Build one seat per client registration; every form's remote listener is released by
238
+ * the caller's effect. No module-level WeakMap: a replaced registration must not share
239
+ * a dead seat with the previous one.
220
240
  */
221
- const seats = /* @__PURE__ */ new WeakMap();
222
241
  function createSettingsSeat(ctx) {
223
- const cached = seats.get(ctx);
224
- if (cached !== void 0) return cached;
225
242
  const forms = /* @__PURE__ */ new Map();
226
- 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);
243
+ return {
244
+ get(namespace) {
245
+ let form = forms.get(namespace);
246
+ if (form === void 0) {
247
+ form = createForm(namespace, ctx);
248
+ forms.set(namespace, form);
249
+ }
250
+ return form;
251
+ },
252
+ dispose: () => {
253
+ for (const form of forms.values()) form.dispose?.();
254
+ forms.clear();
231
255
  }
232
- return form;
233
- } };
234
- seats.set(ctx, seat);
235
- return seat;
256
+ };
236
257
  }
237
258
  //#endregion
238
259
  //#region ../evolution-client-kit/src/workspace.ts
@@ -491,7 +512,8 @@ window.__ModuleLoader__.load({
491
512
  startCommands: facts.startCommands,
492
513
  notes: [...facts.notes],
493
514
  degraded: facts.degraded,
494
- ...facts.active === null && facts.last === null ? {} : { latestRun: toRun(facts.active ?? facts.last) },
515
+ ...facts.active === null ? {} : { activeRun: toRun(facts.active) },
516
+ ...facts.last === null ? {} : { lastRun: toRun(facts.last) },
495
517
  approval: {
496
518
  mergeApproval: facts.approval.mergeApproval,
497
519
  ...platformWrite === void 0 ? {} : { platformWrite }
@@ -518,9 +540,14 @@ window.__ModuleLoader__.load({
518
540
  };
519
541
  }
520
542
  async function body(name, side, rowId) {
521
- const answer = await requestWire(HOST_ROUTES.body + (rowId === void 0 ? "?name=" + encodeURIComponent(name) : "?id=" + encodeURIComponent(rowId)) + "&side=" + side);
543
+ const query = new URLSearchParams({
544
+ name,
545
+ side
546
+ });
547
+ if (rowId !== void 0) query.set("id", rowId);
548
+ const answer = await requestWire(HOST_ROUTES.body + "?" + query.toString());
522
549
  if (!answer.ok) {
523
- if (answer.failure.kind === "problem" && answer.failure.code === "bad-query" && (answer.failure.message ?? "").includes("multiple merge rows")) return {
550
+ if (answer.failure.kind === "problem" && answer.failure.code === "bad-query" && (answer.failure.message ?? "").includes("ambiguous")) return {
524
551
  ok: false,
525
552
  failure: {
526
553
  ...answer.failure,
@@ -541,6 +568,56 @@ window.__ModuleLoader__.load({
541
568
  }
542
569
  };
543
570
  }
571
+ async function decide(rowId, verb, sessionId) {
572
+ const path = HOST_ROUTES.body.replace(/body$/, "decision");
573
+ let response;
574
+ try {
575
+ response = await fetch(path, {
576
+ method: "POST",
577
+ headers: {
578
+ accept: "application/json",
579
+ "content-type": "application/json"
580
+ },
581
+ body: JSON.stringify({
582
+ rowId,
583
+ verb,
584
+ sessionId
585
+ })
586
+ });
587
+ } catch {
588
+ return {
589
+ ok: false,
590
+ failure: problem("server", 0)
591
+ };
592
+ }
593
+ let body;
594
+ try {
595
+ body = await response.json();
596
+ } catch {
597
+ return {
598
+ ok: false,
599
+ failure: problem("server", response.status)
600
+ };
601
+ }
602
+ if (typeof body !== "object" || body === null) return {
603
+ ok: false,
604
+ failure: problem("server", response.status)
605
+ };
606
+ const record = body;
607
+ if (record.wireVersion !== 1 || typeof record.ok !== "boolean" || typeof record.message !== "string") return {
608
+ ok: false,
609
+ failure: problem("wire-version", response.status)
610
+ };
611
+ return {
612
+ ok: true,
613
+ payload: {
614
+ ok: record.ok,
615
+ message: record.message,
616
+ ...typeof record.code === "string" ? { code: record.code } : {},
617
+ ...typeof record.runId === "string" ? { runId: record.runId } : {}
618
+ }
619
+ };
620
+ }
544
621
  async function history() {
545
622
  const answer = await requestWire(HOST_ROUTES.history);
546
623
  return answer.ok ? {
@@ -634,6 +711,8 @@ window.__ModuleLoader__.load({
634
711
  "skipKind.forced": "强制",
635
712
  "action.approve": "复制批准",
636
713
  "action.reject": "复制拒绝",
714
+ "decision.approve": "批准",
715
+ "decision.reject": "拒绝",
637
716
  "action.retry-candidates": "复制重试候选",
638
717
  "action.retry-resume": "重试续跑",
639
718
  "action.discard": "复制丢弃",
@@ -686,6 +765,7 @@ window.__ModuleLoader__.load({
686
765
  "stage.state.absent": "无暂存",
687
766
  "origin.system": "系统",
688
767
  "row.id": "编号",
768
+ "row.details": "详情",
689
769
  "row.requestedTarget": "请求目标",
690
770
  "row.stage": "合并暂存",
691
771
  "row.summaryA": "摘要 A",
@@ -707,7 +787,6 @@ window.__ModuleLoader__.load({
707
787
  "history.reason.archived": "已归档",
708
788
  "history.reason.unknown": "原因未提供",
709
789
  "history.lastVersion": "最后版本",
710
- "history.archivedAt": "归档时间",
711
790
  "body.truncated": "正文过长,已截断。",
712
791
  "body.empty": "正文为空。",
713
792
  "body.totalBytes": "总字节",
@@ -834,6 +913,8 @@ window.__ModuleLoader__.load({
834
913
  "skipKind.forced": "forced",
835
914
  "action.approve": "Copy approve",
836
915
  "action.reject": "Copy reject",
916
+ "decision.approve": "Approve",
917
+ "decision.reject": "Reject",
837
918
  "action.retry-candidates": "Copy retry candidates",
838
919
  "action.retry-resume": "Retry resume",
839
920
  "action.discard": "Copy discard",
@@ -886,6 +967,7 @@ window.__ModuleLoader__.load({
886
967
  "stage.state.absent": "Not staged",
887
968
  "origin.system": "System",
888
969
  "row.id": "ID",
970
+ "row.details": "Details",
889
971
  "row.requestedTarget": "Requested target",
890
972
  "row.stage": "Merge staging",
891
973
  "row.summaryA": "Summary A",
@@ -907,7 +989,6 @@ window.__ModuleLoader__.load({
907
989
  "history.reason.archived": "archived",
908
990
  "history.reason.unknown": "reason not provided",
909
991
  "history.lastVersion": "Last version",
910
- "history.archivedAt": "Archived at",
911
992
  "body.truncated": "Body is truncated.",
912
993
  "body.empty": "Body is empty.",
913
994
  "body.totalBytes": "Total bytes",
@@ -966,14 +1047,14 @@ window.__ModuleLoader__.load({
966
1047
  "settings.saved": "已保存",
967
1048
  "settings.saveFailed": "保存失败",
968
1049
  "settings.openPlatform": "打开平台设置",
969
- "settings.openPlatformPath": "设置 → 内置插件 → 插件配置 → 技能自动合并",
1050
+ "settings.openPlatformPath": "设置 → 内置插件 → 自进化 → 技能自动合并",
970
1051
  "settings.live": "可直接在此保存;platformWrite 能力来自宿主。"
971
1052
  });
972
1053
  Object.assign(en, {
973
1054
  "settings.saved": "Saved",
974
1055
  "settings.saveFailed": "Save failed",
975
1056
  "settings.openPlatform": "Open platform settings",
976
- "settings.openPlatformPath": "Settings → Built-in plugins → Plugin configuration → Skill auto-merge",
1057
+ "settings.openPlatformPath": "Settings → Built-in plugins → Evolution → Skill auto-merge",
977
1058
  "settings.live": "Save directly here; platformWrite capability comes from the host."
978
1059
  });
979
1060
  Object.assign(zh, { "settings.platformWriteLinkHint": "此开关属于平台设置,这里只读;请到平台设置修改。" });
@@ -1015,6 +1096,13 @@ window.__ModuleLoader__.load({
1015
1096
  ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
1016
1097
  ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
1017
1098
  ".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); }",
1099
+ ".evo-merge-modal { width:min(820px, calc(100vw - 48px)); max-width:calc(100vw - 48px); max-height:min(78vh, 720px); }",
1100
+ ".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); }",
1101
+ ".evo-merge-modal [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1102
+ ".evo-merge-modal [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1103
+ ".evo-merge-modal [data-role=\"body-text\"] { margin:0; max-height:52vh; overflow:auto; white-space:pre-wrap; }",
1104
+ ".evo-merge-modal [data-role=\"history-list\"] { margin:0; padding-left:var(--evo-space-20); }",
1105
+ ".evo-merge-modal [data-role=\"history-item\"] { padding:var(--evo-space-4) 0; }",
1018
1106
  ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1019
1107
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1020
1108
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
@@ -1055,6 +1143,25 @@ window.__ModuleLoader__.load({
1055
1143
  ".evolution-merge-panel [data-role=\"refresh\"] { align-self:flex-start; }"
1056
1144
  ].join("\n");
1057
1145
  //#endregion
1146
+ //#region src/client/platform-modal.ts
1147
+ const PLATFORM_UI_MODULE$1 = "@deepseek-ai/dsh-client-ui-primitives";
1148
+ let cached;
1149
+ /** The platform Modal, or undefined in a non-browser/SSR render or a deployment without the row. */
1150
+ function platformModal() {
1151
+ if (cached !== void 0) return cached ?? void 0;
1152
+ if (typeof window === "undefined") {
1153
+ cached = null;
1154
+ return;
1155
+ }
1156
+ try {
1157
+ const row = require(PLATFORM_UI_MODULE$1);
1158
+ cached = typeof row.Modal === "function" ? row.Modal : null;
1159
+ } catch {
1160
+ cached = null;
1161
+ }
1162
+ return cached ?? void 0;
1163
+ }
1164
+ //#endregion
1058
1165
  //#region src/client/platform-menu.ts
1059
1166
  /**
1060
1167
  * The one seam onto the platform's menu material and rows.
@@ -1116,9 +1223,9 @@ window.__ModuleLoader__.load({
1116
1223
  const value = workspaceId?.trim() ?? "";
1117
1224
  return value === "" ? "local" : value;
1118
1225
  }
1119
- /** Stable localStorage key for one workspace and queue. */
1120
- function rowOrderKey(workspaceId, queue) {
1121
- return "dsh-evolution-merge.row-order.v1:" + encodeURIComponent(resolveWorkspaceId(workspaceId)) + ":" + queue;
1226
+ /** Stable localStorage key for one workspace, queue and expired filter. */
1227
+ function rowOrderKey(workspaceId, queue, expiredOnly = false) {
1228
+ return "dsh-evolution-merge.row-order.v1:" + encodeURIComponent(resolveWorkspaceId(workspaceId)) + ":" + queue + (expiredOnly ? ":expired" : ":live");
1122
1229
  }
1123
1230
  function defaultStorage() {
1124
1231
  try {
@@ -1147,18 +1254,18 @@ window.__ModuleLoader__.load({
1147
1254
  /** One tab's persisted order; failures degrade to an empty (wire) order. */
1148
1255
  function createRowOrderStore(storage = defaultStorage()) {
1149
1256
  return {
1150
- read(workspaceId, queue) {
1257
+ read(workspaceId, queue, expiredOnly = false) {
1151
1258
  if (storage === void 0) return [];
1152
1259
  try {
1153
- return normalize(storage.getItem(rowOrderKey(workspaceId, queue)));
1260
+ return normalize(storage.getItem(rowOrderKey(workspaceId, queue, expiredOnly)));
1154
1261
  } catch {
1155
1262
  return [];
1156
1263
  }
1157
1264
  },
1158
- write(workspaceId, queue, ids) {
1265
+ write(workspaceId, queue, ids, expiredOnly = false) {
1159
1266
  if (storage === void 0) return;
1160
1267
  try {
1161
- storage.setItem(rowOrderKey(workspaceId, queue), JSON.stringify(ids));
1268
+ storage.setItem(rowOrderKey(workspaceId, queue, expiredOnly), JSON.stringify(ids));
1162
1269
  } catch {}
1163
1270
  }
1164
1271
  };
@@ -1214,13 +1321,6 @@ window.__ModuleLoader__.load({
1214
1321
  //#endregion
1215
1322
  //#region src/client/Panel.ts
1216
1323
  /** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
1217
- async function saveSetting(form, field, value) {
1218
- try {
1219
- return await form.set(field, value);
1220
- } catch {
1221
- return false;
1222
- }
1223
- }
1224
1324
  const QUEUES = [
1225
1325
  "verify",
1226
1326
  "pending",
@@ -1320,8 +1420,12 @@ window.__ModuleLoader__.load({
1320
1420
  }, t("stage.state." + stage.state) + expiry);
1321
1421
  }
1322
1422
  function runNode(view, t, copy) {
1323
- const run = view.latestRun;
1324
- if (run === void 0) return null;
1423
+ const active = view.activeRun;
1424
+ const last = view.lastRun;
1425
+ if (active === void 0 && last === void 0) return null;
1426
+ return (0, react.createElement)("div", { "data-role": "run-strips" }, active === void 0 ? null : runStrip(active, t, copy, "active"), last === void 0 ? null : runStrip(last, t, copy, "last"));
1427
+ }
1428
+ function runStrip(run, t, copy, role) {
1325
1429
  const report = run.actions.find((action) => action.id === "report");
1326
1430
  const reportEnabled = report?.enabled === true;
1327
1431
  const internals = [
@@ -1359,6 +1463,7 @@ window.__ModuleLoader__.load({
1359
1463
  ];
1360
1464
  return (0, react.createElement)("div", {
1361
1465
  "data-role": "run-strip",
1466
+ "data-run-role": role,
1362
1467
  "data-run-state": run.state
1363
1468
  }, (0, react.createElement)("span", { "data-role": "run-state" }, t("run.state") + " " + t("run." + run.state)), run.progress === null || run.progress === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-progress" }, t("run.progress") + " " + String(run.progress.done) + "/" + String(run.progress.total)), (0, react.createElement)("button", {
1364
1469
  type: "button",
@@ -1392,78 +1497,18 @@ window.__ModuleLoader__.load({
1392
1497
  "data-value": missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off"
1393
1498
  }, text), note === void 0 ? null : (0, react.createElement)("span", { "data-role": "settings-note" }, note), link === true ? (0, react.createElement)("span", { "data-role": "settings-link" }, t("settings.openPlatformPath")) : null);
1394
1499
  }
1395
- function EditableSetting({ form, field, label, fallback, capability, t }) {
1396
- const subscribe = (0, react.useCallback)((listener) => form.subscribe(listener), [form]);
1397
- const readSnapshot = (0, react.useCallback)(() => form["getSnapshot"](), [form]);
1398
- const snapshot = (0, react.useSyncExternalStore)(subscribe, readSnapshot, readSnapshot);
1399
- const fieldPresent = snapshot.status !== "ready" || snapshot.value === void 0 || Object.prototype.hasOwnProperty.call(snapshot.value, field);
1400
- const [busy, setBusy] = (0, react.useState)(false);
1401
- const [saved, setSaved] = (0, react.useState)(void 0);
1402
- const value = snapshot.status === "ready" && typeof snapshot.value?.[field] === "boolean" ? snapshot.value[field] : fallback;
1403
- const disabled = !capability || busy || snapshot.status !== "ready" || !snapshot.writable;
1404
- const toggle = async () => {
1405
- if (disabled) return;
1406
- setBusy(true);
1407
- setSaved(void 0);
1408
- const ok = await saveSetting(form, field, !value);
1409
- setBusy(false);
1410
- setSaved(ok ? "saved" : "failed");
1411
- };
1412
- if (!fieldPresent) return (0, react.createElement)(ReadOnlySetting, {
1413
- section: "platform-write",
1414
- label,
1415
- value: fallback,
1416
- disabled: !capability,
1417
- missing: false,
1418
- note: t("settings.platformWriteLinkHint"),
1419
- link: true,
1420
- t
1421
- });
1422
- return (0, react.createElement)("div", {
1423
- "data-role": "settings-section",
1424
- "data-section": field === "mergeApproval" ? "merge-approval" : "platform-write",
1425
- ...disabled ? { "data-disabled": "true" } : {}
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,
1432
- type: "checkbox",
1433
- role: "switch",
1434
- "data-role": "settings-toggle",
1435
- "data-field": field,
1436
- "data-value": value ? "on" : "off",
1437
- checked: value,
1438
- disabled,
1439
- onChange: () => {
1440
- toggle();
1441
- }
1442
- }), (0, react.createElement)("span", { "data-role": "settings-value" }, value ? t("settings.on") : t("settings.off")), ...saved === void 0 ? [] : [(0, react.createElement)("span", {
1443
- "data-role": "settings-save-state",
1444
- "data-save": saved
1445
- }, t(saved === "saved" ? "settings.saved" : "settings.saveFailed"))]);
1446
- }
1447
- function SettingsCard({ view, settings, t }) {
1500
+ function SettingsCard({ view, t }) {
1448
1501
  const platform = view.approval.platformWrite;
1449
- const platformCapability = platform?.capability ?? settings?.platform !== void 0;
1502
+ const platformCapability = platform?.capability === true;
1450
1503
  const platformFallback = platform?.enabled ?? false;
1451
- const mergeCapability = settings?.merge !== void 0;
1452
1504
  const mergeFallback = view.approval.mergeApproval;
1453
- return (0, react.createElement)("div", { "data-role": "settings-card" }, (0, react.createElement)("h3", { "data-role": "settings-title" }, t("settings.title")), (0, react.createElement)("p", { "data-role": "settings-hint" }, t("settings.hint")), settings?.merge === void 0 ? (0, react.createElement)(ReadOnlySetting, {
1505
+ return (0, react.createElement)("div", { "data-role": "settings-card" }, (0, react.createElement)("h3", { "data-role": "settings-title" }, t("settings.title")), (0, react.createElement)("p", { "data-role": "settings-hint" }, t("settings.hint")), (0, react.createElement)("p", { "data-role": "settings-path" }, t("settings.openPlatformPath")), (0, react.createElement)(ReadOnlySetting, {
1454
1506
  section: "merge-approval",
1455
1507
  label: t("settings.mergeApproval"),
1456
1508
  value: mergeFallback,
1457
1509
  disabled: false,
1458
1510
  missing: false,
1459
1511
  t
1460
- }) : (0, react.createElement)(EditableSetting, {
1461
- form: settings.merge,
1462
- field: "mergeApproval",
1463
- label: t("settings.mergeApproval"),
1464
- fallback: mergeFallback,
1465
- capability: mergeCapability,
1466
- t
1467
1512
  }), (0, react.createElement)(ReadOnlySetting, {
1468
1513
  section: "platform-write",
1469
1514
  label: t("settings.platformWrite"),
@@ -1471,35 +1516,53 @@ window.__ModuleLoader__.load({
1471
1516
  disabled: platformCapability === false,
1472
1517
  missing: platform === void 0,
1473
1518
  note: t("settings.platformWriteLinkHint"),
1474
- link: true,
1475
1519
  t
1476
- }), (0, react.createElement)("p", { "data-role": "settings-readonly" }, settings?.merge === void 0 ? t("settings.readonly") : t("settings.live")));
1520
+ }), (0, react.createElement)("p", { "data-role": "settings-readonly" }, t("settings.readonly")));
1477
1521
  }
1478
- function rowNode(row, t, copy, sort, openBody) {
1479
- const facts = [
1522
+ function rowNode(row, t, copy, sort, openBody, onDecide, busy) {
1523
+ const first = [
1480
1524
  row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
1525
+ row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
1526
+ row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
1527
+ row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText(t, row.reasonCode, row.reason ?? "")
1528
+ ].filter((value) => value !== void 0).join(" · ");
1529
+ const details = [
1530
+ t("row.id") + " " + row.id,
1481
1531
  row.targetActual === void 0 ? void 0 : t("row.actualTarget") + " " + row.targetActual,
1482
1532
  row.verifySubState === void 0 ? void 0 : t("row.verify") + " " + t("verify." + row.verifySubState),
1483
1533
  row.origin === void 0 ? void 0 : t("row.origin") + " " + t("origin." + row.origin),
1484
1534
  row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
1485
- row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2),
1486
- row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
1487
- row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
1488
- row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText(t, row.reasonCode, row.reason ?? "")
1535
+ row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2)
1489
1536
  ].filter((value) => value !== void 0).join(" · ");
1490
- const actions = row.actions.map((action) => (0, react.createElement)("button", {
1491
- key: action.id,
1492
- type: "button",
1493
- "data-role": "row-action",
1494
- "data-action": action.id,
1495
- ...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
1496
- title: action.disabledReason === void 0 ? action.command : t("disabled." + action.disabledReason),
1497
- disabled: !action.enabled,
1498
- onClick: () => {
1499
- if (!action.enabled) return;
1500
- copy(action.command);
1501
- }
1502
- }, t("action." + action.id)));
1537
+ const actions = row.actions.flatMap((action) => {
1538
+ const decision = (action.id === "approve" || action.id === "reject") && onDecide !== void 0;
1539
+ const primary = (0, react.createElement)("button", {
1540
+ key: action.id,
1541
+ type: "button",
1542
+ "data-role": decision ? "row-decision" : "row-action",
1543
+ "data-action": action.id,
1544
+ ...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
1545
+ title: action.command,
1546
+ disabled: !action.enabled || busy,
1547
+ onClick: () => {
1548
+ if (!action.enabled || busy) return;
1549
+ if (decision) onDecide(row.id, action.id);
1550
+ else copy(action.command);
1551
+ }
1552
+ }, decision ? t("decision." + action.id) : t("action." + action.id));
1553
+ if (!decision) return [primary];
1554
+ return [primary, (0, react.createElement)("button", {
1555
+ key: action.id + "-copy",
1556
+ type: "button",
1557
+ "data-role": "row-copy",
1558
+ "data-action": action.id + "-copy",
1559
+ title: action.command,
1560
+ disabled: busy,
1561
+ onClick: () => {
1562
+ if (!busy) copy(action.command);
1563
+ }
1564
+ }, t("row.copy"))];
1565
+ });
1503
1566
  return (0, react.createElement)("div", {
1504
1567
  key: row.id,
1505
1568
  "data-role": "row",
@@ -1539,10 +1602,16 @@ window.__ModuleLoader__.load({
1539
1602
  event.preventDefault();
1540
1603
  sort.menu(row.id, event.clientX, event.clientY);
1541
1604
  }
1542
- }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", {
1543
- "data-role": "row-id",
1544
- title: row.id
1545
- }, t("row.id") + " " + row.id), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), stageNode(row, t), summaryNode(row, t), facts === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, facts)), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, (() => {
1605
+ }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), stageNode(row, t), summaryNode(row, t), first === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, first), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details))), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, (0, react.createElement)("button", {
1606
+ type: "button",
1607
+ "data-role": "row-sort",
1608
+ "aria-haspopup": "menu",
1609
+ title: t("sort.menu"),
1610
+ onClick: (event) => {
1611
+ const rect = event.currentTarget.getBoundingClientRect();
1612
+ sort.menu(row.id, rect.left, rect.bottom);
1613
+ }
1614
+ }, t("sort.menu")), (() => {
1546
1615
  const bodyA = row.bodies?.find((body) => body.name === row.a);
1547
1616
  const bodyB = row.bodies?.find((body) => body.name === row.b);
1548
1617
  return [bodyA === void 0 ? null : (0, react.createElement)("button", {
@@ -1554,7 +1623,7 @@ window.__ModuleLoader__.load({
1554
1623
  onClick: () => {
1555
1624
  openBody(bodyA.name, bodyA.side, bodyA.rowId);
1556
1625
  }
1557
- }, "A"), bodyB === void 0 ? null : (0, react.createElement)("button", {
1626
+ }, "A · " + bodyA.name), bodyB === void 0 ? null : (0, react.createElement)("button", {
1558
1627
  key: "b",
1559
1628
  type: "button",
1560
1629
  "data-role": "body-b",
@@ -1563,7 +1632,7 @@ window.__ModuleLoader__.load({
1563
1632
  onClick: () => {
1564
1633
  openBody(bodyB.name, bodyB.side, bodyB.rowId);
1565
1634
  }
1566
- }, "B")];
1635
+ }, "B · " + bodyB.name)];
1567
1636
  })(), actions)));
1568
1637
  }
1569
1638
  const overlayKey = (event, onClose) => {
@@ -1586,39 +1655,55 @@ window.__ModuleLoader__.load({
1586
1655
  }
1587
1656
  };
1588
1657
  function bodyNode(overlay, t, onClose) {
1589
- return (0, react.createElement)("section", {
1658
+ const Modal = platformModal();
1659
+ const title = t("overlay.body") + " · " + overlay.name;
1660
+ const inner = (0, react.createElement)("section", {
1590
1661
  "data-role": "overlay",
1591
1662
  "data-overlay": "body",
1592
- role: "dialog",
1593
- "aria-modal": "true",
1594
- "aria-label": t("overlay.body"),
1595
- onKeyDown: (event) => {
1596
- overlayKey(event, onClose);
1597
- }
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", {
1663
+ "aria-label": title,
1664
+ ...Modal === void 0 ? {
1665
+ role: "dialog",
1666
+ "aria-modal": "true",
1667
+ onKeyDown: (event) => {
1668
+ overlayKey(event, onClose);
1669
+ }
1670
+ } : { "data-platform-modal": "true" }
1671
+ }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, title), (0, react.createElement)("button", {
1599
1672
  type: "button",
1600
1673
  "data-role": "overlay-close",
1601
- autoFocus: true,
1674
+ ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1602
1675
  onClick: onClose
1603
1676
  }, 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);
1677
+ return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1678
+ open: true,
1679
+ onClose,
1680
+ title,
1681
+ closeLabel: t("close"),
1682
+ headless: true,
1683
+ className: "evo-merge-modal"
1684
+ }, inner);
1604
1685
  }
1605
1686
  function historyNode(overlay, t, onClose) {
1606
- return (0, react.createElement)("section", {
1687
+ const Modal = platformModal();
1688
+ const title = t("history");
1689
+ const inner = (0, react.createElement)("section", {
1607
1690
  "data-role": "overlay",
1608
1691
  "data-overlay": "history",
1609
- role: "dialog",
1610
- "aria-modal": "true",
1611
- "aria-label": t("history"),
1612
- onKeyDown: (event) => {
1613
- if (event.key === "Escape") {
1614
- event.preventDefault();
1615
- onClose();
1692
+ "aria-label": title,
1693
+ ...Modal === void 0 ? {
1694
+ role: "dialog",
1695
+ "aria-modal": "true",
1696
+ onKeyDown: (event) => {
1697
+ if (event.key === "Escape") {
1698
+ event.preventDefault();
1699
+ onClose();
1700
+ }
1616
1701
  }
1617
- }
1618
- }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, t("history")), (0, react.createElement)("button", {
1702
+ } : { "data-platform-modal": "true" }
1703
+ }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, title), (0, react.createElement)("button", {
1619
1704
  type: "button",
1620
1705
  "data-role": "overlay-close",
1621
- autoFocus: true,
1706
+ ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1622
1707
  onClick: onClose
1623
1708
  }, 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", {
1624
1709
  key: item.name + ":" + item.archivedAt,
@@ -1628,6 +1713,14 @@ window.__ModuleLoader__.load({
1628
1713
  const reason = reasonText(t, item.reasonCode, item.reason);
1629
1714
  return kind === reason ? kind : kind + " · " + reason;
1630
1715
  })() + " · v" + String(item.lastVersion) + " · " + t("history.archivedAt", { date: new Date(item.archivedAt).toLocaleString() })))) : null);
1716
+ return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1717
+ open: true,
1718
+ onClose,
1719
+ title,
1720
+ closeLabel: t("close"),
1721
+ headless: true,
1722
+ className: "evo-merge-modal"
1723
+ }, inner);
1631
1724
  }
1632
1725
  function MergePanel(face) {
1633
1726
  const { store, t } = face;
@@ -1635,6 +1728,8 @@ window.__ModuleLoader__.load({
1635
1728
  const [copyState, setCopyState] = (0, react.useState)({ kind: "idle" });
1636
1729
  const [orders, setOrders] = (0, react.useState)({});
1637
1730
  const [dragId, setDragId] = (0, react.useState)(void 0);
1731
+ const [deciding, setDeciding] = (0, react.useState)(void 0);
1732
+ const [decisionNotice, setDecisionNotice] = (0, react.useState)(void 0);
1638
1733
  const [menu, setMenu] = (0, react.useState)(void 0);
1639
1734
  const returnFocus = (0, react.useRef)(null);
1640
1735
  const captureFocus = () => {
@@ -1656,6 +1751,27 @@ window.__ModuleLoader__.load({
1656
1751
  };
1657
1752
  const workspaceId = resolveWorkspaceId(face.workspaceId);
1658
1753
  const orderStore = (0, react.useMemo)(() => createRowOrderStore(), []);
1754
+ const decideRow = (rowId, verb) => {
1755
+ if (face.decide === void 0) return;
1756
+ setDeciding(rowId);
1757
+ (async () => {
1758
+ try {
1759
+ setDecisionNotice({
1760
+ rowId,
1761
+ ...await face.decide(rowId, verb)
1762
+ });
1763
+ store.refresh();
1764
+ } catch (error) {
1765
+ setDecisionNotice({
1766
+ rowId,
1767
+ ok: false,
1768
+ message: error instanceof Error ? error.message : String(error)
1769
+ });
1770
+ } finally {
1771
+ setDeciding(void 0);
1772
+ }
1773
+ })();
1774
+ };
1659
1775
  const copy = (command) => {
1660
1776
  (async () => {
1661
1777
  try {
@@ -1705,15 +1821,15 @@ window.__ModuleLoader__.load({
1705
1821
  const viewFailure = snapshot.viewState === "error" ? snapshot.viewFailure : void 0;
1706
1822
  const queue = snapshot.current;
1707
1823
  const uiState = blocked !== void 0 ? "blocked" : viewFailure !== void 0 ? "error" : snapshot.viewState === "loading" ? "loading" : queue.status === "error" ? "error" : queue.status === "loading" ? "loading" : queue.status === "empty" ? "empty" : "ready";
1708
- const orderKey = rowOrderKey(workspaceId, snapshot.queue);
1709
- const visibleOrder = orders[orderKey] ?? orderStore.read(workspaceId, snapshot.queue);
1824
+ const orderKey = rowOrderKey(workspaceId, snapshot.queue, snapshot.expiredOnly);
1825
+ const visibleOrder = orders[orderKey] ?? orderStore.read(workspaceId, snapshot.queue, snapshot.expiredOnly);
1710
1826
  const visibleRows = orderRows(queue.rows, visibleOrder);
1711
1827
  const saveOrder = (ids) => {
1712
1828
  setOrders((previous) => ({
1713
1829
  ...previous,
1714
1830
  [orderKey]: ids
1715
1831
  }));
1716
- orderStore.write(workspaceId, snapshot.queue, ids);
1832
+ orderStore.write(workspaceId, snapshot.queue, ids, snapshot.expiredOnly);
1717
1833
  };
1718
1834
  const sortHandlers = {
1719
1835
  start: (id) => {
@@ -1785,13 +1901,18 @@ window.__ModuleLoader__.load({
1785
1901
  store.toggleExpired();
1786
1902
  }
1787
1903
  }, t("tab.expired") + " " + String(view.expired)) : null;
1904
+ const notice = decisionNotice === void 0 ? null : (0, react.createElement)("p", {
1905
+ "data-role": "decision-notice",
1906
+ "data-ok": decisionNotice.ok ? "true" : "false"
1907
+ }, decisionNotice.message);
1788
1908
  const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", {
1789
1909
  "data-role": "rows",
1790
1910
  id: "merge-rows",
1791
1911
  role: "tabpanel"
1792
- }, visibleRows.map((row) => rowNode(row, t, copy, sortHandlers, openBody))) : null;
1912
+ }, visibleRows.map((row) => rowNode(row, t, copy, sortHandlers, openBody, face.decide === void 0 ? void 0 : decideRow, deciding === row.id))) : null;
1793
1913
  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;
1794
- const activeFailure = viewFailure !== void 0 ? viewFailure : queue.status === "error" ? queue.failure : void 0;
1914
+ const staleViewFailure = snapshot.viewState === "ready" ? snapshot.viewFailure : void 0;
1915
+ const activeFailure = viewFailure !== void 0 ? viewFailure : staleViewFailure !== void 0 ? staleViewFailure : queue.status === "error" ? queue.failure : void 0;
1795
1916
  const overlay = snapshot.overlay;
1796
1917
  const menuStyle = menu === void 0 ? void 0 : {
1797
1918
  position: "fixed",
@@ -1868,7 +1989,6 @@ window.__ModuleLoader__.load({
1868
1989
  }
1869
1990
  }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(SettingsCard, {
1870
1991
  view,
1871
- settings: face.settings,
1872
1992
  t
1873
1993
  }), runNode(view, t, copy), tabs, expiredToggle, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
1874
1994
  "data-role": "note",
@@ -1879,7 +1999,7 @@ window.__ModuleLoader__.load({
1879
1999
  }, t("copied") + " " + (copyState.text ?? "")) : (0, react.createElement)("p", {
1880
2000
  "data-role": "copy-failed",
1881
2001
  role: "alert"
1882
- }, t("copyFailed")), list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2002
+ }, t("copyFailed")), notice, list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
1883
2003
  type: "button",
1884
2004
  "data-role": "load-more",
1885
2005
  disabled: queue.status === "loading",
@@ -1957,6 +2077,7 @@ window.__ModuleLoader__.load({
1957
2077
  overlayToken = 0;
1958
2078
  snapshot;
1959
2079
  started = false;
2080
+ loadedView = false;
1960
2081
  disposed = false;
1961
2082
  constructor(source, initial = {}) {
1962
2083
  this.source = source;
@@ -2030,14 +2151,16 @@ window.__ModuleLoader__.load({
2030
2151
  });
2031
2152
  return;
2032
2153
  }
2154
+ const keeping = this.loadedView;
2033
2155
  this.set({
2034
2156
  ...this.snapshot,
2035
- viewState: "error",
2157
+ viewState: keeping ? "ready" : "error",
2036
2158
  viewFailure: outcome.failure,
2037
2159
  viewRefreshing: false
2038
2160
  });
2039
2161
  return;
2040
2162
  }
2163
+ this.loadedView = true;
2041
2164
  const wasBlocked = this.snapshot.viewState === "blocked";
2042
2165
  this.set({
2043
2166
  ...this.snapshot,
@@ -2261,8 +2384,19 @@ window.__ModuleLoader__.load({
2261
2384
  }
2262
2385
  };
2263
2386
  //#endregion
2387
+ //#region src/client/session.ts
2388
+ function currentSessionId(ctx) {
2389
+ const byId = ctx.sessions?.list?.getSnapshot?.().byId;
2390
+ if (byId === void 0) return void 0;
2391
+ return Object.values(byId).find((session) => (session.retainedBy?.mainView ?? 0) > 0)?.id;
2392
+ }
2393
+ //#endregion
2264
2394
  //#region src/client/index.ts
2265
- const inject = ["slots", "locale"];
2395
+ const inject = [
2396
+ "slots",
2397
+ "sessions",
2398
+ "locale"
2399
+ ];
2266
2400
  /** The view component receives the workbench's activeView marker alongside its face. */
2267
2401
  function MergeView(props) {
2268
2402
  return props.activeView === "merge" ? MergePanel(props) : null;
@@ -2272,6 +2406,10 @@ window.__ModuleLoader__.load({
2272
2406
  * @param ctx - the client context.
2273
2407
  */
2274
2408
  function apply(ctx) {
2409
+ const seat = createSettingsSeat(ctx);
2410
+ ctx.effect(() => () => {
2411
+ seat.dispose?.();
2412
+ }, "evolution-merge-ui: settings seat");
2275
2413
  registerWorkbenchView(ctx, {
2276
2414
  id: PANEL_ID,
2277
2415
  order: 45,
@@ -2292,7 +2430,6 @@ window.__ModuleLoader__.load({
2292
2430
  body,
2293
2431
  history
2294
2432
  };
2295
- const seat = createSettingsSeat(ctx);
2296
2433
  const workspaceId = panelWorkspaceId(ctx);
2297
2434
  return {
2298
2435
  store: new MergeStore(source),
@@ -2302,6 +2439,20 @@ window.__ModuleLoader__.load({
2302
2439
  ...seat.get("evolution-merge") === void 0 ? {} : { merge: seat.get("evolution-merge") },
2303
2440
  ...seat.get("evolution-approval") === void 0 ? {} : { platform: seat.get("evolution-approval") }
2304
2441
  },
2442
+ decide: async (rowId, verb) => {
2443
+ const sessionId = currentSessionId(ctx);
2444
+ if (sessionId === void 0) return {
2445
+ ok: false,
2446
+ message: "没有当前会话;请打开会话后重试"
2447
+ };
2448
+ const result = await decide(rowId, verb, sessionId);
2449
+ if (result.ok) return result.payload;
2450
+ const failure = result.failure;
2451
+ return {
2452
+ ok: false,
2453
+ message: "message" in failure && typeof failure.message === "string" ? failure.message : "审批请求失败"
2454
+ };
2455
+ },
2305
2456
  ...workspaceId === void 0 ? {} : { workspaceId }
2306
2457
  };
2307
2458
  }
@@ -55,6 +55,7 @@ export declare class MergeStore {
55
55
  private overlayToken;
56
56
  private snapshot;
57
57
  private started;
58
+ private loadedView;
58
59
  private disposed;
59
60
  constructor(source: MergeSource, initial?: MergeStoreInitial);
60
61
  readonly read: () => StoreSnapshot;
@@ -1,7 +1,7 @@
1
1
  /** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
2
2
  import { type ReactNode } from 'react';
3
3
  import type { MergeStore } from './MergeStore.ts';
4
- import type { ConfigFormLike, SettingsFace } from '@lmzhen/dsh-evolution-client-kit';
4
+ import type { SettingsFace } from '@lmzhen/dsh-evolution-client-kit';
5
5
  export type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
6
6
  export interface MergeFace {
7
7
  store: MergeStore;
@@ -9,7 +9,10 @@ export interface MergeFace {
9
9
  doctorCommand?: string;
10
10
  settings?: SettingsFace;
11
11
  workspaceId?: string;
12
+ decide?: (rowId: string, verb: 'approve' | 'reject') => Promise<{
13
+ ok: boolean;
14
+ message: string;
15
+ }>;
12
16
  }
13
- export declare function saveSetting(form: ConfigFormLike, field: string, value: boolean): Promise<boolean>;
14
17
  export declare function MergePanel(face: MergeFace): ReactNode;
15
18
  //# sourceMappingURL=Panel.d.ts.map
@@ -65,7 +65,8 @@ export interface MergeViewPayload {
65
65
  };
66
66
  notes: string[];
67
67
  degraded: boolean;
68
- latestRun?: MergeRunView;
68
+ activeRun?: MergeRunView;
69
+ lastRun?: MergeRunView;
69
70
  approval: MergeApproval;
70
71
  }
71
72
  export interface RowActionView {
@@ -177,6 +178,13 @@ export declare const isBlocked: (failure: MergeFailure) => failure is MergeBlock
177
178
  export declare function view(): Promise<MergeOutcome<MergeViewPayload>>;
178
179
  export declare function rows(queue: MergeQueue, cursor?: string, expiredOnly?: boolean): Promise<MergeOutcome<QueuePage>>;
179
180
  export declare function body(name: string, side: 'live' | 'archive', rowId?: string): Promise<MergeOutcome<MergeBody>>;
181
+ export interface MergeDecisionOutcome {
182
+ readonly ok: boolean;
183
+ readonly message: string;
184
+ readonly code?: string;
185
+ readonly runId?: string;
186
+ }
187
+ export declare function decide(rowId: string, verb: 'approve' | 'reject', sessionId: string): Promise<MergeOutcome<MergeDecisionOutcome>>;
180
188
  export declare function history(): Promise<MergeOutcome<{
181
189
  items: MergeHistoryItem[];
182
190
  }>>;
@@ -0,0 +1,15 @@
1
+ /** Platform Modal reached through the rc.3 client module table. */
2
+ import type { ReactNode } from 'react';
3
+ export interface PlatformModalProps {
4
+ readonly open: boolean;
5
+ readonly onClose: () => void;
6
+ readonly title: string;
7
+ readonly closeLabel: string;
8
+ readonly children?: ReactNode;
9
+ readonly headless?: boolean;
10
+ readonly className?: string;
11
+ }
12
+ export type PlatformModalComponent = (props: PlatformModalProps) => ReactNode;
13
+ /** The platform Modal, or undefined in a non-browser/SSR render or a deployment without the row. */
14
+ export declare function platformModal(): PlatformModalComponent | undefined;
15
+ //# sourceMappingURL=platform-modal.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare function currentSessionId(ctx: unknown): string | undefined;
2
+ //# sourceMappingURL=session.d.ts.map
@@ -7,12 +7,12 @@ export interface StorageLike {
7
7
  export type RowOrderEdge = 'top' | 'bottom';
8
8
  /** Workspace scope for one tab. A missing panel workspace is the local scope. */
9
9
  export declare function resolveWorkspaceId(workspaceId: string | null | undefined): string;
10
- /** Stable localStorage key for one workspace and queue. */
11
- export declare function rowOrderKey(workspaceId: string | null | undefined, queue: MergeQueue): string;
10
+ /** Stable localStorage key for one workspace, queue and expired filter. */
11
+ export declare function rowOrderKey(workspaceId: string | null | undefined, queue: MergeQueue, expiredOnly?: boolean): string;
12
12
  /** One tab's persisted order; failures degrade to an empty (wire) order. */
13
13
  export declare function createRowOrderStore(storage?: StorageLike | undefined): {
14
- read(workspaceId: string | null | undefined, queue: MergeQueue): readonly string[];
15
- write(workspaceId: string | null | undefined, queue: MergeQueue, ids: readonly string[]): void;
14
+ read(workspaceId: string | null | undefined, queue: MergeQueue, expiredOnly?: boolean): readonly string[];
15
+ write(workspaceId: string | null | undefined, queue: MergeQueue, ids: readonly string[], expiredOnly?: boolean): void;
16
16
  };
17
17
  /** Apply persisted ids first, then append unknown rows in their wire order. */
18
18
  export declare function orderRows<T extends {
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.32.6",
14
+ "version": "0.34.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.32.6"
54
+ "@lmzhen/dsh-evolution-client-kit": "^0.34.0"
55
55
  },
56
56
  "engines": {
57
57
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"