---
path: /operator/incidents
surface: operator
domain: operator (house — incident triage)
auth: signed-in
source: apps/oshun/web/src/app/operator/incidents/page.tsx
status: walked
last_walked: >
  2026-06-25 incident-triage row-cell addendum — Playwright real-dev-infra
  coverage for signed-in /operator/incidents render, generatedAt anchor, exact
  1-open/1-closed masthead tally, lane/list counts, row IDs, row metadata
  attributes, severity/summary/meta/mitigation cells, all footer targets, and
  anonymous redirect-before-render. Evidence:
  WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §89; spec:
  apps/oshun/web/e2e/incident-triage.spec.ts.
---

# Operator · incidents triage

## Purpose

The incident clipboard: open and recently-closed incidents from
`GET /v1/incidents`, split into two lanes. Each row is severity colour-coded
(p0–p3) and shows the opener time, acknowledger, and mitigation state. A
read-only triage view — there are no acknowledge/escalate controls in this code.

## Entry points

- **From `/operator`** (hub) — operator-surface card; foot row "← Operator hub"
  returns there
- **Cross-links from siblings** — `/operator/audit` foot row links here; this
  page links onward to `/operator/audit` and `/operator/tenants`
- **Auto-routed alerts** — auto-generated incidents land in the feed (verify
  deep link to a specific incident; no per-incident route observed)
- **Direct URL** — session-gated only (`proxy.ts` redirects sessionless →
  `/welcome?redirect=`); no operator-role gate in the proxy
- **Bookmark** — `canonical = '/operator/incidents'`

## Layout regions

`page.tsx` is an async server component:
`await bffGet<IncidentsResponse>( '/v1/incidents')` (falling back to
`{ incidents: [] }`), partitioned into `open` (`mitigationState` open or
investigating) and `recent` (everything else), rendered inside
`<LWebShell top={<LCustomerNav active="library" />}>` (reuses the **customer**
nav, Library active). Centered column `maxWidth: 1080`.

- **Header**: `LCustomerNav` (Library highlighted — verify intended chrome)
- **Page anchor**: outer content column exposes `data-operator-incidents-page`
  and `data-operator-incidents-generated-at`
- **Masthead**: `LMasthead` wrapped by `data-operator-incidents-masthead` with
  `data-operator-incidents-open-count` and
  `data-operator-incidents-recent-count`; visible copy is left "Operator ·
  incidents", right `${open.length} open · ${recent.length} closed`, kicker "The
  clipboard", title "The _incident_ queue."
- **"Open" lane**: `IncidentTable title="Open"` —
  `<section data-operator-incidents-lane="open" data-operator-incidents-lane-count>`
  with `LEyebrow` + list of rows
- **"Recently closed" lane**: `IncidentTable title="Recently closed"` —
  `<section data-operator-incidents-lane="recent" data-operator-incidents-lane-count>`
- **Lane list**: populated lanes expose `data-operator-incidents-list` plus
  `data-operator-incidents-list-count`
- **Incident row**: `<li data-operator-incident-row>` grid `60px 1fr auto` —
  stable attributes expose `data-operator-incident-id`,
  `data-operator-incident-severity`, `data-operator-incident-mitigation`,
  `data-operator-incident-opened-at`, and
  `data-operator-incident-acknowledged-by`; visible cells are severity pill
  (colour from `SEVERITY_COLOUR`), summary + "opened <time> · ack
  <acknowledgedBy>" sub-line, mitigation-state label (accent text, from
  `MITIGATION_LABEL`). The visible cells also expose
  `data-operator-incident-cell="severity" | "summary" | "meta" | "mitigation"`
- **Footer action row**: `LBtn` ghost "← Operator hub" (`/operator`), `LBtn`
  ghost "Audit log" (`/operator/audit`), `LBtn` primary "Tenants"
  (`/operator/tenants`)

## States

- [x] **Loading** — source-verified 2026-06-25: no route-level `loading.tsx`;
      server-side `await bffGet` blocks render, so there is no skeleton state
- [x] **Anonymous user** — Playwright 2026-06-25: sessionless
      `/operator/incidents` redirects to `/welcome?...` before
      `[data-operator-incidents-page]` or `[data-operator-incident-row]` render
- [x] **Signed-in user** — renders (session-gated only; no operator-role gate in
      the proxy and no in-page guard); Playwright pins the current real fixture
      partition at 1 open (`investigating`) and 1 closed (`mitigated`)
- [x] **Both lanes empty** — source-verified 2026-06-25: each `IncidentTable`
      shows `[data-operator-incidents-empty]` with "Nothing in this lane."
      Masthead right reads "0 open · 0 closed". Dedicated empty-route E2E is
      still pending because the server-component fetch cannot be page-routed
- [ ] **Open populated, closed empty** (and vice versa) — only the populated
      lane lists rows
- [x] **Severity render** — Playwright pins the current p1 and p2 fixture
      badges; a full p0–p3 diversity matrix is not present in the current
      fixture
- [x] **Mitigation render** — Playwright pins the current `investigating` and
      `mitigated` fixture states; full mitigation diversity is not present in
      the current fixture
- [x] **BFF unreachable / non-2xx** — source-verified 2026-06-25: `bffGet`
      returning `null` substitutes `{ incidents: [] }` → both lanes empty (no
      fabricated incidents). Dedicated outage-route E2E remains pending
- [ ] **Offline** — RSC fetch (`revalidate 60`); operator SW policy TBD
- [ ] **Standalone PWA** — renders; verify robots/no-index (no `robots` export
      here)

## Interactions

### Masthead

- [x] **Masthead labels** — non-interactive; Playwright asserts the queue
      heading, "The clipboard", exact "1 open · 1 closed" text, and matching
      stable count attributes

### Incident row (per `row` in each lane)

- [x] **Severity pill** (text badge) — `row.severity` (p0–p3); background from
      `SEVERITY_COLOUR`; non-interactive
- [x] **Summary + meta** (text) — `row.summary`, then "opened
      <localized openedAtIso> · ack <acknowledgedBy>"; non-interactive
- [x] **Mitigation-state label** (accent text) —
      `MITIGATION_LABEL[row.mitigationState]`; non-interactive
- [x] **Row** — Playwright asserts real row ids and stable severity/mitigation
      attributes, lane membership, opened-at/acknowledged-by metadata
      attributes, and visible severity/summary/meta/mitigation cells; source
      confirms no click handler / drill-in / acknowledge control wired in this
      route

### Footer actions

- [x] **"← Operator hub"** (`LBtn` ghost, link) → `/operator`
- [x] **"Audit log"** (`LBtn` ghost, link) → `/operator/audit`
- [x] **"Tenants"** (`LBtn` primary, link) → `/operator/tenants`

## Data & contracts

- **Reads**: `bffGet<IncidentsResponse>('/v1/incidents')` —
  `{ generatedAt: string; incidents: Incident[] }` where
  `Incident = { id, severity: 'p0'|'p1'|'p2'|'p3', summary, openedAtIso, acknowledgedBy, mitigationState: 'open'|'investigating'|'mitigated'|'resolved' }`.
  On `null` substitutes an empty list. Partitioning into open/recent is done
  client-side
- **Writes**: _None observed in this view._ (acknowledge / escalate / close are
  not wired here — triage actions presumably live elsewhere or are a gap)
- **Realtime**: _None observed._ (verify whether new incidents stream in via a
  push channel — see [`operator-admin.md`](./operator-admin.md) open questions)
- **Caching**: RSC fetch, `next.revalidate = 60`, 5 s abort
- **Auth/role check**: session-gated only (`proxy.ts` redirects sessionless →
  `/welcome?redirect=`); no operator-role gate in the proxy and no in-page guard

## E2E coverage

- [`apps/oshun/web/e2e/operator-surfaces.spec.ts`](../../apps/oshun/web/e2e/operator-surfaces.spec.ts)
  — signed-in `/operator/incidents` render, queue masthead/tally, at least one
  incident row, severity text badge, and mitigation-state label.
- [`apps/oshun/web/e2e/incident-triage.spec.ts`](../../apps/oshun/web/e2e/incident-triage.spec.ts)
  — direct `/operator/incidents` signed-in render, stable page/masthead/lane/
  list/row-cell/footer anchors, generatedAt ISO attribute, exact 1-open/1-closed
  tally, exact lane/list counts, real BFF fixture row IDs
  `inc-2026-05-23-bff-503` and `inc-2026-05-25-themis-queue`, row
  severity/mitigation/opened-at/acknowledged-by attributes, visible
  severity/summary/meta/mitigation cells, footer hrefs, and anonymous
  redirect-before-render. It also covers the richer `/operator/admin` incident
  lifecycle actions over the real BFF.
- Still not browser-covered on this server-component route: forced empty lanes,
  forced BFF-unreachable fallback, offline/standalone/SW policy, and unbuilt row
  drill-in/actions.

## Cross-references

- Parent: [`operator-overview.md`](./operator-overview.md)
- Sibling operator routes:
  - [`operator-audit.md`](./operator-audit.md)
  - [`operator-tenants.md`](./operator-tenants.md)
  - [`operator-sso.md`](./operator-sso.md)
  - [`operator-admin.md`](./operator-admin.md) — richer inbox triage with a
    detail pane (the `AdminInbox` component)
- Feature spec: [`V1/features.md`](../../V1/features.md#product-surfaces)
- Architecture:
  [`V1/ARCHITECTURE.md`](../../V1/ARCHITECTURE.md#operator-surfaces)
- Journeys traversing this view:
  - [`../journeys/incident-triage.md`](../journeys/incident-triage.md)

## Open questions / known gaps

- [ ] No acknowledge / investigate / mitigate / resolve actions on rows — an
      operator can read but not act. Confirm whether triage actions belong here
      or only on `/operator/admin` (the `AdminInbox` detail card)
- [ ] No per-incident detail route / row drill-in observed; document where an
      operator opens the full incident record
- [ ] No realtime channel wired; document how the queue refreshes (only RSC
      `revalidate 60`)
- [ ] This operator route renders the customer `LCustomerNav` (Library active)
      and has no `robots: { index: false }` — confirm intended chrome and SEO
      gating
- [ ] An operator-role gate appears absent — `proxy.ts` only redirects
      sessionless users to `/welcome`; no role check in the proxy or page
      component (possible gap vs intended scope)
