---
path: /metis/session
surface: customer
domain: metis
auth: signed-in
source: apps/oshun/web/src/app/metis/session/page.tsx
status: walked
last_walked:
  '2026-06-29 real-infra Playwright chromium — live guarded BFF multi-course
  fixture, signed-in customer shell, assessment-ready start selection, queue
  rows, total minutes, hrefs, mobile standalone shape, no horizontal overflow,
  and shared axe scan. Evidence:
  WALKTHROUGH/results/v1-real-infra-run-2026-06-22.md#199-2026-06-29-metis-lessons-session-live-bff-coverage'
---

# Metis · today's session

## Purpose

A "pick it up here" framing over the lesson backlog: server-side it reads
`/v1/metis/lessons`, picks the first lesson with `assessmentReady=true` (else
the first lesson) as the one to study now, queues the next two, and shows an
estimated total session length. Links into the existing lesson and tutor pages.
See [`V1/features.md#metis`](../../../V1/features.md#metis).

## Entry points

- **Metis lessons index** — "Today's session" ghost button on
  [`metis-lessons.md`](./metis-lessons.md) links here
- **Metis schoolroom** — verify whether [`metis.md`](./metis.md) surfaces a
  "today's session" CTA
- **Direct URL / bookmark** — yes (auth required); `canonical` `/metis/session`
- **Search result** — page metadata title "Metis · today's session"

## Layout regions

`page.tsx` is an async server component:
`await bffGet<LessonsResponse>('/v1/metis/lessons')` (falls back to empty),
computes `next`, a 2-item `queue`, and `totalMin`, rendered inside
`<LWebShell top={<LCustomerNav active="explore" />}>` in a 720px-max centered
column.

- **Header**: web shell top — `LCustomerNav` with `active="explore"`
- **Masthead**: `LMasthead` — left "Metis · today", right "`<totalMin>` min
  planned" (or "nothing queued"), kicker "Pick it up here", title "Today's
  _session_."
- **Main (Start here)**: an accent-bordered `<section>` for the `next` lesson —
  eyebrow "Start here", course/week/minutes meta, `<h2>` title, action buttons
- **Next in line**: a `<section>` listing the up-to-two `queue` lessons (only
  when `queue.length > 0`)
- **Footer**: button row — "← Back to Metis", "All lessons", "Live tutor"
- **Automation anchors**: root exposes `data-metis-session-page`, `data-route`,
  `data-generated-at`, lesson/queue/total-minute/assessment counts, chosen next
  lesson id, and `data-empty`; the start card exposes
  `data-metis-session-start`; queued rows expose `data-metis-session-queue-row`
  plus lesson/week/minute/assessment attributes.

## States

- [x] **Has next lesson** — accent-bordered "Start here" card with the chosen
      lesson; "Open the lesson" primary button
- [x] **Next is assessment-ready** — additional ghost button "Skip to the
      assessment" appears alongside "Open the lesson"
- [ ] **No active lessons** — dashed-border card "No active lessons. Start a
      course in /metis to begin a session."; masthead right reads "nothing
      queued"
- [x] **Queue populated** — "Next in line" section with 2 lesson rows from the
      live guarded BFF fixture
- [ ] **Queue empty** — "Next in line" section omitted (e.g. only one lesson)
- [ ] **Error / null fetch** — `bffGet` null → empty → renders the "No active
      lessons" card (no distinct error banner)
- [x] **Loading** — server-rendered; no route-level `loading.tsx` in folder
- [ ] **Offline** — first load needs the BFF; failure renders empty; verify SW
      HTML cache for offline reload
- [x] **Signed-in shell context** — walked through an authenticated customer
      shell with the Explore nav active
- [x] **Standalone PWA** — mobile standalone launch renders within `LWebShell`
      without horizontal overflow

## Interactions

### Start-here card

- [x] **Start-here meta** — non-interactive: "`<courseLabel>` · week
      `<week>`/`<weekTotal>` · `<minutesEstimated>` min" (mono uppercase)
- [x] **Lesson title** (`<h2>`) — non-interactive serif heading
- [x] **Open the lesson** (`LBtn kind="primary" size="lg"`)
  - Function: navigates to `/metis/lesson?id=<encodeURIComponent(next.id)>`
  - Keyboard: Enter activates; primary action on the page
  - Touch target: ≥ 44×44 px via `LBtn size="lg"`
- [x] **Skip to the assessment** (`LBtn kind="ghost" size="lg"`, only when
      `next.assessmentReady`)
  - Function: navigates to `/metis/assessment` (no lesson id param — see gaps)

### Next-in-line rows

- [x] **Queue row** (`<li>`) — non-interactive: course/week/minutes meta line +
      serif title; no link to open these directly from here

### Footer buttons

- [x] **← Back to Metis** (`LBtn kind="ghost" size="md"`, `href="/metis"`)
- [x] **All lessons** (`LBtn kind="ghost" size="md"`, `href="/metis/lessons"`)
- [x] **Live tutor** (`LBtn kind="primary" size="md"`, `href="/metis/tutor"`)

## Data & contracts

- **Reads**: `GET /v1/metis/lessons` via `bffGet<LessonsResponse>` (same fixture
  as [`metis-lessons.md`](./metis-lessons.md)); `revalidate: 60s`, `5000ms`
  timeout; returns `null` on failure → `{ generatedAt, lessons: [] }`
  - Local `Lesson` interface here is a subset:
    `{ id, courseLabel, title, week, weekTotal, minutesEstimated, assessmentReady }`
    (no `courseId`/`sectionNumber`)
  - Selection: `next = lessons.find(l => l.assessmentReady) ?? lessons[0]`;
    `queue = lessons.filter(l => l.id !== next?.id).slice(0, 2)`;
    `totalMin = next.minutesEstimated + sum(queue.minutesEstimated)`
  - Current guarded fixture yields `phl-102-week-2` as `next`, two queued rows
    (`phl-102-week-3`, `ast-110-week-1`), and `58` planned minutes.
- **Writes**: _None._
- **Realtime**: _None._
- **Caching**: SSR with Next `revalidate: 60`; SW HTML cache for offline reload
- **Auth/role check**: page is walked through a signed-in customer shell;
  `bffGet` forwards the session cookie. The current domain-stubs BFF endpoint is
  production-guarded fixture data, not an auth-scoped per-user route.

No typed contract in `libs/contracts/`/`libs/openapi/`; shapes are inline.

## Cross-references

- Feature spec: [`V1/features.md#metis`](../../../V1/features.md#metis)
- Architecture: [`V1/ARCHITECTURE.md#metis`](../../../V1/ARCHITECTURE.md#metis)
- Sibling Metis routes: [`metis.md`](./metis.md),
  [`metis-lessons.md`](./metis-lessons.md),
  [`metis-lesson.md`](./metis-lesson.md), [`metis-tutor.md`](./metis-tutor.md),
  [`metis-assessment.md`](./metis-assessment.md)
- Journeys:
  [`../../journeys/metis-lesson-completion.md`](../../journeys/metis-lesson-completion.md)
- Component sources:
  - `apps/oshun/web/src/app/metis/session/page.tsx`
  - BFF: `apps/oshun/bff/src/routes/domain-stubs.ts`

## Open questions / known gaps

- [ ] "Skip to the assessment" links to `/metis/assessment` without passing the
      lesson id — confirm whether the assessment page needs to know which lesson
      it is assessing
- [ ] "Next in line" rows are non-interactive (no per-lesson open link) unlike
      the lessons index — confirm whether they should be clickable
- [ ] Selection logic prefers any assessment-ready lesson over true recency;
      confirm this matches the intended "study now" heuristic
- [ ] `/v1/metis/lessons` is a production-guarded fixture; confirm the real
      per-user backlog source
- [ ] A null fetch silently renders the empty state rather than an error/retry —
      confirm desired behavior
