diff --git a/src/components/insights/__tests__/insight-advisor-card.test.tsx b/src/components/insights/__tests__/insight-advisor-card.test.tsx index 33d722cc4..b7ecdd98e 100644 --- a/src/components/insights/__tests__/insight-advisor-card.test.tsx +++ b/src/components/insights/__tests__/insight-advisor-card.test.tsx @@ -177,3 +177,47 @@ describe(" — inline chart tokens", () => { expect(html).toContain("Weight stable"); }); }); + +describe(" — summary typography polish (B1b)", () => { + it("paints the summary slot with a constrained max-width for readability", () => { + const html = render( + buildInsight({ + summary: "Your BP averaged 132/84 over the last 30 days.", + }), + ); + expect(html).toMatch(/data-slot="insight-summary"/); + // The summary slot carries a Tailwind max-w-* class so prose + // doesn't stretch full-bleed on a wide viewport. + expect(html).toMatch(/max-w-(prose|2xl|3xl)/); + }); + + it("uses the polished prose typography (text-base, leading-relaxed)", () => { + const html = render( + buildInsight({ + summary: "All metrics within target.", + }), + ); + // A larger font + relaxed line-height for the page-anchored + // summary; the per-card content uses text-sm. + expect(html).toMatch( + /data-slot="insight-summary"[^>]*class="[^"]*text-base/, + ); + expect(html).toMatch( + /data-slot="insight-summary"[^>]*class="[^"]*leading-relaxed/, + ); + }); + + it("renders summary inline charts as mini sparklines (data-mini=true)", () => { + const html = render( + buildInsight({ + summary: "BP averaging 132/84 metric:BLOOD_PRESSURE_SYS today", + }), + ); + // The summary's inline-chart wrapper must carry `data-mini="true"` + // so the chart shrinks to sparkline form. Per-finding charts use + // the default detail mode. + expect(html).toMatch( + /data-slot="insight-summary"[\s\S]*?data-mini="true"/, + ); + }); +}); diff --git a/src/components/insights/insight-advisor-card.tsx b/src/components/insights/insight-advisor-card.tsx index 29a3ea29f..36753b40e 100644 --- a/src/components/insights/insight-advisor-card.tsx +++ b/src/components/insights/insight-advisor-card.tsx @@ -229,7 +229,19 @@ const INLINE_CHART_TITLE_KEYS: Record = { COMPLIANCE: "insights.navMedication", }; -function InlineCharts({ tokens }: { tokens: ChartToken[] }) { +function InlineCharts({ + tokens, + mini = false, +}: { + tokens: ChartToken[]; + /** + * v1.4.16 phase B1b — render the embedded charts as compact + * sparklines (no range tabs, h-[140px]). The summary surface uses + * this so the prose stays the headline; the finding-card surface + * keeps the full chart so the user can interact with the data. + */ + mini?: boolean; +}) { const { t } = useTranslations(); if (tokens.length === 0) return null; @@ -255,11 +267,12 @@ function InlineCharts({ tokens }: { tokens: ChartToken[] }) { key={token} data-slot="insight-inline-chart" data-metric={metric} + data-mini={mini ? "true" : "false"} > {kind === "mood" ? ( - + ) : ( - + )} ); @@ -431,12 +444,19 @@ export function InsightAdvisorCard({ {/* Summary — prose first, then any allowlisted charts the model * inlined via `metric:` tokens. Tokens are dropped from the - * visible text so the literal substring never reaches the DOM. */} -
-

- {stripChartTokens(insight.summary)} -

- + * visible text so the literal substring never reaches the DOM. + * + * v1.4.16 phase B1b — typography polish: larger font (text-base), + * relaxed line-height, max-w-prose so the summary doesn't span + * full-bleed on a wide viewport. Wraps the inline-chart strip + * in the same constrained column so the rendered sparklines + * align with the prose. */} +
+

{stripChartTokens(insight.summary)}

+
{/* Findings — top finding gets a hero treatment, rest are a compact list */}