---
path: /metis/tutor
surface: customer
domain: metis
auth: signed-in
source: apps/oshun/web/src/app/metis/tutor/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; Trust & Safety report
  affordance target metadata + dialog verified on 2026-06-26; transcript
  anatomy, citation chip navigation, local composer queue, and no-live-voice
  failure mode re-verified by metis-tutor-session.spec.ts'
---

# Metis · tutor (chat with cited sources)

## Purpose

Chat-style tutor for the active course. Each assistant turn shows a visible `✻`
disclosure and cites the sources it draws on. Fixture: "The hour with Epictetus"
— session 14, 11 turns, saved to library.

## Entry points

- **Metis schoolroom** — tutor nudge card on [`metis.md`](./metis.md); verify
  whether the card is wired to navigate here
- **Library** — verify whether saved tutoring sessions link here
- **Direct URL / bookmark** — yes

## Layout regions

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

Inside `MetisTutor` (`metis.tsx`, line 567):

- **Web shell top** — `LCustomerNav active="library"`
- **Header row** (`gridTemplateColumns: '1fr auto'`):
  - **Left** — eyebrow "Metis · tutor · ✻ disclosed", `LDisplay` "The hour with
    Epictetus.", `LCode` "session 14 · 11 turns · saved to library"
  - **Right** — two ghost buttons (`Transcript`, `End session`) plus the shared
    `TrustSafetyFlagButton` targeting
    `metis://tutor/session-14-hour-with-epictetus`
- **Two-column body** (`gridTemplateColumns: '1.5fr 1fr'`):
  - **Left — Transcript** — `data.transcript.map((m, i) => …)` chat bubbles
    where:
    - `m.who === 'you'` aligns right with paper background and a small
      `1px solid ${L.rule}` left border
    - assistant turns align left with a `2px solid ${L.accent}` left border and
      a citation chip row showing each `[name, src]` pair from `m.refs`
    - Per-turn eyebrow: "You · 14:<8+i>" for the user, "Metis · ✻ · turn <n>"
      for the tutor
  - **Right column** — `Context · this session` fields, `✻ Disclosure` policy
    copy, and `End-of-session note`

## Automation anchors

- Root: `data-metis-tutor-page` with transcript, user-turn, assistant-turn,
  citation, and context counts
- Header: `data-metis-tutor-header`, `data-metis-tutor-eyebrow`,
  `data-metis-tutor-heading`, `data-metis-tutor-subtitle`, and
  `data-metis-tutor-header-action="transcript|end-session|report"`
- Transcript: `data-metis-tutor-transcript` with per
  `data-metis-tutor-turn={index}` row, speaker/turn-number/citation-count
  attributes, `data-metis-tutor-turn-eyebrow`, `data-metis-tutor-turn-body`, and
  per-link citation ref/source attributes
- Composer: `data-metis-tutor-composer` with session id, status, draft length,
  prompt, textarea, send button, and queued/failed status-copy anchors
- Right rail: `data-metis-tutor-right-rail`, `data-metis-tutor-context` rows,
  `data-metis-tutor-disclosure-copy`, and `data-metis-tutor-session-note-copy`

## States

- [x] **Loaded** — `getMetisTutor()` resolves before render
- [x] **Transcript populated** — chat bubbles render in order
- [x] **Empty transcript** — no dedicated empty-state card is implemented; the
      transcript map would render no bubbles and the composer/right rail would
      remain visible.
- [x] **Assistant turn with refs** — citation chip row appears
- [x] **Assistant turn without refs** — citation row omitted
- [x] **✻ disclosure** — eyebrow on every assistant turn announces the AI
      runtime
- [x] **Reduced motion** — composes static Lilith primitives; no animated or
      streaming response region is mounted in V1.

## Interactions

### Header

- [x] **`LDisplay`** — non-interactive heading text
- [x] **Transcript** (`LBtn kind="ghost"`) — display-only in V1; no route or
      modal destination is wired
- [x] **End session** (`LBtn kind="ghost"`) — display-only in V1; no end-session
      handler or route is wired
- [x] **Report** (`TrustSafetyFlagButton`) — opens the shared Trust & Safety
      category/rationale dialog; the focused `user-flag-to-review` browser spec
      verifies this button's stable Metis tutor-session target kind/id/label
      metadata and dialog controls. The shared live submit → admin moderation
      read-back path is covered through the Nisaba flag in the same spec.

### Transcript bubbles

- [x] **User bubble** — non-interactive
- [x] **Assistant bubble** — non-interactive prose
- [x] **Citation chip** (`↗ <name> · <src>`) — real link to the cited source in
      Nisaba (`/nisaba?passage=...&from=metis-tutor&ref=...`)

### Right column

- [x] **Cited-source rail** — there is no expandable rail in V1; cited sources
      are per-turn links in the transcript rows
- [x] **Tutor input** — textarea plus Send button render in
      `MetisTutorComposer`; ⌘/Ctrl+Enter queues the local draft
- [x] **Save excerpt to library** — no pin/save control is implemented on
      assistant bubbles or right-rail note in V1.
- [x] **Disclosure copy** — `✻` policy text renders in the right rail

## Data & contracts

- **Reads**: `getMetisTutor()` from `@/lib/lilith-data/metis-depth` — returns
  `MetisTutorData`
  (`transcript: Array<{ who: 'you', t: string } | { who: 'tutor', t: string, turn: number, refs?: [string, string][] }>`
  — verify exact union shape in `metis-depth.ts`)
- **Writes**: `TrustSafetyFlagButton` submits customer reports through
  `/api/trust-safety/flags` → `POST /v1/user-reports`. `MetisTutorComposer`
  queues typed user turns in localStorage under
  `oshun.metis.tutor.draft.v1.<sessionId>.queue`, clears the draft, and shows
  "Queued · the tutor will pick this up"; no BFF tutor-runtime POST is wired on
  this page.
- **Realtime**: no SSE / WS / live-voice response path is mounted in V1; the
  page is transcript plus local queued intent.
- **Caching**: server fetch
- **Auth/role check**: shell middleware

## Cross-references

- Sibling Metis routes:
  - [`metis.md`](./metis.md), [`metis-assessment.md`](./metis-assessment.md),
    [`metis-byom.md`](./metis-byom.md), [`metis-lesson.md`](./metis-lesson.md)
- Cross-domain partners: Nisaba (`../07-nisaba/`) for cited sources
- Trust & Safety moderation journey:
  [`../../journeys/user-flag-to-review.md`](../../journeys/user-flag-to-review.md)
- Component sources:
  - `apps/oshun/web/src/components/lilith/metis.tsx` (`MetisTutor` line 567)
- Feature spec: [`V1/features.md#metis`](../../../V1/features.md#metis)

## Open questions / known gaps

- [x] Document the input affordance for new turns — a textarea, Send button, and
      ⌘/Ctrl+Enter keyboard shortcut queue the turn locally in V1
- [x] Confirm whether assistant responses stream via SSE / WebSocket or are
      delivered in one chunk after the user submits a turn — no streaming
      response path is wired in this page; submitted text is a local queue
- [x] Verify that the ✻ disclosure copy meets the visible-disclosure rule
      required for cited AI runtimes
- [x] Document whether the citation chips are real `Link`s to Nisaba or visual
      chips today
- [x] "Saved to library" is confirmed as fixture/status copy on this page; no
      `useOshunWebLibraryStore` write, BFF transcript-memory write, or
      library-read-back control is mounted here.
