/** * @factory/shared/realtime/notification-policy — the per-type notification * push/toast policy + the `notification.push` frame-kind mapping * (TASK-0094 R5/R7). * * The Prisma `NotificationType` enum is the single source of truth for the * types this module mirrors (recorded: shared cannot import `@prisma/client` * — the worker declares its own event catalog for the same reason, * `apps/worker/src/events.ts`); keep the two in lockstep. * * Policy (recorded in the task file, R5): * - MENTION / ASSIGNMENT → toast + count bump; * - COMMENT / everything else → count bump only (no toast). * The in-app count is ALWAYS updated on a `notification.push` frame (the * row is the durable state — the push is the < 1 s fast path, R7). */ /** The Prisma `NotificationType` enum values (lockstep mirror). */ export const NOTIFICATION_TYPES = [ "MENTION", "COMMENT", "ASSIGNMENT", "PAGE_SHARED", "TASK_ASSIGNED", "DATABASE_CHANGE", "AUTOMATION", "INTEGRATION", "FORM_SUBMITTED", // Phase 10 (TASK-0108) — owner notified on each accepted submission // Confluence expansion (T-157/T-163/T-160) — mirrors the Prisma NotificationType enum. "REVIEW_REQUESTED", "APPROVAL_DECISION", "WATCH_UPDATE", "TASK_DUE" ] as const; export type NotificationTypeValue = (typeof NOTIFICATION_TYPES)[number]; /** One type's push policy. */ export interface NotificationPolicy { /** Whether a toast should fire in the web client (R5). */ readonly toast: boolean; /** The `notification.push` frame's `kind` (the type, snake_case). */ readonly kind: string; } /** The full policy table (R5: only MENTION / ASSIGNMENT toast). */ export const NOTIFICATION_POLICY: Readonly> = { MENTION: { toast: true, kind: "mention" }, ASSIGNMENT: { toast: true, kind: "assignment" }, COMMENT: { toast: false, kind: "comment" }, PAGE_SHARED: { toast: false, kind: "page_shared" }, TASK_ASSIGNED: { toast: false, kind: "task_assigned" }, DATABASE_CHANGE: { toast: false, kind: "database_change" }, AUTOMATION: { toast: false, kind: "automation" }, INTEGRATION: { toast: false, kind: "integration" }, FORM_SUBMITTED: { toast: false, kind: "form_submitted" }, // count bump only (R5) REVIEW_REQUESTED: { toast: true, kind: "review_requested" }, APPROVAL_DECISION: { toast: false, kind: "approval_decision" }, WATCH_UPDATE: { toast: false, kind: "watch_update" }, TASK_DUE: { toast: false, kind: "task_due" } }; /** * Look up a type's policy. An UNKNOWN type (a future enum value the web * has not shipped yet) gets the conservative policy: no toast, kind = the * lower-cased type (never a crash — the count still updates, R7). */ export function notificationPolicy(type: string): NotificationPolicy { const known = (NOTIFICATION_TYPES as readonly string[]).includes(type); if (known) return NOTIFICATION_POLICY[type as NotificationTypeValue]; return { toast: false, kind: type.toLowerCase().replace(/_/g, "_") }; } /** True for the types that toast in the web client (R5). */ export function notificationToasts(type: string): boolean { return notificationPolicy(type).toast; }