From fcd8eccf6ebee0138b92793a0e2c732a64106875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 5 Jun 2026 12:30:40 +0200 Subject: [PATCH] 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) --- messages/de.json | 15 ++-- messages/en.json | 15 ++-- messages/es.json | 15 ++-- messages/fr.json | 15 ++-- messages/it.json | 15 ++-- messages/pl.json | 15 ++-- .../mood/mood-discovered-relations.tsx | 46 ++++++---- .../insights/mood/mood-in-target-tile.tsx | 29 ++++--- .../insights/mood/mood-insights-sections.tsx | 83 ++++++++++++++----- .../insights/mood/mood-stability-tile.tsx | 32 +++---- 10 files changed, 167 insertions(+), 113 deletions(-) diff --git a/messages/de.json b/messages/de.json index 73e41890e..00ea5e3bd 100644 --- a/messages/de.json +++ b/messages/de.json @@ -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": { diff --git a/messages/en.json b/messages/en.json index 140470549..420795132 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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": { diff --git a/messages/es.json b/messages/es.json index 754212920..1d421bde6 100644 --- a/messages/es.json +++ b/messages/es.json @@ -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": { diff --git a/messages/fr.json b/messages/fr.json index f4579f8b5..e8b37bdc1 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -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": { diff --git a/messages/it.json b/messages/it.json index 11dfd526e..b824da013 100644 --- a/messages/it.json +++ b/messages/it.json @@ -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": { diff --git a/messages/pl.json b/messages/pl.json index 9cd479969..543939e4f 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -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": { diff --git a/src/components/insights/mood/mood-discovered-relations.tsx b/src/components/insights/mood/mood-discovered-relations.tsx index 4acf20058..6e087b84c 100644 --- a/src/components/insights/mood/mood-discovered-relations.tsx +++ b/src/components/insights/mood/mood-discovered-relations.tsx @@ -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 ( -
-
-

- {t("insights.mood.discovery.title")} -

+ + {/* 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. */} - + } /> -
-

- {t("insights.mood.discovery.description")} -

-
    + + +

    + {t("insights.mood.discovery.description")} +

    +
      {moodPairs.map((pair) => { const moodIsOutcome = pair.outcome === "MOOD"; const channelKey = moodIsOutcome ? pair.behaviour : pair.outcome; @@ -139,7 +148,8 @@ export function MoodDiscoveredRelations() { ); })} -
    -
+ + + ); } diff --git a/src/components/insights/mood/mood-in-target-tile.tsx b/src/components/insights/mood/mood-in-target-tile.tsx index 6e3241e90..9f19c6bbc 100644 --- a/src/components/insights/mood/mood-in-target-tile.tsx +++ b/src/components/insights/mood/mood-in-target-tile.tsx @@ -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 ( - -
- -
-
-

- {t("insights.mood.inTargetValue", { pct: String(rounded) })} -

-

- {t("insights.mood.inTargetCaption")} -

-
+ + + + +

+ {t("insights.mood.inTargetValue", { pct: String(rounded) })} +

+

+ {t("insights.mood.inTargetCaption")} +

); diff --git a/src/components/insights/mood/mood-insights-sections.tsx b/src/components/insights/mood/mood-insights-sections.tsx index 7271c786e..061871ca1 100644 --- a/src/components/insights/mood/mood-insights-sections.tsx +++ b/src/components/insights/mood/mood-insights-sections.tsx @@ -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 ( - {title} + {children} @@ -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.
+ {/* 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() {
)} - - + {/* "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 && ( - + )} - + {narratives.length > 0 && ( + + + + )} + +
- + - +
{data.timeOfDay.reliable && ( - + )} {hasStructuredTags && ( - + )} {hasTags && ( - + )} {hasInfluence && ( - + )} {hasCrosstab && ( - + )} -
-

- {t("insights.mood.correlationsTitle")} -

-

+ +

{t("insights.mood.correlationsDescription")}

-
+
{/* F3 — the FDR-controlled discovered mood relations. Self-fetches the correlation-discovery surface and renders nothing when the operator diff --git a/src/components/insights/mood/mood-stability-tile.tsx b/src/components/insights/mood/mood-stability-tile.tsx index cc53e9ba5..32da9e524 100644 --- a/src/components/insights/mood/mood-stability-tile.tsx +++ b/src/components/insights/mood/mood-stability-tile.tsx @@ -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 ( - -
- -
-
-

- {t("insights.mood.stability.value", { - score: String(stability.score), - })} -

-

- {t(BAND_KEY[stability.band])} -

-
+ + + + +

+ {t("insights.mood.stability.value", { + score: String(stability.score), + })} +

+

+ {t(BAND_KEY[stability.band])} +

);