# Tech debt

This is the structural and correctness debt worth tracking, ordered by priority.
None of it is a reason to stop shipping — but each item is a thing a future
engineer (or the founder in six months) will need to know.

## Priority findings

| # | Finding | Severity | Where |
|---|---|---|---|
| 1 | CORS allows `"null"` origin with credentials | Review before beta | `server.py` CORS middleware |
| 2 | Daily try-on limit is 25 in code, docs say 10 | Reconcile | `DAILY_TRYON_LIMIT` |
| 3 | FLUX.2 routing is effectively dormant | Confirm intent | `determine_tryon_model` |
| 4 | Body-photo dual-write pattern | Consolidate | analyze + upload paths |
| 5 | Silent pipeline failures return 200 | Wire Sentry | try-on pipeline |
| 6 | Session replay on in production builds | Fix before store | `eas.json` env |
| 7 | Two `.ipa` build artifacts committed to git | Untrack | repo root |
| 8 | Marketing bg videos: 84 MB hero, git bloat, third-party IP | Compress / re-host | `Drape-Marketing/public/video` |

## 1. CORS allows the `null` origin

:::warning[Reconcile against P0 rule 7]
The codebase's own P0 security rules state *"do not add `null` to CORS allowed
origins — it permits `file://` requests."* The live CORS `allow_origins` list
**includes `"null"`** alongside `allow_credentials=True`, with an explanatory
comment. This is a direct deviation from the stated invariant and should be
confirmed with the founder: either the rule is wrong, or the code is. With
`allow_credentials=True`, a `null` origin broadens the surface for
credentialed cross-origin requests.
:::

## 2. Daily try-on limit: 25 vs 10

The tech-stack notes describe "10 try-ons/day per user." The code constant
`DAILY_TRYON_LIMIT` is **25**. Neither is wrong to have chosen — but the docs and
code should agree. The code is the source of truth; the docs on this site reflect
**25**.

## 3. FLUX.2 is present but dormant

The documented routing says "baggy / wide-leg → FLUX.2 LoRA," but
`determine_tryon_model` sends baggy garments to **Fashn `tryon-max`** by default.
A `flux2-lora` branch exists, but no default rule selects it — a product must be
explicitly tagged to reach FLUX.2. Decide whether FLUX.2 should be wired back into
default routing or removed from the mental model (and eventually the code path).
See the [try-on pipeline](../architecture/try-on-pipeline.md#model-routing).

## 4. Body-photo dual-write

`analyze-body-photos` and `body-photo/upload` both write overlapping body-photo
fields on the user document (a dual-write with a `TODO` in the code). This is the
kind of overlap that causes "the photo saved but try-on can't find it" bugs.
Consolidating to one writer is deferred but worth doing before scaling past the
current user count.

## 5. Silent pipeline failures

Several steps can return `200 OK` while producing a wrong or missing result:
`rembg` returning `None`, `body_photo_data` never written, Fashn returning a
marketing image, or the analyze pipeline throwing after measurements are written.
`is_valid_render` catches malformed URLs, but semantic failures pass through. These
need Sentry breadcrumbs so they stop being invisible. Detail on the
[try-on pipeline page](../architecture/try-on-pipeline.md#silent-failure-modes).

## 6. Session replay in production

PostHog **session replay** is gated on `EXPO_PUBLIC_APP_ENV === 'beta'`, and
`eas.json` sets that env to `beta` in **both** the preview and production build
profiles. For a biometric app, replay should be disclosed and ideally off for
production end users — body photos rely on component-level masking only. This is a
15-minute fix (flip the production profile env) and also appears on the
[shipping checklist](../shipping/overview.md).

## 7. Repo hygiene

Two compiled `.ipa` build artifacts (~91 MB total) are committed to git;
`.easignore` excludes `*.png` but not `*.ipa`. Untrack them and add `*.ipa` to
`.gitignore`.

## 8. Marketing video assets & delivery

The marketing site's background loops are plain `.mp4` files **committed in the
repo** under `Drape-Marketing/public/video/`, served as static assets by AWS
Amplify → CloudFront. The `MediaLoop` component plays them muted, looping, and
object-cover (poster first, then the video, reduced-motion respected).

**Addressed (2026-08-23):** the `culture.mp4` "BUILT FOR THE CULTURE" loop was a
**Vans "Off The Wall"** ad (third-party trademark on a commercial site). It was
replaced with **PRK Project** partner content and converted from HEVC `.mov` to
web-safe H.264 (Chrome/Firefox can't play HEVC).

**Still open:**

- **`hero.mp4` is ~84 MB (2min 18s).** Absurd for a muted background loop — every
  visitor pulls a huge file. It should be a short (~10–20s), well-compressed loop
  of a few MB.
- **Large binaries live in git.** The 84 MB + 14 MB videos bloat every clone
  forever. Move big media out of git (Git LFS, or host on a CDN bucket).
- **Third-party IP in the hero.** `hero.mp4` carries **Red Bull** branding and a
  **"viemr" stock watermark** — stock/competitor footage on a commercial marketing
  site is a legal risk. Replace with owned or partner-licensed footage.

:::note[Do you need a video service like Bunny.net?]
The delivery layer (CloudFront) is **already a CDN** — that is not the problem;
compression and git bloat are. So a dedicated video host is **not required yet**:

- **Now:** compress the loops and keep them on CloudFront; get big media out of git.
  This fixes most of the issue with no new vendor.
- **If a real video pipeline is wanted** (adaptive HLS so phones get a lighter
  stream, thumbnails, analytics): **consolidate on Mux** — it is already in the
  stack for live streaming (`backend-postgres/helpers/mux_live.py`) and does VOD
  adaptive streaming too. **Bunny.net (Bunny Stream)** is a viable, cheaper
  alternative; the main reason to pick it over Mux would be pricing. Adding Bunny
  means a second video vendor alongside Mux.
:::

- **`server.py` is ~8,900 lines.** This is a deliberate solo-founder tradeoff
  (everything is greppable). It is not a bug, but it raises the cost of onboarding
  anyone new and makes merge conflicts likely if a second engineer joins. If the
  team grows, split by router first, not by rewrite.
- **Stateless auth lookup.** `get_current_user` hits MongoDB on every request. The
  upside is immediate revocation; the downside is a DB read per call. Fine at
  current scale; revisit with caching only if it shows up in profiling.
- **Dormant white-label layer.** `StoreContext`, `stores.tsx`, and `store-admin`
  implement multi-tenant theming with a purple default theme distinct from the
  cream design system. It is largely inactive relative to the main consumer flow —
  document it as "infrastructure, not a live feature" so no one assumes it is wired
  up.
- **Static message screens.** `messages.tsx` and `message-thread.tsx` render mock
  data with no backend — a placeholder, not a feature.
- **Stale audit docs.** Two 2026-05-25 dependency audits under `frontend/docs/` are
  stale and inverted on python-jose; correct or archive them.

:::note[The founder already knows most of this]
The root `CLAUDE.md` tracks a detailed bug list and P0 rules, and most items there
are genuinely fixed in code (the IDOR check, ref-stored AbortControllers,
token-validated-on-load, and the error boundary all verified present). This page
adds the few items that are **not** yet reflected in that list — chiefly the CORS
finding and the doc/code drifts above.
:::
