Notification System
Architecture flow
User action (mutation succeeds) → showSuccessToast(title, message) enqueues to Zustand store → Sonner toast (if enabled) → NotificationFlusher picks up queue → createNotification GraphQL mutation → persisted to app_notification table → bell badge polls userUnreadNotificationCount every 30s → user opens panel → immediate refetch.
Frontend files
src/stores/notification-store.ts— Zustand store: unread count, flush queue, settingssrc/lib/show-notifications.tsx—showSuccessToast,showFailedToast,showInfoToast,showWarningToastsrc/features/notifications/notification-flusher.tsx— flushes queue to backend, polls unread count every 30ssrc/features/notifications/notification-bell.tsx— bell icon with unread badge in headersrc/features/notifications/notification-panel.tsx— Sheet panel: list, mark-read, settingssrc/features/notifications/hooks/use-notifications.ts— all GraphQL hookssrc/components/layout/authenticated-layout.tsx— mounts<NotificationFlusher />and<NotificationBell />
Notification message format
The message field uses · as separator: "Domain · Application Title · Visibility".
Examples: "Application · My Tax App · Internal", "Document · My Tax App · External", "My Tax App" (plain).
Where notifications are fired
- Comment hooks:
useCreateApplicationComment(applicationId, applicationTitle?)—applicationTitlemust be passed by the caller - Application CRUD:
useCreateApplication,useUpdateApplication - Applicant hooks: use
useApplicationContext()internally - Document review:
useReviewApplicationDocument(applicationId, applicationTitle?)