From 9c9a99aea48899d06f74de77eb89a50b50765c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 21:58:19 +0200 Subject: [PATCH] fix(insights): mirror scatter-chart aspect ratio on correlation-card skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skeleton was a fixed `h-[180px]` while the loaded scatter chart is `aspect-square min-h-[180px] sm:aspect-[3/2] sm:h-auto` — on `sm+` the loaded chart paints ~60 px taller than the placeholder reserved, which CLS-shifted insights cold mount. Mirror the quartet on the skeleton so it reserves the right space at every breakpoint. --- .../correlation-card-skeleton.test.ts | 49 +++++++++++++++++++ src/components/insights/correlation-card.tsx | 7 ++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/components/insights/__tests__/correlation-card-skeleton.test.ts diff --git a/src/components/insights/__tests__/correlation-card-skeleton.test.ts b/src/components/insights/__tests__/correlation-card-skeleton.test.ts new file mode 100644 index 000000000..8f8a5414f --- /dev/null +++ b/src/components/insights/__tests__/correlation-card-skeleton.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +/** + * v1.4.43 W11 — correlation-card scatter skeleton class guard. + * + * The skeleton must mirror the loaded chart's responsive aspect- + * ratio classes (`scatter-correlation-chart.tsx:100`) so the + * placeholder reserves the same space across breakpoints. A simple + * substring check is enough — the only way to break this is to + * remove the `aspect-square` / `sm:aspect-[3/2]` / `sm:h-auto` / + * `min-h-[180px]` quartet, which the audit recommended pinning. + */ +describe("correlation-card scatter skeleton mirrors loaded chart", () => { + const cardSrc = readFileSync( + join(process.cwd(), "src/components/insights/correlation-card.tsx"), + "utf8", + ); + const chartSrc = readFileSync( + join( + process.cwd(), + "src/components/charts/scatter-correlation-chart.tsx", + ), + "utf8", + ); + + it("skeleton declares the chart's aspect-ratio quartet", () => { + // Capture only the dynamic-loader block so the assertion can't + // accidentally match a class somewhere else in the file. + const dynamicBlock = cardSrc.match(/loading: \(\) => \([\s\S]*?\),/); + expect(dynamicBlock).not.toBeNull(); + const block = dynamicBlock?.[0] ?? ""; + expect(block).toContain("aspect-square"); + expect(block).toContain("min-h-[180px]"); + expect(block).toContain("sm:aspect-[3/2]"); + expect(block).toContain("sm:h-auto"); + expect(block).toContain("motion-reduce:animate-none"); + }); + + it("loaded scatter chart still owns the same aspect-ratio quartet", () => { + // If this regression ever silently flips on the loaded chart, the + // skeleton mirror loses its purpose. Pin both sides. + expect(chartSrc).toContain("aspect-square"); + expect(chartSrc).toContain("min-h-[180px]"); + expect(chartSrc).toContain("sm:aspect-[3/2]"); + expect(chartSrc).toContain("sm:h-auto"); + }); +}); diff --git a/src/components/insights/correlation-card.tsx b/src/components/insights/correlation-card.tsx index c06ab39a0..72b7785f9 100644 --- a/src/components/insights/correlation-card.tsx +++ b/src/components/insights/correlation-card.tsx @@ -31,7 +31,12 @@ const ScatterCorrelationChart = dynamic( { ssr: false, loading: () => ( -
+ // v1.4.43 W11 — mirror the loaded chart's responsive aspect- + // ratio classes (`scatter-correlation-chart.tsx:100`) so the + // skeleton reserves the same space across breakpoints. The + // legacy `h-[180px]` was ~60 px shorter than the painted chart + // at `sm+` and caused a visible CLS shift on insights mount. +
), }, );