Context. surface customer · domain metis · route /metis/tutor · auth signed-in · source apps/oshun/web/src/app/metis/tutor/page.tsx
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
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; 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 sharedTrustSafetyFlagButtontargetingmetis://tutor/session-14-hour-with-epictetus
- Left — eyebrow "Metis · tutor · ✻ disclosed",
- 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 small1px 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 fromm.refs - Per-turn eyebrow: "You · 14:<8+i>" for the user, "Metis · ✻ · turn
" for the tutor
- Right column —
Context · this sessionfields,✻ Disclosurepolicy copy, andEnd-of-session note
- Left — Transcript —
Automation anchors#
- Root:
data-metis-tutor-pagewith 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, anddata-metis-tutor-header-action="transcript|end-session|report" - Transcript:
data-metis-tutor-transcriptwith perdata-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-composerwith 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-contextrows,data-metis-tutor-disclosure-copy, anddata-metis-tutor-session-note-copy
States#
- Loaded —
getMetisTutor()resolves before render - Transcript populated — chat bubbles render in order
- Empty transcript — no dedicated empty-state card is implemented; the transcript map would render no bubbles and the composer/right rail would remain visible.
- Assistant turn with refs — citation chip row appears
- Assistant turn without refs — citation row omitted
- ✻ disclosure — eyebrow on every assistant turn announces the AI runtime
- Reduced motion — composes static Lilith primitives; no animated or streaming response region is mounted in V1.
Interactions#
Header#
-
LDisplay— non-interactive heading text - Transcript (
LBtn kind="ghost") — display-only in V1; no route or modal destination is wired - End session (
LBtn kind="ghost") — display-only in V1; no end-session handler or route is wired - Report (
TrustSafetyFlagButton) — opens the shared Trust & Safety category/rationale dialog; the focuseduser-flag-to-reviewbrowser 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#
- User bubble — non-interactive
- Assistant bubble — non-interactive prose
- Citation chip (
↗ <name> · <src>) — real link to the cited source in Nisaba (/nisaba?passage=...&from=metis-tutor&ref=...)
Right column#
- Cited-source rail — there is no expandable rail in V1; cited sources are per-turn links in the transcript rows
- Tutor input — textarea plus Send button render in
MetisTutorComposer; ⌘/Ctrl+Enter queues the local draft - Save excerpt to library — no pin/save control is implemented on assistant bubbles or right-rail note in V1.
- Disclosure copy —
✻policy text renders in the right rail
Data & contracts#
- Reads:
getMetisTutor()from@/lib/lilith-data/metis-depth— returnsMetisTutorData(transcript: Array<{ who: 'you', t: string } | { who: 'tutor', t: string, turn: number, refs?: [string, string][] }>— verify exact union shape inmetis-depth.ts) - Writes:
TrustSafetyFlagButtonsubmits customer reports through/api/trust-safety/flags→POST /v1/user-reports.MetisTutorComposerqueues typed user turns in localStorage underoshun.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:
- Cross-domain partners: Nisaba (
../07-nisaba/) for cited sources - Trust & Safety moderation journey:
../../journeys/user-flag-to-review.md - Component sources:
apps/oshun/web/src/components/lilith/metis.tsx(MetisTutorline 567)
- Feature spec:
V1/features.md#metis
Open questions / known gaps#
- Document the input affordance for new turns — a textarea, Send button, and ⌘/Ctrl+Enter keyboard shortcut queue the turn locally in V1
- 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
- Verify that the ✻ disclosure copy meets the visible-disclosure rule required for cited AI runtimes
- Document whether the citation chips are real
Links to Nisaba or visual chips today - "Saved to library" is confirmed as fixture/status copy on this page; no
useOshunWebLibraryStorewrite, BFF transcript-memory write, or library-read-back control is mounted here.