Files
HealthLog/src/lib/validations/daily.ts
T
Marc-André Bombeck 270f33edb2 feat(daily): let the Today rail dismiss milestone / ECG / tension cards
The worth-a-look rail mixed actionable items (an overdue dose, a broken
sync) with observational ones (a milestone, a new ECG pointer, a tension
window) that have nothing to act on beyond "seen it" — there was no way
to clear them short of them expiring on their own.

Add server-side dismissal scoped to the three observational kinds only:
a new DismissedPriorityItem table (userId + itemKey, composite-unique),
a POST /api/daily/digest/dismiss route, and a deterministic itemKey the
digest builder stamps per kind (metric + reach day for a milestone, the
recording's own timestamp for an ECG, the local day + part of day for a
tension window) so a dismissal never bleeds into a later day's instance.
The actionable kinds never carry an itemKey, so the dismiss affordance
and the endpoint are both structurally unreachable for them — an
itemKey not namespaced under a dismissible kind 422s before any lookup.
2026-07-17 14:29:07 +02:00

31 lines
1.0 KiB
TypeScript

/**
* Request schema for the Today rail's dismiss surface
* (`POST /api/daily/digest/dismiss`).
*
* Lives outside the route file so the OpenAPI registry can import it without
* touching the route module. `itemKey` is namespaced `<kind>:...`
* (`isDismissibleItemKey` in `@/lib/daily/priority-item`) — the prefix alone
* proves the key names one of the OBSERVATIONAL kinds
* (`milestone` / `ecg_new_recording` / `tension_window`); anything else 422s
* before a lookup ever runs, so an actionable item can never be dismissed
* through this endpoint no matter what a client sends.
*/
import { z } from "zod/v4";
import { isDismissibleItemKey } from "@/lib/daily/priority-item";
/** Generous ceiling on the longest real key (an ISO timestamp is ~24 chars). */
const MAX_ITEM_KEY_LENGTH = 200;
export const dismissPriorityItemSchema = z
.object({
itemKey: z
.string()
.min(1)
.max(MAX_ITEM_KEY_LENGTH)
.refine(isDismissibleItemKey, {
message: "itemKey does not name a dismissible rail item",
}),
})
.strict();