V1 Web PWA · Surface walkthrough

Metis · tutor (chat with cited sources)

A per-surface walkthrough of the V1 Web PWA customer surface: layout, states, interactions, data, and cross-references.

walked
9sections4 minread

On this page

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 topLCustomerNav 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 — Transcriptdata.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 " for the tutor
    • Right columnContext · 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#

  • LoadedgetMetisTutor() 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#

  • 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 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#

  • 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 — 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/flagsPOST /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#

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 useOshunWebLibraryStore write, BFF transcript-memory write, or library-read-back control is mounted here.