From d644db1975502d92427bfc7345ff322f4b5704fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 3 Oct 2026 23:03:02 +0200 Subject: [PATCH] Revert "feat(dashboard): send the glucose card in the owner's glucose unit" This reverts commit 9455b64375cce46da1e6e2695cb94f0838761281. --- docs/api/openapi.yaml | 6 +-- .../dashboard/summary/__tests__/route.test.ts | 26 ---------- src/app/api/dashboard/summary/route.ts | 10 +--- .../dashboard/__tests__/glucose-card.test.ts | 47 ------------------- src/lib/dashboard/glucose-card.ts | 39 --------------- src/lib/openapi/routes/dashboard.ts | 2 +- 6 files changed, 4 insertions(+), 126 deletions(-) delete mode 100644 src/lib/dashboard/__tests__/glucose-card.test.ts delete mode 100644 src/lib/dashboard/glucose-card.ts diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 05180b2c4..f40c76d79 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -71896,10 +71896,8 @@ components: anyOf: - type: string - type: "null" - description: "Explicit unit token. When set it wins over `unitKey`. Set on the `sleep` card (`h`, because its value is a - per-night total in hours rather than the canonical SLEEP_DURATION minutes) and, since v1.40.0, on the - `glucose` card: the record owner's glucose unit (`mg/dL` or `mmol/L`), with `latestValue`, `secondaryValue` - and `sparkline` converted to it (mmol/L to one decimal). Null on every other kind." + description: Explicit unit token, set only on the `sleep` card (`h`, because its value is a per-night total in hours + rather than the canonical SLEEP_DURATION minutes). Null on every other kind. sleepStages: anyOf: - type: object diff --git a/src/app/api/dashboard/summary/__tests__/route.test.ts b/src/app/api/dashboard/summary/__tests__/route.test.ts index b1c5dc30e..e61286e5b 100644 --- a/src/app/api/dashboard/summary/__tests__/route.test.ts +++ b/src/app/api/dashboard/summary/__tests__/route.test.ts @@ -561,32 +561,6 @@ describe("GET /api/dashboard/summary", () => { expect(glucose?.lastSeenAt).toBe(tenDaysAgo.toISOString()); }); - it("names the owner's glucose unit on the glucose card", async () => { - // Applied after the summary cache: the unit is the record's own choice - // and the card's numbers are in it (the conversion is pinned in - // `src/lib/dashboard/__tests__/glucose-card.test.ts`). - vi.mocked(getSession).mockResolvedValue({ - ...SESSION_OK, - user: { ...SESSION_OK.user, glucoseUnit: "mmol/L" }, - } as never); - vi.mocked(prisma.measurement.groupBy).mockResolvedValue([ - { - type: "BLOOD_GLUCOSE", - _count: { _all: 4 }, - _max: { measuredAt: new Date(Date.now() - 10 * 86_400_000) }, - }, - ] as never); - const res = await callGet(makeReq()); - const body = (await res.json()) as { - data: { metrics: Array<{ id: string; unit: string | null }> }; - }; - expect(body.data.metrics.find((m) => m.id === "glucose")?.unit).toBe( - "mmol/L", - ); - // Every other card keeps its own unit contract. - expect(body.data.metrics.find((m) => m.id === "weight")?.unit).toBeNull(); - }); - it("emits the sleep tile as the night TIME-ASLEEP total in hours, not one stage (v1.11.4)", async () => { // SLEEP_DURATION is stored one row per STAGE per night (minutes). The // tile must SUM the asleep stages of the latest night and convert to diff --git a/src/app/api/dashboard/summary/route.ts b/src/app/api/dashboard/summary/route.ts index 40d0f9994..26d76552f 100644 --- a/src/app/api/dashboard/summary/route.ts +++ b/src/app/api/dashboard/summary/route.ts @@ -75,8 +75,6 @@ import { } from "@/lib/dashboard/summary-reads"; import { resolveModuleMap } from "@/lib/modules/gate"; import { gateMetricCardsByModules } from "@/lib/dashboard/widget-modules"; -import { glucoseCardInDisplayUnit } from "@/lib/dashboard/glucose-card"; -import { resolveGlucoseUnit } from "@/lib/glucose"; import { summarizeSleepNights, reconstructSleepNights, @@ -146,9 +144,6 @@ interface MetricCard { * `latestValue` is a per-NIGHT total expressed in HOURS (a float), * not the canonical `SLEEP_DURATION` minutes — see the sleep block * below for why the night total replaced the single-stage value. - * v1.40.0 — the `glucose` tile sets it to the owner's glucose unit - * (`"mg/dL"` or `"mmol/L"`), with its values converted to match - * (`glucoseCardInDisplayUnit`, applied after the cache read). */ unit: string | null; /** @@ -330,10 +325,7 @@ export const GET = apiHandler(async () => { // it off. Filtering the cached body makes the toggle effective on the very // next request. const modules = await resolveModuleMap(user.id); - const glucoseUnit = resolveGlucoseUnit(user.glucoseUnit); - const metrics = gateMetricCardsByModules(body.metrics, modules).map((card) => - glucoseCardInDisplayUnit(card, glucoseUnit), - ); + const metrics = gateMetricCardsByModules(body.metrics, modules); annotate({ meta: { metrics_emitted: metrics.length, diff --git a/src/lib/dashboard/__tests__/glucose-card.test.ts b/src/lib/dashboard/__tests__/glucose-card.test.ts deleted file mode 100644 index 1257e2f64..000000000 --- a/src/lib/dashboard/__tests__/glucose-card.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { glucoseCardInDisplayUnit } from "../glucose-card"; - -const card = (over: Record = {}) => ({ - kind: "glucose", - latestValue: 101 as number | null, - secondaryValue: null as number | null, - sparkline: [90, 101, 126], - unit: null as string | null, - unitKey: "dashboard.metric.unit.glucose", - ...over, -}); - -describe("glucoseCardInDisplayUnit", () => { - it("converts the card to mmol/L and names the unit", () => { - expect(glucoseCardInDisplayUnit(card(), "mmol/L")).toMatchObject({ - latestValue: 5.6, - secondaryValue: null, - sparkline: [5, 5.6, 7], - unit: "mmol/L", - unitKey: "dashboard.metric.unit.glucose", - }); - }); - - it("keeps mg/dL values and names that unit too", () => { - expect(glucoseCardInDisplayUnit(card(), "mg/dL")).toMatchObject({ - latestValue: 101, - sparkline: [90, 101, 126], - unit: "mg/dL", - }); - }); - - it("leaves an empty card empty", () => { - expect( - glucoseCardInDisplayUnit( - card({ latestValue: null, sparkline: [] }), - "mmol/L", - ), - ).toMatchObject({ latestValue: null, sparkline: [], unit: "mmol/L" }); - }); - - it("passes every other card through untouched", () => { - const weight = card({ kind: "weight", latestValue: 80 }); - expect(glucoseCardInDisplayUnit(weight, "mmol/L")).toBe(weight); - }); -}); diff --git a/src/lib/dashboard/glucose-card.ts b/src/lib/dashboard/glucose-card.ts deleted file mode 100644 index e8ca5ab2b..000000000 --- a/src/lib/dashboard/glucose-card.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { convertGlucose, type GlucoseUnit } from "@/lib/glucose"; - -/** The fields of a dashboard summary card the glucose unit touches. */ -interface GlucoseConvertibleCard { - kind: string; - latestValue: number | null; - secondaryValue: number | null; - sparkline: number[]; - unit: string | null; -} - -/** - * Put the glucose card in the record owner's display unit. - * - * Glucose is stored in mg/dL. The summary card used to send that number with - * `unit: null` and a `unitKey` that reads "mg/dL", so a reader who chose - * mmol/L was shown 101 "mg/dL" on a client that honours the card. The card now - * carries its value, its secondary value and its sparkline in the chosen unit - * and names that unit in `unit`, the field that wins over `unitKey` (as the - * sleep card's `"h"` already does). Every other card passes through untouched. - * - * Applied after the summary cache is read, so changing the unit takes effect - * on the next request rather than after the cache window. - */ -export function glucoseCardInDisplayUnit( - card: T, - unit: GlucoseUnit, -): T { - if (card.kind !== "glucose") return card; - const show = (value: number | null) => - value === null ? null : convertGlucose(value, unit); - return { - ...card, - latestValue: show(card.latestValue), - secondaryValue: show(card.secondaryValue), - sparkline: card.sparkline.map((value) => convertGlucose(value, unit)), - unit, - }; -} diff --git a/src/lib/openapi/routes/dashboard.ts b/src/lib/openapi/routes/dashboard.ts index b006ee658..2779f7448 100644 --- a/src/lib/openapi/routes/dashboard.ts +++ b/src/lib/openapi/routes/dashboard.ts @@ -186,7 +186,7 @@ const dashboardMetricCard = z .string() .nullable() .describe( - "Explicit unit token. When set it wins over `unitKey`. Set on the `sleep` card (`h`, because its value is a per-night total in hours rather than the canonical SLEEP_DURATION minutes) and, since v1.40.0, on the `glucose` card: the record owner's glucose unit (`mg/dL` or `mmol/L`), with `latestValue`, `secondaryValue` and `sparkline` converted to it (mmol/L to one decimal). Null on every other kind.", + "Explicit unit token, set only on the `sleep` card (`h`, because its value is a per-night total in hours rather than the canonical SLEEP_DURATION minutes). Null on every other kind.", ), sleepStages: z .record(z.string(), z.number())