# Consumer app

The shopper experience lives in the `(main)` route group and navigates via a
floating blurred pill nav (`BottomNav`): Home, Explore, a center Preview action,
Messages, and Profile.

## Getting set up

Before a try-on can work, the shopper completes a setup tunnel that also captures
BIPA consent and writes the body photo.

```mermaid
graph LR
    Land["index.tsx<br/>path chooser"] --> Signup["(auth)/signup"]
    Signup --> Onboard["onboarding.tsx<br/>taste + consent gate 1"]
    Onboard --> Fit["fit-setup.tsx<br/>measurements + consent gate 2"]
    Fit --> Body["body-photo.tsx<br/>standalone capture + consent gate 3"]
    Fit --> Home["home.tsx"]
```

| Screen | What it does |
|---|---|
| `onboarding.tsx` | Taste onboarding (welcome → culture → style) with the **first BIPA consent gate**; writes gender/aesthetic/culture via `updateProfile` |
| `fit-setup.tsx` | 5-step fit profile: gender → height (wheel) → weight (wheel, optional) → body photo → results. `runAnalysis()` posts to `/api/analyze-body-photos`, then writes `user_measurements` to AsyncStorage as bare keys. 120s timeout + error alert |
| `body-photo.tsx` | Standalone body-photo capture/upload with direct camera/gallery pickers (guidance modals were removed to fix a picker loop) |
| `profile.tsx` | View/edit the fit profile, body photo, measurement wheels, unit toggle, logout, and **Delete Account** |

Consent is enforced three times on the frontend and again at the backend (403 if
not consented). See [security](../architecture/security.md).

## Browsing

| Screen | What it does |
|---|---|
| `home.tsx` | Personalized feed — wordmark header, hero, category pills, a two-column `ProductCard` grid, pull-to-refresh, and an error state with retry |
| `catalog.tsx` | Browse / search / filter grid; merges backend products with local sample data; a search modal |
| `product/[id].tsx` | The product page — color/size selectors, info tabs, similar products, and the **Try It On** button that opens the try-on screen |

## The try-on render

`try-on/[productId].tsx` is the core screen (and its viewport is
[frozen](../architecture/frontend.md)). It:

1. Fetches the body photo from `GET /api/body-photo` and only proceeds if the
   server confirms one exists — it never reuses a cached local photo, so a
   previous user's photo can't leak into a render.
2. Posts to `/api/try-on` (the body photo is **not** in the request — it is
   fetched server-side), then either returns a cached result immediately or polls
   `` `/api/try-on/status/{jobId}` `` every 3 seconds up to a 180-second timeout.
3. Shows the `DrapeRenderLoader` during the wait, then a BEFORE/AFTER view, a fit
   score, a color re-render option, and save / share / buy actions.

Errors are bucketed into friendly copy (rate-limit, timeout, abort, generic), and
the poll's `AbortController` is stored in a `useRef` and cancelled on unmount.

## Saving & history

| Screen | What it does |
|---|---|
| `save-closet.tsx` | Post-try-on save confirmation |
| `saved.tsx` | Saved items / wishlist |
| `preview.tsx` | Grid of the shopper's generated renders |
| `history.tsx` | Try-on history, with a `pending_history_writes` retry queue for offline writes |
| `look-board.tsx` | A grid of saved "looks" for a product or store |

## Commerce & extras

| Screen | What it does |
|---|---|
| `orders.tsx` | Consumer order history |
| `checkout-return.tsx` (top-level) | Handles the Stripe checkout return, verifying the order by `session_id` |
| `scan.tsx` | Camera QR/barcode scanner that resolves to a product or store |
| `sell.tsx` | A peer-to-peer listing flow (category / condition) |
| `refer.tsx` | Referral program — code, share sheet, stats |
| `messages.tsx` / `message-thread.tsx` | Inbox and thread — currently **static/mock** data, no backend wiring |
