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 top —
LCustomerNav active="library" - Header row (
data-metis-byom-header, flex row, stacked below 900 px):- Left — title —
data-metis-byom-eyebrowtext "Metis · BYOM · bring your own materials";data-metis-byom-headingheadline "From your books, your study guide." - Right — Generate guide → (
data-metis-byom-generate-cta,LBtn kind="primary")
- Left — title —
- Two-column body (
gridTemplateColumns: '1.3fr 1fr'; stacked to one column below 900 px):- Left — Materials table —
LEyebrow"Your materials ·"; table grid carries data-metis-byom-materials-table=""anddata-metis-byom-materials-count="{N}", with named column anchorskind/title/pages/rights/status. Each material row carriesdata-metis-byom-material-row="", row index, kind, title, pages, rights, and status attributes; visible cells carrydata-metis-byom-material-cellforkind,title,pages,rights, andstatus(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 atdata-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 askind=textand render a completed outline; URL/PDF references post askind=url/kind=pdfand render the BFF'sawaiting_source_contentstate. - 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 foredit-outlineandbegin-week-1.
- Left — Materials table —
States#
- Loaded —
getMetisByom()resolves before render; Playwright verifies[data-metis-byom-page]and material/outline counts. - Materials present — table rows for each
data.materialsentry 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: parsed —
LValidationPill kind="ok"visible for the three parsed rows; row status attributes and cell text are asserted. - Parse status: queued —
LValidationPill 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 rendersdata-metis-byom-ingest-statuswithdata-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 renderdata-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 chip —
LChipper material's rights category; fixture valuesowned,mine,fair use, andcc-byare covered. - Standalone PWA / mobile layout — launch through
/metis/uploadlands 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#
Header#
- 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 card —
data-metis-byom-drop-zonehandles dropped Markdown/text files throughMetisByomIngestControls, posts the real BFF ingest route, and renders the completed outline state. Playwright dispatches a browserDataTransferwithstoic-drop.mdagainst 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 askind=text; PDF files submit a persistedkind=pdfsource reference without fabricated parsing.
- Function: opens the hidden
- Paste URL (
data-metis-byom-upload-action="paste-url",LBtn kind="ghost")- Function: opens
data-metis-byom-url-form, posts the URL askind=url, and renders the BFF'sawaiting_source_contentresponse.
- Function: opens
Data & contracts#
- Reads:
getMetisByom()from@/lib/lilith-data/metis-depth— returnsMetisByomData(verify shape:materials: { ic, t, p, r, s }[]and the generated guide payload) - Writes:
POST /v1/metis/ingestfrom the BYOM upload island. Markdown/text files are browser-read and submitted askind=text; URL/PDF references are persisted honestly asawaiting_source_content. Live course proposal writes remain covered bymetis-ingest-bff.spec.tsand the journey spec. - Realtime: None observed in this view.
- Caching: server fetch
- Auth/role check: shell middleware
Cross-references#
- Sibling Metis routes:
- Cross-domain partners: Nisaba (
../07-nisaba/) for source-grounded citations once the guide is generated - Component sources:
apps/oshun/web/src/components/lilith/metis.tsx(MetisBYOM)
- E2E:
apps/oshun/web/e2e/metis-byom-ingest.spec.tscovers the header, material table columns/counts, every material row/cell, drop-zone constraints, live Browse/Paste/drop ingest controls against real dev infra, guide preview counts, outline rows/cells, provenance, guide action wrappers, and the operator Metis dashboard.apps/oshun/web/e2e/metis-upload-alias.spec.tscovers the legacy alias landing on this page in standalone PWA mode, including the mobile no-horizontal-overflow contract. - Feature spec:
V1/features.md#metis
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/ingestroute 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/lessonwith 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.