From 511d29ab293e693cd8daf0d5077e305f2ca910c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 14:29:45 +0200 Subject: [PATCH] fix(medications): purple dose accent shared across all medication kinds The generic medication card surfaces the upcoming schedule dose with a `font-medium text-purple-400` accent so titration overrides on the schedule visually pop out of the muted next-intake line. The GLP-1 card omitted the accent entirely. Mirror the same accent on the GLP-1 card so the only difference between Mounjaro and Ramipril detail surfaces is the data being shown, not the styling treatment. The schedule.dose still rides inside the existing next-injection paragraph (hidden below sm: like the generic card so the narrow-viewport row stays one line). --- src/app/__tests__/insights-b3-wiring.test.ts | 15 ++++++- src/app/api/analytics/__tests__/route.test.ts | 45 +++++++++++++++++-- .../medications/glp1-medication-card.tsx | 16 ++++++- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/app/__tests__/insights-b3-wiring.test.ts b/src/app/__tests__/insights-b3-wiring.test.ts index feb706aa4..27a44a02a 100644 --- a/src/app/__tests__/insights-b3-wiring.test.ts +++ b/src/app/__tests__/insights-b3-wiring.test.ts @@ -58,8 +58,19 @@ describe("v1.4.20 B3 — /insights mounts CorrelationRow + TrendsRow", () => { }); describe("v1.4.20 B3 — /api/analytics surfaces correlations", () => { + // v1.4.37 W2 — the inline correlation builder relocated to + // `src/lib/analytics/correlations-fast-path.ts`. The route now + // delegates through `computeCorrelationHypothesesFastPath`; the + // three Pearson runner imports live alongside the new dispatcher. + // These guards continue to pin the same load-bearing surface — we + // just look up the corresponding file. + const CORRELATIONS_FAST_PATH = join( + ROOT, + "src/lib/analytics/correlations-fast-path.ts", + ); + it("imports the three correlation runners", () => { - const src = load(ANALYTICS_ROUTE_PATH); + const src = load(CORRELATIONS_FAST_PATH); expect(src).toContain("correlateBpCompliance"); expect(src).toContain("correlateMoodPulse"); expect(src).toContain("correlateWeightWeekday"); @@ -72,7 +83,7 @@ describe("v1.4.20 B3 — /api/analytics surfaces correlations", () => { }); it("annotates the wide event with per-hypothesis status", () => { - const src = load(ANALYTICS_ROUTE_PATH); + const src = load(CORRELATIONS_FAST_PATH); expect(src).toMatch( /annotate\(\{[\s\S]*correlations:[\s\S]*bpCompliance:[\s\S]*moodPulse:[\s\S]*weightWeekday:/, ); diff --git a/src/app/api/analytics/__tests__/route.test.ts b/src/app/api/analytics/__tests__/route.test.ts index a3d3d2ba5..eaff09d66 100644 --- a/src/app/api/analytics/__tests__/route.test.ts +++ b/src/app/api/analytics/__tests__/route.test.ts @@ -19,12 +19,22 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@/lib/db", () => ({ prisma: { - measurement: { findMany: vi.fn() }, + // v1.4.37 W2 — `ensureUserRollupsFresh` pokes `measurement.findFirst` + // for the newest-measurement watermark; mock both shapes. + measurement: { + findMany: vi.fn(), + findFirst: vi.fn().mockResolvedValue(null), + }, moodEntry: { findMany: vi.fn() }, medicationIntakeEvent: { findMany: vi.fn() }, medication: { findMany: vi.fn() }, - // v1.4.33 C1 — slim summaries slice runs through `$queryRaw`. - $queryRaw: vi.fn(), + // v1.4.33 C1 — slim summaries slice runs through `$queryRaw`. The + // v1.4.36 per-type coverage probe and the v1.4.37 default-slice + // probe also ride `$queryRaw`. Default to an empty coverage map so + // the route falls back to the live aggregator branches and the + // assertions stay byte-shape stable. + $queryRaw: vi.fn().mockResolvedValue([]), + $queryRawUnsafe: vi.fn().mockResolvedValue([]), // v1.4.35 — slim slice also reads DAY buckets; the freshness // watermark inside `ensureUserRollupsFresh` pokes `findFirst`. // Default both to empty so the parity check falls back to live @@ -32,7 +42,13 @@ vi.mock("@/lib/db", () => ({ measurementRollup: { findMany: vi.fn().mockResolvedValue([]), findFirst: vi.fn().mockResolvedValue(null), + deleteMany: vi.fn().mockResolvedValue({ count: 0 }), + upsert: vi.fn().mockResolvedValue({}), }, + $transaction: vi.fn().mockImplementation(async (queries: unknown[]) => { + if (Array.isArray(queries)) return Promise.all(queries); + return undefined; + }), }, })); @@ -119,6 +135,29 @@ beforeEach(() => { vi.mocked(prisma.measurementRollup.findFirst).mockResolvedValue( null as never, ); + vi.mocked(prisma.measurementRollup.deleteMany).mockResolvedValue( + { count: 0 } as never, + ); + vi.mocked(prisma.measurementRollup.upsert).mockResolvedValue( + {} as never, + ); + // v1.4.37 W2 — `ensureUserRollupsFresh` reads `measurement.findFirst`; + // the per-type coverage probe + the rollup-recompute aggregator ride + // `$queryRaw` / `$queryRawUnsafe`. Default to empty so the route + // falls back to the live fast-path branches and the assertions stay + // byte-shape stable. + vi.mocked(prisma.measurement.findFirst).mockResolvedValue(null as never); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(prisma.$queryRaw as any).mockResolvedValue([]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(prisma.$queryRawUnsafe as any).mockResolvedValue([]); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + vi.mocked(prisma.$transaction as any).mockImplementation( + async (queries: unknown) => { + if (Array.isArray(queries)) return Promise.all(queries); + return undefined; + }, + ); }); afterEach(() => { diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index dd409e7ef..755684cc8 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -397,7 +397,21 @@ export function Glp1MedicationCard({ })}

)} -

{nextInjectionLabel()}

+

+ {nextInjectionLabel()} + {/* v1.4.37 W4b — purple dose accent on the upcoming + schedule dose, byte-equivalent with the generic + medication card. Schedule.dose can override the + medication-level dose during titration, so we surface + it here when set. Hidden below sm: to keep the narrow + viewport row tight, matching the generic card. */} + {schedule?.dose && ( + + {" "} + — {schedule.dose} + + )} +

{/* Rotation hint — only when we have a last + recommended site