---
path: /
surface: tenant-admin
domain: meta
auth: signed-in (tenant) + scope:tenant:admin:{tenantId} (or tenant:admin:*)
source: apps/oshun/tenant-admin/src/app/page.tsx
status: drafted
last_walked: '—'
---

# Tenant home

## Purpose

Arrival view for the tenant cockpit: it greets the signed-in tenant admin with
their tenant id and lists every tenant-admin workspace (label + primary job) so
they can jump into members, identity, policy, audit, and the rest. It is the
`home` workspace defined in `@oshun/navigation`
(`OSHUN_TENANT_WORKSPACE_MODEL`).

## Entry points

- Direct URL `/` (default landing after a tenant session is issued).
- `/handoff` "Continue to tenant home →" link (after IdP issues a tenant-scoped
  session — see [handoff.md](./handoff.md)).
- `/unauthorized` → `/handoff?returnTo=/` chain once a session exists.
- Shell sidebar **Core › Home** item (`TenantSidebar`, `shortLabel: 'Home'`).

## Layout regions

Wrapped in `<TenantShell session currentWorkspaceId="home">`:

- **Sidebar** (`TenantSidebar`): Oshun brand + "Tenant cockpit" mode, tenant id
  label, three groups (Core / Governance / Configuration) of workspace links.
- **Header** (`TenantHeader`): `tenant · <tenantId>` badge › workspace label
  ("Tenant home"); user id on the right.
- **Main** (`#tenant-main`, `data-testid="tenant-home-page"`):
  - `<h1>Tenant home</h1>`
  - Welcome paragraph containing `<code data-testid="tenant-home-tenant-id">`.
  - `<section aria-labelledby="tenant-workspace-grid-heading">` with
    `<h2 id="tenant-workspace-grid-heading">Workspaces</h2>` and a
    `<ul data-testid="tenant-workspace-grid">` of all 13 workspace definitions.

## States

- [ ] **Loading** — N/A; server component renders synchronously from
      `getTenantServerSession()` + `listOshunTenantWorkspaceDefinitions()` (no
      client fetch, no Suspense boundary).
- [ ] **Empty** — N/A; the workspace list is sourced from a static model
      (`OSHUN_TENANT_WORKSPACE_IDS`, 13 entries) and is never empty.
- [ ] **Populated (short)** — N/A; the list is always exactly the 13 canonical
      workspaces.
- [ ] **Populated (long)** — N/A; fixed-length static list.
- [ ] **Error (recoverable)** — N/A; no query to fail.
- [ ] **Error (unrecoverable)** — no in-page handler; a thrown render error
      surfaces via Next's default error boundary (no custom `error.tsx`).
- [ ] **Offline** — N/A as a write/fetch concern; the page is fully server
      rendered. (verify: no service worker registered for tenant-admin.)
- [ ] **Gated** — no session → `getTenantServerSession()` returns `null` →
      `redirect('/unauthorized?reason=missing-session&returnTo=/')`. Middleware
      redirects unauthenticated requests first (defense-in-depth duplicate).
- [ ] **Standalone PWA** — N/A; `layout.tsx` sets `robots: { index: false }` and
      a viewport but ships no manifest/installability. (verify.)

## Interactions

- [ ] **Workspace list items** (`<li data-workspace-id={definition.id}>`)
  - Function: render-only — each shows `<strong>{label}</strong> — {primaryJob}`
    for all 13 workspaces. The list items are NOT links (navigation links live
    in the sidebar; see [Cross-references](#cross-references)).
  - Keyboard: not focusable (plain text).
  - Screen reader: announced as list items under the "Workspaces" heading.
  - Touch target: N/A (non-interactive).
  - Mobile (≤ 640 px): inherits `TenantShell` layout.
  - Disabled when: N/A.
  - Offline behavior: N/A (static).
  - Telemetry: _None._
- Shell-level interactions (sidebar links, skip link) are documented in
  [Cross-references](#cross-references) and belong to `TenantShell`.

## Data & contracts

- **Reads**: `getTenantServerSession()` (cookie `oshun-tenant-session`, parsed
  by `parseTenantSessionToken`) → `TenantServerSession`;
  `listOshunTenantWorkspaceDefinitions()` from `@oshun/navigation` →
  `OshunTenantWorkspaceDefinition[]`. **No BFF call.**
- **Writes**: _None._
- **Realtime**: _None._
- **Caching**: SSR (dynamic — reads request cookies via `next/headers`).
- **Auth/role check**: `middleware.ts` (redirects to `/unauthorized` when the
  cookie is missing/invalid) + page-level `getTenantServerSession()` which calls
  `canEnterTenantShell(scopes, tenantId)` — requires `tenant:admin:*` or
  `tenant:admin:{tenantId}`.

## Cross-references

- Workspace/IA model: `libs/oshun/navigation/src/tenant-ia.ts`
  (`OSHUN_TENANT_WORKSPACE_MODEL`, `home` = group `core`, path `/`).
- Shell components: `apps/oshun/tenant-admin/src/components/TenantShell.tsx`,
  `TenantSidebar.tsx`, `TenantHeader.tsx`.
- Sibling routes: [agents.md](./agents.md), [audit.md](./audit.md),
  [content.md](./content.md), [data.md](./data.md), [handoff.md](./handoff.md),
  [help.md](./help.md), [identity.md](./identity.md). Workspaces without files
  yet: `/members`, `/roles`, `/integrations`, `/policy`, `/notifications`,
  `/status`.
- Operator-admin counterpart shell pattern:
  [`../ADMIN_WALKTHROUGH/workspaces/operations/analytics.md`](../ADMIN_WALKTHROUGH/workspaces/operations/analytics.md).

## Open questions / known gaps

- [ ] Sidebar groups in `TenantSidebar` cover only `core`, `governance`,
      `configuration` (13 workspaces). Confirm the home-page list (all 13 via
      `listOshunTenantWorkspaceDefinitions`) and sidebar grouping stay in sync.
- [ ] No custom `error.tsx`/`not-found.tsx`/`loading.tsx` in `src/app` — confirm
      whether Next defaults are acceptable for tenant-admin.
- [ ] List items are non-navigable; verify intent that home is purely an index
      and all navigation is via the sidebar.
