Tenant Admin Console · Surface walkthrough

Content scopes

A per-surface walkthrough of the Tenant Admin Console tenant-admin surface: layout, states, interactions, data, and cross-references.

drafted
8sections2 minread

On this page

Context. surface tenant-admin · domain governance · route /content · auth signed-in (tenant) + scope:tenant:admin:{tenantId} (or tenant:admin:*) · source apps/oshun/tenant-admin/src/app/content/page.tsx

Last walked.

Purpose#

Shows the effective tenant visibility (allow/deny) for content objects — courses, personas, briefings, sources — after resolving an inherited district policy plus tenant-level overrides via bulkResolveScope. It is the content workspace (group: configuration) in @oshun/navigation.

Entry points#

  • Shell sidebar Configuration › Content item (TenantSidebar, shortLabel: 'Content', path /content).
  • Direct URL /content (bookmarkable; scope-gated).

Layout regions#

Wrapped in <TenantShell session currentWorkspaceId="content">:

  • Sidebar / Header: standard chrome (header crumb "Content scopes").
  • Main (<section data-testid="tenant-content-panel" data-tenant-id={...}>):
    • <h1>Content scopes</h1> + subtitle on inherited district policy + tenant overrides.
    • styles.summaryGrid of three tiles: Allowed (count of r.visibility === 'allow'), Denied (count of 'deny'), Overrides (schoolScope.entries.length = 2).
    • styles.table columns Object / Kind / Effective visibility, one row per resolved object; visibility renders a badgeGood/badgeBad badge.

States#

  • Loading — N/A; synchronous server render from bulkResolveScope() over hard-coded district + school scope chains (no fetch).
  • Empty — N/A; four objects are always resolved (metis-civic-reasoning, metis-socratic-guide, q2-exam-prep, unapproved-archive).
  • Populated (short) — always 4 rows. The chain resolves district-allow then tenant override (e.g. metis-socratic-guide flips to deny at the school scope; unapproved-archive falls through to defaultPolicy: deny).
  • Populated (long) — N/A; fixed 4-object dataset; no pagination.
  • Error (recoverable) — N/A; no query.
  • Error (unrecoverable) — no custom handler; render throw → Next default.
  • Offline — N/A (server-rendered).
  • Gated — no session → redirect('/unauthorized?reason=missing-session&returnTo=/content'); middleware blocks unauthenticated requests upstream.
  • Standalone PWA — N/A; no manifest. (verify.)

Interactions#

  • Summary tiles (styles.summaryItem × 3) — display-only counts (Allowed / Denied / Overrides); not focusable, no telemetry.
  • Resolved-object table rows (<tr> keyed by objectKind:objectId)
    • Function: render-only; shows objectId (mono <code>), objectKind, and a visibility badge. No toggle/override-edit controls exist in code — read-only.
    • Keyboard: table is not interactive.
    • Screen reader: standard table semantics.
    • Touch target: N/A.
    • Mobile (≤ 640 px): workspace.module.css table styling.
    • Disabled when: N/A.
    • Offline behavior: N/A.
    • Telemetry: None.

Data & contracts#

  • Reads: getTenantServerSession(); bulkResolveScope({ chain, objects }) from @oshun/tenant-console — input is a TenantContentScope[] precedence chain (district → school) plus the list of objects to resolve; returns each object's effective visibility. Both scopes and the object list are hard-coded in the page. No BFF call.
  • Writes: None.
  • Realtime: None.
  • Caching: SSR (dynamic; cookie read). Deterministic per tenant id.
  • Auth/role check: middleware.ts + page getTenantServerSession()canEnterTenantShell.

Cross-references#

  • Domain logic: libs/oshun/tenant-console/src (bulkResolveScope, TenantContentScope). Index: libs/oshun/tenant-console/src/index.ts.
  • Workspace model: libs/oshun/navigation/src/tenant-ia.ts (content, group: configuration).
  • Sibling routes: home.md, agents.md. /policy (inherited bundles + tighten-only overrides) lacks a file yet.

Open questions / known gaps#

  • District + school scope chains and the object list are hard-coded seed data; no BFF read of the tenant's real content-scope policy is wired. Document the eventual backing store and override-write path.
  • Page is read-only — no UI to add/edit allow/deny overrides. Confirm whether editing is in scope for a later phase.