Admin Cockpit · Surface walkthrough

Shell: Workspace pattern

A per-surface walkthrough of the Admin Cockpit: layout, states, interactions, data, and cross-references.

unspecified
11sections5 minread2tables

On this page

Source: apps/oshun/admin/src/lib/server-session.ts (getAdminServerSession, fetchAdminOperatorView), apps/oshun/admin/src/lib/workspace-loader.ts (loadWorkspaceOverview, loadWorkspaceDetail), apps/oshun/admin/src/lib/bff-client.ts (AdminBffFetchResult, AdminBffWorkspaceSummary, AdminBffWorkspaceDetailResponse), apps/oshun/admin/src/components/WorkspaceEntryPoint.tsx, apps/oshun/admin/src/components/WorkspaceSummaryCard.tsx, apps/oshun/admin/src/components/WorkspacePage.tsx, libs/oshun/navigation/src/admin-ia.ts (OSHUN_ADMIN_WORKSPACE_MODEL)

The shape every admin workspace page follows. Walk this once; then per-view files focus only on what's unique to each workspace.

Canonical page shape#

tsx
export default async function FooPage(): Promise<JSX.Element> {
  const session = await getAdminServerSession();
  if (!session) {
    redirect('/unauthorized?reason=missing-session&returnTo=/foo');
  }
  const [detail, operatorView] = await Promise.all([
    loadWorkspaceDetail('foo', session),
    fetchAdminOperatorView(session),
  ]);
  return (
    <AdminShell session={session} currentWorkspaceId="foo" operator={operatorView.view}>
      <FooPanel detail={detail} ... />
      {/* or fallback */}
      <WorkspaceEntryPoint workspaceId="foo" accessible={detail.accessible} detail={detail.result} />
    </AdminShell>
  );
}

Three things happen on every page render:

  1. Server session checkgetAdminServerSession() returns null if no valid admin cookie; page redirects to /unauthorized with reason
    • returnTo.
  2. Parallel data fetchloadWorkspaceDetail(workspaceId, session) for workspace data + fetchAdminOperatorView(session) for the operator's display profile.
  3. Render<AdminShell> wraps the workspace body. The body is either a workspace-specific panel (when data is available and the operator has access) or <WorkspaceEntryPoint> (which itself renders an "Access not granted" or "Workspace data unavailable" notice).

getAdminServerSession()#

Returns AdminServerSession | null. When non-null:

  • session.session.payload.userId — operator user id
  • session.session.payload.scopes — array of scopes
  • session.canEnterShell — boolean used by assistant guards

Null means no valid admin cookie. Pages handle this by redirecting, not by rendering a denial UI inline — that's the middleware//unauthorized role.

loadWorkspaceDetail(workspaceId, session)#

Returns an AdminBffFetchResult<AdminBffWorkspaceDetailResponse>:

  • { ok: true, value: { workspace: AdminBffWorkspaceSummary, items: …, metrics: … } }
  • { ok: false, reason: 'workspace-unavailable' | 'forbidden' | 'network' | …, message?: string }

Plus an accessible flag derived from canEnterAdminWorkspace(session scopes, workspaceId).

For pages with no BFF backing (e.g., lilith workspace has backendStatus: 'backend-pending'), result may be null and the page falls back to <WorkspaceEntryPoint>'s "Workspace data unavailable" notice.

IA id vs BFF id — watch for drift#

Several workspaces' bffWorkspaceId does not match their IA id. loadWorkspaceDetail(<iaId>, session) reads bffWorkspaceId off the workspace definition and hits /v1/admin/workspaces/<bffId>, not the IA id. When you're tracing a log line or a network panel, the request path will reference the BFF id; the URL and sidebar reference the IA id. Use this table to translate.

IA id (route + sidebar) BFF id (request path)
dashboard n/a (composed-from-workspaces)
inbox inbox
review review
policy policy
trust-safety moderation
lilith n/a (backend-pending)
rights rights
incidents incident
editorial editorial
research-integrity research-integrity
personas persona
models model
isis n/a (per-route loaders)
support support
privacy privacy
analytics analytics
admin-tools n/a (composed-from-workspaces)
messaging n/a (backend-pending)
tenant-console n/a (backend-pending)

The four bolded rows differ between IA and BFF and have caused log-tracing confusion in the past. The drift is now codified in libs/oshun/navigation/src/admin-ia.ts as OSHUN_ADMIN_BFF_ID_DRIFT (an explicit list of the four mismatched pairs) plus two derivation helpers (P3 2026-05-25):

  • resolveBffWorkspaceIdFromIaId(iaId) — call this whenever building a BFF URL or correlating a BFF call. Returns undefined for non-BFF-backed workspaces.
  • resolveAdminWorkspaceIdFromBffId(bffId) — reverse direction. Use this when reading raw BFF telemetry / audit logs and needing to surface the matching IA workspace label in the cockpit.

A contract test (admin-ia.test.ts) asserts the four declared drift pairs match the IA model and that every other BFF-backed workspace has IA id === BFF id (no hidden drift). Renaming the BFF ids to match the IA ids would make all four pairs identity-mapped but requires a coordinated rewrite of apps/oshun/bff/src/admin/state.ts; until that happens, the helpers are the canonical translation layer — do not inline the conditional in call sites.

loadWorkspaceOverview(session)#

Returns the full multi-workspace summary for the dashboard. Used by / to render WorkspaceSummaryCards for every workspace at once.

WorkspaceEntryPoint#

apps/oshun/admin/src/components/WorkspaceEntryPoint.tsx (110 lines)

The fallback / wrapper body. Renders:

  • Heading block — h2 with definition.label; tagline from definition.operatorPromise; <dl> with Group, Backend, Operator role from definition.backendStatus and definition.operatorRelationship
  • WorkspaceSummaryCard — summary metrics from the BFF response; renders queue health, item count, last-updated, etc. Falls back to "no summary" if result is null
  • accessible === false panelrole="alert"; heading "Access not granted"; body explains the required scopes (admin:*, admin:studio, or workspace-specific) and the ADR-0029 privileged handoff path
  • accessible === true && !detail.ok panelrole="alert"; heading "Workspace data unavailable"; body either reason: 'workspace-unavailable' or detail.message
  • Related handoffs blocklistOshunAdminWorkspaceRelationships() filtered to from === workspaceId; for each, shows target label, reason, trigger; heading "Handoffs from here"

States to walk:

  • Accessible + data ok — full summary card renders
  • Accessible + workspace-unavailable — "Workspace data unavailable" notice; rest of summary block still renders
  • Accessible + network errordetail.message surfaces
  • Not accessible — "Access not granted" alert; no summary card data shown
  • Empty related handoffs — section omitted

WorkspaceSummaryCard#

apps/oshun/admin/src/components/WorkspaceSummaryCard.tsx

Renders the per-workspace summary tile. Used both inside WorkspaceEntryPoint (one) and in the dashboard (many). Walk this component for its own states (loading / empty / populated / error).

Workspace-specific panels#

Most workspaces have a dedicated panel component beyond WorkspaceEntryPoint. Inventory (incomplete; per-view files fill in):

Workspace Panel component
dashboard CopilotHealthPanel, WorkspaceSummaryCard ×N
inbox UnifiedInboxPanel
review (workspace panel TBD per per-view)
review/[reviewId] review-detail workspace component
trust-safety moderation queue panel
(others) per-view files document the panel

Per-view files own the per-workspace panel walkthrough. This shell doc covers only the WorkspaceEntryPoint fallback path.

Operator copilot guard#

Every workspace inherits the assistant-invocation guard from AdminShell. The guard input includes:

ts
{
  invocationPointId,
  authenticated: session.canEnterShell,
  activePath: window.location.pathname,
  viewportWidthPx: window.innerWidth,
  scopes: session.session.payload.scopes,
  entitlements: ['assistant.admin'],
  policyGrants: ['assistant:operations'],
}

The result becomes assistantGuardDecision. If denied, the assistant panel shows the block reason. See 01-app-shell.md for the full guard composition.

States that apply to every workspace page#

  • Anonymous — middleware redirects; never reaches the page
  • Admin session, missing workspace scopeaccessible: false; <WorkspaceEntryPoint> renders denial panel
  • Admin session, workspace scope, BFF ok — workspace-specific panel renders with data
  • Admin session, scope ok, BFF returns workspace-unavailable — "Workspace data unavailable" notice
  • Admin session, scope ok, BFF returns network error — error message via detail.message
  • backendStatus: 'backend-pending' workspace — no BFF call; always shows the entry-point fallback (Lilith is the canonical example today)

Cross-references#

  • 01-app-shell.md — AdminShell composition
  • 02-routing-layouts.md — middleware that enforces the session before the page runs
  • 03-auth-session.md — scopes and access matrix
  • Workspace definitions: libs/oshun/navigation/src/admin-ia.ts
  • BFF client: apps/oshun/admin/src/lib/bff-client.ts

Open questions / known gaps#

  • Inventory every workspace's dedicated panel component (when one exists beyond WorkspaceEntryPoint) and link from each per-view file
  • Document the BFF endpoint pattern (/v1/admin/workspaces/<id> based on the inbox example) and which workspaces it covers
  • Confirm forbidden-workspace reason originates from a page-level check, not from middleware (middleware doesn't read scopes today)
  • Document how WorkspaceSummaryCard behaves when summary is partial (some fields present, others missing)