Files
dyolink/.cursor/skills/lab-notifications/SKILL.md

64 lines
3.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: dyolink-lab-notifications
description: Lab case activity feed + sidebar tab badge counts. Use when changing notifications API, LabCaseActivity, read state, or Sidebar badges for Cases/Tasks/Treatment.
---
# Lab notifications (tab badges)
Backend: [`backend/src/modules/notifications/`](backend/src/modules/notifications/)
Activity types: [`backend/src/common/lab-case-activity.ts`](backend/src/common/lab-case-activity.ts)
Frontend hook: [`frontend/src/lib/hooks/useTabBadgeCounts.ts`](frontend/src/lib/hooks/useTabBadgeCounts.ts)
## Models
- **`LabCaseActivity`** — append-only events: `CASE_SENT`, `CLINIC_COMMENT`, `LAB_COMMENT`, `CASE_IMPORTANT`, `CASE_AMENDED` (stub for Step 7), `TASK_COMPLETED`
- **`LabCaseUserTabReadState`** — per user/org/tab cursor (`TASKS`) for sidebar badge clearing on tab visit.
- **`LabCaseUserReadState`** — per user/org/labCase cursor; drives Cases tab count and `hasUnread` on case list cards
## Tab badge buckets (Option B — split lab counts)
| Org | Tab | Activity types |
|-----|-----|----------------|
| LAB | Cases | `CASE_SENT`, `CLINIC_COMMENT`, `CASE_IMPORTANT` |
| LAB | Tasks | `TASK_COMPLETED`, `LAB_COMMENT` |
| CLINIC | Treatment | `LAB_COMMENT` (only `visibleToClinic`), `TASK_COMPLETED`**only lab cases for treatments the user provided** |
Counts exclude events where `actorUserId === current user`. Clinic `LAB_COMMENT` counts only when `payload.visibleToClinic === true`.
## APIs
- `GET /notifications/tab-counts``{ cases?, tasks?, treatment? }`**Cases** count = number of cases with unread Cases-bucket activity (per-case read cursor)
- `GET /notifications/lab-cases/:labCaseId/activities` — activity feed for a case (clinic-safe lab comments)
- `POST /notifications/mark-tab-read` `{ tab }` — Tasks only (Cases/Treatment skip tab-level clear)
- `POST /notifications/mark-case-read` `{ labCaseId }` — opening a case clears that cases unread dot and updates Cases tab count
- `GET /treatments/patients/:patientId/lab-cases` — patient shipment summaries for Treatment rail + tracker cards
- `GET /treatments/lab-cases/unread` — org-wide unread shipment summaries for Treatment “All updates” scope
## Emit activity from
| Event | Service |
|-------|---------|
| First send | `treatments.service` `sendLabCase``CASE_SENT` |
| Comment | `lab-case-comments.service``CLINIC_COMMENT` / `LAB_COMMENT` |
| Mark important | `cases.service` `updateImportant` (only when set true) → `CASE_IMPORTANT` |
| Task completed | `tasks.service` `updateStatus``TASK_COMPLETED` |
After mutations, frontend calls `notifyTabBadgesChanged()` (window event).
## Frontend pattern (same as org connections)
- `useTabBadgeCounts()` — fetch on pathname change + `tab-badges-changed` event
- `useMarkTabReadOnVisit()` — Tasks page only (Cases/Treatment badges clear when opening unread cases)
- `NavBadgePill` in [`Sidebar.tsx`](frontend/src/components/ui/shared/Sidebar.tsx)
- **Organizations** pending connections still use `usePendingConnectionsCount` (separate pending-state API)
## Out of scope (later steps)
- Push / email
- Making Cases/Tasks/Treatment lists live via websockets
- `CASE_AMENDED` emit (Step 7)
## Related: header inbox
Permission-free bell + `UserNotification` fan-out + Socket.IO — see `.cursor/skills/notifications-inbox/SKILL.md`. Independent of tab badge cursors.