From c65201ed7c6bcce1eb7e7abb96a15b89eed9e617 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 27 Jun 2026 13:03:41 +0200 Subject: [PATCH] fix(dashboard): render update_meta_inputs + schedule_prompt approval cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both kinds fell through to the spawn branch in renderApprovals, showing a misleading 'spawn' chip and agent-spawn body text. Mara saw a meta-input bump render as a spawn card for agent damocles and denied it. Backend (dashboard.rs): - Add commit_ref: None to the MergeConfigPr arm (struct was incomplete). All arms of ApprovalView now initialise every field. Frontend (call.js): - Add isUpdateMeta / isSchedule booleans alongside the existing kind flags. - Glyph: update_meta_inputs gets ↻, schedule_prompt gets ⏱. - Kind chip: 'meta-update' / 'schedule' (no kind-spawn class for either). - Body: update_meta_inputs parses commit_ref as JSON Vec and shows 'bump flake inputs: foo, bar' or 'bump all flake inputs'; schedule_prompt parses SchedulePromptPayload and shows targets + first-fire time + cadence + a truncated body excerpt. - History row: add 'meta-update' and 'schedule' cases (were both 'spawn'). - Import fmtDuration from util.js (needed for schedule cadence display). --- frontend/packages/dashboard/src/call.js | 34 +++++++++++++++++++++---- hive-c0re/src/dashboard.rs | 13 ++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/frontend/packages/dashboard/src/call.js b/frontend/packages/dashboard/src/call.js index fcb0fa21..7500fe53 100644 --- a/frontend/packages/dashboard/src/call.js +++ b/frontend/packages/dashboard/src/call.js @@ -16,7 +16,7 @@ import { $, el, form, Panel, appendLinkified } from './common.js'; import { themedToast } from './modal.js'; -import { fmtAgo } from './util.js'; +import { fmtAgo, fmtDuration } from './util.js'; import { questionsState, QUESTION_HISTORY_LIMIT } from './state.js'; // Registered by the dashboard entry at boot; defaults to a no-op so the @@ -316,15 +316,17 @@ export function renderApprovals() { const isApply = a.kind === 'apply_commit'; const isInit = a.kind === 'init_config'; const isMergePr = a.kind === 'merge_config_pr'; + const isUpdateMeta = a.kind === 'update_meta_inputs'; + const isSchedule = a.kind === 'schedule_prompt'; const li = el('li', { class: 'approval-card' }); // ── identity header ────────────────────────────────────────── const head = el('div', { class: 'approval-head' }, - el('span', { class: 'glyph' }, isApply ? '→' : isMergePr ? '⇒' : '⊕'), + el('span', { class: 'glyph' }, isApply ? '→' : isMergePr ? '⇒' : isUpdateMeta ? '↻' : isSchedule ? '⏱' : '⊕'), el('span', { class: 'id' }, '#' + a.id), el('span', { class: 'agent' }, a.agent), - el('span', { class: 'kind' + ((isApply || isMergePr) ? '' : ' kind-spawn') }, - isApply ? 'apply' : isMergePr ? 'merge-pr' : isInit ? 'init' : 'spawn'), + el('span', { class: 'kind' + ((isApply || isMergePr || isUpdateMeta || isSchedule) ? '' : ' kind-spawn') }, + isApply ? 'apply' : isMergePr ? 'merge-pr' : isUpdateMeta ? 'meta-update' : isSchedule ? 'schedule' : isInit ? 'init' : 'spawn'), ); if ((isApply || isMergePr) && a.sha_short) head.append(el('code', {}, a.sha_short)); // When the approval was requested — relative time, right-aligned. @@ -372,6 +374,28 @@ export function renderApprovals() { }, '↳ review PR on forge ↗')); } body.append(drill); + } else if (isUpdateMeta) { + let inputs; + try { inputs = JSON.parse(a.commit_ref || '[]'); } catch (_) { inputs = []; } + body.append(el('span', { class: 'meta' }, + inputs.length + ? 'bump flake inputs: ' + inputs.join(', ') + : 'bump all flake inputs')); + } else if (isSchedule) { + let payload; + try { payload = JSON.parse(a.commit_ref || '{}'); } catch (_) { payload = {}; } + const targets = (payload.targets || []).join(', '); + const firstFire = payload.first_fire_at_unix + ? new Date(payload.first_fire_at_unix * 1000).toLocaleString() + : '?'; + const cadence = payload.interval_seconds + ? ' · ↻ every ' + fmtDuration(payload.interval_seconds) + : ' · one-shot'; + body.append(el('div', { class: 'meta' }, '→ ' + targets + ' · first: ' + firstFire + cadence)); + if (payload.body) { + const excerpt = payload.body.length > 80 ? payload.body.slice(0, 80) + '…' : payload.body; + body.append(el('div', { class: 'approval-description' }, excerpt)); + } } else { body.append(el('span', { class: 'meta' }, isInit @@ -418,7 +442,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 === 'merge_config_pr' ? 'merge-pr' : a.kind === 'init_config' ? 'init' : 'spawn'), ' ', + el('span', { class: 'kind' }, a.kind === 'apply_commit' ? 'apply' : a.kind === 'merge_config_pr' ? 'merge-pr' : a.kind === 'update_meta_inputs' ? 'meta-update' : a.kind === 'schedule_prompt' ? 'schedule' : a.kind === 'init_config' ? 'init' : 'spawn'), ' ', ); if (a.sha_short) row.append(el('code', {}, a.sha_short), ' '); row.append( diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 28d39e5a..3c744202 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -448,6 +448,13 @@ struct ApprovalView { /// sha. `None` for every other kind. #[serde(skip_serializing_if = "Option::is_none")] pr_number: Option, + /// Raw `commit_ref` payload for `UpdateMetaInputs` (JSON-encoded + /// `Vec` of input names; `"[]"` = all inputs) and + /// `SchedulePrompt` (JSON-encoded `SchedulePromptPayload`). The + /// frontend parses this to render a human-readable card body. + /// `None` for every other kind. + #[serde(skip_serializing_if = "Option::is_none")] + commit_ref: Option, /// Unix seconds the approval was queued. Rendered as a relative /// time on the card so the operator can spot a stale request. requested_at: i64, @@ -909,6 +916,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: Some(diff), description: a.description, pr_number: None, + commit_ref: None, requested_at: a.requested_at, } } @@ -920,6 +928,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: None, description: a.description, pr_number: None, + commit_ref: None, requested_at: a.requested_at, }, hive_sh4re::ApprovalKind::InitConfig => ApprovalView { @@ -930,6 +939,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: None, description: a.description, pr_number: None, + commit_ref: None, requested_at: a.requested_at, }, hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView { @@ -940,6 +950,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: None, description: a.description, pr_number: None, + commit_ref: Some(a.commit_ref), requested_at: a.requested_at, }, hive_sh4re::ApprovalKind::SchedulePrompt => ApprovalView { @@ -950,6 +961,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: None, description: a.description, pr_number: None, + commit_ref: Some(a.commit_ref), requested_at: a.requested_at, }, hive_sh4re::ApprovalKind::MergeConfigPr => { @@ -971,6 +983,7 @@ async fn build_approval_views(approvals: Vec) -> Vec { diff: None, description: a.description, pr_number, + commit_ref: None, requested_at: a.requested_at, } }