2026-07-13 17:44:44 +03:30
---
2026-07-18 16:57:13 +03:30
name: dyolink-tab-badges
description: Sidebar tab badge counts (Cases/Tasks/Treatment) from LabCaseActivity + read cursors. Use when changing tab-counts API, activity emit, read state, or Sidebar badges — distinct from the header inbox bell.
2026-07-13 17:44:44 +03:30
---
2026-07-18 16:57:13 +03:30
# Tab badges (Cases / Tasks / Treatment)
Sidebar unread pills for **lab Cases ** , **lab Tasks ** , and **clinic Treatment ** . Activity is stored as `LabCaseActivity` (shared clinic↔lab case events); badges are org-type-specific.
2026-07-13 17:44:44 +03:30
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`
2026-07-13 22:53:23 +03:30
- **`LabCaseUserTabReadState` ** — per user/org/tab cursor (`TASKS` ) for sidebar badge clearing on tab visit.
2026-07-13 17:44:44 +03:30
- **`LabCaseUserReadState` ** — per user/org/labCase cursor; drives Cases tab count and `hasUnread` on case list cards
2026-07-18 16:57:13 +03:30
## Tab badge buckets (Option B)
2026-07-13 17:44:44 +03:30
| Org | Tab | Activity types |
|-----|-----|----------------|
| LAB | Cases | `CASE_SENT` , `CLINIC_COMMENT` , `CASE_IMPORTANT` |
| LAB | Tasks | `TASK_COMPLETED` , `LAB_COMMENT` |
2026-07-14 03:39:42 +03:30
| CLINIC | Treatment | `LAB_COMMENT` (only `visibleToClinic` ), `TASK_COMPLETED` — **only lab cases for treatments the user provided ** |
2026-07-13 17:44:44 +03:30
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)
2026-07-13 22:53:23 +03:30
- `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)
2026-07-13 17:44:44 +03:30
- `POST /notifications/mark-case-read` `{ labCaseId }` — opening a case clears that case’ s unread dot and updates Cases tab count
2026-07-13 22:53:23 +03:30
- `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
2026-07-13 17:44:44 +03:30
## 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).
2026-07-18 16:57:13 +03:30
**Live path:** inbox Socket.IO `notification.created` also dispatches that event so sidebar counts refresh without navigation. **Only currently mounted ** feature pages soft-reload list data on the same event (silent; Treatment form/draft untouched). Unmounted tabs do not fetch list data until the user opens them. See `.cursor/skills/notifications-inbox/SKILL.md` .
2026-07-13 17:44:44 +03:30
## Frontend pattern (same as org connections)
2026-07-18 16:57:13 +03:30
- `useTabBadgeCounts()` — always mounted in dashboard Sidebar; fetch on pathname change + `tab-badges-changed`
2026-07-13 22:53:23 +03:30
- `useMarkTabReadOnVisit()` — Tasks page only (Cases/Treatment badges clear when opening unread cases)
2026-07-13 17:44:44 +03:30
- `NavBadgePill` in [`Sidebar.tsx` ](frontend/src/components/ui/shared/Sidebar.tsx )
- **Organizations** pending connections still use `usePendingConnectionsCount` (separate pending-state API)
2026-07-18 16:57:13 +03:30
- **Live soft refresh (mounted page only):**
- Cases → silent list + selected detail
- Tasks → silent task list
- Treatment (`TreatmentWorkspace` ) → silent patient lab cases + unread rail
- Orgs → silent list on `pending-connections-changed` only
2026-07-13 17:44:44 +03:30
## Out of scope (later steps)
2026-07-18 01:09:54 +03:30
- Push / email
2026-07-18 16:57:13 +03:30
- Prefetching unmounted tab list data on every socket event
- Pushing full page remounts / wiping Treatment draft state on live events
2026-07-13 17:44:44 +03:30
- `CASE_AMENDED` emit (Step 7)
2026-07-18 01:09:54 +03:30
## Related: header inbox
Permission-free bell + `UserNotification` fan-out + Socket.IO — see `.cursor/skills/notifications-inbox/SKILL.md` . Independent of tab badge cursors.