From 26fb461cc79aa19d556b6d571f39fa40e9a1fd22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 20:36:39 +0200 Subject: [PATCH 1/4] fix(medications): drop the bottom-pinned action gap on the cards The v1.9.0 equal-height change set the card content to a flex column and pinned the action buttons to the bottom with mt-auto. On a shorter card next to a taller one in the grid, that opened an empty gap between the content and the buttons. The cards already stretch to a shared height via the grid, so the buttons can sit directly after the content: drop the flex column and the mt-auto wrapper on both card variants. The reserved dose-row slot stays, so the dose lines still align across the grid; only the gap is gone. --- .../medications/glp1-medication-card.tsx | 29 +++++++------------ .../medications/medication-card.tsx | 25 +++++++--------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index deb0c3d6d..876e5fe03 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -406,7 +406,7 @@ export function Glp1MedicationCard({ linkLabel={t("medications.openDetailPage")} /> - + {/* Take-now / overdue / very-overdue pill, shared with the generic medication card. The GLP-1 card historically omitted this row, which made Mounjaro feel different from @@ -423,13 +423,8 @@ export function Glp1MedicationCard({ {/* Injection state — last + next. The reserved min-height keeps a card whose last-injection line is absent the same vertical footprint as one that renders both lines, matching the generic - medication card's reserved last/next slot. This — together with - `h-full` + `mt-auto` — equalises the card height and pins a - shared bottom action baseline across the grid row. Note it does - NOT align the compliance bars row-for-row with a generic - sibling: the GLP-1-only rotation-hint block below pushes the - bars down by its own height, so only the header, the reserved - slot, and the pinned action row share a Y. */} + medication card's reserved last/next slot, so the dose rows line + up across the grid. */}
{medication.lastTakenAt && (

@@ -503,17 +498,15 @@ export function Glp1MedicationCard({ {/* Primary actions row — shared with the generic medication card. The GLP-1-specific side-effect quick-log lives in the header-actions overflow (kebab), not this row, so Mounjaro - and Ramipril share the canonical two-button primary row. - Pinned to the bottom with `mt-auto` so the action baseline is - shared across every card in the grid row, identical to the - generic medication card. */} + and Ramipril share the canonical two-button primary row. The + row sits directly after the content (the card still fills the + grid cell via h-full, but the actions are not pushed to the + bottom — that left a void between the content and the buttons). */} {medication.active && ( -

- -
+ )} diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index 26a2c9a11..407cb6442 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -311,7 +311,7 @@ export function MedicationCard({ linkLabel={t("medications.openDetailPage")} /> - + {/* Status, last & next intake info */} {currentWindowStatus.status && ( {nextSchedule && currentWindowStatus.status !== "in_window" && @@ -409,17 +408,15 @@ export function MedicationCard({ /> )} - {/* Quick actions — primary buttons of the medication card. - Pinned to the bottom with `mt-auto` so the action baseline is - shared across every card in the grid row regardless of how many - optional rows above it rendered. */} + {/* Quick actions — primary buttons of the medication card. They sit + directly after the content (the card still fills the grid cell via + h-full, but the actions are not pushed to the bottom — that left a + void between the content and the buttons on shorter cards). */} {medication.active && ( -
- -
+ )}
From 7b3094eeef3dbf46b15f270b1ce11ca330268d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 20:36:40 +0200 Subject: [PATCH 2/4] =?UTF-8?q?chore(release):=20v1.9.3=20=E2=80=94=20medi?= =?UTF-8?q?cation=20card=20layout=20polish?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ docs/api/openapi.yaml | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8de28c26..b81eb79a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.9.3] — 2026-06-02 — Medication card layout polish + +### Fixed + +- **The medication cards look right again.** A recent alignment change pushed each card's action buttons to the very bottom of the cell, which left an empty gap between a shorter card's content and its buttons when it sat next to a taller card in the grid. The buttons now sit directly under the card's content again; the cards still share a height and the dose rows still line up, without the gap. + ## [1.9.2] — 2026-06-02 — Document the medication compliance endpoint ### Changed diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index ffda5e6e9..4aac88a9c 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.1.0 info: title: HealthLog API - version: 1.9.2 + version: 1.9.3 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. diff --git a/package.json b/package.json index 6bf1c3470..6feb7fcb2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.9.2", + "version": "1.9.3", "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", From 0c8506d7d4351e71c47e5f79e210f20a754c4380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 20:52:28 +0200 Subject: [PATCH 3/4] test(e2e): give the next-due chip assertion the 10s budget the name has The medication-list next-due line is computed client-side from the schedule after hydration, so on a loaded CI runner it can appear a beat after the medication name. The chip assertion used the default 5s timeout while the name assertion above it uses 10s; align them so the check isn't racing hydration. --- e2e/medications-wizard-helpers.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e/medications-wizard-helpers.ts b/e2e/medications-wizard-helpers.ts index eba4a9f2a..8d2543581 100644 --- a/e2e/medications-wizard-helpers.ts +++ b/e2e/medications-wizard-helpers.ts @@ -255,6 +255,11 @@ export async function expectMedicationOnList( const main = page.locator("main"); await expect(main.getByText(name).first()).toBeVisible({ timeout: 10_000 }); if (withNextDueChip) { - await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible(); + // The next-due line is computed client-side from the schedule after + // hydration, so it can land a beat after the name on a loaded runner; + // match the name assertion's 10s budget rather than the default 5s. + await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible({ + timeout: 10_000, + }); } } From e7f406ecc78fe47d33ff5d732ec5eb52b555b553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Tue, 2 Jun 2026 21:02:42 +0200 Subject: [PATCH 4/4] test(e2e): pin the clock so the medication next-due chip is deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recurring medication stub schedules an 08:00-09:00 window and the card derives its header from the real clock. When CI ran inside that window the card showed the in-window 'take now' pill and correctly suppressed the 'Nächste Einnahme:' next-due line, so the chip assertion failed depending on wall-clock time. Pin the clock to a fixed afternoon before rendering the list, putting the morning window in the past so the next-due line always renders. --- e2e/medications-wizard-helpers.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/e2e/medications-wizard-helpers.ts b/e2e/medications-wizard-helpers.ts index 8d2543581..2b99cb842 100644 --- a/e2e/medications-wizard-helpers.ts +++ b/e2e/medications-wizard-helpers.ts @@ -251,13 +251,18 @@ export async function expectMedicationOnList( page: Page, { name, withNextDueChip }: { name: string; withNextDueChip: boolean }, ): Promise { + // Pin the clock to a fixed afternoon before rendering the list. The + // recurring stub schedules an 08:00–09:00 window; the card derives its + // header from the real clock, so when CI happens to run inside that + // window the card shows the "take now" (in_window) pill and the + // "Nächste Einnahme:" next-due line is correctly suppressed — making the + // chip assertion below depend on the wall-clock. A fixed afternoon puts + // the morning window in the past so the next-due line always renders. + await page.clock.setFixedTime(new Date("2026-06-04T13:00:00Z")); await page.goto("/medications", { waitUntil: "domcontentloaded" }); const main = page.locator("main"); await expect(main.getByText(name).first()).toBeVisible({ timeout: 10_000 }); if (withNextDueChip) { - // The next-due line is computed client-side from the schedule after - // hydration, so it can land a beat after the name on a loaded runner; - // match the name assertion's 10s budget rather than the default 5s. await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible({ timeout: 10_000, });