Full-stack monorepo for an AI-powered virtual fashion try-on product. A Chrome extension detects clothing items on any shopping page, a FastAPI backend manages fitting profiles and job dispatch, and an async Redis worker queue processes try-on image generation end-to-end.
Full Stack Engineer (solo)
AI try-on generation takes 5–30 seconds per image — too slow for a synchronous API response. At the same time, the product needs to work across arbitrary shopping pages (Zara, H&M, ASOS) where product images live behind inconsistent DOM structures. The core engineering challenge was building an async job pipeline (create → queue → process → archive) that decouples the user's request from the slow generation step, while the Chrome extension handles product detection across heterogeneous storefronts without brittle CSS selectors.
Monorepo: Chrome Ext + Web → FastAPI → Redis → Worker → S3 + PostgreSQL
tryl/
├─ apps/
│ ├─ web/ React + TypeScript — auth, fitting profiles, archive
│ ├─ extension/ Chrome MV3 — product detection on shopping pages
│ ├─ api/ FastAPI — REST API for profiles, jobs, archive
│ └─ worker/ Python — async try-on job processor
├─ packages/
│ ├─ shared-types/ TypeScript types shared by web + extension
│ └─ config/ Shared ESLint + TS config
└─ pnpm-workspace.yamlPOST /api/tryon/jobs { profile_id, product_image_url, product_metadata }
│
├─ Validate profile exists + user owns it
├─ Resolve product: download image → store to S3-compatible store
├─ INSERT job { status: "queued", profile_id, product_image_key }
│
└─ redis.lpush("tryon:queue", job_id) ← enqueue
│
▼ [Worker process — separate container]
redis.brpop("tryon:queue") ← blocking pop
│
├─ UPDATE job { status: "processing" }
├─ Fetch fitting profile image + product image
├─ Call try-on AI model API (async httpx)
│ └─ Poll / stream until result image ready
├─ Store result image
├─ UPDATE job { status: "completed", result_image_key }
│
└─ On any error:
UPDATE job { status: "failed", error_message }
GET /api/tryon/jobs/{job_id} → { status, result_image_url? }
GET /api/tryon/archive → paginated completed jobsContent script injected on shopping page load
│
├─ DOM scan: heuristic selectors for product images
│ ├─ meta[property="og:image"] ← most reliable
│ ├─ [data-testid*="product"] img ← framework apps (Next.js)
│ ├─ .pdp-image, .product-image img ← legacy CSS patterns
│ └─ largest visible <img> (fallback)
│
├─ Inject "Try with Tryl" button adjacent to detected image
│
└─ On button click:
├─ Send product_image_url + page_url to background service worker
├─ background → POST /api/tryon/jobs (with auth cookie / token)
├─ Receive job_id → open popup with job status polling
│ └─ GET /api/tryon/jobs/{job_id} every 3 s
└─ On "completed" → display result image in popup overlaybrpop call replaces a sleep-poll loop and ensures FIFO ordering.status column with an updated_at timestamp. Simpler to query, index, and reason about than event-sourcing for an MVP.og:image (set by site owners for sharing) gives a stable, high-quality image with minimal fragility.shared-types) flow directly to both web and extension without a publish step. The trade-off is a slightly more complex CI matrix (build order matters).Manual end-to-end testing across 3 shopping storefronts (Zara, ASOS, H&M). Job pipeline tested with simulated slow worker (10 s artificial delay) to verify status polling behavior.
status = "failed" with the exception message stored in error_message column.