From 888030fce37542d39cafe64c2adab45087f88ba8 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Sun, 19 Apr 2026 03:07:54 -0700 Subject: [PATCH] fix: recompute yesterday in daily cache to prevent stale menubar data The daily cache never re-processed yesterday once cached, so a mid-day run would freeze partial cost/call data permanently. The "All" provider path in menubar-json relied on this cache, causing the menubar to show wildly incorrect numbers while per-provider views (which parse fresh) were correct. Now yesterday is evicted and recomputed on every run, and addNewDays upserts instead of skipping duplicates as defense-in-depth. --- src/cli.ts | 10 ++++++++++ src/daily-cache.ts | 9 +++------ tests/daily-cache.test.ts | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 059c997..494e313 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -339,8 +339,18 @@ program // The daily cache is provider-agnostic: always backfill it from .all so subsequent // provider-filtered reads can derive per-provider cost+calls from DailyEntry.providers. + // Yesterday is always recomputed: it may have been cached mid-day with partial data. const cache = await withDailyCacheLock(async () => { let c = await loadDailyCache() + + // Evict yesterday (and any stale future entries) so the gap fill recomputes them. + const hadYesterday = c.days.some(d => d.date >= yesterdayStr) + if (hadYesterday) { + const freshDays = c.days.filter(d => d.date < yesterdayStr) + const latestFresh = freshDays.length > 0 ? freshDays[freshDays.length - 1].date : null + c = { ...c, days: freshDays, lastComputedDate: latestFresh } + } + const gapStart = c.lastComputedDate ? new Date(new Date(`${c.lastComputedDate}T00:00:00.000Z`).getTime() + MS_PER_DAY) : new Date(todayStart.getTime() - BACKFILL_DAYS * MS_PER_DAY) diff --git a/src/daily-cache.ts b/src/daily-cache.ts index 1320aa6..11903bf 100644 --- a/src/daily-cache.ts +++ b/src/daily-cache.ts @@ -91,14 +91,11 @@ export async function saveDailyCache(cache: DailyCache): Promise { } export function addNewDays(cache: DailyCache, incoming: DailyEntry[], newestDate: string): DailyCache { - const seen = new Set(cache.days.map(d => d.date)) - const merged = [...cache.days] + const byDate = new Map(cache.days.map(d => [d.date, d])) for (const day of incoming) { - if (seen.has(day.date)) continue - seen.add(day.date) - merged.push(day) + byDate.set(day.date, day) } - merged.sort((a, b) => a.date.localeCompare(b.date)) + const merged = Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date)) const nextLast = cache.lastComputedDate && cache.lastComputedDate > newestDate ? cache.lastComputedDate : newestDate diff --git a/tests/daily-cache.test.ts b/tests/daily-cache.test.ts index b1741e3..e89d65d 100644 --- a/tests/daily-cache.test.ts +++ b/tests/daily-cache.test.ts @@ -117,7 +117,7 @@ describe('addNewDays', () => { expect(updated.lastComputedDate).toBe('2026-04-10') }) - it('skips days already present in the cache (first write wins)', () => { + it('replaces existing days with incoming data (last write wins)', () => { const base: DailyCache = { version: DAILY_CACHE_VERSION, lastComputedDate: '2026-04-08', @@ -125,7 +125,7 @@ describe('addNewDays', () => { } const updated = addNewDays(base, [emptyDay('2026-04-08', 99)], '2026-04-08') const aprilEight = updated.days.find(d => d.date === '2026-04-08')! - expect(aprilEight.cost).toBe(5) + expect(aprilEight.cost).toBe(99) }) it('does not regress lastComputedDate if incoming newestDate is older', () => {