fix(gki): draw the index chart in a chart token

The custom metric chart drew its line in --primary, which put the index
in the action colour right under the ketone series drawn in a chart
token. Give the chart an optional series colour, default primary so a
custom metric looks as before, and draw the index in --chart-4.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 00:52:41 +01:00
1 parent 35b4e75a53
commit 8aa8379861
4 files changed
+78 -12

No files matched your search

@@ -88,7 +88,10 @@ describe("DELETE /api/cycle/feed-subscription", () => {
m.findMany.mockResolvedValue([OLD, SUCCESSOR]);
await (DELETE as unknown as () => Promise<Response>)();
expect(m.audit.mock.calls.map((c) => c[1].details.feedId)).toEqual([
const calls = m.audit.mock.calls as unknown as Array<
[string, { details: { feedId: string } }]
>;
expect(calls.map((c) => c[1].details.feedId)).toEqual([
OLD.id,
SUCCESSOR.id,
]);
@@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import { renderWithApp } from "../../../../tests/browser/render";
import { CustomMetricChart } from "../custom-metric-chart";
/**
* The line takes the series colour it is given (the GKI draws in a chart
* token beside the ketone series) and keeps primary when it is given none,
* so a custom metric's chart looks as it always has.
*/
const NOW = Date.now();
const ENTRIES = [3, 2, 1].map((daysAgo, i) => ({
id: `e${i}`,
customMetricId: "",
value: 2 + i,
unit: "",
measuredAt: new Date(NOW - daysAgo * 86_400_000).toISOString(),
note: null,
}));
async function lineStroke(color?: string): Promise<string | null> {
const { screen } = await renderWithApp(
<div style={{ width: 600 }}>
<CustomMetricChart
name="Index"
unit=""
targetLow={null}
targetHigh={null}
decimals={1}
entries={ENTRIES as never}
{...(color ? { color } : {})}
/>
</div>,
);
const curve = () => screen.container.querySelector(".recharts-line-curve");
await expect.poll(curve).not.toBeNull();
return curve()!.getAttribute("stroke");
}
describe("<CustomMetricChart> series colour", () => {
it("draws the line in the colour it is given", async () => {
expect(await lineStroke("var(--chart-4)")).toBe("var(--chart-4)");
});
it("keeps primary by default", async () => {
expect(await lineStroke()).toBe("var(--primary)");
});
});
@@ -58,6 +58,22 @@ interface ChartPoint {
value: number;
}
/** The values and any target bound, padded by a tenth of their spread. */
function yDomainOf(
points: readonly ChartPoint[],
targetLow: number | null,
targetHigh: number | null,
): [number, number] {
const candidates = points.map((p) => p.value);
if (targetLow != null) candidates.push(targetLow);
if (targetHigh != null) candidates.push(targetHigh);
if (candidates.length === 0) return [0, 1];
const min = Math.min(...candidates);
const max = Math.max(...candidates);
const pad = (max - min || Math.abs(max) || 1) * 0.1;
return [min - pad, max + pad];
}
export function CustomMetricChart({
entries,
name,
@@ -65,6 +81,7 @@ export function CustomMetricChart({
targetLow,
targetHigh,
decimals,
color = "var(--primary)",
}: {
/** The metric's name: the day's focus label and the data table's caption. */
name: string;
@@ -74,6 +91,8 @@ export function CustomMetricChart({
targetLow: number | null;
targetHigh: number | null;
decimals: number | null;
/** The series colour; a chart token where it sits beside other series. */
color?: string;
}) {
const { t } = useTranslations();
const fmt = useFormatters();
@@ -99,16 +118,10 @@ export function CustomMetricChart({
}));
}, [entries, range, nowMs]);
const yDomain = useMemo<[number, number]>(() => {
const candidates = points.map((p) => p.value);
if (targetLow != null) candidates.push(targetLow);
if (targetHigh != null) candidates.push(targetHigh);
if (candidates.length === 0) return [0, 1];
const min = Math.min(...candidates);
const max = Math.max(...candidates);
const pad = (max - min || Math.abs(max) || 1) * 0.1;
return [min - pad, max + pad];
}, [points, targetLow, targetHigh]);
const yDomain = useMemo(
() => yDomainOf(points, targetLow, targetHigh),
[points, targetLow, targetHigh],
);
// v1.42 — each value opens its day (the calendar day of its instant in
// the record's zone) through the doors every day-linked chart has.
@@ -134,7 +147,7 @@ export function CustomMetricChart({
chartDays.openIndex !== undefined ? points[chartDays.openIndex] : undefined;
const animate = !prefersReducedMotion();
const primary = "var(--primary)";
const primary = color;
return (
<div className="space-y-3">
@@ -96,6 +96,7 @@ export function GkiCard() {
targetLow={null}
targetHigh={null}
decimals={1}
color="var(--chart-4)"
entries={points.map((p) => ({
id: p.id,
customMetricId: "",