Merge pull request #243 from MBombeck/release/v1.9.3

Release v1.9.3 — medication card layout polish
This commit is contained in:
Marc-André Bombeck authored and GitHub committed 2026-06-02 21:08:33 +02:00
commit 789eef35c3
6 files changed
+41 -35

No files matched your search

+6
View File
@@ -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
+1 -1
View File
@@ -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.
+11 -1
View File
@@ -251,10 +251,20 @@ export async function expectMedicationOnList(
page: Page,
{ name, withNextDueChip }: { name: string; withNextDueChip: boolean },
): Promise<void> {
// 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) {
await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible();
await expect(main.getByText(/Nächste Einnahme:/i).first()).toBeVisible({
timeout: 10_000,
});
}
}
+1 -1
View File
@@ -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",
@@ -406,7 +406,7 @@ export function Glp1MedicationCard({
linkLabel={t("medications.openDetailPage")}
/>
<CardContent className="flex flex-1 flex-col space-y-3.5">
<CardContent className="space-y-3.5">
{/* 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. */}
<div className="min-h-[2.75rem] space-y-1 text-sm">
{medication.lastTakenAt && (
<p className="text-muted-foreground">
@@ -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 && (
<div className="mt-auto pt-3.5">
<MedicationIntakeActions
intakeLoading={intakeLoading}
onRecordIntake={recordIntake}
/>
</div>
<MedicationIntakeActions
intakeLoading={intakeLoading}
onRecordIntake={recordIntake}
/>
)}
</CardContent>
+11 -14
View File
@@ -311,7 +311,7 @@ export function MedicationCard({
linkLabel={t("medications.openDetailPage")}
/>
<CardContent className="flex flex-1 flex-col space-y-3.5">
<CardContent className="space-y-3.5">
{/* Status, last & next intake info */}
{currentWindowStatus.status && (
<MedicationStatusPill
@@ -323,9 +323,8 @@ export function MedicationCard({
{/* Next / last intake — wrapped in a fixed min-height slot so a
card missing its last-dose line keeps the same vertical
footprint as a sibling that renders both lines. Without the
reserved height the compliance bars and action row below would
start at a different Y card-to-card across the 2-col grid. */}
footprint as a sibling that renders both lines, so the dose rows
line up across the 2-col grid. */}
<div className="min-h-[2.75rem] space-y-3.5">
{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 && (
<div className="mt-auto pt-3.5">
<MedicationIntakeActions
intakeLoading={intakeLoading}
onRecordIntake={recordIntake}
/>
</div>
<MedicationIntakeActions
intakeLoading={intakeLoading}
onRecordIntake={recordIntake}
/>
)}
</CardContent>