From 008a8fba3e4710da4d3fc403ef2db811ae8adcb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 10:16:36 +0200 Subject: [PATCH] revert(charts): remove gradient background fills (clean lines only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Apple-Health-style gradient area underneath each chart line muddied the visual. Drop the linearGradient defs and the area-fill primitives on every chart wrapper and let the line stroke speak for itself. Tooltip, animation, and Dracula tokens stay as they were. Also delete the chart-gradient module — there are no remaining call sites once the fills come out. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../charts/__tests__/chart-gradient.test.tsx | 67 -------------- .../__tests__/health-chart-polish.test.tsx | 36 ++------ .../medication-chart-polish.test.tsx | 18 ++-- .../__tests__/mood-chart-polish.test.tsx | 21 ++--- src/components/charts/chart-gradient.tsx | 87 ------------------ src/components/charts/health-chart.tsx | 91 +------------------ .../charts/medication-compliance-chart.tsx | 46 +--------- src/components/charts/mood-chart.tsx | 53 +---------- 8 files changed, 38 insertions(+), 381 deletions(-) delete mode 100644 src/components/charts/__tests__/chart-gradient.test.tsx delete mode 100644 src/components/charts/chart-gradient.tsx diff --git a/src/components/charts/__tests__/chart-gradient.test.tsx b/src/components/charts/__tests__/chart-gradient.test.tsx deleted file mode 100644 index ca07bb841..000000000 --- a/src/components/charts/__tests__/chart-gradient.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { renderToStaticMarkup } from "react-dom/server"; - -import { - ChartLinearGradient, - chartGradientFill, -} from "../chart-gradient"; - -/** - * v1.4.16 B1a — pin the contract for the gradient primitive that every - * chart wrapper now consumes: - * - * - emits an SVG `` block with the requested id, - * - top stop carries the metric colour at ~35 % alpha by default, - * - bottom stop fades to 0 %, - * - the helper `chartGradientFill()` builds the matching `url(#id)` - * reference Recharts expects on ``. - */ - -describe("", () => { - it("renders a vertical linearGradient with the supplied id and colour token", () => { - // Wrap in because outside an SVG triggers a React - // hydration warning under jsdom; here we render to static markup so - // the wrapper just keeps the output well-formed. - const html = renderToStaticMarkup( - - - , - ); - - expect(html).toContain('id="bp-gradient"'); - // Vertical gradient — top to bottom. - expect(html).toContain('x1="0"'); - expect(html).toContain('y1="0"'); - expect(html).toContain('y2="1"'); - // The colour token round-trips into a `var(...)` reference. - expect(html).toContain("var(--dracula-purple)"); - // Default opacity: 0.35 at top, 0 at bottom. - expect(html).toContain('stop-opacity="0.35"'); - expect(html).toContain('stop-opacity="0"'); - // Marker used by visual-regression / e2e smoke tests. - expect(html).toContain('data-slot="chart-linear-gradient"'); - expect(html).toContain('data-color-var="--dracula-purple"'); - }); - - it("respects custom topOpacity / bottomOpacity overrides", () => { - const html = renderToStaticMarkup( - - - , - ); - expect(html).toContain('stop-opacity="0.5"'); - expect(html).toContain('stop-opacity="0.05"'); - }); -}); - -describe("chartGradientFill()", () => { - it("builds a Recharts-shaped url() reference", () => { - expect(chartGradientFill("bp-gradient")).toBe("url(#bp-gradient)"); - expect(chartGradientFill("mood")).toBe("url(#mood)"); - }); -}); diff --git a/src/components/charts/__tests__/health-chart-polish.test.tsx b/src/components/charts/__tests__/health-chart-polish.test.tsx index e9b63be1b..32956d2fa 100644 --- a/src/components/charts/__tests__/health-chart-polish.test.tsx +++ b/src/components/charts/__tests__/health-chart-polish.test.tsx @@ -112,15 +112,13 @@ describe("computePersonalBaseline()", () => { }); }); -describe(" v1.4.16 B1a polish", () => { - it("emits a sibling-SVG gradient block addressable by Recharts via url(#id)", async () => { - // ResponsiveContainer renders empty during SSR (its dimensions - // depend on the live DOM), so Recharts' own SSR output ships no - // . To keep the gradient SSR-discoverable (and so visual - // regressions can target it without a browser), the wrapper emits - // an external `` next to ResponsiveContainer - // containing the gradient. Recharts picks it up via `url(#id)` - // because SVG IDs are document-scoped. +describe(" v1.4.18 clean-line revert", () => { + it("does NOT paint a gradient fill under the line (clean line only)", async () => { + // v1.4.18 reverts B1a's gradient-area treatment. Marc explicitly + // rejected the soft-color fill below the line: the line itself is + // the chart, no painted background under it. We assert the SSR + // output ships no `` defs and no `chart-gradient` + // primitive markers. const { I18nProvider } = await import("@/lib/i18n/context"); const { HealthChart } = await import("../health-chart"); @@ -134,22 +132,8 @@ describe(" v1.4.16 B1a polish", () => { , ); - expect(html).toContain('data-slot="chart-linear-gradient"'); - }); - - it("namespaces the gradient id by metric type so multi-metric charts don't collide", async () => { - const { I18nProvider } = await import("@/lib/i18n/context"); - const { HealthChart } = await import("../health-chart"); - - const html = renderToStaticMarkup( - - - , - ); - expect(html).toMatch(/id="chart-gradient-BLOOD_PRESSURE_SYS"/); + expect(html).not.toContain("data-slot=\"chart-linear-gradient\""); + expect(html).not.toContain("chart-gradient-BLOOD_PRESSURE_SYS"); + expect(html).not.toContain("linearGradient"); }); }); diff --git a/src/components/charts/__tests__/medication-chart-polish.test.tsx b/src/components/charts/__tests__/medication-chart-polish.test.tsx index d1c7588cc..0ddc11377 100644 --- a/src/components/charts/__tests__/medication-chart-polish.test.tsx +++ b/src/components/charts/__tests__/medication-chart-polish.test.tsx @@ -2,12 +2,11 @@ import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.16 B1a — Medication compliance chart polish contract. + * v1.4.18 — Medication compliance chart clean-line revert. * - * The medication chart already has 7-day-trend + target lines from A6; - * this commit adds the rest of the v1.4.16 visual leap so the surface - * matches BP/weight/pulse/mood: gradient fill, animated first render, - * rich tooltip primitive. + * Rolls back B1a's gradient fill. The animation, target / threshold + * lines, rich tooltip, and 7-day trend chip stay (those weren't + * rejected). */ const sampleData = vi.hoisted(() => @@ -26,8 +25,8 @@ vi.mock("@/hooks/use-auth", () => ({ useAuth: () => ({ isAuthenticated: true, user: null, isLoading: false }), })); -describe(" v1.4.16 B1a polish", () => { - it("emits the gradient defs sibling-SVG block", async () => { +describe(" v1.4.18 clean-line revert", () => { + it("does NOT paint a gradient fill under the line", async () => { const { I18nProvider } = await import("@/lib/i18n/context"); const { MedicationComplianceChart } = await import( "../medication-compliance-chart" @@ -39,7 +38,8 @@ describe(" v1.4.16 B1a polish", () => { , ); - expect(html).toContain('data-slot="chart-linear-gradient"'); - expect(html).toContain('id="chart-gradient-medication"'); + expect(html).not.toContain("data-slot=\"chart-linear-gradient\""); + expect(html).not.toContain("chart-gradient-medication"); + expect(html).not.toContain("linearGradient"); }); }); diff --git a/src/components/charts/__tests__/mood-chart-polish.test.tsx b/src/components/charts/__tests__/mood-chart-polish.test.tsx index 931599f5f..ec6d578a6 100644 --- a/src/components/charts/__tests__/mood-chart-polish.test.tsx +++ b/src/components/charts/__tests__/mood-chart-polish.test.tsx @@ -2,14 +2,12 @@ import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.16 B1a — MoodChart Apple-Health-style polish contract. + * v1.4.18 — MoodChart clean-line revert (gradient + emoji rolled back). * - * The mood chart picks up the same gradient-fill + animation polish - * the rest of the dashboard charts get; the differentiator is per- - * point emoji glyphs (a smiley, frown, etc.) instead of plain dots so - * the user can scan a week at a glance — Apple's Health app uses the - * same affordance for "Mindfulness Minutes" and similar mood-adjacent - * surfaces. + * Marc rejected B1a's coloured area fill below the chart line and the + * smiley/emoji glyphs at every data point. The line stroke + Dracula + * tokens stay; the chart now paints a clean monotone line with simple + * Recharts dots. */ const sampleMoodData = vi.hoisted(() => { @@ -43,8 +41,8 @@ vi.mock("@/hooks/use-auth", () => ({ }), })); -describe(" v1.4.16 B1a polish", () => { - it("emits the gradient defs sibling-SVG block", async () => { +describe(" v1.4.18 clean-line revert", () => { + it("does NOT paint a gradient fill under the line", async () => { const { I18nProvider } = await import("@/lib/i18n/context"); const { MoodChart } = await import("../mood-chart"); @@ -54,7 +52,8 @@ describe(" v1.4.16 B1a polish", () => { , ); - expect(html).toContain('data-slot="chart-linear-gradient"'); - expect(html).toContain('id="chart-gradient-mood"'); + expect(html).not.toContain("data-slot=\"chart-linear-gradient\""); + expect(html).not.toContain("chart-gradient-mood"); + expect(html).not.toContain("linearGradient"); }); }); diff --git a/src/components/charts/chart-gradient.tsx b/src/components/charts/chart-gradient.tsx deleted file mode 100644 index 34dbb01a1..000000000 --- a/src/components/charts/chart-gradient.tsx +++ /dev/null @@ -1,87 +0,0 @@ -"use client"; - -import type { ReactElement } from "react"; - -/** - * Reusable Apple-Health-style linear gradient for area / line fills. - * - * Recharts ships an Area component that accepts a `fill="url(#id)"` prop - * pointing at a `` defined inside an SVG `` block. - * v1.4.16 B1a paints a soft Dracula-token gradient under the line so the - * chart reads like the Apple Health "Trends" cards: the metric colour at - * the top of the area fades down to nearly transparent at the bottom. - * - * Why a wrapper component: - * 1. The gradient lives in `` and must be sibling-rendered to - * ``. Recharts allows arbitrary children in - * `LineChart` / `AreaChart` and renders them inside its ``, so - * a small named primitive keeps the call sites readable - * (``) - * instead of inlining 8 lines of `...` per - * chart. - * 2. Tokens stay canonical — only the `colorVar` prop is exposed; the - * stop offsets / opacities are baked in to enforce a consistent - * look across all 5 charts (BP, weight, pulse, mood, medication). - * 3. SSR safe — pure SVG, no DOM APIs touched. The unit test renders - * it via `renderToStaticMarkup`. - * - * Defaults follow the Apple Health pattern: ~35 % alpha at the top of - * the area, 0 % at the bottom. The pinned stops also stay clear of the - * grid lines so a domain like "60 .. 110 mmHg" still reads as a - * gradient, not a flat colour wash. - */ - -export interface ChartLinearGradientProps { - /** SVG id used by Recharts: ``. */ - id: string; - /** - * CSS variable name (e.g. `--dracula-purple`). The component wraps it - * in `var(...)` so callers can pass the token verbatim. - */ - colorVar: string; - /** Alpha at the top of the area. Defaults to 0.35. */ - topOpacity?: number; - /** Alpha at the bottom of the area. Defaults to 0.0. */ - bottomOpacity?: number; -} - -/** - * Renders a `` block. - * - * The gradient runs vertically (x1=0,x2=0,y1=0,y2=1) so it shades the - * area top-down, matching how Apple Health paints the metric colour - * over the chart background. - */ -export function ChartLinearGradient({ - id, - colorVar, - topOpacity = 0.35, - bottomOpacity = 0, -}: ChartLinearGradientProps): ReactElement { - const fill = `var(${colorVar})`; - - return ( - - - - - - - ); -} - -/** - * Build the `fill` attribute string Recharts expects when wiring an - * `` to a `` defined alongside. - */ -export function chartGradientFill(id: string): string { - return `url(#${id})`; -} diff --git a/src/components/charts/health-chart.tsx b/src/components/charts/health-chart.tsx index dfbb5229c..50d5ef2c3 100644 --- a/src/components/charts/health-chart.tsx +++ b/src/components/charts/health-chart.tsx @@ -5,7 +5,6 @@ import { useAuth } from "@/hooks/use-auth"; import { ResponsiveContainer, ComposedChart, - Area, Line, XAxis, YAxis, @@ -17,10 +16,6 @@ import { } from "recharts"; import { Loader2 } from "lucide-react"; import { useState, useMemo, useId } from "react"; -import { - ChartLinearGradient, - chartGradientFill, -} from "./chart-gradient"; import { RichChartTooltip, type RichTooltipRow } from "./chart-tooltip"; import { ChartEmptyState } from "./chart-empty-state"; import { prefersReducedMotion } from "@/lib/charts/reduced-motion"; @@ -987,73 +982,15 @@ export function HealthChart({ ) : null} - {/* v1.4.16 B1a — sibling SVG block. - ResponsiveContainer is empty during SSR so a gradient - defined inside its child isn't discoverable in - static-rendered markup. Emitting the gradient in a - standalone hidden next to the chart keeps it - available document-wide; Recharts' - looks the gradient up by id at paint time and finds it - regardless of which SVG it lives in. */} - - + {/* v1.4.18 — gradient fill removed per Marc's feedback. + The clean line is the chart; no painted background under + it. */}
- {/* Inline gradient defs as Recharts children — same id - space as the sibling SVG above, so even browsers - that scope SVG-id resolution to the local document - fragment (Safari edge cases) still find a match. */} - - {types.map((type, i) => ( - - - - - ))} - )} - {/* v1.4.16 B1a — gradient-filled Area painted *under* - the line. Each type gets its own gradient - referenced by id. The Area's stroke is set to - transparent so only the fill paints; the line on - top of it carries the actual stroke. */} - {types.map((type) => ( - - ))} + {/* v1.4.18 — gradient Area removed; only the clean + line below paints the metric. */} {types.map((type, i) => ( )} - {/* v1.4.16 B1a — sibling SVG block for SSR-discoverable - gradient. */} - - + {/* v1.4.18 — gradient defs removed; clean line only. */} {isLoading ? (
@@ -331,18 +315,6 @@ export function MedicationComplianceChart({ data={chartData} margin={{ top: 10, right: 8, bottom: 8, left: 8 }} > - - - - - - - {/* v1.4.16 B1a — gradient-filled Area painted under the - line. Animated on first render unless the user opts - for reduced motion. */} - + {/* v1.4.18 — gradient Area removed; clean line only. */} - {/* v1.4.16 B1a — sibling SVG block for the gradient - primitive. SSR-discoverable; Recharts also picks up the id - client-side via the inline below. */} - + {/* v1.4.18 — gradient defs removed; clean line only. */} {isLoading ? (
@@ -641,26 +625,6 @@ export function MoodChart({ data={chartDataWithCompare ?? chartData} margin={{ top: 10, right: 8, bottom: 8, left: 8 }} > - - - - - - - {/* v1.4.16 B1a — gradient-filled Area painted under - the line. */} - + {/* v1.4.18 — gradient Area removed; clean line only. */}