mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
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.
31 lines
1.0 KiB
TypeScript
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();
|