feat(dashboard): align base refresh to wall-clock minute boundary

Instead of a fixed 60s interval from an arbitrary start offset, the base status
refresh now fires on each wall-clock minute boundary (:00), keeping the
minute-precision timestamp shown in the message accurate. Event-driven updates
(new message / agent activity) still refresh in between. Adds
msUntilNextMinuteBoundary with tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-08-21 13:01:02 +09:00
parent 65ef6e8830
commit d57dd68fc0
2 changed files with 29 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ import {
createCoalescingTrigger, createCoalescingTrigger,
editStatusMessageWithRetry, editStatusMessageWithRetry,
formatStatusHeader, formatStatusHeader,
msUntilNextMinuteBoundary,
getDashboardDuplicateCleanupIntervalMs, getDashboardDuplicateCleanupIntervalMs,
renderUsageTable, renderUsageTable,
shouldPurgeDashboardChannelOnStart, shouldPurgeDashboardChannelOnStart,
@@ -349,6 +350,15 @@ describe('editStatusMessageWithRetry', () => {
}); });
}); });
describe('msUntilNextMinuteBoundary', () => {
it('returns time to the next :00 boundary', () => {
expect(msUntilNextMinuteBoundary(120_000)).toBe(60_000); // exactly on boundary
expect(msUntilNextMinuteBoundary(120_000 + 15_000)).toBe(45_000); // 15s in
expect(msUntilNextMinuteBoundary(120_000 + 59_000)).toBe(1_000); // 59s in
expect(msUntilNextMinuteBoundary(120_000 + 1)).toBe(59_999); // 1ms in
});
});
describe('createCoalescingTrigger', () => { describe('createCoalescingTrigger', () => {
it('coalesces a burst into one run and re-arms after firing', () => { it('coalesces a burst into one run and re-arms after firing', () => {
let runs = 0; let runs = 0;

View File

@@ -840,6 +840,16 @@ export function createCoalescingTrigger(
}; };
} }
/**
* Milliseconds from `nowMs` until the next wall-clock minute boundary (:00).
* Minute boundaries align across timezones (offsets are whole minutes), so this
* is timezone-agnostic. Exactly on a boundary returns a full minute.
*/
export function msUntilNextMinuteBoundary(nowMs: number): number {
const rem = nowMs % 60_000;
return rem === 0 ? 60_000 : 60_000 - rem;
}
export async function startUnifiedDashboard( export async function startUnifiedDashboard(
opts: UnifiedDashboardOptions, opts: UnifiedDashboardOptions,
): Promise<void> { ): Promise<void> {
@@ -989,7 +999,15 @@ export async function startUnifiedDashboard(
); );
} }
setInterval(updateStatus, opts.statusUpdateInterval); // Base periodic refresh fires on each wall-clock minute boundary (:00) so the
// minute-precision timestamp shown in the status message stays accurate.
const scheduleMinuteBoundaryUpdate = () => {
setTimeout(() => {
void updateStatus();
scheduleMinuteBoundaryUpdate();
}, msUntilNextMinuteBoundary(Date.now()));
};
scheduleMinuteBoundaryUpdate();
setInterval(() => { setInterval(() => {
if (!isRenderer || !statusMessageId) return; if (!isRenderer || !statusMessageId) return;
void cleanupDashboardDuplicateMessages(opts, statusMessageId); void cleanupDashboardDuplicateMessages(opts, statusMessageId);