Files
dyolink/.cursor/skills/tab-badges/SKILL.md

75 lines
4.6 KiB
Markdown
Raw Permalink 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-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.
---
# 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.
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`, `TASK_ASSIGNED`
- **`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)
| Org | Tab | Activity types |
|-----|-----|----------------|
| LAB | Cases | `CASE_SENT`, `CLINIC_COMMENT`, `CASE_IMPORTANT` |
| LAB | Tasks | `TASK_COMPLETED`, `LAB_COMMENT`, `TASK_ASSIGNED` (assignee only) |
| 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` |
| Task assigned | `cases.service` `assignTask``TASK_ASSIGNED` (inbox + Tasks badge for **assignee**, including self-assign) |
After mutations, frontend calls `notifyTabBadgesChanged()` (window event).
**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`.
## Frontend pattern (same as org connections)
- `useTabBadgeCounts()` — always mounted in dashboard Sidebar; fetch on pathname change + `tab-badges-changed`
- `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)
- **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
- **TASK_ASSIGNED** Tasks badge is assignee-scoped (`payload.assigneeUserId`); other Tasks-bucket events stay org-wide for users with tab access.
## Out of scope (later steps)
- Push / email
- Prefetching unmounted tab list data on every socket event
- Pushing full page remounts / wiping Treatment draft state on live events
- `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.