The provider list rebuilt its own day set straight from the daily cache,
unioning unfiltered historical days with an already-filtered today. Per-provider
costs therefore counted every carried day whole while today honoured the name
filters, so the list could not be reconciled with the headline or the By Project
panel. #864 fixed the headline and left this deliberately untouched.
Reuse durable.days, which is the same union the headline is built from, already
narrowed by range, day selection and project filter. That is what the comment
above the buildDurablePeriod call already promised this section would do.
Providers whose entire spend is excluded do not vanish from the list: the
installed-but-zero backfill below still adds them at cost 0.
The tests added in #864 seeded today's live session at a fixed wall-clock
hour (12:00 local). The periods they build end at `new Date()`, and the
suite runs under TZ=UTC, so for any run before 12:00 UTC that timestamp
is in the FUTURE and the range filter correctly drops it. The live half
of the cache/live union then contributes nothing, and the one assertion
that needs a non-zero headline — the unattributed-cost footnote — fell
into renderOverview's "No usage found" early return and went red. Half
of every day was a failing window; a769b50 fixed the start-of-month
flake but this one survived it.
Verified by bisecting the fixture on the unpatched test: moving the
seeded hour from 12:00 to 01:00 (past, at a 03:38 UTC run) turns the
same 12 tests green, so the timestamp's position relative to `now` is
the whole cause.
- Seed the session a few minutes BEFORE now, clamped to today's
midnight, so it is always both inside today and already in the past.
- Stop the footnote test depending on the live parse at all: seed a
second, attributable cached day so the headline is non-zero from the
cache alone. The test now exercises the footnote instead of the
fixture's timing.
Two issues on top of the --project/--exclude durable-headline fix:
- sanitizeProjects dropped any project whose key is an Object.prototype member
name (constructor, valueOf, __proto__, ...). A project key is a directory
basename, so such a name is legitimate, and dropping it left the day's
per-project split summing to less than the day cost — so the sliced,
project-filtered headline silently lost that project's spend with no footnote.
The keys are written via setOwn (defineProperty), so keeping them is
pollution-safe; only the redundant `name in Object.prototype` guard is removed.
Regression test added (mutation-checked: fails without the guard removed).
- The new project-filter tests seeded a carried day 10 days ago but ranged over
the calendar month, so within the first 10 days of a month that day fell out
of range and the tests went red. Replaced with a fixed 20-day window that
always spans the seeded day.
The durable headline unions the carry-forward daily cache with today's
live parse. Cached days were sliced to the requested provider
(sliceDayToProvider) but never to the requested project, so a
--project/--exclude query counted every historical day WHOLE while the
detail panels — By Project / By Activity / By Model, all built from the
name-filtered live parse — left the filtered projects out. The Overview
total and the panels below it could not be reconciled; on a real
one-week corpus the headline ran $2,428.85 over the By Project sum,
which is exactly the excluded projects' spend.
Add sliceDayToProject, the project-level counterpart of
sliceDayToProvider, and apply it to the cache-sourced days. Cost, calls,
sessions and savings come out of the per-project day stats the cache has
carried since v15, so they stay exact and still include days whose
session files have expired. Provider slices carry their own project
split, so --provider on top of a project filter stays consistent.
Fields the cache has no per-project split for are handled explicitly
rather than reported as the filtered projects' own:
- tokens/models/categories come from the (project-filtered) live parse
when a project filter is active, which is exact for every surviving
session and matches the panels that read the same parse;
- days, or provider slices, carried from before v15 have no project
split at all and cannot be attributed, so they contribute nothing and
their cost is returned as unattributedCostUSD and footnoted by the
terminal overview instead of being folded into a filtered total.