diff --git a/apps/app/src/components/template-library/template-card-pie.test.tsx b/apps/app/src/components/template-library/template-card-pie.test.tsx
new file mode 100644
index 0000000..63305d8
--- /dev/null
+++ b/apps/app/src/components/template-library/template-card-pie.test.tsx
@@ -0,0 +1,34 @@
+import { afterEach, expect, it, vi } from "vitest";
+import { cleanup, render } from "@testing-library/react";
+import { TemplateCard } from "./template-card";
+afterEach(cleanup);
+function preview(data: { label: string; value: number }[]) {
+ return render()
+ .container.querySelector("svg")!;
+}
+it.each([
+ [[{ label: "Only category", value: 10 }], "#3b82f6"],
+ [[{ label: "Empty", value: 0 }, { label: "All revenue", value: 10 }, { label: "Other", value: 0 }], "#8b5cf6"],
+] as const)("draws a full disk when one category represents the entire total", (data, color) => {
+ const svg = preview([...data]);
+ const disk = svg.querySelector("circle");
+ expect(disk).not.toBeNull();
+ expect(Number(disk!.getAttribute("cx"))).toBe(60);
+ expect(Number(disk!.getAttribute("cy"))).toBe(60);
+ expect(Number(disk!.getAttribute("r"))).toBe(50);
+ expect(disk!.getAttribute("fill")).toBe(color);
+});
+it("retains separate slices for multiple positive categories", () => {
+ const svg = preview([{ label: "First", value: 3 }, { label: "Second", value: 1 }]);
+ expect(svg.querySelectorAll("path")).toHaveLength(2);
+ expect(svg.querySelector("circle")).toBeNull();
+});
+
+it.each([
+ { data: [] },
+ { data: [{ label: "Only empty category", value: 0 }] },
+ { data: [{ label: "First", value: 0 }, { label: "Second", value: 0 }] },
+])("keeps empty and zero-total previews unpainted", ({ data }) => {
+ expect(preview(data)).toBeNull();
+});
diff --git a/apps/app/src/components/template-library/template-card.tsx b/apps/app/src/components/template-library/template-card.tsx
index 23b9824..2c0cfbd 100644
--- a/apps/app/src/components/template-library/template-card.tsx
+++ b/apps/app/src/components/template-library/template-card.tsx
@@ -67,6 +67,10 @@ function PieChartPreview({ data }: { data: { label: string; value: number }[] })
return acc;
}, []);
const slices = data.map((d, i) => {
+ // A full SVG arc with coincident endpoints has no painted area.
+ if (d.value === total) {
+ return ;
+ }
const startAngle = ((cumulativeAngles[i] - d.value) / total) * 2 * Math.PI - Math.PI / 2;
const endAngle = (cumulativeAngles[i] / total) * 2 * Math.PI - Math.PI / 2;
const largeArc = d.value / total > 0.5 ? 1 : 0;