fix(dashboard): link metric tiles to Insights detail + unify tile header

- Dashboard metric tiles link to their /insights/<slug> detail page
  (weight, blood pressure, pulse, glucose, mood, sleep, steps, VO2 max);
  tiles with no dedicated sub-page stay un-linked rather than misroute.
- Daily-briefing key findings now route hrv, resting HR, active energy,
  flights, distance, body temperature and recovery to their sub-pages —
  the METRIC_HREF map had gone stale as those routes shipped.
- RecentWorkoutsTile adopts the canonical TileHeader (foreground icon +
  title) instead of a muted hand-rolled row.
- Hero-strip header glyph moves off text-primary to the foreground tier.
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 23:35:41 +02:00
1 parent 6ad2dc858a
commit 2dc9947de2
5 files changed
+124 -47

No files matched your search

@@ -15,6 +15,7 @@ import {
import { useTranslations } from "@/lib/i18n/context";
import { useWorkouts, type WorkoutListEntry } from "@/hooks/use-workouts";
import { cn } from "@/lib/utils";
import { TileHeader } from "@/components/insights/tile-header";
/**
* v1.4.32 — `<RecentWorkoutsTile>`.
@@ -107,23 +108,23 @@ export function RecentWorkoutsTile() {
"bg-card border-border space-y-3 rounded-xl border p-4 md:p-6",
)}
>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<Activity
className="text-muted-foreground size-4"
aria-hidden="true"
/>
<h2 className="text-sm font-semibold">
{t("dashboard.recentWorkouts.title")}
</h2>
</div>
<Link
href="/insights/workouts"
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 items-center text-xs"
>
{t("dashboard.recentWorkouts.viewAll")}
</Link>
</div>
{/* Canonical tile header (foreground icon + title) — the same
`TileHeader` contract the Insights tiles use, so the dashboard
stops speaking a separate header language. `size="sm"` matches the
compact dashboard tile. */}
<TileHeader
icon={Activity}
size="sm"
title={t("dashboard.recentWorkouts.title")}
right={
<Link
href="/insights/workouts"
className="text-muted-foreground hover:text-foreground inline-flex min-h-11 items-center text-xs"
>
{t("dashboard.recentWorkouts.viewAll")}
</Link>
}
/>
{isLoading ? (
// v1.4.43 W11-L6 — reserve roughly the loaded tile height
@@ -95,6 +95,36 @@ describe("<DailyBriefing>", () => {
expect(html).toContain("Weight down 30 d");
});
it("links the key-finding rows for metrics whose Insights sub-page exists", () => {
// Regression guard for the stale METRIC_HREF map: hrv / resting_hr /
// active_energy / flights / distance / body_temp / recovery all have
// dedicated Insights sub-pages now, so their briefing rows must be
// tappable `<a>` links, not inert rows.
const html = render(
<DailyBriefing
briefing={{
...baseBriefing,
keyFindings: [
{ ...baseBriefing.keyFindings[2], sourceMetric: "hrv" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "resting_hr" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "active_energy" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "flights" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "distance" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "body_temp" },
{ ...baseBriefing.keyFindings[2], sourceMetric: "recovery" },
],
}}
/>,
);
expect(html).toContain('href="/insights/hrv"');
expect(html).toContain('href="/insights/resting-pulse"');
expect(html).toContain('href="/insights/active-energy"');
expect(html).toContain('href="/insights/flights-climbed"');
expect(html).toContain('href="/insights/walking-distance"');
expect(html).toContain('href="/insights/body-temperature"');
expect(html).toContain('href="/insights/recovery"');
});
it("renders the delta string when supplied", () => {
const html = render(<DailyBriefing briefing={baseBriefing} />);
expect(html).toMatch(/data-slot="daily-briefing-delta"/);
+11 -11
View File
@@ -53,28 +53,28 @@ export const METRIC_HREF: Record<
pulse: "/insights/pulse",
mood: "/insights/mood",
compliance: "/insights/medications",
// 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,
// Apple Health / GLP-1 additive metrics — each now routes to its
// dedicated sub-page that shipped since the original map was written.
hrv: "/insights/hrv",
sleep: "/insights/sleep",
resting_hr: null,
resting_hr: "/insights/resting-pulse",
// v1.12.4 — steps has no dedicated metric sub-page, so it routes to the
// generic readings view keyed to its `MeasurementType` (the same target
// the category pages use to drill into a single type). Every other
// briefing finding with a real destination is already tappable; steps
// was the lone static row.
steps: "/insights/steps",
active_energy: null,
flights: null,
distance: null,
active_energy: "/insights/active-energy",
flights: "/insights/flights-climbed",
distance: "/insights/walking-distance",
vo2_max: "/insights/pulse",
body_temp: null,
body_temp: "/insights/body-temperature",
glp1_plateau: "/insights/medications",
// ── v1.10.0 derived-wellness additive ──
// Readiness has a score-anatomy detail page; recovery is a read-only
// ring with no dedicated sub-page yet (renders as a static row).
// Readiness has a score-anatomy detail page; recovery has its own
// read-only sub-page.
readiness: "/insights/scores/readiness",
recovery: null,
recovery: "/insights/recovery",
};
/**
+4 -1
View File
@@ -284,8 +284,11 @@ export function HeroStrip({
<div className="flex min-w-0 flex-1 flex-col gap-5">
<div className="flex flex-col gap-3">
<div className="flex items-center gap-2">
{/* Header glyph stays in the foreground tier — UI-STANDARDS §1
reserves primary/accent for actions, not decorative header
icons. */}
<Sparkles
className="text-primary h-5 w-5 shrink-0"
className="text-foreground h-5 w-5 shrink-0"
aria-hidden="true"
/>
<h1