@lmzhen/dsh-evolution-merge-ui 0.30.18 → 0.30.19

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
@@ -7,10 +7,7 @@ window.__ModuleLoader__.load({
7
7
  let react = require("react");
8
8
  //#endregion
9
9
  //#region src/client/api.ts
10
- /**
11
- * The two read routes this panel uses. Parses the frozen wireVersion=1 envelopes
12
- * and maps them to the panel view types; the panel never sees a path or a raw host body.
13
- */
10
+ /** Read-only merge wire client. Classifies every failure and never writes. */
14
11
  const HOST_ROUTES = {
15
12
  approval: {
16
13
  pending: "/api/dsh-evolution/approval/pending",
@@ -37,6 +34,38 @@ window.__ModuleLoader__.load({
37
34
  },
38
35
  diagnostics: { view: "/api/dsh-evolution/diagnostics" }
39
36
  }.merge;
37
+ const isBlocked = (failure) => failure.kind === "blocked";
38
+ const asReadiness = (value) => {
39
+ const raw = typeof value === "object" && value !== null ? value : {};
40
+ const steps = Array.isArray(raw.steps) ? raw.steps.filter((step) => typeof step === "string") : void 0;
41
+ return {
42
+ ...typeof raw.ok === "boolean" ? { ok: raw.ok } : {},
43
+ ...typeof raw.code === "string" ? { code: raw.code } : {},
44
+ ...typeof raw.message === "string" ? { message: raw.message } : {},
45
+ ...steps === void 0 ? {} : { steps }
46
+ };
47
+ };
48
+ const asPlatformWrite = (value) => {
49
+ if (typeof value === "boolean") return {
50
+ enabled: value,
51
+ capability: true
52
+ };
53
+ if (typeof value !== "object" || value === null) return void 0;
54
+ const raw = value;
55
+ const enabled = typeof raw.enabled === "boolean" ? raw.enabled : typeof raw.value === "boolean" ? raw.value : void 0;
56
+ const capability = typeof raw.capability === "boolean" ? raw.capability : typeof raw.available === "boolean" ? raw.available : true;
57
+ return enabled === void 0 ? void 0 : {
58
+ enabled,
59
+ capability
60
+ };
61
+ };
62
+ const problem = (code, status, wireCode, message) => ({
63
+ kind: "problem",
64
+ code,
65
+ status,
66
+ ...wireCode === void 0 ? {} : { wireCode },
67
+ ...message === void 0 ? {} : { message }
68
+ });
40
69
  async function requestWire(path) {
41
70
  let response;
42
71
  try {
@@ -44,39 +73,16 @@ window.__ModuleLoader__.load({
44
73
  } catch {
45
74
  return {
46
75
  ok: false,
47
- failure: {
48
- kind: "server",
49
- status: 0
50
- }
76
+ failure: problem("server", 0)
51
77
  };
52
78
  }
53
79
  if (response.status === 401) return {
54
80
  ok: false,
55
- failure: {
56
- kind: "unauthenticated",
57
- status: 401
58
- }
81
+ failure: problem("unauthenticated", 401)
59
82
  };
60
83
  if (response.status === 403) return {
61
84
  ok: false,
62
- failure: {
63
- kind: "forbidden",
64
- status: 403
65
- }
66
- };
67
- if (response.status === 404) return {
68
- ok: false,
69
- failure: {
70
- kind: "unmounted",
71
- status: 404
72
- }
73
- };
74
- if (!response.ok) return {
75
- ok: false,
76
- failure: {
77
- kind: "server",
78
- status: response.status
79
- }
85
+ failure: problem("forbidden", 403)
80
86
  };
81
87
  let body;
82
88
  try {
@@ -84,34 +90,64 @@ window.__ModuleLoader__.load({
84
90
  } catch {
85
91
  return {
86
92
  ok: false,
87
- failure: {
88
- kind: "server",
89
- status: response.status
90
- }
93
+ failure: problem("server", response.status)
91
94
  };
92
95
  }
93
96
  const record = body;
94
- if (record.wireVersion !== 1) return {
97
+ const code = typeof record.code === "string" ? record.code : void 0;
98
+ const message = typeof record.message === "string" ? record.message : void 0;
99
+ if (code === "E-323" || message?.startsWith("E-323") === true) return {
95
100
  ok: false,
96
- failure: {
97
- kind: "server",
98
- status: response.status
99
- }
101
+ failure: problem("capability", response.status, code, message)
100
102
  };
101
- if (typeof record.code === "string") return {
103
+ if (response.status === 503 && code === "merge-state-blocked" && typeof record.readiness === "object" && record.readiness !== null) return {
102
104
  ok: false,
103
105
  failure: {
104
- kind: "refused",
105
- code: record.code,
106
- message: typeof record.message === "string" ? record.message : record.code
106
+ kind: "blocked",
107
+ code,
108
+ readiness: asReadiness(record.readiness)
107
109
  }
108
110
  };
111
+ if (response.status === 404) {
112
+ if (code === "E-325") return {
113
+ ok: false,
114
+ failure: problem("unmounted", 404, code, message)
115
+ };
116
+ if (code === "merge-body-missing" || code === "merge-body-not-found") return {
117
+ ok: false,
118
+ failure: problem("body-missing", 404, code, message)
119
+ };
120
+ return {
121
+ ok: false,
122
+ failure: problem("unmounted", 404, code, message)
123
+ };
124
+ }
125
+ if (response.status === 400 && code === "merge-bad-query") return {
126
+ ok: false,
127
+ failure: problem("bad-query", 400, code, message)
128
+ };
129
+ if (!response.ok) return {
130
+ ok: false,
131
+ failure: problem("server", response.status, code, message)
132
+ };
133
+ if (record.wireVersion !== 1) return {
134
+ ok: false,
135
+ failure: problem("wire-version", response.status, code, message)
136
+ };
137
+ if (code === "merge-body-missing" || code === "merge-body-not-found") return {
138
+ ok: false,
139
+ failure: problem("body-missing", response.status, code, message)
140
+ };
141
+ if (code !== void 0) return {
142
+ ok: false,
143
+ failure: problem("refused", response.status, code, message)
144
+ };
109
145
  return {
110
146
  ok: true,
111
147
  payload: body
112
148
  };
113
149
  }
114
- const escapeHtml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
150
+ const toPlatformWrite = (facts) => asPlatformWrite(facts.approval.platformWrite ?? facts.platformWrite);
115
151
  const toRun = (run) => ({
116
152
  id: run.id,
117
153
  state: run.state,
@@ -121,8 +157,47 @@ window.__ModuleLoader__.load({
121
157
  done: run.progress.done,
122
158
  total: run.progress.total
123
159
  } },
124
- startedAt: run.startedAt
160
+ startedAt: run.startedAt,
161
+ ...run.endedAt === void 0 ? {} : { endedAt: run.endedAt },
162
+ ...run.failure === void 0 ? {} : { failure: {
163
+ stage: run.failure.stage,
164
+ code: run.failure.code,
165
+ retryable: run.failure.retryable,
166
+ ...run.failure.detail === void 0 ? {} : { detail: run.failure.detail }
167
+ } },
168
+ ...run.parentRunId === void 0 ? {} : { parentRunId: run.parentRunId },
169
+ ...run.currentPairKey === void 0 ? {} : { currentPairKey: run.currentPairKey },
170
+ ...run.resultRef === void 0 ? {} : { resultRef: run.resultRef },
171
+ actions: (run.actions ?? []).map((action) => ({
172
+ id: action.id,
173
+ command: action.command,
174
+ confirm: action.confirm === true,
175
+ enabled: action.enabled !== false,
176
+ ...action.disabledReason === void 0 ? {} : { disabledReason: action.disabledReason }
177
+ }))
125
178
  });
179
+ const stageOf = (row) => {
180
+ const normalize = (raw) => {
181
+ if (typeof raw === "string") return raw === "reserved" || raw === "present" || raw === "expired" || raw === "lost" || raw === "absent" ? { state: raw } : void 0;
182
+ if (raw === void 0) return void 0;
183
+ if (raw.state === "reserved" || raw.state === "present" || raw.state === "expired" || raw.state === "lost" || raw.state === "absent") return {
184
+ state: raw.state,
185
+ ...raw.expiresAt === void 0 ? {} : { expiresAt: raw.expiresAt }
186
+ };
187
+ if (raw.state === "kept") return {
188
+ state: "present",
189
+ ...raw.expiresAt === void 0 ? {} : { expiresAt: raw.expiresAt }
190
+ };
191
+ };
192
+ const stage = normalize(row.stage);
193
+ const staging = normalize(row.staging);
194
+ if (stage === void 0) return staging;
195
+ const expiresAt = stage.expiresAt ?? (staging?.state === stage.state ? staging.expiresAt : void 0);
196
+ return expiresAt === void 0 ? stage : {
197
+ ...stage,
198
+ expiresAt
199
+ };
200
+ };
126
201
  const toRow = (row) => ({
127
202
  id: row.id,
128
203
  pairKey: row.id,
@@ -139,31 +214,31 @@ window.__ModuleLoader__.load({
139
214
  ...row.target.actual === void 0 ? {} : { targetActual: row.target.actual },
140
215
  ...row.target.created === void 0 ? {} : { targetCreated: row.target.created }
141
216
  },
142
- ...row.staging?.state === void 0 ? {} : { stagingState: row.staging.state },
143
- ...row.verifySubState === void 0 ? {} : { verifySubState: row.verifySubState },
144
- ...row.skipKind === void 0 ? {} : { skipKind: row.skipKind },
145
- ...row.bodies === void 0 ? {} : { bodies: row.bodies.map((body) => ({
146
- name: body.name,
147
- side: body.side
148
- })) },
149
217
  ...row.confidence === void 0 ? {} : { confidence: row.confidence },
150
- ...row.expiresAt === void 0 ? {} : { expiresAt: row.expiresAt },
151
- ...row.summary === void 0 ? {} : { summary: [
152
- row.summary.a,
153
- row.summary.b,
154
- row.summary.rationale
155
- ].filter((value) => value !== null) },
218
+ ...row.summary === void 0 ? {} : { summary: {
219
+ a: row.summary.a,
220
+ b: row.summary.b,
221
+ rationale: row.summary.rationale
222
+ } },
156
223
  ...row.reason === void 0 ? {} : { reason: row.reason },
157
- ...row.origin === void 0 ? {} : { origin: row.origin === "system" ? "agent" : row.origin },
224
+ ...row.reasonCode === void 0 ? {} : { reasonCode: row.reasonCode },
225
+ ...row.origin === void 0 ? {} : { origin: row.origin },
226
+ ...row.attempts === void 0 ? {} : { attempts: row.attempts },
227
+ ...row.expiresAt === void 0 ? {} : { stagingExpiresAt: row.expiresAt },
228
+ ...row.expired === void 0 ? {} : { expired: row.expired.kind },
229
+ ...stageOf(row) === void 0 ? {} : { stageView: stageOf(row) },
158
230
  ...row.failure === void 0 ? {} : { failure: {
159
231
  stage: row.failure.stage,
160
232
  code: row.failure.code,
161
233
  message: row.failure.detail ?? row.failure.code,
162
234
  retryable: row.failure.retryable
163
235
  } },
164
- ...row.staging?.expiresAt === void 0 ? {} : { stagingExpiresAt: row.staging.expiresAt },
165
- ...row.expired === void 0 ? {} : { expired: row.expired.kind },
166
- ...row.attempts === void 0 ? {} : { attempts: row.attempts },
236
+ ...row.verifySubState === void 0 ? {} : { verifySubState: row.verifySubState },
237
+ ...row.skipKind === void 0 ? {} : { skipKind: row.skipKind },
238
+ ...row.bodies === void 0 ? {} : { bodies: row.bodies.map((body) => ({
239
+ name: body.name,
240
+ side: body.side
241
+ })) },
167
242
  actions: row.actions.map((action) => ({
168
243
  id: action.id,
169
244
  command: action.command,
@@ -176,6 +251,7 @@ window.__ModuleLoader__.load({
176
251
  const answer = await requestWire(HOST_ROUTES.view);
177
252
  if (!answer.ok) return answer;
178
253
  const facts = answer.payload;
254
+ const platformWrite = toPlatformWrite(facts);
179
255
  return {
180
256
  ok: true,
181
257
  payload: {
@@ -195,7 +271,10 @@ window.__ModuleLoader__.load({
195
271
  notes: [...facts.notes],
196
272
  degraded: facts.degraded,
197
273
  ...facts.last === null && facts.active === null ? {} : { latestRun: toRun(facts.last ?? facts.active) },
198
- approval: facts.approval.mergeApproval
274
+ approval: {
275
+ mergeApproval: facts.approval.mergeApproval,
276
+ ...platformWrite === void 0 ? {} : { platformWrite }
277
+ }
199
278
  }
200
279
  };
201
280
  }
@@ -204,30 +283,42 @@ window.__ModuleLoader__.load({
204
283
  if (!answer.ok) return answer;
205
284
  if (answer.payload.queue !== queue || answer.payload.expiredOnly !== expiredOnly) return {
206
285
  ok: false,
207
- failure: {
208
- kind: "server",
209
- status: 200
210
- }
286
+ failure: problem("server", 200)
211
287
  };
212
288
  return {
213
289
  ok: true,
214
290
  payload: {
215
291
  queue: answer.payload.queue,
292
+ expiredOnly: answer.payload.expiredOnly,
216
293
  items: answer.payload.rows.map(toRow),
294
+ counts: answer.payload.counts,
217
295
  ...answer.payload.nextCursor === void 0 ? {} : { nextCursor: answer.payload.nextCursor }
218
296
  }
219
297
  };
220
298
  }
221
- async function body(id, side) {
222
- const answer = await requestWire(HOST_ROUTES.body + "?id=" + encodeURIComponent(id) + "&side=" + side);
223
- return answer.ok ? {
299
+ async function body(name, side) {
300
+ const answer = await requestWire(HOST_ROUTES.body + "?name=" + encodeURIComponent(name) + "&side=" + side);
301
+ if (!answer.ok) {
302
+ if (answer.failure.kind === "problem" && answer.failure.code === "bad-query" && (answer.failure.message ?? "").includes("multiple merge rows")) return {
303
+ ok: false,
304
+ failure: {
305
+ ...answer.failure,
306
+ code: "body-ambiguous"
307
+ }
308
+ };
309
+ return answer;
310
+ }
311
+ return {
224
312
  ok: true,
225
313
  payload: {
226
- content: answer.payload.text,
314
+ name: answer.payload.name,
315
+ side: answer.payload.side,
316
+ text: answer.payload.text,
227
317
  markdown: answer.payload.markdown,
228
- html: escapeHtml(answer.payload.text)
318
+ truncated: answer.payload.truncated,
319
+ totalBytes: answer.payload.totalBytes
229
320
  }
230
- } : answer;
321
+ };
231
322
  }
232
323
  async function history() {
233
324
  const answer = await requestWire(HOST_ROUTES.history);
@@ -237,6 +328,7 @@ window.__ModuleLoader__.load({
237
328
  name: item.name,
238
329
  kind: item.kind,
239
330
  reason: item.reason,
331
+ ...item.reasonCode === void 0 ? {} : { reasonCode: item.reasonCode },
240
332
  lastVersion: item.lastVersion,
241
333
  archivedAt: new Date(item.archivedAt).toISOString(),
242
334
  ...item.sourceRunId === void 0 ? {} : { sourceRunId: item.sourceRunId }
@@ -266,6 +358,7 @@ window.__ModuleLoader__.load({
266
358
  "tab.fuse": "待合并",
267
359
  "tab.skipped": "无需合并",
268
360
  "empty.verify": "没有待判断的对。",
361
+ "empty.scope": "这里只做两轮合成;机械追加属于 skill 管理,不进入合并队列。",
269
362
  "empty.pending": "没有待批准的合并。",
270
363
  "empty.fuse": "没有待合并的对。",
271
364
  "empty.skipped": "没有标记为无需合并的对。",
@@ -275,11 +368,20 @@ window.__ModuleLoader__.load({
275
368
  "approval.on": "审批开:合并前需人工批准。",
276
369
  "approval.off": "审批关:合并自动执行。",
277
370
  "failed": "读取失败,保留上次结果。",
371
+ "blocked.title": "合并状态未就绪",
372
+ "blocked.hint": "迁移/备份修复后刷新;面板不会自动降级。",
373
+ "blocked.doctor": "复制诊断命令",
374
+ "blocked.detail": "查看原始信息",
375
+ "blocked.reason.migration-blocked": "合并状态需要迁移,面板已暂停读取。",
376
+ "blocked.reason.needs-repair": "合并状态需要修复,面板已暂停读取。",
377
+ "blocked.reason.forward-only": "合并状态版本过新,当前面板暂不支持。",
378
+ "blocked.reason.unknown": "合并状态未就绪,面板已暂停读取。",
278
379
  "stage.decide": "判定",
279
380
  "stage.author": "合并",
280
381
  "stage.validate": "检查",
281
382
  "stage.commit": "提交",
282
383
  "row.target": "目标",
384
+ "row.actualTarget": "实际目标",
283
385
  "row.confidence": "置信度",
284
386
  "row.attempts": "尝试",
285
387
  "row.origin": "来源",
@@ -329,10 +431,14 @@ window.__ModuleLoader__.load({
329
431
  "failureUnauthorized": "未登录",
330
432
  "failureForbidden": "无权访问",
331
433
  "failureUnmounted": "宿主未挂载",
434
+ "failureCapability": "子代理通道不可用",
435
+ "failureCapabilityHint": "先运行全局诊断,或复制下方命令继续处理。preset/host-only 形态没有面板时仍可用这些命令兜底。",
436
+ "failureBodyAmbiguous": "同名技能匹配到多行,无法确定读取哪一个;请用唯一名称或从具体行打开。",
332
437
  "fallbackHint": "可复制命令见下方",
333
438
  "body.missing": "正文不存在或不可读。",
334
439
  "expired.target-taken": "目标已被占用",
335
440
  "expired.source-changed": "源已变更",
441
+ "expired.source-gone": "源已缺失",
336
442
  "expired.skipped-ttl": "跳过已到期",
337
443
  "history.kind.merged": "已合并",
338
444
  "history.kind.deleted": "已删除",
@@ -346,6 +452,89 @@ window.__ModuleLoader__.load({
346
452
  "state.fuse": "待合并",
347
453
  "state.skipped": "已跳过"
348
454
  };
455
+ Object.assign(zh, {
456
+ "stage.screening": "筛选",
457
+ "stage.settle": "结算",
458
+ "stage.state.reserved": "已预留",
459
+ "stage.state.present": "已暂存",
460
+ "stage.state.expired": "已过期",
461
+ "stage.state.lost": "暂存丢失",
462
+ "stage.state.absent": "无暂存",
463
+ "origin.system": "系统",
464
+ "row.id": "编号",
465
+ "row.requestedTarget": "请求目标",
466
+ "row.stage": "合并暂存",
467
+ "row.summaryA": "摘要 A",
468
+ "row.summaryB": "摘要 B",
469
+ "row.rationale": "理由",
470
+ "row.notProvided": "未提供",
471
+ "row.fullId": "完整编号",
472
+ "row.actions": "操作",
473
+ "action.unavailable": "不可用",
474
+ "history.reason.target-taken": "目标已被占用",
475
+ "history.reason.source-changed": "源已变更",
476
+ "history.reason.skipped-ttl": "跳过已到期",
477
+ "history.reason.validation-failed": "检查失败",
478
+ "history.reason.merge-over-limit": "超过限制",
479
+ "history.reason.source-gone": "源已缺失",
480
+ "history.reason.merged": "已合并",
481
+ "history.reason.archived": "已归档",
482
+ "history.reason.unknown": "原因未提供",
483
+ "history.lastVersion": "最后版本",
484
+ "history.archivedAt": "归档时间",
485
+ "body.truncated": "正文过长,已截断。",
486
+ "body.empty": "正文为空。",
487
+ "body.totalBytes": "总字节",
488
+ "body.live": "当前版本",
489
+ "body.archive": "归档版本",
490
+ "failureWireVersion": "宿主返回了不兼容的数据版本,请升级页面。",
491
+ "failureBadQuery": "请求已失效,请刷新后重试。",
492
+ "failureBodyMissing": "正文不存在或不可读。",
493
+ "failureRefused": "宿主拒绝了这次读取。",
494
+ "failureDetails": "查看详情",
495
+ "failureFallback": "可复制命令见下方",
496
+ "settings.title": "审批设置",
497
+ "settings.hint": "两段审批分开显示;值来自宿主能力,不从另一项推导。",
498
+ "settings.mergeApproval": "合并审批",
499
+ "settings.platformWrite": "平台写入审批",
500
+ "settings.on": "开",
501
+ "settings.off": "关",
502
+ "settings.capabilityMissing": "宿主未提供该能力",
503
+ "settings.capabilityDisabled": "当前部署不可用",
504
+ "settings.readonly": "只读显示;修改请到平台设置页。",
505
+ "run.title": "运行",
506
+ "run.id": "运行编号",
507
+ "run.state": "状态",
508
+ "run.stage": "阶段",
509
+ "run.progress": "进度",
510
+ "run.endedAt": "结束时间",
511
+ "run.failure": "失败",
512
+ "run.currentPair": "当前对",
513
+ "run.parent": "父运行",
514
+ "run.report": "报告",
515
+ "run.noReport": "暂无报告",
516
+ "run.resultRef": "报告位置",
517
+ "run.actions": "运行操作",
518
+ "run.running": "运行中",
519
+ "run.ended": "已结束",
520
+ "run.cancelled": "已取消",
521
+ "run.failed": "失败",
522
+ "run.succeeded": "成功",
523
+ "run.awaiting": "等待审批",
524
+ "run.awaiting-approval": "等待审批",
525
+ "run.undone": "已撤销",
526
+ "run.superseded": "已失效",
527
+ "run.interrupted": "已中断",
528
+ "run.orphan": "孤立",
529
+ "error.title": "读取失败",
530
+ "error.retry": "重试",
531
+ "error.raw": "原始信息",
532
+ "doctor.open": "打开全局诊断",
533
+ "sort.updatedAt": "按更新时间",
534
+ "sort.score": "按分数",
535
+ "pin": "置顶",
536
+ "unpin": "取消置顶"
537
+ });
349
538
  const en = {
350
539
  "entry.label": "Skill merge",
351
540
  "title": "Skill merge",
@@ -363,6 +552,7 @@ window.__ModuleLoader__.load({
363
552
  "tab.fuse": "Ready to merge",
364
553
  "tab.skipped": "Skipped",
365
554
  "empty.verify": "No pairs to judge.",
555
+ "empty.scope": "This panel only performs two-round synthesis; mechanical append belongs to skill management and does not enter the merge queue.",
366
556
  "empty.pending": "No pending merges.",
367
557
  "empty.fuse": "No pairs ready to merge.",
368
558
  "empty.skipped": "No pairs marked as not worth merging.",
@@ -372,11 +562,20 @@ window.__ModuleLoader__.load({
372
562
  "approval.on": "Approval on: merges wait for a human.",
373
563
  "approval.off": "Approval off: merges run automatically.",
374
564
  "failed": "Read failed; keeping the last result.",
565
+ "blocked.title": "Merge state blocked",
566
+ "blocked.hint": "Fix the migration/backup, then refresh. The panel does not fall back automatically.",
567
+ "blocked.doctor": "Copy diagnostics command",
568
+ "blocked.detail": "Show host message",
569
+ "blocked.reason.migration-blocked": "Merge state needs migration; the panel is paused.",
570
+ "blocked.reason.needs-repair": "Merge state needs repair; the panel is paused.",
571
+ "blocked.reason.forward-only": "Merge state was written by a newer version; this panel cannot read it.",
572
+ "blocked.reason.unknown": "Merge state is not ready; the panel is paused.",
375
573
  "stage.decide": "Decide",
376
574
  "stage.author": "Author",
377
575
  "stage.validate": "Validate",
378
576
  "stage.commit": "Commit",
379
577
  "row.target": "Target",
578
+ "row.actualTarget": "Actual target",
380
579
  "row.confidence": "Confidence",
381
580
  "row.attempts": "Attempts",
382
581
  "row.origin": "Origin",
@@ -426,10 +625,14 @@ window.__ModuleLoader__.load({
426
625
  "failureUnauthorized": "Not signed in",
427
626
  "failureForbidden": "Forbidden",
428
627
  "failureUnmounted": "Host not mounted",
628
+ "failureCapability": "Subagent channel unavailable",
629
+ "failureCapabilityHint": "Run global diagnostics first, or copy a command below to continue. The command fallback also covers preset/host-only shapes without a panel.",
630
+ "failureBodyAmbiguous": "The skill name matches multiple rows, so the body is ambiguous. Use a unique name or open it from a specific row.",
429
631
  "fallbackHint": "Copy the command below",
430
632
  "body.missing": "Body missing or unreadable.",
431
633
  "expired.target-taken": "target taken",
432
634
  "expired.source-changed": "source changed",
635
+ "expired.source-gone": "source gone",
433
636
  "expired.skipped-ttl": "skip expired",
434
637
  "history.kind.merged": "Merged",
435
638
  "history.kind.deleted": "Deleted",
@@ -443,6 +646,117 @@ window.__ModuleLoader__.load({
443
646
  "state.fuse": "Ready to merge",
444
647
  "state.skipped": "Skipped"
445
648
  };
649
+ Object.assign(en, {
650
+ "stage.screening": "Screen",
651
+ "stage.settle": "Settle",
652
+ "stage.state.reserved": "Reserved",
653
+ "stage.state.present": "Staged",
654
+ "stage.state.expired": "Expired",
655
+ "stage.state.lost": "Staging lost",
656
+ "stage.state.absent": "Not staged",
657
+ "origin.system": "System",
658
+ "row.id": "ID",
659
+ "row.requestedTarget": "Requested target",
660
+ "row.stage": "Merge staging",
661
+ "row.summaryA": "Summary A",
662
+ "row.summaryB": "Summary B",
663
+ "row.rationale": "Rationale",
664
+ "row.notProvided": "not provided",
665
+ "row.fullId": "Full ID",
666
+ "row.actions": "Actions",
667
+ "action.unavailable": "Unavailable",
668
+ "history.reason.target-taken": "target taken",
669
+ "history.reason.source-changed": "source changed",
670
+ "history.reason.skipped-ttl": "skip expired",
671
+ "history.reason.validation-failed": "validation failed",
672
+ "history.reason.merge-over-limit": "over limit",
673
+ "history.reason.source-gone": "source gone",
674
+ "history.reason.merged": "merged",
675
+ "history.reason.archived": "archived",
676
+ "history.reason.unknown": "reason not provided",
677
+ "history.lastVersion": "Last version",
678
+ "history.archivedAt": "Archived at",
679
+ "body.truncated": "Body is truncated.",
680
+ "body.empty": "Body is empty.",
681
+ "body.totalBytes": "Total bytes",
682
+ "body.live": "Live",
683
+ "body.archive": "Archive",
684
+ "failureWireVersion": "The host returned an incompatible data version; reload the page.",
685
+ "failureBadQuery": "This request is stale; refresh and retry.",
686
+ "failureBodyMissing": "Body missing or unreadable.",
687
+ "failureRefused": "The host refused this read.",
688
+ "failureDetails": "Show details",
689
+ "failureFallback": "Copy a command below",
690
+ "settings.title": "Approvals",
691
+ "settings.hint": "Two independent approval values; each comes from host capability, never derived from the other.",
692
+ "settings.mergeApproval": "Merge approval",
693
+ "settings.platformWrite": "Platform write approval",
694
+ "settings.on": "On",
695
+ "settings.off": "Off",
696
+ "settings.capabilityMissing": "Host did not provide this capability",
697
+ "settings.capabilityDisabled": "Not available in this deployment",
698
+ "settings.readonly": "Read-only here; edit in platform settings.",
699
+ "run.title": "Run",
700
+ "run.id": "Run ID",
701
+ "run.state": "State",
702
+ "run.stage": "Stage",
703
+ "run.progress": "Progress",
704
+ "run.endedAt": "Ended at",
705
+ "run.failure": "Failure",
706
+ "run.currentPair": "Current pair",
707
+ "run.parent": "Parent run",
708
+ "run.report": "Report",
709
+ "run.noReport": "No report yet",
710
+ "run.resultRef": "Report location",
711
+ "run.actions": "Run actions",
712
+ "run.running": "Running",
713
+ "run.ended": "Ended",
714
+ "run.cancelled": "Cancelled",
715
+ "run.failed": "Failed",
716
+ "run.succeeded": "Succeeded",
717
+ "run.awaiting": "Awaiting approval",
718
+ "run.awaiting-approval": "Awaiting approval",
719
+ "run.undone": "Undone",
720
+ "run.superseded": "Superseded",
721
+ "run.interrupted": "Interrupted",
722
+ "run.orphan": "Orphan",
723
+ "error.title": "Read failed",
724
+ "error.retry": "Retry",
725
+ "error.raw": "Raw message",
726
+ "doctor.open": "Open global diagnostics",
727
+ "sort.updatedAt": "Last updated",
728
+ "sort.score": "Score",
729
+ "pin": "Pin",
730
+ "unpin": "Unpin"
731
+ });
732
+ Object.assign(zh, {
733
+ "settings.saved": "已保存",
734
+ "settings.saveFailed": "保存失败",
735
+ "settings.openPlatform": "打开平台设置",
736
+ "settings.live": "可直接在此保存;platformWrite 能力来自宿主。"
737
+ });
738
+ Object.assign(en, {
739
+ "settings.saved": "Saved",
740
+ "settings.saveFailed": "Save failed",
741
+ "settings.openPlatform": "Open platform settings",
742
+ "settings.live": "Save directly here; platformWrite capability comes from the host."
743
+ });
744
+ Object.assign(zh, { "settings.platformWriteLinkHint": "此开关属于平台设置,这里只读;请到平台设置修改。" });
745
+ Object.assign(en, { "settings.platformWriteLinkHint": "This switch belongs to platform settings; this card is read-only. Change it in platform settings." });
746
+ Object.assign(zh, {
747
+ "sort.drag": "拖动排序",
748
+ "sort.menu": "行排序",
749
+ "sort.pinTop": "置顶",
750
+ "sort.pinBottom": "置底",
751
+ "sort.cancel": "取消"
752
+ });
753
+ Object.assign(en, {
754
+ "sort.drag": "Drag to reorder",
755
+ "sort.menu": "Row order",
756
+ "sort.pinTop": "Pin to top",
757
+ "sort.pinBottom": "Pin to bottom",
758
+ "sort.cancel": "Cancel"
759
+ });
446
760
  //#endregion
447
761
  //#region src/client/styles.ts
448
762
  const CSS_TAG_ID = "evolution-merge-panel";
@@ -465,320 +779,1043 @@ window.__ModuleLoader__.load({
465
779
  ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
466
780
  ".evolution-merge-panel [data-role=\"failure\"] [data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); }",
467
781
  ".evolution-merge-panel [data-role=\"fallback\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
468
- ".evolution-merge-panel [data-role=\"overlay\"] { position:relative; 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); }",
782
+ ".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); }",
783
+ ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
469
784
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
470
785
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
471
- ".evolution-merge-panel [data-role=\"row\"] { display:grid; grid-template-columns:1fr auto; gap:var(--evo-space-8); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); }",
786
+ ".evolution-merge-panel [data-role=\"row\"] { display:grid; grid-template-columns:1fr auto; gap:var(--evo-space-8); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); cursor:grab; }",
787
+ ".evolution-merge-panel [data-role=\"row\"]:active { cursor:grabbing; }",
472
788
  ".evolution-merge-panel [data-role=\"row-label\"] { font-weight:600; }",
473
789
  ".evolution-merge-panel [data-role=\"row-meta\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
474
790
  ".evolution-merge-panel [data-role=\"note\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
791
+ ".evolution-merge-panel [data-role=\"blocked\"] { display:flex; flex-direction:column; gap:var(--evo-space-8); padding:var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-state-warning-primary); border-left-width:var(--evo-hairline-width); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); }",
792
+ ".evolution-merge-panel [data-role=\"blocked-title\"] { margin:0; font-size:var(--evo-type-lg); font-weight:600; color:var(--dsw-alias-state-warning-primary); }",
793
+ ".evolution-merge-panel [data-role=\"blocked-reason\"] { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; }",
794
+ ".evolution-merge-panel [data-role=\"blocked-hint\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
795
+ ".evolution-merge-panel [data-role=\"blocked-actions\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
796
+ ".evolution-merge-panel [data-role=\"content\"] { display:flex; flex-direction:column; gap:var(--evo-space-12); }",
797
+ ".evolution-merge-panel [data-role=\"settings-card\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); padding:var(--evo-space-10); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); }",
798
+ ".evolution-merge-panel [data-role=\"settings-title\"] { margin:0; font-size:var(--evo-type-sm); }",
799
+ ".evolution-merge-panel [data-role=\"settings-hint\"], .evolution-merge-panel [data-role=\"settings-readonly\"] { margin:0; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
800
+ ".evolution-merge-panel [data-role=\"settings-section\"] { display:grid; grid-template-columns:minmax(0,1fr) auto auto; align-items:center; column-gap:var(--evo-space-8); }",
801
+ ".evolution-merge-panel [data-role=\"settings-section\"][data-disabled=\"true\"] { color:var(--dsw-alias-label-tertiary); }",
802
+ ".evolution-merge-panel [data-role=\"settings-toggle\"] { margin:0; }",
803
+ ".evolution-merge-panel [data-role=\"settings-value\"] { font-weight:600; min-width:2.5em; text-align:right; }",
804
+ ".evolution-merge-panel [data-role=\"settings-note\"] { grid-column:1/-1; color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
805
+ ".evolution-merge-panel [data-role=\"settings-link\"] { grid-column:1/-1; color:var(--dsw-alias-brand-primary); font-size:var(--evo-type-xs); }",
806
+ ".evolution-merge-panel [data-role=\"run-strip\"] { display:flex; gap:var(--evo-space-8); flex-wrap:wrap; align-items:center; font-size:var(--evo-type-xs); }",
807
+ ".evolution-merge-panel [data-role=\"run-failure\"] { color:var(--dsw-alias-state-error-primary); }",
808
+ ".evolution-merge-panel [data-role=\"stage-badge\"] { display:inline-block; margin-top:var(--evo-space-4); padding:0 var(--evo-space-4); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); font-size:var(--evo-type-xs); }",
809
+ ".evolution-merge-panel [data-role=\"stage-badge\"][data-tone=\"warn\"] { color:var(--dsw-alias-state-warning-primary); }",
810
+ ".evolution-merge-panel [data-role=\"stage-badge\"][data-tone=\"fail\"] { color:var(--dsw-alias-state-error-primary); border-color:var(--dsw-alias-state-error-primary); }",
811
+ ".evolution-merge-panel [data-role=\"row-id\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
812
+ ".evolution-merge-panel [data-role=\"pending-summary\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); margin-top:var(--evo-space-4); }",
813
+ ".evolution-merge-panel [data-role=\"pending-summary-slot\"] { display:flex; gap:var(--evo-space-6); }",
814
+ ".evolution-merge-panel [data-role=\"pending-summary-label\"] { color:var(--dsw-alias-label-tertiary); min-width:calc(var(--evo-space-16) * 2); }",
815
+ ".evolution-merge-panel [data-role=\"body-text\"] { white-space:pre-wrap; overflow-wrap:anywhere; max-height:50vh; overflow:auto; }",
816
+ ".evolution-merge-panel [data-role=\"overlay\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); padding:var(--evo-space-10); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); }",
817
+ ".evolution-merge-panel button:focus-visible, .evolution-merge-panel a:focus-visible { outline:2px solid var(--dsw-alias-brand-primary); outline-offset:2px; }",
818
+ ".evolution-merge-panel [data-role=\"sort-menu\"] { position:fixed; z-index:70; display:flex; flex-direction:column; min-width:var(--evo-measure-read); padding:var(--evo-space-4); 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-6) var(--dsw-alias-border-l2); }",
819
+ ".evolution-merge-panel [data-role=\"sort-menu\"] button { text-align:left; }",
475
820
  ".evolution-merge-panel [data-role=\"refresh\"] { align-self:flex-start; }"
476
821
  ].join("\n");
477
822
  //#endregion
478
- //#region src/client/states.generated.ts
479
- /** A neutral/ warn/ fail block; a fail block is role=alert so assistive tech announces it. */
480
- function Block(props) {
481
- const tone = props.tone ?? "neutral";
482
- return (0, react.createElement)("div", {
483
- "data-tone": tone,
484
- ...tone === "fail" ? { role: "alert" } : {}
485
- }, props.title === void 0 ? null : (0, react.createElement)("div", { "data-tone-title": tone }, props.title), props.children);
486
- }
487
- /** One classified failure as a reader-facing sentence. */
488
- function failureText(failure, t) {
489
- switch (failure.kind) {
490
- case "unauthenticated": return t("failureUnauthorized");
491
- case "forbidden": return t("failureForbidden");
492
- case "unmounted": return t("failureUnmounted");
493
- case "server": return failure.status === 0 ? t("failureServer") : t("failureServer") + " (HTTP " + String(failure.status) + ")";
494
- case "refused": return failure.message;
823
+ //#region src/client/sorting.ts
824
+ /** Workspace scope for one tab. A missing panel workspace is the local scope. */
825
+ function resolveWorkspaceId(workspaceId) {
826
+ const value = workspaceId?.trim() ?? "";
827
+ return value === "" ? "local" : value;
828
+ }
829
+ /** Stable localStorage key for one workspace and queue. */
830
+ function rowOrderKey(workspaceId, queue) {
831
+ return "dsh-evolution-merge.row-order.v1:" + encodeURIComponent(resolveWorkspaceId(workspaceId)) + ":" + queue;
832
+ }
833
+ function defaultStorage() {
834
+ try {
835
+ return globalThis.localStorage;
836
+ } catch {
837
+ return;
495
838
  }
496
839
  }
497
- /** The failed-state block: sentence + (when the surface exists but is unreachable) the command fallback. */
498
- function FailureBlock(props) {
499
- const hint = props.fallback === void 0 ? null : (0, react.createElement)("div", { "data-tone-hint": "true" }, props.t("fallbackHint", { command: props.fallback.command }));
500
- return Block({
501
- tone: "fail",
502
- title: failureText(props.failure, props.t),
503
- children: hint
504
- });
840
+ function normalize(raw) {
841
+ if (raw === null) return [];
842
+ try {
843
+ const value = JSON.parse(raw);
844
+ if (!Array.isArray(value)) return [];
845
+ const ids = [];
846
+ const seen = /* @__PURE__ */ new Set();
847
+ for (const item of value) {
848
+ if (typeof item !== "string" || item === "" || seen.has(item)) continue;
849
+ seen.add(item);
850
+ ids.push(item);
851
+ }
852
+ return ids;
853
+ } catch {
854
+ return [];
855
+ }
505
856
  }
506
- //#endregion
507
- //#region src/client/fallbacks.generated.ts
508
- /**
509
- * The command-face fallback for every Host surface, generated from evolution-commands'
510
- * COMMAND_FALLBACKS (the ONE home). When the platform's connection carrier is absent (E-325) the
511
- * panels render this hint instead of a bare HTTP failure — same use case, command face.
512
- */
513
- const FALLBACKS = {
514
- approval: { command: "/evolution pending … /evolution approve <id> … /evolution reject <id> … /evolution release <id>" },
515
- "skill-history": { command: "/evolution restore <name> [v](查看:/evolution skills history <name>)" },
516
- "skill-health": { command: "/evolution skills refresh(健康行:/evolution doctor)" },
517
- merge: { command: "/evolution merge status / pending / approve <id> / report" },
518
- diagnostics: {
519
- command: "/evolution doctor",
520
- platformCli: "dsh --json /evolution doctor"
857
+ /** One tab's persisted order; failures degrade to an empty (wire) order. */
858
+ function createRowOrderStore(storage = defaultStorage()) {
859
+ return {
860
+ read(workspaceId, queue) {
861
+ if (storage === void 0) return [];
862
+ try {
863
+ return normalize(storage.getItem(rowOrderKey(workspaceId, queue)));
864
+ } catch {
865
+ return [];
866
+ }
867
+ },
868
+ write(workspaceId, queue, ids) {
869
+ if (storage === void 0) return;
870
+ try {
871
+ storage.setItem(rowOrderKey(workspaceId, queue), JSON.stringify(ids));
872
+ } catch {}
873
+ }
874
+ };
875
+ }
876
+ /** Apply persisted ids first, then append unknown rows in their wire order. */
877
+ function orderRows(rows, order) {
878
+ if (order.length === 0) return rows;
879
+ const byId = new Map(rows.map((row) => [row.id, row]));
880
+ const out = [];
881
+ const seen = /* @__PURE__ */ new Set();
882
+ for (const id of order) {
883
+ const row = byId.get(id);
884
+ if (row === void 0 || seen.has(id)) continue;
885
+ seen.add(id);
886
+ out.push(row);
521
887
  }
522
- };
888
+ for (const row of rows) if (!seen.has(row.id)) out.push(row);
889
+ return out;
890
+ }
891
+ /** Move one visible row before another; unknown ids leave the order unchanged. */
892
+ function moveRow(order, fromId, beforeId) {
893
+ if (!order.includes(fromId)) return order;
894
+ const rest = order.filter((id) => id !== fromId);
895
+ if (beforeId === void 0) return [...rest, fromId];
896
+ const index = rest.indexOf(beforeId);
897
+ if (index < 0) return [...rest, fromId];
898
+ return [
899
+ ...rest.slice(0, index),
900
+ fromId,
901
+ ...rest.slice(index)
902
+ ];
903
+ }
904
+ /** Pin one visible row to the top or bottom of the full visible order. */
905
+ function pinRow(order, id, edge) {
906
+ if (!order.includes(id)) return order;
907
+ const rest = order.filter((item) => item !== id);
908
+ return edge === "top" ? [id, ...rest] : [...rest, id];
909
+ }
910
+ //#endregion
911
+ //#region src/client/fallbacks.ts
912
+ /** Static command-face fallback for a Facts read failure; the API fallback is unavailable then. */
913
+ const FALLBACK_COMMANDS = [
914
+ "/evolution merge scan",
915
+ "/evolution merge pair <a> <b>",
916
+ "/evolution merge pending",
917
+ "/evolution merge pending --expired",
918
+ "/evolution merge skipped",
919
+ "/evolution merge candidates",
920
+ "/evolution merge status",
921
+ "/evolution merge report",
922
+ "/evolution doctor"
923
+ ];
523
924
  //#endregion
524
925
  //#region src/client/Panel.ts
525
- /**
526
- * The merge panel body: three queues, counts, and a read-only row list. Actions are
527
- * intentionally copied commands, not buttons that write from the browser.
528
- */
926
+ /** Batch 4 merge panel: keyed read store, four tabs, local overlays, localized errors. */
927
+ async function saveSetting(form, field, value) {
928
+ try {
929
+ return await form.set(field, value);
930
+ } catch {
931
+ return false;
932
+ }
933
+ }
529
934
  const QUEUES = [
530
935
  "verify",
531
936
  "pending",
532
937
  "fuse",
533
938
  "skipped"
534
939
  ];
535
- function failure(t, failure) {
536
- if (failure.kind === "refused" && failure.code === "merge-body-missing") return FailureBlock({
537
- failure: {
538
- ...failure,
539
- message: t("body.missing")
540
- },
541
- t,
542
- fallback: FALLBACKS["merge"]
543
- });
544
- return FailureBlock({
545
- failure,
546
- t,
547
- fallback: FALLBACKS["merge"]
940
+ const DOCTOR_COMMAND = "/evolution doctor";
941
+ function problemKey(problem) {
942
+ switch (problem.code) {
943
+ case "unauthenticated": return "failureUnauthorized";
944
+ case "forbidden": return "failureForbidden";
945
+ case "unmounted": return "failureUnmounted";
946
+ case "capability": return "failureCapability";
947
+ case "body-ambiguous": return "failureBodyAmbiguous";
948
+ case "wire-version": return "failureWireVersion";
949
+ case "body-missing": return "failureBodyMissing";
950
+ case "bad-query": return "failureBadQuery";
951
+ case "refused": return "failureRefused";
952
+ case "server": return "failureServer";
953
+ }
954
+ }
955
+ function reasonText(t, reasonCode, fallback) {
956
+ if (reasonCode === void 0) return fallback;
957
+ const key = "history.reason." + reasonCode;
958
+ const text = t(key);
959
+ return text === key ? fallback : text;
960
+ }
961
+ function blockedReasonKey(failure) {
962
+ const code = failure.readiness.code ?? failure.code;
963
+ if (code.endsWith("needs-repair")) return "blocked.reason.needs-repair";
964
+ if (code.endsWith("forward-only")) return "blocked.reason.forward-only";
965
+ if (code.endsWith("migration-blocked")) return "blocked.reason.migration-blocked";
966
+ return "blocked.reason.unknown";
967
+ }
968
+ function failureNode(t, failure, fallback, copy) {
969
+ if (isBlocked(failure)) {
970
+ const raw = failure.readiness.message;
971
+ return (0, react.createElement)("div", {
972
+ "data-role": "blocked",
973
+ role: "alert",
974
+ "data-block-code": failure.code,
975
+ "data-readiness-code": failure.readiness.code ?? ""
976
+ }, (0, react.createElement)("h3", { "data-role": "blocked-title" }, t("blocked.title")), (0, react.createElement)("p", {
977
+ "data-role": "blocked-reason",
978
+ title: raw ?? t(blockedReasonKey(failure))
979
+ }, t(blockedReasonKey(failure))), (0, react.createElement)("p", { "data-role": "blocked-hint" }, t("blocked.hint")), raw === void 0 ? null : (0, react.createElement)("details", { "data-role": "blocked-detail" }, (0, react.createElement)("summary", null, t("blocked.detail")), (0, react.createElement)("p", { "data-role": "blocked-raw" }, raw)), (0, react.createElement)("div", { "data-role": "blocked-actions" }, (0, react.createElement)("button", {
980
+ type: "button",
981
+ "data-role": "doctor",
982
+ onClick: () => {
983
+ copy(DOCTOR_COMMAND);
984
+ }
985
+ }, t("blocked.doctor")), (0, react.createElement)("button", {
986
+ type: "button",
987
+ "data-role": "refresh",
988
+ onClick: () => {
989
+ window.dispatchEvent(new Event("evolution-merge:refresh"));
990
+ }
991
+ }, t("refresh"))));
992
+ }
993
+ const capability = failure.code === "capability";
994
+ return (0, react.createElement)("div", {
995
+ "data-role": capability ? "capability-failure" : "failure",
996
+ "data-error-code": failure.code,
997
+ "data-status": String(failure.status)
998
+ }, (0, react.createElement)("p", {
999
+ "data-role": "failure-title",
1000
+ title: failure.message
1001
+ }, t(problemKey(failure))), capability ? (0, react.createElement)("p", { "data-role": "capability-hint" }, t("failureCapabilityHint")) : null, failure.message === void 0 ? null : (0, react.createElement)("details", { "data-role": "failure-detail" }, (0, react.createElement)("summary", null, t("failureDetails")), (0, react.createElement)("p", { "data-role": "failure-raw" }, failure.message)), fallback.length === 0 ? null : (0, react.createElement)("div", { "data-role": "fallback" }, fallback.map((command) => (0, react.createElement)("button", {
1002
+ key: command,
1003
+ type: "button",
1004
+ "data-role": "fallback-command",
1005
+ onClick: () => {
1006
+ copy(command);
1007
+ }
1008
+ }, command))));
1009
+ }
1010
+ function summaryNode(row, t) {
1011
+ if (row.state !== "pending" || row.summary === void 0) return null;
1012
+ return (0, react.createElement)("div", { "data-role": "pending-summary" }, [
1013
+ ["row.summaryA", row.summary.a],
1014
+ ["row.summaryB", row.summary.b],
1015
+ ["row.rationale", row.summary.rationale]
1016
+ ].map(([label, value]) => (0, react.createElement)("div", {
1017
+ key: label,
1018
+ "data-role": "pending-summary-slot"
1019
+ }, (0, react.createElement)("span", { "data-role": "pending-summary-label" }, t(label)), (0, react.createElement)("span", { "data-role": "pending-summary-value" }, value === null || value === "" ? t("row.noSummary") : value))));
1020
+ }
1021
+ function stageNode(row, t) {
1022
+ const stage = row.stageView;
1023
+ if (stage === void 0) return null;
1024
+ const tone = stage.state === "lost" ? "fail" : stage.state === "expired" || stage.state === "reserved" ? "warn" : "neutral";
1025
+ const expiry = stage.expiresAt === void 0 ? "" : " · " + t("row.expires") + " " + new Date(stage.expiresAt).toLocaleString();
1026
+ return (0, react.createElement)("span", {
1027
+ "data-role": "stage-badge",
1028
+ "data-stage": stage.state,
1029
+ "data-tone": tone
1030
+ }, t("stage.state." + stage.state) + expiry);
1031
+ }
1032
+ function runNode(view, t, copy) {
1033
+ const run = view.latestRun;
1034
+ if (run === void 0) return null;
1035
+ const report = run.actions.find((action) => action.id === "report");
1036
+ const reportEnabled = report?.enabled === true;
1037
+ return (0, react.createElement)("div", {
1038
+ "data-role": "run-strip",
1039
+ "data-run-state": run.state
1040
+ }, (0, react.createElement)("span", {
1041
+ "data-role": "run-id",
1042
+ title: run.id
1043
+ }, 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", {
1044
+ "data-role": "run-failure",
1045
+ title: run.failure.detail
1046
+ }, 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", {
1047
+ "data-role": "run-result-ref",
1048
+ title: run.resultRef
1049
+ }, t("run.resultRef")), (0, react.createElement)("button", {
1050
+ type: "button",
1051
+ "data-role": "run-report",
1052
+ "data-action": "report",
1053
+ disabled: !reportEnabled,
1054
+ title: reportEnabled ? report?.command ?? t("run.report") : t("run.noReport"),
1055
+ onClick: () => {
1056
+ if (reportEnabled && report !== void 0) copy(report.command);
1057
+ }
1058
+ }, t("run.report")), ...run.actions.filter((action) => action.id !== "report").map((action) => (0, react.createElement)("button", {
1059
+ key: action.id,
1060
+ type: "button",
1061
+ "data-role": "run-action",
1062
+ "data-action": action.id,
1063
+ disabled: !action.enabled,
1064
+ title: action.disabledReason === void 0 ? action.command : t("disabled." + action.disabledReason),
1065
+ onClick: () => {
1066
+ if (action.enabled) copy(action.command);
1067
+ }
1068
+ }, t("action." + action.id))));
1069
+ }
1070
+ function ReadOnlySetting({ section, label, value, disabled, missing, note, link, t }) {
1071
+ const text = missing ? t("settings.capabilityMissing") : disabled ? t("settings.capabilityDisabled") : value === true ? t("settings.on") : t("settings.off");
1072
+ return (0, react.createElement)("div", {
1073
+ "data-role": "settings-section",
1074
+ "data-section": section,
1075
+ "data-disabled": missing || disabled ? "true" : "false"
1076
+ }, (0, react.createElement)("span", { "data-role": "settings-label" }, label), (0, react.createElement)("span", {
1077
+ "data-role": "settings-value",
1078
+ "data-value": missing ? "missing" : disabled ? "unavailable" : value === true ? "on" : "off"
1079
+ }, text), note === void 0 ? null : (0, react.createElement)("span", { "data-role": "settings-note" }, note), link === true ? (0, react.createElement)("a", {
1080
+ "data-role": "settings-link",
1081
+ href: "#/settings",
1082
+ onClick: () => {
1083
+ window.dispatchEvent(new KeyboardEvent("keydown", {
1084
+ key: ",",
1085
+ code: "Comma",
1086
+ ctrlKey: true,
1087
+ altKey: true,
1088
+ bubbles: true
1089
+ }));
1090
+ }
1091
+ }, t("settings.openPlatform")) : null);
1092
+ }
1093
+ function EditableSetting({ form, field, label, fallback, capability, t }) {
1094
+ const subscribe = (0, react.useCallback)((listener) => form.subscribe(listener), [form]);
1095
+ const readSnapshot = (0, react.useCallback)(() => form["getSnapshot"](), [form]);
1096
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, readSnapshot, readSnapshot);
1097
+ const fieldPresent = snapshot.status !== "ready" || snapshot.value === void 0 || Object.prototype.hasOwnProperty.call(snapshot.value, field);
1098
+ const [busy, setBusy] = (0, react.useState)(false);
1099
+ const [saved, setSaved] = (0, react.useState)(void 0);
1100
+ const value = snapshot.status === "ready" && typeof snapshot.value?.[field] === "boolean" ? snapshot.value[field] : fallback;
1101
+ const disabled = !capability || busy || snapshot.status !== "ready" || !snapshot.writable;
1102
+ const toggle = async () => {
1103
+ if (disabled) return;
1104
+ setBusy(true);
1105
+ setSaved(void 0);
1106
+ const ok = await saveSetting(form, field, !value);
1107
+ setBusy(false);
1108
+ setSaved(ok ? "saved" : "failed");
1109
+ };
1110
+ if (!fieldPresent) return (0, react.createElement)(ReadOnlySetting, {
1111
+ section: "platform-write",
1112
+ label,
1113
+ value: fallback,
1114
+ disabled: !capability,
1115
+ missing: false,
1116
+ note: t("settings.platformWriteLinkHint"),
1117
+ link: true,
1118
+ t
548
1119
  });
1120
+ return (0, react.createElement)("div", {
1121
+ "data-role": "settings-section",
1122
+ "data-section": field === "mergeApproval" ? "merge-approval" : "platform-write",
1123
+ ...disabled ? { "data-disabled": "true" } : {}
1124
+ }, (0, react.createElement)("label", { "data-role": "settings-label" }, label), (0, react.createElement)("input", {
1125
+ type: "checkbox",
1126
+ role: "switch",
1127
+ "data-role": "settings-toggle",
1128
+ "data-field": field,
1129
+ "data-value": value ? "on" : "off",
1130
+ checked: value,
1131
+ disabled,
1132
+ onChange: () => {
1133
+ toggle();
1134
+ }
1135
+ }), (0, react.createElement)("span", { "data-role": "settings-value" }, value ? t("settings.on") : t("settings.off")), ...saved === void 0 ? [] : [(0, react.createElement)("span", {
1136
+ "data-role": "settings-save-state",
1137
+ "data-save": saved
1138
+ }, t(saved === "saved" ? "settings.saved" : "settings.saveFailed"))]);
1139
+ }
1140
+ function SettingsCard({ view, settings, t }) {
1141
+ const platform = view.approval.platformWrite;
1142
+ const platformCapability = platform?.capability ?? settings?.platform !== void 0;
1143
+ const platformFallback = platform?.enabled ?? false;
1144
+ const mergeCapability = settings?.merge !== void 0;
1145
+ const mergeFallback = view.approval.mergeApproval;
1146
+ 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, {
1147
+ section: "merge-approval",
1148
+ label: t("settings.mergeApproval"),
1149
+ value: mergeFallback,
1150
+ disabled: false,
1151
+ missing: false,
1152
+ t
1153
+ }) : (0, react.createElement)(EditableSetting, {
1154
+ form: settings.merge,
1155
+ field: "mergeApproval",
1156
+ label: t("settings.mergeApproval"),
1157
+ fallback: mergeFallback,
1158
+ capability: mergeCapability,
1159
+ t
1160
+ }), settings?.platform === void 0 ? (0, react.createElement)(ReadOnlySetting, {
1161
+ section: "platform-write",
1162
+ label: t("settings.platformWrite"),
1163
+ value: platformFallback,
1164
+ disabled: platformCapability === false,
1165
+ missing: platform === void 0,
1166
+ note: t("settings.platformWriteLinkHint"),
1167
+ link: true,
1168
+ t
1169
+ }) : (0, react.createElement)(EditableSetting, {
1170
+ form: settings.platform,
1171
+ field: "enabled",
1172
+ label: t("settings.platformWrite"),
1173
+ fallback: platformFallback,
1174
+ capability: platformCapability,
1175
+ t
1176
+ }), settings?.merge === void 0 || settings?.platform === void 0 ? (0, react.createElement)("a", {
1177
+ "data-role": "settings-link",
1178
+ href: "#/settings"
1179
+ }, t("settings.openPlatform")) : null, (0, react.createElement)("p", { "data-role": "settings-readonly" }, settings?.merge === void 0 || settings?.platform === void 0 ? t("settings.readonly") : t("settings.live")));
1180
+ }
1181
+ function rowNode(row, store, t, copy, sort) {
1182
+ const facts = [
1183
+ row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
1184
+ row.targetActual === void 0 ? void 0 : t("row.actualTarget") + " " + row.targetActual,
1185
+ row.verifySubState === void 0 ? void 0 : t("row.verify") + " " + t("verify." + row.verifySubState),
1186
+ row.origin === void 0 ? void 0 : t("row.origin") + " " + t("origin." + row.origin),
1187
+ row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
1188
+ row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2),
1189
+ row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
1190
+ row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
1191
+ row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText(t, row.reasonCode, row.reason ?? "")
1192
+ ].filter((value) => value !== void 0).join(" · ");
1193
+ const actions = row.actions.map((action) => (0, react.createElement)("button", {
1194
+ key: action.id,
1195
+ type: "button",
1196
+ "data-role": "row-action",
1197
+ "data-action": action.id,
1198
+ ...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
1199
+ title: action.disabledReason === void 0 ? action.command : t("disabled." + action.disabledReason),
1200
+ disabled: !action.enabled,
1201
+ onClick: () => {
1202
+ if (!action.enabled) return;
1203
+ if (action.confirm && globalThis.confirm !== void 0 && !globalThis.confirm(t("action.confirm") + " " + (t("action." + action.id) || action.id))) return;
1204
+ copy(action.command);
1205
+ }
1206
+ }, t("action." + action.id)));
1207
+ return (0, react.createElement)("div", {
1208
+ key: row.id,
1209
+ "data-role": "row",
1210
+ "data-row-id": row.id,
1211
+ "data-row-state": row.state,
1212
+ "data-row-queue": row.queue,
1213
+ draggable: true,
1214
+ title: t("sort.drag"),
1215
+ onDragStart: (event) => {
1216
+ if (event.dataTransfer != null) {
1217
+ event.dataTransfer.setData("text/plain", row.id);
1218
+ event.dataTransfer.effectAllowed = "move";
1219
+ }
1220
+ sort.start(row.id);
1221
+ },
1222
+ onDragOver: (event) => {
1223
+ event.preventDefault();
1224
+ if (event.dataTransfer != null) event.dataTransfer.dropEffect = "move";
1225
+ sort.over(event);
1226
+ },
1227
+ onDrop: (event) => {
1228
+ event.preventDefault();
1229
+ sort.drop(row.id);
1230
+ },
1231
+ onDragEnd: () => {
1232
+ sort.end();
1233
+ },
1234
+ onContextMenu: (event) => {
1235
+ event.preventDefault();
1236
+ sort.menu(row.id, event.clientX, event.clientY);
1237
+ }
1238
+ }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", {
1239
+ "data-role": "row-id",
1240
+ title: row.id
1241
+ }, 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" }, (() => {
1242
+ const bodyA = row.bodies?.find((body) => body.name === row.a);
1243
+ const bodyB = row.bodies?.find((body) => body.name === row.b);
1244
+ return [bodyA === void 0 ? null : (0, react.createElement)("button", {
1245
+ key: "a",
1246
+ type: "button",
1247
+ "data-role": "body-a",
1248
+ onClick: () => {
1249
+ store.openBody(bodyA.name, bodyA.side);
1250
+ }
1251
+ }, "A"), bodyB === void 0 ? null : (0, react.createElement)("button", {
1252
+ key: "b",
1253
+ type: "button",
1254
+ "data-role": "body-b",
1255
+ onClick: () => {
1256
+ store.openBody(bodyB.name, bodyB.side);
1257
+ }
1258
+ }, "B")];
1259
+ })(), actions)));
1260
+ }
1261
+ function bodyNode(overlay, t, onClose) {
1262
+ return (0, react.createElement)("section", {
1263
+ "data-role": "overlay",
1264
+ "data-overlay": "body",
1265
+ role: "dialog",
1266
+ "aria-modal": "true",
1267
+ "aria-label": t("overlay.body")
1268
+ }, (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", {
1269
+ type: "button",
1270
+ "data-role": "overlay-close",
1271
+ onClick: onClose
1272
+ }, 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);
1273
+ }
1274
+ function historyNode(overlay, t, onClose) {
1275
+ return (0, react.createElement)("section", {
1276
+ "data-role": "overlay",
1277
+ "data-overlay": "history",
1278
+ role: "dialog",
1279
+ "aria-modal": "true",
1280
+ "aria-label": t("history")
1281
+ }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, t("history")), (0, react.createElement)("button", {
1282
+ type: "button",
1283
+ "data-role": "overlay-close",
1284
+ onClick: onClose
1285
+ }, 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", {
1286
+ key: item.name + ":" + item.archivedAt,
1287
+ "data-role": "history-item"
1288
+ }, item.name + " · " + (() => {
1289
+ const kind = t("history.kind." + item.kind);
1290
+ const reason = reasonText(t, item.reasonCode, item.reason);
1291
+ return kind === reason ? kind : kind + " · " + reason;
1292
+ })() + " · v" + String(item.lastVersion)))) : null);
549
1293
  }
550
1294
  function MergePanel(face) {
551
- const t = face.t;
552
- const [view, setView] = (0, react.useState)(face.view);
553
- const [tab, setTab] = (0, react.useState)("pending");
554
- const [expiredOnly, setExpiredOnly] = (0, react.useState)(false);
555
- const [rows, setRows] = (0, react.useState)(face.rows);
556
- const [loading, setLoading] = (0, react.useState)(face.loadRows !== void 0 || face.loadView !== void 0);
557
- const [failed, setFailed] = (0, react.useState)(void 0);
558
- const [nextCursor, setNextCursor] = (0, react.useState)(void 0);
1295
+ const { store, t } = face;
1296
+ const snapshot = (0, react.useSyncExternalStore)(store.subscribe, store.read, store.read);
559
1297
  const [copied, setCopied] = (0, react.useState)(void 0);
560
- const [overlay, setOverlay] = (0, react.useState)(void 0);
561
- const request = (0, react.useRef)(0);
562
- const copyCommand = (command) => {
1298
+ const [orders, setOrders] = (0, react.useState)({});
1299
+ const [dragId, setDragId] = (0, react.useState)(void 0);
1300
+ const [menu, setMenu] = (0, react.useState)(void 0);
1301
+ const workspaceId = resolveWorkspaceId(face.workspaceId);
1302
+ const orderStore = (0, react.useMemo)(() => createRowOrderStore(), []);
1303
+ const copy = (command) => {
563
1304
  navigator.clipboard.writeText(command);
564
1305
  setCopied(command);
565
1306
  };
566
- const loadRows = (queue, cursor, expired = false) => {
567
- const load = face.loadRows;
568
- if (load === void 0) return;
569
- const token = request.current + 1;
570
- request.current = token;
571
- setLoading(true);
572
- load(queue, cursor, queue === "pending" && expired).then((outcome) => {
573
- if (token !== request.current) return;
574
- if (outcome.ok) {
575
- setRows((current) => [...cursor === void 0 ? [] : current, ...outcome.payload.items].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)));
576
- setNextCursor(outcome.payload.nextCursor);
577
- setFailed(void 0);
578
- } else setFailed(outcome.failure);
579
- }).catch(() => {
580
- setFailed({
581
- kind: "server",
582
- status: 0
583
- });
584
- }).finally(() => {
585
- setLoading(false);
586
- });
587
- };
588
- const loadView = () => {
589
- const load = face.loadView;
590
- if (load === void 0) return;
591
- load().then((outcome) => {
592
- if (!outcome.ok) {
593
- setFailed(outcome.failure);
594
- return;
595
- }
596
- setView(outcome.payload);
597
- }).catch(() => {
598
- setFailed({
599
- kind: "server",
600
- status: 0
601
- });
602
- });
1307
+ const doctorCommand = face.doctorCommand ?? DOCTOR_COMMAND;
1308
+ (0, react.useEffect)(() => {
1309
+ store.start();
1310
+ return () => {
1311
+ store.dispose();
1312
+ };
1313
+ }, [store]);
1314
+ (0, react.useEffect)(() => {
1315
+ const onFocus = () => {
1316
+ store.refresh();
1317
+ };
1318
+ const onVisibility = () => {
1319
+ if (document.visibilityState === "visible") store.refresh();
1320
+ };
1321
+ const onRefresh = () => {
1322
+ store.refresh();
1323
+ };
1324
+ window.addEventListener("focus", onFocus);
1325
+ document.addEventListener("visibilitychange", onVisibility);
1326
+ window.addEventListener("evolution-merge:refresh", onRefresh);
1327
+ return () => {
1328
+ window.removeEventListener("focus", onFocus);
1329
+ document.removeEventListener("visibilitychange", onVisibility);
1330
+ window.removeEventListener("evolution-merge:refresh", onRefresh);
1331
+ };
1332
+ }, [store]);
1333
+ const view = snapshot.view;
1334
+ const fallback = [...new Set([...view.commands, ...FALLBACK_COMMANDS])];
1335
+ const blocked = snapshot.viewState === "blocked" && snapshot.viewFailure !== void 0 ? snapshot.viewFailure : void 0;
1336
+ const viewFailure = snapshot.viewState === "error" ? snapshot.viewFailure : void 0;
1337
+ const queue = snapshot.current;
1338
+ 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";
1339
+ const orderKey = rowOrderKey(workspaceId, snapshot.queue);
1340
+ const visibleOrder = orders[orderKey] ?? orderStore.read(workspaceId, snapshot.queue);
1341
+ const visibleRows = orderRows(queue.rows, visibleOrder);
1342
+ const saveOrder = (ids) => {
1343
+ setOrders((previous) => ({
1344
+ ...previous,
1345
+ [orderKey]: ids
1346
+ }));
1347
+ orderStore.write(workspaceId, snapshot.queue, ids);
603
1348
  };
604
- const openBody = (id, side) => {
605
- const load = face.loadBody;
606
- if (load === void 0) return;
607
- load(id, side).then((outcome) => {
608
- if (outcome.ok) {
609
- setFailed(void 0);
610
- setOverlay({
611
- kind: "body",
612
- html: outcome.payload.html,
613
- label: id + ":" + side
614
- });
615
- } else setFailed(outcome.failure);
616
- }).catch(() => {
617
- setFailed({
618
- kind: "server",
619
- status: 0
1349
+ const sortHandlers = {
1350
+ start: (id) => {
1351
+ setDragId(id);
1352
+ },
1353
+ over: () => {},
1354
+ end: () => {
1355
+ setDragId(void 0);
1356
+ },
1357
+ drop: (targetId) => {
1358
+ const fromId = dragId;
1359
+ setDragId(void 0);
1360
+ if (fromId === void 0 || fromId === targetId) return;
1361
+ saveOrder(moveRow(visibleRows.map((row) => row.id), fromId, targetId));
1362
+ },
1363
+ menu: (id, x, y) => {
1364
+ setMenu({
1365
+ id,
1366
+ x,
1367
+ y
620
1368
  });
621
- });
1369
+ }
622
1370
  };
623
- const openHistory = () => {
624
- const load = face.loadHistory;
625
- if (load === void 0) return;
626
- load().then((outcome) => {
627
- if (outcome.ok) {
628
- setFailed(void 0);
629
- setOverlay({
630
- kind: "history",
631
- items: outcome.payload.items
632
- });
633
- } else setFailed(outcome.failure);
634
- }).catch(() => {
635
- setFailed({
636
- kind: "server",
637
- status: 0
638
- });
639
- });
1371
+ const pinMenu = (edge) => {
1372
+ if (menu === void 0) return;
1373
+ saveOrder(pinRow(visibleRows.map((row) => row.id), menu.id, edge));
1374
+ setMenu(void 0);
640
1375
  };
641
- (0, react.useEffect)(() => {
642
- loadView();
643
- loadRows("pending");
644
- }, [face]);
645
- (0, react.useEffect)(() => {
646
- if (tab !== "pending" && expiredOnly) setExpiredOnly(false);
647
- setNextCursor(void 0);
648
- loadRows(tab, void 0, tab === "pending" && expiredOnly);
649
- }, [tab, expiredOnly]);
650
- const counts = view.counts;
651
- const latest = view.latestRun;
652
- const runStrip = latest === void 0 ? null : (0, react.createElement)("div", { "data-role": "run-strip" }, (0, react.createElement)("span", { "data-role": "run-id" }, latest.id), (0, react.createElement)("span", { "data-role": "run-state" }, latest.state), latest.stage === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-stage" }, t("stage." + latest.stage)), latest.progress === void 0 ? null : (0, react.createElement)("span", { "data-role": "run-progress" }, String(latest.progress.done) + "/" + String(latest.progress.total)));
653
1376
  const tabs = (0, react.createElement)("div", {
654
1377
  "data-role": "tabs",
655
1378
  role: "tablist"
656
- }, QUEUES.map((queue) => (0, react.createElement)("button", {
657
- key: queue,
1379
+ }, QUEUES.map((item) => (0, react.createElement)("button", {
1380
+ key: item,
658
1381
  type: "button",
659
1382
  role: "tab",
660
- "aria-selected": tab === queue ? "true" : "false",
1383
+ "aria-selected": snapshot.queue === item ? "true" : "false",
661
1384
  "data-role": "tab",
662
- "data-active": tab === queue ? "true" : "false",
1385
+ "data-active": snapshot.queue === item ? "true" : "false",
663
1386
  onClick: () => {
664
- setTab(queue);
1387
+ setMenu(void 0);
1388
+ store.select(item);
665
1389
  }
666
- }, t("tab." + queue) + " " + String(counts[queue]))));
667
- const expiredToggle = tab === "pending" ? (0, react.createElement)("button", {
1390
+ }, t("tab." + item) + " " + String(view.counts[item]))));
1391
+ const expiredToggle = snapshot.queue === "pending" ? (0, react.createElement)("button", {
668
1392
  type: "button",
669
1393
  role: "switch",
670
- "aria-checked": expiredOnly ? "true" : "false",
1394
+ "aria-checked": snapshot.expiredOnly ? "true" : "false",
671
1395
  "data-role": "expired-toggle",
672
- "data-active": expiredOnly ? "true" : "false",
1396
+ "data-active": snapshot.expiredOnly ? "true" : "false",
673
1397
  onClick: () => {
674
- setExpiredOnly((value) => !value);
1398
+ store.toggleExpired();
675
1399
  }
676
1400
  }, t("tab.expired") + " " + String(view.expired)) : null;
677
- const list = rows.length === 0 ? (0, react.createElement)("p", { "data-role": "note" }, t("empty." + tab)) : (0, react.createElement)("div", { "data-role": "rows" }, rows.map((row) => {
678
- const facts = [
679
- row.target === void 0 ? void 0 : t("row.target") + " " + row.target + (row.targetActual === void 0 ? "" : " → " + row.targetActual) + (row.targetCreated === true ? " (" + t("row.targetCreated") + ")" : ""),
680
- row.verifySubState === void 0 ? void 0 : t("row.verify") + " " + t("verify." + row.verifySubState),
681
- row.stagingState === void 0 || row.stagingState === "none" ? void 0 : t("row.staging") + " " + t("staging." + row.stagingState),
682
- row.skipKind === void 0 ? void 0 : t("row.skipKind") + " " + t("skipKind." + row.skipKind),
683
- row.bodies === void 0 ? void 0 : t("row.bodies") + " " + row.bodies.map((body) => body.name + "(" + body.side + ")").join(", "),
684
- row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2),
685
- row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
686
- row.origin === void 0 ? void 0 : t("row.origin") + " " + t("origin." + row.origin),
687
- row.stagingExpiresAt === void 0 ? void 0 : t("row.expires") + " " + new Date(row.stagingExpiresAt).toLocaleString(),
688
- row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
689
- row.reason === void 0 ? void 0 : t("row.reason") + " " + row.reason,
690
- row.summary === void 0 ? void 0 : row.summary.filter(Boolean).join(" · ") || t("row.noSummary"),
691
- row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.message + (row.failure.retryable ? "" : " · " + t("row.noRetry"))
692
- ].filter((value) => value !== void 0).join(" · ");
693
- return (0, react.createElement)("div", {
694
- key: row.id,
695
- "data-role": "row"
696
- }, (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))), 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" }, face.loadBody === void 0 || row.bodies === void 0 ? null : (0, react.createElement)("button", {
697
- type: "button",
698
- "data-role": "body-a",
699
- onClick: () => {
700
- openBody(row.id, "a");
701
- }
702
- }, "A"), face.loadBody === void 0 || row.bodies === void 0 ? null : (0, react.createElement)("button", {
703
- type: "button",
704
- "data-role": "body-b",
705
- onClick: () => {
706
- openBody(row.id, "b");
707
- }
708
- }, "B"), (row.actions ?? []).map((action) => (0, react.createElement)("button", {
709
- key: action.id,
710
- type: "button",
711
- "data-role": "row-action",
712
- "data-action": action.id,
713
- title: action.disabledReason === void 0 ? action.command : t("disabled." + action.disabledReason),
714
- disabled: !action.enabled,
715
- onClick: () => {
716
- if (!action.enabled) return;
717
- if (action.confirm && globalThis.confirm !== void 0 && !globalThis.confirm(t("action.confirm") + " " + t("action." + action.id))) return;
718
- copyCommand(action.command);
719
- }
720
- }, t("action." + action.id))))));
721
- }));
1401
+ const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", { "data-role": "rows" }, visibleRows.map((row) => rowNode(row, store, t, copy, sortHandlers))) : null;
1402
+ 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;
1403
+ const activeFailure = viewFailure !== void 0 ? viewFailure : queue.status === "error" ? queue.failure : void 0;
1404
+ const overlay = snapshot.overlay;
1405
+ const menuNode = menu === void 0 ? null : (0, react.createElement)("div", {
1406
+ "data-role": "sort-menu",
1407
+ role: "menu",
1408
+ "aria-label": t("sort.menu"),
1409
+ style: {
1410
+ left: String(Math.min(menu.x, Math.max(0, (globalThis.innerWidth || 1440) - 180))) + "px",
1411
+ top: String(Math.min(menu.y, Math.max(0, (globalThis.innerHeight || 800) - 110))) + "px"
1412
+ }
1413
+ }, (0, react.createElement)("button", {
1414
+ type: "button",
1415
+ role: "menuitem",
1416
+ "data-role": "sort-pin-top",
1417
+ onClick: () => {
1418
+ pinMenu("top");
1419
+ }
1420
+ }, t("sort.pinTop")), (0, react.createElement)("button", {
1421
+ type: "button",
1422
+ role: "menuitem",
1423
+ "data-role": "sort-pin-bottom",
1424
+ onClick: () => {
1425
+ pinMenu("bottom");
1426
+ }
1427
+ }, t("sort.pinBottom")), (0, react.createElement)("button", {
1428
+ type: "button",
1429
+ role: "menuitem",
1430
+ "data-role": "sort-cancel",
1431
+ onClick: () => {
1432
+ setMenu(void 0);
1433
+ }
1434
+ }, t("sort.cancel")));
722
1435
  return (0, react.createElement)("section", {
723
1436
  className: CSS_TAG_ID,
724
- "data-state": loading ? "loading" : failed !== void 0 ? "failed" : rows.length === 0 ? "empty" : "ready"
725
- }, (0, react.createElement)("h2", { "data-role": "title" }, t("title")), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
1437
+ "data-state": uiState
1438
+ }, (0, react.createElement)("h2", { "data-role": "title" }, t("title")), (0, react.createElement)("p", { "data-role": "hint" }, t("hint")), blocked !== void 0 ? failureNode(t, blocked, fallback, copy) : (0, react.createElement)("div", { "data-role": "content" }, (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
726
1439
  type: "button",
727
1440
  "data-role": "start-scan",
728
1441
  onClick: () => {
729
- copyCommand(view.startCommands.scan);
1442
+ copy(view.startCommands.scan);
730
1443
  }
731
1444
  }, t("start.scan")), (0, react.createElement)("button", {
732
1445
  type: "button",
733
1446
  "data-role": "start-pair",
734
1447
  onClick: () => {
735
- copyCommand(view.startCommands.pair);
736
- }
737
- }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, view.approval === void 0 ? null : (0, react.createElement)("p", { "data-role": "approval" }, t(view.approval ? "approval.on" : "approval.off")), runStrip, tabs, expiredToggle, loading ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : null, failed === void 0 ? null : (0, react.createElement)("div", { "data-role": "failure" }, failure(t, failed), (0, react.createElement)("div", { "data-role": "fallback" }, view.commands.map((command) => (0, react.createElement)("button", {
738
- key: command,
739
- type: "button",
740
- "data-role": "fallback-command",
741
- onClick: () => {
742
- copyCommand(command);
1448
+ copy(view.startCommands.pair);
743
1449
  }
744
- }, command)))), copied === void 0 ? null : (0, react.createElement)("p", {
1450
+ }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(SettingsCard, {
1451
+ view,
1452
+ settings: face.settings,
1453
+ t
1454
+ }), 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", {
745
1455
  "data-role": "copied",
746
1456
  role: "status"
747
- }, t("copied") + " " + copied), list, nextCursor === void 0 ? null : (0, react.createElement)("button", {
1457
+ }, t("copied") + " " + copied), list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
748
1458
  type: "button",
749
1459
  "data-role": "load-more",
1460
+ disabled: queue.status === "loading",
750
1461
  onClick: () => {
751
- loadRows(tab, nextCursor, expiredOnly);
1462
+ store.loadMore();
752
1463
  }
753
- }, t("loadMore")), face.loadHistory === void 0 ? null : (0, react.createElement)("button", {
1464
+ }, t("loadMore")), (0, react.createElement)("button", {
754
1465
  type: "button",
755
1466
  "data-role": "history",
756
- onClick: openHistory
757
- }, t("history")), overlay === void 0 ? null : (0, react.createElement)("section", {
758
- "data-role": "overlay",
759
- role: "dialog",
760
- "aria-modal": "true",
761
- "aria-label": overlay.kind === "body" ? t("overlay.body") : t("history")
762
- }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, overlay.kind === "body" ? t("overlay.body") : t("history")), (0, react.createElement)("button", {
1467
+ onClick: () => {
1468
+ store.openHistory();
1469
+ }
1470
+ }, t("history")), (0, react.createElement)("button", {
763
1471
  type: "button",
764
- "data-role": "overlay-close",
1472
+ "data-role": "doctor",
765
1473
  onClick: () => {
766
- setOverlay(void 0);
1474
+ copy(doctorCommand);
767
1475
  }
768
- }, t("close")), overlay.kind === "body" ? (0, react.createElement)("div", {
769
- "data-role": "body",
770
- "data-label": overlay.label,
771
- dangerouslySetInnerHTML: { __html: overlay.html }
772
- }) : 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", { key: item.name }, item.name + " · " + t("history.kind." + item.kind) + " · " + item.reason + " · v" + String(item.lastVersion))))), (0, react.createElement)("button", {
1476
+ }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, () => {
1477
+ store.closeOverlay();
1478
+ }) : historyNode(overlay, t, () => {
1479
+ store.closeOverlay();
1480
+ }), (0, react.createElement)("button", {
773
1481
  type: "button",
774
1482
  "data-role": "refresh",
775
1483
  onClick: () => {
776
- loadView();
777
- loadRows(tab, void 0, tab === "pending" && expiredOnly);
1484
+ store.refresh();
778
1485
  }
779
- }, t("refresh")));
1486
+ }, t("refresh")), menuNode));
780
1487
  }
781
1488
  //#endregion
1489
+ //#region src/client/MergeStore.ts
1490
+ const emptyView = () => ({
1491
+ counts: {
1492
+ verify: 0,
1493
+ pending: 0,
1494
+ fuse: 0,
1495
+ skipped: 0
1496
+ },
1497
+ expired: 0,
1498
+ tabs: [
1499
+ {
1500
+ queue: "verify",
1501
+ count: 0
1502
+ },
1503
+ {
1504
+ queue: "pending",
1505
+ count: 0
1506
+ },
1507
+ {
1508
+ queue: "fuse",
1509
+ count: 0
1510
+ },
1511
+ {
1512
+ queue: "skipped",
1513
+ count: 0
1514
+ }
1515
+ ],
1516
+ commands: [],
1517
+ startCommands: {
1518
+ scan: "/evolution merge scan",
1519
+ pair: "/evolution merge pair <a> <b>"
1520
+ },
1521
+ notes: [],
1522
+ degraded: false,
1523
+ approval: { mergeApproval: false }
1524
+ });
1525
+ const keyOf = (queue, expiredOnly) => queue + ":" + (expiredOnly ? "1" : "0");
1526
+ const loadingQueue = () => ({
1527
+ status: "loading",
1528
+ rows: []
1529
+ });
1530
+ var MergeStore = class {
1531
+ source;
1532
+ listeners = /* @__PURE__ */ new Set();
1533
+ cache = /* @__PURE__ */ new Map();
1534
+ tokens = /* @__PURE__ */ new Map();
1535
+ viewToken = 0;
1536
+ overlayToken = 0;
1537
+ snapshot;
1538
+ started = false;
1539
+ disposed = false;
1540
+ constructor(source, initial = {}) {
1541
+ this.source = source;
1542
+ const queue = initial.queue ?? "pending";
1543
+ const expiredOnly = queue === "pending" ? initial.expiredOnly ?? false : false;
1544
+ const initialRows = initial.rows;
1545
+ const current = initialRows === void 0 ? loadingQueue() : {
1546
+ status: initialRows.length === 0 ? "empty" : "ready",
1547
+ rows: initialRows
1548
+ };
1549
+ if (initialRows !== void 0) this.cache.set(keyOf(queue, expiredOnly), current);
1550
+ this.snapshot = {
1551
+ view: initial.view ?? emptyView(),
1552
+ viewState: initial.view === void 0 ? "loading" : "ready",
1553
+ queue,
1554
+ expiredOnly,
1555
+ current
1556
+ };
1557
+ }
1558
+ read = () => this.snapshot;
1559
+ subscribe = (listener) => {
1560
+ this.listeners.add(listener);
1561
+ return () => {
1562
+ this.listeners.delete(listener);
1563
+ };
1564
+ };
1565
+ emit() {
1566
+ if (this.disposed) return;
1567
+ for (const listener of this.listeners) listener();
1568
+ }
1569
+ set(next) {
1570
+ this.snapshot = next;
1571
+ this.emit();
1572
+ }
1573
+ isCurrent(queue, expiredOnly) {
1574
+ return this.snapshot.queue === queue && this.snapshot.expiredOnly === expiredOnly;
1575
+ }
1576
+ start() {
1577
+ if (this.started || this.disposed) return;
1578
+ this.started = true;
1579
+ this.loadView();
1580
+ this.loadQueue(this.snapshot.queue, this.snapshot.expiredOnly);
1581
+ }
1582
+ async loadView(showLoading = true) {
1583
+ const token = ++this.viewToken;
1584
+ if (showLoading) this.set({
1585
+ ...this.snapshot,
1586
+ viewState: "loading",
1587
+ viewFailure: void 0
1588
+ });
1589
+ const outcome = await this.source.view();
1590
+ if (this.disposed || token !== this.viewToken) return;
1591
+ if (!outcome.ok) {
1592
+ if (outcome.failure.kind === "blocked") {
1593
+ this.set({
1594
+ ...this.snapshot,
1595
+ viewState: "blocked",
1596
+ viewFailure: outcome.failure,
1597
+ current: {
1598
+ status: "idle",
1599
+ rows: []
1600
+ }
1601
+ });
1602
+ for (const key of this.cache.keys()) this.cache.set(key, {
1603
+ status: "idle",
1604
+ rows: []
1605
+ });
1606
+ return;
1607
+ }
1608
+ this.set({
1609
+ ...this.snapshot,
1610
+ viewState: "error",
1611
+ viewFailure: outcome.failure
1612
+ });
1613
+ return;
1614
+ }
1615
+ this.set({
1616
+ ...this.snapshot,
1617
+ view: outcome.payload,
1618
+ viewState: "ready",
1619
+ viewFailure: void 0
1620
+ });
1621
+ }
1622
+ async loadQueue(queue, expiredOnly, cursor) {
1623
+ const key = keyOf(queue, expiredOnly);
1624
+ const token = (this.tokens.get(key) ?? 0) + 1;
1625
+ this.tokens.set(key, token);
1626
+ const previous = this.cache.get(key);
1627
+ const next = cursor === void 0 ? {
1628
+ status: "loading",
1629
+ rows: []
1630
+ } : {
1631
+ status: "loading",
1632
+ rows: previous?.rows ?? [],
1633
+ ...previous?.nextCursor === void 0 ? {} : { nextCursor: previous.nextCursor }
1634
+ };
1635
+ this.cache.set(key, next);
1636
+ if (this.isCurrent(queue, expiredOnly)) this.set({
1637
+ ...this.snapshot,
1638
+ current: next
1639
+ });
1640
+ const outcome = await this.source.rows(queue, cursor, queue === "pending" && expiredOnly);
1641
+ if (this.disposed || token !== this.tokens.get(key)) return;
1642
+ if (!outcome.ok) {
1643
+ if (outcome.failure.kind === "blocked") {
1644
+ this.set({
1645
+ ...this.snapshot,
1646
+ viewState: "blocked",
1647
+ viewFailure: outcome.failure,
1648
+ current: {
1649
+ status: "idle",
1650
+ rows: []
1651
+ }
1652
+ });
1653
+ return;
1654
+ }
1655
+ const failed = {
1656
+ status: "error",
1657
+ rows: [],
1658
+ failure: outcome.failure
1659
+ };
1660
+ this.cache.set(key, failed);
1661
+ if (this.isCurrent(queue, expiredOnly)) this.set({
1662
+ ...this.snapshot,
1663
+ current: failed
1664
+ });
1665
+ return;
1666
+ }
1667
+ const items = cursor === void 0 ? outcome.payload.items : [...previous?.rows ?? [], ...outcome.payload.items];
1668
+ const ready = {
1669
+ status: items.length === 0 ? "empty" : "ready",
1670
+ rows: items,
1671
+ ...outcome.payload.nextCursor === void 0 ? {} : { nextCursor: outcome.payload.nextCursor }
1672
+ };
1673
+ this.cache.set(key, ready);
1674
+ if (this.isCurrent(queue, expiredOnly)) this.set({
1675
+ ...this.snapshot,
1676
+ current: ready,
1677
+ expiredOnly,
1678
+ queue
1679
+ });
1680
+ }
1681
+ select(queue) {
1682
+ const expiredOnly = queue === "pending" ? this.snapshot.expiredOnly : false;
1683
+ if (queue === this.snapshot.queue && expiredOnly === this.snapshot.expiredOnly) return;
1684
+ const key = keyOf(queue, expiredOnly);
1685
+ const cached = this.cache.get(key);
1686
+ const current = cached ?? loadingQueue();
1687
+ this.set({
1688
+ ...this.snapshot,
1689
+ queue,
1690
+ expiredOnly,
1691
+ current,
1692
+ overlay: void 0
1693
+ });
1694
+ if (cached === void 0) this.loadQueue(queue, expiredOnly);
1695
+ }
1696
+ toggleExpired() {
1697
+ if (this.snapshot.queue !== "pending") return;
1698
+ const expiredOnly = !this.snapshot.expiredOnly;
1699
+ const key = keyOf("pending", expiredOnly);
1700
+ const cached = this.cache.get(key);
1701
+ const current = cached ?? loadingQueue();
1702
+ this.set({
1703
+ ...this.snapshot,
1704
+ expiredOnly,
1705
+ current,
1706
+ overlay: void 0
1707
+ });
1708
+ if (cached === void 0) this.loadQueue("pending", expiredOnly);
1709
+ }
1710
+ refresh() {
1711
+ if (this.disposed) return;
1712
+ this.loadView(true);
1713
+ this.loadQueue(this.snapshot.queue, this.snapshot.expiredOnly);
1714
+ }
1715
+ loadMore() {
1716
+ const current = this.snapshot.current;
1717
+ if (current.nextCursor === void 0 || current.status === "loading" || this.snapshot.viewState === "blocked") return;
1718
+ this.loadQueue(this.snapshot.queue, this.snapshot.expiredOnly, current.nextCursor);
1719
+ }
1720
+ async openBody(name, side) {
1721
+ const token = ++this.overlayToken;
1722
+ this.set({
1723
+ ...this.snapshot,
1724
+ overlay: {
1725
+ kind: "body",
1726
+ name,
1727
+ side,
1728
+ status: "loading"
1729
+ }
1730
+ });
1731
+ const outcome = await this.source.body(name, side);
1732
+ if (this.disposed || token !== this.overlayToken) return;
1733
+ if (!outcome.ok) {
1734
+ if (outcome.failure.kind === "blocked") {
1735
+ this.set({
1736
+ ...this.snapshot,
1737
+ viewState: "blocked",
1738
+ viewFailure: outcome.failure,
1739
+ overlay: void 0
1740
+ });
1741
+ return;
1742
+ }
1743
+ this.set({
1744
+ ...this.snapshot,
1745
+ overlay: {
1746
+ kind: "body",
1747
+ name,
1748
+ side,
1749
+ status: "error",
1750
+ failure: outcome.failure
1751
+ }
1752
+ });
1753
+ return;
1754
+ }
1755
+ this.set({
1756
+ ...this.snapshot,
1757
+ overlay: {
1758
+ kind: "body",
1759
+ name,
1760
+ side,
1761
+ status: "ready",
1762
+ body: outcome.payload
1763
+ }
1764
+ });
1765
+ }
1766
+ async openHistory() {
1767
+ const token = ++this.overlayToken;
1768
+ this.set({
1769
+ ...this.snapshot,
1770
+ overlay: {
1771
+ kind: "history",
1772
+ status: "loading"
1773
+ }
1774
+ });
1775
+ const outcome = await this.source.history();
1776
+ if (this.disposed || token !== this.overlayToken) return;
1777
+ if (!outcome.ok) {
1778
+ if (outcome.failure.kind === "blocked") {
1779
+ this.set({
1780
+ ...this.snapshot,
1781
+ viewState: "blocked",
1782
+ viewFailure: outcome.failure,
1783
+ overlay: void 0
1784
+ });
1785
+ return;
1786
+ }
1787
+ this.set({
1788
+ ...this.snapshot,
1789
+ overlay: {
1790
+ kind: "history",
1791
+ status: "error",
1792
+ failure: outcome.failure
1793
+ }
1794
+ });
1795
+ return;
1796
+ }
1797
+ this.set({
1798
+ ...this.snapshot,
1799
+ overlay: {
1800
+ kind: "history",
1801
+ status: "ready",
1802
+ items: outcome.payload.items
1803
+ }
1804
+ });
1805
+ }
1806
+ closeOverlay() {
1807
+ this.overlayToken += 1;
1808
+ this.set({
1809
+ ...this.snapshot,
1810
+ overlay: void 0
1811
+ });
1812
+ }
1813
+ dispose() {
1814
+ this.disposed = true;
1815
+ this.listeners.clear();
1816
+ }
1817
+ };
1818
+ //#endregion
782
1819
  //#region src/client/index.ts
783
1820
  const inject = ["slots", "locale"];
784
1821
  function PanelIcon(props = {}) {
@@ -795,51 +1832,19 @@ window.__ModuleLoader__.load({
795
1832
  strokeLinejoin: "round"
796
1833
  }, (0, react.createElement)("path", { d: "M3 4h10M3 8h6M3 12h8" }));
797
1834
  }
798
- function seedView() {
799
- return {
800
- counts: {
801
- verify: 0,
802
- pending: 0,
803
- fuse: 0,
804
- skipped: 0
805
- },
806
- expired: 0,
807
- tabs: [
808
- {
809
- queue: "verify",
810
- count: 0
811
- },
812
- {
813
- queue: "pending",
814
- count: 0
815
- },
816
- {
817
- queue: "fuse",
818
- count: 0
819
- },
820
- {
821
- queue: "skipped",
822
- count: 0
823
- }
824
- ],
825
- commands: [
826
- "/evolution merge scan",
827
- "/evolution merge pair <a> <b>",
828
- "/evolution merge candidates",
829
- "/evolution merge pending",
830
- "/evolution merge resume",
831
- "/evolution merge skipped",
832
- "/evolution merge status",
833
- "/evolution merge report",
834
- "/evolution merge doctor"
835
- ],
836
- startCommands: {
837
- scan: "/evolution merge scan",
838
- pair: "/evolution merge pair <a> <b>"
839
- },
840
- notes: [],
841
- degraded: false
842
- };
1835
+ function interpolate(text, values) {
1836
+ if (values === void 0) return text;
1837
+ return text.replace(/\{(\w+)\}/g, (_match, key) => String(values[key] ?? ""));
1838
+ }
1839
+ /** Best-effort panel workspace; the merge panel itself is not workspace-keyed. */
1840
+ function panelWorkspaceId(ctx) {
1841
+ const get = ctx.get;
1842
+ if (typeof get !== "function") return void 0;
1843
+ for (const name of ["workspace", "uiWorkspace"]) try {
1844
+ const seat = get.call(ctx, name);
1845
+ const id = seat?.workspaceId ?? seat?.currentWorkspaceId;
1846
+ if (typeof id === "string" && id.trim() !== "") return id;
1847
+ } catch {}
843
1848
  }
844
1849
  function apply(ctx) {
845
1850
  const seam = ctx;
@@ -867,14 +1872,15 @@ window.__ModuleLoader__.load({
867
1872
  tag.remove();
868
1873
  };
869
1874
  }, "evolution-merge-ui: styles");
870
- const t = (key) => {
1875
+ const t = (key, values) => {
871
1876
  const fallback = zh[key] ?? key;
872
- if (!registered) return fallback;
873
- try {
874
- return seam.locale.bind(NS)(key);
1877
+ let text = fallback;
1878
+ if (registered) try {
1879
+ text = seam.locale.bind(NS)(key);
875
1880
  } catch {
876
- return fallback;
1881
+ text = fallback;
877
1882
  }
1883
+ return interpolate(text, values);
878
1884
  };
879
1885
  const label = () => t("entry.label");
880
1886
  seam.slots.inject("sidebar.panellist", function* () {
@@ -887,15 +1893,31 @@ window.__ModuleLoader__.load({
887
1893
  yield seam.slots.register({
888
1894
  name: "main",
889
1895
  key: PANEL_ID,
890
- inject: () => ({
891
- view: seedView(),
892
- rows: [],
893
- loadView: view,
894
- loadRows: rows,
895
- loadBody: body,
896
- loadHistory: history,
897
- t
898
- })
1896
+ inject: () => {
1897
+ const source = {
1898
+ view,
1899
+ rows,
1900
+ body,
1901
+ history
1902
+ };
1903
+ let seat;
1904
+ try {
1905
+ seat = ctx.get?.("configForms");
1906
+ } catch {
1907
+ seat = void 0;
1908
+ }
1909
+ const workspaceId = panelWorkspaceId(ctx);
1910
+ return {
1911
+ store: new MergeStore(source),
1912
+ t,
1913
+ doctorCommand: "/evolution doctor",
1914
+ settings: { ...seat === void 0 ? {} : {
1915
+ merge: seat.get("evolution-merge"),
1916
+ platform: seat.get("evolution-approval")
1917
+ } },
1918
+ ...workspaceId === void 0 ? {} : { workspaceId }
1919
+ };
1920
+ }
899
1921
  }, MergePanel);
900
1922
  });
901
1923
  }