From 4731b64f3e16844c8efe1e9955eb6980b5a2fb75 Mon Sep 17 00:00:00 2001 From: iris Date: Fri, 5 Jun 2026 11:51:31 +0200 Subject: [PATCH] feat(dashboard): live ticker for approval request-age chips + precise stale threshold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approval cards render their age label ("requested N ago") at render time from a cold /api/state or an approval_added / approval_resolved SSE event. An approval sitting pending for an hour would show a stale "0s ago" unless one of those events fired in the meantime. Fix: stamp data-requested-at= on each .approval-ts span. A 1s setInterval ticker reads all live chips, recomputes the relative time via fmtAgo, and toggles .stale (amber highlight) at exactly the 1-hour threshold — no longer dependent on the next full re-render cycle to apply the amber colour. --- frontend/packages/dashboard/src/tabs.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/packages/dashboard/src/tabs.js b/frontend/packages/dashboard/src/tabs.js index 3f4ad6a3..54325005 100644 --- a/frontend/packages/dashboard/src/tabs.js +++ b/frontend/packages/dashboard/src/tabs.js @@ -1994,6 +1994,22 @@ window.marked = marked; }); }, 30_000); + // Live ticker for approval request-age chips. Approval cards only + // re-render on `approval_added`/`approval_resolved` SSE events, so + // a request pending for an hour could still show "0s ago" without + // this ticker. Also flips `.stale` (amber highlight) at exactly 1h + // rather than only at the next re-render. + setInterval(() => { + const now = Math.floor(Date.now() / 1000); + document.querySelectorAll('.approval-ts[data-requested-at]').forEach((node) => { + const requestedAt = Number(node.getAttribute('data-requested-at')); + if (!Number.isFinite(requestedAt)) return; + const ageSec = Math.max(0, now - requestedAt); + node.textContent = 'requested ' + fmtAgo(requestedAt); + node.classList.toggle('stale', ageSec >= 3600); + }); + }, 1000); + const APPROVAL_TAB_KEY = 'hyperhive:approvals:tab'; // Derived approval state — cold-loaded from /api/state, then mutated // live by `approval_added` / `approval_resolved` dashboard events. @@ -2248,6 +2264,7 @@ window.marked = marked; head.append(el('span', { class: 'approval-ts' + (ageSec >= 3600 ? ' stale' : ''), title: 'requested ' + new Date(a.requested_at * 1000).toLocaleString(), + 'data-requested-at': String(a.requested_at), }, 'requested ' + fmtAgo(a.requested_at))); } li.append(head);