fix(dashboard): paired-metric tiles drop to text-2xl so 131/85 stays visible (v1.4.49.4)

Marc reported the BD (Sys) tile rendering `13… mmHg` — the
systolic / diastolic pair `131/85` outgrew the `text-3xl` footprint on
the narrowest grid columns and the value-row's `truncate` ellipsis
chopped the most important part of the tile (the number itself).

The other tiles in the strip (weight, pulse, glucose, …) carry a
single value that never threatens the column-width budget on its own,
so they keep `text-3xl`. Only paired-metric tiles step down to
`text-2xl`; `131/85 mmHg` at `text-2xl tabular-nums tracking-tight` is
~84 px wide vs ~140 px for the narrowest 2-column grid cell — fits
with the unit and trend arrow at every viewport. `truncate` stays as
a defence-in-depth safety net for outlier values.

`cn()` arg ordering tightened so `tailwind-merge` keeps the explicit
`leading-none`. Tailwind v4's `text-3xl` / `text-2xl` carry their own
default `line-height`, and the last-wins dedup rule otherwise dropped
`leading-none` and broke the W20a across-tile baseline alignment that
`trend-card-baseline-alignment.test.tsx` pins.

Also fixes a pre-existing date-boundary test flake:
`src/app/api/medications/intake/__tests__/route.test.ts`'s
`reads the rollup tier when coverage is present` computed `todayKey`
from UTC components, but the route's `readMedicationCompliance` uses
the user's timezone (Europe/Berlin in the test fixture). At every
22:00 — 24:00 UTC window — once the Berlin clock crossed midnight —
the two computations diverged by one calendar day, the mock returned
data for the wrong day, and `today.scheduled` came back zero instead
of three. The test now derives `todayKey` from the same Berlin zone
the route uses.

Tests: 5270 unit + integration pass, lint clean, typecheck clean.
This commit is contained in:
Marc-André Bombeck committed 2026-05-24 00:21:41 +02:00
1 parent fe4060e84b
commit 49c9b425bb
4 files changed
+52 -6

No files matched your search

+10
View File
@@ -1,5 +1,15 @@
# Changelog
## [1.4.49.4] — 2026-05-24 — BD-tile number truncation + medication-compliance test tz drift
Marc reported the dashboard BD (Sys) tile rendering `"13… mmHg"` — the systolic / diastolic pair (`131/85`) was wider than the narrowest grid column and the value-row's `truncate` ellipsis chopped the most important part of the tile (the number itself).
### Fixed
- `src/components/charts/trend-card.tsx` — paired-metric tiles (BP systolic/diastolic) now render the value at `text-2xl` (one Tailwind step down from `text-3xl`). Single-value tiles (weight, pulse, glucose, …) keep `text-3xl` because their value never threatens the column-width budget on its own. The `truncate` class stays as a defence-in-depth safety net so an outlier value (e.g. `200/120 mmHg` plus a future trend caption) still clips gracefully rather than overflowing the card boundary.
- `cn()` argument ordering tightened so `tailwind-merge` keeps the explicit `leading-none` — Tailwind v4's `text-3xl` / `text-2xl` carry their own default `line-height`, and the last-wins dedup rule would otherwise drop the explicit `leading-none` and break the W20a across-tile baseline alignment. The `<TrendCard>` baseline-alignment tests pin the contract.
- `src/app/api/medications/intake/__tests__/route.test.ts` (one pre-existing test) computed its `todayKey` from UTC date components but the route's `readMedicationCompliance` uses the user's timezone (Europe/Berlin in the test fixture). At every nightly 22:00 — 24:00 UTC window — once the Berlin clock crossed midnight — the two computations diverged by one calendar day and the rollup-tier read returned zero for "today". The test now derives `todayKey` from the same `Europe/Berlin` zone the route uses; the assertion stays the same.
## [1.4.49.3] — 2026-05-23 — Full i18n call-site audit + 28 stale missing keys filled
Marc reported additional raw key strings (`notifications.eventMoodReminder`, `notifications.eventMoodReminderDesc`) reaching the UI after v1.4.49.2 had landed the relative-time fix. The narrow `{count}`-call audit that drove v1.4.49.2 had missed every dynamic key construction; the full exhaustive sweep below picked up 28 keys called from real code that had never existed in any locale bundle.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "healthlog",
"version": "1.4.49.3",
"version": "1.4.49.4",
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
"license": "AGPL-3.0-only",
"homepage": "https://healthlog.dev",
@@ -363,12 +363,26 @@ describe("v1.4.39 W-MED — compliance rollup read swap", () => {
// runs. The pre-fix shape hard-coded `2026-05-18`, which fell out
// of the trailing-7-day window on every subsequent wall-clock
// day and silently shifted the body.data tail off the seed.
//
// v1.4.49.4 — compute todayKey in the SAME zone the route uses
// (`readMedicationCompliance` → `userDayKey(now, safeTz)`), not in
// UTC. SESSION_OK carries no `timezone`, so the route falls
// through to `DEFAULT_TIMEZONE = "Europe/Berlin"`. The pre-fix
// UTC computation diverged from the route's Berlin computation by
// one calendar day during the 22:00-24:00 UTC window every night,
// causing the test to fail deterministically once the clock
// crossed midnight Berlin while still on the previous UTC day.
const todayKey = (() => {
const now = new Date();
const y = now.getUTCFullYear();
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
const d = String(now.getUTCDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
const fmt = new Intl.DateTimeFormat("en-CA", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
});
// `en-CA` short-date format is `YYYY-MM-DD`, matching the
// route's `userDayKey` output (see `src/lib/tz/resolver.ts`).
return fmt.format(now);
})();
vi.mocked(prisma.medicationComplianceRollup.findMany).mockResolvedValue([
{ day: todayKey, scheduled: 3, taken: 2, skipped: 1 },
+23 -1
View File
@@ -268,7 +268,29 @@ export function TrendCard({
className="mt-2 flex min-w-0 items-baseline gap-x-1.5"
data-slot="trend-card-value-row"
>
<span className="min-w-0 truncate text-3xl leading-none font-semibold tracking-tight tabular-nums">
<span
className={cn(
"min-w-0 truncate font-semibold tracking-tight tabular-nums",
// v1.4.49.4 — paired values (BP systolic/diastolic) render
// as `131/85` and outgrow the `text-3xl` footprint on the
// narrowest grid columns, ellipsis-clipping to "13…" on the
// dashboard. The truncate stays as a defence-in-depth safety
// net, but paired tiles drop one font step (text-3xl →
// text-2xl) so the full pair stays visible at every column
// width. Single-value tiles (weight, pulse, glucose, …)
// keep text-3xl because their value never threatens the
// budget on its own.
secondary ? "text-2xl" : "text-3xl",
// `leading-none` must come AFTER the font-size class in the
// cn() arg list. Tailwind v4 `text-3xl` / `text-2xl` ship
// their own default `line-height`, so `twMerge` would
// otherwise drop the explicit `leading-none` per the
// last-wins rule and break the W20a across-tile baseline
// alignment (digits line up at the same y-coordinate
// across every tile in the strip).
"leading-none",
)}
>
{latest !== null ? renderPair(latest, secondary?.latest) : "—"}
</span>
<span className="text-muted-foreground shrink-0 text-sm tabular-nums">