mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
feat(insights): polished summary typography with inline-chart sparklines
Bumps the InsightAdvisorCard summary surface to text-base / leading- relaxed and constrains it to max-w-prose so the prose stays readable on a wide viewport. Wraps the existing chart-token renderer in a new mini flag so the summary's inline charts shrink to sparkline form (h-[140px], no range tabs); per-finding inline charts keep the full chart so the user can still interact with the data series. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9e8be4b359
commit
b76351e1a8
2 files changed
+73
-9
No files matched your search
@@ -177,3 +177,47 @@ describe("<InsightAdvisorCard> — inline chart tokens", () => {
|
||||
expect(html).toContain("Weight stable");
|
||||
});
|
||||
});
|
||||
|
||||
describe("<InsightAdvisorCard> — 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"/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -229,7 +229,19 @@ const INLINE_CHART_TITLE_KEYS: Record<string, string> = {
|
||||
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" ? (
|
||||
<MoodChart />
|
||||
<MoodChart mini={mini} />
|
||||
) : (
|
||||
<HealthChart types={[metric]} title={title} />
|
||||
<HealthChart types={[metric]} title={title} mini={mini} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -431,12 +444,19 @@ export function InsightAdvisorCard({
|
||||
|
||||
{/* Summary — prose first, then any allowlisted charts the model
|
||||
* inlined via `metric:<TYPE>` tokens. Tokens are dropped from the
|
||||
* visible text so the literal substring never reaches the DOM. */}
|
||||
<div className="space-y-3">
|
||||
<p className="text-muted-foreground text-sm leading-relaxed">
|
||||
{stripChartTokens(insight.summary)}
|
||||
</p>
|
||||
<InlineCharts tokens={parseChartTokens(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. */}
|
||||
<div
|
||||
data-slot="insight-summary"
|
||||
className="text-foreground/90 max-w-prose space-y-3 text-base leading-relaxed"
|
||||
>
|
||||
<p>{stripChartTokens(insight.summary)}</p>
|
||||
<InlineCharts tokens={parseChartTokens(insight.summary)} mini />
|
||||
</div>
|
||||
|
||||
{/* Findings — top finding gets a hero treatment, rest are a compact list */}
|
||||
|
||||
Reference in new issue
Block a user