mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
chore(misc): auth padding, daily-briefing links, tab fade, trends min-h, list metadata, passkey badge
v1.4.27 MB7 — residual surface polish bundled into a single commit: - CF-61: the auth login + register cards drop `p-8` to `p-6 sm:p-8` so Galaxy Fold / Pixel 5 keep the card breathable instead of surrendering half the visible viewport to padding. - CF-68: each daily-briefing key-finding row wraps in a `<Link>` to the matching routed sub-page so the whole card is tappable on mobile. Metrics without a dedicated sub-page (HRV, resting HR, steps, …) render the legacy static row. - CF-70: the sleep-stage window-toggle row bumps from `gap-1` to `gap-1.5` so the active pill border doesn't fuse with the inactive neighbour on Pixel 5. - CF-71: the trends-row card `min-h-[300px]` floor moves behind a `md:` gate so mobile single-column doesn't eat dead space below a short chart. - CF-72: the insights tab-strip paints a right-edge fade gradient on `<sm` so the user reads "there's more to scroll" when the pill row overflows. - CF-46: the measurement-list filter row stacks vertically on `<sm` so the `w-48` SelectTrigger doesn't crowd the count caption to a 2-line wrap. - CF-76: the measurement-list mobile-row badges bump from `text-[10px]` to `text-[11px]` so the metadata holds the legibility floor. - CF-75: the passkey mobile card promotes the device-type from plain text to an outline Badge so the chip row reads consistent with the desktop table column. - MA2-F8: the medikamente page card header wraps the title in `min-w-0` + `truncate` so long medication names don't push the streak badge off the card on Galaxy Fold.
This commit is contained in:
1 parent
ab4529fcb3
commit
0b3f9e3e2c
9 files changed
+148
-26
No files matched your search
@@ -124,7 +124,11 @@ export default function LoginPage() {
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-sm flex-col gap-4">
|
||||
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
|
||||
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
|
||||
auth card breathes on Galaxy Fold / Pixel 5 without stealing
|
||||
half the visible viewport with padding. Tablet+ keeps the
|
||||
generous 32 px padding. */}
|
||||
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<Logo className="text-primary" size={28} />
|
||||
|
||||
@@ -65,7 +65,11 @@ export default function RegisterPage() {
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="border-border bg-card rounded-xl border p-8 shadow-lg shadow-black/20">
|
||||
{/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the
|
||||
auth card breathes on Galaxy Fold / Pixel 5 without stealing
|
||||
half the visible viewport with padding. Tablet+ keeps the
|
||||
generous 32 px padding. */}
|
||||
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">
|
||||
<div className="flex flex-col items-center gap-3 text-center">
|
||||
<div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded-lg">
|
||||
<Logo className="text-primary" size={28} />
|
||||
|
||||
@@ -154,15 +154,22 @@ export default function InsightsMedikamentePage() {
|
||||
return (
|
||||
<Card key={med.id}>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Pill className="text-dracula-orange h-4 w-4" />
|
||||
<CardTitle className="text-sm font-medium">
|
||||
{/* v1.4.27 MB7 / MA2-F8 — `min-w-0` on the title
|
||||
wrapper + `truncate` on the CardTitle keeps long
|
||||
medication names (e.g. "Semaglutid Wegovy 0.5 mg
|
||||
weekly") from pushing the streak badge off the
|
||||
card on Galaxy Fold / Pixel 5. The Pill icon
|
||||
+ CardTitle pair shrinks to fit; the badge stays
|
||||
on the right at its natural width. */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Pill className="text-dracula-orange h-4 w-4 shrink-0" />
|
||||
<CardTitle className="truncate text-sm font-medium">
|
||||
{med.name}
|
||||
</CardTitle>
|
||||
</div>
|
||||
{med.streak > 0 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Badge variant="outline" className="shrink-0 text-xs">
|
||||
{t("insights.dayStreak", { count: med.streak })}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Activity,
|
||||
FileText,
|
||||
@@ -30,6 +31,33 @@ import type {
|
||||
DailyBriefingKeyFinding,
|
||||
} from "@/lib/ai/schema";
|
||||
|
||||
/**
|
||||
* v1.4.27 MB7 / CF-68 — per-metric routing target for the briefing
|
||||
* key findings. Maps the schema's `sourceMetric` discriminator to the
|
||||
* sub-page slug that owns the deeper view. Metrics that don't have a
|
||||
* dedicated routed sub-page yet (hrv, resting_hr, steps, …) fall
|
||||
* through to `null` and render as a plain row (no link wrap).
|
||||
*/
|
||||
const METRIC_HREF: Record<DailyBriefingKeyFinding["sourceMetric"], string | null> = {
|
||||
bp: "/insights/blutdruck",
|
||||
weight: "/insights/gewicht",
|
||||
pulse: "/insights/puls",
|
||||
mood: "/insights/stimmung",
|
||||
compliance: "/insights/medikamente",
|
||||
// Apple Health / GLP-1 additive metrics — no dedicated sub-page yet.
|
||||
// They render as static rows; v1.4.28 can wire each as they ship.
|
||||
hrv: null,
|
||||
sleep: "/insights/schlaf",
|
||||
resting_hr: null,
|
||||
steps: null,
|
||||
active_energy: null,
|
||||
flights: null,
|
||||
distance: null,
|
||||
vo2_max: "/insights/puls",
|
||||
body_temp: null,
|
||||
glp1_plateau: "/insights/medikamente",
|
||||
};
|
||||
|
||||
/**
|
||||
* v1.4.20 phase B1 — full-width Daily Briefing card.
|
||||
*
|
||||
@@ -128,11 +156,15 @@ function DeltaBadge({
|
||||
|
||||
function KeyFindingRow({ finding }: { finding: DailyBriefingKeyFinding }) {
|
||||
const Icon = METRIC_ICON[finding.sourceMetric];
|
||||
return (
|
||||
<div
|
||||
data-slot="daily-briefing-finding"
|
||||
className="border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3"
|
||||
>
|
||||
const href = METRIC_HREF[finding.sourceMetric];
|
||||
// v1.4.27 MB7 / CF-68 — when the briefing's `sourceMetric` maps to
|
||||
// a routed sub-page, wrap the entire row in a `<Link>` so the
|
||||
// whole card is tappable on mobile instead of the user having to
|
||||
// hit the small headline text. Hover + focus paint a subtle bg
|
||||
// shift; metrics that don't have a sub-page yet render the
|
||||
// original static row.
|
||||
const rowContent = (
|
||||
<>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
@@ -158,6 +190,31 @@ function KeyFindingRow({ finding }: { finding: DailyBriefingKeyFinding }) {
|
||||
{stripChartTokens(finding.detail)}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
if (href) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
data-slot="daily-briefing-finding"
|
||||
data-metric={finding.sourceMetric}
|
||||
className={cn(
|
||||
"border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3",
|
||||
"hover:bg-accent/40 transition-colors",
|
||||
"focus-visible:ring-ring/50 focus-visible:ring-2 focus-visible:outline-none",
|
||||
)}
|
||||
>
|
||||
{rowContent}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
data-slot="daily-briefing-finding"
|
||||
data-metric={finding.sourceMetric}
|
||||
className="border-border/60 bg-card/40 relative flex items-start gap-3 rounded-md border p-3"
|
||||
>
|
||||
{rowContent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,6 +137,13 @@ export function InsightsTabStrip({
|
||||
data-slot="insights-tab-strip"
|
||||
aria-label={t("insights.navAriaLabel")}
|
||||
className={cn(
|
||||
// v1.4.27 MB7 / CF-72 — relative wrapper hosts the right-edge
|
||||
// fade overlay below so the user reads "there's more to
|
||||
// scroll" when the pill row overflows. The fade is a tiny
|
||||
// pointer-events-none pseudo-strip painted onto the inner
|
||||
// strip via a sibling div; it sits only on `<sm` because the
|
||||
// wider viewport above already shows every pill.
|
||||
"relative",
|
||||
"bg-background/95 sticky top-0 z-30 overflow-x-auto border-b py-2 backdrop-blur",
|
||||
"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
||||
)}
|
||||
@@ -175,6 +182,20 @@ export function InsightsTabStrip({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* v1.4.27 MB7 / CF-72 — right-edge fade. The gradient
|
||||
absolute-positions over the rightmost ~24 px of the strip
|
||||
so the last visible pill softly fades into the background
|
||||
colour, signalling "scroll for more" without a scrollbar.
|
||||
Only paints on `<sm` because the wider viewports above
|
||||
already fit every pill. The fade is a sibling of the
|
||||
scrollable inner row + the regenerate button so it
|
||||
visually overlays both without trapping pointer events. */}
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"from-background/95 pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l to-transparent sm:hidden",
|
||||
)}
|
||||
/>
|
||||
{onRegenerate && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -216,7 +216,11 @@ export function SleepStageStackedBar({ breakdown }: SleepStageStackedBarProps) {
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-1 self-end sm:self-auto"
|
||||
// v1.4.27 MB7 / CF-70 — bump the gap from `gap-1` to
|
||||
// `gap-1.5` so the three window-toggle buttons (7d /
|
||||
// 14d / 30d) breathe enough on Pixel 5 that the active
|
||||
// pill's border doesn't fuse with the inactive neighbour.
|
||||
className="flex items-center gap-1.5 self-end sm:self-auto"
|
||||
data-slot="sleep-stage-window-toggle"
|
||||
>
|
||||
{WINDOW_DAYS.map((w) => (
|
||||
|
||||
@@ -105,13 +105,17 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
v1.4.25 W3 — strengthen the equal-height contract with
|
||||
`md:auto-rows-fr` + `md:items-stretch` on the grid and
|
||||
`h-full` on each card so the tallest annotation still pins
|
||||
every row member to a single baseline. The `min-h-[300px]`
|
||||
floor stays in place for the single-column mobile view. */}
|
||||
every row member to a single baseline.
|
||||
|
||||
v1.4.27 MB7 / CF-71 — drop the unconditional `min-h-[300px]`
|
||||
floor to `md:min-h-[300px]` so mobile single-column doesn't
|
||||
eat dead space below a short chart; the floor stays on `md+`
|
||||
to preserve the equal-height baseline across the row. */}
|
||||
<div className="grid grid-cols-1 gap-4 md:auto-rows-fr md:grid-cols-3 md:items-stretch">
|
||||
<div
|
||||
data-slot="trends-row-card"
|
||||
data-metric="bp"
|
||||
className="flex h-full min-h-[300px] flex-col gap-2"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<HealthChart
|
||||
types={["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA"]}
|
||||
@@ -131,7 +135,7 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
<div
|
||||
data-slot="trends-row-card"
|
||||
data-metric="weight"
|
||||
className="flex h-full min-h-[300px] flex-col gap-2"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<HealthChart
|
||||
types={["WEIGHT"]}
|
||||
@@ -150,7 +154,7 @@ export function TrendsRow({ annotations, confidence }: TrendsRowProps) {
|
||||
<div
|
||||
data-slot="trends-row-card"
|
||||
data-metric="mood"
|
||||
className="flex h-full min-h-[300px] flex-col gap-2"
|
||||
className="flex h-full flex-col gap-2 md:min-h-[300px]"
|
||||
>
|
||||
<MoodChart
|
||||
title={t("charts.mood")}
|
||||
|
||||
@@ -318,10 +318,16 @@ export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* v1.4.27 MB7 / CF-46 — the filter row stacks the SelectTrigger
|
||||
and the count caption vertically on `<sm` so the trigger
|
||||
fills the column and the count drops to a separate line.
|
||||
Pre-fix the trigger had a fixed `w-48` (192 px) which on
|
||||
Pixel 5 (375 px content width) left only ~120 px for the
|
||||
count, which wrapped to 2 lines. */}
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<Select value={typeFilter} onValueChange={setTypeFilter}>
|
||||
<SelectTrigger
|
||||
className="w-48"
|
||||
className="w-full sm:w-48"
|
||||
aria-label={t("measurements.filterByType")}
|
||||
>
|
||||
<SelectValue placeholder={t("measurements.allTypes")} />
|
||||
@@ -501,11 +507,20 @@ export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) {
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
{/* v1.4.27 MB7 / CF-76 — bump the metadata
|
||||
badges from `text-[10px]` to `text-[11px]`
|
||||
so the legibility floor (12 px is the
|
||||
mobile baseline; 11 px is the lowest
|
||||
tolerated value for non-primary chrome)
|
||||
holds across the row. The badge heights
|
||||
stay at `h-5` / `h-4` since the type-
|
||||
preface badge owns vertical real estate
|
||||
inside the headline line. */}
|
||||
{(m.type === "BLOOD_PRESSURE_SYS" ||
|
||||
m.type === "BLOOD_PRESSURE_DIA") && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="mr-1.5 h-5 px-1 text-[10px]"
|
||||
className="mr-1.5 h-5 px-1 text-[11px]"
|
||||
>
|
||||
{t(TYPE_LABEL_KEYS[m.type])}
|
||||
</Badge>
|
||||
@@ -519,7 +534,7 @@ export function MeasurementList({ onEdit, onAddFirst }: MeasurementListProps) {
|
||||
<Badge
|
||||
variant="outline"
|
||||
data-testid="measurement-source-badge"
|
||||
className={`ml-1.5 h-4 px-1 text-[10px] ${sourceBadgeClass(m.source)}`.trim()}
|
||||
className={`ml-1.5 h-4 px-1 text-[11px] ${sourceBadgeClass(m.source)}`.trim()}
|
||||
>
|
||||
{formatMeasurementSource(m.source, t)}
|
||||
</Badge>
|
||||
|
||||
@@ -833,11 +833,17 @@ function PasskeyListSection({ isAuthenticated }: { isAuthenticated: boolean }) {
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{pk.name}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{DEVICE_TYPE_LABELS[pk.credentialDeviceType] ??
|
||||
pk.credentialDeviceType}
|
||||
</p>
|
||||
{/* v1.4.27 MB7 / CF-75 — promote the device-type from
|
||||
plain text to an outline Badge so the mobile card
|
||||
list reads consistent with the desktop table's
|
||||
"Single-device / Multi-device" column. Sits on the
|
||||
same chip row as the backup status and date so all
|
||||
metadata reads as a single horizontal stride. */}
|
||||
<div className="mt-1 flex flex-wrap items-center gap-1.5 text-[11px]">
|
||||
<Badge variant="outline" className="text-[11px]">
|
||||
{DEVICE_TYPE_LABELS[pk.credentialDeviceType] ??
|
||||
pk.credentialDeviceType}
|
||||
</Badge>
|
||||
<Badge
|
||||
variant={pk.credentialBackedUp ? "secondary" : "outline"}
|
||||
className="text-[11px]"
|
||||
|
||||
Reference in new issue
Block a user