From 779aac5f7871dc7ffa341a46165986d7b5a9e71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 27 Jun 2026 11:01:13 +0200 Subject: [PATCH] fix(coach): integration cleanup for the Horizon-B merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the unused extractCoachClaims helper (+ its CoachClaim types) and the unused CoachPlanStatus type — the deterministic grader uses the boolean claim helpers directly, and the plan status enum is consumed via the const array. Update the dashboard-snapshot suite for the locale-keyed cell: stub the i18n context and add getQueriesData to the fake query client so the locale-aware preload probe is exercised. --- src/lib/ai/coach/coach-prose-grounding.ts | 36 ------------------- src/lib/ai/coach/plans.ts | 2 -- .../__tests__/use-dashboard-snapshot.test.tsx | 22 ++++++++++-- 3 files changed, 20 insertions(+), 40 deletions(-) diff --git a/src/lib/ai/coach/coach-prose-grounding.ts b/src/lib/ai/coach/coach-prose-grounding.ts index 38a8bf413..49efb46f5 100644 --- a/src/lib/ai/coach/coach-prose-grounding.ts +++ b/src/lib/ai/coach/coach-prose-grounding.ts @@ -142,22 +142,6 @@ export function findUnverifiedCoachNumbers( * judge covers — never a false positive that blocks a good answer. * ────────────────────────────────────────────────────────────────────────── */ -/** A claim category the deterministic grader recognises. */ -export type CoachClaimKind = - /** A verdict against a clinical/own threshold ("you have hypertension"). */ - | "threshold" - /** Framing against the user's own range ("above your usual", "for you"). */ - | "ownBaseline" - /** A confident state verdict ("you're clearly improving"). */ - | "confidentVerdict"; - -/** One structured claim the grader extracted from Coach prose. */ -export interface CoachClaim { - kind: CoachClaimKind; - /** The phrase that triggered the match (truncated for logging). */ - source: string; -} - /** * Confident-verdict phrasings: an unhedged state assertion. These are the * phrasings a data-honesty case must NOT see when the snapshot is sparse — @@ -246,26 +230,6 @@ export function hasThresholdVerdict(prose: string): boolean { return THRESHOLD_VERDICT_PATTERNS.some((p) => p.test(prose)); } -/** - * Extract the structured claims the grader recognises from Coach prose. Each - * entry is a high-precision match the golden-set graders assert on. The list is - * intentionally non-exhaustive — see the module-level posture note. - */ -export function extractCoachClaims(prose: string): CoachClaim[] { - if (!prose) return []; - const claims: CoachClaim[] = []; - const push = (kind: CoachClaimKind, patterns: readonly RegExp[]) => { - for (const p of patterns) { - const m = p.exec(prose); - if (m) claims.push({ kind, source: m[0].slice(0, 48) }); - } - }; - push("threshold", THRESHOLD_VERDICT_PATTERNS); - push("confidentVerdict", CONFIDENT_VERDICT_PATTERNS); - push("ownBaseline", OWN_BASELINE_PATTERNS); - return claims; -} - /** * Soft-correct the prose: replace each unverified numeric token with a neutral * placeholder so a drifted figure never reaches the user as if authoritative, diff --git a/src/lib/ai/coach/plans.ts b/src/lib/ai/coach/plans.ts index 8907cd7f1..78ef99474 100644 --- a/src/lib/ai/coach/plans.ts +++ b/src/lib/ai/coach/plans.ts @@ -48,8 +48,6 @@ export const COACH_PLAN_STATUSES = [ "abandoned", ] as const; -export type CoachPlanStatus = (typeof COACH_PLAN_STATUSES)[number]; - /** Hard cap on non-terminal (proposed + active) plans per user. */ export const MAX_PLANS_PER_USER = 25; /** Per-field text length cap (mirrors the Zod gate + the prompt instruction). */ diff --git a/src/lib/queries/__tests__/use-dashboard-snapshot.test.tsx b/src/lib/queries/__tests__/use-dashboard-snapshot.test.tsx index 582dcbccb..e08ef32be 100644 --- a/src/lib/queries/__tests__/use-dashboard-snapshot.test.tsx +++ b/src/lib/queries/__tests__/use-dashboard-snapshot.test.tsx @@ -29,6 +29,14 @@ vi.mock("@tanstack/react-query", () => ({ }), })); +// v1.21.3 (b) — the hook now keys the cell by the active locale via +// `useTranslations()`; the suite exercises the hook body directly (react-query +// is mocked), so stub the i18n context to a fixed locale rather than mounting a +// provider. +vi.mock("@/lib/i18n/context", () => ({ + useTranslations: () => ({ locale: "en" }), +})); + import { useDashboardSnapshot, prefetchDashboardSnapshot, @@ -80,10 +88,12 @@ describe("useDashboardSnapshot — auto-refresh on an open page", () => { expect(opts.refetchOnWindowFocus).toBe(true); }); - it("routes the queryKey through the centralised factory", () => { + it("routes the queryKey through the centralised factory, keyed by locale", () => { useDashboardSnapshot(); const opts = lastOpts(); - expect(opts.queryKey).toEqual(queryKeys.dashboardSnapshot()); + // v1.21.3 (b) — the live cell carries the active locale; a zero-arg + // invalidation still prefix-matches it. + expect(opts.queryKey).toEqual(queryKeys.dashboardSnapshot("en")); }); it("retries once on transient failures only (network / 5xx, never 4xx)", () => { @@ -130,6 +140,14 @@ describe("prefetchDashboardSnapshot — hydration-safe promise handoff", () => { state?: { data: unknown; dataUpdatedAt: number } | undefined, ): QueryClient { return { + // v1.21.3 (b) — the preloader probes ALL locale-keyed snapshot cells + // via `getQueriesData` (prefix) then reads each cell's state. A fresh + // `state` stands in for one warm locale cell; absent → no warm cell. + getQueriesData: vi + .fn() + .mockReturnValue( + state ? [[queryKeys.dashboardSnapshot("en"), state.data]] : [], + ), getQueryState: vi.fn().mockReturnValue(state), getQueryData: getQueryDataMock, setQueryData: setQueryDataMock,