diff --git a/frontend/packages/dashboard/src/call.js b/frontend/packages/dashboard/src/call.js index 967f89c4..6c0965a9 100644 --- a/frontend/packages/dashboard/src/call.js +++ b/frontend/packages/dashboard/src/call.js @@ -314,17 +314,18 @@ export function renderApprovals() { for (const a of pending) { const isApply = a.kind === 'apply_commit'; const isInit = a.kind === 'init_config'; + const isMergePr = a.kind === 'merge_config_pr'; const li = el('li', { class: 'approval-card' }); // ── identity header ────────────────────────────────────────── const head = el('div', { class: 'approval-head' }, - el('span', { class: 'glyph' }, isApply ? '→' : '⊕'), + el('span', { class: 'glyph' }, isApply ? '→' : isMergePr ? '⇒' : '⊕'), el('span', { class: 'id' }, '#' + a.id), el('span', { class: 'agent' }, a.agent), - el('span', { class: 'kind' + (isApply ? '' : ' kind-spawn') }, - isApply ? 'apply' : isInit ? 'init' : 'spawn'), + el('span', { class: 'kind' + ((isApply || isMergePr) ? '' : ' kind-spawn') }, + isApply ? 'apply' : isMergePr ? 'merge-pr' : isInit ? 'init' : 'spawn'), ); - if (isApply && a.sha_short) head.append(el('code', {}, a.sha_short)); + if ((isApply || isMergePr) && a.sha_short) head.append(el('code', {}, a.sha_short)); // When the approval was requested — relative time, right-aligned. // Goes amber once it's been pending an hour so a stale request is // obvious at a glance (see docs/web-ui.md::Approval card). @@ -357,6 +358,19 @@ export function renderApprovals() { }, '↳ commit on forge ↗')); } body.append(drill); + } else if (isMergePr) { + // PR-based config deploy: link to the reviewed PR on the forge + // (mirrors the apply_commit "commit on forge" link). The config + // diff side-panel is apply_commit-only for now. + const drill = el('div', { class: 'drill-ins' }); + if (forgeBase && a.pr_number != null) { + drill.append(el('a', { + class: 'panel-trigger', target: '_blank', rel: 'noopener', + href: `${forgeBase}/agent-configs/${a.agent}/pulls/${a.pr_number}`, + title: 'review this config PR on the hive forge', + }, '↳ review PR on forge ↗')); + } + body.append(drill); } else { body.append(el('span', { class: 'meta' }, isInit @@ -403,7 +417,7 @@ function renderApprovalHistory(root, history) { el('span', { class: 'glyph glyph-' + a.status }, glyph), ' ', el('span', { class: 'id' }, '#' + a.id), ' ', el('span', { class: 'agent' }, a.agent), ' ', - el('span', { class: 'kind' }, a.kind === 'apply_commit' ? 'apply' : a.kind === 'init_config' ? 'init' : 'spawn'), ' ', + el('span', { class: 'kind' }, a.kind === 'apply_commit' ? 'apply' : a.kind === 'merge_config_pr' ? 'merge-pr' : a.kind === 'init_config' ? 'init' : 'spawn'), ' ', ); if (a.sha_short) row.append(el('code', {}, a.sha_short), ' '); row.append(