From 03f12ce81f3579142af255465ac7cc1215528a5b Mon Sep 17 00:00:00 2001 From: Resham Joshi Date: Fri, 17 Apr 2026 17:05:08 -0700 Subject: [PATCH] fix(status): bucket Today/Month by local date, not UTC renderStatusBar computed `today` via `new Date().toISOString().slice(0,10)`, which is the UTC date. Session timestamps are also UTC ISO strings, but the user's expectation of "today" is their wall-clock day. During the window between local midnight and UTC midnight (e.g. 17:00 PDT on 2026-04-17, which is already 00:00 UTC on 2026-04-18), every session bucketed under local April 17 missed the UTC-April-18 filter and the status bar read `Today $0.00 0 calls` even while `--format json` and the menubar app correctly showed the spend. Both sides of the comparison now use the local date of each session timestamp, so the terminal status and the JSON / menubar paths agree. Verified at UTC midnight (the regression moment that surfaced the bug): Before: Today $0.0000 0 calls After: Today $339.87 1839 calls Caught during the fresh-clone review of the menubar PR. --- src/format.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/format.ts b/src/format.ts index 493d4e6..3905048 100644 --- a/src/format.ts +++ b/src/format.ts @@ -13,9 +13,20 @@ export function formatTokens(n: number): string { return n.toString() } +/// Returns YYYY-MM-DD for the given date in the process-local timezone. Cheaper than shelling +/// out to Intl.DateTimeFormat for every turn in a loop and avoids the UTC drift that bites +/// `Date.toISOString().slice(0,10)` whenever the user runs this between local midnight and +/// UTC midnight. +function localDateString(d: Date): string { + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, '0') + const day = String(d.getDate()).padStart(2, '0') + return `${y}-${m}-${day}` +} + export function renderStatusBar(projects: ProjectSummary[]): string { const now = new Date() - const today = now.toISOString().slice(0, 10) + const today = localDateString(now) const monthStart = `${today.slice(0, 7)}-01` let todayCost = 0, todayCalls = 0, monthCost = 0, monthCalls = 0 @@ -24,7 +35,11 @@ export function renderStatusBar(projects: ProjectSummary[]): string { for (const session of project.sessions) { for (const turn of session.turns) { if (!turn.timestamp) continue - const day = turn.timestamp.slice(0, 10) + // Bucket by the session timestamp's local date so the user's "today" and "this month" + // match the wall clock on their machine. Session timestamps are stored as UTC ISO + // strings; naively slicing `timestamp.slice(0,10)` bucketed them by UTC date, which + // showed `Today $0` during the UTC-midnight-to-local-midnight window. + const day = localDateString(new Date(turn.timestamp)) const turnCost = turn.assistantCalls.reduce((s, c) => s + c.costUSD, 0) const turnCalls = turn.assistantCalls.length if (day === today) { todayCost += turnCost; todayCalls += turnCalls }