From 2b512501db4130632f341c3e9bec25275e7dcfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 14 Sep 2026 16:51:33 +0200 Subject: [PATCH] fix(insights): leave mood out of the trends row when its module is off The tab strip already hides the Mood pill when the mood module is off, but the trends row still charted mood, both from a briefing finding and in its blood pressure / weight / mood fallback. selectTrendCharts now takes the metrics to leave out, and the overview passes mood when the module is off. Refs #975 --- src/app/insights/page-client.tsx | 6 ++++++ src/components/insights/trends-row.tsx | 9 +++++++- .../__tests__/trend-chart-select.test.ts | 21 +++++++++++++++++++ src/lib/insights/trend-chart-select.ts | 13 ++++++++++-- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/app/insights/page-client.tsx b/src/app/insights/page-client.tsx index 936a33c09..d161b00e6 100644 --- a/src/app/insights/page-client.tsx +++ b/src/app/insights/page-client.tsx @@ -74,6 +74,9 @@ function BlockSkeleton({ ); } +/** Stable reference so the trends row does not re-select on every render. */ +const HIDDEN_MOOD = ["mood"] as const; + const DailyBriefing = dynamic( () => import("@/components/insights/daily-briefing").then((mod) => ({ @@ -513,6 +516,9 @@ export default function InsightsPageClient() { trends: ( diff --git a/src/components/insights/trends-row.tsx b/src/components/insights/trends-row.tsx index 7ed4e976d..85d8255a8 100644 --- a/src/components/insights/trends-row.tsx +++ b/src/components/insights/trends-row.tsx @@ -13,6 +13,7 @@ import { selectTrendCharts, type TrendAnnotationKey, type TrendChartConfig, + type SelectTrendChartsOptions, } from "@/lib/insights/trend-chart-select"; import { TrendAnnotation, @@ -65,6 +66,11 @@ const MoodChart = dynamic( ); interface TrendsRowProps { + /** + * Metrics to leave out because their module is switched off (for + * example `mood`). Passed straight to `selectTrendCharts`. + */ + hiddenMetrics?: SelectTrendChartsOptions["hiddenMetrics"]; /** * Daily briefing payload. Drives the chart set: the row charts the * metrics the briefing flags, in order, deduped + capped. `null` / @@ -132,13 +138,14 @@ export function TrendsRow({ annotations, confidence, loading = false, + hiddenMetrics, }: TrendsRowProps) { const { t } = useTranslations(); // v1.8.5 — derive the chart set from the briefing. No new fetch: the // briefing payload is already on the page (advisor cache), so this is // a pure read that respects the v1.8.3 anti-freeze contract. - const charts = selectTrendCharts(briefing); + const charts = selectTrendCharts(briefing, { hiddenMetrics }); // v1.4.36 W2 T3 — derive the tri-state status per metric from the // advisor's loading flag + the annotation presence. Pending wins diff --git a/src/lib/insights/__tests__/trend-chart-select.test.ts b/src/lib/insights/__tests__/trend-chart-select.test.ts index 209422697..eb812dddb 100644 --- a/src/lib/insights/__tests__/trend-chart-select.test.ts +++ b/src/lib/insights/__tests__/trend-chart-select.test.ts @@ -157,3 +157,24 @@ describe("selectTrendCharts", () => { expect(TREND_CHART_CONFIG.steps?.detailHref).toBe("/insights/steps"); }); }); + +describe("selectTrendCharts — hidden metrics", () => { + it("drops mood from the fallback triple when the mood module is off", () => { + const charts = selectTrendCharts(null, { hiddenMetrics: ["mood"] }); + expect(charts.map((c) => c.metric)).not.toContain("mood"); + expect(charts).toHaveLength(2); + }); + + it("skips a hidden metric the briefing flags", () => { + const charts = selectTrendCharts(briefing(["mood", "weight"]), { + hiddenMetrics: ["mood"], + }); + expect(charts.map((c) => c.metric)).not.toContain("mood"); + expect(charts).toHaveLength(1); + }); + + it("keeps mood when nothing is hidden", () => { + const charts = selectTrendCharts(null, { hiddenMetrics: [] }); + expect(charts.map((c) => c.metric)).toContain("mood"); + }); +}); diff --git a/src/lib/insights/trend-chart-select.ts b/src/lib/insights/trend-chart-select.ts index 31e04a6aa..2c3114ecb 100644 --- a/src/lib/insights/trend-chart-select.ts +++ b/src/lib/insights/trend-chart-select.ts @@ -267,16 +267,24 @@ export const DEFAULT_TREND_CHART_CAP = 3; export interface SelectTrendChartsOptions { /** Max number of charts to return. Defaults to {@link DEFAULT_TREND_CHART_CAP}. */ cap?: number; + /** + * Metrics whose module is switched off. They are skipped on both the + * briefing path and the fallback triple, so a disabled module never + * charts on the overview. + */ + hiddenMetrics?: ReadonlyArray; } function configsFor( metrics: ReadonlyArray, cap: number, + hidden: ReadonlySet, ): TrendChartConfig[] { const seen = new Set(); const out: TrendChartConfig[] = []; for (const metric of metrics) { if (out.length >= cap) break; + if (hidden.has(metric)) continue; // module switched off const config = TREND_CHART_CONFIG[metric]; if (!config) continue; // metric has no standalone trend chart if (seen.has(config.metric)) continue; // dedupe @@ -301,14 +309,15 @@ export function selectTrendCharts( options: SelectTrendChartsOptions = {}, ): TrendChartConfig[] { const cap = Math.max(1, options.cap ?? DEFAULT_TREND_CHART_CAP); + const hidden = new Set(options.hiddenMetrics ?? []); const findingMetrics = briefing?.keyFindings?.map((f) => f.sourceMetric) ?? []; - const fromBriefing = configsFor(findingMetrics, cap); + const fromBriefing = configsFor(findingMetrics, cap, hidden); if (fromBriefing.length > 0) { return fromBriefing; } - return configsFor(DEFAULT_TREND_METRICS, cap); + return configsFor(DEFAULT_TREND_METRICS, cap, hidden); }