dashboard: show when an approval was requested (closes #272)

This commit is contained in:
iris 2026-05-22 20:06:45 +02:00
parent 908cadb151
commit a9a10b631f
4 changed files with 40 additions and 2 deletions

View file

@ -208,8 +208,11 @@ agent is stale. Banner pulses on each broker SSE event
Each pending approval renders as a card (`assets/app.js::
renderApprovals`) with three stacked sections:
- **identity header** — glyph, `#id`, agent, kind chip, and (for
`apply_commit`) the short proposal sha as `<code>`.
- **identity header** — glyph, `#id`, agent, kind chip, (for
`apply_commit`) the short proposal sha as `<code>`, and a
right-aligned `requested <N> ago` relative time from
`ApprovalView.requested_at` — amber once the request has been
pending ≥ 1h so a stale approval stands out (issue #272).
- **what-changed body** — the manager's description, then
drill-in triggers: `↳ view diff` opens the diff in the side
panel; `↳ commit on forge ↗` deep-links the proposal commit

View file

@ -1193,6 +1193,12 @@
sha_short: ev.sha_short || null,
diff: ev.diff || null,
description: ev.description || null,
// The ApprovalAdded event carries no requested_at; a live-added
// approval was queued just now, so client-now is accurate — and
// consistent with how fmtAgo compares everything to client-now.
// A later /api/state cold-load swaps in the server value. (#272)
requested_at: ev.requested_at != null
? ev.requested_at : Math.floor(Date.now() / 1000),
};
if (existing >= 0) approvalsState.pending[existing] = row;
else approvalsState.pending.push(row);
@ -1364,6 +1370,16 @@
isApply ? 'apply' : isInit ? 'init' : 'spawn'),
);
if (isApply && 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. (issue #272)
if (a.requested_at != null) {
const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - a.requested_at));
head.append(el('span', {
class: 'approval-ts' + (ageSec >= 3600 ? ' stale' : ''),
title: 'requested ' + new Date(a.requested_at * 1000).toLocaleString(),
}, 'requested ' + fmtAgo(a.requested_at)));
}
li.append(head);
// ── what-changed body ────────────────────────────────────────

View file

@ -299,6 +299,18 @@ code {
flex-wrap: wrap;
gap: 0.3em;
}
/* When the approval was requested right-aligned in the head row;
goes amber once it has been pending 1h so a stale request stands
out at a glance (issue #272). */
.approval-ts {
margin-left: auto;
color: var(--muted);
font-size: 0.85em;
}
.approval-ts.stale {
color: var(--amber);
text-shadow: 0 0 6px rgba(250, 179, 135, 0.5);
}
.approval-body {
margin: 0.45em 0;
padding-left: 1.3em;

View file

@ -301,6 +301,9 @@ struct ApprovalView {
/// Manager-supplied description shown on the approval card.
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<String>,
/// Unix seconds the approval was queued. Rendered as a relative
/// time on the card so the operator can spot a stale request. (#272)
requested_at: i64,
}
/// Replace silent `.unwrap_or_default()` on the data sources behind
@ -606,6 +609,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
sha_short: Some(sha),
diff: Some(diff),
description: a.description,
requested_at: a.requested_at,
}
}
hive_sh4re::ApprovalKind::Spawn => ApprovalView {
@ -615,6 +619,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
sha_short: None,
diff: None,
description: a.description,
requested_at: a.requested_at,
},
hive_sh4re::ApprovalKind::InitConfig => ApprovalView {
id: a.id,
@ -623,6 +628,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
sha_short: None,
diff: None,
description: a.description,
requested_at: a.requested_at,
},
hive_sh4re::ApprovalKind::UpdateMetaInputs => ApprovalView {
id: a.id,
@ -631,6 +637,7 @@ async fn build_approval_views(approvals: Vec<Approval>) -> Vec<ApprovalView> {
sha_short: None,
diff: None,
description: a.description,
requested_at: a.requested_at,
},
});
}