# 14 — Support staff UI **Agent:** 14/20 **Status:** Implemented **Coordinates with:** `06-staff-roles` (middleware + `staff_role`), `11-support-design` / `12-support-backend` (claim/scope/agents), `07-admin-shell`, `10-admin-orgs-ui` (grant role) --- ## PROBLEM Platform support work lived behind full `is_platform_admin`. Staff need an inbox with claim/unassign, assignment, and filters — without billing/settings access. Platform admins must be able to grant `support_staff` from the orgs/users UI. --- ## WHAT SHIPPED ### Access model | Actor | Gate | Surfaces | |-------|------|----------| | Full admin (`admin` / `developer` / legacy platform admin) | `RequirePlatformAdmin` / `staff.full_admin` | Full `/admin/*` | | `support_staff` | `RequireSupportDesk` / `staff.support_desk` | `/admin/support/**` (+ Overview in nav) | | Customers | company session | `/support/**` (unchanged) | - API: `/api/admin/support/*` mounted under `RequireSupportDesk` (not full admin). - Web: `requireSupportDesk()` on support pages; `requirePlatformAdmin()` elsewhere. - `AdminNav` hides billing/settings/users/analytics for support-only staff. - Layout shows admin shell when `staff_access.support_desk` or `full_admin`. ### Queue + ticket UI (`/admin/support`) - **Scopes:** Inbox · Mine · Unassigned · All (All = full admin only). Persisted in `?scope=`. - **Status chips** + search (subject / email / company). - **Claim** on unassigned open/pending rows and on ticket detail. - **Unassign / release** when assignee is self (or force for full admin). - **Assign to staff** (full admin): agent picker from `GET /api/admin/support/agents`. - Assignee column shows email / “You” / Unassigned. - Visual language matches reworked admin shell (`PageShell`, `Card`, `TableShell`, filter chips). ### Role grant coordination (orgs UI) Platform admins grant roles via: - `PATCH /api/admin/users/{id}/staff-role` body `{ "staff_role": "support_staff" | "admin" | "developer" | null }` - Admin Users UI (`apps/web/src/routes/admin/users/+page.svelte` + `$lib/admin-orgs.ts`) — role dialog owned with agent 10. - Optional alias: `PUT /api/admin/support/agents/{id}` `{ "is_support_agent": true }` → sets `staff_role=support_staff`. `/api/auth/me` returns `staff_access` + `staff_capabilities` for client gating. --- ## FILES (primary) | Area | Path | |------|------| | Client API | `apps/web/src/lib/support/admin-api.ts` | | Gates | `apps/web/src/lib/admin-gate.ts` (`requireSupportDesk`) | | Types / session | `apps/web/src/lib/types.ts`, `auth-session.svelte.ts` | | Nav / layout | `AdminNav.svelte`, `routes/+layout.svelte` | | Queue / detail | `routes/admin/support/+page.svelte`, `[id]/+page.svelte` | | Handlers | `apps/api/internal/httpapi/support_handlers.go` (scope, claim, release, agents) | | Desk domain | `apps/api/internal/support/desk.go`, `agents.go` | --- ## VERIFICATION ```text cd apps/api && go test ./internal/support/ ./internal/auth/ ./internal/httpapi/ -count=1 ``` Manual: 1. As platform admin: Users → set a user `staff_role=support_staff`. 2. Sign in as that user → admin shell shows Support (not billing). 3. Open `/admin/support?scope=unassigned` → Claim → appears under Mine. 4. Unassign → returns to Unassigned. 5. As full admin: open ticket → Assign to staff → Apply. 6. Confirm `/admin/billing` returns 403 for support_staff (API + UI). --- ## ASSUMPTIONS - ASSUMPTION: Agent 6 `ResolveStaffAccess` — `support_staff` never gets `full_admin` even if `is_platform_admin` column is true. - ASSUMPTION: Least-privilege visibility for agents (inbox / claim) is enforced server-side in list/get/update (agent 12). - CSAT ratings UI is owned by sibling agents; this doc does not cover rating widgets. ## ROLLBACK Revert support route UI/gate/nav changes and remount support APIs under `RequirePlatformAdmin` only if needed. Staff role column (`029_staff_roles`) is additive — leave in place.