feat(dashboard): live countdown ticker for reminder + schedule due-at labels
Reminder due-at labels ("in 3m 45s") and schedule next-fire cells
were computed once at render time and never updated. For short-horizon
reminders the stale count was visually misleading.
Both renderers now stamp data-due-at=<unix> on their respective
elements (.reminder-due / .sched-due). A shared 1s setInterval
ticker walks all live elements and rewrites textContent using
fmtDuration / fmtAgo — same zero-re-render pattern as the
existing question TTL chip ticker.
This commit is contained in:
parent
bf25d71fc9
commit
c0d7ec6572
1 changed files with 21 additions and 3 deletions
|
|
@ -455,8 +455,7 @@ window.marked = marked;
|
|||
menuItem('↻ R3BU1LD', { action: '/rebuild/', confirm: `rebuild ${c.name}? hot-reloads the container.` }),
|
||||
menuSep(),
|
||||
// Deep-link to the AGENT log tab pre-filtered to this container.
|
||||
// The ?agent= param is read by logs.js on load; the stored localStorage
|
||||
// selection is overridden so the operator lands directly in this
|
||||
// The ?agent= param is read by logs.js on load and pre-selects this
|
||||
// agent's journal without extra clicks.
|
||||
menuLink('journal logs →',
|
||||
`/logs.html?agent=${encodeURIComponent(c.name)}#agent`,
|
||||
|
|
@ -1999,6 +1998,11 @@ window.marked = marked;
|
|||
// 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.
|
||||
// Live countdown for reminder due-at labels and schedule next-fire
|
||||
// cells. Both renderers stamp `data-due-at` on the element so this
|
||||
// single ticker keeps them fresh without triggering a full re-render.
|
||||
// `.reminder-due` → "overdue X ago" / "in Y"
|
||||
// `.sched-due` → same pattern
|
||||
setInterval(() => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
document.querySelectorAll('.approval-ts[data-requested-at]').forEach((node) => {
|
||||
|
|
@ -2008,6 +2012,14 @@ window.marked = marked;
|
|||
node.textContent = 'requested ' + fmtAgo(requestedAt);
|
||||
node.classList.toggle('stale', ageSec >= 3600);
|
||||
});
|
||||
document.querySelectorAll('.reminder-due[data-due-at], .sched-due[data-due-at]').forEach((node) => {
|
||||
const dueAt = Number(node.getAttribute('data-due-at'));
|
||||
if (!Number.isFinite(dueAt)) return;
|
||||
const dueIn = dueAt - now;
|
||||
node.textContent = dueIn <= 0
|
||||
? 'overdue ' + fmtAgo(dueAt)
|
||||
: (node.classList.contains('reminder-due') ? 'in ' : '') + fmtDuration(dueIn);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const APPROVAL_TAB_KEY = 'hyperhive:approvals:tab';
|
||||
|
|
@ -2790,7 +2802,11 @@ window.marked = marked;
|
|||
: `in ${fmtDuration(dueIn)}`;
|
||||
const head = el('div', { class: 'reminder-head' },
|
||||
el('span', { class: 'agent' }, r.agent), ' ',
|
||||
el('span', { class: 'meta', title: new Date(r.due_at * 1000).toISOString() }, dueLabel),
|
||||
el('span', {
|
||||
class: 'meta reminder-due',
|
||||
title: new Date(r.due_at * 1000).toISOString(),
|
||||
'data-due-at': String(r.due_at),
|
||||
}, dueLabel),
|
||||
' ',
|
||||
el('span', { class: 'meta' }, `· id ${r.id}`),
|
||||
);
|
||||
|
|
@ -3393,6 +3409,8 @@ window.marked = marked;
|
|||
nextCell.textContent = dueIn <= 0
|
||||
? 'overdue ' + fmtAgo(s.next_fire_at_unix)
|
||||
: fmtDuration(dueIn);
|
||||
nextCell.classList.add('sched-due');
|
||||
nextCell.dataset.dueAt = String(s.next_fire_at_unix);
|
||||
}
|
||||
tr.append(nextCell);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue