Files

3.2 KiB

name, description
name description
dyolink-notifications-inbox Header notification bell/inbox (UserNotification fan-out + Socket.IO). Use when changing inbox cards, realtime gateway, or notification deep links — distinct from sidebar tab badges.

Notifications inbox (bell)

Permission-free feature (every dashboard user sees the bell). Cards are permission-filtered at fan-out time.

vs tab badges

Inbox (UserNotification) Sidebar badges (LabCaseActivity)
Entry Header bell → dropdown + /notifications Sidebar Cases/Tasks/Treatment/Orgs
Live Socket.IO (/realtime) Same socket → notifyTabBadgesChanged() → REST tab-counts
Read Per-card readAt only Per-case / tab cursors

Do not clear tab badges when marking an inbox card read.

Live cascade (one socket)

On notification.created, RealtimeProvider:

  1. Updates inbox state (bell list + unread).
  2. Calls notifyTabBadgesChanged() → Sidebar refetches GET /notifications/tab-counts.
  3. For connection-request types, also notifyPendingConnectionsChanged().
  4. Currently mounted feature pages soft-refresh (no remount, no form wipe). Unmounted tabs do not fetch list data until opened:
    • Cases — silent list + selected detail reload
    • Tasks — silent task list reload
    • Treatment (TreatmentWorkspace, not thin page.tsx) — silent patient lab-cases + unread rail
    • Organizations — silent connections list on pending-connections-changed only

Sidebar badge counts always refetch (hook lives in the always-mounted Sidebar). Treatment draft/form state is not cleared by this cascade.

Full tab-badge map: .cursor/skills/tab-badges/SKILL.md.

Backend

  • Model: UserNotification + UserNotificationType in Prisma
  • Fan-out: user-notification.service.ts
  • Realtime: backend/src/realtime/RealtimeGateway (cookie JWT), RealtimeEmitter, rooms user:{userId}:org:{organizationId}
  • REST: GET /notifications/inbox, GET /notifications/inbox/unread-count, POST /notifications/inbox/:id/read, POST /notifications/inbox/read-all

Emit sites (parallel to LabCaseActivity)

CASE_SENT, CLINIC_COMMENT, LAB_COMMENT (+ LAB_COMMENT_CLINIC), CASE_IMPORTANT, TASK_COMPLETED, TASK_ASSIGNED (assignee only), CONNECTION_REQUEST, STAFF_INVITE — see service call sites.

Inbox card context is denormalized inside UserNotificationService.notify() (enrichInboxPayload) from ids already on the payload (labCaseId, taskId, fromOrganizationId). Emit sites stay thin ({ labCaseId }, etc.). Inbox list/read does not join related tables. Older rows may lack these fields until new events are emitted.

Realtime auth: after access-token refresh (proactive, axios 401 retry, or checkAuth), frontend dispatches dyolink:access-token-refreshed so RealtimeProvider reconnects with the new cookie.

Frontend UI

  • NotificationBell + NotificationsPage + NotificationCard (full-width page list; same card height; extra context truncated on one line)
  • Deep links: /cases?caseId=, /tasks?taskId=, /treatment?labCaseId=, /organizations, /staff