V1 Web PWA · Surface walkthrough

Operator · incidents triage

A per-surface walkthrough of the V1 Web PWA operator surface: layout, states, interactions, data, and cross-references.

walked
9sections6 minread

On this page

Context. surface operator · domain operator (house — incident triage) · route /operator/incidents · auth signed-in · source apps/oshun/web/src/app/operator/incidents/page.tsx

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.

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
  • Bookmarkcanonical = '/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
  • Footer action row: LBtn ghost "← Operator hub" (/operator), LBtn ghost "Audit log" (/operator/audit), LBtn primary "Tenants" (/operator/tenants)

States#

  • Loading — source-verified 2026-06-25: no route-level loading.tsx; server-side await bffGet blocks render, so there is no skeleton state
  • Anonymous user — Playwright 2026-06-25: sessionless /operator/incidents redirects to /welcome?... before [data-operator-incidents-page] or [data-operator-incident-row] render
  • 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)
  • 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
  • Severity render — Playwright pins the current p1 and p2 fixture badges; a full p0–p3 diversity matrix is not present in the current fixture
  • Mitigation render — Playwright pins the current investigating and mitigated fixture states; full mitigation diversity is not present in the current fixture
  • 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#

  • 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)#

  • Severity pill (text badge) — row.severity (p0–p3); background from SEVERITY_COLOUR; non-interactive
  • Summary + meta (text) — row.summary, then "opened · ack "; non-interactive
  • Mitigation-state label (accent text) — MITIGATION_LABEL[row.mitigationState]; non-interactive
  • 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
  • "← Operator hub" (LBtn ghost, link) → /operator
  • "Audit log" (LBtn ghost, link) → /operator/audit
  • "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 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 — 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 — 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#

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)