diff --git a/messages/de.json b/messages/de.json index 95642eca1..6f08908a0 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Wellness-Werte", + "loadError": "Deine Wellness-Werte konnten gerade nicht geladen werden.", "recovery": "Erholung", "stress": "Stress", "strain": "Belastung" diff --git a/messages/en.json b/messages/en.json index 790aa9ac6..13328d1ce 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Wellness scores", + "loadError": "Could not load your wellness scores right now.", "recovery": "Recovery", "stress": "Stress", "strain": "Strain" diff --git a/messages/es.json b/messages/es.json index 39cf0107c..68ab74986 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Puntuaciones de bienestar", + "loadError": "No se pudieron cargar tus puntuaciones de bienestar en este momento.", "recovery": "Recuperación", "stress": "Estrés", "strain": "Esfuerzo" diff --git a/messages/fr.json b/messages/fr.json index 6ca07d04c..ab478edbd 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Scores de bien-être", + "loadError": "Impossible de charger vos scores de bien-être pour le moment.", "recovery": "Récupération", "stress": "Stress", "strain": "Effort" diff --git a/messages/it.json b/messages/it.json index e57588d7f..c4a4248a5 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Punteggi di benessere", + "loadError": "Al momento non è stato possibile caricare i tuoi punteggi di benessere.", "recovery": "Recupero", "stress": "Stress", "strain": "Sforzo" diff --git a/messages/pl.json b/messages/pl.json index dd9542416..312778ce2 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2772,6 +2772,7 @@ }, "scores": { "sectionTitle": "Wyniki dobrostanu", + "loadError": "Nie udało się teraz wczytać Twoich wyników dobrostanu.", "recovery": "Regeneracja", "stress": "Stres", "strain": "Obciążenie" diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index 19b1f2235..958792a4d 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -326,6 +326,8 @@ export default function InsightsPage() { {flags.briefing && ( diff --git a/src/components/insights/derived/wellness-scores.tsx b/src/components/insights/derived/wellness-scores.tsx index 4db638a99..f99f0cf49 100644 --- a/src/components/insights/derived/wellness-scores.tsx +++ b/src/components/insights/derived/wellness-scores.tsx @@ -2,8 +2,9 @@ import type { ComponentType } from "react"; import Link from "next/link"; -import { Activity, Flame, Gauge, HeartPulse, Moon } from "lucide-react"; +import { Activity, Flame, Gauge, HeartPulse, Moon, RefreshCw } from "lucide-react"; import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; import { useTranslations } from "@/lib/i18n/context"; import { TileHeader } from "@/components/insights/tile-header"; import { ScoreRing } from "./score-ring"; @@ -36,6 +37,15 @@ interface ScoreStripProps { /** The batched-read selector from the parent dashboard's one query. */ read: DerivedBatchRead; isLoading: boolean; + /** + * The shared derived-batch error flag. On a failed batch the strip renders + * a compact error + Retry in place instead of vanishing silently (the strip + * is now top-of-page, so its only Retry must live here, not far below in the + * Vitals slot). + */ + isError?: boolean; + /** Refetch the one shared batch query. Recovers both strips at once. */ + refetch?: () => void; className?: string; } @@ -86,9 +96,51 @@ function RingTile({ ); } -export function WellnessScores({ read, isLoading, className }: ScoreStripProps) { +export function WellnessScores({ + read, + isLoading, + isError = false, + refetch, + className, +}: ScoreStripProps) { const { t } = useTranslations(); + // A failed shared batch must read as an error, not as "no scores" — the + // strip is top-of-page, so the only Retry lives here (mirrors the Vitals + // error card styling; one Retry recovers both strips). + if (isError) { + return ( +
+ +
+ {t("insights.derived.scores.loadError")} + +
+
+ ); + } + const readiness = read({ metric: "READINESS" }); const sleep = read({ metric: "SLEEP_SCORE" }); const recovery = read({ metric: "RECOVERY_SCORE" }); diff --git a/src/components/insights/insights-layout-shell.tsx b/src/components/insights/insights-layout-shell.tsx index c04fa0a26..2f6d31349 100644 --- a/src/components/insights/insights-layout-shell.tsx +++ b/src/components/insights/insights-layout-shell.tsx @@ -1,6 +1,7 @@ "use client"; import { useMemo, type ReactNode } from "react"; +import { useSelectedLayoutSegment } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; import { useTranslations } from "@/lib/i18n/context"; @@ -57,6 +58,15 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) { const flags = useFeatureFlags(); const advisorEnabled = isAuthenticated && flags.enabled && flags.briefing; + + // v1.12.6 — routes that carry their own purpose-built disclaimer must NOT + // also show the generic footer below (otherwise two disclaimers stack). + // The Coach full page renders its own `composerDisclaimer` under the + // composer, so its segment is excluded here. Every other segment — the + // overview (null segment), the metric sub-pages, mood, medications, the + // raw values table — keeps the single generic footer. + const segment = useSelectedLayoutSegment(); + const showDisclaimerFooter = segment !== "coach"; const advisor = useInsightsAdvisorQuery(advisorEnabled); // Shared analytics fetch — the layout shell only reads @@ -118,13 +128,16 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) { caveats (health score, correlations, mood relations, derived cards, rhythm events) stay on their own cards — those convey real, non-generic information. Other waves remove the generic line from the - overview / mood / medications pages; this footer is where it lives. */} -

- {t("insights.disclaimer.footer")} -

+ overview / mood / medications pages; this footer is where it lives. + Suppressed on the Coach route, which carries its own disclaimer. */} + {showDisclaimerFooter ? ( +

+ {t("insights.disclaimer.footer")} +

+ ) : null} ); }