---
path: /inbox
surface: admin
domain: governance
auth: signed-in (admin)
source: apps/oshun/admin/src/app/inbox/page.tsx
status: drafted
last_walked: '—'
---

# Governance · Unified inbox

## Purpose

Folds the six per-workspace queues (review, moderation, support, privacy,
incident, model) into a single triage surface backed by the BFF inbox composer.
`operatorPromise`: "Inbox lets a reviewer clear cross-workspace urgency without
context-switching between disparate cockpits."

## Entry points

- Sidebar item **"Inbox"** (AdminSidebar — governance group)
- Dashboard urgency / SLA tile → inbox (from
  `OSHUN_ADMIN_WORKSPACE_RELATIONSHIPS` `dashboard → inbox` entry)
- Deep link `/inbox#filters=…&detail=<itemId>` — `useAdminQueueState` hydrates
  filter / sort / detail state from the URL hash so a copy-pasted URL reproduces
  the same triage view (see V1-AWEB-094 comment in `UnifiedInboxPanel.tsx`)

## Layout regions

Inside `AdminShell`:

- `<WorkspaceEntryPoint workspaceId="inbox" accessible detail>` — always
  rendered first; surfaces the "Access not granted" / "Workspace data
  unavailable" notice when applicable (shell/04-workspace-pattern.md)
- `<UnifiedInboxPanel>` — rendered only when `detail.accessible` AND `snapshot`
  (from `extractInboxSnapshot`) is non-null. Sections inside the panel:
  - **Header band**: h2 "Unified inbox"; subtitle "Cross-workspace triage across
    review, moderation, support, privacy, incident, and model queues.";
    right-aligned "`visible` of `total` items shown"
  - **Metrics row**: four cells — Total open / Overdue / Due soon / Unassigned
    (from `metrics`)
  - **Query bar**: `<AdminQueryFilterInput>` — typed
    `workspace:review priority:critical sla:overdue` filter input (V1-AWEB-096)
  - **Filters row**: workspace checkboxes (6), priority checkboxes (5), SLA
    checkboxes (5), Assignee select (All / Assigned to me / Unassigned),
    free-text Search input, Sort select (4 options)
  - **Bulk action toolbar**: appears only when ≥1 row selected; target-operator
    input + Bulk acknowledge / Bulk assign / Clear buttons + live `bulkSubmit`
    status region
  - **List + drawer**: `ul[data-testid="unified-inbox-list-drawer"]` with one
    `<li>` per visible item; right-side `AdminInlineDrawer` when
    `queue.state.detailId` is set

## States

- [ ] Anonymous → middleware redirects to
      `/unauthorized?reason=missing-session&returnTo=/inbox`
- [ ] Signed-in (admin) + scope ok + BFF returns inbox detail with items → panel
      renders, both `WorkspaceEntryPoint` summary block and `UnifiedInboxPanel`
      mount (the entry-point renders above the panel; visually redundant but
      intentional per page code)
- [ ] Signed-in (admin) + scope ok + BFF returns `workspace-unavailable` →
      `WorkspaceEntryPoint` shows "Workspace data unavailable";
      `UnifiedInboxPanel` does NOT render (guarded by
      `detail.accessible && snapshot`)
- [ ] Signed-in (admin) + missing scope → `WorkspaceEntryPoint` "Access not
      granted"; panel hidden
- [ ] Filters applied but no matches → `p[data-testid="unified-inbox-empty"]`
      "No items match the current filters."
- [ ] Bulk submit `submitting` / `success` / `error` →
      `span[data-testid="unified-inbox-bulk-status"]` with `role="status"` and
      `aria-live="polite"`
- [ ] Bulk-assign over `MAX_INBOX_BULK_ACTION_BATCH_SIZE` items → Bulk
      acknowledge / Bulk assign disabled
- [ ] Bulk-assign with mixed eligibility (`assignEligibility.denied     > 0`) →
      button title surfaces the eligible / denied counts
- [ ] No current operator id → "Assigned to me" option disabled in Assignee
      select

## Interactions

- [ ] **AdminQueryFilterInput typed bar** (input)
  - Function: parses `key:value` tokens (`workspace`, `priority`, `sla`,
    `assignee`) and free-text into a structured filter via `handleParsedQuery`;
    empty input is a no-op
  - Replaces `queue.state.filters` + `queue.state.query` through `queue.replace`
- [ ] **Workspace filter checkboxes** (×6: review / moderation / support /
      privacy / incident / model) — toggle pushes/removes an
      `{ key: 'workspace', op: 'eq', value }` filter
- [ ] **Priority filter checkboxes** (×5: critical / high / medium / low /
      unknown)
- [ ] **SLA filter checkboxes** (×5: overdue / due_soon / on_track / no_sla /
      paused)
- [ ] **Assignee select** — values `all` / `me` / `unassigned`; `me` disabled
      when `currentOperatorId === null`
- [ ] **Search input** — `<input type="search">`; updates `queue.state.query`
- [ ] **Sort select** — 4 options from `ADMIN_INBOX_SORTS`: `sla_then_priority`
      (default), `priority_then_sla`, `last_updated_desc`,
      `workspace_then_priority`
- [ ] **Row checkbox** (per row) — `aria-label="Select <title>"`; toggles
      selection set
- [ ] **Row title link** — `<Link href={item.originUrl}>`; jumps to the source
      workspace's detail surface
- [ ] **Row "Inspect" button** — `aria-label="Inspect <title>"`; opens
      `AdminInlineDrawer` by setting `queue.state.detailId`
- [ ] **Bulk-assign target input** — free-text reviewer id (e.g.
      `reviewer-sec-01`); required for Bulk assign
- [ ] **Bulk acknowledge** (button) — disabled while submitting or over batch
      max; `POST /api/admin/inbox/bulk-action` with `action: 'acknowledge'`
- [ ] **Bulk assign** (button) — disabled when no eligible items; submits
      `action: 'assign'` with `targetOperatorId`
- [ ] **Clear** (button) — resets `selectedIds` and bulkSubmit state

## Data & contracts

- **Reads**: `loadWorkspaceDetail('inbox', session)` →
  `/v1/admin/workspaces/inbox` (BFF inbox composer `getInboxWorkspace`);
  snapshot extracted via `extractInboxSnapshot` yields `items: AdminInboxItem[]`
  and `metrics: AdminInboxMetrics` (contracts in `@oshun/contracts`)
- **Writes**: `POST /api/admin/inbox/bulk-action` with
  `{ action: 'acknowledge'|'assign', itemIds, targetOperatorId? }`; response
  `{ bulkActionRecord: AdminInboxBulkActionRecord }`
- **Realtime**: _None._ Server-rendered items array is static per navigation; no
  SSE/WS in this panel
- **Auth/role check**: middleware (any admin session) + page
  (`canEnterAdminWorkspace(scopes, 'inbox')` → required scopes
  `['admin:*', 'admin:studio']`)

## Cross-references

- Shell: `shell/01-app-shell.md`, `shell/04-workspace-pattern.md`
- Workspace definition: `libs/oshun/navigation/src/admin-ia.ts` (`inbox` —
  `backendStatus: 'bff-backed'`, `bffWorkspaceId: 'inbox'`,
  `kind: 'unified-queue'`)
- Sibling routes (handoff destinations from inbox per relationships):
  `review.md`, `../safety/trust-safety.md`, `../operations/support.md`,
  `../operations/privacy.md`, `../safety/incidents.md`
- Component sources: `apps/oshun/admin/src/components/UnifiedInboxPanel.tsx`,
  `apps/oshun/admin/src/components/AdminQueryFilterInput.tsx`,
  `apps/oshun/admin/src/lib/use-admin-queue-state.ts`,
  `apps/oshun/admin/src/lib/inbox-detail.ts`
- V1 PWA equivalent (consumer-shell operator surface):
  `WALKTHROUGH/operator/operator-admin.md` for cross-product context

## Open questions / known gaps

- [ ] Whether the page intentionally renders `WorkspaceEntryPoint` AND
      `UnifiedInboxPanel` together in the happy path, or whether the entry-point
      block is meant to be hidden once the panel mounts
- [ ] Document the `AdminInlineDrawer` content rendered when `detailId` is set
      (it is referenced but not walked above line 720; nested interactions live
      in that component)
- [ ] Confirm `MAX_INBOX_BULK_ACTION_BATCH_SIZE` value and whether it's exposed
      on the wire (it is enforced client-side; the BFF also caps?)
