From d57dd68fc01e989201a1125f848084ab45be5ace Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 21 Aug 2026 13:01:02 +0900 Subject: [PATCH] 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 --- src/unified-dashboard.test.ts | 10 ++++++++++ src/unified-dashboard.ts | 20 +++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/unified-dashboard.test.ts b/src/unified-dashboard.test.ts index 69d349f..f0f4b41 100644 --- a/src/unified-dashboard.test.ts +++ b/src/unified-dashboard.test.ts @@ -6,6 +6,7 @@ import { createCoalescingTrigger, editStatusMessageWithRetry, formatStatusHeader, + msUntilNextMinuteBoundary, getDashboardDuplicateCleanupIntervalMs, renderUsageTable, 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', () => { it('coalesces a burst into one run and re-arms after firing', () => { let runs = 0; diff --git a/src/unified-dashboard.ts b/src/unified-dashboard.ts index ff92248..b48a28c 100644 --- a/src/unified-dashboard.ts +++ b/src/unified-dashboard.ts @@ -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( opts: UnifiedDashboardOptions, ): Promise { @@ -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(() => { if (!isRenderer || !statusMessageId) return; void cleanupDashboardDuplicateMessages(opts, statusMessageId);