feat(insights): elevate coincident-deviation to a dedicated "Today's signal" card

Promote the COINCIDENT_DEVIATION flag from a fire-only tile buried in the
below-the-fold vitals grid to an always-mounted headline card at the top of
the Insights overview. The card renders four calm states off the single
existing derived payload — building-baselines, all-clear, watch, fired — with
no new engine math, route, or schema change.

Keep it calm and clinically safe: at most an amber band, never red, no score,
no chart, no push. Soften a fired flag to the watch tone when the backing
history is thin. The fired/watch states carry the load-bearing "possible
factors, never a cause / not a diagnosis" framing and reuse ProvenanceExplainer
+ CoverageMeter + the band tokens verbatim.

Remove the now-redundant tile, its batch token, and the hasRenderableVital
branch from the vitals grid so the signal is not shown twice.
This commit is contained in:
Marc-André Bombeck committed 2026-06-03 19:29:36 +02:00
1 parent fe099acd67
commit 789ec60d97
11 files changed
+562 -102

No files matched your search

+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Mehrere Vitalwerte außerhalb der Baseline",
"summary": "{count} deiner Vitalwerte lagen heute außerhalb ihres persönlichen Bereichs.",
"vitals": "Außerhalb des Bereichs: {list}"
"vitals": "Außerhalb des Bereichs: {list}",
"cardTitle": "Signal des Tages",
"allClear": "Alle deine Vitalwerte liegen heute in ihrem persönlichen Bereich.",
"allClearMeta": "{count} deiner Vitalwerte gegen deinen eigenen typischen Bereich geprüft.",
"watch": "Ein Vitalwert liegt heute außerhalb seines üblichen Bereichs.",
"watchVital": "Außerhalb des Bereichs: {vital}. Oft nur einmalig — behalte es in den nächsten Tagen im Blick.",
"firedHeadline": "{count} deiner Vitalwerte liegen heute außerhalb ihres persönlichen Bereichs.",
"factors": "Mögliche Faktoren — nie eine Ursache: ein hartes Training, schlechter Schlaf, Alkohol, Höhe, Stress oder Krankheit. Das ist ein Hinweis aus deinen eigenen Baselines, keine Diagnose.",
"building": "Deine persönlichen Baselines entstehen — erfasse ein paar Tage mehr, dann erscheint dies."
}
},
"cardioFitness": {
+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Several vitals off baseline",
"summary": "{count} of your vitals sat outside their personal range today.",
"vitals": "Outside range: {list}"
"vitals": "Outside range: {list}",
"cardTitle": "Today's signal",
"allClear": "All your vitals are within their personal range today.",
"allClearMeta": "Checked {count} of your vitals against your own typical range.",
"watch": "One vital is outside its usual range today.",
"watchVital": "Outside range: {vital}. Often just a one-off — worth a glance over the next few days.",
"firedHeadline": "{count} of your vitals are outside their personal range today.",
"factors": "Possible factors — never a cause: a hard workout, poor sleep, alcohol, altitude, stress, or illness. This is awareness from your own baselines, not a diagnosis.",
"building": "Building your personal baselines — track a few more days and this will appear."
}
},
"cardioFitness": {
+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Varias constantes fuera de la base",
"summary": "Hoy, {count} de tus constantes quedaron fuera de su rango personal.",
"vitals": "Fuera de rango: {list}"
"vitals": "Fuera de rango: {list}",
"cardTitle": "Señal de hoy",
"allClear": "Hoy todas tus constantes están dentro de su rango personal.",
"allClearMeta": "Se revisaron {count} de tus constantes frente a tu propio rango habitual.",
"watch": "Hoy una constante está fuera de su rango habitual.",
"watchVital": "Fuera de rango: {vital}. A menudo es algo puntual; conviene vigilarlo los próximos días.",
"firedHeadline": "Hoy, {count} de tus constantes están fuera de su rango personal.",
"factors": "Posibles factores, nunca una causa: un entrenamiento intenso, dormir mal, alcohol, altitud, estrés o una enfermedad. Es un aviso a partir de tus propias bases, no un diagnóstico.",
"building": "Construyendo tus bases personales: registra unos días más y esto aparecerá."
}
},
"cardioFitness": {
+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Plusieurs constantes hors référence",
"summary": "Aujourd’hui, {count} de vos constantes sont sorties de leur plage personnelle.",
"vitals": "Hors plage : {list}"
"vitals": "Hors plage : {list}",
"cardTitle": "Signal du jour",
"allClear": "Aujourd’hui, toutes vos constantes sont dans leur plage personnelle.",
"allClearMeta": "{count} de vos constantes vérifiées par rapport à votre propre plage habituelle.",
"watch": "Aujourd’hui, une constante est hors de sa plage habituelle.",
"watchVital": "Hors plage : {vital}. Souvent ponctuel — à surveiller ces prochains jours.",
"firedHeadline": "Aujourd’hui, {count} de vos constantes sont hors de leur plage personnelle.",
"factors": "Facteurs possibles — jamais une cause : un entraînement intense, un mauvais sommeil, l’alcool, l’altitude, le stress ou une maladie. C’est un repère issu de vos propres références, pas un diagnostic.",
"building": "Construction de vos références personnelles — enregistrez quelques jours de plus et cela apparaîtra."
}
},
"cardioFitness": {
+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Più parametri fuori dalla base",
"summary": "Oggi {count} dei tuoi parametri vitali sono usciti dal loro intervallo personale.",
"vitals": "Fuori intervallo: {list}"
"vitals": "Fuori intervallo: {list}",
"cardTitle": "Segnale di oggi",
"allClear": "Oggi tutti i tuoi parametri vitali sono nel loro intervallo personale.",
"allClearMeta": "Controllati {count} dei tuoi parametri rispetto al tuo intervallo abituale.",
"watch": "Oggi un parametro è fuori dal suo intervallo abituale.",
"watchVital": "Fuori intervallo: {vital}. Spesso è un episodio isolato — tienilo d’occhio nei prossimi giorni.",
"firedHeadline": "Oggi {count} dei tuoi parametri vitali sono fuori dal loro intervallo personale.",
"factors": "Possibili fattori — mai una causa: un allenamento intenso, poco sonno, alcol, altitudine, stress o una malattia. È un avviso basato sui tuoi parametri, non una diagnosi.",
"building": "Stiamo costruendo i tuoi riferimenti personali — registra ancora qualche giorno e comparirà."
}
},
"cardioFitness": {
+9 -1
View File
@@ -2632,7 +2632,15 @@
"coincident": {
"label": "Kilka parametrów poza bazą",
"summary": "Dziś {count} Twoich parametrów życiowych znalazło się poza osobistym zakresem.",
"vitals": "Poza zakresem: {list}"
"vitals": "Poza zakresem: {list}",
"cardTitle": "Sygnał dnia",
"allClear": "Dziś wszystkie Twoje parametry życiowe mieszczą się w osobistym zakresie.",
"allClearMeta": "Sprawdzono {count} Twoich parametrów względem Twojego typowego zakresu.",
"watch": "Dziś jeden parametr jest poza swoim zwykłym zakresem.",
"watchVital": "Poza zakresem: {vital}. Często to jednorazowy odczyt — warto obserwować przez kilka dni.",
"firedHeadline": "Dziś {count} Twoich parametrów życiowych jest poza osobistym zakresem.",
"factors": "Możliwe czynniki — nigdy przyczyna: intensywny trening, słaby sen, alkohol, wysokość, stres lub choroba. To wskazówka z Twoich własnych baz, nie diagnoza.",
"building": "Budujemy Twoje osobiste bazy — zarejestruj jeszcze kilka dni, a to się pojawi."
}
},
"cardioFitness": {
+20
View File
@@ -97,6 +97,24 @@ const RhythmEventsCard = dynamic(
})),
{ ssr: false },
);
// v1.10.3 — "Today's signal" headline card. Promotes COINCIDENT_DEVIATION from
// a buried below-the-fold tile to the top-of-overview daily read (the
// always-present Apple/WHOOP/Oura pattern). Deferred behind `next/dynamic`; it
// owns its own derived-metric query and renders four calm states. A
// fixed-height skeleton matches the resolved card so the top of the page does
// not shift while the chunk + data resolve.
const CoincidentDeviationCard = dynamic(
() =>
import("@/components/insights/coincident-deviation-card").then((mod) => ({
default: mod.CoincidentDeviationCard,
})),
{
ssr: false,
loading: () => (
<div className="bg-card border-border h-[7.5rem] animate-pulse rounded-xl border motion-reduce:animate-none" />
),
},
);
/**
* v1.4.25 W4d — Insights mother page.
@@ -246,6 +264,8 @@ export default function InsightsPage() {
healthScore={analytics?.healthScore ?? undefined}
/>
<CoincidentDeviationCard enabled={isAuthenticated} />
<div className="flex justify-end">
<Button
size="sm"
@@ -0,0 +1,194 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import type { DerivedMetricResponse } from "@/components/insights/derived/use-derived-metric";
import type { CoincidentDeviationValue } from "@/lib/insights/derived/coincident-deviation";
// Drive the card by stubbing the single derived-metric hook it reads.
const useDerivedMetric = vi.fn();
vi.mock("@/components/insights/derived/use-derived-metric", () => ({
useDerivedMetric: (...a: unknown[]) => useDerivedMetric(...a),
}));
// The provenance explainer pulls a mobile flag + the i18n context; render it
// in its desktop (Popover) form for the static markup so its trigger is in the
// tree. The card test only asserts the trigger is present, not its open state.
vi.mock("@/hooks/use-is-mobile", () => ({ useIsMobile: () => false }));
import { CoincidentDeviationCard } from "../coincident-deviation-card";
function render(node: React.ReactNode) {
return renderToStaticMarkup(
<I18nProvider initialLocale="en">{node}</I18nProvider>,
);
}
type Resp = DerivedMetricResponse<CoincidentDeviationValue>;
function deviation(
type: string,
outside: boolean,
): CoincidentDeviationValue["vitals"][number] {
return {
type: type as never,
value: outside ? 70 : 55,
center: 55,
low: 48,
high: 62,
outside,
direction: outside ? "above" : "in",
};
}
function ok(
value: CoincidentDeviationValue,
band: "high" | "medium" | "low" | "draft" = "high",
): Resp {
return {
metric: "COINCIDENT_DEVIATION",
status: "ok",
value,
coverage: { requiredInputs: 5, presentInputs: 5, historyDays: 30, missing: [] },
confidence: { score: 90, band },
provenance: {
inputs: ["RESTING_HEART_RATE", "RESPIRATORY_RATE"],
source: "DAY",
windowDays: 30,
computedAt: "2026-06-03T00:00:00.000Z",
},
reason: null,
};
}
function insufficient(): Resp {
return {
metric: "COINCIDENT_DEVIATION",
status: "insufficient",
value: null,
coverage: { requiredInputs: 2, presentInputs: 1, historyDays: 0, missing: [] },
confidence: null,
provenance: {
inputs: ["RESTING_HEART_RATE"],
source: "live",
windowDays: 30,
computedAt: "2026-06-03T00:00:00.000Z",
},
reason: "too_few_banded_vitals",
};
}
function mock(data: Resp | undefined) {
useDerivedMetric.mockReturnValue({ data });
}
beforeEach(() => vi.clearAllMocks());
describe("<CoincidentDeviationCard>", () => {
it("renders a CLS-safe skeleton while the read is in flight", () => {
mock(undefined);
const html = render(<CoincidentDeviationCard />);
expect(html).toContain('data-slot="coincident-deviation-card-skeleton"');
expect(html).not.toContain('data-slot="coincident-deviation-card"');
});
it("renders the insufficient (building baselines) state, never an alarm", () => {
mock(insufficient());
const html = render(<CoincidentDeviationCard />);
expect(html).toContain('data-state="insufficient"');
expect(html).toContain('data-slot="coincident-building"');
expect(html).toContain('data-slot="coverage-meter"');
// Calm, not amber/red.
expect(html).not.toContain("border-warning");
expect(html).not.toContain("text-destructive");
});
it("renders the all-clear state with the count checked", () => {
mock(
ok({
fired: false,
day: "2026-06-03",
vitals: [deviation("RESTING_HEART_RATE", false), deviation("WEIGHT", false)],
contributing: [],
}),
);
const html = render(<CoincidentDeviationCard />);
expect(html).toContain('data-state="all-clear"');
expect(html).toContain("All your vitals are within their personal range");
expect(html).toContain("text-success");
// No alarm tone.
expect(html).not.toContain("border-warning");
expect(html).not.toContain("text-destructive");
});
it("renders the watch state for a single out-of-band vital, not the fired tone", () => {
mock(
ok({
fired: false,
day: "2026-06-03",
vitals: [deviation("RESTING_HEART_RATE", true), deviation("WEIGHT", false)],
contributing: [deviation("RESTING_HEART_RATE", true)],
}),
);
const html = render(<CoincidentDeviationCard />);
expect(html).toContain('data-state="watch"');
expect(html).toContain("One vital is outside its usual range");
// Watch never uses the fired/alert border or red.
expect(html).not.toContain("border-warning");
expect(html).not.toContain("text-destructive");
});
it("renders the fired state with the named vitals + the possible-factors line", () => {
mock(
ok({
fired: true,
day: "2026-06-03",
vitals: [
deviation("RESTING_HEART_RATE", true),
deviation("RESPIRATORY_RATE", true),
],
contributing: [
deviation("RESTING_HEART_RATE", true),
deviation("RESPIRATORY_RATE", true),
],
}),
);
const html = render(<CoincidentDeviationCard />);
expect(html).toContain('data-state="fired"');
expect(html).toContain('data-slot="coincident-factors"');
// The load-bearing framing line.
expect(html).toContain("Possible factors — never a cause");
expect(html).toContain("not a diagnosis");
// At most amber — never destructive/red.
expect(html).toContain("border-warning");
expect(html).not.toContain("text-destructive");
// The provenance affordance reaches the card.
expect(html).toContain('data-slot="provenance-explainer-trigger"');
});
it("softens a fired flag to the watch tone when history is thin", () => {
mock(
ok(
{
fired: true,
day: "2026-06-03",
vitals: [
deviation("RESTING_HEART_RATE", true),
deviation("RESPIRATORY_RATE", true),
],
contributing: [
deviation("RESTING_HEART_RATE", true),
deviation("RESPIRATORY_RATE", true),
],
},
"low",
),
);
const html = render(<CoincidentDeviationCard />);
// Two contributing + fired, but thin history → watch tone, not fired.
expect(html).toContain('data-state="watch"');
expect(html).not.toContain('data-state="fired"');
expect(html).not.toContain("border-warning");
// The coverage meter accompanies the softened flag.
expect(html).toContain('data-slot="coverage-meter"');
});
});
@@ -0,0 +1,284 @@
"use client";
import { CheckCircle2, AlertTriangle, Activity } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { cn } from "@/lib/utils";
import { MEASUREMENT_TYPE_LABEL_KEYS } from "@/components/measurements/measurement-list-meta";
import { CoverageMeter } from "@/components/insights/derived/coverage-meter";
import { ProvenanceExplainer } from "@/components/insights/derived/provenance-explainer";
import { METRIC_PROVENANCE } from "@/components/insights/derived/standards";
import { useDerivedMetric } from "@/components/insights/derived/use-derived-metric";
// Type-only — the compute payload never drags the server graph into the bundle
// (the v1.9.0 lesson, mirrored at vitals-dashboard.tsx).
import type { CoincidentDeviationValue } from "@/lib/insights/derived/coincident-deviation";
import type { DerivedProvenance } from "@/lib/insights/derived/types";
/**
* v1.10.3 — "Today's signal" headline card.
*
* Promotes the COINCIDENT_DEVIATION flag from one buried below-the-fold vitals
* tile (painted only when it fired) to a dedicated, always-mounted card at the
* top of the Insights overview, matching the always-present pattern Apple
* Vitals / WHOOP Health Monitor / Oura Symptom Radar use. The signal is the
* daily headline read, not an alarm: an "all clear" day is itself the
* reassuring product.
*
* It renders four calm states off the SINGLE existing `Derived` payload — no
* new engine math, no new route, no schema change:
* - insufficient (< 2 banded vitals) → "building your baselines" + coverage.
* - all-clear (ok, !fired, 0 contributing) → a green check + the count
* checked.
* - watch (ok, !fired, 1 contributing) → a calm, non-alert line naming the
* one vital.
* - fired (ok, fired, ≥ 2 contributing) → an amber awareness card naming the
* vitals + the load-bearing "possible factors — never a cause" line.
*
* Restraint guarantees: at most the `warning` (amber) band — never
* `destructive`/red, never a score, never a chart (so no Recharts), never a
* push. When `confidence.band` is thin (low/draft) the fired tone is softened
* to the watch tone + the coverage meter, so a multi-signal flag from too
* little history never reads as a confident verdict.
*/
const COINCIDENT_METRIC = "COINCIDENT_DEVIATION";
interface CoincidentDeviationCardProps {
/** Gate the underlying derived-metric read (e.g. on the auth flag). */
enabled?: boolean;
className?: string;
}
/** The four calm visual states, derived from the single payload. */
type SignalState = "insufficient" | "all-clear" | "watch" | "fired";
/** The provenance ⓘ explainer for the coincident flag, wired from the map. */
function CoincidentProvenance({
provenance,
}: {
provenance: DerivedProvenance;
}) {
const { t } = useTranslations();
const meta = METRIC_PROVENANCE.COINCIDENT_DEVIATION;
const method = (
<>
{meta.caveatKey ? (
<span className="text-warning block font-medium">
{t(meta.caveatKey)}
</span>
) : null}
{t(meta.methodKey)}
</>
);
return (
<ProvenanceExplainer
provenance={provenance}
method={method}
standard={meta.standard}
/>
);
}
/**
* The card shell — uppercase label + the provenance affordance, with the
* state-specific body as children. Keeps every state on one card geometry so
* the page reserves a stable footprint.
*/
function CardShell({
state,
provenance,
children,
}: {
state: SignalState;
provenance?: DerivedProvenance;
children: React.ReactNode;
}) {
const { t } = useTranslations();
return (
<div
data-slot="coincident-deviation-card"
data-state={state}
className={cn(
"bg-card flex w-full min-w-0 flex-col gap-2 rounded-xl border p-4 md:p-6",
// At most amber — never destructive/red. The fired state borders
// warning; every calmer state keeps the neutral card border.
state === "fired" ? "border-warning/40" : "border-border",
)}
>
<div className="flex items-center justify-between gap-2">
<span className="text-muted-foreground truncate text-xs font-medium tracking-wide uppercase">
{t("insights.derived.coincident.cardTitle")}
</span>
{provenance ? <CoincidentProvenance provenance={provenance} /> : null}
</div>
{children}
</div>
);
}
/** A fixed-footprint skeleton matching the resolved card (CLS-safe). */
function CardSkeleton() {
return (
<div
data-slot="coincident-deviation-card-skeleton"
aria-hidden="true"
className="bg-card border-border flex w-full min-w-0 flex-col gap-3 rounded-xl border p-4 md:p-6"
>
<div className="bg-muted/40 h-3 w-28 rounded" />
<div className="bg-muted/40 h-5 w-3/4 rounded" />
<div className="bg-muted/40 h-3 w-1/2 rounded" />
</div>
);
}
export function CoincidentDeviationCard({
enabled = true,
className,
}: CoincidentDeviationCardProps) {
const { t } = useTranslations();
const { data } = useDerivedMetric<CoincidentDeviationValue>(
COINCIDENT_METRIC,
{ enabled },
);
// CLS-safe placeholder while the single read is in flight.
if (!data) {
return (
<div className={className}>
<CardSkeleton />
</div>
);
}
// Building the baselines — fewer than two banded vitals. Calm, never an
// alarm, never blank.
if (data.status === "insufficient") {
return (
<div className={className}>
<CardShell state="insufficient" provenance={data.provenance}>
<p
className="text-muted-foreground text-sm"
data-slot="coincident-building"
>
{t("insights.derived.coincident.building")}
</p>
<CoverageMeter coverage={data.coverage} />
</CardShell>
</div>
);
}
const v = data.value!;
const contributing = v.contributing;
const names = contributing
.map((d) => {
const labelKey = MEASUREMENT_TYPE_LABEL_KEYS[d.type];
return labelKey ? t(labelKey) : d.type;
})
.join(", ");
// Thin history behind the deepest contributing vital → soften a fired flag
// to the watch tone rather than presenting a confident multi-signal verdict
// from too little data. Read straight off the existing payload.
const band = data.confidence?.band;
const thinHistory = band === "low" || band === "draft";
// Map the single payload onto one of the four calm states.
const state: SignalState =
contributing.length === 0
? "all-clear"
: v.fired && contributing.length >= 2 && !thinHistory
? "fired"
: "watch";
if (state === "all-clear") {
return (
<div className={className}>
<CardShell state="all-clear" provenance={data.provenance}>
<div className="flex items-start gap-2">
<CheckCircle2
className="text-success mt-0.5 h-4 w-4 shrink-0"
aria-hidden="true"
/>
<div className="min-w-0 space-y-1">
<p
className="text-foreground text-sm font-medium"
data-slot="coincident-headline"
>
{t("insights.derived.coincident.allClear")}
</p>
<p className="text-muted-foreground text-xs leading-snug">
{t("insights.derived.coincident.allClearMeta", {
count: v.vitals.length,
})}
</p>
</div>
</div>
</CardShell>
</div>
);
}
if (state === "watch") {
return (
<div className={className}>
<CardShell state="watch" provenance={data.provenance}>
<div className="flex items-start gap-2">
<Activity
className="text-muted-foreground mt-0.5 h-4 w-4 shrink-0"
aria-hidden="true"
/>
<div className="min-w-0 space-y-1">
<p
className="text-foreground text-sm font-medium"
data-slot="coincident-headline"
>
{t("insights.derived.coincident.watch")}
</p>
<p className="text-muted-foreground text-xs leading-snug">
{t("insights.derived.coincident.watchVital", { vital: names })}
</p>
</div>
</div>
{thinHistory ? <CoverageMeter coverage={data.coverage} /> : null}
</CardShell>
</div>
);
}
// fired — amber awareness, named vitals, possible-factors line (mandatory).
return (
<div className={className}>
<CardShell state="fired" provenance={data.provenance}>
<div className="flex items-start gap-2">
<AlertTriangle
className="text-warning mt-0.5 h-4 w-4 shrink-0"
aria-hidden="true"
/>
<div className="min-w-0 space-y-1">
<p
className="text-foreground text-sm font-medium"
data-slot="coincident-headline"
>
{t("insights.derived.coincident.firedHeadline", {
count: contributing.length,
})}
</p>
<p
className="text-muted-foreground text-xs leading-snug"
data-slot="coincident-vitals"
>
{t("insights.derived.coincident.vitals", { list: names })}
</p>
</div>
</div>
<p
className="text-muted-foreground text-xs leading-snug"
data-slot="coincident-factors"
>
{t("insights.derived.coincident.factors")}
</p>
</CardShell>
</div>
);
}
@@ -173,36 +173,16 @@ describe("<VitalsDashboard>", () => {
expect(html).not.toContain('data-metric="BMI"');
});
it("surfaces the coincident-deviation flag when it fired", () => {
it("does not read the coincident-deviation flag (now the top-of-overview card)", () => {
// The flag moved to the dedicated `CoincidentDeviationCard`; the dashboard
// batch no longer requests it and the grid never paints it.
let requestedCoincident = false;
mockBatch((token) => {
if (token.metric === "COINCIDENT_DEVIATION") {
return ok({
fired: true,
day: "2026-06-02",
vitals: [],
contributing: [
{ type: "RESTING_HEART_RATE", value: 70, center: 55, low: 48, high: 62, outside: true, direction: "above" },
{ type: "RESPIRATORY_RATE", value: 20, center: 14, low: 12, high: 16, outside: true, direction: "above" },
],
});
}
return insufficient("no_readings_in_window");
});
const html = render(<VitalsDashboard />);
expect(html).toContain('data-metric="COINCIDENT_DEVIATION"');
expect(html).toContain('data-state="fired"');
// The provenance affordance reaches the flag.
expect(html).toContain('data-slot="provenance-explainer-trigger"');
});
it("hides the coincident-deviation flag when it did not fire", () => {
mockBatch((token) => {
if (token.metric === "COINCIDENT_DEVIATION") {
return ok({ fired: false, day: "2026-06-02", vitals: [], contributing: [] });
}
if (token.metric === "COINCIDENT_DEVIATION") requestedCoincident = true;
return insufficient("no_readings_in_window");
});
const html = render(<VitalsDashboard />);
expect(requestedCoincident).toBe(false);
expect(html).not.toContain('data-metric="COINCIDENT_DEVIATION"');
});
});
@@ -29,9 +29,7 @@ import type { FitnessAgeValue } from "@/lib/insights/derived/fitness-age";
import type { VascularAgeDeltaValue } from "@/lib/insights/derived/vascular-age";
import type { HrvBalanceValue } from "@/lib/insights/derived/hrv-balance";
import type { BmiValue } from "@/lib/insights/derived/bmi";
import type { CoincidentDeviationValue } from "@/lib/insights/derived/coincident-deviation";
import type { DerivedProvenance } from "@/lib/insights/derived/types";
import { AlertTriangle } from "lucide-react";
/**
* v1.10.0 — the Vitals dashboard surface (Apple-Health-Highlights grid).
@@ -358,59 +356,6 @@ function BmiTile({ read, isLoading }: TileProps) {
);
}
/**
* Coincident-deviation flag tile — surfaces ONLY when the flag fired today
* (≥ N vitals outside their personal band on the same day). Lists the
* contributing vitals as plain text and carries the provenance affordance
* (the Hampel/Leys median ± k·MAD basis + the descriptive-not-clinical
* caveat). When the flag has not fired the tile un-mounts entirely — never
* an alarming empty card.
*/
function CoincidentDeviationTile({ read, isLoading }: TileProps) {
const { t } = useTranslations();
const data = read<CoincidentDeviationValue>({
metric: "COINCIDENT_DEVIATION",
});
if (isLoading || !data || data.status !== "ok" || !data.value) return null;
const v = data.value;
if (!v.fired || v.contributing.length === 0) return null;
const names = v.contributing
.map((d) => {
const labelKey = MEASUREMENT_TYPE_LABEL_KEYS[d.type];
return labelKey ? t(labelKey) : d.type;
})
.join(", ");
return (
<div
data-slot="vitals-tile"
data-metric="COINCIDENT_DEVIATION"
data-state="fired"
className="bg-card border-warning/40 flex h-full w-full min-w-0 flex-col gap-2 rounded-xl border p-4 md:p-6"
>
<div className="flex items-center justify-between gap-2">
<span className="text-muted-foreground truncate text-xs font-medium tracking-wide uppercase">
{t("insights.derived.coincident.label")}
</span>
<MetricProvenance
metric="COINCIDENT_DEVIATION"
provenance={data.provenance}
/>
<AlertTriangle className="text-warning h-4 w-4 shrink-0" />
</div>
<p className="text-foreground text-sm" data-slot="coincident-summary">
{t("insights.derived.coincident.summary", {
count: v.contributing.length,
})}
</p>
<p className="text-muted-foreground text-xs leading-snug">
{t("insights.derived.coincident.vitals", { list: names })}
</p>
</div>
);
}
/**
* A loading placeholder occupying the resolved tile footprint so the grid
* reserves its final height and the real tiles drop in without a layout
@@ -442,17 +387,6 @@ function VitalsTileSkeleton() {
* above — if a new tile gate changes, reflect it here.
*/
function hasRenderableVital(read: DerivedBatchRead): boolean {
const coincident = read<CoincidentDeviationValue>({
metric: "COINCIDENT_DEVIATION",
});
if (
coincident?.status === "ok" &&
coincident.value?.fired &&
coincident.value.contributing.length > 0
) {
return true;
}
const fitness = read<FitnessAgeValue>({ metric: "FITNESS_AGE" });
if (fitness?.status === "ok" && fitness.value) return true;
@@ -492,8 +426,10 @@ function hasRenderableVital(read: DerivedBatchRead): boolean {
/**
* The full set of tokens the dashboard reads in one batch — the five
* wellness scores + the four derived re-frames + the coincident-deviation
* flag + one baseline per vital (minus HRV, which has its own balance tile).
* wellness scores + the four derived re-frames + one baseline per vital
* (minus HRV, which has its own balance tile). The coincident-deviation
* flag is no longer read here: it is now the dedicated "Today's signal"
* card at the top of the overview (`CoincidentDeviationCard`).
*/
function dashboardTokens(): DerivedBatchToken[] {
const tokens: DerivedBatchToken[] = [
@@ -506,7 +442,6 @@ function dashboardTokens(): DerivedBatchToken[] {
{ metric: "VASCULAR_AGE_DELTA" },
{ metric: "HRV_BALANCE" },
{ metric: "BMI" },
{ metric: "COINCIDENT_DEVIATION" },
];
for (const type of SECTION_VITALS) {
if (type === "HEART_RATE_VARIABILITY") continue;
@@ -604,7 +539,6 @@ export function VitalsDashboard({ enabled = true, className }: DashboardProps) {
))
) : (
<>
<CoincidentDeviationTile {...tileProps} />
<FitnessAgeTile {...tileProps} />
<VascularAgeTile {...tileProps} />
<HrvBalanceTile {...tileProps} />