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); }