---
path: /metis/upload
surface: customer
domain: metis
auth: signed-in
source: apps/oshun/web/src/app/metis/upload/page.tsx
status: walked
last_walked:
  '2026-06-30 by Codex (query-preserving 308, anonymous auth-before-alias,
  and standalone PWA alias launch covered in metis-upload-alias.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'
---

# Metis · upload (redirect to BYOM)

## Purpose

A permanent redirect alias. `/metis/upload` was an earlier walkthrough name for
the "bring your own material" upload surface; the proxy now enforces auth first
and then issues a signed-in `308` to `/metis/byom?...` so the canonical
destination is [`metis-byom.md`](./metis-byom.md) with query params preserved.
The route component keeps the same query-preserving `permanentRedirect` as a
fallback for runtimes that reach the page. URL hash fragments are not sent to
the server and therefore cannot be preserved by this server-side redirect. No UI
is rendered here. See [`V1/features.md#metis`](../../../V1/features.md#metis).

## Entry points

- **Legacy links / bookmarks** — anything still pointing at `/metis/upload`
- **Direct URL** — yes; signed-in requests immediately 308-redirect to
  `/metis/byom`
- **Old walkthrough / docs references** — this file documents the alias so older
  references resolve

## Layout regions

`proxy.ts` handles normal requests before render: anonymous traffic is gated to
`/welcome`, and signed-in `/metis/upload?...` traffic gets a `308` to
`/metis/byom?...`. `page.tsx` renders no markup and remains a fallback that
reads `searchParams`, serializes defined values into a `URLSearchParams`, and
calls `permanentRedirect(query ? `/metis/byom?${query}` : '/metis/byom')` from
`next/navigation`, which returns `never`. There is no shell, masthead, or
content region on this route.

- **Header**: _None_ (redirect before render)
- **Main**: _None_
- **Footer / sticky bottom**: _None_

## States

- [x] **Loading** — N/A; the route redirects before any UI renders
- [x] **Empty** — N/A; no content surface
- [x] **Populated (short)** — N/A; no content
- [x] **Populated (long)** — N/A; no content
- [x] **Error (recoverable)** — N/A; static redirect, no data fetch
- [x] **Error (unrecoverable)** — N/A; no data fetch
- [x] **Offline** — N/A; redirect is resolved by Next routing (server/SW level),
      not a runtime fetch
- [x] **Gated** — middleware enforces signed-in before the alias page runs.
      Anonymous `/metis/upload?origin=anonymous` returns a 307 to
      `/welcome?redirect=%2Fmetis%2Fupload%3Forigin%3Danonymous`; BYOM never
      renders.
- [x] **Standalone PWA** — a signed-in standalone launch to
      `/metis/upload?surface=pwa&origin=shortcut` lands on
      `/metis/byom?surface=pwa&origin=shortcut`, keeps standalone display mode,
      and renders without horizontal overflow.

## Interactions

This route has no interactive elements — it redirects on request.

- [x] **Redirect** (proxy `308`, page fallback `permanentRedirect`)
  - Function: signed-in requests issue an HTTP 308 permanent redirect to
    `/metis/byom`; query params are preserved, and the user never sees this
    route's chrome
  - Keyboard / screen reader / touch: N/A (no rendered UI)
  - Telemetry: none observed; verify whether the redirect is counted anywhere

## Data & contracts

- **Reads**: _None._
- **Writes**: _None._
- **Realtime**: _None._
- **Caching**: the proxy-level signed-in alias sends `Cache-Control: no-store`
  with the 308, even though the alias is permanent, so authenticated query
  launches are not cached unexpectedly
- **Auth/role check**: proxy auth applies before the alias redirect; anonymous
  traffic is gated to `/welcome` first
- **Query/hash contract**: query params are preserved by the alias page. Hash
  fragments cannot be preserved because browsers do not send them in HTTP
  requests.

## E2E coverage

Spec:
[`apps/oshun/web/e2e/metis-upload-alias.spec.ts`](../../../apps/oshun/web/e2e/metis-upload-alias.spec.ts)
— real dev-infra coverage for the alias contract:

- signed-in raw request returns `308` with `Location: /metis/byom?...`,
  including repeated query params
- signed-in browser navigation lands on the canonical BYOM surface and renders
  `[data-metis-byom-page]`
- standalone PWA launch through the alias lands on BYOM with launch params and
  no mobile horizontal overflow
- anonymous traffic is intercepted by the proxy with a `307` to `/welcome`
  before the alias page can redirect or render BYOM

## Cross-references

- Feature spec: [`V1/features.md#metis`](../../../V1/features.md#metis)
- Architecture: [`V1/ARCHITECTURE.md#metis`](../../../V1/ARCHITECTURE.md#metis)
- Redirect target / canonical: [`metis-byom.md`](./metis-byom.md)
- Sibling Metis routes: [`metis.md`](./metis.md),
  [`metis-ingest.md`](./metis-ingest.md),
  [`metis-courses-new.md`](./metis-courses-new.md)
- Journeys:
  [`../../journeys/metis-byom-ingest-to-course.md`](../../journeys/metis-byom-ingest-to-course.md)
- Component sources:
  - `apps/oshun/web/src/app/metis/upload/page.tsx`
  - `apps/oshun/web/src/proxy.ts`
- E2E:
  - `apps/oshun/web/e2e/metis-upload-alias.spec.ts`

## Open questions / known gaps

- [x] Confirm the redirect status code and query behavior — signed-in proxy
      requests return 308 and preserve query params. Hash fragments cannot be
      preserved server-side because they are not sent in HTTP requests.
- [x] Confirm interaction with middleware: unauthenticated `/metis/upload`
      redirects to `/welcome?redirect=...` first; it does not reach
      `/metis/byom`.
- [ ] This route is purely a back-compat alias; confirm it is still required for
      V1 or whether the old link sources can be repointed and the route removed
