# Findings — 2026-11-03

Each finding carries a verdict from running the same crawl locally and in
production. **Production** means it reproduces on the live site today.
**Both** means the behaviour is identical in both environments. **Dev only**
means it cannot be observed in production and is a local-environment matter.

## Real in production

| # | Surface | Finding | Evidence | Severity |
| --- | --- | --- | --- | --- |
| P1 | Marketing | `drape.to/privacy` and `drape.to/terms` return 404 | `/privacy.html` returns 200, so the files are deployed and the `next.config.ts` rewrite is not applied by the host. Every marketing footer links the clean URLs, and the config comment calls them the App Store Connect canonical URLs. Local serves both. | Critical |
| P2 | Marketing | Product share page loses its image | `/product/<id>` renders the product, but `/_next/image` answers 400 for the `v3b.fal.media` source because `images.remotePatterns` is not configured. Locally the same gap throws a runtime error and the page shows "This page couldn't load". All 24 production products use fal.media or `mfxtjylgcryarajinvbd.supabase.co`. | Needs attention |
| P3 | Admin | Wrong password reloads the login page with no message | The API returns 401 "Invalid admin credentials", but `lib/api.ts` redirects to `/login` on every 401, including the login request, so the page reloads and the email field empties. Reproduced on `admin.localhost` and `admin.drape.to`. The brand portal's client skips the redirect for login and shows "Invalid email or password". | Needs attention |
| P4 | Backend | `GET /api/products/featured` returns 500 | "Internal server error" from `dev-api.drape.to` and `api.drape.to` when called directly. The app's Featured screen shows "No featured drops" rather than an error. | Needs attention |

## Hygiene — same in both environments

| # | Surface | Finding | Evidence | Severity |
| --- | --- | --- | --- | --- |
| H1 | `Drape-` repo | A test account password is committed in four files | `kcb1099@gmail.com` with its password appears in `frontend/docs/CHANGELOG_ENGINEERING.md`, `frontend/docs/PROD_ENV_TEMPLATE.md`, `frontend/docs/ENVIRONMENT_RECOVERY_PLAYBOOK.md` and `frontend/qa/maestro/rc1_gate.yaml`. The credentials no longer work (401 on dev and prod) but they contradict the repo's own secrets policy. | Needs attention |
| H2 | Brand portal, storefront, admin, marketing | No `main` landmark and often no `h1` | 36 pages across the four surfaces have no `main` landmark; around 20 signed-out brand-portal and storefront pages have no `h1`; the marketing and brand `html` elements have no `lang`. Identical counts in production, so it lives in the shared layouts. | Minor |
| H3 | Brand portal (production) | The 404 page has no title and no heading | Production's unknown-route page renders without a `title` or `h1`; the local `develop` build has both ("404"). Production is behind the branch here. | Minor |
| H4 | Marketing, brand portal | Hero images are lazy-loaded | Next's dev server flags the hero image on marketing `/` and `/for-brands` and on the brand landing as the Largest Contentful Paint without `loading="eager"`. The warning only prints in dev; the behaviour ships. | Minor |

## Dev environment only

| # | Where | Finding | Evidence | Note |
| --- | --- | --- | --- | --- |
| D1 | Dev API CORS | Plain `localhost:3004` and `localhost:3005` are blocked | `server.py` allows `localhost` on 3000 to 3003 and any `*.localhost` host. The first brand and admin crawls on plain localhost failed every API call and were discarded; `brand.localhost` and `admin.localhost` work. | `start-drape-local.sh` should open the subdomain hosts |
| D2 | Brand portal | Hydration mismatch warning on `/` | The inline theme script sets `data-theme` before React hydrates. Production React strips the warning, so it is not observable on `brand.drape.to`, but the mismatch is in the code. | `suppressHydrationWarning` on `html` |
| D3 | App, seller billing | "Stripe is not configured on this server" | The committed `deploy/task-def-dev.json` injects no `STRIPE_*` secrets; `task-def-prod.json` injects all seven. | An uncommitted edit to the dev task definition adds them |
| D4 | Dev data | Seed products show the wrong photo and no tenant resolves | Both QA Atelier products (Ink Wide Trousers, Cream Oversized Shirt) show a wakeskate photo. No store on dev has a slug that `/api/tenant/<slug>` resolves, so the storefront, door landing (`/l`) and QR product page (`/p`) could only be captured in their not-found states. Those states match production. | Seed a store with a slug and real images |
| D5 | `frontend/.maestro` | The committed Maestro flows no longer match the app | `01-signup` fails at its second step (asserts a "Create Account" title); the brutal-audit flow expects a login path that moved. A replacement tour flow is in the downloads. | Refresh or replace |

## Mobile app — 47 screens, Release build on the dev API

What held up:

- Signup and login validate empty submits with clear copy ("Please fill in all
  fields.", "Please enter your email and password. Reset password →").
- Onboarding runs welcome → Select your culture → Define your style → Body
  profile, with the biometric consent gate in place.
- Home, The Market, Explore, Featured, Arrivals, Saved, Bag, Scan, Sell, Refer,
  Messages, Orders, Render archive, Look board, Notifications, Profile, Fit
  setup, Body photo, Saved looks, Privacy policy and Privacy & Terms all render.
- The seller hub, orders, messages, analytics (correctly gated to Pro Seller)
  and billing render for a consumer account; brand routes fall back to home.
- Deep links opened without their parameters fall back sensibly: look board
  asks for a username, save-closet says "Missing product info", checkout-return
  shows "Order cancelled", billing-return shows "Could not confirm".
- The Scan screen detects the simulator and offers a manual path.

Not exercised: try-on rendering (spends Fashn credits), marketplace purchase
(Stripe), body-photo upload (needs a photo library), and the brand and seller
signed-in roles.

## Where local and production differ

| Surface | Route | Local | Production |
| --- | --- | --- | --- |
| Marketing | `/privacy`, `/terms` | 200 | 404 |
| Marketing | `/product/<id>` | Runtime error page | Renders, image missing |
| Marketing | `/download` | Heading "DOWNLOAD DRAPE" | Heading "BE FIRST TO TRY DRAPE" (production predates the branch) |
| Brand portal | unknown route | 404 page with title and heading | 404 page with neither |
| Storefront | `/shop` | "Brand not found" (no dev tenant) | CHECKtheFeed storefront renders |
| Storefront | `/` | redirects to `/login` | redirects to `/login` (a shopper on a brand subdomain lands on the portal login) |

## Expected outcomes, not findings

The crawler deliberately submits empty forms, signs in with a non-existent
account, opens `/verify-email`, `/reset-password` and `/accept-invite` with an
invalid token, and requests an unknown route. The resulting 4xx responses and
error states were captured to document the UI, and are listed in the report
under "expected outcomes from deliberate probes". No account was created on
production and no data was written.
