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