insights(mood): order classification first, route every tile through TileHeader

Reorder the mood subpage spine so the classification tiles (in-target
share + stability band) lead, then the better-days assessment, then the
descriptive breakdown sections. The classification answers where the user
stands; the better-days board, directly beneath, answers what associates
with their good days.

Every mood card header now routes through the shared TileHeader (icon +
white heading) so the subpage speaks one card language. The in-target and
stability tiles gain proper headings (new inTargetTitle / stability.title
keys across all six locales) and drop the icon-in-a-circle headline glyph;
the narrative feed and the discovered-relations board move into headed
cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-06-05 12:36:12 +02:00
1 parent 85c9f3ff3f
commit fcd8eccf6e
10 files changed
+167 -113

No files matched your search

+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Deine Stimmung war sehr gleichmäßig",
"bandSteady": "Deine Stimmung war recht gleichmäßig",
"bandVariable": "Deine Stimmung war merklich schwankend",
"bandVeryVariable": "Deine Stimmung hat zuletzt stark geschwankt"
"bandVeryVariable": "Deine Stimmung hat zuletzt stark geschwankt",
"title": "Stabilität"
},
"influence": {
"title": "Einfluss auf die Stimmung",
"detail": "Durchschnitt {withAvg} an den {withDays} Tagen mit diesem Tag, {withoutAvg} ohne.",
"confidenceLow": "Geringe Sicherheit",
"confidenceMedium": "Mittlere Sicherheit",
"confidenceHigh": "Hohe Sicherheit",
"disclaimer": "Das sind Zusammenhänge in deinen eigenen Daten, kein Beweis für eine Ursache. Muster, die einen Blick wert sind."
"confidenceHigh": "Hohe Sicherheit"
},
"betterDays": {
"title": "Was zu deinen besseren Tagen passt",
"description": "Tags und Werte, die mit deinen Tagen besserer und schlechterer Stimmung einhergehen, sortiert danach, wie stark sie sich zusammen bewegen.",
"disclaimer": "Nur Zusammenhänge, keine Ursachen. Sprich mit deiner Ärztin oder deinem Arzt, bevor du etwas änderst."
"description": "Tags und Werte, die mit deinen Tagen besserer und schlechterer Stimmung einhergehen, sortiert danach, wie stark sie sich zusammen bewegen."
},
"crosstab": {
"title": "Tags und deine Gesundheitswerte",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "Pkt.",
"detailSameDay": "Durchschnittlich {withAvg} {unit} an den {withDays} Tagen mit diesem Tag gegenüber {withoutAvg} {unit} ohne.",
"detailNextDay": "Durchschnittlich {withAvg} {unit} am Tag nach den {withDays} Tagen mit diesem Tag gegenüber {withoutAvg} {unit} sonst.",
"disclaimer": "Das sind Zusammenhänge in deinen eigenen Daten, kein Beweis für Ursachen. Muster, die einen Blick wert sind."
"detailNextDay": "Durchschnittlich {withAvg} {unit} am Tag nach den {withDays} Tagen mit diesem Tag gegenüber {withoutAvg} {unit} sonst."
},
"discovery": {
"title": "Was auffällt",
@@ -2625,7 +2623,8 @@
"stat": "{n} gepaarte Tage · r = {r} · q = {q}",
"statLabel": "Wie das berechnet wurde",
"explainerLabel": "Wie das berechnet wurde"
}
},
"inTargetTitle": "Im Zielbereich"
},
"derived": {
"coverage": {
+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Your mood has been very steady",
"bandSteady": "Your mood has been fairly steady",
"bandVariable": "Your mood has been noticeably variable",
"bandVeryVariable": "Your mood has swung a lot lately"
"bandVeryVariable": "Your mood has swung a lot lately",
"title": "Stability"
},
"influence": {
"title": "Influence on mood",
"detail": "Averages {withAvg} on the {withDays} days you tagged this vs {withoutAvg} without it.",
"confidenceLow": "Low confidence",
"confidenceMedium": "Medium confidence",
"confidenceHigh": "High confidence",
"disclaimer": "These are associations in your own data, not proof of cause. Patterns worth watching."
"confidenceHigh": "High confidence"
},
"betterDays": {
"title": "What goes with your better days",
"description": "Tags and metrics that line up with your higher- and lower-mood days, ranked by how strongly they move together.",
"disclaimer": "Associations only, not causes. Talk to your doctor before changing anything based on these."
"description": "Tags and metrics that line up with your higher- and lower-mood days, ranked by how strongly they move together."
},
"crosstab": {
"title": "Tags and your health metrics",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "pts",
"detailSameDay": "Averages {withAvg} {unit} on the {withDays} days you tagged this vs {withoutAvg} {unit} without it.",
"detailNextDay": "Averages {withAvg} {unit} the day after the {withDays} days you tagged this vs {withoutAvg} {unit} otherwise.",
"disclaimer": "These are associations in your own data, not proof of cause. Patterns worth watching."
"detailNextDay": "Averages {withAvg} {unit} the day after the {withDays} days you tagged this vs {withoutAvg} {unit} otherwise."
},
"discovery": {
"title": "What stands out",
@@ -2625,7 +2623,8 @@
"stat": "{n} paired days · r = {r} · q = {q}",
"statLabel": "How this was computed",
"explainerLabel": "How this was computed"
}
},
"inTargetTitle": "In your range"
},
"derived": {
"coverage": {
+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Tu estado de ánimo ha sido muy estable",
"bandSteady": "Tu estado de ánimo ha sido bastante estable",
"bandVariable": "Tu estado de ánimo ha sido notablemente variable",
"bandVeryVariable": "Tu estado de ánimo ha fluctuado mucho últimamente"
"bandVeryVariable": "Tu estado de ánimo ha fluctuado mucho últimamente",
"title": "Estabilidad"
},
"influence": {
"title": "Influencia en el estado de ánimo",
"detail": "Promedio de {withAvg} en los {withDays} días que lo etiquetaste, frente a {withoutAvg} sin él.",
"confidenceLow": "Confianza baja",
"confidenceMedium": "Confianza media",
"confidenceHigh": "Confianza alta",
"disclaimer": "Son asociaciones en tus propios datos, no una prueba de causa. Patrones que vale la pena observar."
"confidenceHigh": "Confianza alta"
},
"betterDays": {
"title": "Qué acompaña a tus mejores días",
"description": "Etiquetas y métricas que coinciden con tus días de mejor y peor ánimo, ordenadas por la fuerza con que se mueven juntas.",
"disclaimer": "Solo asociaciones, no causas. Consulta a tu médico antes de cambiar nada por esto."
"description": "Etiquetas y métricas que coinciden con tus días de mejor y peor ánimo, ordenadas por la fuerza con que se mueven juntas."
},
"crosstab": {
"title": "Etiquetas y tus métricas de salud",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "pts",
"detailSameDay": "Promedia {withAvg} {unit} en los {withDays} días que lo etiquetaste frente a {withoutAvg} {unit} sin él.",
"detailNextDay": "Promedia {withAvg} {unit} el día después de los {withDays} días que lo etiquetaste frente a {withoutAvg} {unit} el resto.",
"disclaimer": "Son asociaciones en tus propios datos, no prueba de causa. Patrones que vale la pena observar."
"detailNextDay": "Promedia {withAvg} {unit} el día después de los {withDays} días que lo etiquetaste frente a {withoutAvg} {unit} el resto."
},
"discovery": {
"title": "Lo que destaca",
@@ -2625,7 +2623,8 @@
"stat": "{n} días emparejados · r = {r} · q = {q}",
"statLabel": "Cómo se calculó esto",
"explainerLabel": "Cómo se calculó esto"
}
},
"inTargetTitle": "En tu rango"
},
"derived": {
"coverage": {
+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Votre humeur a été très stable",
"bandSteady": "Votre humeur a été plutôt stable",
"bandVariable": "Votre humeur a été nettement variable",
"bandVeryVariable": "Votre humeur a beaucoup fluctué récemment"
"bandVeryVariable": "Votre humeur a beaucoup fluctué récemment",
"title": "Stabilité"
},
"influence": {
"title": "Influence sur l'humeur",
"detail": "Moyenne de {withAvg} les {withDays} jours où vous l'avez indiqué, contre {withoutAvg} sans.",
"confidenceLow": "Confiance faible",
"confidenceMedium": "Confiance moyenne",
"confidenceHigh": "Confiance élevée",
"disclaimer": "Ce sont des associations dans vos propres données, pas une preuve de cause. Des tendances à surveiller."
"confidenceHigh": "Confiance élevée"
},
"betterDays": {
"title": "Ce qui accompagne vos meilleurs jours",
"description": "Les tags et mesures qui coïncident avec vos jours de meilleure et de moins bonne humeur, classés selon la force du lien.",
"disclaimer": "Des associations seulement, pas des causes. Parlez-en à votre médecin avant de changer quoi que ce soit."
"description": "Les tags et mesures qui coïncident avec vos jours de meilleure et de moins bonne humeur, classés selon la force du lien."
},
"crosstab": {
"title": "Tags et vos métriques de santé",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "pts",
"detailSameDay": "En moyenne {withAvg} {unit} les {withDays} jours avec ce tag contre {withoutAvg} {unit} sans.",
"detailNextDay": "En moyenne {withAvg} {unit} le lendemain des {withDays} jours avec ce tag contre {withoutAvg} {unit} sinon.",
"disclaimer": "Ce sont des associations dans vos propres données, pas une preuve de cause. Des tendances à surveiller."
"detailNextDay": "En moyenne {withAvg} {unit} le lendemain des {withDays} jours avec ce tag contre {withoutAvg} {unit} sinon."
},
"discovery": {
"title": "Ce qui ressort",
@@ -2625,7 +2623,8 @@
"stat": "{n} jours appariés · r = {r} · q = {q}",
"statLabel": "Comment ceci est calculé",
"explainerLabel": "Comment ceci est calculé"
}
},
"inTargetTitle": "Dans votre plage"
},
"derived": {
"coverage": {
+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Il tuo umore è stato molto stabile",
"bandSteady": "Il tuo umore è stato abbastanza stabile",
"bandVariable": "Il tuo umore è stato notevolmente variabile",
"bandVeryVariable": "Il tuo umore ha oscillato molto di recente"
"bandVeryVariable": "Il tuo umore ha oscillato molto di recente",
"title": "Stabilità"
},
"influence": {
"title": "Influenza sull'umore",
"detail": "Media di {withAvg} nei {withDays} giorni in cui l'hai indicato, contro {withoutAvg} senza.",
"confidenceLow": "Affidabilità bassa",
"confidenceMedium": "Affidabilità media",
"confidenceHigh": "Affidabilità alta",
"disclaimer": "Sono associazioni nei tuoi dati, non una prova di causa. Schemi da tenere d'occhio."
"confidenceHigh": "Affidabilità alta"
},
"betterDays": {
"title": "Cosa accompagna le tue giornate migliori",
"description": "Tag e parametri che coincidono con le tue giornate di umore migliore e peggiore, ordinati per quanto si muovono insieme.",
"disclaimer": "Solo associazioni, non cause. Parla con il tuo medico prima di cambiare qualcosa."
"description": "Tag e parametri che coincidono con le tue giornate di umore migliore e peggiore, ordinati per quanto si muovono insieme."
},
"crosstab": {
"title": "Tag e le tue metriche di salute",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "pt",
"detailSameDay": "In media {withAvg} {unit} nei {withDays} giorni con questo tag contro {withoutAvg} {unit} senza.",
"detailNextDay": "In media {withAvg} {unit} il giorno dopo i {withDays} giorni con questo tag contro {withoutAvg} {unit} altrimenti.",
"disclaimer": "Sono associazioni nei tuoi dati, non prova di causa. Schemi che vale la pena osservare."
"detailNextDay": "In media {withAvg} {unit} il giorno dopo i {withDays} giorni con questo tag contro {withoutAvg} {unit} altrimenti."
},
"discovery": {
"title": "Cosa risalta",
@@ -2625,7 +2623,8 @@
"stat": "{n} giorni appaiati · r = {r} · q = {q}",
"statLabel": "Come è stato calcolato",
"explainerLabel": "Come è stato calcolato"
}
},
"inTargetTitle": "Nel tuo intervallo"
},
"derived": {
"coverage": {
+7 -8
View File
@@ -2583,20 +2583,19 @@
"bandVerySteady": "Twój nastrój był bardzo stabilny",
"bandSteady": "Twój nastrój był dość stabilny",
"bandVariable": "Twój nastrój był zauważalnie zmienny",
"bandVeryVariable": "Twój nastrój ostatnio mocno się wahał"
"bandVeryVariable": "Twój nastrój ostatnio mocno się wahał",
"title": "Stabilność"
},
"influence": {
"title": "Wpływ na nastrój",
"detail": "Średnia {withAvg} w {withDays} dniach z tym tagiem wobec {withoutAvg} bez niego.",
"confidenceLow": "Niska pewność",
"confidenceMedium": "Średnia pewność",
"confidenceHigh": "Wysoka pewność",
"disclaimer": "To powiązania w Twoich danych, a nie dowód przyczyny. Wzorce warte obserwacji."
"confidenceHigh": "Wysoka pewność"
},
"betterDays": {
"title": "Co towarzyszy Twoim lepszym dniom",
"description": "Tagi i wskaźniki, które pokrywają się z dniami lepszego i gorszego nastroju, uszeregowane według siły powiązania.",
"disclaimer": "Tylko powiązania, nie przyczyny. Skonsultuj się z lekarzem, zanim coś zmienisz."
"description": "Tagi i wskaźniki, które pokrywają się z dniami lepszego i gorszego nastroju, uszeregowane według siły powiązania."
},
"crosstab": {
"title": "Tagi a Twoje wskaźniki zdrowia",
@@ -2609,8 +2608,7 @@
"unitKcal": "kcal",
"unitScore": "pkt",
"detailSameDay": "Średnio {withAvg} {unit} w {withDays} dniach z tym tagiem wobec {withoutAvg} {unit} bez niego.",
"detailNextDay": "Średnio {withAvg} {unit} dzień po {withDays} dniach z tym tagiem wobec {withoutAvg} {unit} w pozostałe dni.",
"disclaimer": "To powiązania w Twoich danych, nie dowód przyczyny. Wzorce warte obserwacji."
"detailNextDay": "Średnio {withAvg} {unit} dzień po {withDays} dniach z tym tagiem wobec {withoutAvg} {unit} w pozostałe dni."
},
"discovery": {
"title": "Co się wyróżnia",
@@ -2625,7 +2623,8 @@
"stat": "{n} sparowanych dni · r = {r} · q = {q}",
"statLabel": "Jak to obliczono",
"explainerLabel": "Jak to obliczono"
}
},
"inTargetTitle": "W Twoim zakresie"
},
"derived": {
"coverage": {
@@ -1,7 +1,10 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { Compass } from "lucide-react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { useAuth } from "@/hooks/use-auth";
import { queryKeys } from "@/lib/query-keys";
import { useTranslations } from "@/lib/i18n/context";
@@ -81,26 +84,32 @@ export function MoodDiscoveredRelations() {
if (moodPairs.length === 0) return null;
return (
<div className="space-y-2" data-slot="mood-discovered-relations">
<div className="flex items-center gap-1.5">
<h3 className="text-base font-semibold">
{t("insights.mood.discovery.title")}
</h3>
<Card data-slot="mood-discovered-relations">
<CardHeader className="pb-2">
{/* v1.12.4 (C3) — the false-discovery footer + observational
disclaimer were two full-width footnote rows. Fold both into a
single explainer icon so the explanation is one hover/focus away
instead of a low-density block under the list. */}
<MoodExplainerIcon
label={t("insights.mood.discovery.explainerLabel")}
detail={`${t("insights.mood.discovery.footer", {
tested: data.pairsTested,
})} ${t("insights.mood.discovery.disclaimer")}`}
instead of a low-density block under the list. v1.12.6 — the
heading now rides the canonical `TileHeader`, with the explainer
icon pinned to its trailing slot. */}
<TileHeader
icon={Compass}
title={t("insights.mood.discovery.title")}
right={
<MoodExplainerIcon
label={t("insights.mood.discovery.explainerLabel")}
detail={`${t("insights.mood.discovery.footer", {
tested: data.pairsTested,
})} ${t("insights.mood.discovery.disclaimer")}`}
/>
}
/>
</div>
<p className="text-muted-foreground text-sm">
{t("insights.mood.discovery.description")}
</p>
<ul className="divide-border divide-y">
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-2 text-sm">
{t("insights.mood.discovery.description")}
</p>
<ul className="divide-border divide-y">
{moodPairs.map((pair) => {
const moodIsOutcome = pair.outcome === "MOOD";
const channelKey = moodIsOutcome ? pair.behaviour : pair.outcome;
@@ -139,7 +148,8 @@ export function MoodDiscoveredRelations() {
</li>
);
})}
</ul>
</div>
</ul>
</CardContent>
</Card>
);
}
@@ -2,7 +2,8 @@
import { Target } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { useTranslations } from "@/lib/i18n/context";
/**
@@ -12,6 +13,10 @@ import { useTranslations } from "@/lib/i18n/context";
* the LLM snapshot) to a visible stat: the share of recent days that
* landed in the good-mood band. Renders nothing when the number is
* unavailable (no recent data) so the page degrades gracefully.
*
* v1.12.6 — the tile now leads with the canonical `TileHeader`
* (icon + white heading) so it speaks the same card language as every
* other Insights tile; the icon-in-a-circle headline glyph is dropped.
*/
export function MoodInTargetTile({ pct }: { pct: number | null }) {
const { t } = useTranslations();
@@ -22,18 +27,16 @@ export function MoodInTargetTile({ pct }: { pct: number | null }) {
return (
<Card>
<CardContent className="flex items-center gap-4 py-4">
<div className="bg-muted text-muted-foreground flex size-10 shrink-0 items-center justify-center rounded-full">
<Target className="size-5" />
</div>
<div className="min-w-0">
<p className="text-foreground text-2xl font-semibold tabular-nums">
{t("insights.mood.inTargetValue", { pct: String(rounded) })}
</p>
<p className="text-muted-foreground text-sm">
{t("insights.mood.inTargetCaption")}
</p>
</div>
<CardHeader className="pb-2">
<TileHeader icon={Target} title={t("insights.mood.inTargetTitle")} />
</CardHeader>
<CardContent>
<p className="text-foreground text-2xl font-semibold tabular-nums">
{t("insights.mood.inTargetValue", { pct: String(rounded) })}
</p>
<p className="text-muted-foreground text-sm">
{t("insights.mood.inTargetCaption")}
</p>
</CardContent>
</Card>
);
@@ -3,7 +3,22 @@
import dynamic from "next/dynamic";
import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import type { ComponentType } from "react";
import {
BarChart3,
CalendarDays,
CalendarRange,
Clock,
Grid3x3,
Link2,
Sparkles,
Tag,
Tags,
TrendingUp,
} from "lucide-react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/hooks/use-auth";
import { queryKeys } from "@/lib/query-keys";
@@ -136,15 +151,19 @@ interface MoodInsightsResponse {
function SectionCard({
title,
icon,
children,
}: {
title: string;
// v1.12.6 — every mood section header now routes through `TileHeader`
// (icon + white heading) so the subpage reads as one card language.
icon: ComponentType<{ className?: string }>;
children: React.ReactNode;
}) {
return (
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-base">{title}</CardTitle>
<TileHeader icon={icon} title={title} />
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
@@ -210,7 +229,14 @@ export function MoodInsightsSections() {
const hasCrosstab = crosstabRows.length > 0;
return (
// v1.12.6 — the mood spine is ordered as: Einordnung (the classification
// tiles: in-target share + stability band) FIRST, then the "what goes with
// your better days" assessment read, then the descriptive breakdown
// sections. The classification answers "where do I stand", the better-days
// board answers "what associates with my good days", and the rest is the
// supporting detail.
<div className="space-y-4">
{/* Einordnung — the classification tiles. */}
{(hasInTargetTile || hasStabilityTile) && (
// Two-up only when BOTH tiles render; a lone tile spans full width so
// it never leaves a half-width orphan with dead space beside it.
@@ -229,15 +255,25 @@ export function MoodInsightsSections() {
</div>
)}
<MoodNarrativeFeed items={narratives} />
{/* "Was zu deinen besseren Tagen passt" — the better-days assessment,
ranked by effect size. The substance of the Einschätzung sits here,
directly under the classification. */}
{hasBetterDays && (
<SectionCard title={t("insights.mood.betterDays.title")}>
<SectionCard
title={t("insights.mood.betterDays.title")}
icon={Sparkles}
>
<MoodBetterDays factors={betterDays} />
</SectionCard>
)}
<SectionCard title={t("insights.mood.heatmapTitle")}>
{narratives.length > 0 && (
<SectionCard title={t("insights.mood.narrative.title")} icon={TrendingUp}>
<MoodNarrativeFeed items={narratives} />
</SectionCard>
)}
<SectionCard title={t("insights.mood.heatmapTitle")} icon={CalendarDays}>
<MoodHeatmap
cells={heatmapCells}
days={data.heatmap.windowDays}
@@ -246,49 +282,58 @@ export function MoodInsightsSections() {
</SectionCard>
<div className="grid gap-4 lg:grid-cols-2">
<SectionCard title={t("insights.mood.distributionTitle")}>
<SectionCard
title={t("insights.mood.distributionTitle")}
icon={BarChart3}
>
<MoodDistributionChart distribution={data.distribution} />
</SectionCard>
<SectionCard title={t("insights.mood.weekdayTitle")}>
<SectionCard
title={t("insights.mood.weekdayTitle")}
icon={CalendarRange}
>
<MoodWeekdayChart weekday={data.weekday} />
</SectionCard>
</div>
{data.timeOfDay.reliable && (
<SectionCard title={t("insights.mood.timeOfDay.title")}>
<SectionCard title={t("insights.mood.timeOfDay.title")} icon={Clock}>
<MoodTimeOfDayChart pattern={data.timeOfDay} />
</SectionCard>
)}
{hasStructuredTags && (
<SectionCard title={t("insights.mood.structuredTagsTitle")}>
<SectionCard
title={t("insights.mood.structuredTagsTitle")}
icon={Tags}
>
<MoodStructuredTagBreakdown tags={data.structuredTags} />
</SectionCard>
)}
{hasTags && (
<SectionCard title={t("insights.mood.tagsTitle")}>
<SectionCard title={t("insights.mood.tagsTitle")} icon={Tag}>
<MoodTagBreakdown tags={data.tags} />
</SectionCard>
)}
{hasInfluence && (
<SectionCard title={t("insights.mood.influence.title")}>
<SectionCard title={t("insights.mood.influence.title")} icon={TrendingUp}>
<MoodTagInfluence rows={influenceRows} />
</SectionCard>
)}
{hasCrosstab && (
<SectionCard title={t("insights.mood.crosstab.title")}>
<SectionCard title={t("insights.mood.crosstab.title")} icon={Grid3x3}>
<MoodTagMetricCrosstab rows={crosstabRows} />
</SectionCard>
)}
<div className="space-y-2">
<h3 className="text-base font-semibold">
{t("insights.mood.correlationsTitle")}
</h3>
<p className="text-muted-foreground text-sm">
<SectionCard
title={t("insights.mood.correlationsTitle")}
icon={Link2}
>
<p className="text-muted-foreground mb-2 text-sm">
{t("insights.mood.correlationsDescription")}
</p>
<MoodCorrelationCards
@@ -298,7 +343,7 @@ export function MoodInsightsSections() {
weight={data.correlations.weight}
bloodPressureSystolic={data.correlations.bloodPressureSystolic}
/>
</div>
</SectionCard>
{/* F3 — the FDR-controlled discovered mood relations. Self-fetches the
correlation-discovery surface and renders nothing when the operator
@@ -2,7 +2,8 @@
import { Activity } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { TileHeader } from "@/components/insights/tile-header";
import { useTranslations } from "@/lib/i18n/context";
/**
@@ -46,20 +47,21 @@ export function MoodStabilityTile({
return (
<Card>
<CardContent className="flex items-center gap-4 py-4">
<div className="bg-muted text-muted-foreground flex size-10 shrink-0 items-center justify-center rounded-full">
<Activity className="size-5" />
</div>
<div className="min-w-0">
<p className="text-foreground text-2xl font-semibold tabular-nums">
{t("insights.mood.stability.value", {
score: String(stability.score),
})}
</p>
<p className="text-muted-foreground text-sm">
{t(BAND_KEY[stability.band])}
</p>
</div>
<CardHeader className="pb-2">
<TileHeader
icon={Activity}
title={t("insights.mood.stability.title")}
/>
</CardHeader>
<CardContent>
<p className="text-foreground text-2xl font-semibold tabular-nums">
{t("insights.mood.stability.value", {
score: String(stability.score),
})}
</p>
<p className="text-muted-foreground text-sm">
{t(BAND_KEY[stability.band])}
</p>
</CardContent>
</Card>
);