From 437a627a66b036f665063a2fae9004b45ef2f609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 5 Jun 2026 13:49:01 +0200 Subject: [PATCH] perf(dashboard): reuse the probed coverage map in the summaries slice The snapshot builder probed rollup coverage up front to gate the thick extras phase, then called computeSummariesSlice which re-ran the identical probeRollupCoverage query. Thread the already-probed map into the slice as an optional argument so the snapshot's hot path issues the probe once. The argument is optional with the internal probe as the fallback, so every other caller is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/analytics/summaries-slice.ts | 13 +++++++++++- src/lib/dashboard/__tests__/snapshot.test.ts | 22 ++++++++++++++++++++ src/lib/dashboard/snapshot.ts | 4 +++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/lib/analytics/summaries-slice.ts b/src/lib/analytics/summaries-slice.ts index ece84d573..fb28f6e5a 100644 --- a/src/lib/analytics/summaries-slice.ts +++ b/src/lib/analytics/summaries-slice.ts @@ -70,6 +70,7 @@ import { import { isFullyCovered, probeRollupCoverage, + type RollupCoverageMap, } from "@/lib/rollups/measurement-coverage"; import { readBestGranularityRollups } from "@/lib/rollups/measurement-read-wmy"; import { @@ -220,6 +221,16 @@ export interface SummariesSlice { export async function computeSummariesSlice( userId: string, + /** + * v1.12.7 A5 — optional pre-probed coverage map. The dashboard + * snapshot already calls `probeRollupCoverage` up front to decide + * whether to run the thick `extras` phase, then called this slice + * which re-probed the identical query. Threading the map in lets the + * slice reuse it and saves one round-trip on the snapshot's hot path. + * Omit it (every other caller) to keep the internal probe as the + * fallback — byte-stable for those callers. + */ + precomputedCoverage?: RollupCoverageMap, ): Promise { // v1.4.37.1 hotfix — fire-and-forget. See `src/app/api/analytics/route.ts` // for the full rationale: awaiting this on the read path can stall @@ -242,7 +253,7 @@ export async function computeSummariesSlice( // only take the rollup path when EVERY type the user has logged is // covered. Partial coverage falls back to the live aggregate so the // brand-new-type case stays correct. - const coverage = await probeRollupCoverage(userId); + const coverage = precomputedCoverage ?? (await probeRollupCoverage(userId)); if (isFullyCovered(coverage)) { return withSleepNightTotals(userId, await computeFromRollups(userId)); } diff --git a/src/lib/dashboard/__tests__/snapshot.test.ts b/src/lib/dashboard/__tests__/snapshot.test.ts index ff331f27f..b8fa2643f 100644 --- a/src/lib/dashboard/__tests__/snapshot.test.ts +++ b/src/lib/dashboard/__tests__/snapshot.test.ts @@ -145,6 +145,28 @@ describe("buildDashboardSnapshot — envelope shape", () => { expect(snap.layout).toBeDefined(); expect(typeof snap.generatedAt).toBe("string"); }); + + it("probes rollup coverage once and threads it into the summaries slice (A5)", async () => { + const coverageMap = new Map([["WEIGHT", true]]); + probeRollupCoverage.mockResolvedValue(coverageMap); + isFullyCovered.mockReturnValue(true); + computeBpInTargetFastPath.mockResolvedValue({ + last7Days: { pct: 70 }, + last30Days: { pct: 80 }, + allTime: { pct: 75 }, + priorMonth: { pct: 60 }, + priorYear: { pct: 50 }, + }); + + await buildDashboardSnapshot(fakePrisma, baseUser()); + + // The snapshot probes coverage exactly once up front — the slice no + // longer re-runs the identical probe internally. + expect(probeRollupCoverage).toHaveBeenCalledTimes(1); + // The already-probed map is passed as the slice's second argument so + // it reuses it instead of probing again. + expect(computeSummariesSlice).toHaveBeenCalledWith("user-1", coverageMap); + }); }); describe("buildDashboardSnapshot — two-phase null extras", () => { diff --git a/src/lib/dashboard/snapshot.ts b/src/lib/dashboard/snapshot.ts index a6b4218fe..71ec5be29 100644 --- a/src/lib/dashboard/snapshot.ts +++ b/src/lib/dashboard/snapshot.ts @@ -609,7 +609,9 @@ export async function buildDashboardSnapshot( const warm = isFullyCovered(coverage); const [slim, mood, extras, flags] = await Promise.all([ - time("summaries", () => computeSummariesSlice(user.id)), + // A5 — reuse the coverage map already probed above so the slice + // doesn't re-run the identical `probeRollupCoverage` query. + time("summaries", () => computeSummariesSlice(user.id, coverage)), time("mood", () => buildMoodBlock(prisma, user.id)), warm ? time("extras", () => buildExtras(prisma, user, userTz, coverage))