diff --git a/messages/de.json b/messages/de.json index a7421034d..62f56f668 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2751,7 +2751,8 @@ "headline": "Wenn dieses Muster anhält — die nächsten {days} Tage", "range": "Projiziert auf rund {low}–{high} {unit} in {days} Tagen.", "caveat": "Eine Fortschreibung deines eigenen jüngsten Trends, keine Vorhersage. Das schattierte Band wird breiter und zeigt, wie schnell die Unsicherheit wächst.", - "insufficient": "Noch kein klar genuger Trend für eine Projektion — erfasse weiter, dann erscheint eine Projektion, sobald das jüngste Muster deutlich genug ist." + "insufficient": "Noch kein klar genuger Trend für eine Projektion — erfasse weiter, dann erscheint eine Projektion, sobald das jüngste Muster deutlich genug ist.", + "daysNeeded": "Noch etwa {count} Tage Erfassung nötig, bis eine Projektion erscheinen kann." } }, "cardioFitness": { diff --git a/messages/en.json b/messages/en.json index 4d483d001..28e8ae249 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2751,7 +2751,8 @@ "headline": "If this pattern continues — the next {days} days", "range": "Projected around {low}–{high} {unit} in {days} days.", "caveat": "A projection of your own recent trend, not a prediction. The shaded band widens to show how quickly the uncertainty grows.", - "insufficient": "Not enough of a clear trend to project yet — keep tracking and a projection will appear once the recent pattern is strong enough." + "insufficient": "Not enough of a clear trend to project yet — keep tracking and a projection will appear once the recent pattern is strong enough.", + "daysNeeded": "About {count} more days of tracking needed before a projection can appear." } }, "cardioFitness": { diff --git a/messages/es.json b/messages/es.json index a2e3a9331..fa606ac4b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2751,7 +2751,8 @@ "headline": "Si este patrón continúa: los próximos {days} días", "range": "Proyectado en torno a {low}–{high} {unit} en {days} días.", "caveat": "Una proyección de tu propia tendencia reciente, no una predicción. La banda sombreada se ensancha para mostrar la rapidez con que crece la incertidumbre.", - "insufficient": "Aún no hay una tendencia lo bastante clara para proyectar: sigue registrando y aparecerá una proyección cuando el patrón reciente sea lo bastante marcado." + "insufficient": "Aún no hay una tendencia lo bastante clara para proyectar: sigue registrando y aparecerá una proyección cuando el patrón reciente sea lo bastante marcado.", + "daysNeeded": "Faltan unos {count} días más de registro para que pueda aparecer una proyección." } }, "cardioFitness": { diff --git a/messages/fr.json b/messages/fr.json index 3e43c1dbd..fb6ca414a 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2751,7 +2751,8 @@ "headline": "Si ce schéma se poursuit — les {days} prochains jours", "range": "Projeté autour de {low}–{high} {unit} dans {days} jours.", "caveat": "Une projection de votre propre tendance récente, pas une prédiction. La bande ombrée s’élargit pour montrer la vitesse à laquelle l’incertitude augmente.", - "insufficient": "Pas encore de tendance assez nette pour projeter — continuez à enregistrer et une projection apparaîtra dès que le schéma récent sera suffisamment marqué." + "insufficient": "Pas encore de tendance assez nette pour projeter — continuez à enregistrer et une projection apparaîtra dès que le schéma récent sera suffisamment marqué.", + "daysNeeded": "Encore environ {count} jours d’enregistrement nécessaires avant qu’une projection puisse apparaître." } }, "cardioFitness": { diff --git a/messages/it.json b/messages/it.json index a1fa85e22..d03d132d7 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2751,7 +2751,8 @@ "headline": "Se questo schema continua — i prossimi {days} giorni", "range": "Proiettato intorno a {low}–{high} {unit} tra {days} giorni.", "caveat": "Una proiezione della tua tendenza recente, non una previsione. La banda ombreggiata si allarga per mostrare quanto rapidamente cresce l’incertezza.", - "insufficient": "Non c’è ancora una tendenza abbastanza chiara da proiettare — continua a registrare e comparirà una proiezione quando lo schema recente sarà abbastanza marcato." + "insufficient": "Non c’è ancora una tendenza abbastanza chiara da proiettare — continua a registrare e comparirà una proiezione quando lo schema recente sarà abbastanza marcato.", + "daysNeeded": "Servono ancora circa {count} giorni di registrazione prima che possa comparire una proiezione." } }, "cardioFitness": { diff --git a/messages/pl.json b/messages/pl.json index eef8da327..8229ce793 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2751,7 +2751,8 @@ "headline": "Jeśli ten wzorzec się utrzyma — następne {days} dni", "range": "Rzutowane na około {low}–{high} {unit} za {days} dni.", "caveat": "Rzut Twojego własnego ostatniego trendu, nie prognoza. Zacieniony pas poszerza się, pokazując, jak szybko rośnie niepewność.", - "insufficient": "Jeszcze brak wystarczająco wyraźnego trendu do rzutowania — rejestruj dalej, a rzut pojawi się, gdy ostatni wzorzec będzie dość wyraźny." + "insufficient": "Jeszcze brak wystarczająco wyraźnego trendu do rzutowania — rejestruj dalej, a rzut pojawi się, gdy ostatni wzorzec będzie dość wyraźny.", + "daysNeeded": "Potrzeba jeszcze około {count} dni rejestrowania, zanim pojawi się rzut." } }, "cardioFitness": { diff --git a/src/app/insights/blood-pressure/page.tsx b/src/app/insights/blood-pressure/page.tsx index 87fd0d52c..994bfbc96 100644 --- a/src/app/insights/blood-pressure/page.tsx +++ b/src/app/insights/blood-pressure/page.tsx @@ -118,7 +118,6 @@ export default function InsightsBlutdruckPage() { > {/* The period-over-period delta keys on the systolic series — the canonical BP figure the targets + status surfaces lead with. */} - + {/* v1.12.0 — range pills + period-over-period delta below the chart. */} + diff --git a/src/app/insights/bmi/page.tsx b/src/app/insights/bmi/page.tsx index a405931e1..5b74d78dc 100644 --- a/src/app/insights/bmi/page.tsx +++ b/src/app/insights/bmi/page.tsx @@ -121,7 +121,6 @@ export default function InsightsBmiPage() { page's controls. BMI is derived from WEIGHT, so the period-over-period delta keys on the underlying weight series the chart reuses. */} - + {/* v1.12.0 — range pills + period-over-period delta below the chart. */} + diff --git a/src/app/insights/pulse/page.tsx b/src/app/insights/pulse/page.tsx index becdb4e0a..fb7ab30c7 100644 --- a/src/app/insights/pulse/page.tsx +++ b/src/app/insights/pulse/page.tsx @@ -132,7 +132,6 @@ export default function InsightsPulsPage() { coachLaunch showAllValuesType="PULSE" > - + {/* v1.12.0 — range pills + period-over-period delta below the chart. */} + diff --git a/src/app/insights/sleep/page.tsx b/src/app/insights/sleep/page.tsx index f2a4642c2..ca88e2191 100644 --- a/src/app/insights/sleep/page.tsx +++ b/src/app/insights/sleep/page.tsx @@ -80,10 +80,11 @@ export default function InsightsSchlafPage() { min / max / median / mean strip would duplicate it and read in an awkward unit. */ > - - + {/* v1.12.0 — range pills + period-over-period delta below the chart. */} + + {/* diff --git a/src/app/insights/weight/page.tsx b/src/app/insights/weight/page.tsx index b9e4824b5..a48e6e8a7 100644 --- a/src/app/insights/weight/page.tsx +++ b/src/app/insights/weight/page.tsx @@ -90,7 +90,6 @@ export default function InsightsGewichtPage() { coachLaunch showAllValuesType="WEIGHT" > - + {/* v1.12.0 — range pills + period-over-period delta below the chart. */} + @@ -109,6 +110,7 @@ export default function InsightsGewichtPage() { unit="kg" color="#bd93f9" enabled={!isEmpty} + compact /> {t("insights.heroGenerated", { time: formatRelativeTime(updatedAt, t), diff --git a/src/components/insights/derived/trajectory-forecast-card.tsx b/src/components/insights/derived/trajectory-forecast-card.tsx index bff5eb41b..af1e6cd6c 100644 --- a/src/components/insights/derived/trajectory-forecast-card.tsx +++ b/src/components/insights/derived/trajectory-forecast-card.tsx @@ -47,6 +47,16 @@ import type { DerivedProvenance } from "@/lib/insights/derived/types"; const TRAJECTORY_METRIC = "TRAJECTORY"; +/** + * Mirror of `TRAJECTORY_MIN_HISTORY_DAYS` (`src/lib/insights/derived/trajectory.ts`). + * Re-declared as a client-side literal rather than value-imported because the + * engine module pulls the rollup/baseline server graph in at its top level — + * importing the constant would drag that graph into the client bundle (the same + * reason `TrajectoryValue` is a type-only import). Keep this in lockstep with + * the engine; the unit test pins both at 14. + */ +const TRAJECTORY_MIN_HISTORY_DAYS = 14; + /** A single value-scale used to render stored values in the display unit. */ function scaled(value: number, scale: number): number { return value * scale; @@ -63,6 +73,14 @@ interface TrajectoryForecastCardProps { color?: string; /** Gate the underlying derived read (e.g. on the auth/empty flag). */ enabled?: boolean; + /** + * v1.12.0 — density by render context. Sub-pages pass `compact` so the + * card sheds the `min-h-48` floor + drops to a shorter chart and a tighter + * no-trend state (a too-tall "1 of 1" empty band was the maintainer flag). + * The Insights OVERVIEW grid omits it so the tile KEEPS its current size + * for grid symmetry — the default is the unchanged, roomier geometry. + */ + compact?: boolean; className?: string; } @@ -96,16 +114,25 @@ function TrajectoryProvenance({ /** Card shell — uppercase label + provenance affordance + body children. */ function CardShell({ provenance, + compact = false, children, }: { provenance?: DerivedProvenance; + compact?: boolean; children: React.ReactNode; }) { const { t } = useTranslations(); return (
@@ -119,18 +146,21 @@ function CardShell({ } /** CLS-safe skeleton matching the resolved card geometry. */ -function CardSkeleton() { +function CardSkeleton({ compact = false }: { compact?: boolean }) { return (