// Shared dashboard render + format helpers. // // Small pure utilities used across most dashboard tabs (the container // tree, the rebuild queue, questions / approvals, schedules + reminders): // an atomic-swap render helper and a handful of relative-time / duration // formatters. They live here — a dashboard-internal module — rather than // in the cross-page `common.js`, because they're specific to the // dashboard's render style and not needed by the stand-alone pages. // // All pure: no DOM/module state captured, so any tab module can import // them freely without ordering concerns. // Atomic-swap render: build into an off-DOM DocumentFragment, then commit // with a single `replaceChildren`. Avoids an intermediate empty-state // flash on poll cycles even when the builder allocates a lot of nodes. export function paintAtomic(liveRoot, build) { const buf = document.createDocumentFragment(); build(buf); liveRoot.replaceChildren(buf); } // Epoch seconds from an API timestamp. The hive-c0re dashboard API // ships timestamps as RFC 3339 strings; a few feeds (rebuild queue, // meta inputs, turn stats) still carry unix-second numbers, so // numbers pass through unchanged. export function epochSec(ts) { return typeof ts === 'number' ? ts : Math.floor(Date.parse(ts) / 1000); } // Relative age of a timestamp (RFC 3339 string or unix seconds), // coarsened to one unit ("5m ago"). export function fmtAgo(ts) { const ageSec = Math.max(0, Math.floor(Date.now() / 1000 - epochSec(ts))); if (ageSec < 60) return ageSec + 's ago'; if (ageSec < 3600) return Math.floor(ageSec / 60) + 'm ago'; if (ageSec < 86400) return Math.floor(ageSec / 3600) + 'h ago'; return Math.floor(ageSec / 86400) + 'd ago'; } // Truncate a string to `n` chars, appending an ellipsis when clipped. export function truncate(s, n) { return s.length <= n ? s : s.slice(0, n - 1) + '…'; } // Running-duration label for in-flight items ("3m 12s running"). export function fmtElapsed(secs) { if (secs < 60) return secs + 's running'; if (secs < 3600) return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's running'; return Math.floor(secs / 3600) + 'h ' + Math.floor((secs % 3600) / 60) + 'm running'; } // Compact duration label, two units deep ("1h 5m", "2d 3h"). export function fmtDuration(secs) { if (secs < 60) return secs + 's'; if (secs < 3600) return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's'; if (secs < 86400) return Math.floor(secs / 3600) + 'h ' + Math.floor((secs % 3600) / 60) + 'm'; return Math.floor(secs / 86400) + 'd ' + Math.floor((secs % 86400) / 3600) + 'h'; }