feat(dashboard): live-tick agent status-age chip via data-set-at + 30s interval

Stamp data-set-at on .status-age spans so the '(set N ago)' label
stays accurate as time passes. Previously the age was computed once
when the async dashboard-state fetch completed and never updated.

With the keyed container row cache, rows persist much longer between
rebuilds (no more full rebuild on every SSE event), making stale
status-age labels more noticeable. The 30s ticker fixes this without
requiring a full row rebuild or re-fetch.
This commit is contained in:
iris 2026-06-05 12:18:24 +02:00 committed by mara
commit 2a5444fdc8

View file

@ -737,13 +737,18 @@ window.marked = marked;
const nowUnix = Math.floor(Date.now() / 1000);
const ageStr = ds.status_set_at != null
? ` (set ${fmtAgeSecs(nowUnix - ds.status_set_at)} ago)` : '';
// Stamp data-set-at so the 30s ticker below keeps the age
// label current even when the row is reused from the cache.
const ageAttrs = ds.status_set_at != null
? { class: 'status-age', 'data-set-at': String(ds.status_set_at) }
: { class: 'status-age' };
body.append(el('div', {
class: 'agent-status',
title: `agent self-reported status${ageStr}`,
},
el('span', { class: 'status-icon' }, '◈ '),
ds.status_text,
el('span', { class: 'status-age' }, ageStr),
el('span', ageAttrs, ageStr),
));
}
})
@ -1896,6 +1901,20 @@ window.marked = marked;
});
}, 1000);
// 30s ticker for agent status-age chips. Renderers stamp `data-set-at`
// (unix seconds) on the `.status-age` span. Keyed container rows persist
// across re-renders, so without this ticker the "(set N ago)" label would
// become stale as time passes. 30s granularity matches fmtAgeSecs precision
// (sub-minute values round to seconds, coarser above that).
setInterval(() => {
const now = Math.floor(Date.now() / 1000);
document.querySelectorAll('.status-age[data-set-at]').forEach((node) => {
const setAt = Number(node.dataset.setAt);
if (!Number.isFinite(setAt) || setAt === 0) return;
node.textContent = ` (set ${fmtAgeSecs(now - setAt)} ago)`;
});
}, 30_000);
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.