Context. surface operator · domain operator (house — incidents, citation drift, tenant requests, support routing) · route /operator/admin · auth signed-in · source apps/oshun/web/src/app/operator/admin/page.tsx
Last walked. 2026-07-03 Operator admin inbox decision addendum — page/header, filter-chip, queue row-cell, detail-card, drift-source, decision-panel, reject/retract rationale gate with BFF readback, incident-action, and anonymous redirect-before-render coverage in apps/oshun/web/e2e/incident-triage.spec.ts and apps/oshun/web/e2e/operator-editorial-review-approval.spec.ts. Evidence: WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md §88
Purpose#
The operator admin inbox — "Lilith Admin · House · Inbox" in the plate-of-record register. A single page that surfaces incidents, citation drift, tenant requests, and support routing as a triage queue with a detail pane. Robots-noindex.
Entry points#
- From
/operator(the index) — surface link routed viagetOperatorIndex() - Profile menu → Operator → Admin
- Direct URL — session-gated only (
proxy.tsredirects sessionless →/welcome?redirect=); no operator-role gate in the proxy - Auto-routed incidents — alerts/auto-generated INC tickets deep-link here
robots: { index: false, follow: false } keeps it out of public SEO.
Layout regions#
The page uses Lilith design primitives directly (LGraphPaper, LOperatorBar,
LCard, LCode, LChip, LEyebrow, LDataCell, LSeverityPill, L,
SERIF_L, SANS_L) rather than ShellLayout.
- Top:
LOperatorBar— product="Lilith Admin", section="House", breadcrumb="Inbox" - Header band:
- Eyebrow accent "Admin · inbox"
- H1-like (Garamond 28) "6 open · 1 critical" (italic open count)
- Right-aligned ghost link:
Audit log→/operator/audit
- Admin inbox console (
data-operator-admin-console) — client island grid (2fr / 1fr) with filterable queue and selected-incident detail.- Filter chips (
data-operator-filter) — real buttons:All,Editorial,Tenant,Auto; active state is recorded indata-operator-active-filter. - Items column (2fr) (
data-operator-inbox-list) — list rows, each laid out as110px 90px 1fr 130px 100px(code / severity / title+sub / assignee / when):LCodeticket codeLSeverityPill level={sev}— S1 → "critical", S2 → "degraded", anything else → "advisory"- Title (Garamond 17 medium) + italic subtitle (Garamond 13 muted)
LDataCell monoassigneeLDataCell monotime, right-aligned- Each row exposes
data-operator-item, category/selected attributes, and cell anchors forcode,severity,title,subtitle,assignee, andwhen.
- Detail card (1fr) (
data-operator-incident-detail="<INC>") — accent-bordered, alignedstart:LCodeheader, e.g. "INC-2041 · 14:22 · auto · Lilith"- Garamond 22 selected title
- Italic selected subtitle
- Severity pill, optional public-scene deep-link for abuse-derived INCs, and optional provenance handoff for citation-drift/generated-artifact INCs
- Drift sources list —
LEyebrowheading + rows mapped fromdata.driftSourcesonly for the citation-drift incident (each[source, when, weight]: italic source +LCodewhen + weight) AdminInboxDecisionPanel(incidentId="INC-2041") — three verdicts:approved-republish/request-rereview/rejected-retract, keyed to the currently selected incident.AdminInboxIncidentActions— acknowledge, assign, escalate, snooze, and resolve controls against the selected incident.AdminEvidenceQueuePanel(data={data.evidenceReview}) renders below the grid (operator.tsx:661)
- Filter chips (
States#
- Loading — source-verified: no local
loading.tsx;page.tsxawaitsgetAdminInbox()server-side before rendering. - Anonymous user — redirected to
/welcome?redirect=(proxy.ts) before[data-operator-admin-page], rows, or the decision panel render (Playwright, 2026-06-25) - Signed-in user — full inbox renders (session-gated only; no operator-role gate in the proxy)
- Inbox with items + selection — selected row has accent ring; detail card and decision/action panels match the selected incident
- Inbox with no items — no full-page empty fixture is available; filter
empty state copy exists in
AdminInboxQueuebut is not reachable from the seeded all/editorial/tenant/auto categories. -
getAdminInbox()fails — no local segmenterror.tsxis present;getAdminInbox()fail-softs live abuse-report fetches back to the static fixture. - Offline — write-heavy surface; queued or disabled (verify)
- Different operator personas — items filtered by role
Interactions#
Filter chips#
-
Allchip (accent active) — shows all six seeded INC rows -
Editorialchip — filters to the two editorial-class rows -
Tenantchip — filters to the two tenant-class rows -
Autochip — filters to the two auto-generated rows
Filter state is component-local. It updates data-operator-active-filter and
data-operator-inbox-count; it does not write URL state.
Items list#
- Each row — click selects; selected row paints with
data-operator-item-selected="true"; detail card, decision panel, and incident actions swap to the selected incident.- Keyboard: row is
role="button"and Enter/Space selects; arrow-key roving focus is not implemented. - Touch ≥ 44×44 px by row padding/grid height.
- Keyboard: row is
- Row cells — all six fixture rows are asserted cell-by-cell (code,
severity, title, subtitle, assignee, when) via
data-operator-item-cell. -
LSeverityPill— non-interactive; conveys severity via shape + label, not color alone -
LCodeticket code — non-interactive display
Detail card#
-
AdminInboxDecisionPanelverdict picker — three radio-style verdicts (approved-republish,request-rereview,rejected-retract); posts to the real audited BFF decision route in E2E and rendersdata-decision-audit-id. The reject/retract branch now proves no/short rationale leaves the action placeholder-disabled with no browser POST; valid rationale records and BFF GET rehydrates the same rejected decision, rationale, and audit id. - Drift-sources list — non-interactive source / when / weight rows, visible for the default citation-drift incident and removed when a non-drift row is selected.
- Provenance handoff — citation-drift rows can render
data-operator-incident-provenance-link="<INC>";INC-2041links to/operator/admin/isis/provenance?inc=INC-2041and is covered bycitation-drift-resolution.spec.ts. -
AdminEvidenceQueuePanel— evidence review queue below the grid; Playwright covers source/retrieval inspection, citation validation, and grounded-report promotion. - Incident lifecycle actions — acknowledge, escalate, and resolve are covered through real BFF writes; server rationale floors are covered by pure-HTTP tests.
- Cross-reference link to fixture artifact codes like
TR-047— not present for the static drift fixture. Abuse-derived rows can expose a public-scene "Open offering" link. - Audit-trail visible — the page exposes an
Audit logentry point and decision writes render the real audit id.
E2E coverage#
apps/oshun/web/e2e/incident-triage.spec.tscovers signed-in/operator/admin, page/header/audit-entry anchors, filter chips, all six fixture rows cell-by-cell, detail-card selection, drift-source rows, decision-panel rationale gates and real audited BFF writes, incident lifecycle actions with an opt-in BFF harness reseed, server-side rationale-floor failures, and anonymous redirect-before-render.apps/oshun/web/e2e/operator-editorial-review-approval.spec.tscovers the shared decision panel's editorial review approve/reopen path, reject/retract rationale gate with audited BFF readback, and local rehydrate behavior.apps/oshun/web/e2e/operator-admin-evidence-queue.spec.tscovers the evidence review queue below the inbox grid.apps/oshun/web/e2e/citation-drift-resolution.spec.tscovers the citation-drift row's "Open provenance" handoff from the selected incident detail into/operator/admin/isis/provenance?inc=INC-2041.
Data & contracts#
- Reads:
getAdminInbox()from@/lib/lilith-data/operator-depthreturningAdminInboxData. It prepends live public-abuse reports from/v1/admin/abuse-reportswhen present and otherwise falls back to the static fixture baseline. Rows may carryshortCodefor scene review orprovenanceHreffor provenance inspection handoff. - Writes:
AdminInboxDecisionPanelposts to/v1/admin/operator-inbox/:id/decisionand.../reopen;AdminInboxIncidentActionsposts to/v1/admin/operator-incidents/:id/<verb>for acknowledge, assign, escalate, snooze, and resolve. - Realtime: incidents stream in via
oshun-sw-updateor similar push channel (verify against shell/05-notifications.md) - Caching: server-rendered; SW excludes operator routes from runtime cache (verify)
- Auth/role check: session-gated only (
proxy.tsredirects sessionless →/welcome?redirect=); no operator-role gate in the proxy
Cross-references#
- Parent:
operator-overview.md - Shell:
shell/04-auth-session.md— role gates - Sibling operator routes:
operator-studio.md— editorial review queueoperator-tenant.md— tenant consoleoperator-metis.md— institutional dashboardoperator-admin-isis-provenance.md— provenance inspector (sub-route)
- Component sources:
apps/oshun/web/src/components/lilith/operator.tsx(line 486:AdminInbox)
- Feature spec:
V1/features.md - Architecture:
V1/ARCHITECTURE.md
Open questions / known gaps#
- Audit the detail-card action set in current source (acknowledge, assign, escalate, snooze, resolve) and cover acknowledge/escalate/resolve in Playwright (2026-06-25)
- Confirm filter chips wire to a real handler (component-local filter state, no URL state)
- Document the realtime channel (Psyche topic) that feeds new incidents to this view
- Confirm SW caching policy for
/operator/*(likely network-only, audit-stamped writes) - An operator-role gate appears absent —
proxy.tsonly redirects sessionless users to/welcome; no role check in the proxy or page components (possible gap vs intended scope)