---
path: /metis
surface: customer
domain: metis
auth: signed-in
source: apps/oshun/web/src/app/metis/page.tsx
status: walked
last_walked:
  '2026-05-29 automated runtime walk (Playwright headless) — render, /v1 data
  (2xx), console/page-errors, expected content, screenshot verified; live
  screen-reader, touch, offline, and telemetry-delivery checks pending a manual
  AT pass. Evidence: WALKTHROUGH/results/runtime-sweep-2026-05-29.md; content
  re-verified 2026-06-03 against current source; course-card links, first-run
  no-assessment state, and hub CTAs reconciled 2026-06-27 by
  metis-lesson-completion.spec.ts'
---

# Metis · the schoolroom

## Purpose

The Metis domain hub. The schoolroom: active courses, this week's progress
stats, the open assessment, and a tutor grounded in the sources. The recommended
next action resumes a specific course at a specific section when the BFF returns
an in-progress cursor; the live first-run state instead says "Begin your first
lesson" and keeps the same lesson CTA route.

## Entry points

- **Home domain card grid** — see
  [`../02-home-discovery/home.md`](../02-home-discovery/home.md)
- **Domain switcher** — `/switcher` (see
  [`../02-home-discovery/switcher.md`](../02-home-discovery/switcher.md))
- **Library / Explore** — learning-reentry collection and curated routes link
  here
- **Direct URL / bookmark** — yes (auth required via shell middleware)
- **Server fetch** — `getMetis()` runs on the server before render

## Layout regions

`page.tsx` is a thin server component: `await getMetis()` →
`<MetisSchoolroom data={data} />`.

Inside `MetisSchoolroom` (`metis.tsx`, line 65):

- **Web shell top** — `LCustomerNav active="library"`
- **Header row** (`gridTemplateColumns: '1fr auto'`):
  - **Left — Resume / first-run headline** — Metis glyph
    (`LDomainMark domain="metis"`), eyebrow "Metis · the schoolroom", `LDisplay`
    "Resume at § <section>." when `resume.weekTotal > 0`; otherwise "Begin your
    first lesson." The meta line is either
    `<courseLabel> · week <n> of <total> · <minutes> minutes today` or "No
    lesson in progress yet — open a course below to start."
  - **Right — `LBtn kind="primary" href="/metis/lesson"`** — "Continue lesson →"
    or first-run "Start a lesson →"
- **Two-column body** (`gridTemplateColumns: '1.4fr 1fr'`):
  - **Left — Open courses · N** — list of `Link`-wrapped `LCard`s per
    `data.courses`, each with code, week/weekTotal, title, teacher, progress
    bar, and `href="/metis/lesson?course=<code>"`; the `here` course gets a 3 px
    accent left border and bg colour
  - **Right — sidebar**:
    - **This week** — `LCard` with `LField` rows for Lessons read, Assessments,
      Tutor minutes, Streak (from `data.weekStats`)
    - **Open assessment** — `LCard` (accent border). When
      `data.openAssessment.open` is true it shows code, title, kind, word count,
      due label, "Begin" primary button to `/metis/assessment`, and "Rubric"
      ghost button. When false it shows "No assessment is open." plus the honest
      explanatory eyebrow and hides Begin/Rubric.
    - **Tutor · ✻** — `LCard` (dotted border) with a paragraph from
      `data.tutorNudge` and eyebrow "AI tutor · grounded in 4 sources ·
      disclosed"

## States

- [x] **Loaded** — `getMetis()` resolves before render; covered by
      `metis-lesson-completion.spec.ts`.
- [x] **Resume present / first-run** — `data.resume.sectionNumber` populates the
      resume headline when `weekTotal > 0`; the live first-run BFF state renders
      "Begin your first lesson." instead.
- [x] **Open courses populated** — one linked card per `data.courses`; the E2E
      count and click-through are covered.
- [x] **Active course (`here === true`)** — highlighted with accent border in
      source.
- [x] **Week stats populated** — `data.weekStats` field rows (lessons read,
      assessments, tutor minutes, streak)
- [x] **Open assessment present** — `data.openAssessment.open === true`
      populates the sidebar card and exposes Begin/Rubric.
- [x] **No open assessment** — live first-run BFF state shows "No assessment is
      open." and hides Begin/Rubric; covered in
      `metis-lesson-completion.spec.ts`.
- [x] **Tutor nudge present** — `data.tutorNudge` italic paragraph and visible
      "AI tutor · grounded in 4 sources · disclosed" eyebrow.
- [x] **Reduced motion** — composes static Lilith primitives; no route-level
      animation on this hub.
- [x] **Auth gate** — shell middleware only.

## Interactions

### Header

- [x] **Continue lesson → / Start a lesson →** (`LBtn kind="primary"`)
  - Function: navigates to `/metis/lesson`
  - Disabled when: _no in-source disabled branch — always rendered_
  - Covered by: `metis-lesson-completion.spec.ts`

### Open courses lane

- [x] **Course card** (`Link` wrapping `LCard`) — navigates to
      `/metis/lesson?course=<code>`; covered by browser click-through.
- [x] **Progress bar** — non-interactive.

### Sidebar

- [x] **`LField` rows** — non-interactive (Lessons read, Assessments, Tutor
      minutes, Streak)
- [x] **Begin** (`LBtn kind="primary" href="/metis/assessment"`)
  - Function: navigates to `/metis/assessment`
- [x] **Rubric** (`LBtn kind="ghost"`)
  - Function: display-only in V1; no destination or modal handler is wired.
- [x] **Tutor card** — non-interactive paragraph + eyebrow; no `href` wired.

## Data & contracts

- **Reads**: `getMetis()` from `@/lib/lilith-data/metis` — fetches the live BFF
  route `GET /v1/metis/room` via `bffGet` (`metis.ts` ~14-19) and returns
  `MetisData` with `resume`, `courses[]`, `weekStats`, `openAssessment`,
  `tutorNudge`. On an unreachable feed it returns an honest `metisUnavailable()`
  room (no embedded fixture).
- **Writes**: _None — read-only hub._
- **Realtime**: _None._
- **Caching**: server fetch; SW caches the rendered HTML for offline reload
- **Auth/role check**: shell middleware
- **Route metadata**: title "Metis · the schoolroom", canonical `/metis`

## Cross-references

- Shell: [`shell/01-app-shell.md`](../../shell/01-app-shell.md)
- Sibling Metis routes:
  - [`metis-assessment.md`](./metis-assessment.md) — short-answer assessment
  - [`metis-byom.md`](./metis-byom.md) — bring your own materials
  - [`metis-lesson.md`](./metis-lesson.md) — course reader
  - [`metis-tutor.md`](./metis-tutor.md) — chat tutor with cited sources
- Cross-domain partners:
  - Nisaba (`../07-nisaba/`) — source-grounded study moments
- Component sources:
  - `apps/oshun/web/src/components/lilith/metis.tsx` (`MetisSchoolroom` line 65)
  - `apps/oshun/web/src/lib/lilith-data/metis.ts`
- Feature spec: [`V1/features.md#metis`](../../../V1/features.md#metis)

## Open questions / known gaps

- [x] Course cards are real `Link`s to `/metis/lesson?course=<code>`; no
      separate per-course detail page is mounted.
- [x] The Rubric ghost button is display-only in V1; the rubric preview itself
      lives on `/metis/assessment`.
- [x] `MetisData` is served live from `GET /v1/metis/room`; the browser spec
      asserts the live first-run/no-assessment shape instead of page-routing a
      fake server component payload.
- [x] Tutor nudge card integration is confirmed absent in V1; direct URL
      `/metis/tutor` is the reachable tutor entry.
