Context. surface customer · domain metis · route /metis/session · auth signed-in · source apps/oshun/web/src/app/metis/session/page.tsx
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
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.
Entry points#
- Metis lessons index — "Today's session" ghost button on
metis-lessons.mdlinks here - Metis schoolroom — verify whether
metis.mdsurfaces 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 —
LCustomerNavwithactive="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 thenextlesson — eyebrow "Start here", course/week/minutes meta,<h2>title, action buttons - Next in line: a
<section>listing the up-to-twoqueuelessons (only whenqueue.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, anddata-empty; the start card exposesdata-metis-session-start; queued rows exposedata-metis-session-queue-rowplus lesson/week/minute/assessment attributes.
States#
- Has next lesson — accent-bordered "Start here" card with the chosen lesson; "Open the lesson" primary button
- 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"
- 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 —
bffGetnull → empty → renders the "No active lessons" card (no distinct error banner) - Loading — server-rendered; no route-level
loading.tsxin folder - Offline — first load needs the BFF; failure renders empty; verify SW HTML cache for offline reload
- Signed-in shell context — walked through an authenticated customer shell with the Explore nav active
- Standalone PWA — mobile standalone launch renders within
LWebShellwithout horizontal overflow
Interactions#
Start-here card#
- Start-here meta — non-interactive: "
<courseLabel>· week<week>/<weekTotal>·<minutesEstimated>min" (mono uppercase) - Lesson title (
<h2>) — non-interactive serif heading - 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"
- Function: navigates to
- Skip to the assessment (
LBtn kind="ghost" size="lg", only whennext.assessmentReady)- Function: navigates to
/metis/assessment(no lesson id param — see gaps)
- Function: navigates to
Next-in-line rows#
- Queue row (
<li>) — non-interactive: course/week/minutes meta line + serif title; no link to open these directly from here
Footer buttons#
- ← Back to Metis (
LBtn kind="ghost" size="md",href="/metis") - All lessons (
LBtn kind="ghost" size="md",href="/metis/lessons") - Live tutor (
LBtn kind="primary" size="md",href="/metis/tutor")
Data & contracts#
- Reads:
GET /v1/metis/lessonsviabffGet<LessonsResponse>(same fixture asmetis-lessons.md);revalidate: 60s,5000mstimeout; returnsnullon failure →{ generatedAt, lessons: [] }- Local
Lessoninterface here is a subset:{ id, courseLabel, title, week, weekTotal, minutesEstimated, assessmentReady }(nocourseId/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-2asnext, two queued rows (phl-102-week-3,ast-110-week-1), and58planned minutes.
- Local
- 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;
bffGetforwards 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 - Architecture:
V1/ARCHITECTURE.md#metis - Sibling Metis routes:
metis.md,metis-lessons.md,metis-lesson.md,metis-tutor.md,metis-assessment.md - Journeys:
../../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/assessmentwithout 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/lessonsis 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