V1 Web PWA · Surface walkthrough

Metis · BYOM (bring your own materials)

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

walked
8sections6 minread

On this page

Context. surface customer · domain metis · route /metis/byom · auth signed-in · source apps/oshun/web/src/app/metis/byom/page.tsx

Last walked. 2026-06-30 by Codex (standalone PWA alias launch verified the BYOM mobile stacked material table and no horizontal page overflow in metis-upload-alias.spec.ts), against commit HEAD' '2026-06-30 by Codex (BYOM Browse/Paste/drop controls wired to POST /v1/metis/ingest and covered against real dev infra in metis-byom-ingest.spec.ts), against commit HEAD' '2026-06-25 by Codex (BYOM material table, drop zone, guide preview, outline, provenance, and action anchors covered in metis-byom-ingest.spec.ts), against commit HEAD' '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

Purpose#

Bring your own materials. Upload books, PDFs, notes, and URLs; Metis parses them and generates a study guide grounded in those sources. Fixture lists four materials with parse status.

Entry points#

  • Metis schoolroom — verify cross-link from metis.md
  • Direct URL / bookmark — yes

Layout regions#

page.tsx is a thin server component: await getMetisByom()<MetisBYOM data={data} />.

Inside MetisBYOM (metis.tsx), the root carries data-metis-byom-page="", data-metis-byom-material-count="{N}", and data-metis-byom-outline-count="{N}":

  • Web shell topLCustomerNav active="library"
  • Header row (data-metis-byom-header, flex row, stacked below 900 px):
    • Left — titledata-metis-byom-eyebrow text "Metis · BYOM · bring your own materials"; data-metis-byom-heading headline "From your books, your study guide."
    • Right — Generate guide → (data-metis-byom-generate-cta, LBtn kind="primary")
  • Two-column body (gridTemplateColumns: '1.3fr 1fr'; stacked to one column below 900 px):
    • Left — Materials tableLEyebrow "Your materials · "; table grid carries data-metis-byom-materials-table="" and data-metis-byom-materials-count="{N}", with named column anchors kind/title/pages/rights/status. Each material row carries data-metis-byom-material-row="", row index, kind, title, pages, rights, and status attributes; visible cells carry data-metis-byom-material-cell for kind, title, pages, rights, and status (LValidationPill kind={s === 'parsed' ? 'ok' : 'info'}). Below 640 px, route-scoped responsive rules hide the desktop column header and reflow each material row into a compact kind/status, title, pages/rights grid so the page has no global horizontal overflow.
    • Left — Drop zone — dashed-border drop card "Drop a book, a PDF, your notes." with data-metis-byom-drop-zone="", constraint copy anchored at data-metis-byom-drop-zone-constraints, and a client island (data-metis-byom-ingest-controls) wrapping live Browse files / Paste URL controls plus drag-and-drop. Markdown/text bytes post as kind=text and render a completed outline; URL/PDF references post as kind=url / kind=pdf and render the BFF's awaiting_source_content state.
    • Right column — generated study-guide preview with data-metis-byom-guide-preview="", source count, outline count, guide meta, guide heading, outline row/cell anchors, provenance card/copy anchors, and guide action wrappers for edit-outline and begin-week-1.

States#

  • LoadedgetMetisByom() resolves before render; Playwright verifies [data-metis-byom-page] and material/outline counts.
  • Materials present — table rows for each data.materials entry are verified row-by-row through row attributes and cell anchors.
  • Materials empty — verify the empty-state copy (the drop zone is still visible)
  • Parse status: parsedLValidationPill kind="ok" visible for the three parsed rows; row status attributes and cell text are asserted.
  • Parse status: queuedLValidationPill kind="info" visible for the queued row; row status attribute and cell text are asserted.
  • Guide preview present — generated-guide meta, heading, all five outline rows, provenance heading/copy, and guide action wrappers are asserted. The CTA remains display-only.
  • BYOM URL source accepted — Paste URL posts to POST /v1/metis/ingest, then the page renders data-metis-byom-ingest-status with data-state="awaiting_source_content", data-source-kind="url", and the deploy-bound extraction reason.
  • BYOM Markdown/text file completed — Browse files and drag-and-drop read Markdown/text bytes in the browser, post kind=text, and render data-state="completed" with outline summary attributes.
  • Generate guide post-click — verify destination once the CTA is wired (in-page guide vs. navigation)
  • Upload over quota — verify what happens at 50 MB per file / 200 MB total
  • Rights chipLChip per material's rights category; fixture values owned, mine, fair use, and cc-by are covered.
  • Standalone PWA / mobile layout — launch through /metis/upload lands on BYOM in standalone mode with query params intact; the mobile material table uses the compact row layout and the page has no horizontal overflow.

Interactions#

  • Generate guide → (data-metis-byom-generate-cta, LBtn kind="primary")
    • Function: display-only in the current specimen; Playwright verifies presence and deliberately does not click until the destination is wired.

Materials table#

  • Row (per material) — non-link, non-button row; Playwright verifies all four fixture rows by index, kind/title/pages/rights/status attributes, and visible cell text. No view/remove/re-parse controls exist in source.
  • Status pill — non-interactive; parsed/queued cell text covered.
  • Rights chip — non-interactive; owned/mine/fair use/cc-by cell text covered.

Drop zone#

  • Drag-and-drop carddata-metis-byom-drop-zone handles dropped Markdown/text files through MetisByomIngestControls, posts the real BFF ingest route, and renders the completed outline state. Playwright dispatches a browser DataTransfer with stoic-drop.md against real dev infra.
  • Browse files (data-metis-byom-upload-action="browse-files", LBtn kind="ghost")
    • Function: opens the hidden data-metis-byom-file-input. Markdown/text files are read as bytes and submitted as kind=text; PDF files submit a persisted kind=pdf source reference without fabricated parsing.
  • Paste URL (data-metis-byom-upload-action="paste-url", LBtn kind="ghost")
    • Function: opens data-metis-byom-url-form, posts the URL as kind=url, and renders the BFF's awaiting_source_content response.

Data & contracts#

  • Reads: getMetisByom() from @/lib/lilith-data/metis-depth — returns MetisByomData (verify shape: materials: { ic, t, p, r, s }[] and the generated guide payload)
  • Writes: POST /v1/metis/ingest from the BYOM upload island. Markdown/text files are browser-read and submitted as kind=text; URL/PDF references are persisted honestly as awaiting_source_content. Live course proposal writes remain covered by metis-ingest-bff.spec.ts and the journey spec.
  • Realtime: None observed in this view.
  • Caching: server fetch
  • Auth/role check: shell middleware

Cross-references#

Open questions / known gaps#

  • Confirm whether drag-and-drop and the Browse files / Paste URL controls are wired in V1 — they now call the live POST /v1/metis/ingest route from this page, with Playwright coverage for URL, browse, and drop.
  • Wire PDF byte extraction and EPUB/DOCX parsing if those formats are meant to produce outlines from this page instead of persisted source-reference jobs.
  • Document the "Generate guide" destination once it is wired — does it open /metis/lesson with a generated payload, or expand the guide in-place?
  • Extend the parse-status state machine beyond the current fixture values (parsed, queued) when parsing/failed rows are available.