docs(inbox): reply + Direct/Institution/Broadcast compose, personal contact groups, recipient scoping

Reflects the 2026-07-25 prod release (profile 1.1.1, messaging 1.0.2, auth 1.2.2, mgmt 1.1.2).
This commit is contained in:
2026-07-25 08:09:21 -04:00
parent 91f15710fa
commit 850dfd4f69
2 changed files with 27 additions and 6 deletions
@@ -65,6 +65,25 @@ Terse agent reference. Owner service: **`hiveops-messaging`** (port 8086, DB `hi
- Badge colour is `#ef4444`, deliberately NOT the `#2563eb` of Inbox's own `.tab-badge`: the nav sits on the sidebar gradient (`#081651` → `#1c49b8`) where blue-on-blue disappears.
- The nav badge re-calls the endpoint rather than reusing Inbox's local `messages.filter(m => !m.read).length`, which is page-local (size 50) and would undercount past page 0. **Do NOT "simplify" it to use the local tally.**
## Reply + Compose targets (added 2026-07-25)
- **Reply**: open-message `Reply` button calls `replyToMessage()` → opens Compose pre-addressed to the sender (`composeTarget='DIRECT'`, sender chip pre-loaded). No new endpoint; sends via the normal `POST /api/messaging/messages`.
- Compose has **three** targets, `composeTarget ∈ {DIRECT, INSTITUTION, BROADCAST}`. `INSTITUTION` was split out of the old Broadcast+institution dropdown — it still sends `type=BROADCAST` with a non-null `institutionKey`; `BROADCAST` = all institutions (`institutionKey` null). `DIRECT` = `type=DIRECT`, per-recipient.
- Direct picker accepts **users AND contact groups**. Selecting a group sets `recipientGroupId` on the send payload — messaging fans out to the group's members server-side. `selectedGroups`/`filteredGroups` drive the group chips in `Inbox.svelte`.
## Personal contact groups (added 2026-07-25)
- New page `hiveops-profile/frontend/src/components/Profile/ContactGroups.svelte` (nav item 👥 Contact Groups), device-groups **shuttle** UI (Available | »›‹« | Assigned).
- Owner service **`hiveops-messaging`**, `ContactGroupController` `@RequestMapping("/api/messaging/groups")` → SPA hits `/messaging/groups`. `groupsAPI` in `lib/api.ts`.
- Endpoints: `GET /groups` (my groups), `GET /groups/{uuid}`, `POST /groups`, `PUT /groups/{uuid}`, `DELETE /groups/{uuid}`, `POST /groups/{uuid}/members {userIds}`, `DELETE /groups/{uuid}/members/{userId}`.
- **Owner-scoped**: `getMyGroups(principal)`; `requireOwned()` throws `EntityNotFoundException`→**404** (not 403) for someone else's group to avoid probing. Per-owner name uniqueness (global unique dropped in **V6** `V6__personal_contact_groups.sql`). Not the same feature as [msp.contact-groups] (MSP distribution lists with notification subscriptions) — different concept, do not conflate.
- Errors mapped by `GlobalExceptionHandler`: `IllegalArgumentException`→400, `EntityNotFoundException`→404 (duplicate-name / not-owned no longer 500).
## Recipient scoping — `/auth/api/admin/users` (changed 2026-07-25)
- The picker is `authenticated()` but the **result list is scoped by JWT role** (was: everyone):
- **ADMIN/BCOS_ADMIN** → all enabled users.
- **MSP_ADMIN** → only their managed customers (via JWT `institutionKeys`); no MSP peers, no other MSP orgs, no BCOS.
- **CUSTOMER** → only their **managing MSP** (auth calls mgmt `GET /api/internal/customers/institution-key/{key}/msp-domains`, secret-gated); NOT other customers, not their own institution colleagues, not BCOS.
- Depends on **mgmt** being live + a matching `INTERNAL_SERVICE_SECRET` across auth/mgmt (shared `.env` var). If mgmt is down/secret mismatches, the customer list degrades to empty (graceful).
## Do NOT
- Do NOT create message endpoints/tables outside `hiveops-messaging`.
- Do NOT hard-delete from `messages` to "remove" a user's message — use per-user soft-delete.
@@ -12,7 +12,8 @@ Your Inbox is where you read and send internal messages and system notifications
1. The **Inbox** tab lists messages sent to you, newest activity first.
2. Unread messages are highlighted and show a blue dot with a bold subject.
3. Click any row to open the full message in a side panel — it's marked as read automatically.
4. The count at the top shows how many messages you have and how many are still unread.
4. Click **Reply** in the open message to answer the sender — Compose opens pre-addressed to them, so you can respond without leaving the inbox.
5. The count at the top shows how many messages you have and how many are still unread.
> Click **Mark all read** in the toolbar to clear every unread message at once.
@@ -23,12 +24,13 @@ Your Inbox is where you read and send internal messages and system notifications
## ✏️ Compose a Message
1. Click **+ Compose** in the toolbar.
2. Choose a type: **Direct** to message specific people, or **Broadcast** to reach a whole group (available to administrators).
3. For a Direct message, search by name or email and add one or more recipients — added people appear as removable chips.
4. Add an optional **Subject** and write your **Message**.
5. Click **Send**.
2. Choose who it goes to: **Direct** for specific people, **Institution** to reach everyone at an institution, or **Broadcast** to reach every institution at once (administrators).
3. For a **Direct** message, search by name or email and add one or more recipients. You can also pick one of your **Contact Groups** to add everyone in it at once — added people and groups appear as removable chips.
4. For an **Institution** message, choose the institution from the dropdown.
5. Add an optional **Subject** and write your **Message**.
6. Click **Send**.
> Administrators can target a **Broadcast** to a specific institution, or send it to all institutions at once.
> **Contact Groups** are your own reusable recipient lists — build them on the **Contact Groups** page, then reuse them here so you don't re-pick the same people every time.
## 🗂️ What You're Looking At
- **Type** — a colored tag showing the kind of message: **Direct** (person to person), **Broadcast** (sent to a group), or **Action** (a system notification that may need your attention).