@lmzhen/dsh-evolution-merge-ui 0.35.0 → 0.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -291,6 +291,125 @@ window.__ModuleLoader__.load({
291
291
  return options.seconds === false ? head : head + ":" + pad(date.getSeconds());
292
292
  }
293
293
  //#endregion
294
+ //#region ../evolution-client-kit/src/notice.ts
295
+ /** One inline state notice: semantic tone plus text, no surface-specific styling. */
296
+ //#endregion
297
+ //#region ../evolution-client-kit/src/presentation/decision-frame.ts
298
+ /** One decision card: title, facts, at most one primary, secondary actions and details. */
299
+ function actionButton(action, role) {
300
+ return (0, react.createElement)("button", {
301
+ key: action.id,
302
+ type: "button",
303
+ "data-role": role === "primary" ? "decision-primary" : "decision-secondary",
304
+ "data-action": action.id,
305
+ "aria-busy": action.busy === true ? "true" : void 0,
306
+ ...action.attributes ?? {},
307
+ disabled: action.disabled === true || action.busy === true,
308
+ title: action.title ?? action.label,
309
+ onClick: () => {
310
+ if (action.disabled !== true && action.busy !== true) action.onSelect();
311
+ }
312
+ }, action.label);
313
+ }
314
+ /**
315
+ * Render the one-card decision surface. The model has a singular primary on purpose.
316
+ * @param props - decision model and optional surface class.
317
+ * @returns the decision frame.
318
+ */
319
+ function DecisionFrame({ decision, className }) {
320
+ const secondary = decision.secondary ?? [];
321
+ return (0, react.createElement)("section", {
322
+ "data-role": "decision-frame",
323
+ ...className === void 0 ? {} : { className }
324
+ }, (0, react.createElement)("h2", { "data-role": "decision-title" }, decision.title), decision.summary === void 0 || decision.summary === "" ? null : (0, react.createElement)("p", { "data-role": "decision-summary" }, decision.summary), decision.facts.length === 0 ? null : (0, react.createElement)("dl", { "data-role": "decision-facts" }, decision.facts.map((fact) => (0, react.createElement)("div", {
325
+ key: fact.label,
326
+ "data-role": "decision-fact"
327
+ }, (0, react.createElement)("dt", null, fact.label), (0, react.createElement)("dd", null, fact.value)))), decision.details === void 0 ? null : (0, react.createElement)("div", { "data-role": "decision-details" }, decision.details), decision.primary === void 0 && secondary.length === 0 ? null : (0, react.createElement)("div", { "data-role": "decision-actions" }, decision.primary === void 0 ? null : actionButton(decision.primary, "primary"), secondary.map((action) => actionButton(action, "secondary"))));
328
+ }
329
+ //#endregion
330
+ //#region ../evolution-client-kit/src/presentation/task-banner.ts
331
+ /** A read-only task hint; it deliberately has no primary action. */
332
+ /**
333
+ * Render a task count plus one sentence. The absence of an action prop is the contract.
334
+ * @param props - count, sentence and optional surface class.
335
+ * @returns the banner.
336
+ */
337
+ function TaskBanner({ count, text, className }) {
338
+ return (0, react.createElement)("div", {
339
+ "data-role": "task-banner",
340
+ "data-count": String(count),
341
+ role: "status",
342
+ ...className === void 0 ? {} : { className }
343
+ }, (0, react.createElement)("strong", { "data-role": "task-banner-count" }, String(count)), (0, react.createElement)("span", { "data-role": "task-banner-text" }, text));
344
+ }
345
+ //#endregion
346
+ //#region ../evolution-client-kit/src/presentation/chips.ts
347
+ /** Read-only status chips and toggleable filter chips. */
348
+ //#endregion
349
+ //#region ../evolution-client-kit/src/presentation/empty-state.ts
350
+ /** A compact completed/empty state with at most one primary next step. */
351
+ //#endregion
352
+ //#region ../evolution-client-kit/src/presentation/disclosure-group.ts
353
+ /** A native details group for normal/secondary facts. */
354
+ //#endregion
355
+ //#region ../evolution-client-kit/src/presentation/time-text.ts
356
+ /** The one local-time presentation used by reading surfaces. */
357
+ //#endregion
358
+ //#region ../evolution-client-kit/src/presentation/action-menu.ts
359
+ /**
360
+ * The one seam onto the platform's menu material, plus a small fallback for deployments
361
+ * without the primitive row. No page owns a second menu implementation.
362
+ */
363
+ const PLATFORM_UI_MODULE$1 = "@deepseek-ai/dsh-client-ui-primitives";
364
+ /**
365
+ * Render the platform menu directly. Kept exported for callers that already own the primitive row.
366
+ * @param primitives - platform MenuSurface/MenuItemButton pair.
367
+ * @param actions - rows in display order.
368
+ * @param label - accessible menu name.
369
+ * @param style - fixed position computed from the pointer.
370
+ * @param className - optional surface class.
371
+ * @returns the menu element.
372
+ */
373
+ function renderPlatformMenu(primitives, actions, label, style, className) {
374
+ return (0, react.createElement)(primitives.MenuSurface, {
375
+ "data-role": "action-menu",
376
+ "data-platform": "true",
377
+ role: "menu",
378
+ "aria-label": label,
379
+ ...className === void 0 ? {} : { className },
380
+ style
381
+ }, actions.map((action) => (0, react.createElement)(primitives.MenuItemButton, {
382
+ key: action.id,
383
+ children: action.label,
384
+ ...action.separatorBefore === true ? { separatorBefore: true } : {},
385
+ ...action.disabled === true ? { disabled: true } : {},
386
+ ...action.danger === true ? { danger: true } : {},
387
+ onSelect: action.onSelect
388
+ })));
389
+ }
390
+ /**
391
+ * Accept only the two component functions; a namespace row without them is not a menu.
392
+ * @param row - the module row's exports.
393
+ * @returns the two components, or undefined.
394
+ */
395
+ function menuPrimitivesFrom(row) {
396
+ const surface = row.MenuSurface;
397
+ const item = row.MenuItemButton;
398
+ if (typeof surface !== "function" || typeof item !== "function") return void 0;
399
+ return {
400
+ MenuSurface: surface,
401
+ MenuItemButton: item
402
+ };
403
+ }
404
+ /** The module-table row, when this deployment carries it. */
405
+ const PLATFORM_MENU = (() => {
406
+ try {
407
+ return menuPrimitivesFrom(require(PLATFORM_UI_MODULE$1));
408
+ } catch {
409
+ return;
410
+ }
411
+ })();
412
+ //#endregion
294
413
  //#region src/client/api.ts
295
414
  /** Read-only merge wire client. Classifies every failure and never writes. */
296
415
  const HOST_ROUTES = ROUTES.merge;
@@ -660,7 +779,8 @@ window.__ModuleLoader__.load({
660
779
  const NS = "evolution-merge";
661
780
  const zh = {
662
781
  "entry.label": "技能合并",
663
- "title": "技能合并",
782
+ "title": "合并建议",
783
+ "task.pending": "条合并建议待处理",
664
784
  "start.scan": "复制扫描命令",
665
785
  "start.pair": "复制配对命令模板",
666
786
  "degraded": "读模型降级:",
@@ -670,7 +790,7 @@ window.__ModuleLoader__.load({
670
790
  "row.noSummary": "无摘要",
671
791
  "row.summaryBoth": "双方摘要",
672
792
  "overlay.body": "正文",
673
- "hint": "手动触发;后台逐对判定。这里只读,不写技能。",
793
+ "hint": "先处理需要判断的合并,其他信息按需展开。",
674
794
  "history": "历史",
675
795
  "close": "关闭",
676
796
  "tab.verify": "待判断",
@@ -713,6 +833,8 @@ window.__ModuleLoader__.load({
713
833
  "row.noRetry": "不可重试",
714
834
  "row.copy": "复制命令",
715
835
  "row.more": "更多命令",
836
+ "row.viewBoth": "查看双方",
837
+ "overlay.pair": "查看双方",
716
838
  "tab.expired": "已过期",
717
839
  "row.targetCreated": "新建",
718
840
  "row.verify": "判定状态",
@@ -869,7 +991,8 @@ window.__ModuleLoader__.load({
869
991
  });
870
992
  const en = {
871
993
  "entry.label": "Skill merge",
872
- "title": "Skill merge",
994
+ "title": "Merge suggestions",
995
+ "task.pending": "merge suggestions need attention",
873
996
  "start.scan": "Copy scan command",
874
997
  "start.pair": "Copy pair command template",
875
998
  "degraded": "Degraded read model:",
@@ -879,7 +1002,7 @@ window.__ModuleLoader__.load({
879
1002
  "row.noSummary": "No summary",
880
1003
  "row.summaryBoth": "Shared summary",
881
1004
  "overlay.body": "Body",
882
- "hint": "Manual only; pairs are judged in the background. Read-only here.",
1005
+ "hint": "Resolve the merges that need a decision first; everything else stays folded.",
883
1006
  "history": "History",
884
1007
  "close": "Close",
885
1008
  "tab.verify": "To judge",
@@ -922,6 +1045,8 @@ window.__ModuleLoader__.load({
922
1045
  "row.noRetry": "not retryable",
923
1046
  "row.copy": "Copy command",
924
1047
  "row.more": "More commands",
1048
+ "row.viewBoth": "View both",
1049
+ "overlay.pair": "View both",
925
1050
  "tab.expired": "Expired",
926
1051
  "row.targetCreated": "created",
927
1052
  "row.verify": "Verify state",
@@ -1124,6 +1249,30 @@ window.__ModuleLoader__.load({
1124
1249
  ".evolution-merge-panel [data-role=\"title\"] { margin:0; font-size:var(--evo-type-lg); font-weight:600; }",
1125
1250
  ".evolution-merge-panel [data-role=\"start\"] { display:flex; gap:var(--evo-space-6); flex-wrap:wrap; }",
1126
1251
  ".evolution-merge-panel [data-role=\"degraded\"] { margin:0; color:var(--dsw-alias-state-warn-primary); font-size:var(--evo-type-xs); }",
1252
+ ".evolution-merge-panel [data-role=\"task-banner\"] { display:flex; align-items:baseline; gap:var(--evo-space-6); padding:var(--evo-space-10) var(--evo-space-12); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); background:var(--dsw-alias-bg-layer-1); color:var(--dsw-alias-label-primary); }",
1253
+ ".evolution-merge-panel [data-role=\"task-banner-count\"] { font-size:var(--evo-type-lg); font-variant-numeric:tabular-nums; }",
1254
+ ".evolution-merge-panel [data-role=\"task-banner-text\"] { color:var(--dsw-alias-label-secondary); font-size:var(--evo-type-sm); }",
1255
+ ".evolution-merge-panel .merge-decision { 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); }",
1256
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-title\"] { margin:0; font-size:var(--evo-type-sm); font-weight:600; }",
1257
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-summary\"] { margin:0; color:var(--dsw-alias-label-secondary); font-size:var(--evo-type-xs); }",
1258
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-facts\"] { display:grid; grid-template-columns:max-content minmax(0,1fr); gap:var(--evo-space-4) var(--evo-space-12); margin:0; font-size:var(--evo-type-xs); }",
1259
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-fact\"] { display:contents; }",
1260
+ ".evolution-merge-panel .merge-decision dt { color:var(--dsw-alias-label-tertiary); }",
1261
+ ".evolution-merge-panel .merge-decision dd { margin:0; min-width:0; overflow-wrap:anywhere; }",
1262
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-actions\"] { display:flex; flex-wrap:wrap; align-items:center; gap:var(--evo-space-8); padding-top:var(--evo-space-8); }",
1263
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"], .evolution-merge-panel .merge-decision [data-role=\"decision-secondary\"], .evolution-merge-panel .merge-decision [data-role=\"view-both\"] { padding:var(--evo-space-6) 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); color:var(--dsw-alias-label-primary); font-size:var(--evo-type-sm); cursor:pointer; }",
1264
+ ".evolution-merge-panel [data-role=\"pair-grid\"] { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--evo-space-12); }",
1265
+ ".evolution-merge-panel [data-role=\"body-a\"], .evolution-merge-panel [data-role=\"body-b\"] { min-width:0; }",
1266
+ ".evolution-merge-panel [data-role=\"body-a\"] pre, .evolution-merge-panel [data-role=\"body-b\"] pre { max-height:min(52vh,560px); overflow:auto; white-space:pre-wrap; }",
1267
+ "@media (max-width:900px){.evolution-merge-panel [data-role=\"pair-grid\"]{grid-template-columns:minmax(0,1fr)}}",
1268
+ ".evo-merge-modal [data-role=\"pair-grid\"]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--evo-space-12)}",
1269
+ ".evo-merge-modal [data-role=\"body-a\"],.evo-merge-modal [data-role=\"body-b\"]{min-width:0}",
1270
+ ".evo-merge-modal [data-role=\"body-a\"] pre,.evo-merge-modal [data-role=\"body-b\"] pre{max-height:min(52vh,560px);overflow:auto;white-space:pre-wrap}",
1271
+ "@media (max-width:900px){.evo-merge-modal [data-role=\"pair-grid\"]{grid-template-columns:minmax(0,1fr)}}",
1272
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"] { background:var(--dsw-alias-label-primary); border-color:transparent; color:var(--dsw-alias-label-primary-foreground); }",
1273
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-primary\"]:disabled, .evolution-merge-panel .merge-decision [data-role=\"decision-secondary\"]:disabled, .evolution-merge-panel .merge-decision [data-role=\"view-both\"]:disabled { color:var(--dsw-alias-label-dimmed); cursor:default; }",
1274
+ ".evolution-merge-panel .merge-decision [data-role=\"decision-details\"] { display:flex; flex-direction:column; gap:var(--evo-space-4); }",
1275
+ ".evolution-merge-panel [data-role=\"row-actions\"] { display:flex; align-items:center; justify-content:flex-end; gap:var(--evo-space-8); }",
1127
1276
  ".evolution-merge-panel [data-role=\"expired-toggle\"] { display:inline-flex; align-items:center; gap:var(--evo-space-4); border:var(--evo-hairline-width) solid var(--dsw-alias-border-secondary); border-radius:var(--evo-radius-control); background:transparent; color:var(--dsw-alias-label-secondary); font:inherit; font-size:var(--evo-type-xs); padding:var(--evo-space-4) var(--evo-space-8); cursor:pointer; }",
1128
1277
  ".evolution-merge-panel [data-role=\"expired-toggle\"][data-active=\"true\"] { color:var(--dsw-alias-state-warn-primary); border-color:var(--dsw-alias-state-warn-primary); background:transparent; }",
1129
1278
  ".evolution-merge-panel [data-role=\"failure\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
@@ -1140,7 +1289,7 @@ window.__ModuleLoader__.load({
1140
1289
  ".evolution-merge-panel [data-role=\"overlay-head\"] { display:flex; align-items:center; justify-content:space-between; gap:var(--evo-space-8); }",
1141
1290
  ".evolution-merge-panel [data-role=\"overlay-title\"] { margin:0; font-size:var(--evo-type-lg); }",
1142
1291
  ".evolution-merge-panel [data-role=\"rows\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); }",
1143
- ".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; }",
1292
+ ".evolution-merge-panel [data-role=\"row\"] { display:flex; flex-direction:column; gap:var(--evo-space-6); padding:var(--evo-space-8); border:var(--evo-hairline-width) solid var(--dsw-alias-border-l2); border-radius:var(--evo-radius-control); cursor:grab; }",
1144
1293
  ".evolution-merge-panel [data-role=\"row\"]:active { cursor:grabbing; }",
1145
1294
  ".evolution-merge-panel [data-role=\"row-label\"] { font-weight:600; }",
1146
1295
  ".evolution-merge-panel [data-role=\"row-meta\"] { color:var(--dsw-alias-label-tertiary); font-size:var(--evo-type-xs); }",
@@ -1180,7 +1329,7 @@ window.__ModuleLoader__.load({
1180
1329
  ].join("\n");
1181
1330
  //#endregion
1182
1331
  //#region src/client/platform-modal.ts
1183
- const PLATFORM_UI_MODULE$1 = "@deepseek-ai/dsh-client-ui-primitives";
1332
+ const PLATFORM_UI_MODULE = "@deepseek-ai/dsh-client-ui-primitives";
1184
1333
  let cached;
1185
1334
  /** The platform Modal, or undefined in a non-browser/SSR render or a deployment without the row. */
1186
1335
  function platformModal() {
@@ -1190,7 +1339,7 @@ window.__ModuleLoader__.load({
1190
1339
  return;
1191
1340
  }
1192
1341
  try {
1193
- const row = require(PLATFORM_UI_MODULE$1);
1342
+ const row = require(PLATFORM_UI_MODULE);
1194
1343
  cached = typeof row.Modal === "function" ? row.Modal : null;
1195
1344
  } catch {
1196
1345
  cached = null;
@@ -1198,61 +1347,6 @@ window.__ModuleLoader__.load({
1198
1347
  return cached ?? void 0;
1199
1348
  }
1200
1349
  //#endregion
1201
- //#region src/client/platform-menu.ts
1202
- /**
1203
- * The one seam onto the platform's menu material and rows.
1204
- *
1205
- * The family bundle keeps `@deepseek-ai/*` external, so the platform components are reached through
1206
- * the loader-provided `require` (the same door the Markdown seam uses). A deployment without the
1207
- * row gets the panel's own menu fallback; the platform path is not a hard dependency.
1208
- * @module dsh-evolution-merge-ui/client
1209
- */
1210
- const PLATFORM_UI_MODULE = "@deepseek-ai/dsh-client-ui-primitives";
1211
- /**
1212
- * Render the coordinate menu through the platform primitives.
1213
- * @param primitives - the platform MenuSurface/MenuItemButton pair.
1214
- * @param actions - rows in display order.
1215
- * @param label - accessible menu name.
1216
- * @param style - fixed position computed from the pointer.
1217
- * @returns the menu element.
1218
- */
1219
- function renderPlatformMenu(primitives, actions, label, style) {
1220
- return (0, react.createElement)(primitives.MenuSurface, {
1221
- "data-role": "sort-menu",
1222
- "data-platform": "true",
1223
- role: "menu",
1224
- "aria-label": label,
1225
- style
1226
- }, actions.map((action) => (0, react.createElement)(primitives.MenuItemButton, {
1227
- key: action.id,
1228
- children: action.label,
1229
- ...action.separatorBefore === true ? { separatorBefore: true } : {},
1230
- onSelect: action.onSelect
1231
- })));
1232
- }
1233
- /**
1234
- * Accept only the two component functions; a namespace row without them is not a menu.
1235
- * @param row - the module row's exports.
1236
- * @returns the two components, or undefined.
1237
- */
1238
- function menuPrimitivesFrom(row) {
1239
- const surface = row.MenuSurface;
1240
- const item = row.MenuItemButton;
1241
- if (typeof surface !== "function" || typeof item !== "function") return void 0;
1242
- return {
1243
- MenuSurface: surface,
1244
- MenuItemButton: item
1245
- };
1246
- }
1247
- /** The module-table row, when this deployment carries it. */
1248
- const PLATFORM_MENU = (() => {
1249
- try {
1250
- return menuPrimitivesFrom(require(PLATFORM_UI_MODULE));
1251
- } catch {
1252
- return;
1253
- }
1254
- })();
1255
- //#endregion
1256
1350
  //#region src/client/sorting.ts
1257
1351
  /** Workspace scope for one tab. A missing panel workspace is the local scope. */
1258
1352
  function resolveWorkspaceId(workspaceId) {
@@ -1443,7 +1537,7 @@ window.__ModuleLoader__.load({
1443
1537
  }
1444
1538
  //#endregion
1445
1539
  //#region src/client/MergeRow.ts
1446
- /** One merge row: summary, stage, facts, primary decisions and the copy drawer. */
1540
+ /** One merge row: summary, stage, facts, one primary decision and the copy/sort drawer. */
1447
1541
  function reasonText$1(t, reasonCode, fallback) {
1448
1542
  return reasonCode === void 0 ? fallback : t("history.reason." + reasonCode);
1449
1543
  }
@@ -1469,18 +1563,13 @@ window.__ModuleLoader__.load({
1469
1563
  }, t("stage.state." + stage.state) + expiry);
1470
1564
  }
1471
1565
  /**
1472
- * Render one row. Primary decisions stay visible; copy commands live in one details drawer.
1566
+ * Render one row as a decision card. The approve action is the only primary; A/B, reject and
1567
+ * auxiliary sort/copy commands cannot borrow that weight.
1473
1568
  * @param props - row, translator, copier, sort hooks, body opener and decision callback.
1474
- * @returns the row element.
1569
+ * @returns the row with drag/keyboard wiring and a separate more drawer.
1475
1570
  */
1476
1571
  function MergeRow(props) {
1477
- const { row, t, copy, sort, openBody, onDecide, busy } = props;
1478
- const first = [
1479
- row.target === void 0 ? void 0 : t("row.target") + " " + row.target,
1480
- row.failure === void 0 ? void 0 : t("row.failure") + " " + row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry")),
1481
- row.expired === void 0 ? void 0 : t("row.expired") + " " + t("expired." + row.expired),
1482
- row.reasonCode === void 0 && row.reason === void 0 ? void 0 : t("row.reason") + " " + reasonText$1(t, row.reasonCode, row.reason ?? "")
1483
- ].filter((value) => value !== void 0).join(" · ");
1572
+ const { row, t, copy, sort, openBoth, onDecide, busy } = props;
1484
1573
  const details = [
1485
1574
  t("row.id") + " " + row.id,
1486
1575
  row.targetActual === void 0 ? void 0 : t("row.actualTarget") + " " + row.targetActual,
@@ -1489,23 +1578,83 @@ window.__ModuleLoader__.load({
1489
1578
  row.attempts === void 0 ? void 0 : t("row.attempts") + " " + String(row.attempts),
1490
1579
  row.confidence === void 0 ? void 0 : t("row.confidence") + " " + row.confidence.toFixed(2)
1491
1580
  ].filter((value) => value !== void 0).join(" · ");
1492
- const actions = row.actions.filter((action) => action.id !== "copy" && action.id !== "discard").map((action) => {
1493
- const decision = (action.id === "approve" || action.id === "reject") && onDecide !== void 0;
1494
- return (0, react.createElement)("button", {
1495
- key: action.id,
1496
- type: "button",
1497
- "data-role": decision ? "row-decision" : "row-action",
1498
- "data-action": action.id,
1499
- ...action.disabledReason === void 0 ? {} : { "data-disabled-reason": action.disabledReason },
1500
- title: action.command,
1581
+ const approve = row.actions.find((action) => action.id === "approve");
1582
+ const reject = row.actions.find((action) => action.id === "reject");
1583
+ const bodyA = row.bodies?.find((body) => body.name === row.a);
1584
+ const bodyB = row.bodies?.find((body) => body.name === row.b);
1585
+ const primary = approve === void 0 ? void 0 : {
1586
+ id: "approve",
1587
+ label: t("decision.approve"),
1588
+ disabled: !approve.enabled || busy || onDecide === void 0,
1589
+ ...approve.disabledReason === void 0 ? {} : { attributes: { "data-disabled-reason": approve.disabledReason } },
1590
+ onSelect: () => {
1591
+ if (approve.enabled && !busy && onDecide !== void 0) onDecide(row.id, "approve");
1592
+ }
1593
+ };
1594
+ const secondary = [];
1595
+ if (approve !== void 0 && onDecide === void 0) secondary.push({
1596
+ id: "approve",
1597
+ label: t("action.approve"),
1598
+ disabled: !approve.enabled || busy,
1599
+ attributes: { "data-role": "row-action" },
1600
+ onSelect: () => {
1601
+ if (approve.enabled && !busy) copy(approve.command);
1602
+ }
1603
+ });
1604
+ if (reject !== void 0) secondary.push({
1605
+ id: "reject",
1606
+ label: t("decision.reject"),
1607
+ disabled: !reject.enabled || busy || onDecide === void 0,
1608
+ ...reject.disabledReason === void 0 ? {} : { attributes: { "data-disabled-reason": reject.disabledReason } },
1609
+ onSelect: () => {
1610
+ if (reject.enabled && !busy && onDecide !== void 0) onDecide(row.id, "reject");
1611
+ }
1612
+ });
1613
+ if (bodyA !== void 0 && bodyB !== void 0) secondary.push({
1614
+ id: "view-both",
1615
+ label: t("row.viewBoth"),
1616
+ attributes: { "data-role": "view-both" },
1617
+ onSelect: () => {
1618
+ openBoth({
1619
+ name: bodyA.name,
1620
+ side: bodyA.side
1621
+ }, {
1622
+ name: bodyB.name,
1623
+ side: bodyB.side
1624
+ }, bodyA.rowId);
1625
+ }
1626
+ });
1627
+ for (const action of row.actions) {
1628
+ if (action.id === "approve" || action.id === "reject" || action.id === "copy" || action.id === "discard") continue;
1629
+ secondary.push({
1630
+ id: action.id,
1631
+ label: t("action." + action.id),
1501
1632
  disabled: !action.enabled || busy,
1502
- onClick: () => {
1503
- if (!action.enabled || busy) return;
1504
- if (decision) onDecide(row.id, action.id);
1505
- else copy(action.command);
1633
+ attributes: { "data-role": "row-action" },
1634
+ onSelect: () => {
1635
+ if (action.enabled && !busy) copy(action.command);
1506
1636
  }
1507
- }, decision ? t("decision." + action.id) : t("action." + action.id));
1508
- });
1637
+ });
1638
+ }
1639
+ const facts = [
1640
+ ...row.target === void 0 ? [] : [{
1641
+ label: t("row.target"),
1642
+ value: row.target
1643
+ }],
1644
+ ...row.failure === void 0 ? [] : [{
1645
+ label: t("row.failure"),
1646
+ value: row.failure.code + (row.failure.retryable ? "" : " · " + t("row.noRetry"))
1647
+ }],
1648
+ ...row.expired === void 0 ? [] : [{
1649
+ label: t("row.expired"),
1650
+ value: t("expired." + row.expired)
1651
+ }],
1652
+ ...row.reasonCode === void 0 && row.reason === void 0 ? [] : [{
1653
+ label: t("row.reason"),
1654
+ value: reasonText$1(t, row.reasonCode, row.reason ?? "")
1655
+ }]
1656
+ ];
1657
+ const decisionDetails = (0, react.createElement)("div", null, stageNode(row, t), summaryNode(row, t), bodyA === void 0 || bodyB === void 0 ? null : (0, react.createElement)("div", { "data-role": "body-names" }, "A · " + bodyA.name + " · B · " + bodyB.name), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details)));
1509
1658
  return (0, react.createElement)("div", {
1510
1659
  key: row.id,
1511
1660
  "data-role": "row",
@@ -1545,7 +1694,16 @@ window.__ModuleLoader__.load({
1545
1694
  event.preventDefault();
1546
1695
  sort.menu(row.id, event.clientX, event.clientY);
1547
1696
  }
1548
- }, (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-label" }, row.label), (0, react.createElement)("div", { "data-role": "row-meta" }, t("state." + row.state) + (row.stage === null ? "" : " · " + t("stage." + row.stage))), stageNode(row, t), summaryNode(row, t), first === "" ? null : (0, react.createElement)("div", { "data-role": "row-facts" }, first), (0, react.createElement)("details", { "data-role": "row-details" }, (0, react.createElement)("summary", null, t("row.details")), (0, react.createElement)("p", null, details))), (0, react.createElement)("div", null, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("div", { "data-role": "row-actions" }, (0, react.createElement)("button", {
1697
+ }, (0, react.createElement)(DecisionFrame, {
1698
+ className: "merge-decision",
1699
+ decision: {
1700
+ title: row.label,
1701
+ facts,
1702
+ ...primary === void 0 ? {} : { primary },
1703
+ ...secondary.length === 0 ? {} : { secondary },
1704
+ details: decisionDetails
1705
+ }
1706
+ }), (0, react.createElement)("div", { "data-role": "row-actions" }, (0, react.createElement)("div", { "data-role": "row-meta" }, new Date(row.updatedAt).toLocaleString()), (0, react.createElement)("details", { "data-role": "row-more" }, (0, react.createElement)("summary", null, t("row.more")), (0, react.createElement)("button", {
1549
1707
  type: "button",
1550
1708
  "data-role": "row-sort",
1551
1709
  "aria-haspopup": "menu",
@@ -1554,29 +1712,7 @@ window.__ModuleLoader__.load({
1554
1712
  const rect = event.currentTarget.getBoundingClientRect();
1555
1713
  sort.menu(row.id, rect.left, rect.bottom);
1556
1714
  }
1557
- }, t("sort.menu")), (() => {
1558
- const bodyA = row.bodies?.find((body) => body.name === row.a);
1559
- const bodyB = row.bodies?.find((body) => body.name === row.b);
1560
- return [bodyA === void 0 ? null : (0, react.createElement)("button", {
1561
- key: "a",
1562
- type: "button",
1563
- "data-role": "body-a",
1564
- "aria-label": t("body." + bodyA.side) + " " + bodyA.name,
1565
- title: t("body." + bodyA.side) + " " + bodyA.name,
1566
- onClick: () => {
1567
- openBody(bodyA.name, bodyA.side, bodyA.rowId);
1568
- }
1569
- }, "A · " + bodyA.name), bodyB === void 0 ? null : (0, react.createElement)("button", {
1570
- key: "b",
1571
- type: "button",
1572
- "data-role": "body-b",
1573
- "aria-label": t("body." + bodyB.side) + " " + bodyB.name,
1574
- title: t("body." + bodyB.side) + " " + bodyB.name,
1575
- onClick: () => {
1576
- openBody(bodyB.name, bodyB.side, bodyB.rowId);
1577
- }
1578
- }, "B · " + bodyB.name)];
1579
- })(), actions), (0, react.createElement)("details", { "data-role": "row-more" }, (0, react.createElement)("summary", null, t("row.more")), ...row.actions.map((action) => (0, react.createElement)("button", {
1715
+ }, t("sort.menu")), ...row.actions.map((action) => (0, react.createElement)("button", {
1580
1716
  key: "copy-" + action.id,
1581
1717
  type: "button",
1582
1718
  "data-role": "row-copy",
@@ -1770,6 +1906,39 @@ window.__ModuleLoader__.load({
1770
1906
  className: "evo-merge-modal"
1771
1907
  }, inner);
1772
1908
  }
1909
+ function pairNode(overlay, t, onClose) {
1910
+ const Modal = platformModal();
1911
+ const title = t("overlay.pair");
1912
+ const block = (target, body) => (0, react.createElement)("section", {
1913
+ "data-role": target === overlay.a ? "body-a" : "body-b",
1914
+ "data-side": target.side
1915
+ }, (0, react.createElement)("h4", { "data-role": "pair-name" }, target.name + " · " + t("body." + target.side)), body === void 0 ? null : body.truncated ? (0, react.createElement)("p", { "data-role": "body-truncated" }, t("body.truncated")) : null, body === void 0 ? null : body.text === "" ? (0, react.createElement)("p", { "data-role": "body-empty" }, t("body.empty")) : (0, react.createElement)("pre", { "data-role": "body-text" }, body.text));
1916
+ const inner = (0, react.createElement)("section", {
1917
+ "data-role": "overlay",
1918
+ "data-overlay": "pair",
1919
+ "aria-label": title,
1920
+ ...Modal === void 0 ? {
1921
+ role: "dialog",
1922
+ "aria-modal": "true",
1923
+ onKeyDown: (event) => {
1924
+ overlayKey(event, onClose);
1925
+ }
1926
+ } : { "data-platform-modal": "true" }
1927
+ }, (0, react.createElement)("div", { "data-role": "overlay-head" }, (0, react.createElement)("h3", { "data-role": "overlay-title" }, title), (0, react.createElement)("button", {
1928
+ type: "button",
1929
+ "data-role": "overlay-close",
1930
+ ...Modal === void 0 ? { autoFocus: true } : { "data-modal-autofocus": "true" },
1931
+ onClick: onClose
1932
+ }, 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" ? (0, react.createElement)("div", { "data-role": "pair-grid" }, block(overlay.a, overlay.aBody), block(overlay.b, overlay.bBody)) : null);
1933
+ return Modal === void 0 ? inner : (0, react.createElement)(Modal, {
1934
+ open: true,
1935
+ onClose,
1936
+ title,
1937
+ closeLabel: t("close"),
1938
+ headless: true,
1939
+ className: "evo-merge-modal"
1940
+ }, inner);
1941
+ }
1773
1942
  function historyNode(overlay, t, onClose) {
1774
1943
  const Modal = platformModal();
1775
1944
  const title = t("history");
@@ -1822,9 +1991,9 @@ window.__ModuleLoader__.load({
1822
1991
  const captureFocus = () => {
1823
1992
  returnFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
1824
1993
  };
1825
- const openBody = (name, side, rowId) => {
1994
+ const openBoth = (a, b, rowId) => {
1826
1995
  captureFocus();
1827
- store.openBody(name, side, rowId);
1996
+ store.openPair(a, b, rowId);
1828
1997
  };
1829
1998
  const openHistory = () => {
1830
1999
  captureFocus();
@@ -1992,6 +2161,10 @@ window.__ModuleLoader__.load({
1992
2161
  "data-role": "decision-notice",
1993
2162
  "data-ok": decisionNotice.ok ? "true" : "false"
1994
2163
  }, decisionNotice.message);
2164
+ const taskBanner = view.counts.pending > 0 ? (0, react.createElement)(TaskBanner, {
2165
+ count: view.counts.pending,
2166
+ text: t("task.pending")
2167
+ }) : null;
1995
2168
  const list = uiState === "ready" && queue.status === "ready" ? (0, react.createElement)("div", {
1996
2169
  "data-role": "rows",
1997
2170
  id: "merge-rows",
@@ -2001,7 +2174,7 @@ window.__ModuleLoader__.load({
2001
2174
  t,
2002
2175
  copy,
2003
2176
  sort: sortHandlers,
2004
- openBody,
2177
+ openBoth,
2005
2178
  onDecide: face.decide === void 0 ? void 0 : decideRow,
2006
2179
  busy: deciding === row.id
2007
2180
  }))) : null;
@@ -2070,26 +2243,7 @@ window.__ModuleLoader__.load({
2070
2243
  return (0, react.createElement)("section", {
2071
2244
  className: CSS_TAG_ID,
2072
2245
  "data-state": uiState
2073
- }, (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", {
2074
- type: "button",
2075
- "data-role": "start-scan",
2076
- onClick: () => {
2077
- copy(view.startCommands.scan);
2078
- }
2079
- }, t("start.scan")), (0, react.createElement)("button", {
2080
- type: "button",
2081
- "data-role": "start-pair",
2082
- onClick: () => {
2083
- copy(view.startCommands.pair);
2084
- }
2085
- }, t("start.pair"))), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)(MergeSettings, {
2086
- view,
2087
- t
2088
- }), (0, react.createElement)(MergeRunStrips, {
2089
- view,
2090
- t,
2091
- copy
2092
- }), tabs, expiredToggle, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
2246
+ }, (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" }, taskBanner, snapshot.viewState === "loading" || queue.status === "loading" ? (0, react.createElement)("p", { "data-role": "note" }, t("loading")) : queue.refreshing === true ? (0, react.createElement)("p", {
2093
2247
  "data-role": "note",
2094
2248
  role: "status"
2095
2249
  }, t("refreshing")) : null, activeFailure === void 0 ? null : failureNode(t, activeFailure, fallback, copy), copyState.kind === "idle" ? null : copyState.kind === "copied" ? (0, react.createElement)("p", {
@@ -2104,14 +2258,33 @@ window.__ModuleLoader__.load({
2104
2258
  onClick: () => {
2105
2259
  if (copyState.text !== void 0) copy(copyState.text);
2106
2260
  }
2107
- }, t("copyRetry"))), notice, list, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2261
+ }, t("copyRetry"))), notice, list, tabs, expiredToggle, empty, queue.nextCursor === void 0 || uiState !== "ready" ? null : (0, react.createElement)("button", {
2108
2262
  type: "button",
2109
2263
  "data-role": "load-more",
2110
2264
  disabled: queue.status === "loading",
2111
2265
  onClick: () => {
2112
2266
  store.loadMore();
2113
2267
  }
2114
- }, t("loadMore")), (0, react.createElement)("button", {
2268
+ }, t("loadMore")), (0, react.createElement)(MergeSettings, {
2269
+ view,
2270
+ t
2271
+ }), (0, react.createElement)(MergeRunStrips, {
2272
+ view,
2273
+ t,
2274
+ copy
2275
+ }), view.degraded ? (0, react.createElement)("p", { "data-role": "degraded" }, t("degraded") + " " + view.notes.join(",")) : null, (0, react.createElement)("div", { "data-role": "start" }, (0, react.createElement)("button", {
2276
+ type: "button",
2277
+ "data-role": "start-scan",
2278
+ onClick: () => {
2279
+ copy(view.startCommands.scan);
2280
+ }
2281
+ }, t("start.scan")), (0, react.createElement)("button", {
2282
+ type: "button",
2283
+ "data-role": "start-pair",
2284
+ onClick: () => {
2285
+ copy(view.startCommands.pair);
2286
+ }
2287
+ }, t("start.pair"))), (0, react.createElement)("button", {
2115
2288
  type: "button",
2116
2289
  "data-role": "history",
2117
2290
  onClick: () => {
@@ -2123,7 +2296,7 @@ window.__ModuleLoader__.load({
2123
2296
  onClick: () => {
2124
2297
  copy(doctorCommand);
2125
2298
  }
2126
- }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), (0, react.createElement)("button", {
2299
+ }, t("doctor.open")), overlay === void 0 ? null : overlay.kind === "body" ? bodyNode(overlay, t, closeOverlay) : overlay.kind === "pair" ? pairNode(overlay, t, closeOverlay) : historyNode(overlay, t, closeOverlay), (0, react.createElement)("button", {
2127
2300
  type: "button",
2128
2301
  "data-role": "refresh",
2129
2302
  onClick: () => {
@@ -2436,6 +2609,54 @@ window.__ModuleLoader__.load({
2436
2609
  }
2437
2610
  });
2438
2611
  }
2612
+ async openPair(a, b, rowId) {
2613
+ const token = ++this.overlayToken;
2614
+ this.set({
2615
+ ...this.snapshot,
2616
+ overlay: {
2617
+ kind: "pair",
2618
+ a,
2619
+ b,
2620
+ status: "loading"
2621
+ }
2622
+ });
2623
+ const [left, right] = await Promise.all([this.source.body(a.name, a.side, rowId), this.source.body(b.name, b.side, rowId)]);
2624
+ if (this.disposed || token !== this.overlayToken) return;
2625
+ if (!left.ok || !right.ok) {
2626
+ const failure = !left.ok ? left.failure : !right.ok ? right.failure : void 0;
2627
+ if (failure?.kind === "blocked") {
2628
+ this.set({
2629
+ ...this.snapshot,
2630
+ viewState: "blocked",
2631
+ viewFailure: failure,
2632
+ overlay: void 0
2633
+ });
2634
+ return;
2635
+ }
2636
+ this.set({
2637
+ ...this.snapshot,
2638
+ overlay: {
2639
+ kind: "pair",
2640
+ a,
2641
+ b,
2642
+ status: "error",
2643
+ ...failure === void 0 ? {} : { failure }
2644
+ }
2645
+ });
2646
+ return;
2647
+ }
2648
+ this.set({
2649
+ ...this.snapshot,
2650
+ overlay: {
2651
+ kind: "pair",
2652
+ a,
2653
+ b,
2654
+ status: "ready",
2655
+ aBody: left.payload,
2656
+ bBody: right.payload
2657
+ }
2658
+ });
2659
+ }
2439
2660
  async openHistory() {
2440
2661
  const token = ++this.overlayToken;
2441
2662
  this.set({
@@ -1,4 +1,4 @@
1
- /** One merge row: summary, stage, facts, primary decisions and the copy drawer. */
1
+ /** One merge row: summary, stage, facts, one primary decision and the copy/sort drawer. */
2
2
  import { type DragEvent, type ReactNode } from 'react';
3
3
  import type { MergeRowView } from './api.ts';
4
4
  type Translate = (key: string, values?: Readonly<Record<string, string | number>>) => string;
@@ -10,16 +10,23 @@ export interface RowSortHandlers {
10
10
  menu(id: string, x: number, y: number): void;
11
11
  }
12
12
  /**
13
- * Render one row. Primary decisions stay visible; copy commands live in one details drawer.
13
+ * Render one row as a decision card. The approve action is the only primary; A/B, reject and
14
+ * auxiliary sort/copy commands cannot borrow that weight.
14
15
  * @param props - row, translator, copier, sort hooks, body opener and decision callback.
15
- * @returns the row element.
16
+ * @returns the row with drag/keyboard wiring and a separate more drawer.
16
17
  */
17
18
  export declare function MergeRow(props: {
18
19
  row: MergeRowView;
19
20
  t: Translate;
20
21
  copy: (command: string) => void;
21
22
  sort: RowSortHandlers;
22
- openBody: (name: string, side: 'live' | 'archive', rowId: string | undefined) => void;
23
+ openBoth: (a: {
24
+ name: string;
25
+ side: 'live' | 'archive';
26
+ }, b: {
27
+ name: string;
28
+ side: 'live' | 'archive';
29
+ }, rowId: string | undefined) => void;
23
30
  onDecide: ((rowId: string, verb: 'approve' | 'reject') => void) | undefined;
24
31
  busy: boolean;
25
32
  }): ReactNode;
@@ -17,6 +17,10 @@ export interface QueueState {
17
17
  nextCursor?: string | undefined;
18
18
  failure?: MergeFailure | undefined;
19
19
  }
20
+ export interface BodyTarget {
21
+ readonly name: string;
22
+ readonly side: 'live' | 'archive';
23
+ }
20
24
  export type OverlayState = {
21
25
  kind: 'body';
22
26
  name: string;
@@ -24,6 +28,14 @@ export type OverlayState = {
24
28
  status: 'loading' | 'ready' | 'error';
25
29
  body?: MergeBody;
26
30
  failure?: MergeFailure;
31
+ } | {
32
+ kind: 'pair';
33
+ a: BodyTarget;
34
+ b: BodyTarget;
35
+ status: 'loading' | 'ready' | 'error';
36
+ aBody?: MergeBody;
37
+ bBody?: MergeBody;
38
+ failure?: MergeFailure;
27
39
  } | {
28
40
  kind: 'history';
29
41
  status: 'loading' | 'ready' | 'error';
@@ -71,6 +83,7 @@ export declare class MergeStore {
71
83
  refresh(): void;
72
84
  loadMore(): void;
73
85
  openBody(name: string, side: 'live' | 'archive', rowId?: string): Promise<void>;
86
+ openPair(a: BodyTarget, b: BodyTarget, rowId?: string): Promise<void>;
74
87
  openHistory(): Promise<void>;
75
88
  closeOverlay(): void;
76
89
  dispose(): void;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  ]
12
12
  }
13
13
  },
14
- "version": "0.35.0",
14
+ "version": "0.36.1",
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
@@ -51,7 +51,7 @@
51
51
  "react-dom": "^18.2.0"
52
52
  },
53
53
  "dependencies": {
54
- "@lmzhen/dsh-evolution-client-kit": "^0.35.0"
54
+ "@lmzhen/dsh-evolution-client-kit": "^0.36.1"
55
55
  },
56
56
  "engines": {
57
57
  "dsh": ">=0.2.1-rc.2 <0.2.2-0"
@@ -1,52 +0,0 @@
1
- /**
2
- * The one seam onto the platform's menu material and rows.
3
- *
4
- * The family bundle keeps `@deepseek-ai/*` external, so the platform components are reached through
5
- * the loader-provided `require` (the same door the Markdown seam uses). A deployment without the
6
- * row gets the panel's own menu fallback; the platform path is not a hard dependency.
7
- * @module dsh-evolution-merge-ui/client
8
- */
9
- import { type ReactNode } from 'react';
10
- /** The platform's menu surface: a styled container that accepts div props. */
11
- export type MenuSurfaceComponent = (props: Record<string, unknown> & {
12
- children?: ReactNode;
13
- }) => ReactNode;
14
- /** The platform's menu row: role=menuitem plus selected/disabled/danger semantics. */
15
- export type MenuItemButtonComponent = (props: {
16
- children: ReactNode;
17
- current?: boolean;
18
- disabled?: boolean;
19
- danger?: boolean;
20
- separatorBefore?: boolean;
21
- onSelect: () => void;
22
- }) => ReactNode;
23
- /** The two components this panel consumes from one platform module row. */
24
- export interface MenuPrimitives {
25
- MenuSurface: MenuSurfaceComponent;
26
- MenuItemButton: MenuItemButtonComponent;
27
- }
28
- /** One row of the coordinate menu, independent of the renderer. */
29
- export interface RowMenuAction {
30
- readonly id: string;
31
- readonly label: string;
32
- readonly onSelect: () => void;
33
- readonly separatorBefore?: boolean;
34
- }
35
- /**
36
- * Render the coordinate menu through the platform primitives.
37
- * @param primitives - the platform MenuSurface/MenuItemButton pair.
38
- * @param actions - rows in display order.
39
- * @param label - accessible menu name.
40
- * @param style - fixed position computed from the pointer.
41
- * @returns the menu element.
42
- */
43
- export declare function renderPlatformMenu(primitives: MenuPrimitives, actions: readonly RowMenuAction[], label: string, style: Record<string, string | number>): ReactNode;
44
- /**
45
- * Accept only the two component functions; a namespace row without them is not a menu.
46
- * @param row - the module row's exports.
47
- * @returns the two components, or undefined.
48
- */
49
- export declare function menuPrimitivesFrom(row: Record<string, unknown>): MenuPrimitives | undefined;
50
- /** The module-table row, when this deployment carries it. */
51
- export declare const PLATFORM_MENU: MenuPrimitives | undefined;
52
- //# sourceMappingURL=platform-menu.d.ts.map