From ba5a17a54ee3735f730c88fe2b3b7f79cf5c2ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 17 Jul 2026 09:37:39 +0200 Subject: [PATCH] fix(daily): keep the coach check-in constants out of the client bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Today hero and the check-in hook imported the check-in intent constants from digest.ts, which — since the milestone slice — transitively reaches the database through the streak baseline engine. That pulled the Postgres driver into the client bundle (a Turbopack 'Can't resolve net/tls/dns' build failure the worktree build couldn't surface). Move the four constants to a tiny, dependency-free coach-checkin-intents module; the client imports them from there, and digest.ts (the server builder) stays server-only. --- src/app/api/coach/plans/[id]/route.ts | 2 +- src/components/daily/today-hero.tsx | 4 ++-- src/hooks/use-coach-checkin.ts | 2 +- src/lib/daily/__tests__/digest.test.ts | 8 +++++--- src/lib/daily/coach-checkin-intents.ts | 28 ++++++++++++++++++++++++++ src/lib/daily/digest.ts | 14 +++++++++---- 6 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 src/lib/daily/coach-checkin-intents.ts diff --git a/src/app/api/coach/plans/[id]/route.ts b/src/app/api/coach/plans/[id]/route.ts index f21351a5c..02a13b6df 100644 --- a/src/app/api/coach/plans/[id]/route.ts +++ b/src/app/api/coach/plans/[id]/route.ts @@ -34,7 +34,7 @@ import { prisma } from "@/lib/db"; import { requireModuleEnabled } from "@/lib/modules/gate"; import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit"; import { decryptFromBytes } from "@/lib/ai/coach/bytes-codec"; -import { COACH_CHECKIN_REVIEW_DAYS } from "@/lib/daily/digest"; +import { COACH_CHECKIN_REVIEW_DAYS } from "@/lib/daily/coach-checkin-intents"; import { coachPlanPatchSchema } from "@/lib/validations/coach-plan"; interface RouteCtx { diff --git a/src/components/daily/today-hero.tsx b/src/components/daily/today-hero.tsx index 7d8429226..c52c5bcf1 100644 --- a/src/components/daily/today-hero.tsx +++ b/src/components/daily/today-hero.tsx @@ -46,11 +46,11 @@ import { useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import type { ScoreRingId } from "@/lib/dashboard-layout"; import type { DashboardScoreRing } from "@/lib/dashboard/score-rings"; +import type { DailyDigest } from "@/lib/daily/digest"; import { COACH_CHECKIN_KEEP_INTENT, COACH_CHECKIN_LETGO_INTENT, - type DailyDigest, -} from "@/lib/daily/digest"; +} from "@/lib/daily/coach-checkin-intents"; /** * v1.29.0 — the selected score rings return to the hero. The maps mirror the diff --git a/src/hooks/use-coach-checkin.ts b/src/hooks/use-coach-checkin.ts index b58f741f2..4b288afb5 100644 --- a/src/hooks/use-coach-checkin.ts +++ b/src/hooks/use-coach-checkin.ts @@ -22,7 +22,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiPatch } from "@/lib/api/api-fetch"; import { queryKeys } from "@/lib/query-keys"; -import { COACH_CHECKIN_REVIEW_DAYS } from "@/lib/daily/digest"; +import { COACH_CHECKIN_REVIEW_DAYS } from "@/lib/daily/coach-checkin-intents"; export function useCoachCheckinAction() { const queryClient = useQueryClient(); diff --git a/src/lib/daily/__tests__/digest.test.ts b/src/lib/daily/__tests__/digest.test.ts index 9fc5bae1f..69edc029d 100644 --- a/src/lib/daily/__tests__/digest.test.ts +++ b/src/lib/daily/__tests__/digest.test.ts @@ -5,14 +5,16 @@ import type { DailyBriefing } from "@/lib/ai/schema"; import type { MedsTodayBlock } from "@/lib/dashboard/meds-today"; import { buildDailyDigest, - COACH_CHECKIN_KEEP_INTENT, - COACH_CHECKIN_LETGO_INTENT, COACH_CHECKIN_RESURFACE_DAYS, - COACH_CHECKIN_REVIEW_DAYS, MAX_WORTH_A_LOOK, type DailyDigestCoachPlan, type DailyDigestInput, } from "@/lib/daily/digest"; +import { + COACH_CHECKIN_KEEP_INTENT, + COACH_CHECKIN_LETGO_INTENT, + COACH_CHECKIN_REVIEW_DAYS, +} from "@/lib/daily/coach-checkin-intents"; import type { Milestone } from "@/lib/daily/milestones"; import type { PriorityItem } from "@/lib/daily/priority-item"; diff --git a/src/lib/daily/coach-checkin-intents.ts b/src/lib/daily/coach-checkin-intents.ts new file mode 100644 index 000000000..bdc676c56 --- /dev/null +++ b/src/lib/daily/coach-checkin-intents.ts @@ -0,0 +1,28 @@ +/** + * Coach check-in constants shared by BOTH the server-side digest builder and + * the client Today surface + hooks. + * + * They live in this tiny, dependency-free module on purpose: `digest.ts` (their + * former home) transitively reaches the database through the milestone/streak + * baseline engine, so importing a constant from it would drag the whole server + * builder — and the Postgres driver — into a client bundle (a Turbopack + * `Can't resolve 'net'/'tls'/'dns'` build failure). Keeping the constants here + * lets the Today hero and the check-in hook import them with zero server reach. + */ + +/** + * Days after a plan's activation / last review it becomes due for a look-back. + * One source of truth for the digest builder AND the PATCH route that re-arms it. + */ +export const COACH_CHECKIN_REVIEW_DAYS = 7; + +/** + * Closed allowlist of the check-in card's two MUTATING intents. The generic, + * id-less `PriorityCard` forwards only a single `intent` string, so the target + * plan id is appended after the ":" — the Today handler recovers it and PATCHes + * the existing plan-lifecycle route. "Adjust" is navigation (an `href` into the + * coach), so it carries no plan id and never mutates. + */ +export const COACH_CHECKIN_KEEP_INTENT = "coach.checkin.keep"; +export const COACH_CHECKIN_LETGO_INTENT = "coach.checkin.letGo"; +export const COACH_CHECKIN_ADJUST_INTENT = "coach.checkin.adjust"; diff --git a/src/lib/daily/digest.ts b/src/lib/daily/digest.ts index c3b2c8aa2..b0099578c 100644 --- a/src/lib/daily/digest.ts +++ b/src/lib/daily/digest.ts @@ -32,6 +32,12 @@ import { milestoneHref, type Milestone, } from "@/lib/daily/milestones"; +import { + COACH_CHECKIN_REVIEW_DAYS, + COACH_CHECKIN_KEEP_INTENT, + COACH_CHECKIN_LETGO_INTENT, + COACH_CHECKIN_ADJUST_INTENT, +} from "@/lib/daily/coach-checkin-intents"; /** At most three rail items — a glance, never a wall (§2.5, never padded). */ export const MAX_WORTH_A_LOOK = 3; @@ -47,7 +53,7 @@ const MS_PER_DAY = 86_400_000; * ones the coach explicitly dated. The PATCH-to-`active` route defaults * `reviewDate` to `+COACH_CHECKIN_REVIEW_DAYS`; this constant is the one source. */ -export const COACH_CHECKIN_REVIEW_DAYS = 7; +// COACH_CHECKIN_REVIEW_DAYS lives in ./coach-checkin-intents (client-safe). /** * After a check-in comes due, it resurfaces for at most this many days before @@ -65,9 +71,9 @@ export const COACH_CHECKIN_RESURFACE_DAYS = 14; * and PATCHes the existing plan-lifecycle route. "Adjust" is navigation (an * `href` into the coach), so it carries no plan id and never mutates here. */ -export const COACH_CHECKIN_KEEP_INTENT = "coach.checkin.keep"; -export const COACH_CHECKIN_LETGO_INTENT = "coach.checkin.letGo"; -export const COACH_CHECKIN_ADJUST_INTENT = "coach.checkin.adjust"; +// The check-in intents live in ./coach-checkin-intents (client-safe) and are +// imported above, so the client Today surface can use them without pulling this +// server-side builder into its bundle. type Translate = ServerTranslator["t"];