From de16da43fe304e2cd765227df4640942427e66a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 9 Jul 2026 12:00:12 +0200 Subject: [PATCH] fix(insights): unify the left reading edge across metric/score cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Establish one inset system on the Insights surface. SubPageShell's flat px-6 header sat 8 px right of every card body on mobile — track the card slot inset (px-4 md:px-6) so the heading-vs-body step disappears. DeviceScoreTile and ResilienceTile hand-rolled a muted h-4 header row; route them through TileHeader size=sm (foreground icon, shared gutter), latest reading in the right slot. ScoreAnatomyView dropped its banned p-5 + centred uppercase title for a left-aligned TileHeader on p-4 md:p-6, so the ring hero stays a centred identity block while the header shares the reading edge with the stacked assessment card. SparklineDeltaTile swaps its hand-copied header row for the TileHeader primitive so it can't drift, and its contributor label rounds off the sub-xs size. --- .../insights/derived/score-anatomy-view.tsx | 18 +++---- .../insights/derived/sparkline-delta-tile.tsx | 42 +++++++-------- src/components/insights/device-score-tile.tsx | 52 +++++++++---------- .../insights/recovery/resilience-tile.tsx | 43 ++++++++------- src/components/insights/sub-page-shell.tsx | 11 ++-- 5 files changed, 84 insertions(+), 82 deletions(-) diff --git a/src/components/insights/derived/score-anatomy-view.tsx b/src/components/insights/derived/score-anatomy-view.tsx index bbc2b1dc5..aed092d6a 100644 --- a/src/components/insights/derived/score-anatomy-view.tsx +++ b/src/components/insights/derived/score-anatomy-view.tsx @@ -10,6 +10,7 @@ import type { DerivedCoverage, DerivedProvenance, } from "@/lib/insights/derived/types"; +import { TileHeader } from "@/components/insights/tile-header"; import { ScoreRing } from "./score-ring"; import { CoverageMeter } from "./coverage-meter"; import { @@ -126,20 +127,19 @@ export function ScoreAnatomyView({ : undefined } className={cn( - "flex flex-col gap-5 rounded-xl border p-5", + "flex flex-col gap-4 rounded-xl border p-4 md:p-6", hue ? "wellness-detail-card" : "border-border bg-card", className, )} aria-label={typeof title === "string" ? title : undefined} > - {/* Hero: title + ring + caption */} + {/* Left-aligned canonical tile header — sits on the same reading edge as + the sibling assessment card that stacks beneath it on a score + sub-page, instead of the old centred uppercase title that floated + toward the middle and shared no left line with any card. */} + + {/* Hero: ring + caption — a centred identity block below the header. */}
-

- {title} -

{/* v1.15.12 F2 — vertical breathing room so the ring's bloom/glow is never clipped top/bottom by the hero container. */}
@@ -176,7 +176,7 @@ export function ScoreAnatomyView({ /> ) : (
-

+

{t("insights.derived.anatomy.contributorsLabel")}

    diff --git a/src/components/insights/derived/sparkline-delta-tile.tsx b/src/components/insights/derived/sparkline-delta-tile.tsx index f6a3467eb..c6241b52f 100644 --- a/src/components/insights/derived/sparkline-delta-tile.tsx +++ b/src/components/insights/derived/sparkline-delta-tile.tsx @@ -9,6 +9,7 @@ import { sentimentColorClass, type TrendDirectionSentiment, } from "@/lib/insights/trend-sentiment"; +import { TileHeader } from "@/components/insights/tile-header"; /** * v1.10.0 — the Apple-Health-Highlights grid tile. @@ -138,27 +139,26 @@ export function SparklineDeltaTile({ className, )} > - {/* Header row matches the canonical TileHeader language: a leading - foreground icon + a foreground `CardTitle`-weight heading — never - the old muted-uppercase caption. Kept on one line for the dense - grid; provenance trails to the right edge. */} -
    -
    + {/* The canonical tile-header primitive (was a hand-rolled copy of it): + leading foreground icon + foreground `CardTitle` heading, provenance + in the `right` slot. Routing through the primitive keeps this dense + grid tile from drifting out of the header contract. */} + + {provenance} + + ) : undefined + } + />
    - - - {latest != null ? ( - - {fmt(latest)} - {unit ? ( - - {unit} + {/* Canonical compact tile header — foreground `h-4` icon + `text-sm` + title on the shared inset; the latest reading rides the `right` + slot so it stays pinned to the row's right edge. */} + + {fmt(latest)} + {unit ? ( + + {unit} + + ) : null} - ) : null} - - ) : null} + ) : undefined + } + /> {isLearning ? ( diff --git a/src/components/insights/recovery/resilience-tile.tsx b/src/components/insights/recovery/resilience-tile.tsx index dc7233802..2fd357fbd 100644 --- a/src/components/insights/recovery/resilience-tile.tsx +++ b/src/components/insights/recovery/resilience-tile.tsx @@ -4,15 +4,10 @@ import { ShieldCheck } from "lucide-react"; import { useInsightsAnalytics } from "@/hooks/use-insights-analytics"; import { useTranslations } from "@/lib/i18n/context"; -import { - Card, - CardAction, - CardContent, - CardHeader, - CardTitle, -} from "@/components/ui/card"; +import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { LearningGate } from "@/components/ui/learning-gate"; import { LearnMoreLink } from "@/components/ui/learn-more-link"; +import { TileHeader } from "@/components/insights/tile-header"; /** * v1.19.2 — calm readout for Oura's daily resilience level. @@ -96,21 +91,25 @@ export function ResilienceTile() { return ( - - - {bandKey != null ? ( - - {t(bandKey)} - - ) : null} + {/* Canonical compact tile header — foreground `h-4` icon + `text-sm` + title; the current band rides the `right` slot. */} + + {t(bandKey)} + + ) : undefined + } + />

    diff --git a/src/components/insights/sub-page-shell.tsx b/src/components/insights/sub-page-shell.tsx index ece0a44a0..0908f48c3 100644 --- a/src/components/insights/sub-page-shell.tsx +++ b/src/components/insights/sub-page-shell.tsx @@ -218,10 +218,13 @@ export function SubPageShell({

    {/* v1.8.7.1 — back-nav leads the page, above the heading. */} {backLink} - {/* The heading + explainer indent to the cards' inner text edge - (`px-6`, the Card content padding), so the page head reads as - part of the card column instead of hanging outside it. */} -
    + {/* The heading + explainer indent to the cards' inner text edge so + the page head reads as part of the card column instead of hanging + outside it. Track the card slot inset EXACTLY (`px-4 md:px-6`) — + never a flat `px-6`, which sat 8 px right of every card body on + mobile and reopened the heading-vs-body step (insights left-edge + rule: one reading edge across breakpoints). */} +
    {/* v1.8.6 — the heading row pins the Coach icon top-right while the title / nudge / badge wrap together on the left.